@staix/agent-hub 0.12.33 → 0.12.35

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 (126) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -3
  3. package/docs/agent-notes/adapters.md +9 -3
  4. package/docs/agent-notes/budget.md +7 -3
  5. package/docs/agent-notes/bus.md +2 -1
  6. package/docs/agent-notes/daemon.md +7 -3
  7. package/docs/agent-notes/local-worker.md +1 -1
  8. package/docs/agent-notes/tasks.md +6 -2
  9. package/docs/agent-notes/tests.md +2 -1
  10. package/docs/gsd-collaboration-analysis.md +5 -1
  11. package/docs/operations.md +80 -16
  12. package/docs/quickstart.md +10 -2
  13. package/docs/sdlc.md +59 -34
  14. package/docs/security.md +7 -4
  15. package/docs/smoke.md +1 -1
  16. package/docs/specs/2026-09-19-agent-hub-design.md +407 -10
  17. package/docs/verification/2026-10-18-0.12.34.md +48 -0
  18. package/docs/verification/2026-10-18-0.12.35.md +49 -0
  19. package/docs/verification/2026-10-18-493-claude-busy-channel.md +91 -0
  20. package/docs/verified.json +35 -19
  21. package/package.json +1 -1
  22. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  23. package/plugins/agent-hub/server.js +23567 -16606
  24. package/src/adapters/acp.ts +3 -1
  25. package/src/adapters/claude-channel.ts +59 -29
  26. package/src/adapters/codex-appserver.ts +82 -12
  27. package/src/adapters/definitions/builtin-metadata.ts +101 -7
  28. package/src/adapters/definitions/native-cli-claude-launch.ts +90 -0
  29. package/src/adapters/definitions/native-cli-claude-setup.ts +80 -0
  30. package/src/adapters/definitions/native-cli-codex-launch.ts +14 -0
  31. package/src/adapters/definitions/native-cli-doctor.ts +46 -0
  32. package/src/adapters/definitions/native-cli-entry-claude.ts +52 -0
  33. package/src/adapters/definitions/native-cli-entry-codex.ts +34 -0
  34. package/src/adapters/definitions/native-cli-entry-jcode.ts +27 -0
  35. package/src/adapters/definitions/native-cli-entry-kimi.ts +57 -0
  36. package/src/adapters/definitions/native-cli-entry-local.ts +15 -0
  37. package/src/adapters/definitions/native-cli-entry-pi.ts +53 -0
  38. package/src/adapters/definitions/native-cli-entry-qwen.ts +21 -0
  39. package/src/adapters/definitions/native-cli-entry-types.ts +41 -0
  40. package/src/adapters/definitions/native-cli-entry.ts +19 -0
  41. package/src/adapters/definitions/native-cli-jcode-mcp.ts +58 -0
  42. package/src/adapters/definitions/native-cli-launch-common.ts +17 -0
  43. package/src/adapters/definitions/native-cli-pi.ts +33 -0
  44. package/src/adapters/definitions/native-cli-preview-claude.ts +37 -0
  45. package/src/adapters/definitions/native-cli-preview-codex.ts +20 -0
  46. package/src/adapters/definitions/native-cli-preview-common.ts +57 -0
  47. package/src/adapters/definitions/native-cli-preview-jcode.ts +20 -0
  48. package/src/adapters/definitions/native-cli-preview-kimi.ts +28 -0
  49. package/src/adapters/definitions/native-cli-preview-pi.ts +36 -0
  50. package/src/adapters/definitions/native-cli-preview-qwen.ts +22 -0
  51. package/src/adapters/definitions/native-cli-preview.ts +16 -0
  52. package/src/adapters/definitions/native-config-defaults.ts +19 -0
  53. package/src/adapters/definitions/native-config-policy.ts +35 -0
  54. package/src/adapters/definitions/native-context.ts +35 -0
  55. package/src/adapters/definitions/native-daemon-kimi.ts +5 -0
  56. package/src/adapters/definitions/native-daemon-policy.ts +18 -0
  57. package/src/adapters/definitions/native-display.ts +18 -0
  58. package/src/adapters/definitions/native-factories.ts +48 -0
  59. package/src/adapters/definitions/native-policy-aux.ts +130 -0
  60. package/src/adapters/definitions/native-quota.ts +29 -0
  61. package/src/adapters/definitions/native-recovery-continuity.ts +12 -0
  62. package/src/adapters/definitions/native-recovery.ts +88 -0
  63. package/src/adapters/definitions/qwen-native.ts +2 -2
  64. package/src/adapters/definitions/qwen.ts +8 -4
  65. package/src/adapters/local-worker.ts +3 -2
  66. package/src/adapters/pi.ts +3 -1
  67. package/src/adapters/registry-types.ts +100 -10
  68. package/src/adapters/registry.ts +127 -11
  69. package/src/cli/console-state.ts +5 -5
  70. package/src/cli/identity.ts +8 -7
  71. package/src/cli/jcode-mcp.ts +2 -58
  72. package/src/cli/launch.ts +41 -102
  73. package/src/cli/main.ts +126 -290
  74. package/src/cli/output.ts +3 -2
  75. package/src/cli/preview.ts +26 -126
  76. package/src/cli/recovery-guidance.ts +43 -22
  77. package/src/cli/setup.ts +2 -80
  78. package/src/cli/shell-quote.ts +3 -0
  79. package/src/cli/terminal-recovery.ts +81 -22
  80. package/src/cli/upgrade-interactive.ts +4 -2
  81. package/src/cli/upgrade-restore-state.ts +17 -7
  82. package/src/cli/upgrade-runtime.ts +126 -147
  83. package/src/cli/upgrade.ts +44 -27
  84. package/src/hub/budget.ts +59 -31
  85. package/src/hub/bus.ts +1 -1
  86. package/src/hub/conductor.ts +19 -9
  87. package/src/hub/config-apply.ts +22 -15
  88. package/src/hub/config-reload.ts +18 -3
  89. package/src/hub/config-trust.ts +1 -1
  90. package/src/hub/context-window.ts +3 -27
  91. package/src/hub/control-client.ts +1 -1
  92. package/src/hub/crash.ts +15 -39
  93. package/src/hub/daemon.ts +506 -341
  94. package/src/hub/execution-budget.ts +3 -2
  95. package/src/hub/hub-tools.ts +19 -8
  96. package/src/hub/instance-budget.ts +73 -0
  97. package/src/hub/instance-config.ts +47 -0
  98. package/src/hub/instance-execution.ts +50 -0
  99. package/src/hub/instances.ts +100 -0
  100. package/src/hub/lifecycle-schema.ts +7 -3
  101. package/src/hub/peer-catalog.ts +4 -13
  102. package/src/hub/permission-mode.ts +14 -17
  103. package/src/hub/recovery-continuity.ts +6 -5
  104. package/src/hub/report.ts +9 -8
  105. package/src/hub/roster.ts +9 -9
  106. package/src/hub/routing.ts +94 -19
  107. package/src/hub/sdlc-queue.ts +2 -2
  108. package/src/hub/sdlc.ts +84 -38
  109. package/src/hub/settings.ts +39 -10
  110. package/src/hub/settled-roles.ts +1 -1
  111. package/src/hub/snapshots.ts +40 -9
  112. package/src/hub/start-mode.ts +13 -8
  113. package/src/hub/status-roster.ts +8 -0
  114. package/src/hub/tasks.ts +60 -27
  115. package/src/hub/text-projection.ts +7 -3
  116. package/src/hub/tooling.ts +4 -60
  117. package/src/hub/ui.ts +3 -1
  118. package/src/hub/workflow.ts +30 -52
  119. package/src/memory/recall.ts +4 -5
  120. package/src/models/route/signals.ts +3 -1
  121. package/src/pi/launch.ts +5 -3
  122. package/src/ui/index.html +4 -3
  123. package/src/ui/office-model.ts +3 -1
  124. package/src/ui/peer-palette.ts +16 -6
  125. package/templates/AGENTS.block.md +2 -1
  126. package/templates/routing.toml +2 -2
