| name | wr-itil:work-problems |
| description | Batch-work ITIL problem tickets while the user is AFK. Loops through the problem backlog by WSJF priority, delegating each problem to wr-itil:manage-problem, and stops when nothing is left to progress. Use this skill whenever the user says things like "work through my problems", "grind problems", "work the backlog", "work problems while I'm away", "process problems AFK", or any request to autonomously work through multiple problem tickets without interactive input. Also trigger when the user asks to "loop" or "batch" problem work, or says they'll be away and wants problems handled. |
| allowed-tools | Agent, Skill, Bash, Glob, Grep, Read |
Work Problems — AFK Batch Orchestrator
Autonomously loop through ITIL problem tickets by WSJF priority, working each one via wr-itil:manage-problem, until nothing actionable remains.
The user is AFK during this process, so every decision point that would normally require interactive input should be resolved automatically using safe defaults. The skill reports progress between iterations so the user can review what happened when they return.
How It Works
Each iteration is one cycle of: scan backlog, pick highest-WSJF problem, work it, report result. The loop continues until a stop condition is met.
First-run intake-scaffold pointer (P065 / ADR-036)
This skill is one of the two host skills wired to surface the /wr-itil:scaffold-intake skill on first invocation in a project that has not yet adopted the OSS intake surface. The contract is documented in ADR-036 (Scaffold downstream OSS intake — skill + layered triggers).
Preamble check (run once at session start, before Step 0 of the loop):
- Look for the four intake paths:
.github/ISSUE_TEMPLATE/config.yml, .github/ISSUE_TEMPLATE/problem-report.yml, SECURITY.md, SUPPORT.md, CONTRIBUTING.md.
- Look for
.claude/.intake-scaffold-declined (explicit decline marker — never re-prompt).
- Look for
.claude/.intake-scaffold-done (done marker — already scaffolded).
If any intake file is missing AND both markers are absent: this skill is always invoked from an AFK orchestrator context (per the skill's allowed-tools and persona). The Rule 6 fail-safe applies unconditionally:
- Do not fire
AskUserQuestion.
- Do not auto-scaffold.
- Append a one-line
"pending intake scaffold" note to the iteration's ITERATION_SUMMARY notes field. The note is a per-iteration audit trail signal — accumulating one line per AFK iter is acceptable per ADR-036 § Bad consequences and JTBD-006 "audit trail — every action taken during AFK mode should be traceable".
The user reviews the pending note on their next interactive session and runs /wr-itil:scaffold-intake (or /wr-itil:manage-problem with the foreground prompt branch) at that point. JTBD-006 forbids the agent from making this judgement call autonomously.
Step 0: Preflight (per ADR-019)
Before opening the work loop, get the repo into a clean state so the orchestrator does not iterate against a stale backlog, silently strand prior-session in-flight work, or proceed past an ambiguously-dirty tree (P040, P109, P293). ADR-019 names three branches under the umbrella goal:
- Branch 1 — Pull: origin moved; trivial fast-forward divergence. Action:
git pull --ff-only non-interactively (the existing fetch/divergence path below).
- Branch 2 — Commit: pre-existing uncommitted work that belongs in a commit (prior AFK iter hit quota / cancel / crash mid-ticket). Auto-commit when both discriminator conditions hold: (a) provenance is unambiguous (attributable to the prior iter's own in-flight flow) AND (b) risk is within appetite per ADR-018. Deferred — current implementation routes Branch 2 → Branch 3: the auto-commit mechanism + gate-composition wiring + bats are not yet shipped. Pre-existing uncommitted source edits demote to Branch 3 (halt-with-report) until the follow-up lands.
- Branch 3 — AskUserQuestion / AFK-halt: genuinely messy tree (ambiguous uncommitted state, non-fast-forward divergence, partial-prior-session work whose provenance is unclear). Interactive:
AskUserQuestion per ADR-013 Rule 1 (four-option report: Resume / Discard / Leave-and-lower-priority / Halt). AFK: halt with structured Prior-Session State report — a deliberate carve-out from the 2026-06-06 ADR-013 Rule 6 queue-and-continue default (ambiguous session-continuity state requires user input; non-interactive recovery would mask the bug this preflight is meant to surface).
The Branch 1 fetch/divergence table below is the live implementation of Branch 1. The session-continuity detection pass after it is Branch 3's detection mechanism — it enumerates the signals that populate the Prior-Session State report when Branch 3 fires.
Branch 1 mechanism:
- Run
git fetch origin.
- Compare local
HEAD with origin/<base> (default main; otherwise the branch the user is on).
- Branch on the divergence shape:
| Local vs origin | Action |
|---|
| HEAD at or ahead of origin/ | Proceed to Step 1 |
| origin/ ahead, local has no unpushed commits (pure fast-forward) | Run git pull --ff-only non-interactively. Log the count of pulled commits in the AFK iteration log. Proceed to Step 1. |
| origin/ ahead, local has unpushed commits (non-fast-forward) | STOP the loop (Branch 3 routing — non-fast-forward divergence is a "genuinely messy" signal). Report the divergence with git log --oneline HEAD..origin/<base> and git log --oneline origin/<base>..HEAD. Do NOT attempt to rebase or merge non-interactively — that is a judgment call the persona forbids in AFK mode. |
Network failure: if git fetch origin returns a network error, stop and report. Default behaviour is fail-closed — the user can retry when network is restored.
Non-interactive authorisation: per ADR-013 Rule 6, git fetch origin and git pull --ff-only are policy-authorised actions (no semantic merge, no destructive overwrite). git pull --rebase, git merge, and any operation that resolves conflicts are NOT policy-authorised — they require user input.
Cross-cutting: this rule applies to every AFK orchestrator skill. The next-ID collision guard (ADR-019 confirmation criterion 2) belongs in the ticket-creator skills (manage-problem and wr-architect:create-adr), not here — see the related problem ticket for that work.
Branch 3 detection mechanism — session-continuity signal enumeration (per P109)
After the Branch 1 fetch/divergence check, Step 0 MUST run the session-continuity detection pass that populates Branch 3's signal set (and, when the Branch 2 follow-up lands, feeds the Branch 2 / Branch 3 discriminator). The Branch 1 check handles "did origin move under us"; this pass handles the distinct failure mode "did the prior session leave partial work that changes what iter 1 should do". A prior AFK subprocess can exit mid-ticket (quota 429, user-cancel, subprocess crash) and leave observable state in the working tree that the orchestrator must classify before opening the work loop.
Signals to enumerate (each maps to one git status --porcelain / filesystem / git worktree probe):
| Signal | Detection |
|---|
Untracked docs/decisions/*.proposed.md | git status --porcelain docs/decisions/ filtered for ?? entries ending .proposed.md — drafted but unlanded ADRs from a prior iter. |
Untracked docs/problems/*.md | git status --porcelain docs/problems/ filtered for ?? entries ending .md — drafted but unlanded problem tickets. |
.afk-run-state/iter-*.json error markers | Files under .afk-run-state/ containing "is_error": true OR "api_error_status" >= 400 AND fresh per the staleness filter — file mtime is newer than HEAD's commit time (git log -1 --format=%at HEAD) OR within the last 24h, whichever is more permissive. Stale residuals (mtime older than HEAD's commit time AND older than 24h) are skipped silently — they represent prior-session partial work whose load-bearing trace has since been verified/landed via a subsequent commit, and the directional asymmetry of the contract is fresh = halt, stale = silent skip (P333; closes the indefinite false-positive halt where e.g. an iter-4-p246.json from 2026-05-18 was still firing the gate on 2026-05-30 despite P246 having been verified-closed on a subsequent session). Success files ("is_error": false) are ignored regardless of freshness. When ≥1 stale iter-error-marker is silently skipped, emit a one-line iter-summary annotation per JTBD-006 audit-trail outcome: Step 0: N stale iter-error-markers skipped (oldest: iter-X-pNNN.json, age: D days). Run \ls .afk-run-state/iter-*.json` to inspect.` — preserves traceability of the skip action at near-zero cost and gives a recovery path if a stale-skipped marker was actually load-bearing. Contract source: ADR-032 subprocess artefact + P333 staleness refinement. |
Stale .claude/worktrees/* dirs + matching claude/* branches | git worktree list filtered on claude/* branches adjacent to .claude/worktrees/* directories — prior subagent worktrees that were not cleaned up. Detection only — mutation (cleanup) is out of scope and requires a separate ADR. |
| Uncommitted modifications to SKILL.md / source / ADR files | git status --porcelain filtered for M / M entries on packages/*/skills/*/SKILL.md, packages/*/hooks/*, docs/decisions/*.proposed.md, or other source paths the prior session was mid-authoring. |
Classification: when any signal is present, build a structured Prior-Session State report listing each hit (signal category, path, one-line summary). An empty signal set means clean pass-through to Step 1.
Routing on interactive-vs-AFK (per ADR-013 Rule 1 / Rule 6):
- Interactive (
AskUserQuestion is available AND the loop was not started in AFK mode): prompt the user with the Prior-Session State report and four options — Resume the prior work (land the drafted files as iter 1), Discard the draft and restart from scratch, Leave-and-lower-priority (skip the dirty paths and work the next backlog item that doesn't touch them), Halt the loop (too much dirty state to proceed non-interactively). Route the chosen branch before opening Step 1.
- Non-interactive / AFK (default for this skill per JTBD-006): do NOT call
AskUserQuestion. Halt the loop with the structured Prior-Session State report in the AFK summary. Per ADR-013 Rule 6 fail-safe: ambiguous session-continuity state requires user input; non-interactive recovery would mask the bug this check is meant to surface. This matches Step 6.75's "dirty for unknown reason → halt" stance at the Step 0 layer — the orchestrator does not silently proceed past partial work.
Step 2.5b cross-reference (P126): before emitting the final AFK summary for a Step 0 session-continuity halt, run Step 2.5b's surfacing routine. The routine is gated on ≥1 accumulated user-answerable skip; at Step 0 no iters have run yet so the gate is normally empty and Step 2.5b returns immediately, but the cross-reference is named here for contract uniformity — every halt path that emits a final summary routes through Step 2.5b regardless of whether the gating clause is empty in the typical case (halt-paths-must-route-design-questions-through-Step-2.5b).
Network failure halt (Step 0 fetch failure): if git fetch origin returns a network error, the loop halts and reports per the rule above. Before emitting the final AFK summary for a network-failure halt, run Step 2.5b's surfacing routine — same Step 2.5b cross-reference as the session-continuity halt. The gating clause is normally empty at Step 0 (no iters have run), but the cross-reference is named here for contract uniformity (halt-paths-must-route-design-questions-through-Step-2.5b).
Step 6.75 treats a Step-0-resolved-with-user-confirmation state as dirty-for-known-reason: if the interactive branch's Resume option landed the drafted ADR as iter 1, the iter's commit clears the dirty state and the rest of the loop proceeds normally.
README reconciliation preflight (per P118)
After the session-continuity detection pass, Step 0 MUST run the diagnose-only README reconciliation check. The orchestrator reads docs/problems/README.md's WSJF Rankings table to pick the highest-WSJF actionable ticket (Step 3); if that table lies about which tickets are open vs verifying vs closed, the orchestrator burns iterations on no-op tickets — exactly the failure class P118 captures (a prior session committed a ticket transition without staging the README refresh, and no subsequent session systematically reconciled).
wr-itil-reconcile-readme docs/problems
The wr-itil-reconcile-readme command is a $PATH-resolved shim shipped in packages/itil/bin/ that dispatches the canonical packages/itil/scripts/reconcile-readme.sh body. ADR-049 — never invoke the canonical script via repo-relative path; the path does not resolve in adopter trees.
Exit-code routing:
- Exit 0 (clean): continue to Step 1.
- Exit 1 (drift detected): structured diff lines printed to stdout, one per drift entry (≤150 bytes per ADR-038 progressive-disclosure budget). Capture stdout to a temp file and classify the drift via the uncommitted-rename carve-out (P149) before halt-routing — see "Drift classification carve-out" immediately below.
- Exit 2 (parse error): README missing or malformed. Halt the loop with the parse-error message and the structured Prior-Session State report — this is a deeper repair that needs investigation, not mechanical reconciliation.
Drift classification carve-out (P149)
The Exit 1 auto-route to /wr-itil:reconcile-readme is correct for committed cross-session drift but wrong for uncommitted-rename-rooted drift — when a prior AFK iter (or any in-flight session) carries a staged ticket rename that the next iteration's in-flow P094 / P062 refresh will reconcile in the upcoming commit per ADR-014's single-commit grain. Auto-routing in the latter case fires an extra chore(problems): reconcile README ... commit and splits one logical change across two commits, violating the grain. Worse for the AFK orchestrator: that extra commit lands BEFORE the iter's actual work commit, so the audit trail reads "reconcile, then ticket work" when the truth is "ticket work in progress, README refresh deferred to its in-flow contract".
Run the classifier on Exit 1 to distinguish the two cases:
wr-itil-reconcile-readme docs/problems > /tmp/wr-itil-drift-$$.txt
reconcile_exit=$?
if [ "$reconcile_exit" -eq 1 ]; then
wr-itil-classify-readme-drift /tmp/wr-itil-drift-$$.txt docs/problems
classify_exit=$?
rm -f /tmp/wr-itil-drift-$$.txt
fi
The wr-itil-classify-readme-drift command is a $PATH-resolved shim (ADR-049 naming grammar) dispatching packages/itil/scripts/classify-readme-drift.sh. It cross-references drifting IDs from the script's stdout against git status --porcelain docs/problems/ filtered for staged rename (R) entries.
Classifier exit-code routing:
classify_exit == 0 (INLINE_REFRESH): every drifting ID is the destination of a staged rename in the working tree. Log a one-line note in the iter summary ("Step 0 reconcile drift covered by N staged rename(s); deferring README refresh to in-flow Step 5 / Step 7 per P094 / P062 + ADR-014 single-commit grain") and continue to Step 1. Do NOT invoke /wr-itil:reconcile-readme — the in-flow refresh will land the README correction in the same commit as the iter's ticket work.
classify_exit == 1 (HALT_ROUTE_RECONCILE): at least one drifting ID is NOT covered by a staged rename — committed cross-session drift OR mixed. Per ADR-013 Rule 6 (non-interactive AFK fail-safe), invoke /wr-itil:reconcile-readme to apply the corrections + commit a chore(problems): reconcile README ... commit, then proceed to Step 1. The reconciled README is the orchestrator's source of truth for Step 3 ranking — a stale read at Step 1 would propagate the lie into the iteration's selection. Mixed routes to halt because /wr-itil:reconcile-readme resolves both classes safely; the in-flow refresh only handles the rename'd subset.
classify_exit == 2 (parse error): classifier received empty / missing drift input — contract violation upstream. Fall back to the conservative auto-route.
This is a robustness layer ON TOP of P094 + P062, not a supersession — both per-operation contracts remain in force inside each iteration's manage-problem / transition-problem invocation.
Step 0a: Auto-migrate adopter layout (P170 / RFC-002 / ADR-031)
After Step 0's fetch/divergence preflight and the README reconciliation block but before Step 1's backlog scan, source the shared shell migration routine and call the idempotent entrypoint:
wr-itil-migrate-problems-layout "$PWD"
wr-itil-migrate-problems-layout is the ADR-049 $PATH shim (adopter-safe — resolves lib/migrate-problems-layout.sh relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline source packages/itil/lib/migrate-problems-layout.sh; migrate_problems_to_per_state_layout "$PWD". NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
The routine is idempotent and partial-migration-safe. It no-ops when no flat-layout files (docs/problems/*.<state>.md at the top level of docs/problems/) are detected — the common case post-Slice-5 T5a in this monorepo and in freshly-migrated adopter repos.
Closes the Step 1 false-zero defect (per ADR-031 § Backward Compatibility line 126 "Why both skills"): Step 1 enumerates BEFORE delegating to manage-problem. On a flat-layout adopter repo, the post-ADR-031 Step 1 glob would return zero matches at the per-state shape and stop-condition #1 would fire incorrectly — the orchestrator would exit with a false "nothing to do" signal, never reaching manage-problem's Step 0a auto-migrate. Wiring auto-migrate here at Step 0a is structurally required, not an optimisation.
On a flat-layout adopter repo (first invocation post-update — JTBD-101 plugin-developer auto-migration path), the routine:
- Creates the five state subdirectories under
docs/problems/.
- Runs
git mv to relocate every existing ticket from flat to per-state subdir.
- Emits a standalone commit with subject
docs(problems): auto-migrate to per-state subdirectory layout (ADR-031) and footer trailer RISK_BYPASS: adr-031-migration (recognised by the commit-gate hook per T11).
AFK authorisation per ADR-013 Rule 6: this fires unconditionally even in AFK / non-interactive / orchestrated mode. Pure-rename + pure-mkdir + standalone-commit actions are policy-authorised under ADR-019 precedent — fully reversible (git revert), no external-comms surface, no destructive overwrite. No AskUserQuestion gate.
First-fire signal: the routine emits a single stderr line on the migrating invocation; silent on no-op re-invocations.
After Step 0a completes (whether no-op or migration), proceed to Step 0b's inbound-discovery pre-flight check. The dual-tolerant glob at Step 1 (RFC-002 transitional window) continues to match both layouts; post-T6 (single-pattern collapse), Step 1 will tighten to per-state only and the migration commit ensures the adopter tree matches.
Step 0b: Upstream inbound-discovery pre-flight (per ADR-062 § JTBD-006 driver)
After Step 0a's auto-migrate and before Step 1's backlog scan, check whether the upstream inbound-discovery cache is fresh. ADR-062 § Decision Drivers names /wr-itil:work-problems as the surface that should keep inbound reports visible during AFK loops; the TTL self-healing branch inside /wr-itil:review-problems Step 4.5b only fires if review-problems is entered. This step closes that gap by pre-flighting /wr-itil:review-problems when the cache is stale or missing.
Mechanism:
preflight_reason="$(wr-itil-check-upstream-cache-staleness "$PWD")"
wr-itil-check-upstream-cache-staleness is the ADR-049 $PATH shim (adopter-safe — resolves lib/check-upstream-cache-staleness.sh relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline source ...; should_promote_inbound_discovery_preflight "$PWD" and echoes the result. NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
The helper returns one of five outcomes (contract documented at packages/itil/lib/check-upstream-cache-staleness.sh + asserted by packages/itil/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats):
preflight_reason | Action |
|---|
no-channels-config | Silent-pass. Downstream-adopter non-obligation per ADR-062 § Downstream-adopter contract. Proceed to Step 1. |
first-run-cache-absent | Dispatch /wr-itil:review-problems as a pre-flight iter via the standard claude -p subprocess wrapper (same shape as Step 5; see Step 5 for the subprocess invocation contract). |
first-run-last-checked-null | Same as first-run-cache-absent — cache schema present but never populated. |
ttl-expiry age=<N>s ttl=<M>s | Dispatch /wr-itil:review-problems as a pre-flight iter. Cache is stale; review-problems' Step 4.5b's TTL-expiry auto-recheck branch fires inside the dispatched subprocess and refreshes the cache + audit-log + README. |
fresh-within-ttl | Silent-pass per ADR-013 Rule 5 + P132 mechanical-stage carve-out. Proceed to Step 1. |
Pre-flight dispatch shape: when promoted, dispatch a single claude -p --permission-mode bypassPermissions --output-format json subprocess that invokes /wr-itil:review-problems (per P084 + ADR-032 subprocess isolation). The subprocess runs the full Step 4.5 inbound-discovery + assessment pipeline; the cache + docs/audits/inbound-discovery-log.md + docs/problems/README.md are refreshed in its own commit per ADR-014 (review-problems' Slice E commit grain). After the subprocess completes, the orchestrator reads the freshly-refreshed README at Step 1. If the pre-flight subprocess exits non-zero OR returns is_error: true, apply the non-blocking revert-and-proceed contract in "Step 0 pre-flight subprocess failure handling (P358)" below — do NOT halt the loop (a failed pre-flight is a non-load-bearing cache-refresh dependency, NOT an iter).
Iter-summary annotation:
- Channels-config absent:
Step 0b skipped — no upstream-channels.json (downstream-adopter non-obligation).
- Cache fresh:
Step 0b skipped — upstream inbound-discovery cache fresh within TTL.
- Pre-flight ran:
Step 0b pre-flighted /wr-itil:review-problems — reason=<preflight_reason>, <N> reports discovered, <M> local tickets created.
The annotation pre-empts the "surprise heavy iter" perception JTBD-006 expects auditability for — a maintainer running multiple short AFK loops within a 24h window will hit fresh-within-ttl on subsequent invocations and see the cache-fresh annotation, confirming the system's silent-pass discipline rather than wondering whether the check ran at all.
AFK authorisation per ADR-013 Rule 6: review-problems' Step 4.5 pipeline is itself AFK-safe — branch decisions are mechanical per P132 / ADR-044 category 4 silent framework action; external-comms gates on verdict/acknowledgement/pushback comments silent-pass on low-risk verdicts per ADR-028 + the wr-risk-scorer:external-comms subagent's "policy-authorised drafts proceed silently" contract (packages/risk-scorer/agents/external-comms.md § PASS Output); gate-denial sub-branches fail-soft and retry on the next discovery pass. No new user-attention surface introduced at the Step 0b promotion point.
Compose-with: ADR-014 (review-problems' Slice E commit grain holds — the pre-flight subprocess emits its own commit; orchestrator-main-turn does not commit Step 0b), ADR-013 Rule 5/6 (silent-pass + AFK fail-safe — both honored), P084 + P077 (subprocess isolation reuse — same claude -p wrapper as Step 5), ADR-019 (preflight surface — Step 0b is the natural extension of "reconcile state before opening the loop"), P132 (mechanical-stage carve-out — no AskUserQuestion at the promotion point). Mid-loop ticket creation by Step 4.5e's safe-and-valid branch enters the WSJF queue Step 1 reads on the same invocation — natural absorption, no deadlock; the pre-flight commit lands before Step 1's README read.
Staleness contract drift: the staleness comparison MUST stay symmetric with /wr-itil:review-problems Step 4.5b's branches (first-run / TTL-expiry / cache-fresh). Drift here re-opens the inbound-discovery staleness contract — any change to TTL semantics MUST update both this Step 0b helper and review-problems Step 4.5b in the same commit.
After Step 0b completes (whether dispatched or silent-passed), proceed to Step 0c.
Step 0c: Deferred-placeholder + README-cadence pre-flight (per P271)
After Step 0b's inbound-discovery pre-flight and before Step 1's backlog scan, check whether the deferred-placeholder backlog has accumulated past threshold AND the docs/problems/README.md "Last reviewed" cadence has slipped. This step closes the load-bearing gap P271 names: /wr-itil:capture-problem leaves deferred-placeholder Priority + Effort lines that /wr-itil:review-problems is the only authoritative re-rate path for; without an auto-fire trigger, placeholders accumulate silently across sessions (76 → 83 evidenced on the 2026-05-24 work-problems session) and the orchestrator dispatches iters against stale WSJF rankings.
Mechanism:
preflight_reason="$(wr-itil-check-deferred-placeholder-staleness "$PWD")"
wr-itil-check-deferred-placeholder-staleness is the ADR-049 + ADR-080 $PATH shim (adopter-safe — resolves lib/check-deferred-placeholder-staleness.sh relative to the script, NOT cwd; P317/RFC-009) that internalises should_promote_review_problems_dispatch "$PWD" and echoes the result. NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
The helper returns one of five outcomes (contract documented at packages/itil/lib/check-deferred-placeholder-staleness.sh + asserted by packages/itil/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats):
preflight_reason | Action |
|---|
no-deferred-placeholders | Silent-pass per ADR-013 Rule 5 + P132 mechanical-stage carve-out. Proceed to Step 1. |
below-threshold count=<N> threshold=3 | Silent-pass — there is work to re-rate but not enough to be worth a heavyweight pass. Proceed to Step 1. |
no-readme count=<N> | Dispatch /wr-itil:review-problems as a pre-flight iter via the standard claude -p subprocess wrapper (same shape as Step 0b / Step 5). README absent OR malformed line 3 → first-run dispatch. |
fresh-readme count=<N> age=<X>s threshold=<Y>s | Silent-pass per ADR-013 Rule 5 — the cadence is in spec; today's captures are tomorrow's review. |
stale-readme count=<N> age=<X>s threshold=<Y>s | Dispatch /wr-itil:review-problems as a pre-flight iter. Both axes met — there is work AND the cadence has slipped. |
Two-axis AND rule (load-bearing per architect verdict on the P271 fix shape). Both axes — count ≥ 3 AND README age > 7 days — must hold. Either axis alone over-fires:
- Count ≥ 3 alone fires on a backlog where review-problems was run yesterday and 3 captures came in today (that's the in-spec deferred-placeholder behaviour, not a staleness signal).
- Age > 7 days alone fires on quiet weeks where no captures occurred and there is nothing to re-rate.
The intersection is the actual signal: "there is work to do AND the cadence has slipped".
Pre-flight dispatch shape: when promoted (no-readme or stale-readme), dispatch a single claude -p --permission-mode bypassPermissions --output-format json subprocess that invokes /wr-itil:review-problems (per P084 + ADR-032 subprocess isolation). Reuse the Step 5 subprocess wrapper verbatim — same flag set, same idle-timeout SIGTERM poll loop, same retro-on-exit contract. The subprocess runs the full Step 2 + Step 2.5 + Step 4 + Step 5 re-rate + README refresh + commit; the orchestrator reads the freshly-refreshed README at Step 1. If the pre-flight subprocess exits non-zero OR returns is_error: true, apply the non-blocking revert-and-proceed contract in "Step 0 pre-flight subprocess failure handling (P358)" below — do NOT halt the loop (a failed pre-flight is a non-load-bearing cache-refresh dependency, NOT an iter).
ADR-079 composition note: Step 0c dispatches /wr-itil:review-problems which includes Step 4.6 relevance-close per ADR-079 — relevance-close fires as a side-effect of the auto-dispatch. This is desirable: relevance closes accumulate the same way deferred placeholders do, and the AND-trigger reasonably gates both pieces of work.
Iter-summary annotation:
- No placeholders / below threshold:
Step 0c skipped — <N> deferred placeholders below threshold (3).
- Fresh README cadence:
Step 0c skipped — README cadence fresh (age=<X>s within 7-day window).
- Pre-flight ran:
Step 0c pre-flighted /wr-itil:review-problems — reason=<preflight_reason>, <N> placeholders re-rated, <M> tickets auto-transitioned, <K> tickets relevance-closed.
The annotation pre-empts the "surprise heavy iter" perception JTBD-006 expects auditability for — a maintainer running multiple short AFK loops with fresh-cache will see the silent-pass annotation, confirming the system's silent-pass discipline rather than wondering whether the check ran at all.
AFK authorisation per ADR-013 Rule 6: review-problems is itself AFK-safe — branch decisions are mechanical per P132 / ADR-044 category 4 silent framework action; Step 4 verification prompts skip silently when AskUserQuestion is unavailable per the review-problems Step 4 AFK branch. No new user-attention surface introduced at the Step 0c promotion point.
Compose-with: ADR-013 Rule 5/6 (silent-pass + AFK fail-safe), ADR-044 category 4 (silent-framework — the trigger is policy + observable evidence), ADR-014 (review-problems' commit grain holds — the pre-flight subprocess emits its own commit), ADR-049 / ADR-080 (PATH shim grammar + highest-version-wins wrapper), ADR-062 § Step 0b (precedent staleness-pre-flight shape), ADR-079 § Step 4.6 (relevance-close composition), P084 + P077 (subprocess isolation reuse — same claude -p wrapper as Step 5), P132 (mechanical-stage carve-out — no AskUserQuestion at the promotion point), P170 / RFC-002 (dual-tolerant glob — the helper handles both layouts), P317 / RFC-009 (adopter-safe PATH shim).
Staleness contract drift: the two-axis trigger (count ≥ 3 AND age > 7 days) MUST stay symmetric across the four SKILL surfaces that read it — this Step 0c, /wr-itil:manage-problem Step 0.5 (advisory), /wr-itil:capture-problem Step 7 (conditional trailing pointer), AND the helper's threshold constants. Drift here re-opens P271.
After Step 0c completes (whether dispatched or silent-passed), proceed to Step 0d.
Step 0d: Outbound upstream-responses pre-flight (per JTBD-006 AFK driver + JTBD-004 cross-repo coordination)
After Step 0c's deferred-placeholder pre-flight and before Step 1's backlog scan, check whether the outbound-responses cache is fresh. P249 Phase 1 shipped /wr-itil:check-upstream-responses as a manual skill (the outbound symmetric counterpart to Step 0b's inbound pipeline); P220 names the cadence gap that without an auto-fire trigger, upstream responses to issues we filed via /wr-itil:report-upstream go unread until the maintainer remembers to invoke the skill. This step closes that gap with the same pre-flight shape Step 0b uses for the inbound axis.
Mechanism:
preflight_reason="$(wr-itil-check-outbound-responses-staleness "$PWD")"
wr-itil-check-outbound-responses-staleness is the ADR-049 + ADR-080 $PATH shim (adopter-safe — resolves lib/check-outbound-responses-staleness.sh relative to the script, NOT cwd; P317/RFC-009) that internalises should_promote_outbound_responses_preflight "$PWD" and echoes the result. NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
The helper returns one of five outcomes (contract documented at packages/itil/lib/check-outbound-responses-staleness.sh + asserted by packages/itil/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats):
preflight_reason | Action |
|---|
no-back-link-tickets | Silent-pass. No local tickets carry a ## Reported Upstream section; nothing to poll. Downstream-adopter non-obligation analogue to Step 0b's no-channels-config. Proceed to Step 1. |
first-run-cache-absent | Dispatch /wr-itil:check-upstream-responses as a pre-flight iter via the standard claude -p subprocess wrapper (same shape as Step 0b / Step 0c / Step 5). |
first-run-last-checked-null | Same as first-run-cache-absent — cache schema present but never populated. |
ttl-expiry age=<N>s ttl=<M>s | Dispatch /wr-itil:check-upstream-responses as a pre-flight iter. Cache stale; the skill polls each back-linked upstream URL, diffs against the cache, and emits STATE / NEW / LABEL / NONE / FAIL per back-link ticket. |
fresh-within-ttl | Silent-pass per ADR-013 Rule 5 + P132 mechanical-stage carve-out. Proceed to Step 1. |
Pre-flight dispatch shape: when promoted, dispatch a single claude -p --permission-mode bypassPermissions --output-format json subprocess that invokes /wr-itil:check-upstream-responses (per P084 + ADR-032 subprocess isolation). Reuse the Step 5 subprocess wrapper verbatim — same flag set, same idle-timeout SIGTERM poll loop. The subprocess runs the full check-upstream-responses Step 1 + Step 2 + Step 3 pipeline; the cache file docs/problems/.outbound-responses-cache.json + audit-log docs/audits/outbound-responses-log.md are refreshed in its own commit per ADR-014 (check-upstream-responses' SKILL.md Step 3 commit grain). After the subprocess completes, the orchestrator proceeds to Step 1. If the pre-flight subprocess exits non-zero OR returns is_error: true, apply the non-blocking revert-and-proceed contract in "Step 0 pre-flight subprocess failure handling (P358)" below — do NOT halt the loop (a failed pre-flight is a non-load-bearing cache-refresh dependency, NOT an iter).
Iter-summary annotation:
- No back-link tickets:
Step 0d skipped — no tickets carry ## Reported Upstream (downstream-adopter non-obligation).
- Cache fresh:
Step 0d skipped — outbound-responses cache fresh within TTL.
- Pre-flight ran:
Step 0d pre-flighted /wr-itil:check-upstream-responses — reason=<preflight_reason>, <N> back-link tickets polled, <M> STATE/NEW deltas surfaced.
The annotation pre-empts the "surprise heavy iter" perception JTBD-006 expects auditability for — a maintainer running multiple short AFK loops within a 24h window will hit fresh-within-ttl on subsequent invocations and see the cache-fresh annotation, confirming the system's silent-pass discipline rather than wondering whether the check ran at all.
AFK authorisation per ADR-013 Rule 6: check-upstream-responses is itself AFK-safe by construction — read-only externally (gh issue view only; no gh issue comment / gh issue create), so does NOT trip ADR-028's external-comms gate; zero AskUserQuestion calls (flag-based knobs per CLAUDE.md P085); partial-failure exit code 2 distinguishes "some upstream URLs unreachable" from "everything broke" so AFK orchestrators can branch correctly. No new user-attention surface introduced at the Step 0d promotion point.
Compose-with: ADR-013 Rule 5/6 (silent-pass + AFK fail-safe), ADR-044 category 4 (silent-framework — the trigger is policy + observable evidence), ADR-014 (check-upstream-responses' commit grain holds — the pre-flight subprocess emits its own commit), ADR-024 (back-link ## Reported Upstream section is the source-of-truth scanned by the helper and read by the dispatched skill), ADR-049 / ADR-080 (PATH shim grammar + highest-version-wins wrapper), ADR-062 § Step 0b (precedent staleness-pre-flight shape — Step 0d is the outbound symmetric counterpart), P084 + P077 (subprocess isolation reuse — same claude -p wrapper as Step 5), P132 (mechanical-stage carve-out — no AskUserQuestion at the promotion point), P170 / RFC-002 (dual-tolerant glob — the helper handles both layouts), P317 / RFC-009 (adopter-safe PATH shim), P249 Phase 1 (the manual skill this step wires into a cadence).
Staleness contract drift: the staleness comparison MUST stay symmetric with the check-upstream-responses SKILL's Confirmation surface (TTL semantics + outcome shape). Drift here re-opens the outbound-responses staleness contract — any change to TTL semantics MUST update this Step 0d, the lib helper, AND the check-upstream-responses SKILL.md Confirmation section in the same commit.
After Step 0d completes (whether dispatched or silent-passed), proceed to the shared pre-flight failure-handling contract below, then to Step 0e, then to Step 1.
Step 0 pre-flight subprocess failure handling (P358 — non-blocking revert-and-proceed)
Step 0b / Step 0c / Step 0d (and any future Step 0x pre-flight that reuses the Step 5 claude -p subprocess wrapper) dispatch a /wr-itil:review-problems or /wr-itil:check-upstream-responses pre-flight subprocess "same shape as Step 5". That phrase imports the Step 5 dispatch mechanism (the claude -p --output-format json wrapper + the idle-timeout SIGTERM poll loop), but the failure semantics are NOT shared — and the prior prose left this implicit, which P358 surfaced. Step 5's exit-code semantics HALT the loop on non-zero exit / is_error: true because the iter IS the loop body unit — its failure is the loop's failure. A pre-flight is a non-load-bearing cache-refresh dependency, not an iteration of the loop body: Step 1's backlog scan reads whatever docs/problems/README.md already exists (freshly-refreshed or slightly-stale), so a failed pre-flight degrades to "cache not refreshed this pass" — never to "halt the loop".
Contract — a pre-flight subprocess that exits non-zero OR returns is_error: true is NON-BLOCKING (general rule; every Step 0x pre-flight inherits it):
- Revert any dirty working-tree state the failed pre-flight left. The dispatched skill commits its own refresh per ADR-014 (review-problems' Slice E grain / check-upstream-responses' Step 3 grain) — it commits end-to-end or not at all. A subprocess that died mid-refresh may leave an UNSTAGED partial write across any path the dispatched skill is contractually allowed to touch: the staleness cache (
docs/problems/.upstream-cache.json for 0b, docs/problems/.outbound-responses-cache.json for 0d), the audit log (docs/audits/inbound-discovery-log.md for 0b, docs/audits/outbound-responses-log.md for 0d), AND docs/problems/README.md + re-rated ticket bodies (0c). Revert the whole contractually-touchable set — not just the cache JSON — so a half-written README or audit-log is also restored. Revert each path independently (git checkout -- docs/problems/ 2>/dev/null; git checkout -- docs/audits/ 2>/dev/null) rather than as a combined git checkout -- docs/problems/ docs/audits/ pathspec: the combined form errors and reverts NOTHING when docs/audits/ is absent (a fresh adopter repo that has never run inbound/outbound discovery), whereas the per-path form tolerates the missing directory and still reverts the dirty docs/problems/ write. Do NOT commit a partial write: a half-refreshed cache/README is worse than a stale-but-coherent one. If the dead pre-flight somehow left STAGED residue (it should not — the pre-flight owns its commit end-to-end), git reset (unstage) it first, then revert, so the orchestrator's own subsequent Step 1+ gate flow is not contaminated by a dead subprocess's index (mirrors the ADR-009 no-trust-window-extension reasoning — a dead is_error: true subprocess MUST NOT seed the parent's commit).
- Log a one-line iter-summary annotation naming the failed pre-flight + the failure class:
Step 0<b|c|d> pre-flight FAILED (<exit-code | is_error class>) — reverted partial cache write, proceeding to Step 1 with existing README. Preserves the JTBD-006 audit-trail outcome (the silent degradation becomes observable rather than invisible).
- Proceed to Step 1. The pre-flight failure does NOT halt the loop and does NOT count against the Step 0 prior-session-state Branch 3 detection (step 1 above restored a clean tree, so the iter dispatches that follow start from a clean state).
is_error: true sub-class note (reconciles P358 with the Step 5 taxonomy). A pre-flight subprocess failure is the SAME is_error: true family the Step 5 exit-code semantics taxonomise (P261 SALVAGE / P214 HALT) — including the socket connection was closed unexpectedly variant (an is_error: true shape that routes to the Step 5 catch-all advisory). The load-bearing distinction P358 surfaces is orthogonal to the SALVAGE-vs-HALT axis: that axis is scoped to iters (the loop body); pre-flights have their own non-blocking failure contract. The Step 5 SALVAGE branch does NOT apply to a pre-flight even when the pre-flight left staged work — a pre-flight is not an iteration whose work the orchestrator salvages-and-commits; its job is a cache refresh the dispatched skill owns end-to-end. Pre-flight failure is therefore ALWAYS the revert-and-proceed branch above, never SALVAGE.
AFK authorisation per ADR-013 Rule 6: revert-and-proceed is a deterministic, non-interactive recovery — no AskUserQuestion. Reverting an unstaged partial write is fully reversible (the next loop pass re-attempts the refresh) and policy-authorised (ADR-019 preflight-reconciliation "leave the tree clean" precedent). Mirrors the P121 SIGTERM Rule-6 posture.
Compose-with: ADR-032 § "Pre-flight subprocess failure handling — non-blocking revert-and-proceed (P358 amendment)" (the architectural record + the iter-vs-pre-flight failure-semantics distinction), Step 5 exit-code semantics (the iter-failure HALT contract this is distinguished from), ADR-019 (preflight-reconciliation clean-tree surface), ADR-009 (no-trust-window-extension — the git reset of any staged residue), ADR-013 Rule 6 (non-interactive recovery), P358 (driver ticket). This is a fourth symmetric pre-flight surface alongside the three "Staleness contract drift" clauses (lines for Step 0b/0c/0d) — a future Step 0x pre-flight inherits this failure rule by construction; do NOT re-derive a step-specific copy.
Step 0e: /goal loop-anchor (P390 / ADR-094 / RFC-047 / STORY-040)
The loop's stop decision is anchored by Claude Code's native /goal command (≥ v2.1.139): a per-turn external evaluator (the configured small fast model, wrapping a session-scoped prompt-based Stop hook) judges a completion condition against what the orchestrator has printed in the transcript. This breaks the P390 same-actor conflation — the working agent that is prone to inventing subjective stops no longer decides whether stopping is justified; Step 2.4 Gate (0) remains the first-line objective self-check, and /goal is the external check that the orchestrator keeps turning until Gate (0) genuinely passes.
Canonical goal condition (owned here; the Step 2.4 Gate (0) table shape and this condition are a coupled contract — reshape both in the same commit):
The /wr-itil:work-problems AFK backlog drain is complete: the final summary printed in the conversation contains a Step 2.4 Gate (0) re-scan table (fresh open/known-error glob) classifying every ticket and showing ZERO dispatchable tickets, followed by the ALL_DONE sentinel — or the session ends with a Hard-fail halt directive naming the gate that could not complete — or the summary reports quota exhaustion.
There is no turn-bound: the loop runs until a real end state (printed Gate (0) zero-dispatchable + ALL_DONE, a Hard-fail halt, or quota exhaustion). Trust the goal — a turn cap would just re-create the premature stop this anchor exists to prevent (P422). P160/ADR-093 quota pacing throttles token burn so an honest ALL_DONE is reachable within the window.
Placement — orchestrator session ONLY. The goal lives on the orchestrator session, never on the claude -p iter subprocesses: iters end naturally after one ticket (ADR-032 / P077 / P084), and a backlog-empty goal there would push an iter past its one-ticket carve-out.
Setting the anchor. There is no programmatic mid-session surface (empirically probed 2026-07-06, v2.1.201: no --goal CLI flag; the Skill tool rejects it — "goal is a UI command, not a skill"; only the user can type it mid-session). So:
-
Headless AFK launch (the anchor-guaranteed path) — start the orchestrator with the goal set. Copy-paste-complete one-liner:
claude -p --permission-mode bypassPermissions "/goal Run /wr-itil:work-problems to drain the problem backlog. The drain is complete only when the final summary printed in the conversation contains a Step 2.4 Gate (0) re-scan table showing zero dispatchable open/known-error tickets followed by the ALL_DONE sentinel, or a Hard-fail halt directive, or reported quota exhaustion."
(The condition text itself carries the skill invocation, so the anchored session enters the loop — a bare condition would set a goal over an empty session.)
-
Interactive invocation (nudge-and-proceed) — when the loop starts without an active goal (no /goal directive or evaluator-reason lines visible in the session context), print ONE nudge line surfacing the exact command for the user to type — /goal <canonical condition above> — then proceed with the loop regardless. The anchor is defense-in-depth over Gate (0), never a precondition: halting an AFK loop for a missing anchor would itself defeat JTBD-006. No AskUserQuestion fires here (mechanical stage; ADR-044 category 4).
One-directional anchor. The goal forces continuation; it never authorises a stop. A goal that is cleared (or was never set) does NOT discharge Gate (0) — ALL_DONE still requires the full Step 2.4 sequence. Requirements floor: /goal needs workspace trust + hooks enabled; below the floor the loop degrades honestly to Gate (0)-only behaviour.
Step 1: Scan the backlog
Read docs/problems/README.md if it exists and is fresh (check via git history — see manage-problem step 9 for the cache freshness check). If stale or missing, scan all open + known-error tickets via the dual-tolerant pattern ls docs/problems/*.open.md docs/problems/*.known-error.md docs/problems/open/*.md docs/problems/known-error/*.md 2>/dev/null (RFC-002 migration window — covers BOTH the flat <NNN>-<title>.<state>.md filename-suffix layout AND the per-state subdir <state>/<NNN>-<title>.md layout), extract their WSJF scores, and rank them.
README row order matches Step 3 tier + tie-break selection (P138 + ADR-076): the README's WSJF Rankings table is rendered tier-first — rows partition into Tier 0 Critical-bypass (Severity ≥17 OR security-classified OR incident-linked) → Tier 1 Inbound-reported (**Origin**: inbound-reported) → Tier 2 Internal, and within each tier by the multi-key sort (WSJF desc, Known-Error-first, Effort-divisor asc, Reported-date asc, ID asc). The cache-fresh path can therefore read the rendered table top-to-bottom and the first row is the orchestrator's pick — no in-memory tier/tie-break re-application needed. The slow path scan must apply the same tier partition then multi-key sort.
Exclude:
.closed.md files (done)
.parked.md files (blocked on upstream)
.verifying.md files (Verification Pending — fix released, awaiting user verification per ADR-022; surfaced in the Verification Queue section, never in dev-work ranking)
- Problems with no WSJF score (need a review first — run
/wr-itil:review-problems as the first iteration if scores are missing)
Step 2: Check stop conditions
Stop the loop and report a summary if any of these are true:
- No actionable problems — zero open or known-error problems remain
- All remaining problems require interactive input — e.g., they all need user verification (known-errors with
## Fix Released), or their scope expanded beyond what's safe to auto-resolve
- All remaining problems are blocked — investigation hit a dead end, or the fix requires changes outside the project
Step 2.5 fires unconditionally at loop end (P135 Phase 3 / ADR-044) — promoted from "fallback when stop-condition #2" to default loop-end emit shape. Anti-BUFD framing per ADR-044: the AFK loop is the empirical-discovery engine; direction-class observations + deviation-candidates accumulate from real friction across iters; loop-end batched presentation is the user-facing deliverable. Per-iter surfacing was the old (now-superseded) pattern; Phase 3 makes batch-at-loop-end the default for ALL stop conditions, not just #2.
For stop-conditions #1 and #3 (no actionable problems / all blocked), Step 2.5 still runs — it reads the accumulated outstanding_questions queue from .afk-run-state/outstanding-questions.jsonl and presents the batch. Empty queue → no AskUserQuestion fires; non-empty queue → batched per ADR-013 Rule 1 cap (≤4 per call, sequential if >4).
Step 2.4: Pre-ALL_DONE gate sequence (UNCONDITIONAL — fires before every ALL_DONE emit, P341)
Before the orchestrator emits the final ALL_DONE sentinel for the AFK loop, it MUST run the following gate sequence. The sequence fires unconditionally — at every stop-condition (#1, #2, #3 per Step 2) AND at every halt-path that emits a final AFK summary AND on quota-exhaustion / natural loop end. The sequence has four parts that MUST complete in order (gate (0) prepended per P390); the structural rule is ALL_DONE emits ONLY after (0) AND (a) AND (b) complete cleanly. Per-state subdir layout reminder: this step's order in the SKILL is logical (Step 2.4 fires between the Step 2 stop-check and the Step 2.5 surfacing routine only as a wrapper); the numerical ordering reflects the conceptual sequence (Step 2.4 wraps Step 2.5 + the new retro gate, then Step 2.5/2.5b execute as gate (a)'s worker).
Gate (0) — Objective backlog-empty assertion (P390, fires FIRST, before gate (a)). Before the rest of the sequence runs, the orchestrator MUST re-scan the live backlog and prove that the Step 2 stop-condition it is about to act on OBJECTIVELY holds. ALL_DONE is forbidden while ≥1 dispatchable ticket remains — a non-empty actionable backlog is itself the disproof of stop-condition #1/#2/#3.
-
Re-scan. Re-run the Step 1 dual-tolerant glob ls docs/problems/*.open.md docs/problems/*.known-error.md docs/problems/open/*.md docs/problems/known-error/*.md 2>/dev/null (RFC-002 window — both layouts). This is a fresh filesystem read, NOT a re-use of the Step 1 cache or the agent's recollection — tickets may have transitioned, closed, or been created by prior iters / the session-level retro since Step 1.
-
Classify each ticket as dispatchable or not — objectively, per recorded marker, never by salience. A ticket is non-dispatchable ONLY when an objective, recorded condition excludes it:
- it is
verifying / carries ## Fix Released awaiting user verification (stop-condition #2, interactive);
- it carries an upstream-blocked marker (
## Reported Upstream / - **Upstream report pending** -- / em-dash legacy) or a recorded blocked classification with a dead-end investigation (stop-condition #3);
- it was filtered out THIS session by Step 3.5 (interactive-ratification predicate) or Step 3.6 (already-shipped relevance gate) — keyed off the durable per-session skip record those steps write (the
outstanding_questions entry in .afk-run-state/outstanding-questions.jsonl carrying the ticket id), NOT agent recollection, so the classification is reproducible across the re-scan and cannot loop forever;
Every other open / known-error ticket is dispatchable — ordinary autonomous fix-and-commit work. The agent MUST NOT reclassify a dispatchable ticket as non-dispatchable because the salient remainder of the backlog is interactive-gated, because the ticket "feels" out of scope, or because a user-directed pivot consumed the loop's attention. The subjective "this is a natural stopping point" judgement is exactly the P390 failure; the classification is per-ticket and marker-bound. The classification MUST be PRINTED as a table in the turn output (ticket → dispatchable/non-dispatchable → the recorded marker that decided) — not merely computed. A computed-but-unprinted re-scan is invisible to the Step 0e /goal external evaluator, which judges only what the transcript surfaces (ADR-026 grounding); the printed table is the evidence the canonical goal condition names.
-
Decide. If the re-scan yields ≥1 dispatchable ticket, ALL_DONE is FORBIDDEN: the stop-condition the orchestrator was about to emit does NOT objectively hold. The orchestrator loops back to Step 3 tier-first selection (Critical-bypass → Inbound-reported → Internal, within-tier WSJF per ADR-076) over the dispatchable set and dispatches the next iter — it does NOT proceed to gate (a)/(b)/(c). Only when the re-scan yields zero dispatchable tickets does gate (0) pass and the sequence proceed to gate (a). Gate (0) finding work is a loopback, not a halt — it is productive (the loop resumes draining), so it is NOT a Hard-fail halt trigger.
Why gate (0) fires first: gates (a)/(b)/(c) (surface questions → retro → emit) presume the loop is genuinely done; running the retro and emitting ALL_DONE while dispatchable work remains prematurely ends the AFK drain (P390), forcing the user to re-prompt "keep working the backlog" and defeating JTBD-006. Gate (0) makes "the backlog is objectively empty of dispatchable tickets" a hard, re-verified precondition of the whole sequence rather than a subjective agent judgement. A user-directed mid-loop pivot (e.g. an eval-cohort detour) does NOT discharge the Tier-exhaustion obligation: after the pivot, gate (0)'s re-scan resumes tier selection rather than terminating — which also catches the P390 coverage miss where a Tier-1 ticket (P382) was skipped entirely. Sibling class: P332 (run-retro skip rationalisation), P148 (Stage-1 ticketing skip), P175 (scope-pin loop-control inference) — all agent-invented loop-control stops the framework did not authorise (ADR-044 "Continue / stop loops" is framework-resolved: the natural stop is concrete — ALL_DONE conditions objectively met — not "this feels done").
Gate (0) × Step 0e /goal anchor (ADR-094). Under an active goal, the ALL_DONE emit does not by itself end the session — the Step 0e external evaluator reads the printed gate (0) table + sentinel and independently confirms the condition holds; a premature emit just triggers a "keep working" turn with the evaluator's reason as guidance. The anchor is one-directional: a cleared goal (or a loop that was never anchored) does NOT relax this gate — gate (0) fires unconditionally either way.
Gate (a) — Outstanding-questions surface + oversight-unconfirmed drain (P348 amendment 2026-06-02). Two sub-surfaces, both fire in this gate:
-
Outstanding-questions surface. Read .afk-run-state/outstanding-questions.jsonl. If non-empty, invoke Step 2.5b's surfacing routine to present the accumulated queue (via AskUserQuestion-when-available-else-table per ADR-013 Rule 1 / Rule 6). On completion, truncate the queue file. If the queue is empty, this sub-surface returns immediately. The surfacing routine is the existing Step 2.5b — Step 2.4 does NOT re-implement; it sequences.
-
Oversight-unconfirmed drain. Run wr-architect-detect-unoversighted, wr-jtbd-detect-unoversighted, and wr-itil-detect-unratified-stories-maps (all ADR-049 PATH shims, all always exit 0; output is the list of unoversighted/unratified artefact paths). The story/map detector (ADR-090) is drift-aware: it lists story maps + stories that are never-ratified, explicitly unconfirmed, OR drift-reopened — a confirmed marker whose oversight-hash fingerprint no longer matches the edited content. Re-ratify each via /wr-itil:manage-story / /wr-itil:manage-story-map (or wr-itil-mark-story-oversight-confirmed); the nudge + 2-option Drain now / Defer surface below applies to this list identically. If either lists ≥ 1 artefact whose frontmatter carries human-oversight: unconfirmed (the AFK-explicit-deferred state, distinct from the implicit-absent state pre-existing ADR/JTBD files carry), surface a one-line nudge: "N iter-deferred decision(s)/job(s) carry human-oversight: unconfirmed. Run /wr-architect:review-decisions and /wr-jtbd:confirm-jobs-and-personas to drain." If AskUserQuestion is available (/wr-itil:work-problems was invoked interactively before the AFK loop started), surface a 2-option choice — Drain now (invokes the appropriate drain skill before ALL_DONE) / Defer to next session (proceeds to gate (b) with the nudge in the final summary). If AskUserQuestion is unavailable, the nudge prints in the final summary table and gate (b) proceeds. The drain is NOT a halt — unconfirmed markers are explicit-by-design AFK signals (the iter wrote them KNOWING the user would need to confirm), and the drain is the documented path. Detector difference matters: ADRs/JTBDs that pre-date the ADR-066/ADR-068 marker contract carry NO human-oversight: line at all; they fall through to the existing review-decisions/confirm-jobs-and-personas backlog drain (no new surfacing here). The new surfacing fires ONLY on the explicit unconfirmed value — the AFK-iter-deferred class P348 introduces.
Gate (b) — Session-level retro. Invoke /wr-retrospective:run-retro via the Skill tool. This is the orchestrator-main-turn session-level retro, distinct from the per-iter retro fired inside each iter subprocess (per P086 / Step 5 retro-on-exit clause). The session-level retro covers cross-iter patterns, friction observations, framework-improvement candidates, and the AFK loop's overall trajectory — surface visible only after multiple iters have completed. Retro commits its own work per ADR-014; any tickets retro creates ride retro's own commit, and the orchestrator picks them up on the next invocation of /wr-itil:work-problems rather than re-entering the loop here.
Gate (c) — Emit ALL_DONE. The sentinel emits ONLY after gate (0), gate (a), and gate (b) complete. The final summary (per Output Format below) includes the Session Cost section and the Outstanding Design Questions table (when gate (a)'s fallback branch fired). ALL_DONE is the single canonical emit position — Step 2.5 no longer emits ALL_DONE directly; its closing prose hands control to Step 2.4 (b) per the cross-reference.
ALL_DONE
Hard-fail mode (halt with directive instead of emit ALL_DONE). If either gate cannot complete to a clean state, the orchestrator MUST halt with a clear directive rather than emit ALL_DONE. The halt is recoverable — the user returns, satisfies the gate, and re-invokes /wr-itil:work-problems (which observes the now-clean state and emits ALL_DONE on the natural Step 2 → Step 2.4 path).
Halt triggers:
- Gate (a) cannot complete: queue has user-input-required entries AND
AskUserQuestion is unavailable AND the fallback Outstanding Design Questions table cannot render (e.g. write error to .afk-run-state/). The halt directive cites the queue file path + entry count + the rendering failure.
- Gate (b) cannot complete:
/wr-retrospective:run-retro returns a non-zero exit code or the Skill tool itself is unavailable. The halt directive cites the run-retro failure mode (skill-unavailable / non-zero exit / commit-gate rejection per ADR-014). Retro is non-blocking within the iter subprocess (per Step 5's retro-on-exit clause) but load-bearing at the orchestrator-main-turn session-level gate — these are distinct surfaces.
Why unconditional: prior to this gate, Step 2.5's outstanding-questions surface fired conditionally on stop-condition #2; stop-conditions #1 and #3 did NOT route through it unless the queue happened to be non-empty AND the agent remembered the cross-reference. Session-level retro was implicit — only per-iter retros existed. The structural gap was that ALL_DONE could emit while direction-class observations remained queued AND without a session-level retro running — both gates were nominally documented but neither was a hard prerequisite. Step 2.4 closes this by making the gate sequence a hard, unconditional prerequisite. The 2026-05-31 user direction codified the invariant: "the work-problems skill MUST surface the outstanding questions at the end before emitting ALL_DONE. It MUST then run a retro. Only then should it emit ALL_DONE" (P341 Description verbatim).
Composition: gate (a) inherits the Step 2.5 / Step 2.5b surfacing routine without modification — the new structure is a wrapper, not a re-implementation. Gate (b) is the orchestrator-level extension of P086 (which fires retro at iter-subprocess level only). Gate (c) is the same ALL_DONE sentinel; only its emit position is amended. The pre-existing P126 cross-reference principle (halt-paths-must-route-design-questions-through-Step-2.5b) is preserved — halt-paths still route through Step 2.5b; the only addition is that even successful loop ends now route through Step 2.4 (a)+(b) before ALL_DONE. Per ADR-044 framework-resolution boundary: the agent-internal trust-boundary for when to surface is now framework-resolved (unconditional pre-ALL_DONE); the user-input surface within gate (a) is unchanged (still ADR-013 Rule 1 batched-AskUserQuestion when available, Rule 6 table fallback otherwise).
Step 2.5: Surface accumulated outstanding questions at loop end (P135 Phase 3 — default emit shape)
Per ADR-044 framework-resolution boundary: human input is for direction-setting / deviation-approval / one-time-override / silent-framework / taste / authentic-correction (six categories). Across N iters, those observations accumulate at iter level (ITERATION_SUMMARY.outstanding_questions) and persist to a session-level queue file. Loop-end Step 2.5 reads, ranks, and presents the batch.
1. Read the accumulated queue. Read .afk-run-state/outstanding-questions.jsonl — each line is one entry per the ITERATION_SUMMARY outstanding_questions schema (see Step 5 Output contract). De-duplicate identical entries (same category + same question text + same existing_decision for deviation-approval).
2. Rank the entries. Apply the ranking precedence: deviation-approval (highest) > direction > one-time-override > silent-framework > taste > correction-followup. Within each category, preserve iter-order (oldest first) so the user reads the queue in temporal sequence.
3. Branch on interactivity.
-
Default branch — call AskUserQuestion when available (the orchestrator's main turn is interactive by construction; the user is presumed at the keyboard at loop end). Batch the entries into one or more AskUserQuestion calls per ADR-013 Rule 1 cap. Header per category: "Outstanding direction", "Approve deviation from existing decision", "One-time override", etc. For deviation-approval entries, options are Approve + amend ADR / Approve + supersede ADR / Approve + one-time exception / Reject (existing decision stands) / Defer (need more evidence) — the 5-option shape matching the proposed_shape field. For other entries, options are extracted from the entry's question text or candidate fixes. Write answers back to the corresponding ticket files so the next AFK loop does not re-ask.
-
Fallback branch — emit ### Outstanding Design Questions table when AskUserQuestion is unavailable (restricted permission mode, hook-disabled tool surface). The table lists each entry with its category, question, existing_decision / contradicting_evidence for deviation-approval entries, and ticket_id. The user answers on return.
4. Cleanup. After all entries are resolved (whether via AskUserQuestion or table), truncate .afk-run-state/outstanding-questions.jsonl to empty. The next AFK loop starts with a clean queue.
5. Cleanup + hand control to Step 2.4 (b) for session-level retro (P341). Step 2.5 is the worker of Step 2.4 gate (a); after gate (a)'s surfacing routine completes and the queue file is truncated, control passes to Step 2.4 gate (b) for the session-level retro. The final summary (including the Outstanding Design Questions table when Step 2.5b's fallback branch fired) is prepared here but the ALL_DONE sentinel emits at Step 2.4 (c) AFTER retro completes — not at Step 2.5 directly. This makes Step 2.4 the single canonical ALL_DONE emit position; external scripts watching for completion read the sentinel from the post-retro position per Step 2.4.
Step 2.5b: Surface accumulated user-answerable skips (reusable surfacing routine, P122 + P126)
Step 2.5b is the single source of truth for routing accumulated user-answerable skip-reasons through AskUserQuestion-when-available-else-table. It is the sub-step that Step 2.5 (stop-condition #2) AND every halt path that fires after iters have accumulated skipped tickets cross-references — keeping the surfacing logic in one place rather than duplicated across each halt path.
Gating clause — fire only when at least one accumulated user-answerable skip exists. Iterate the skip list collected by Step 4's classifier and count entries whose skip_reason_category == user-answerable. If the count is zero, return immediately and let the caller emit its summary directly. This guards empty-skip halts (e.g. Step 0 fetch-failure halt before any iters have run) from triggering an unnecessary round-trip.
1. Extract the question set. For every skipped ticket whose classifier skip-reason is user-answerable (see Step 4's taxonomy), extract its outstanding question(s) from the ticket body — typically from a "Pacing decision", "Naming decision", or outstanding "Investigation Tasks" section. Cap at 4 questions per AskUserQuestion call per Anthropic's tool documentation; the same cap applies regardless of whether Step 2.5b was invoked from stop-condition #2 or a halt path.
2. Branch on interactivity per ADR-013 Rule 1 / Rule 6.
- Default branch — call
AskUserQuestion when available (the orchestrator's main turn is interactive by construction; the user is presumed at the keyboard). Batch the questions into one AskUserQuestion call (or more, if >4 questions, issued sequentially). Header: "Outstanding design questions". For each question, set the prompt from the extracted text and the options from the ticket's candidate fixes or option list. Write each answer back to the corresponding ticket file so the next AFK loop does not re-ask. This is ADR-013 Rule 1 applied to the orchestrator's main-turn surface.
- Fallback branch — emit
### Outstanding Design Questions table when AskUserQuestion is unavailable (restricted permission mode, hook-disabled tool surface, or any other context where the structured-question primitive cannot fire). The table lists each question with its Ticket ID, the question text, and one-line context. The user answers on return. This is ADR-013 Rule 6 fail-safe — fall back to a structured summary when the structured-interaction primitive is unavailable.
Return. Hand control back to the caller. The caller is responsible for emitting its own final summary (and the ALL_DONE sentinel for stop-condition #2; halt paths each have their own outcome label per Step 6 / ADR-042 Rule 5 / Step 6.75 / etc.).
Cross-skill principle (architect FLAG, P122 + P126): orchestrator main turns default to AskUserQuestion when available; the AFK persona (JTBD-006) is served by the subprocess-boundary contract under ADR-032 (iteration subprocess workers are AFK by construction via claude -p — they exit at ITERATION_SUMMARY and never reach the orchestrator's stop or halt surfaces), NOT by suppressing AskUserQuestion at the orchestrator layer. Step 5's iteration-prompt template carries the per-subprocess AFK contract (constraint: "Do not call AskUserQuestion"); the orchestrator's stop and halt surfaces fire only in the main turn where the user is presumed present. P122 established this principle at Step 2.5; P126 extends it to every halt path that emits a final AFK summary (the principle: halt-paths-must-route-design-questions-through-Step-2.5b — every halt path that fires after iters have accumulated user-answerable skips MUST run Step 2.5b before emitting its summary).
Step 3: Pick the highest-WSJF problem in the highest non-empty tier
Selection partitions the backlog into three tiers and works the highest non-empty tier first; the WSJF tie-break ladder applies within a tier, not across tiers. Tiers, highest first (ADR-076):
- Tier 0 — Critical bypass: Severity Very High (≥17) OR security-classified OR incident-linked. The most critical issues always come first, regardless of origin.
- Tier 1 — Inbound-reported: ticket carries
**Origin**: inbound-reported (reported to us by an external user; ADR-062). Worked ahead of internal tickets — customer-service / feedback-signal preservation: ignored reporters stop reporting and churn.
- Tier 2 — Internal: everything else (
**Origin**: internal or no Origin field).
Within the highest non-empty tier, select the problem with the highest WSJF score. If there's a tie, prefer:
- Known Errors over Open problems (they have a confirmed fix path — less risk of wasted effort)
- Smaller effort over larger (faster throughput)
- Older reported date (longer wait = higher urgency)
- Lower ID (deterministic final tiebreaker)
The full selection order is therefore: tier (Critical-bypass → Inbound-reported → Internal), then the within-tier ladder (WSJF desc, Known-Error-first, Effort-divisor asc, Reported-date asc, ID asc).
Step 3.5: JTBD ratification predicate-check (per RFC-016 / P344)
After Step 3 selects a candidate ticket and before Step 4 classifies it for dispatch, predicate-check the cited JTBDs of the selected ticket. The per-iter JTBD review subagent (ADR-068 surface 3 — the [Unratified Dependency] verdict) catches the same class INSIDE the iter subprocess, but only after spending iter-dispatch cost (~$3-5 + 5-10 min per skip). This step shifts the predicate left to the orchestrator layer for the cost of one grep + per-JTBD shim call — analogous to how Step 0b pre-flights inbound-discovery staleness rather than letting iters discover it. Driving exemplar: 2026-05-31 session 9 iter 5 dispatched P082 against unratified JTBD-001 + JTBD-006; the iter correctly skipped per ADR-074 substance-confirm-before-build, but the per-dispatch cost was wasted.
Mechanism:
wr-itil-check-ticket-jtbd-ratification "<selected-ticket-path>"
predicate_exit=$?
wr-itil-check-ticket-jtbd-ratification is the ADR-049 / ADR-080 $PATH shim that dispatches packages/itil/scripts/check-ticket-jtbd-ratification.sh. The script extracts cited JTBD-NNN IDs from the ticket body (Decision Drivers / **JTBD**: / **Persona**: references) and delegates per-JTBD ratification to wr-jtbd-is-job-or-persona-unconfirmed (the ADR-068 surface 3 single-artifact predicate). Polarity is INVERTED vs the inner predicate — the outer script answers "are all cited JTBDs ratified?" rather than "is THIS one unconfirmed?". Behavioural contract asserted by test/work-problems-step-3-5-jtbd-ratification-predicate.bats.
Exit-code routing:
predicate_exit | Meaning | Action |
|---|
0 | All cited JTBDs ratified, OR ticket cites no JTBDs, OR per-JTBD shim missing (ADR-031 silent-pass) | Proceed to Step 4 normally — the ticket is dispatchable. |
1 | ≥1 cited JTBD unratified (or unresolved) — IDs on stdout, one per line; JTBD-NNN (unresolved) for inner exit-2 cases | Route the ticket to Step 4's user-answerable skip path (skip_reason_category: user-answerable). Queue an outstanding_questions entry (category: "direction") naming the unratified JTBDs + ticket ID + remedy: "Run /wr-jtbd:confirm-jobs-and-personas to ratify the cited jobs/personas, then re-invoke /wr-itil:work-problems." Loop back to Step 3 to re-run the tier-first selection over the remaining backlog minus the skipped ticket. |
2 | Ticket file missing / unreadable | Halt the loop with the structured Prior-Session State report — this is the same shape as the README-reconciliation Exit 2 halt at Step 0 (deeper repair needed, not mechanical reconciliation). |
Loopback tier preservation: re-run the Step 3 tier-first selection over the remaining backlog minus the skipped ticket. Tier order (Critical-bypass → Inbound-reported → Internal) and within-tier WSJF ladder are preserved per ADR-076. If every actionable ticket is filtered out by Step 3.5, Step 2 stop-condition #1 (no actionable problems) fires naturally and the accumulated outstanding_questions entries surface at Step 2.4 gate (a) per the existing batched-AskUserQuestion contract.
Why orchestrator-layer, not iter-layer: the inner per-iter JTBD subagent stays in place (defence-in-depth — the iter-layer is the authoritative second-source, not a replacement surface). The orchestrator predicate is the optimisation: cheap pre-check eliminates wasted dispatch when the answer is knowable from a grep + frontmatter read. The two surfaces are not redundant — they cover different failure modes (orchestrator: shift-left cost optimisation; iter: substance-confirm-before-build governance gate per ADR-074). When the orchestrator silent-passes (predicate-exit 0 via missing-shim degenerate case per ADR-031), the iter-layer still catches any unratified-dep correctly.
AFK authorisation per ADR-013 Rule 6: this is a pure read-only predicate-check (no writes, no commits, no external comms). No AskUserQuestion at this step — the routing is deterministic per the table above. The user-answerable question accumulates in the queue file and surfaces at Step 2.4 gate (a) per the existing batched contract. Per ADR-044 framework-resolution boundary: routing is framework-resolved (mechanical); user input is preserved at the loop-end surface where it belongs.
Compose-with: ADR-068 (surface 3 single-artifact predicate — mirrored to orchestrator), ADR-074 (substance-confirm-before-build — JTBD-as-driver symmetric sibling to ADR-as-driver), ADR-076 (tier-first selection preserved by the loopback), ADR-031 (degenerate adopter silent-pass when per-JTBD shim absent), ADR-049 / ADR-080 (PATH shim grammar + highest-version-wins wrapper), ADR-014 (no commit at this step — predicate is read-only). The sibling-class gap for ADRs cited as Decision Drivers (ADR-074 master class) is RFC-016 § Deferred item 1 — captured for follow-on after this Step 3.5 dogfoods.
Step 3.6: Pre-dispatch relevance gate (per P385)
After Step 3.5's JTBD predicate-check and before Step 4 classifies the selected ticket, run the cheap deterministic relevance evaluator on the selected ticket only. On a mature backlog a meaningful fraction of "open" / known-error tickets have already been fixed by later work but never transitioned (observed: 3 of 6 worked tickets in one session). A full Step 5 manage-problem dispatch (~$3-5 + 5-10 min) against such a ticket only rediscovers the shipped fix and transitions it — the conclusion is correct but the rediscovery is expensive. This step shifts that conclusion left to a millisecond shell check, exactly as Step 3.5 shifts the JTBD-ratification predicate left and Step 0c pre-flights the backlog-wide relevance-close.
Mechanism:
wr-itil-evaluate-relevance "<selected-ticket-path>"
relevance_exit=$?
wr-itil-evaluate-relevance is the ADR-049 $PATH shim dispatching packages/itil/scripts/evaluate-relevance.sh — the same evaluator /wr-itil:review-problems Step 4.6 uses for its relevance-close pass (ADR-079). It emits one verdict line and carries a built-in ≥7-day age gate (a freshly-reported ticket SKIPs and falls through to normal work — no self-close paradox). Behavioural coverage of the verdict shapes is the existing packages/itil/scripts/test/evaluate-relevance.bats; Step 3.6 adds no new computational surface, only routing, so it carries no separate behavioural script of its own (a SKILL.md-prose grep would be a structural test, rejected per P081 / ADR-052).
Exit-code routing:
relevance_exit / verdict | Meaning | Action |
|---|
0 CLOSE-CANDIDATE (no caveat) | cited fix shipped — clean evidence per ADR-079 shapes | Do not dispatch a full iter. Dispatch ONE /wr-itil:review-problems relevance-close sweep reusing the Step 0c claude -p pre-flight shape (see Step 0c / Step 5). Its Step 4.6 batch-closes the selected ticket and any sibling CLOSE-CANDIDATEs in one ADR-014 commit. Set the once-per-session sweep sentinel. Loop back to Step 1 (the sweep changed the backlog + refreshed the README — re-scan re-applies the ADR-076 tier partition from the refreshed rankings). |