Issue Handler — End-to-End Orchestrator
This is the high-level scenario skill for handling a single GitHub issue. It does not do the detailed work itself; it sequences the leaf skills into one iterative pipeline and reports the result. Each stage's mechanics live in its own skill — read and follow that skill when you reach its stage.
Every agent-produced diff is a proposal. This skill commits the
staged fix onto a dedicated local branch agent/fix-issue-<N> (single
bug) or agent/fix-issue-<N>-<seq>-<slug> (per batch sub-item) and
records it in a fix_result*.json. It never
pushes, tags, or opens a PR — the invoking workflow reads each
fix_result*.json, exports base_sha..branch as a patch, and a human
applies it. The leaves themselves only stage (they never commit).
Contents
- Pipeline overview
- Inputs
- Execution modes
- Stage 0: Re-run gate
- Stage 1: Triage
- Stage 2: Reproduce
- Stage 1u: Batch-issue fan-out (skip-list + heterogeneous)
- Stage 3: Root cause
- Stage 4: Implement
- Stage 5: Verify
- Stage 6: Report
- Iterative loop bounds
- Issue-body status contract
Pipeline overview
Single-bug path (default for issue_type=single-bug):
triage → reproduce → root-cause → implement → verify → report
↑ |
└─── loop up to 3 times ───┘
Batch path (issue_type=batch-bug, either batch_kind) — fan out the
single-bug pipeline per sub-item, each on its own fix branch:
triage → [preflight: install nightly wheel once]
→ for each sub-item:
reset + reproduce
├─ NOT_REPRODUCED → heterogeneous: ALREADY_FIXED · skip-list: STALE_SKIP (+follow-up)
└─ REPRODUCED → branch agent/fix-issue-<N>-<seq>-<slug>
→ root-cause → implement → verify
(any failure marks the sub-item and continues the batch)
→ fan-out report
skip-list vs heterogeneous differ only in how a NOT_REPRODUCED
sub-item is labeled; the loop is identical.
| Stage | Leaf skill | Purpose |
|---|---|---|
| 1. Triage | issue-triage |
Text-only classification: single-bug / batch-bug (+ batch_kind) / nonbug, scope, runtime_dependencies, preliminary verdict |
| 2. Reproduce | fix-reproduce |
Verify the failure still reproduces against the nightly wheel (stage=nightly) |
| 3. Root cause | fix-root-cause |
Deep source analysis, target_repo, domain, IMPLEMENTING/NEEDS_HUMAN |
| 4. Implement | fix-implement |
Edit code, stage the diff (never commit) |
| 5. Verify | fix-verify |
Run the refined command against source build, PASSED/FAILED/CANNOT_VERIFY |
| 6. Report | this skill | Summarize outcome to the user (or into the issue in pipeline mode) |
Inputs
- A GitHub issue on
intel/torch-xpu-opsorpytorch/pytorch(URL, number, or raw body). pytorch_dir— path to a local pytorch checkout, resolved as described infix-reproducePrepare. If absent, this skill letsfix-reproduce/fix-root-causeclone it into$XPU_OPS_ROOT/agent_space_xpu/pytorch/.- Mode (see below).
Execution modes
The pipeline runs in one of two modes — interactive (default) or pipeline — which changes how every stage reports results and whether it writes to the GitHub issue. Decide the mode at the start and pass it to every leaf. See references/execution-modes.md for the full contract.
- Interactive (default): ask the user when blocked; report conversationally; do not touch the issue body / labels / comments unless the user asks.
- Pipeline (explicit): no human to interrupt — advance the
issue's
agent:statusmarker, update stage labels, let leaf skills leave their<!-- agent:<name> -->comments, and stop when the pipeline settles on a terminal verdict.
Stage 0 — Re-run gate (first-run vs re-run)
Pipeline mode only; skip in interactive mode (a human is already
driving, so just run the full pipeline). An agent:active issue is
frequently re-triggered — a maintainer leaves feedback, or the bot is
re-invoked with no new information. This gate decides, before spending
a build, whether this is a fresh run, a human-feedback re-run
(highest priority), or a bare re-run (skip work that already ran and
is still valid).
Step 0.1: Detect prior agent activity
Find the most recent agent comment and its timestamp:
last_agent_ts=$(gh issue view "$N" --repo "$OWNER/$REPO" --json comments \
--jq '[.comments[] | select(.body | test("<!-- agent:"))] | last | .createdAt // ""')
Empty last_agent_ts → first run. Skip the rest of Stage 0 and go
to Stage 1 normally.
Step 0.2: Detect new human feedback
A comment is human feedback when it is authored by a non-bot account
and created after last_agent_ts. (The bot account is the one that
authored the <!-- agent:* --> comments; exclude it by login.)
new_human=$(gh issue view "$N" --repo "$OWNER/$REPO" --json comments \
--jq --arg ts "$last_agent_ts" --arg bot "$BOT_LOGIN" \
'[.comments[] | select(.author.login != $bot and .createdAt > $ts)] | length')
new_human > 0→ human-feedback re-run. Human feedback is the highest priority signal. Read every such comment verbatim and prepend it to the failure description you hand to Stage 3 (fix-root-causetakes a free-form failure description; a leading "Maintainer feedback since last run: ..." block steers the re-analysis without any new leaf input). Run the full pipeline from Stage 1; do not take any of the skip fast-paths below. A human saying "still wrong" or "change X" overrides any cached verdict.new_human == 0→ bare re-run. Continue to Step 0.3.
Step 0.3: Bare re-run — skip what is still valid
No human pointed anything out since the last agent comment, so re-doing the whole pipeline would just repeat identical work. Re-run only the cheap front of the pipeline and compare against last time:
- Run Stage 1 (triage) and Stage 2 (reproduce) as normal.
- Compare the reproduce result to the previous run. The previous
refined_command+ verdict are recoverable from the last<!-- agent:root-cause -->comment'sanalyzed_shacontext, or re-derived by reading the prior<!-- agent:reproduce -->/ sweep comment. "Identical" means same verdict and samerefined_command.- Reproduce differs (now passes, or a different command reproduces) → the situation changed on its own; resume the full pipeline from Stage 3 (root-cause) with the new reproduce result. Do not reuse the cached root-cause.
- Reproduce identical → nothing observable changed. Hand off to
Stage 3, which runs
fix-root-cause's own<!-- agent:root-cause -->analyzed_shafast-path: iftarget_repoHEAD sha equals the recordedanalyzed_sha, that leaf re-emits the prior verdict verbatim and this orchestrator stops (the earlier outcome — fix already staged, orNEEDS_HUMAN— still stands; there is nothing new to do). If the sha moved,fix-root-causere-analyzes and the pipeline continues from Stage 3 as usual.
This never skips Stage 1/Stage 2 — they are cheap (text + nightly wheel) and are the only way to notice the failure went away. It only avoids the expensive Stage 3-5 rebuild+fix when both the observable failure and the analyzed code are unchanged.
Stage 1 — Triage (issue-triage)
Call issue-triage on the issue body + comments. It emits
issue_type (single-bug / batch-bug / nonbug), batch_kind
(skip-list / heterogeneous / null), reproduction_missing
(yes / no), scope, runtime_dependencies, and a preliminary
handling (agent-fixable / needs-human).
Branch on issue_type (triage already made the batch-vs-nonbug call;
no re-detection here):
nonbug→ stop the fix pipeline; skip to Stage 6 Report withSKIPPED(reason=nonbug).single-bugwithreproduction_missing=yes→ stop; Stage 6 Report withNEEDS_HUMAN(reason=reproduction_missing).issue-triage's own comment already asks the reporter for a reproducer.single-bugwithreproduction_missing=no→ continue to Stage 2.batch-bug→ Stage 1u, passing throughbatch_kind(the loop uses it only to label aNOT_REPRODUCEDsub-item).
Stage 2 — Reproduce (fix-reproduce)
Only for the single-bug path (issue_type=single-bug). Call
fix-reproduce with:
reproducer_command— extracted byissue-triagefrom the issue body.stage=nightly— reproduce against the nightly wheel.ci_repo— inferred from repo (torch-xpu-opsfor issues on intel/torch-xpu-ops,pytorchfor pytorch/pytorch), or the value the bot passes explicitly.
Branch on its verdict:
REPRODUCED→ continue to Stage 3. Record therefined_commandandbasefor downstream stages.NOT_REPRODUCED→ the issue is stale; Stage 6 Report withSKIPPED(reason=no_longer_reproduces).NO_REPRODUCER→ Stage 6 Report withNEEDS_HUMAN(reason=no_reproducer).CANNOT_VERIFY→ Stage 6 Report withNEEDS_HUMAN(reason=cannot_verify)and theblockerfield.
Stage 1u — Batch-issue fan-out
One loop for every parent issue that tracks multiple children. The parent lists several sub-items; run the single-bug pipeline on each independently, each on its own fix branch, and report every outcome back on the parent. "Fix what you can" — a sub-item that can't be fixed is marked and the batch continues.
Entered for issue_type=batch-bug. issue-triage already set
batch_kind; the two kinds share the entire loop and differ only in how
a NOT_REPRODUCED sub-item is labeled (see step 2 below):
heterogeneous— the parent body lists distinct sub-bugs. ANOT_REPRODUCEDentry means that bug is ALREADY_FIXED (no longer reproduces); nothing to do.skip-list— aBug Skipissue listing homogeneous already-skipped tests. ANOT_REPRODUCEDentry means the test now passes, so its skip decorator is now stale and should be removed (follow-up).
No child GitHub issues are created. Each sub-item is a checklist entry on the parent; its fix lives on a dedicated branch so a human can open one PR per fixed sub-item.
Extract sub-items
Parse the parent body into a list of sub-items. Each is either:
- an inline sub-bug — a checklist line naming a test node id or
reproducer (skip-list entries are always this form; normalize a bare
Class::methodto a node id,fix-reproduce's Prepare step resolves the file), or - a linked child reference
owner/repo#N— fetch that issue and use its body as the sub-item's failure description. Only followintel/torch-xpu-opsandpytorch/pytorchreferences; ignore any other repo (untrusted, perfix-root-cause).
A sub-item may also arrive as a plain node id from the caller (a nightly
report, an email, a log excerpt): normalize Class::method to a node id,
drop blank lines, headers and comments, and collapse exact duplicates.
Split infra failures out before the loop. A runner crash, a docker
pull timeout, a full disk or a missing device is not a test failure and
will not reproduce. Record these as NEEDS_HUMAN(infra) and report
them; they never enter the loop.
Give each sub-item two identifiers used for branch naming:
seq— 1-based position in the body checklist order. Readable and maps a branch back to its body line. Not the stable identity (editing or reordering the body changes it).slug— the stable identity, derived from the sub-item's test node id: take the leaf method name, strip device suffixes (_cpu/_xpu/_cuda/_meta) and any dtype suffix, lowercase, keep[a-z0-9._-], truncate to 40 chars. If two sub-items produce the same slug, append-2,-3, … in body order so every slug is unique within the issue.
Branch name is agent/fix-issue-<N>-<seq>-<slug> (N = parent issue
number). On a re-run, match a sub-item to its prior branch by slug —
look for an existing agent/fix-issue-<N>-*-<slug> (any seq); if found,
that is the same sub-item (rename to the current seq if it moved, never
create a duplicate). Skip headers, prose, and empty lines.
Preflight (many entries): install nightly wheel once
When the list is long (skip-list issues routinely have dozens),
front-load the one real wheel install so the per-entry
fix-reproduce(stage=nightly) calls each find the env already current
and return fast. fix-reproduce always issues pip install --pre --upgrade (it refuses to trust a stale wheel); running it once here
does the real work. There is no skip_wheel_install flag — this is
purely an ordering optimization:
pip3 install --pre --upgrade torch torchvision torchaudio \
--index-url https://download.pytorch.org/whl/nightly/xpu
python -c "import torch; print('nightly:', torch.__version__)"
One fix may cover several sub-items
Entries in a batch are often the same bug in the same class. Fixing each one separately runs root-cause and a source build (1-3 h) per entry and produces the same patch several times.
Do not guess which entries share a fix:
- Order the
REPRODUCEDentries so likely relatives sit together (same test class or file, same error). Ordering only; it decides nothing. - Fix the first entry.
- Re-run the other entries in that group against the staged fix. The
build is already done, so this costs one test run each.
fix-verifyalready produces a before/after table; extend it to the group. - Entries that now pass go in the fix's
coverslist and are not fixed again. Entries that still fail take their own turn from step 2.
One fix gets one branch and one fix_result.json, naming in covers
every entry it also fixed.
Per-sub-item loop
Capture the two base SHAs once, then for each sub-item reset both checkouts per the shared reset-between-entries recipe — a prior sub-item's staged diff must not bleed into the next.
For each sub-item:
Reset both checkouts to the base SHAs (shared recipe).
Reproduce (
fix-reproduce). It detaches the pytorch tree to its base (origin/mainor theci_commitfallback) and returns thatbaseplus arefined_command.stagefollows the issue, likeallow_skip:- CI break (
pytorch-ci-failure, or a mirrored DISABLED test) →stage=auto. Many CI failures only reproduce on a source build.stage=nightlyreturnsNOT_REPRODUCEDas soon as the nightly wheel passes and does not fall through, which records unfixed work as already fixed. - Everything else →
stage=nightly.
Branch on the verdict:
REPRODUCED→ continue to step 3; keep itsbaseandrefined_command.NOT_REPRODUCED→ nothing to fix; record it and go to the next sub-item. The label depends onbatch_kind:heterogeneous→ ALREADY_FIXED: the reported bug no longer reproduces on latest nightly (upstream fixed it, or it was flaky). No action needed.skip-list→ STALE_SKIP: the test now passes, so its skip decorator is obsolete. Record a follow-up to remove the decorator — the orchestrator does not delete it here (see "Stale skips" below).
NO_REPRODUCER→ INVALID_ENTRY (renamed/removed test, or malformed). Record, continue.CANNOT_VERIFY→ UNVERIFIED (environmental). Record, continue.
- CI break (
Root-cause, then branch. Run Stage 3 (
fix-root-cause) first — it returnstarget_repo, which decides which checkout the fix (and thus the branch) lives in:target_repo=pytorch→target_repo_dir = pytorch_dir, base is the reproducebaseon the pytorch tree.target_repo=torch-xpu-ops→target_repo_dir = pytorch_dir/third_party/torch-xpu-ops, base isxpu_ops_base(the submodule's pinned commit from the shared recipe).fix-implement/fix-verifyhandle thexpu.txtpin rewrite internally; the orchestrator only creates the branch.
Create the isolated fix branch on
target_repo_diroff its base so the diff is pushable on its own:git -C "$target_repo_dir" checkout -B "agent/fix-issue-${N}-${seq}-${slug}" "$base"Then run Stage 4 → 5 (same contract and 3-attempt bound as the single-bug path).
fix-implementexpects exactly this: a fresh branch the orchestrator just created, clean worktree.On any leaf
NEEDS_HUMAN/CANNOT_VERIFY/FAILED(or attempts exhausted): mark this sub-item blocked with the reason, record it, and continue to the next sub-item — never abort the whole batch on one hard sub-item.On
fix-verifyPASSED: do NOT push or open a PR. Update the sub-item'sfix_result-${slug}.jsonin place (written atverdict=PENDING_VERIFYwhen the branch was committed) toverdict=PASSEDwithfix_repo_dir,branch,base_sha(the sub-item's base from step 3),changed_files, so the workflow can exportbase_sha..branchper sub-item. As in the single-bug path, commit the branch and write thePENDING_VERIFYrecord as soon asfix-implementreturnsREADY, not only on PASSED, so a crash mid-sub-item leaves a recoverable record instead of an orphan branch.
Stale skips (batch_kind=skip-list only)
A STALE_SKIP sub-item's skip decorator can be removed, but this
orchestrator does not delete it. Deleting a skip is itself a code
change that needs its own verify + PR; folding it into this sweep would
mix "the skip is obsolete" with "and here's the removal diff" and
obscure the batch outcome. Instead, surface every STALE_SKIP in the
report as an explicit follow-up (candidate for a human, or a separately
invoked fix-implement run to remove the decorator). The report is the
hand-off; nothing is auto-removed.
Fan-out report
Post one summary comment on the parent (or surface to the user in interactive mode), and mirror it into the parent's checklist:
<!-- agent:batch-fanout -->
## Batch fan-out results
Base: <torch nightly version or base sha>
| Sub-item | Outcome | Branch / Reason |
|---|---|---|
| test_bar_xpu_float32 | FIXED | agent/fix-issue-4321-1-test_bar |
| test_baz | NEEDS_HUMAN | cross_repo_coordinated |
| test_qux | NEEDS_HUMAN | attempts_exhausted |
| test_new | ALREADY_FIXED | no longer reproduces on latest nightly |
| test_old | STALE_SKIP | follow-up: remove skip decorator |
| test_dup | COVERED | by test_bar's fix, agent/fix-issue-4321-1-test_bar |
| test_hard | SKIPPED | skip added, tracking issue intel/torch-xpu-ops#1234 |
| test_gone | INVALID_ENTRY | does not collect |
- **FIXED:** N sub-items — one branch each, ready for a human to open
a PR.
- **NEEDS_HUMAN:** M sub-items — see per-sub-item reason.
- **ALREADY_FIXED:** J sub-items (heterogeneous only) — no longer
reproduce; the reported bug is resolved, no action needed.
- **STALE_SKIP:** K sub-items (skip-list only) — the test now passes;
follow up to remove the obsolete skip decorator.
- **INVALID_ENTRY / UNVERIFIED:** P sub-items — malformed/renamed, or
environmental during reproduce.
*Automated by issue-handler.*
Omit category rows that have no members (a heterogeneous batch has
ALREADY_FIXED but never STALE_SKIP, and vice versa). The
<!-- agent:batch-fanout --> marker lets a
re-run locate and update this same comment in place. On a re-run
(Stage 0), only re-process sub-items that are not already FIXED on a
live branch, unless human feedback (Stage 0.2) reopens a specific one.
After the loop, go to Stage 6 Report with the aggregate outcome
(IMPLEMENTING(fix_verified) if any sub-item was fixed;
NEEDS_HUMAN only if every actionable sub-item needed a human —
STALE_SKIP follow-ups do not by themselves force NEEDS_HUMAN).
Machine-readable outputs
Written on both paths: the single-bug Stage 4/5 writes
fix_result.json, and a batch fan-out writes batch_summary.json plus
one fix_result-<slug>.json per fixed sub-item. The
<!-- agent:batch-fanout --> comment is for humans; these files let the
invoking bot workflow export patches and drive re-verification without
re-parsing a comment.
They live under $AGENT_SPACE (the gitignored scratch dir). In pipeline
mode the bot workflow always sets it. In interactive mode it is usually
unset, which would resolve to /fix_result.json — so fall back to
<repo>/agent_space_xpu/ (the same gitignored dir AGENTS.md defines) and
report the path you used:
agent_space="${AGENT_SPACE:-$(git -C "$target_repo_dir" rev-parse --show-toplevel)/agent_space_xpu}"
mkdir -p "$agent_space"
batch_summary.json— one file listing every sub-item:{ "issue": 4321, "kind": "batch-bug", "batch_kind": "heterogeneous", "sub_items": [ { "seq": 1, "slug": "test_bar", "outcome": "FIXED", "branch": "agent/fix-issue-4321-1-test_bar", "target_repo": "torch-xpu-ops", "fix_result": "fix_result-test_bar.json", "summary": "one-line what/why" }, { "seq": 2, "slug": "test_baz", "outcome": "NEEDS_HUMAN", "branch": null, "reason": "cross_repo_coordinated" }, { "seq": 3, "slug": "test_new", "outcome": "ALREADY_FIXED", "branch": null, "reason": "no longer reproduces on latest nightly" } ] }fix_result-<slug>.json— for eachFIXEDsub-item, the same schema the single-bug Stage 5 writes asfix_result.json. It MUST include the keys the workflow's schema gate and patch-export read:verdict—PASSED/FAILED/CANNOT_VERIFY/PENDING_VERIFY. Written incrementally: Stage 4 writesPENDING_VERIFYwhen it commits the branch, Stage 5 updates it to the terminal verdict. OnlyPASSEDis exported as a normal patch; a non-PASSED verdict that still names a reachablebranch+base_shais salvaged as anunverified/patch so the committed work is not lost with the runner. Keepbranchandbase_shaaccurate even on a non-PASSED record.target_repo—torch-xpu-ops|pytorch,fix_repo_dir— absolute path of the git repo holding the fix commit,branch—agent/fix-issue-<N>-<seq>-<slug>(single bug:agent/fix-issue-<N>),base_sha— the commit the fix branch was started from,changed_files— list of the files the fix touched, plusneeds_build,refined_command,notes. Suffixed by slug so each sub-item can be exported / re-verified independently.
The patch-export step iterates every fix_result*.json and emits one
patch series per unit from base_sha..branch (the branches are not
pushed); batch_summary.json enriches it (target_repo, summary line).
A unit that reports a verified fix but yields no patch fails the step.
Stage 3 — Root cause (fix-root-cause)
Called on the single-bug path, and once per sub-item from Stage 1u.
Call fix-root-cause with the failure description and the
refined_command from Stage 2.
Branch on its verdict:
IMPLEMENTING(reason=ok)→ recordtarget_repo,domain,analyzed_sha,root_cause,fix_strategy, then create the fix branch before Stage 4 —target_repois what decides which checkout it lives in, andfix-implementexpects a fresh branch with a clean worktree ($baseis Stage 2's reproduce base; the batch path already did this in its own step 3, with its own branch name):git -C "$target_repo_dir" checkout -B "agent/fix-issue-${N}" "$base"NEEDS_HUMAN→ Stage 6 Report with the specificreason(task_or_feature/feature_gap/hardware_specific/cross_repo_coordinated/no_registered_domain/ etc.). Each reason maps to a different finalagent:statusvalue; see execution-modes.md.
Stage 4 — Implement (fix-implement)
Call fix-implement with triage_result, pytorch_dir,
target_repo_dir (derived from target_repo), and allow_skip:
allow_skip follows the issue:
- CI break — the issue carries the
pytorch-ci-failurelabel, or mirrors a DISABLED test from pytorch/pytorch.allow_skip=true. Fix it in place where you can, in pytorch or here. Skip only when the fix needs a dependency, information you do not have, or feature-sized work;fix-implementthen files a tracking issue for the real fix. - Everything else —
allow_skip=false. Never add a skip decorator.
If the caller states the flag, the caller wins.
Branch on the verdict:
READY(reason=ok)→ commit the staged fix onto the branch created in Stage 3, and write the incrementalfix_result.json, then continue to Stage 5. Do NOT wait for Stage 5 to persist — if verify then crashes or the context runs out, the committed branch would otherwise be an orphan the Export step flags as a lost fix. Committing here keeps the branch and the hand-off record in lock-step:git -C "$target_repo_dir" commit -m "fix: <one-line summary> (#${N})"Then write
fix_result.json(see "Machine-readable outputs" for the path and its interactive-mode fallback) with the fields known so far andverdict=PENDING_VERIFY:target_repo,fix_repo_dir,branch,base_sha,changed_files,analyzed_sha,root_cause,notes. The branch is local and dies with the runner, so this record is what lets the workflow salvage the commit as anunverified/patch if verification never finishes — without it, a crash after this point loses the work outright.NEEDS_HUMAN→ Stage 6 Report. The specificreason(skip_outside_target_repo/skip_guard_rejected/no_fix_possible/ etc.) drives the final label.
Stage 5 — Verify (fix-verify)
Call fix-verify with refined_command (from Stage 2),
target_repo_dir, and changed_files (from Stage 4). fix-verify
unconditionally produces the FAIL->PASS before/after table and runs
spin fixlint on a passing result — no flags to pass.
Branch on the verdict:
PASSED(reason=ok)→ the fix is verified. The branch andfix_result.jsonalready exist from Stage 4; update the record in place — setverdict=PASSEDand addneeds_build, the before/after result, and any finalnotes— then go to Stage 6 withIMPLEMENTING(fix_verified).fix-verifyleaves itsspin fixlintfixes staged: amend them into the Stage 4 commit (git commit --amend --no-edit) so they reach the exported patch — never a second commit. The workflow exportsbase_sha..branchas the patch. Never push or open a PR.FAILED→ loop back to Stage 4 with the failure output as additional context (see "Iterative loop bounds"). On a retry the branch already carries the previous attempt's commit, so amend it (git commit --amend --no-editafter staging the new edits) rather than creating a second commit or a new branch —base_sha..branchmust stay a single reviewable fix, and Stage 5's clean-worktree assertion requires nothing left uncommitted. If attempts are exhausted, updatefix_result.jsontoverdict=FAILEDwith the last failure innotesso the record explains the branch, then Stage 6 ReportNEEDS_HUMAN(reason=attempts_exhausted).CANNOT_VERIFY→ updatefix_result.jsontoverdict=CANNOT_VERIFYwith the blocker innotes, then Stage 6 ReportNEEDS_HUMAN(reason=<verify's reason>). Do not loop on CANNOT_VERIFY — the environment problem will not fix itself.
Stage 6 — Report
At the end, summarize the outcome. In interactive mode present
this to the user in plain language. In pipeline mode advance
the issue's agent:status to the terminal stage
(DONE / NEEDS_HUMAN / SKIPPED) and update the checklist per
execution-modes.md; a batch fan-out
run also posts the <!-- agent:batch-fanout --> summary.
Always include in the summary:
- Issue: link/number and one-line title.
- Path:
single-bug/batch(withbatch_kind). - Outcome:
IMPLEMENTING(fix_verified)/NEEDS_HUMAN(<reason>)/SKIPPED(<reason>). - Root cause (from Stage 3, if reached).
- Files changed (from Stage 4, if reached).
- Verification (from Stage 5, if reached).
- For a batch: per-sub-item outcome + branch name (FIXED) or reason
(NEEDS_HUMAN / ALREADY_FIXED / INVALID_ENTRY / UNVERIFIED), plus any
STALE_SKIPfollow-ups whenbatch_kind=skip-list.
If the outcome is IMPLEMENTING(fix_verified), the fix is committed on
its agent/fix-issue-<N> branch and recorded in fix_result.json. The
invoking workflow reads that, exports base_sha..branch as a patch
artifact, and a human applies it. Do not push or open the PR from this
skill.
Review request block
When the job ends, the fix workflow appends a template to the session
comment for a human to review the fix. Do not write it yourself:
**strong-accepted** <!-- review: strong-accepted -->
**weak-accepted** <!-- review: weak-accepted -->
**weak-rejected** <!-- review: weak-rejected -->
**strong-rejected** <!-- review: strong-rejected -->
notes:
A reviewer copies it into a new comment, keeps one line, and says why.
Iterative loop bounds
The pipeline is not strictly linear. Loop when a later stage invalidates an earlier assumption:
- Stage 5
FAILED→ return to Stage 4 (refine the fix). - Stage 4's Step 3.5 skip-guard rejects → the leaf itself re-runs
once; a second rejection returns
NEEDS_HUMANand the orchestrator does not retry.
Bound: maximum 3 fix attempts (Stage 4 → Stage 5 → Stage 4 …).
This matches the legacy pipeline's max_agent_attempts. When you
stop without success, report NEEDS_HUMAN(reason=attempts_exhausted)
with the last fix-verify failure output in reason_detail.
Do not loop on:
CANNOT_VERIFYat any stage (environment problem, not fix problem).NEEDS_HUMANfrom any leaf (contract: the leaf already decided it needs a human).- Stage 3
no_registered_domain(domain registry is a fixed set, looping won't unstick it).
Issue-body status contract
Pipeline mode only. In interactive mode, do not touch the issue body/markers/labels unless the user asks — report to the user instead.
This orchestrator owns advancing the overall <!-- agent:status:X -->
marker through:
DISCOVERED → TRIAGING → REPRODUCING → TRIAGED → IMPLEMENTING →
VERIFYING → DONE
with terminal alternates NEEDS_HUMAN and SKIPPED. Stage-by-stage
mapping to labels is in
references/execution-modes.md; each
leaf skill owns its own <!-- agent:<name> --> comment/log slot,
this orchestrator owns the overall agent:status marker + the
Action Items checklist.