package/docs/sdlc.md CHANGED
@@ -1,4 +1,4 @@
1
- # SDLC for agent-hub (#257)
1
+ # Playbook lifecycle for agent-hub (#257, #481)
2
2
 
3
3
  ## Authority and evidence
4
4
 
@@ -8,15 +8,37 @@ A reviewed head is a full lowercase 40-character SHA observed through a successf
8
8
 
9
9
  ## Default lifecycle
10
10
 
11
- | Stage | Acting role | Artifact or exit gate | Enforcement |
12
- | --- | --- | --- | --- |
13
- | spec | person/planner | issue identity and scope | shown |
14
- | plan | planner/owner | plan, branch, existing predecessor gates | existing Tasks rules |
15
- | implement | owner | PR identity/current head and local check on that head | entering review, when enabled |
16
- | review | assigned reviewer | current head-bound verdict, no pending change request/open thread | approval, when enabled |
17
- | verify | verifier/reviewer | required checks and local gate on the reviewed head | approval, when enabled |
18
- | merge | person | current-head readiness, ordered touching PRs | shown; no mutation |
19
- | release | person | merged commit, version/tag, docs freshness/live receipt checklist | shown; no mutation |
11
+ The built-in lifecycle follows the [AI-Native SDLC Playbook](https://claude.com/resources/articles/the-ai-native-sdlc-playbook). Stage ids describe the hub's artifacts and handoffs; the playbook labels group them into Plan, Design, Build, Test, Deploy and Maintain.
12
+
13
+ | Stage id | Playbook stage | Acting role | Artifact or exit gate | Current enforcement |
14
+ | --- | --- | --- | --- | --- |
15
+ | intent | Plan | person/planner | intent or backlog identity | shown; acceptance gate pending S1 |
16
+ | spec | Design | person/planner | issue identity and scope | shown; acceptance/drift gate pending S1 |
17
+ | plan | Build | planner/owner | plan, branch, existing predecessor gates | existing Tasks rules; conformance pending S2 |
18
+ | build | Build | owner | PR identity/current head and local check on that head | entering review, when enabled |
19
+ | test | Test | verifier/reviewer | required checks and local gate on the reviewed head | approval, when enabled; integrity/evals pending S4/S5 |
20
+ | review | Deploy | assigned reviewer | current head-bound verdict, no pending change request/open thread; AC checklist under Compliance | approval, when enabled; structured findings pending S3 |
21
+ | merge | Deploy | person | current-head readiness, ordered touching PRs | shown; no mutation |
22
+ | release | Deploy | person | merged commit, version/tag, docs freshness/live receipt checklist | shown; authorization pending S6 |
23
+ | maintain | Maintain | person/planner | follow-up intent and operational evidence | shown; metrics/control bands pending S7 |
24
+
25
+ Stored `implement` and `verify` stage ids read as `build` and `test`. A write that names the old id stores the id the configuration lists, so new enabled-lifecycle receipts use the canonical ids. The default remains off, preserving existing off-mode output, receipt and schema shapes. Status, explain, console and dashboard name the playbook stage beside enabled lifecycle ids.
26
+
27
+ ## Playbook alignment
28
+
29
+ | Slice | Playbook play | Delivery in this slice |
30
+ | --- | --- | --- |
31
+ | S0 | Plan, Design, Build, Test, Deploy, Maintain stage model | Nine built-in bases, canonical ids, stored receipt aliases and playbook labels |
32
+ | S1 | Accepted intent/spec and spec drift | Pending; issue metadata acceptance is not yet a hub gate |
33
+ | S2 | Accepted plan and diff conformance | Pending; existing task plans remain authoritative without a conformance readback |
34
+ | S3 | Bugs, Security and Compliance review passes | Pending structured findings; existing reviewer separation and head-bound review remain |
35
+ | S4 | Red-first tests and test integrity | Pending receipts/gates; repository test-integrity rules still apply |
36
+ | S5 | Continuous evals for agent configuration | Pending gate, dependent on #251/#266; no eval pass claimed |
37
+ | S6 | Production approval gate | Pending person-only release authorization; release report stays read-only |
38
+ | S7 | Metrics, deterministic control bands and follow-up intent | Pending; no automated metric triage or security baseline claimed |
39
+ | S8 | Compliance acceptance-criteria checklist | Head-bound checklist retained; legacy workflow and phase compatibility deprecated; task import removed |
40
+
41
+ S0/S8 establish vocabulary and checklist compatibility only. Later slices must retain the same authority contract: the issue is the spec, Tasks owns mutations, GitHub owns PR/check/tag facts, and the hub stores bounded identities, timestamps, hashes and counts instead of remote bodies. No slice writes GitHub or grants capabilities. Advise shows readiness without blocking; enforce applies only the named implemented gates. A full live advise cycle through S1, merge, release and S6 authorization remains pending and cannot be certified by S0/S8 source tests.
20
42
 
21
43
  The repository uses feature branches and PRs. Release PRs require merge commits because documentation stamps name release-branch ancestors. Do not describe squash as a universal release practice. Worktree/branch cleanup follows confirmed merge; installation and native readback are separate live receipts. A successful source gate cannot certify those receipts.
22
44
 
@@ -36,54 +58,57 @@ ahub sdlc status and explain expose mode, stage, gate, observed/reviewed/local-c
36
58
 
37
59
  ## Composition and measurement
38
60
 
39
- The #252 gsd-lite option remains guidance, bounded predecessor edges and criterion/evidence-ref checklists over the same board. It creates no second STATE/PLAN/HANDOFF authority and cannot auto-advance. #259 profiles supply project defaults without changing gate authority. #247 records store ids, timings and outcomes, not plan text. #251 compares off/advise/enforce and default/gsd-lite on a sealed common suite, with failures, unknowns, operator interventions and overhead retained. No experiment was run for this design.
61
+ The acceptance-criteria checklist belongs to the Compliance pass over the same board. It creates no second STATE/PLAN/HANDOFF authority and cannot auto-advance. #259 profiles supply project defaults without changing gate authority. #247 records store ids, timings and outcomes, not plan text. #251/#266 provide the separate evaluation work; no effectiveness comparison or native lifecycle experiment is claimed here.
40
62
 
41
63
  ## Delivery and verification state
42
64
 
43
- The initial implementation adds built-in configuration validation, optional board receipts, fake-gh stale-head/required-check tests, Tasks transition gates and CLI/console/dashboard projections. Bounded custom stage/gate definitions and read-only merge-queue/release-source reports are implemented in the source slices below. Dependency editing/import and the actual native advise-through-merge/release cycle remain pending. The bounded checklist guidance slice below is implemented separately. A native/account cycle must be recorded separately; no merge or release is implied by this document.
65
+ Head-bound review/approval gates, bounded custom definitions, read-only merge/release reports and the S0/S8 vocabulary/checklist migration are implemented in source. S1-S7 and the native advise-through-merge/release cycle remain pending. Installation, native acceptance and release authorization require separate receipts.
44
66
 
45
67
  ## Initial commands
46
68
 
47
- A per-project `.agenthub/sdlc.toml` may set `mode = "advise"` and `required_checks = ["check (ubuntu-latest)", "check (macos-latest)", "seeded guards"]`. Omit the file for off. A missing/empty check set is unknown, never a green approval gate. The built-in stage order is fixed in this slice; changing the file applies at the next hub start.
69
+ A per-project `.agenthub/sdlc.toml` may set `mode = "advise"` and `required_checks = ["check (ubuntu-latest)", "check (macos-latest)", "seeded guards"]`. Omit the file for off. A missing/empty check set is unknown, never a green approval gate. The built-in stage order follows the table above; changing the file applies at the next hub start.
48
70
 
49
- A person records ids and a claimed local receipt with `ahub sdlc record 12 '{"repo":"owner/repo","issue":109,"pr":325,"localHead":"<full SHA>","localResult":"passed","stage":"implement"}'`. Owners/reviewers have the bounded `hub_sdlc_record` tool; it accepts no remote bodies or external-check results. Only the assigned reviewer or person records a reviewed head. `ahub review 12 approved --head <full SHA>` rechecks current evidence before approval. Changes requested does not require green CI.
71
+ A person records ids and a claimed local receipt with `ahub sdlc record 12 '{"repo":"owner/repo","issue":109,"pr":325,"localHead":"<full SHA>","localResult":"passed","stage":"build"}'`. Owners/reviewers have the bounded `hub_sdlc_record` tool; it accepts no remote bodies or external-check results. Only the assigned reviewer or person records a reviewed head. `ahub review 12 approved --head <full SHA>` rechecks current evidence before approval. Changes requested does not require green CI.
50
72
 
51
73
  `ahub sdlc status` lists bounded local projections without polling every remote PR. `ahub sdlc explain 12` or `status 12` performs a fresh structured readback, with a console-only boundary and no remote body retention. Shared console/dashboard projections explicitly show unknown external evidence; they do not certify freshness from cached data. Default task rows and contracts remain unchanged when off.
52
74
 
53
- # gsd-lite checklist guidance
75
+ # Acceptance-criteria checklist
54
76
 
55
- - Select `workflow = "none" | "gsd-lite"` in `.agenthub/sdlc.toml`; none is the default and preserves existing receipts, contracts and traces. Like the SDLC mode, this file is loaded at daemon start. Workflow choice adds guidance; it does not select enforcement.
56
- - A task's reviewer or the person records `phase` through `hub_sdlc_record` or `ahub sdlc record`. The board owns the receipt, with its actor/history. No writable STATE/PLAN/HANDOFF file is introduced.
57
- - A phase names a bounded slug `id`, a full `head`, up to 64 existing task/predecessor records and 1..32 criterion records. `tasks` entries use `{ "id": 2, "after": [1] }`; after must match current Tasks.after edges exactly. Imports refuse missing ids, cycles, closures above 256 tasks and ordinary-parent references to private work. They do not edit dependencies, accepted owners or reservations. Create dependencies through the existing `ahub task propose --after` flow.
58
- - Each criterion has a bounded `id`, boolean `required`, `result` verified|failed|unverified and up to eight hashed refs. Accepted refs are `sha256:<64-hex>`, or `artifact|check|source:<ASCII-reference>@<64-hex>`. Whole phase JSON is at most 32 KiB. The hub never fetches those refs or treats a model's claim as a deterministic check. Verified outcomes are reviewer claims, separately labelled from fresh GitHub checks.
59
- - Required failed/unverified criteria, unfinished child tasks, changed/unavailable dependencies and missing/stale checklist heads make phase readiness false. In advise/off, board transitions retain their existing behavior; readiness can remain false after a historical approval. Enforce adds phase blockers only to the existing approval transition. Entering review remains possible while criteria are unverified, so the reviewer can inspect and record them.
60
- - `ahub sdlc status <id>` and `explain` expose the same workflow/phase projection used by the console and dashboard; `ahub route explain` names active workflow guidance. `ahub sdlc export <id>` emits sanitized ids, states, criteria/hashes and two SHA-256 values: `stateHash` hashes that exact projection, `sourceHash` hashes the normalized effective lifecycle configuration (not a code/build attestation). Private exports contain a stub and no phase refs. Exports are read-only derived views and cannot be imported as task authority.
61
- - Full GSD import, dependency editing, native acceptance and the actual advise-through-merge/release cycle remain pending. Bounded custom gates and the read-only release-source report are implemented below. No automatic advance, merge, release, permission change or experiment is included. The default versus gsd-lite #251 comparison is not run during the solo implementation.
77
+ - A task's reviewer or the person records a head-bound `criteria` receipt through `hub_sdlc_record` or `ahub sdlc record` (an off or older daemon's tool schema still offers `phase`). Its ids name the issue's acceptance criteria. The board owns the receipt and its actor/history; no writable STATE/PLAN/HANDOFF file is introduced.
78
+ - A checklist names a bounded slug `id`, a full `head` and 1..32 criterion records. Each criterion has a bounded `id`, boolean `required`, `result` verified|failed|unverified and up to eight hashed refs. Accepted refs are `sha256:<64-hex>`, or `artifact|check|source:<ASCII-reference>@<64-hex>`. Whole receipt JSON is at most 32 KiB. The hub never fetches those refs or treats a model claim as a deterministic check. Verified outcomes are reviewer claims, separately labelled from fresh GitHub checks.
79
+ - Required failed/unverified criteria and missing/stale checklist heads keep readiness false. Advise/off preserve existing transition behavior. Enforce adds criteria blockers only at approval. Entering review remains possible with unverified criteria so the reviewer can inspect and record them. Criteria claims do not replace exact-head local/CI checks.
80
+ - Once recorded, a checklist adds its blockers in any enabled lifecycle; off ignores it, as #257 did. The `criteria` entry/exit gate uses the same checklist without a workflow selector, and a missing checklist is a blocker the reviewer clears by recording one. Task dependencies remain with `Tasks.after`, created through `ahub task propose --after`. Checklist task import is removed.
81
+ - `ahub sdlc status <id>`, `explain` and console/dashboard projections show the same criteria view. `ahub sdlc export <id>` keeps the #257 schema-1 bytes in plain off (`workflow: "none"`, `phase: null`); an enabled or retired-setting lifecycle emits schema 2, with sanitized state, criteria/hashes and `stateHash`/`sourceHash`: the exact projection hash and normalized effective lifecycle-configuration hash. These are derived hashes, not code/build attestations or import authority. Private exports contain a stub and no checklist refs.
62
82
 
