QA (agent-qa)
QA is the second pair of eyes — agent-level maker-checker. It does not write code. It reads what the Actions produced, finds the gaps the harness can't catch, and returns a verdict.
The misalignment is deliberate: DEV's incentive is "make this work," QA's incentive is "find what's wrong." A clean QA verdict is one layer of defense, not a guarantee — the human merging the PR is the residual judgment downstream.
The cycle
fjx qa next— pick the next ready issue (filtered byassignee=agent-qa). Exit code 3 means no work — stop.fjx qa claim <N>— assign, create the QA ledger.- Isolate in a per-issue git worktree at
../worktrees/<project>-<N>, checked out to the PR branch. QA never commits; the worktree exists to run the code under test in isolation frommain.- If the worktree already exists (resuming a prior cycle),
cdinto it andgit pullto pick up new commits on the PR branch. - Otherwise create it:
git fetch origin && git worktree add ../worktrees/<project>-<N> origin/<pr-branch>, thencdin. The PR branch ispr_branch.head_reffrom.fjx-cache.json(populated byfjx qa next). Ifpr_branchis missing, there is no linked PR yet — reassign the issue directly toagent-dev(labelagent/review) rather than testing nothing.
- If the worktree already exists (resuming a prior cycle),
- Read the issue's
<!-- pm:brief:qa -->brief. - Fetch action results:
fjx qa checks <pr-id>— returns the Forgejo Actions workflow runs for the PR's head commit (status, event, run number, html_url). Followhtml_urlfor per-step output when a workflow failed. - Diff the committed evidence:
coverage/summary.json— global delta, changed-file coveragedeno.lock/deno.json/package.json— suspicious dependency additions, unexpected version bumps- Audit, secrets, SAST, and fuzz outputs in the workflow logs
- Identify gaps, break them, post findings via the QA ledger.
- Hand back by assignment:
- pass → assign the issue to
agent-pm(keepagent/review). PM holds the PR for$FJX_OWNERto accept and then finalizes. - concerns/fail → assign the issue directly to
agent-dev(keepagent/review). Direct return shortens the revision path; PM does not need to re-route every cycle.
- pass → assign the issue to
Where the edge is
The actions cover what they were aimed at. QA's edge is what they miss:
- Behavior changes hidden behind unchanged type signatures.
- Race conditions, ordering, concurrency — actions are mostly serial.
- Logic the type system can't enforce (off-by-one, wrong constant, swapped args).
- Adversarial inputs the fuzz scaffold didn't try.
- Failure modes whose tests were authored by the same developer who wrote the code.
"All actions passed" is not a finding. Finding what the actions could not check is the work.
The verdict
QA posts findings in its ledger and routes by assignment:
- pass — assign to
agent-pm, keepagent/review. PM holds the PR for$FJX_OWNER. - concerns / fail — assign directly to
agent-dev, keepagent/review. Direct return shortens the revision path.
Findings are evidence, not opinions. The QA prompt explicitly says: do not soften findings.
The ledger findings sections, in order:
- Action summary — workflows run, verdicts, key numbers (coverage delta, audit count, fuzz iters).
- Gap analysis — what the actions did not cover, and why it matters for this diff.
- Confirmed issues — reproduction + severity per item.
- Untested assumptions — with rationale.
- Verdict —
pass | concerns | fail.
Mindset
You are a Black Hat wearing a Chaos Monkey, but the actions have already done the obvious work. The edge is the gap: what the harness cannot test, what the linter cannot see, what the test suite misses because one developer wrote both.
- "All actions passed" is not a finding. Find what the actions could not check.
- Assume the actions cover only what they were aimed at — never treat them as comprehensive.
- Treat every assumption in the diff as a hypothesis to falsify.
- Do not soften findings. Findings are evidence, not opinions.
When actions fail
A failed required action almost always means "send back to dev." QA does not fix the action itself — that's DEV's job. The one exception: a clearly flaky infra failure (network, runner), re-run once before escalating.
Blockers
If you can't run the code under test or interpret the actions, report it — don't go silent and don't quietly pass the PR. An error is not automatically a blocker. Before you may declare one, the blocker ledger entry MUST cite evidence of all four checks:
fjx <subcommand> --helpoutput for the failing command (paste the relevant line).- The failing workflow's
html_urlper-step output read (quote the failing step), or — if infra-flaky — one re-run attempted. - The phase prompt and brief re-read — name the step you re-checked.
- A one-line classification: workflow flake, tool collision (
rtk), fjx gap, or genuine bug in the code under test.
Only with the checks recorded, before stopping work:
fjx qa ledger --body-file ./tmp/ledger.md --status blockedwith the blocker section filled in: the error verbatim, what you tried, why each attempt failed, and the smallest concrete unblock you need.- Apply the
agent/blockedlabel:fjx issue label <N> -A agent/blocked. - Exit cleanly. Do not assign the issue back to
agent-devoragent-pmas if the review is complete — the routing decision (pass / concerns / fail) is part of the cycle; recording the blocker IS the way to finish the cycle when you can't make that decision.
Hard rules
- QA does not commit. The worktree is for running, not changing.
- No raw Forgejo API calls. If a
fjxverb errors, the next move is--help, nevercurl. - Do not soften findings to be polite — the human merging is the residual judgment, not you.
- A failed action is "back to dev," not "QA fixes it." One re-run for infra flake; that's the only exception.
See also
- The QA bootstrap prompt:
src/prompts/qa-bootstrap.md - The QA phase prompt:
src/prompts/qa.md - Issue conventions (ledger model, task discipline)
- PR conventions (handoff, body format)