@reddb-io/red-skills-dev 2.75.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +30 -0
  3. package/package.json +32 -0
  4. package/skills/engineering/README.md +36 -0
  5. package/skills/engineering/_report-runtime/WRAPPER.md +47 -0
  6. package/skills/engineering/afk/AGENT-PROMPT.md +295 -0
  7. package/skills/engineering/afk/SAFETY.md +103 -0
  8. package/skills/engineering/afk/SKILL.md +160 -0
  9. package/skills/engineering/afk/TROUBLESHOOTING.md +260 -0
  10. package/skills/engineering/afk/actions-lane.md +213 -0
  11. package/skills/engineering/afk/bin/README.md +29 -0
  12. package/skills/engineering/afk/bin/afk.mjs +51 -0
  13. package/skills/engineering/afk/defaults/cargo-pre-worktree.sh +37 -0
  14. package/skills/engineering/afk/defaults/envelope-post-attempt.sh +47 -0
  15. package/skills/engineering/afk/defaults/gradle-pre-worktree.sh +42 -0
  16. package/skills/engineering/afk/defaults/heartbeat-post-attempt.sh +43 -0
  17. package/skills/engineering/afk/defaults/validation-post-merge.sh +77 -0
  18. package/skills/engineering/afk/detectors/README.md +72 -0
  19. package/skills/engineering/afk/detectors/cargo.sh +27 -0
  20. package/skills/engineering/afk/detectors/gradle.sh +35 -0
  21. package/skills/engineering/afk/docs/BOOT-SWEEPS.md +81 -0
  22. package/skills/engineering/afk/docs/CONFIG.md +238 -0
  23. package/skills/engineering/afk/docs/ENVELOPE.md +177 -0
  24. package/skills/engineering/afk/docs/HANDOFF.md +70 -0
  25. package/skills/engineering/afk/docs/LIVENESS.md +56 -0
  26. package/skills/engineering/afk/docs/OPERATIONS.md +409 -0
  27. package/skills/engineering/afk/examples/only-mine.sh +32 -0
  28. package/skills/engineering/afk/examples/red-afk-attempt-action.yml +53 -0
  29. package/skills/engineering/afk/examples/rs-afk-attempt.yml +76 -0
  30. package/skills/engineering/afk/fleet.md +127 -0
  31. package/skills/engineering/afk/hooks/red-cargo +44 -0
  32. package/skills/engineering/afk/hooks/red-envelope +50 -0
  33. package/skills/engineering/afk/hooks/red-gradle +50 -0
  34. package/skills/engineering/afk/hooks/red-heartbeat +46 -0
  35. package/skills/engineering/afk/hooks/red-validation +79 -0
  36. package/skills/engineering/afk/monitor.md +170 -0
  37. package/skills/engineering/afk/runner-claude-minimax.md +71 -0
  38. package/skills/engineering/afk/runner-claude.md +67 -0
  39. package/skills/engineering/afk/runner-codex.md +138 -0
  40. package/skills/engineering/afk/runner-hermes.md +40 -0
  41. package/skills/engineering/afk/runner-opencode.md +142 -0
  42. package/skills/engineering/ask-red/SKILL.md +137 -0
  43. package/skills/engineering/audit-skills/SKILL.md +46 -0
  44. package/skills/engineering/code-review/SKILL.md +106 -0
  45. package/skills/engineering/context/SKILL.md +142 -0
  46. package/skills/engineering/curate/SKILL.md +133 -0
  47. package/skills/engineering/daily-review/SKILL.md +68 -0
  48. package/skills/engineering/dashboard/SKILL.md +44 -0
  49. package/skills/engineering/diagnose/SKILL.md +155 -0
  50. package/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -0
  51. package/skills/engineering/go/SKILL.md +144 -0
  52. package/skills/engineering/go/TROUBLESHOOTING.md +81 -0
  53. package/skills/engineering/ground-truth/SKILL.md +84 -0
  54. package/skills/engineering/hitl/SKILL.md +171 -0
  55. package/skills/engineering/hitl/TROUBLESHOOTING.md +76 -0
  56. package/skills/engineering/implement/SKILL.md +69 -0
  57. package/skills/engineering/improve-codebase-architecture/DEEPENING.md +37 -0
  58. package/skills/engineering/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  59. package/skills/engineering/improve-codebase-architecture/LANGUAGE.md +53 -0
  60. package/skills/engineering/improve-codebase-architecture/SKILL.md +81 -0
  61. package/skills/engineering/model-tier-policy/SKILL.md +83 -0
  62. package/skills/engineering/prototype/LOGIC.md +79 -0
  63. package/skills/engineering/prototype/SKILL.md +30 -0
  64. package/skills/engineering/prototype/UI.md +112 -0
  65. package/skills/engineering/red-doctor/APPLY.md +35 -0
  66. package/skills/engineering/red-doctor/SKILL.md +172 -0
  67. package/skills/engineering/red-gains/SKILL.md +49 -0
  68. package/skills/engineering/red-setup/INTERVIEW.md +266 -0
  69. package/skills/engineering/red-setup/ISSUE-SWEEP.md +18 -0
  70. package/skills/engineering/red-setup/REFERENCE.md +35 -0
  71. package/skills/engineering/red-setup/SKILL.md +58 -0
  72. package/skills/engineering/red-setup/WORKFLOWS.md +74 -0
  73. package/skills/engineering/red-setup/WRITE-CONTRACT.md +151 -0
  74. package/skills/engineering/red-setup/config-template.yaml +174 -0
  75. package/skills/engineering/red-setup/domain.md +52 -0
  76. package/skills/engineering/red-setup/issue-tracker-github.md +88 -0
  77. package/skills/engineering/red-setup/rsp-retirement-runbook.md +35 -0
  78. package/skills/engineering/red-setup/scripts/install-runtime-shim.sh +189 -0
  79. package/skills/engineering/red-setup/triage-labels.md +176 -0
  80. package/skills/engineering/red-setup/workflows/red-issues-needs-triage.yml +36 -0
  81. package/skills/engineering/red-statusline/HOST-NOTES.md +180 -0
  82. package/skills/engineering/red-statusline/SKILL.md +58 -0
  83. package/skills/engineering/report-bug/SKILL.md +155 -0
  84. package/skills/engineering/resolving-merge-conflicts/SKILL.md +143 -0
  85. package/skills/engineering/retake/SKILL.md +184 -0
  86. package/skills/engineering/review-adrs/SKILL.md +125 -0
  87. package/skills/engineering/start/ADR-FORMAT.md +47 -0
  88. package/skills/engineering/start/CONTEXT-FORMAT.md +77 -0
  89. package/skills/engineering/start/DOC-LANDING-FINALIZER.md +9 -0
  90. package/skills/engineering/start/SKILL.md +179 -0
  91. package/skills/engineering/tdd/SKILL.md +96 -0
  92. package/skills/engineering/tdd/deep-modules.md +33 -0
  93. package/skills/engineering/tdd/interface-design.md +31 -0
  94. package/skills/engineering/tdd/mocking.md +59 -0
  95. package/skills/engineering/tdd/tests.md +85 -0
  96. package/skills/engineering/to-spec/SKILL.md +104 -0
  97. package/skills/engineering/to-tickets/SKILL.md +158 -0
  98. package/skills/engineering/triage/AGENT-BRIEF.md +207 -0
  99. package/skills/engineering/triage/OUT-OF-SCOPE.md +101 -0
  100. package/skills/engineering/triage/SKILL.md +176 -0
  101. package/skills/engineering/verify/SKILL.md +136 -0
  102. package/skills/engineering/wayfinder/SKILL.md +128 -0
  103. package/skills/engineering/zoom-out/SKILL.md +51 -0
  104. package/skills/engineering/zoom-out/scripts/graph-mode-plumbing.sh +36 -0
  105. package/skills/engineering/zoom-out/scripts/tests/contract.test.sh +168 -0
  106. package/skills/knowledge/README.md +5 -0
  107. package/skills/knowledge/research/SKILL.md +76 -0
  108. package/skills/misc/README.md +8 -0
  109. package/skills/misc/branch-lock/SKILL.md +187 -0
  110. package/skills/misc/branch-lock/scripts/branch-lock-hook.sh +113 -0
  111. package/skills/misc/branch-lock/scripts/branch-lock-session-start.sh +81 -0
  112. package/skills/misc/branch-lock/scripts/branch-lock.sh +68 -0
  113. package/skills/misc/branch-lock/scripts/lib/dev-config.sh +126 -0
  114. package/skills/misc/branch-lock/scripts/lib/git-command-classifier.sh +198 -0
  115. package/skills/misc/branch-lock/scripts/lib/lock-store.sh +67 -0
  116. package/skills/misc/branch-lock/scripts/lib/scope-resolver.sh +43 -0
  117. package/skills/misc/branch-lock/scripts/tests/branch-lock-cli.test.sh +114 -0
  118. package/skills/misc/branch-lock/scripts/tests/claude-plugin-hook.test.sh +117 -0
  119. package/skills/misc/branch-lock/scripts/tests/codex-hook.test.sh +229 -0
  120. package/skills/misc/branch-lock/scripts/tests/dev-config.test.sh +99 -0
  121. package/skills/misc/branch-lock/scripts/tests/git-command-classifier.test.sh +187 -0
  122. package/skills/misc/branch-lock/scripts/tests/lock-store.test.sh +90 -0
  123. package/skills/misc/branch-lock/scripts/tests/scope-resolver.test.sh +63 -0
  124. package/skills/misc/branch-lock/scripts/tests/session-start.test.sh +123 -0
  125. package/skills/misc/git-guardrails-claude-code/SKILL.md +105 -0
  126. package/skills/misc/git-guardrails-claude-code/scripts/block-dangerous-git.sh +231 -0
  127. package/skills/misc/git-guardrails-claude-code/scripts/tests/block-dangerous-git.test.sh +143 -0
  128. package/skills/misc/migrate-to-shoehorn/SKILL.md +96 -0
  129. package/skills/misc/setup-pre-commit/SKILL.md +94 -0
  130. package/skills/productivity/README.md +8 -0
  131. package/skills/productivity/ff/SKILL.md +116 -0
  132. package/skills/productivity/handoff/SKILL.md +37 -0
  133. package/skills/productivity/reflect/SKILL.md +18 -0
  134. package/skills/productivity/write-a-skill/SKILL.md +190 -0
  135. package/skills/productivity/write-a-skill/WRITING-STYLE.md +62 -0