63
- Example reviewer receipt (task 2 already depends on task 1):
83
+ Example reviewer receipt:
64
84
 
65
85
  ```json
66
86
  {
67
- "phase": {
87
+ "criteria": {
68
88
  "id": "delivery-one",
69
89
  "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
70
- "tasks": [{ "id": 2, "after": [1] }],
71
- "criteria": [{ "id": "native-smoke", "required": true, "result": "unverified", "refs": [] }]
90
+ "criteria": [{ "id": "AC0", "required": true, "result": "unverified", "refs": [] }]
72
91
  }
73
92
  }
74
93
  ```
75
94
 
76
- The repeated SHA is a syntax example, not verification evidence. Replace it with the head actually reviewed. A verified criterion requires hashed evidence refs and still does not replace the configured exact-head local/CI gates.
95
+ The repeated SHA is syntax only. Replace it with the reviewed head; a verified criterion also requires hashed evidence refs.
96
+
97
+ ## Retired workflow compatibility (#252, #481 S8)
98
+
99
+ The old `gsd-lite` workflow is retired. Omit `workflow` from `.agenthub/sdlc.toml` and use the criteria checklist above. Until hub 0.13.0, `workflow = "none"` loads silently and `workflow = "gsd-lite"` loads with a migration warning in `hub.log` and makes a criteria checklist required on every task. The `phase` gate and receipt name are compatibility aliases for `criteria` during that same window; use `criteria` for new configuration and writes. From 0.13.0, `workflow`, the `phase` gate and the `phase` receipt name are refused with a field-level reason.
100
+
101
+ Stored phase receipts remain readable: their criteria are shown, while legacy task lists are ignored with a visible note. Reading historical receipts never imports or edits task edges. [The retired analysis](gsd-collaboration-analysis.md) preserves the original rationale and unrun measurement plan; it is not current workflow guidance.
77
102
 
78
103
  # Custom lifecycle definition contract (#257)
79
104
 
80
105
  - The sdlc.toml loader reads a single regular single-link file up to 64 KiB, checks its identity/size across a bounded read and refuses symlinks or changing files.
81
- - Optional `definitions` in sdlc.toml maps a stage id to bounded data: `base`, `actor`, `entry_gates`, `exit_gates` and `after`. `stages` is the explicit ordered list, at most 32 distinct lower-case ASCII ids of at most 48 characters. Built-in names retain their original base; custom ids map to one of the seven existing semantic stages. Unknown keys, gates, actors, duplicate gates and references refuse before startup.
106
+ - Optional `definitions` in sdlc.toml maps a stage id to bounded data: `base`, `actor`, `entry_gates`, `exit_gates` and `after`. `stages` is the explicit ordered list, at most 32 distinct lower-case ASCII ids of at most 48 characters. Built-in names retain their original base; custom ids map to one of the nine semantic bases above. Unknown keys, gates, actors, duplicate gates and references refuse before startup.
82
107
  - The list must contain a review-base and merge-base stage because existing Tasks done/review transitions need those destinations. Other stages can be renamed, inserted or omitted. `after` has at most eight distinct earlier ids; self, forward, missing and cyclic dependencies refuse. Configuration defines no new task states, runnable commands or capability.