@@ -0,0 +1,127 @@
1
+ # AFK Fleet Mode — running `afk fleet` (multi-worker supervisor)
2
+
3
+ This file serves the `afk fleet` branch: launching, stopping, and supervising `N`
4
+ concurrent `run` workers on one checkout. Reached from *When To Use*
5
+ (`/afk fleet [N]`, `/afk fleet stop`) in [`SKILL.md`](./SKILL.md).
6
+
7
+ ## Fleet Mode (runner-portable — binding)
8
+
9
+ `/dev:afk fleet [N]` and `/dev:afk fleet stop` are the user-facing fleet commands. They let one terminal command spin up (or shut down) `N` concurrent `run` workers on the current checkout, with the supervisor handling respawn, the circuit breaker, the **passive stall detector** (samples each slot's per-attempt **agent lane** `agent.log.jsonl` mtime — the clean liveness signal — every `RED_AFK_STALL_POLL_S=30s`; flags any slot alive ≥ `RED_AFK_STALL_THRESHOLD_S=600` whose agent lane has been idle ≥ the same — surfaces as `⏸️ stalled` in `/dev:afk monitor`. It keys off the agent lane, never `afk.log`/`log.jsonl`, because the orchestrator heartbeat writes those every minute and would mask a real stall — the masking that defeated detection in #243), the **hard stall reaper** (a slot silent on the agent lane past `RED_AFK_STALL_KILL_THRESHOLD_S=1800` is only a *candidate*: the irreversible kill is gated behind a reaper-signal predicate, so a worker mid-build/test — an active `vitest`/`tsc`/`cargo`/… descendant under its tree, or non-trivial aggregate cpu — is **busy** and left alone, while a genuinely stuck worker [idle past the threshold, no active descendant, flat cpu] is killed tree-wide, a `data-attempt-status="no-sentinel"` envelope is posted with the attempt-dir `afk.log` tail, the issue label is rotated back to `ready-for-agent`, the worktree + attempt dir are removed, and the slot is freed for the next health-check respawn — `RED_AFK_STALL_KILL_THRESHOLD_S` must be strictly greater than `RED_AFK_STALL_THRESHOLD_S`, validated at supervisor boot), and per-slot build isolation.
10
+
11
+ **Worker env passthrough.** Any `RED_AFK_*` variable exported in the operator's shell before `/dev:afk fleet` is auto-forwarded to every worker the supervisor spawns. Use this for worker-side toggles like `RED_AFK_SKIP_PERF=1` or `RED_AFK_SKIP_COMPETITIVE_BASELINE=1` without writing a hook. Internal supervisor knobs (`RED_AFK_TARGET`, `RED_AFK_POLL_S`, `RED_AFK_STALL_*`, `RED_AFK_CIRCUIT_*`, `RED_AFK_RUNNER`, `RED_AFK_REQUEST`, `RED_AFK_PLUGIN_DIR`) and the per-slot `*_BASE` build-isolation vars are excluded — they have dedicated wiring and the supervisor denylists them from passthrough. The supervisor re-pins `RED_AFK_RUNNER=<runner>` for each worker.
12
+
13
+ ```bash
14
+ $ export RED_AFK_SKIP_PERF=1
15
+ $ export RED_AFK_SKIP_COMPETITIVE_BASELINE=1
16
+ $ /dev:afk fleet 1 # every worker sees both vars
17
+ ```
18
+
19
+ Fleet mode is **runner-portable**: the supervisor is plain process orchestration, not a Claude Code primitive. Claude Code, Codex, and bare terminals may all launch and stop the supervisor when the normal AFK hard preconditions pass. Runner-specific observability degrades independently:
20
+
21
+ - Claude Code: launch fleet. For manual monitoring, run `/dev:afk monitor` or tail `.red/tmp/supervisors/default/supervisor.log.toonl`.
22
+ - Codex: launch fleet with `RED_AFK_RUNNER=codex` and spawn one read-only Codex monitor agent from the bundle's `codex-monitor-agent --mode fleet` prompt when a sub-agent primitive is available. If no sub-agent primitive is available, launch fleet anyway and print `monitor loop unavailable in this runner; run /dev:afk monitor or tail .red/tmp/supervisors/default/supervisor.log.toonl manually.`
23
+ - Bare terminal / unknown runner: launch fleet and print the manual-monitor guidance.
24
+
25
+ **Release recycle rule.** A fleet supervisor keeps running the exact dev bundle
26
+ version it was launched from. After any RedSkills release that changes AFK or
27
+ castle engine behavior, stop and relaunch the fleet before starting or counting
28
+ a proving drain; otherwise the drain may still be executing on the pre-release
29
+ engine. `monitor` and the statusline fleet cell show the running bundle version
30
+ and mark skew against a newer locally cached bundle so stale supervisors are
31
+ visible. Automatic drain-and-respawn on version change is a future enhancement,
32
+ not part of the current fleet contract.
33
+
34
+ ### `/dev:afk fleet [N]` — launch
35
+
36
+ `N` is optional and defaults to `2`. Parse it as a non-negative integer; reject anything else (including `stop`, which is the other subcommand and routes below). Steps the agent must perform, in order:
37
+
38
+ 1. **Resolve runner.** Determine the active runner using the same intent as the normal AFK cascade: explicit user `--runner` if present, else `RED_AFK_RUNNER`, else runner env/process/path signals, else `claude`. The resolved value is carried into the supervisor as `RED_AFK_RUNNER=<runner>` so detached workers do not fall through to the supervisor's historical `claude` fallback. Under Codex, this must resolve to `codex`.
39
+ 2. **PID-file pre-check / live directive.** Read `.red/tmp/supervisors/default/afk-supervisor.pid`. If it exists and `kill -0 <pid>` succeeds, do not launch a second supervisor. Instead the bundle command writes the `afk-supervisor.resize` directive file in the supervisor runtime lane (`.red/tmp/supervisors/default/`) as the live directive:
40
+ - `fleet <N>` changes the desired worker count while keeping the current runner.
41
+ - `fleet <N> --shrink-mode hard-kill|drain-then-retire` changes the resize shrink behavior. The default is `drain-then-retire`.
42
+ - `fleet <N> --runner <runner>` asks the running supervisor to switch runner. A changed runner is applied by re-pinning the supervisor's worker env, marking every live slot `drain-then-retire`, letting in-flight claims finish, then respawning replacement slots on the new runner. An unchanged runner is a no-op.
43
+ After writing the directive, the launcher reads `.red/tmp/supervisors/default/state.toon` and prints `fleet directive applied ...` when the heartbeat already echoes the requested target/runner/shrink-mode, or `fleet directive pending ...` while waiting for the next supervisor tick to apply it.
44
+
45
+ Older launch guidance used to say a live supervisor is refused:
46
+ ```
47
+ ✗ fleet already running (supervisor pid=<pid>, log .red/tmp/supervisors/default/supervisor.log.toonl).
48
+ to stop it: /dev:afk fleet stop
49
+ ```
50
+ That remains the model for truly conflicting second supervisors, but `fleet <N>` is now the supported live resize/switch surface. A stale PID file (file exists but `kill -0` fails) is left alone — the `fleet` command clears it itself when it acquires the supervisor lock.
51
+ 3. **Launch the fleet.** From the project root, run the bundle's `fleet` command with the target and any flags:
52
+ ```bash
53
+ RED_AFK_RUNNER=<runner> red-skills-dev fleet <N> [--request <text>]
54
+ ```
55
+ The command performs the PID-file pre-check from step 2 itself (refusing if a live supervisor already runs), detaches the supervisor, and forwards the resolved runner and the `--request/-r` text to every worker it spawns. It waits up to 3 s for `.red/tmp/supervisors/default/afk-supervisor.pid` to appear and contain a live PID, then prints the launched supervisor PID and target; on failure it reports the tail of `.red/tmp/supervisors/default/supervisor.log.toonl`. Capture the reported PID for the *Report back* step. The launched supervisor is the native `__supervise` entrypoint of the same bundle.
56
+ 4. **Attach the best available monitor surface.**
57
+ - Claude Code: no automatic monitor cron. Tell the user to run `/dev:afk monitor` manually or tail `.red/tmp/supervisors/default/supervisor.log.toonl`.
58
+ - Codex: fetch a sub-agent spawn primitive via `ToolSearch` (query: `spawn agent background monitor`). If available, emit the canonical prompt with `RED_AFK_RUNNER=codex red-skills-dev codex-monitor-agent --project-root "$PWD" --mode fleet` and spawn exactly one read-only Codex monitor agent for this newly-launched supervisor. Its task: from the project root, periodically run `/dev:afk monitor --once` (the bundle's `monitor --once`), report concise progress, and auto-close when `.red/tmp/supervisors/default/afk-supervisor.pid` is missing/dead and no `[live]` workers remain. It must never edit files, claim issues, stop workers, or run merges. The user may close it manually; workers continue. If the primitive is unavailable, skip and use the manual-monitor line.
59
+ - Bare/unknown: skip native monitor setup and use the manual-monitor line.
60
+ 5. **Report back.** Print:
61
+ ```
62
+ 🚀 fleet launched (supervisor pid=<pid>, target=<N>)
63
+ log: .red/tmp/supervisors/default/supervisor.log.toonl
64
+ stop: /dev:afk fleet stop
65
+ <monitor-status-line>
66
+ ```
67
+ Monitor status line choices:
68
+ - Claude Code: `monitor: run /dev:afk monitor or tail .red/tmp/supervisors/default/supervisor.log.toonl`
69
+ - Codex monitor agent spawned: `Codex monitor agent spawned — auto-closes when fleet exits; manual monitor: /dev:afk monitor.`
70
+ - Codex monitor unavailable: `monitor loop unavailable in this runner; run /dev:afk monitor or tail .red/tmp/supervisors/default/supervisor.log.toonl manually.`
71
+ - Bare/unknown: `monitor loop unavailable in this runner; run /dev:afk monitor or tail .red/tmp/supervisors/default/supervisor.log.toonl manually.`
72
+
73
+ ### `/dev:afk fleet stop` — graceful shutdown
74
+
75
+ Steps, in order:
76
+
77
+ 1. **Liveness check.** Read `.red/tmp/supervisors/default/afk-supervisor.pid`. The three cases:
78
+ - File missing → print `no fleet running.` and continue to step 3 (still try runner-specific monitor teardown).
79
+ - File present but `kill -0` fails → stale. Print `no fleet running (stale pid file at .red/tmp/supervisors/default/afk-supervisor.pid — cleaning).`, `rm -f` it, and continue to step 3.
80
+ - File present and PID alive → continue to step 2.
81
+ 2. **Touch the stop file.** `touch .red/tmp/supervisors/default/afk-supervisor.stop`. The supervisor's health-check cycle (default `RED_AFK_POLL_S=15s`) picks it up and runs `cleanup`, which SIGTERMs every worker, removes the PID file, removes the stop file, and exits. Wait up to **30 s** for the PID file to disappear (poll every 1 s, deadline-bounded — never bare `while`). If it's gone, print `🛑 fleet stopped (supervisor pid=<pid> exited).`. If the deadline trips, print one warning line naming the PID and the log path, and continue to step 3 anyway — the stop file is still there and the supervisor will pick it up eventually.
82
+ 3. **Tear down runner-specific monitors.**
83
+ - Claude Code: no automatic monitor cron to cancel. Print `no auto-monitor cron (manual monitoring only).`
84
+ - Codex: do not stop workers through the monitor agent. It auto-closes when it observes no supervisor/live workers, and the user may close it manually. Print `Codex monitor agent will self-close when it observes fleet stopped.`
85
+ - Bare/unknown: print `no native monitor teardown for this runner.`
86
+ 4. **Idempotency.** Re-running `/dev:afk fleet stop` after a successful stop just hits the "file missing" branch in step 1 and the runner-specific teardown no-op in step 3. Exit 0 either way.
87
+
88
+ ### `/dev:afk fleet logs` — local structured log reader
89
+
90
+ `/dev:afk fleet logs --supervisor`, `/dev:afk fleet logs --worker <id>`, and
91
+ `/dev:afk fleet logs --all` are read-only local views over the castle lanes.
92
+ They do not call GitHub and do not mutate fleet state. They decode structured
93
+ TOONL records and render human-readable lines only at read time:
94
+
95
+ - `--supervisor` reads supervisor lanes under `.red/tmp/supervisors/`.
96
+ - `--worker <id>` reads that worker's `.red/tmp/workers/<id>/worker.log.toonl`.
97
+ - `--all` reads every worker lane, merges records by timestamp, and prefixes
98
+ each rendered line with `[worker-id]`.
99
+ - `--follow` keeps polling the selected lane set and streams appended records
100
+ until the caller stops the command.
101
+
102
+ ### Circuit Trip Sweep
103
+
104
+ When the circuit breaker parks a slot (`CIRCUIT_K` fast deaths inside `CIRCUIT_WINDOW_S`) the supervisor — not a human — runs `sweep_parked_slot` to clean up after the burned workers. Three actions, in order, gated on the trip:
105
+
106
+ 1. **Sweep affected attempt dirs.** From the dated slot log (`.red/tmp/logs/<yyyy-mm-dd>/afk-supervisor-slot-{slot}.log`) the supervisor parses every `[afk] worker: w…` boot stamp emitted while the slot was alive, globs `.red/tmp/workers/{wid}/*/` for each ID, and reads `afk.state.json`'s `.current.number` to identify the affected issues. During the logs TTL aging window, the reader also accepts the legacy tmp-root `afk-supervisor-slot-{slot}.log` path so pre-migration slot logs can be swept before the janitor reclaims them. Each attempt dir is `rm -rf`'d after its issue has been processed.
107
+ 2. **Post a discard envelope on each affected issue.** Same `<details data-attempt-status="…">` schema as the per-issue terminal envelope, with `status="discarded"` and a summary line that names the runner and the trip cause (`runner-broken, slot parked after K fast deaths`). The envelope's `data-section="summary"` block carries the slot index, comma-joined worker IDs, fast-death count, and the supervisor log path. No `notes`, `drop`, or `log` sections — the attempts produced no usable artefacts.
108
+ 3. **Restore label state on each affected issue.** Single `gh issue edit` adds `ready-for-agent` and `runner-error`, removes `ready-for-human` and (defensively) `running` — covers both the "issue had already been promoted to `ready-for-human`" path and the "issue was still `running` at the moment of trip" path.
109
+
110
+ The `runner-error` label is created idempotently by `/red-setup` (see [triage-labels.md](../red-setup/triage-labels.md)). The supervisor still calls `gh label create runner-error` on the fly during a trip so cleanup never fails just because the label is missing.
111
+
112
+ Idempotency: `SLOT_SWEPT[slot]=1` blocks a second sweep within the same supervisor lifetime. Across restarts a new trip yields fresh worker IDs and fresh attempt dirs, so re-tripping never re-touches the previously swept issues. A trip that finds no claimed issues (all workers exited before claiming) parks the slot but posts no envelopes — the attempt-dir sweep is a no-op.
113
+
114
+ ## Fleet Width by Disjunction
115
+
116
+ The safe fleet width for a given queue is the **degree of disjunction** — the maximum number of concurrently `ready-for-agent` slices that touch non-overlapping file sets.
117
+
118
+ - **Disjoint queue** (each slice writes to files no other ready slice touches): run the full fleet. `fleet 4` is safe when the queue has four independent file cones.
119
+ - **Partially entangled queue**: lower the fleet width to the number of disjoint groups. If two groups of two slices each are independent between groups but internally serialized via `req:N`, `fleet 2` drains both chains in parallel without risk.
120
+ - **Fully entangled refactor** (every slice touches shared files, serialized via `req:N`): run `fleet 1` or bare `/afk`. Parallel workers share no ready-for-agent work at any moment — extra slots sit idle and raise the risk of a spurious merge conflict should a `req:N` edge be missing.
121
+
122
+ **Rule: fleet width = degree of disjunction.** Raise it when the queue is disjoint; lower it to `fleet 1` when slices are entangled. The `/to-tickets` slicing skill is responsible for expressing file-level dependencies as `req:N` edges before the queue is published, so a correctly-sliced backlog never produces a file-overlap merge conflict from concurrent workers.
123
+
124
+ ### Refs
125
+
126
+ - The bundle's `fleet` / `fleet stop` commands — the entrypoints this section drives. Stop-file path, env contract, circuit breaker, and trip-sweep are part of the supervisor behaviour described above.
127
+ - [`monitor.md`](./monitor.md) — the readonly dashboard and native-task mirror.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # red-cargo — built-in pre_worktree library hook for Rust projects.
3
+ #
4
+ # Contract (ADR 0026, PRD #207, issue #211):
5
+ # stdin — JSON object: pre_worktree context (issue, target, branch, env).
6
+ # stdout — empty (context unchanged) when no Cargo.toml is present at
7
+ # $PROJECT_ROOT, OR a JSON object that merges
8
+ # {env: {CARGO_TARGET_DIR: <slot dir>}} into the context.
9
+ # exit — 0 on either branch. Never aborts the chain.
10
+ #
11
+ # Side effect: mkdir -p the slot target dir so cargo can use it immediately.
12
+ # CARGO_TARGET_DIR=${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}/slot-${RED_AFK_SLOT:-0}
13
+ # so each worker slot gets its own target directory and Cargo never
14
+ # serializes on .cargo-lock between workers.
15
+ #
16
+ # Env contract (RED_AFK_* exported by the orchestrator):
17
+ # RED_AFK_SLOT — worker slot number (0-based); defaults to 0.
18
+ # RED_AFK_CARGO_TARGET_BASE — base dir for slot target dirs; defaults to
19
+ # /opt/cargo-target. Set in .red/config.yaml
20
+ # via afk.cargo_target_base or by environment.
21
+ #
22
+ # Shadowing: place a same-named file in .red/hooks/red-cargo to replace this
23
+ # default with a project-local variant. An empty (exit-0) file disables it.
24
+
25
+ set -uo pipefail
26
+
27
+ project_root="${PROJECT_ROOT:-$(pwd)}"
28
+ ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
29
+ [[ -z "$ctx" ]] && ctx='{}'
30
+
31
+ if [[ ! -f "$project_root/Cargo.toml" ]]; then
32
+ # Not a Rust project — pass-through.
33
+ exit 0
34
+ fi
35
+
36
+ slot="${RED_AFK_SLOT:-0}"
37
+ base="${RED_AFK_CARGO_TARGET_BASE:-/opt/cargo-target}"
38
+ target_dir="${base}/slot-${slot}"
39
+
40
+ mkdir -p "$target_dir" 2>/dev/null || true
41
+
42
+ printf '%s' "$ctx" | jq -c \
43
+ --arg t "$target_dir" \
44
+ '. + {env: ((.env // {}) + {CARGO_TARGET_DIR: $t})}'
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # red-envelope — built-in post_attempt library hook (ADR 0026, PRD #207,
3
+ # issue #212; renamed from post_worker in #226).
4
+ #
5
+ # Reconciles the iteration's intermediate envelope state on the AFK state
6
+ # file as soon as the runner returns — before any user post_attempt hook
7
+ # runs, so a user notifier/pager integration that reads state already sees
8
+ # the worker's terminal result classified.
9
+ #
10
+ # Contract:
11
+ # stdin — JSON object: post_attempt context with .result.status of
12
+ # "success" or "fail".
13
+ # stdout — empty (context unchanged). Pure side-effect on the state file.
14
+ # exit — 0. Never aborts the chain.
15
+ #
16
+ # Env contract (RED_AFK_* exported by the orchestrator):
17
+ # RED_AFK_STATE_FILE — absolute path to the iteration's afk.state.json;
18
+ # when unset/missing, the default is a no-op.
19
+ # RED_AFK_RESULT_STATUS — redundant fallback ("success"|"fail") for
20
+ # runners/tests that cannot shape a context JSON;
21
+ # if ctx.result.status is absent this is used
22
+ # instead.
23
+ #
24
+ # Shadowing: place a same-named file in .red/hooks/red-envelope to replace
25
+ # this default with a project-local variant. An empty (exit-0) file disables
26
+ # it.
27
+
28
+ set -uo pipefail
29
+
30
+ ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
31
+
32
+ state_file="${RED_AFK_STATE_FILE:-}"
33
+ [[ -z "$state_file" || ! -f "$state_file" ]] && exit 0
34
+
35
+ status=""
36
+ if [[ -n "$ctx" ]]; then
37
+ status="$(printf '%s' "$ctx" | jq -r '.result.status // empty' 2>/dev/null || true)"
38
+ fi
39
+ [[ -z "$status" ]] && status="${RED_AFK_RESULT_STATUS:-}"
40
+ [[ -z "$status" ]] && exit 0
41
+
42
+ tmp="${state_file}.tmp.$$"
43
+ if jq --arg s "$status" '.current = ((.current // {}) + {result_status: $s})' \
44
+ "$state_file" > "$tmp" 2>/dev/null; then
45
+ mv "$tmp" "$state_file"
46
+ else
47
+ rm -f "$tmp"
48
+ fi
49
+
50
+ exit 0
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # red-gradle — built-in pre_worktree library hook for Gradle projects.
3
+ #
4
+ # Contract (ADR 0026, PRD #207, issue #211):
5
+ # stdin — JSON object: pre_worktree context (issue, target, branch, env).
6
+ # stdout — empty (context unchanged) when no build.gradle* is present at
7
+ # $PROJECT_ROOT, OR when RED_AFK_GRADLE_USER_HOME_BASE is unset
8
+ # (opt-in by env var — we do not claim a path on the user's
9
+ # filesystem without their consent). Otherwise a JSON object
10
+ # that merges {env: {GRADLE_USER_HOME: <slot dir>}} into ctx.
11
+ # exit — 0 on either branch. Never aborts the chain.
12
+ #
13
+ # Side effect: mkdir -p the slot Gradle home so daemons / caches are isolated
14
+ # between worker slots.
15
+ #
16
+ # Env contract (RED_AFK_* exported by the orchestrator):
17
+ # RED_AFK_SLOT — worker slot number (0-based); defaults to 0.
18
+ # RED_AFK_GRADLE_USER_HOME_BASE — base dir for per-slot Gradle homes;
19
+ # UNSET by default (opt-in). Set in
20
+ # .red/config.yaml or by environment to
21
+ # activate per-slot Gradle isolation.
22
+ #
23
+ # Shadowing: place a same-named file in .red/hooks/red-gradle to replace this
24
+ # default with a project-local variant. An empty (exit-0) file disables it.
25
+
26
+ set -uo pipefail
27
+
28
+ project_root="${PROJECT_ROOT:-$(pwd)}"
29
+ ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
30
+ [[ -z "$ctx" ]] && ctx='{}'
31
+
32
+ shopt -s nullglob
33
+ matches=("$project_root"/build.gradle*)
34
+ shopt -u nullglob
35
+ if [[ "${#matches[@]}" -eq 0 ]]; then
36
+ exit 0
37
+ fi
38
+
39
+ if [[ -z "${RED_AFK_GRADLE_USER_HOME_BASE:-}" ]]; then
40
+ exit 0
41
+ fi
42
+
43
+ slot="${RED_AFK_SLOT:-0}"
44
+ home_dir="${RED_AFK_GRADLE_USER_HOME_BASE}/slot-${slot}"
45
+
46
+ mkdir -p "$home_dir" 2>/dev/null || true
47
+
48
+ printf '%s' "$ctx" | jq -c \
49
+ --arg h "$home_dir" \
50
+ '. + {env: ((.env // {}) + {GRADLE_USER_HOME: $h})}'
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # red-heartbeat — built-in post_attempt library hook (ADR 0026, PRD #207,
3
+ # issue #212; renamed from post_worker in #226).
4
+ #
5
+ # Stops the orchestrator's per-minute heartbeat side-channel (when a PID is
6
+ # available) and writes the "iteration stopped" boundary marker into the
7
+ # iteration log, migrating the inline heartbeat_stop that used to fire from
8
+ # afk.sh right after run_inner returned.
9
+ #
10
+ # Contract:
11
+ # stdin — JSON object: post_attempt context (issue, workspace, result).
12
+ # stdout — empty (context unchanged). Pure side-effect.
13
+ # exit — 0. Never aborts the post_attempt chain.
14
+ #
15
+ # Env contract (RED_AFK_* exported by the orchestrator):
16
+ # RED_AFK_HEARTBEAT_PID — PID of the orchestrator heartbeat subprocess;
17
+ # empty/unset when no external heartbeat runs
18
+ # (e.g. the native TS runtime manages heartbeat
19
+ # internally). Killing a stale pid is guarded via
20
+ # kill -0.
21
+ # RED_AFK_ITER_LOG — absolute path to the iteration log (afk.log);
22
+ # the boundary line is appended only when the file
23
+ # exists. Unset/empty → skip the marker.
24
+ #
25
+ # Shadowing: place a same-named file in .red/hooks/red-heartbeat to replace
26
+ # this default. An empty (exit-0) file disables it; the orchestrator EXIT
27
+ # trap remains the backstop for heartbeat teardown.
28
+
29
+ set -uo pipefail
30
+
31
+ # Drain stdin (context flows through unchanged — emit nothing).
32
+ timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat >/dev/null 2>&1 || true
33
+
34
+ pid="${RED_AFK_HEARTBEAT_PID:-}"
35
+ if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
36
+ kill "$pid" 2>/dev/null || true
37
+ # Best-effort reap; the sub-shell may already be exiting.
38
+ wait "$pid" 2>/dev/null || true
39
+ fi
40
+
41
+ iterlog="${RED_AFK_ITER_LOG:-}"
42
+ if [[ -n "$iterlog" && -f "$iterlog" ]]; then
43
+ printf '[heartbeat] iteration stopped at %s\n' "$(date -Is)" >> "$iterlog"
44
+ fi
45
+
46
+ exit 0
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # red-validation — built-in post_merge library hook (ADR 0026, PRD #207,
3
+ # issue #213).
4
+ #
5
+ # Runs CI/smoke validation against the merged primary checkout so user
6
+ # post_merge hooks see the validation status already reconciled into the
7
+ # context. The pre-merge feedback() step in the mechanism layer stays as the
8
+ # safety gate (ADR 0008 — anything that must abort the merge loudly is
9
+ # mechanism, not a hook); this default exposes a post-merge re-run hook
10
+ # authors can disable, observe, or replace.
11
+ #
12
+ # Contract:
13
+ # stdin — JSON object: post_merge context (issue, workspace, merge_commit).
14
+ # stdout — JSON object: the same context with
15
+ # .result = {validation_status: "passed"|"failed"|"skipped",
16
+ # validation_summary: <one-line string>} merged into
17
+ # it. Non-JSON stdin → pass-through with no validation attempt.
18
+ # exit — 0. Post_merge policy is continue; even a failing validation
19
+ # only logs (it cannot roll back the merge — that ship has sailed
20
+ # at this lifecycle point).
21
+ #
22
+ # Env contract (RED_AFK_* exported by the orchestrator):
23
+ # RED_AFK_WORKSPACE — primary checkout (post-merge tip). When unset or
24
+ # not a directory, the default is a no-op.
25
+ # RED_AFK_MERGE_COMMIT — full sha of the merge commit on target. Not
26
+ # consumed by this default but documented so user
27
+ # hooks reading env directly see a consistent view.
28
+ #
29
+ # Shadowing: place a same-named file in .red/hooks/red-validation to replace
30
+ # this default with a project-local variant. An empty (exit-0) file disables
31
+ # it.
32
+
33
+ set -uo pipefail
34
+
35
+ ctx="$(timeout "${RED_SKILLS_HOOK_STDIN_TIMEOUT_S:-5s}" cat 2>/dev/null || true)"
36
+ ws="${RED_AFK_WORKSPACE:-}"
37
+
38
+ emit_ctx() {
39
+ local status="$1" summary="$2"
40
+ if [[ -z "$ctx" ]]; then
41
+ return 0
42
+ fi
43
+ printf '%s' "$ctx" \
44
+ | jq --arg s "$status" --arg sum "$summary" \
45
+ '.result = ((.result // {}) + {validation_status: $s, validation_summary: $sum})' \
46
+ 2>/dev/null \
47
+ || printf '%s' "$ctx"
48
+ }
49
+
50
+ if [[ -z "$ws" || ! -d "$ws" ]]; then
51
+ emit_ctx "skipped" "no workspace"
52
+ exit 0
53
+ fi
54
+
55
+ if ! [[ -f "$ws/package.json" ]]; then
56
+ emit_ctx "skipped" "no package.json"
57
+ exit 0
58
+ fi
59
+
60
+ # Run pnpm test/typecheck/lint/build at the workspace root if the script
61
+ # exists. This mirrors the mechanism-owned feedback() step shape but
62
+ # operates on the merged tip rather than the worker's worktree.
63
+ overall="passed"
64
+ parts=""
65
+ for script in test typecheck lint build; do
66
+ if jq -e --arg s "$script" '.scripts[$s] // empty' "$ws/package.json" >/dev/null 2>&1; then
67
+ if ( cd "$ws" && pnpm -s "$script" >/dev/null 2>&1 ); then
68
+ parts+="${script}:✓ "
69
+ else
70
+ parts+="${script}:✗ "
71
+ overall="failed"
72
+ fi
73
+ else
74
+ parts+="${script}:skip "
75
+ fi
76
+ done
77
+
78
+ emit_ctx "$overall" "${parts% }"
79
+ exit 0
@@ -0,0 +1,170 @@
1
+ # AFK Monitor — running `afk monitor` (readonly dashboard + task mirror)
2
+
3
+ This file serves the `afk monitor` branch: the readonly aggregated dashboard
4
+ across all live workers, the native-task mirror and its self-cancel teardown, and
5
+ the Codex monitor agent. Reached from *When To Use* (`/afk monitor`).
6
+
7
+ ## The binding mirror rule (authoritative — stated once)
8
+
9
+ **The mirror is binding: every monitor tick must (1) render the dashboard and (2) mirror live workers onto the host runner's native task surface, re-checking `afk.state.json` on every tick.** It is never optional and never a shortcut — skipping it because "nothing changed" or because the user "only asked for status" is a bug, not an optimization, since `monitor --mirror-plan` is idempotent and emits zero descriptors when nothing changed. Per-runner mapping:
10
+
11
+ - **Claude Code:** pipe the tracked-task JSONL into the bundle's `monitor --mirror-plan` and apply the emitted call plan via `TaskCreate` (one task per live worker, titled `#<n> w<id> — <title>`) and `TaskUpdate` (description carries `stage:<x>`, terminal events flip `state` to `completed`/`failed`). See *Task Mirror* below for the full protocol.
12
+ - **Codex:** run `monitor --mirror-plan --runner codex`. Today Codex exposes no native task surface, so the sink emits an empty plan and the mirror falls back to the dashboard plus a one-line notice — that *is* the mirror under Codex; do not silently skip. If Codex grows a native surface, the sink emits the same call-plan descriptors against it.
13
+ - **Bare terminal / unknown runner:** skip the mirror silently — the `monitor` dashboard is the canonical view.
14
+
15
+ ## Dashboard
16
+
17
+ `/afk monitor` is the readonly aggregated view across all live workers. **Run the bundle's `monitor` command — do not reinvent the rendering in inline bash.** It:
18
+
19
+ 1. Globs `.red/tmp/workers/*/*/afk.state.json` and renders one section per active attempt.
20
+ 2. Verifies liveness via the orchestrator PID recorded in `afk.state.json` (`.pid` field), paired with `pid_start_time` when the platform exposes a stable process-start token. Attempts whose PID identity is dead or mismatched are flagged `stale`/`gone`; PID-live but agent-lane-quiet workers render `[quiet]` and are still counted as running.
21
+ 3. Reads sibling `afk.log` line counts through the monitor cursor and renders `log:<total>(+<new>)` when available, without re-reading whole logs on every tick.
22
+ 4. Renders WorkerVitals activity counters (`tools:<n> reason:<n> text:<n> wait:<n>`) so a quiet but pid-live worker can show useful progress signals.
23
+ 5. Renders the 48h sparkline header (next subsection) on every refresh.
24
+
25
+ To invoke, from the project root:
26
+
27
+ ```bash
28
+ RED_AFK_RUNNER=<runner> red-skills-dev monitor
29
+ ```
30
+
31
+ The command has **two modes**, auto-selected by stdout type:
32
+
33
+ - **TTY (real terminal)**: full box-drawing layout, refreshes every 3 s, `clear` between frames. Ctrl-C to exit.
34
+ - **Non-TTY (piped, captured by an agent, redirected)**: one-shot **compact dashboard** — one sparkline header + one line per worker, then exit 0. Force this with `--once` or `RED_AFK_MONITOR_COMPACT=1` even from a TTY.
35
+
36
+ Compact output shape (≈3 lines total for 2 workers — fits inline without truncation in an agent transcript):
37
+
38
+ ```
39
+ 48h: ···············································█ (4 closed, peak 4/h, all workers) Δ fleet +382 -45
40
+ wZ2R4 [live] claude issues 4/5 #150 [blog/D] Agent SDK on RedDB stage:impl 00:23:01 +382 -45 tools:39 reason:4 text:112 wait:2 log:540(+12)
41
+ wK7M2 [live] codex issues 0/16 idle +0 -0
42
+ ```
43
+
44
+ The progress counter is `issues <done>/<total>` — issues *closed* over the queue total, **not** a completion percentage (the old `(80%)` form read as "no work done" while a worker had already committed thousands of lines). The real volume signal is the `+A -R` **diff** (committed + uncommitted, measured from the branch's merge-base with `origin/main`), which is rendered on **every** worker line unconditionally — idle and `+0 -0` included — and **summed across the fleet** into the `Δ fleet +A -R` suffix on the sparkline header, so the total diff volume is always visible at a glance.
45
+
46
+ When invoking from inside another agent session (Claude Code, Codex), prefer `--once` even if stdin is a pipe — explicit beats inference. Don't use the full TTY mode in agent transcripts; the 3 s refresh loop floods the captured stream and gets truncated to garbage.
47
+
48
+ Single-worker operation shows one section/line. Multi-worker adds one section/line per live worker, sorted by `started_at`. The sparkline aggregates **all workers** in this checkout's `.red/state/castle/history.toonl` — not fractured per-worker; the `Δ fleet` diff total likewise sums every worker.
49
+
50
+ The header of every render shows a **48h sparkline** of issues closed, one glyph per hour, scaled to the peak hour:
51
+
52
+ ```
53
+ 48h: ·▁··▁·▁·▁··█▁▁··▁·▁···▁·▁·▆▁▁··▁···▁▆·▁··▁▃▁·▃▁· (35 closed, peak 5/h)
54
+ ```
55
+
56
+ Source data: `.red/state/castle/history.toonl`, an append-only TOONL event log written by the orchestrator on every terminal event. `tq` is the pinned required reader for this lane; do not document or use a jq fallback.
57
+
58
+ ```toonl
59
+ []{ts,epoch,worker,issue,event,duration_s,runner,merge_sha,reason}:
60
+ "2026-05-17T12:14:00-03:00",1747494840,wK7M2,571,done,816,codex,0936ba54,null
61
+ "2026-05-17T12:18:00-03:00",1747495080,wK7M2,569,blocked,120,codex,null,merge-conflict
62
+ "2026-05-17T12:20:00-03:00",1747495200,wK7M2,568,exhausted,0,claude,null,both-runners
63
+ ```
64
+
65
+ Example inspection:
66
+
67
+ ```bash
68
+ tq -p toonl -o json 'select(.event == "done") | {issue: .issue, worker: .worker, runner: .runner}' .red/state/castle/history.toonl
69
+ ```
70
+
71
+ `.red/state/` is gitignored. The orchestrator creates it during bootstrap, parallel workers serialise appends via `flock`, and the boot-time orphan sweep truncates the file to the last 10000 lines if it grows past that cap.
72
+
73
+ The sparkline only counts `event == "done"`. Blockers and exhausted runs are recorded for forensics but excluded from the throughput view.
74
+
75
+ ## Self-Cancel (binding when invoked under Claude Code via a manual /loop)
76
+
77
+ When the user has manually set up a `/loop`-based recurring `/dev:afk monitor` run, each tick is responsible for tearing down the loop once there are no workers left to watch.
78
+
79
+ After rendering the dashboard, the agent must:
80
+
81
+ 1. Count observable workers with status `[live]` or `[quiet]` in the rendered output (i.e., orchestrator pid identity alive, post-orphan-cleanup).
82
+ 2. If `observable_workers == 0`:
83
+ - Fetch `CronList` and `CronDelete` via `ToolSearch` if not already loaded.
84
+ - `CronList` — find every job with `prompt == "/dev:afk monitor"`.
85
+ - `CronDelete` each match.
86
+ - Append one line to the user-facing output: `🛑 no live workers — cancelled monitor loop (cron <id>).`
87
+
88
+ When `CronList` / `CronDelete` are unavailable (Codex runner, or `/afk monitor` invoked outside Claude Code), skip the teardown silently — the cron infrastructure isn't running there to begin with.
89
+
90
+ ## Task Mirror
91
+
92
+ Every `/dev:afk monitor` run also **mirrors each live worker onto the runner's native task list when that runner exposes one**, so a `/afk` session surfaces progress on the host's native UI — advancing through stages on its own, with no extra typing. This is a **read-only reflection of `afk.state.json`**; the mirror never writes state and never touches the orchestration.
93
+
94
+ **Host capability matrix (binding — no parity).** The Task mirror is per-runner by construction (ADR 0003/0015): there is **no shared native task API** across the hosts, so each runner gets its own explicit adapter, never a generic cross-runner abstraction. The honest matrix — encoded as `taskMirrorCapability(host)` in `core/mirror.ts` and exercised by `tests/mirror.test.ts` — is:
95
+
96
+ | Host (Agent runner) | Surface | Native task API | Adapter / sink | Today's behavior |
97
+ |---|---|---|---|---|
98
+ | Claude Code | `native-task` | yes | `mirrorPlan` (`TaskCreate`/`TaskUpdate`) | the in-session Agent runner drives the native Task mirror through the host task tools |
99
+ | Codex | `monitor-agent` | no | `codexSinkPlan` | no task API — the mirror falls back to the `monitor` dashboard plus **one read-only Codex monitor agent** |
100
+ | OpenCode runner | `headless` | no | none (empty plan) | a headless API-auth **Worker** with no host session — there is no surface to mirror into, so no native calls are ever emitted |
101
+
102
+ The three surfaces are deliberately distinct values (never a single `supported: boolean`) so the matrix can **never imply parity**. Exactly one host (Claude Code) exposes a native task API; the other two degrade explicitly, each on its own adapter. An unknown host fails loudly rather than silently inheriting the Claude native path.
103
+
104
+ The mirror surfaces **two signals on one lifecycle** (issue #811): the task **title** carries the calm **macro phase** — `w<id> [<n>/5 <phase>] #<issue> <slug>` — while the task **description** carries the fine **micro stage** — `stage: <impl|explore|tests|commit>`. The phase vocabulary is the ordered `setup → coding → validating → merging → done` (1-based `n/5`), plus the terminal `blocked` which drops the `n/5` and renders `[blocked]`. The title changes only when the macro phase moves, so it never flickers on every inner-agent tool call.
105
+
106
+ The mirror is a pure diff: it reconciles the live worker state files against the tasks already on the native surface and emits a **call plan**. After rendering the dashboard, the agent (under Claude Code only) must:
107
+
108
+ 1. Fetch `TaskCreate`, `TaskUpdate`, and `TaskList` via `ToolSearch` if not already loaded (deferred tools).
109
+ 2. **Build the tracked set.** `TaskList` → keep the mirror-owned tasks (those whose title matches `w<id> [<…>] #<n> <slug>`). For each, emit one JSONL line `{"key":"<worker_id>:<issue>","stage":"<last stage>","phase":"<last phase>"}`, reading the key (`worker_id` from the leading token, `issue` from the `#<n>`) and the **phase** (the word inside the title's `[…]` bracket, after any `n/5 `) from the title, and the **stage** from the description (`stage: <x>`). Keep a key→task_id map for step 4.
110
+ 3. **Compute the plan.** Pipe the tracked JSONL from step 2 into the bundle's `monitor --mirror-plan` subcommand:
111
+ ```bash
112
+ printf '%s\n' "$tracked" | red-skills-dev monitor --mirror-plan
113
+ ```
114
+ The command globs the state files and reconciles them against the tracked set on stdin (keyed by `worker_id:issue`, so parallel workers each get exactly one task and re-runs never duplicate), then prints a JSONL **call plan** to stdout — one descriptor per harness call (empty stdin → cold reconcile; empty plan → no output). A `TaskUpdate` rewrites the **title** when the macro phase moves and refreshes the **description** when the micro stage moves; a terminal failure re-titles to `[blocked]` and flips the task to `failed`:
115
+ ```jsonl
116
+ {"call":"TaskCreate","key":"wAAAA:22","title":"wAAAA [2/5 coding] #22 extract state.sh","description":"stage: impl","state":"in_progress"}
117
+ {"call":"TaskUpdate","key":"wAAAA:22","title":"wAAAA [3/5 validating] #22 extract state.sh","description":"stage: tests","state":"in_progress"}
118
+ {"call":"TaskUpdate","key":"wAAAA:22","state":"completed"}
119
+ ```
120
+ 4. **Apply the plan.** For each descriptor in order:
121
+ - `TaskCreate` → create the task; record `key → task_id`.
122
+ - `TaskUpdate` → resolve `key` to its `task_id` via the map and update. A `state` of `completed`/`failed` marks the worker's terminal event (`done`/`blocked`); the task drops off the active list and the mirror self-cleans. A descriptor whose `key` has no known `task_id` (e.g. a complete for a task that was never created in this session) is skipped.
123
+
124
+ An empty plan means nothing changed since the last tick — apply no calls. Because the plan is keyed by `worker_id:issue`, an idempotent re-run with no stage advance emits zero descriptors.
125
+
126
+ **Re-hydration on session reopen.** A native task dies with the Claude Code session; the `nohup` AFK worker does not. When a session opens with workers still running, `TaskList` (step 2) returns no mirror-owned tasks, so the tracked set is **empty** and `monitor --mirror-plan` reconciles cold — emitting a `TaskCreate` for every live worker. The status bar recovers the per-worker tasks with no operator action. This is the same path as steady-state, not a new one: only workers whose orchestrator PID (the `.pid` field in `afk.state.json`, via `state_is_live`) is alive re-hydrate (dead workers are untracked-terminal on a cold tick → no ghost task), and the next tick is idempotent because the freshly-created tasks now form the tracked set.
127
+
128
+ When `TaskCreate` / `TaskUpdate` are unavailable because the session is **outside any runner** (a bare terminal), **skip the mirror silently** — there is no native surface to drive, and the `monitor` dashboard is already the canonical view.
129
+
130
+ **Codex sink (runner-specific — binding).** The mirror is per-runner, mirroring the `runner-claude.md` / `runner-codex.md` split (ADR 0003). Under Codex the state reader and plan reconciler are reused unchanged — only the sink differs. After rendering the dashboard, the Codex agent runs `monitor --mirror-plan --runner codex` instead of the Claude `TaskCreate`/`TaskUpdate` loop:
131
+
132
+ - If Codex grows a native background-task surface, the sink emits the **same call-plan descriptors** the Claude sink applies — apply them against the Codex primitive.
133
+ - Otherwise (today's reality), `--runner codex` emits an **empty plan**, so the mirror falls back to the `monitor` dashboard and a one-line notice. No native calls are emitted, so there is no half-rendered state, and a dashboard hiccup is swallowed so the tick never crashes.
134
+
135
+ Do **not** invent a cross-runner task abstraction (rejected in ADR 0003) — keep the adapter explicitly per-runner.
136
+
137
+ ## Codex Monitor Agent (Codex only — binding)
138
+
139
+ Codex does not expose Claude Code's `TaskCreate` / `TaskUpdate` task surface, and
140
+ its `tui.status_line` only renders built-in footer widgets. It does expose a
141
+ native sub-agent UI in hosts where the sub-agent primitive is available. For
142
+ Codex runs, use that sub-agent UI as a read-only presentation layer over the
143
+ canonical `/afk monitor` dashboard.
144
+
145
+ When `/afk` launches a normal detached worker under Codex (`run`, not
146
+ `monitor`, not `--once`, not `--boot-only`), or `/dev:afk fleet N` launches a new
147
+ supervisor under Codex:
148
+
149
+ 1. Fetch a sub-agent spawn primitive via `ToolSearch` (query:
150
+ `spawn agent background monitor`).
151
+ 2. If unavailable, continue the worker launch and print:
152
+ `monitor loop unavailable in this runner; run /dev:afk monitor or tail .red/tmp/workers/*/*/afk.log manually.`
153
+ 3. If available, emit the canonical prompt from the bundle (use `--mode run` for a
154
+ single worker, `--mode fleet` for a supervisor, so the read-only rules stay
155
+ identical across launches):
156
+ ```bash
157
+ RED_AFK_RUNNER=codex red-skills-dev codex-monitor-agent --project-root "$PWD" --mode run
158
+ ```
159
+ Spawn exactly one monitor agent with that prompt. The monitor agent is a
160
+ presentation consumer only: it periodically runs `/dev:afk monitor --once`,
161
+ reports concise progress in the Codex UI, and exits once no supervisor or
162
+ live workers remain.
163
+ 4. Tell the user one line:
164
+ `Codex monitor agent spawned — auto-closes when AFK exits; manual monitor: /dev:afk monitor.`
165
+
166
+ Hard boundaries for the monitor agent are non-negotiable: it must never edit
167
+ files, claim issues, change labels, comment, stop workers, run validation, push,
168
+ merge, `/retake`, `/go`, `/hitl`, `/triage`, `/afk run`, `/afk fleet`, `/afk fleet stop`,
169
+ `/afk reap`, or `/afk requeue`. Closing it manually must not affect the AFK
170
+ worker.