83
108
  - Actors are owner, reviewer, person or planner (defaulting to the base's existing actor guidance). A configured actor never grants lifecycle write authority to a new caller. Existing task owner/reviewer/person eligibility remains the outer boundary. In enforce, an explicit custom stage change additionally requires its configured actor; a person may record every stage. A planner must already be an eligible task holder with the existing planner flow role. Off/advise preserve their nonblocking claim behavior.
84
- - Gate names are issue, pr, local, review, checks and phase. A phase gate requires workflow = "gsd-lite"; otherwise configuration refuses before startup. They read only the existing artifact identities, exact-head GitHub readback, claimed local receipt or current board-owned phase checklist. Entry/exit readiness is shown separately from approval readiness. Custom gates cannot remove the existing review/approval floor, turn a missing required-check set green or treat a phase claim as external verification.
109
+ - Gate names are issue, pr, local, review, checks and criteria. The legacy phase gate is accepted only during the deprecation window. They read only the existing artifact identities, exact-head GitHub readback, claimed local receipt or current board-owned acceptance-criteria checklist. Entry/exit readiness is shown separately from approval readiness. Custom gates cannot remove the existing review/approval floor, turn a missing required-check set green or treat a criteria claim as external verification.
85
110
  - Receipts keep their configured stage id. Tasks validates stage names against the active definition before storing. A definition removed after a restart makes the old stage visibly unknown until an eligible person/holder records a valid one; it is never silently remapped. Existing state-derived completion/approval selects the first configured review/merge base destination, not a hard-coded name; no arbitrary stage auto-advance is added.
86
- - CLI, console and dashboard use the same definition/base/actor and entry/exit blocker projection. Every managed native/local task schema advertises the same active stage ids. Separate MCP processes read authenticated daemon status, not a mutable sdlc.toml; a live older daemon without the optional field retains the seven-stage schema. An unavailable/malformed read withholds hub_sdlc_record until a successful tools-list read rather than inventing stages. The default definition produces the old seven-stage config, receipts, schemas and projections unchanged. Native model ingestion and an actual advise/release cycle remain unverified.
111
+ - CLI, console and dashboard use the same definition/base/actor and entry/exit blocker projection. Every managed native/local task schema advertises the same active stage ids. Separate MCP processes read authenticated daemon status, not a mutable sdlc.toml; a live older daemon without the optional field retains the seven-stage schema. An unavailable/malformed read withholds hub_sdlc_record until a successful tools-list read rather than inventing stages. Enabled defaults use the nine playbook bases; off-mode config, receipts, schemas and projections retain their prior shapes. Native model ingestion and an actual advise/release cycle remain unverified.
87
112
 
88
113
  Example:
89
114
 
@@ -98,13 +123,13 @@ actor = "planner"
98
123
  entry_gates = ["issue"]
99
124
 
100
125
  [definitions.draft]
101
- base = "implement"
126
+ base = "build"
102
127
  actor = "owner"
103
128
  after = ["question"]
104
129
  exit_gates = ["local"]
105
130
 
106
131
  [definitions.fact-check]
107
- base = "verify"
132
+ base = "test"
108
133
  actor = "reviewer"
109
134
  after = ["draft"]
110
135
  exit_gates = ["checks"]
@@ -121,7 +146,7 @@ actor = "person"
121
146
  after = ["approve"]
122
147
  ```
123
148
 
124
- These ids are guidance/receipt destinations. Publish neither merges nor releases. The read-only source-readiness report is implemented below. Native acceptance/release permission, bidirectional GSD import and research measurements remain unverified or pending.
149
+ These ids are guidance/receipt destinations. Publish neither merges nor releases. The read-only source-readiness report is implemented below. Native acceptance/release authorization and research measurements remain unverified or pending.
125
150
 
126
151
 
127
152
  # Structured documentation readiness evidence (#257)
@@ -167,4 +192,4 @@ After dashboard authentication, those two delivery operations get a 35-second HT
167
192
 
168
193
  The connection idle setting is not a guaranteed 10-second deadline for an active handler. The actual HTTP fixture records that an ordinary action also completes after 11.5 seconds; that legacy behavior is retained. The owner approved correcting only the newly added test's mistaken ordinary-action failure expectation. Manager control request caps are separate from HTTP connection idle behavior. Delivery HTTP results omit the internal control-request rid.
169
194
 
170
- - Runtime doc checker ships explicitly in the npm tarball and package verification asserts its presence. Merge queue phase readiness derives each holder's current board phase view under gsd-lite; absent or blocked phases remain unknown/blocked.
195
+ - Runtime doc checker ships explicitly in the npm tarball and package verification asserts its presence. Merge queue criteria readiness derives each holder's current board checklist view; absent or blocked required checklists remain unknown/blocked.
package/docs/security.md CHANGED
@@ -7,7 +7,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
7
7
  - **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
8
8
  - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. External clients cannot claim the console user's id or a hub-managed peer's id.
9
9
  - **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default.
10
- - **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`) apply only from a config file git confirms nobody committed: `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot has a 10 s cap). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
10
+ - **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`, `instances`) apply only from a config file git confirms nobody committed (`instances` is read only from an untracked `.agenthub/config.local.json`; in `config.json` it is ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot has a 10 s cap). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
11
11
  - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. It stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
12
12
  - **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
13
13
  - **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
@@ -67,7 +67,7 @@ the console. Merged and generated Claude settings live in 0600 state files and
67
67
  are passed by path, keeping caller settings out of process argv.
68
68
 
69
69
  Claude ask-when-needed grants only resolving project file targets outside
70
- .agenthub, .git, .claude, .codex, .qwen, .kimi, .kimi-code, .pi, .mcp.json, .agents and .claude.json. Symlink escapes, protected aliases, traversal and
70
+ .agenthub, .git, .claude, .codex, .qwen, .kimi, .kimi-code, .jcode, .pi, .mcp.json, .agents, .agent, _agents, _agent, .gemini, .grok and .claude.json. Symlink escapes, protected aliases, traversal and
71
71
  unresolved wildcard targets receive no decision. Names are compared folded (letter case, compatibility forms and
72
72
  the code points HFS+ ignores; case-insensitive APFS folds case but ignored none in the probe; so a look-alike such
73
73
  as a long s or a name with a zero-width joiner is the same name), the path is judged inside the project, and
@@ -163,13 +163,13 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
163
163
 
164
164
  ## Dashboard settings and the settings session (issue #269)
165
165
 
166
- - Settings are a closed registry (`src/hub/settings.ts`). The dashboard and `ahub settings` can write only its keys, each checked against its own closed values, and only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`. A file git tracks is never written, so the shared `config.json` and `routing.toml` stay byte-identical; in a repository where git cannot answer, nothing is written either. Commands and binaries (`kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`), credential files and gateway URLs (`omniroute`, `memory.worker_url`), the local worker's reach (`local.read_allow`, `local.bash_network`, `local.network_allow`), `bench.enabled` and `terminal` are refused by name from every surface: they choose what the hub runs or where secrets and task text go.
166
+ - Settings are a closed registry (`src/hub/settings.ts`). The dashboard and `ahub settings` can write only its keys, each checked against its own closed values, and only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`. A file git tracks is never written, so the shared `config.json` and `routing.toml` stay byte-identical; in a repository where git cannot answer, nothing is written either. Commands and binaries (`kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`), credential files and gateway URLs (`omniroute`, `memory.worker_url`), the local worker's reach (`local.read_allow`, `local.bash_network`, `local.network_allow`), `bench.enabled`, `terminal` and `instances` are refused by name from every surface: they choose what the hub runs or where secrets and task text go.
167
167
  - `routing.local.toml` holds `stay_switch` and, per class, `peers`, `escalate_to`, `route` (an id `routing.toml` defines) and `pi_backend`, and nothing else: targets, routes, signals and the PII constraint cannot be set through it. A write is read by the hub's own loader before it replaces the file (temp file and rename), and one previous version is kept for undo.
168
168
  - An ordinary dashboard session (`ahub ui`) reads settings, lowers a mode to `ask`, turns `pi.auto_start`, `task_sweep.enabled` or `task_sweep.auto_reassign` off, sets a start mode back to `tui` or a WIP limit to 0, and changes the safe switches (`research.enabled`, `approvals.notify`, `snapshots.enabled`, `coordination`). Whatever can widen what agents do without asking needs a settings session: a non-ask mode now or at hub start, routing, starting Pi with the hub, removing a machine-local value, and undo. `ahub ui --settings` opens one: its ticket marks the session for 15 minutes, after which it is an ordinary session. `never-ask` also needs the peer's id typed as the confirmation, and a stored never-ask default still waits for the console's `y` at each hub start. An undo never puts never-ask back, and removing a machine-local value that uncovers a never-ask in the shared file needs the same confirmation. "Lowering to `ask`" means handing the decision back to the agent's own rules: for Codex that is the approval policy the person configured natively, which the hub does not judge.
169
169
  - A settings session is as strong as the console for these settings and no stronger elsewhere. A mode change is the console's own request with the same refusals; tool approvals stay deny-only for Pi and local; there is still no shell, file, task-detail or launch-command API. `ahub ui --settings` and `ahub settings` pass the same identity gate as `ahub permission`, which is an operating policy, not authentication. The hostile-agent boundary above applies unchanged: an agent with an unrestricted shell as the same user can impersonate the console, and so can open a settings session.
170
170
  - Every change writes a `settings` event (the key, the value before and after, the source `dashboard` or `terminal`) and a line in `hub.log` and open consoles; opening a settings session is logged too. The unified dashboard (`ahub ui --all`) forwards settings actions as an ordinary session and opens no settings session.
171
171
  - Configuration reload (issue #439). The daemon watches `config.json`, `config.local.json`, `routing.toml` and `routing.local.toml` and re-reads the complete candidate after a change, as the dashboard's Reload configuration and `ahub settings reload` do; an invalid candidate applies nothing. Each changed key is then authorized on its own against its file's actual git provenance. A privacy tightening (local-only PII, the model screen on, added PII patterns, narrower roles or capability lists, unless it moves the planner or conductor) and a safe registered setting apply from any file. Every other change, machine-local fields included, keeps its running value when its file is tracked or git cannot answer. From an untracked file such a guarded change is adopted only by `ahub settings reload` in an interactive terminal, after it lists each guarded key with its full before and after values and the person types `y` within 30 seconds; the confirmation is bound to the file contents and accepted state it was shown for. File watches and dashboard reloads, a settings session's included, never confirm, and no reload changes the roster: membership moves only through the roster setting. Keys whose consumers are bound at start (memory, the gateway and MLX, checks, the privacy and sandbox policy of existing sessions, the terminal provider and others) stay pending until a restart.
172
- - Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
172
+ - Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). A declared extra instance accepts only `headless` from any surface. For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
173
173
 
174
174
  ## Multi-project manager (protocol 7)
175
175
 
@@ -218,6 +218,9 @@ console request; pending permissions are withdrawn on watchdog, offline or stop,
218
218
  and a late person answer cannot grant them (#311).
219
219
 
220
220
  The shared scoped-grant exclusion also covers `.agents` skills and `.claude.json`.
221
+ It also covers the Antigravity, Gemini CLI and Grok project configuration
222
+ directories `.agent`, `_agents`, `_agent`, `.gemini` and `.grok` (#485), folded like
223
+ the other names; look-alikes such as `.agent-backup/` or `src/gemini.ts` stay grantable.
221
224
  The latter is conservatively protected by name; its default documented location
222
225
  is in the home directory, not a project loader. The source inventory and limits
223
226
  are in [the configuration inventory](verification/2026-10-11-agent-config-inventory.md).
package/docs/smoke.md CHANGED
@@ -637,7 +637,7 @@ Needs a model gateway in `omniroute.urls` (for the owner: the campus gateway ove
637
637
  2. `ahub task propose bulk_edit "Fix the spelling mistakes in words.ts" --path words.ts`: `local` accepts, edits, calls `hub_task_done`; `ahub board` shows it `approved` (no reviewer attached) or `in_review`.
638
638
  3. `ahub task propose test "Check that words.ts contains ..."`: Kimi takes it and reports through the hub's MCP tools (`ahub task show <id>` history: `kimi accepted`, `kimi done`).
639
639
  4. PII: propose a task whose title matches `signals.pii_patterns`. Owner `local`, reviewer `user`; `grep <value> .agenthub/state/hub.log` and the `ahub tail` output find nothing; `ahub board` shows `[pii]`; `ahub task show <id>` shows the text; `ahub review <id> approved` closes it; claude-mem has no row with the value.
640
- 5. With Claude and Codex attached: a task proposed by Claude, done by Codex, reaches Claude as a review; two `changes_requested` move it to the next peer in `escalate_to`.
640
+ 5. With Claude and Codex attached: a task proposed by Claude, done by Codex, reaches Claude as a review; `tasks.escalate_after` consecutive `changes_requested` (default 3) move it to the next peer in `escalate_to`.
641
641
 
642
642
  ## M5: budget relay
643
643