@staix/agent-hub 0.19.0 → 0.21.0

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 (184) hide show
  1. package/CHANGELOG.md +70 -3
  2. package/README.md +2 -2
  3. package/docs/agent-notes/README.md +11 -0
  4. package/docs/agent-notes/adapters.md +10 -5
  5. package/docs/agent-notes/benchmarks.md +27 -0
  6. package/docs/agent-notes/budget.md +2 -1
  7. package/docs/agent-notes/bus.md +3 -3
  8. package/docs/agent-notes/daemon.md +20 -7
  9. package/docs/agent-notes/local-worker.md +5 -0
  10. package/docs/agent-notes/models.md +2 -0
  11. package/docs/agent-notes/tasks.md +20 -10
  12. package/docs/agent-notes/tests.md +13 -5
  13. package/docs/bench.md +1 -1
  14. package/docs/data-policy.md +2 -0
  15. package/docs/dogfood.md +41 -5
  16. package/docs/events.md +2 -1
  17. package/docs/memory.md +9 -3
  18. package/docs/operations.md +598 -63
  19. package/docs/profiles.md +13 -1
  20. package/docs/quickstart.md +18 -9
  21. package/docs/research.md +7 -4
  22. package/docs/sdlc.md +66 -10
  23. package/docs/security.md +397 -35
  24. package/docs/smoke.md +127 -0
  25. package/docs/specs/2026-09-19-agent-hub-design.md +1149 -19
  26. package/docs/verification/2026-10-27-0.19.0.md +5 -5
  27. package/docs/verification/2026-10-27-0.20.0.md +75 -0
  28. package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-bash-permission.json +635 -0
  29. package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-permission-transport.json +403 -0
  30. package/docs/verification/2026-10-28-claude-implementer-permission-triage.md +123 -0
  31. package/docs/verification/2026-10-28-jcode-0.99.1-usage.md +41 -0
  32. package/docs/verification/2026-10-28-realpath-name-audit.md +222 -0
  33. package/docs/verification/2026-10-28-routing-f0bb8ad0.md +1000 -0
  34. package/docs/verification/2026-10-29-0.21.0.md +61 -0
  35. package/docs/verification/2026-10-29-model-effort-capabilities.md +123 -0
  36. package/docs/verification/2026-10-29-modelrouting-b152ddb9.md +953 -0
  37. package/docs/verification/claude-implementer-permission-triage.ts +160 -0
  38. package/docs/verification/claude-permission-transport-native.py +71 -14
  39. package/docs/verification/profiles/YYYY-MM-DD-research.md +1 -1
  40. package/docs/verified.json +26 -18
  41. package/package.json +2 -2
  42. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  43. package/plugins/agent-hub/server.js +1116 -284
  44. package/src/adapters/acp.ts +87 -3
  45. package/src/adapters/channel-peer.ts +11 -5
  46. package/src/adapters/claude-channel.ts +13 -6
  47. package/src/adapters/claude-implementer.ts +61 -7
  48. package/src/adapters/codex-appserver.ts +88 -5
  49. package/src/adapters/conformance/enrollment.ts +4 -4
  50. package/src/adapters/conformance/factories.ts +2 -1
  51. package/src/adapters/conformance/fakes/jcode-agent.ts +6 -2
  52. package/src/adapters/conformance/kit.ts +10 -6
  53. package/src/adapters/definitions/builtin-metadata.ts +3 -114
  54. package/src/adapters/definitions/native-config-defaults.ts +1 -1
  55. package/src/adapters/definitions/native-config-policy.ts +3 -2
  56. package/src/adapters/definitions/native-context.ts +12 -0
  57. package/src/adapters/external-metadata.ts +48 -0
  58. package/src/adapters/external-modules.ts +63 -3
  59. package/src/adapters/jcode.ts +6 -2
  60. package/src/adapters/local-worker.ts +42 -25
  61. package/src/adapters/native-proof-ports.ts +10 -0
  62. package/src/adapters/pi.ts +7 -0
  63. package/src/adapters/registry-types.ts +4 -0
  64. package/src/adapters/reviewer-print.ts +1 -0
  65. package/src/broker/protocol.ts +203 -0
  66. package/src/broker/server.ts +314 -0
  67. package/src/broker/store.ts +548 -0
  68. package/src/cli/bench.ts +3 -2
  69. package/src/cli/broker.ts +58 -0
  70. package/src/cli/conductor-recovery.ts +142 -0
  71. package/src/cli/configured-acp-entry.ts +26 -6
  72. package/src/cli/console-state.ts +59 -17
  73. package/src/cli/console.ts +17 -2
  74. package/src/cli/facts-hook.ts +3 -1
  75. package/src/cli/help.ts +28 -16
  76. package/src/cli/identity.ts +23 -10
  77. package/src/cli/init.ts +34 -7
  78. package/src/cli/intake-output.ts +33 -0
  79. package/src/cli/main.ts +314 -71
  80. package/src/cli/output.ts +41 -1
  81. package/src/cli/recovery-package.ts +18 -1
  82. package/src/cli/role-holders.ts +35 -0
  83. package/src/cli/room.ts +304 -0
  84. package/src/cli/say-input.ts +16 -0
  85. package/src/cli/schedule-output.ts +49 -0
  86. package/src/cli/sdlc-output.ts +44 -6
  87. package/src/cli/smoke-pty-bridge.ts +33 -0
  88. package/src/cli/smoke-pty.ts +145 -0
  89. package/src/cli/smoke-run.ts +139 -0
  90. package/src/cli/smoke-target.ts +130 -0
  91. package/src/cli/source-output.ts +29 -0
  92. package/src/cli/task-output.ts +3 -2
  93. package/src/cli/upgrade-runtime.ts +98 -16
  94. package/src/cli/upgrade.ts +66 -7
  95. package/src/hub/ask.ts +76 -12
  96. package/src/hub/board.ts +132 -22
  97. package/src/hub/bus.ts +399 -56
  98. package/src/hub/child-process.ts +2 -0
  99. package/src/hub/ci-watch.ts +162 -0
  100. package/src/hub/claude-failure-stage.ts +85 -0
  101. package/src/hub/claude-launch-proof.ts +67 -2
  102. package/src/hub/claude-shell-wrapper.ts +11 -5
  103. package/src/hub/conductor.ts +17 -4
  104. package/src/hub/config-apply.ts +32 -5
  105. package/src/hub/config-reload.ts +7 -5
  106. package/src/hub/config-trust.ts +8 -1
  107. package/src/hub/context-window.ts +33 -3
  108. package/src/hub/control-client.ts +14 -2
  109. package/src/hub/corpus-config.ts +34 -0
  110. package/src/hub/corpus-evidence.ts +184 -0
  111. package/src/hub/corpus.ts +373 -0
  112. package/src/hub/cost-governance.ts +26 -0
  113. package/src/hub/daemon.ts +1228 -242
  114. package/src/hub/deliverable-files.ts +8 -2
  115. package/src/hub/delivery-journal.ts +123 -17
  116. package/src/hub/dogfood-profile.ts +32 -5
  117. package/src/hub/dogfood-read.ts +6 -2
  118. package/src/hub/envelope.ts +11 -0
  119. package/src/hub/eval-gate.ts +131 -0
  120. package/src/hub/events.ts +1 -1
  121. package/src/hub/external-instance.ts +69 -0
  122. package/src/hub/facts.ts +15 -5
  123. package/src/hub/folder-trigger.ts +223 -0
  124. package/src/hub/hub-tools.ts +2 -1
  125. package/src/hub/inference.ts +20 -5
  126. package/src/hub/instance-config.ts +69 -3
  127. package/src/hub/instance-execution.ts +6 -2
  128. package/src/hub/instances.ts +32 -4
  129. package/src/hub/intake.ts +125 -0
  130. package/src/hub/learning.ts +85 -0
  131. package/src/hub/lifecycle.ts +7 -3
  132. package/src/hub/limits.ts +12 -5
  133. package/src/hub/native-permission-request.ts +77 -0
  134. package/src/hub/operational-authority.ts +63 -5
  135. package/src/hub/operational-settings.ts +76 -0
  136. package/src/hub/operational-view.ts +1 -1
  137. package/src/hub/packs.ts +132 -0
  138. package/src/hub/path-policy.ts +7 -0
  139. package/src/hub/peer-doing.ts +130 -0
  140. package/src/hub/peers.ts +46 -0
  141. package/src/hub/queue-backpressure.ts +58 -0
  142. package/src/hub/read-tools.ts +2 -0
  143. package/src/hub/recovery-authority.ts +151 -0
  144. package/src/hub/recovery-policy.ts +220 -0
  145. package/src/hub/research.ts +24 -14
  146. package/src/hub/restart.ts +7 -0
  147. package/src/hub/routing.ts +54 -11
  148. package/src/hub/schedules.ts +266 -0
  149. package/src/hub/sdlc-merge.ts +38 -14
  150. package/src/hub/sdlc-metrics.ts +32 -10
  151. package/src/hub/sdlc-queue.ts +7 -1
  152. package/src/hub/sdlc-release.ts +1 -1
  153. package/src/hub/sdlc.ts +37 -7
  154. package/src/hub/security-baseline.ts +38 -0
  155. package/src/hub/settings.ts +7 -1
  156. package/src/hub/shipped-packs.ts +8 -0
  157. package/src/hub/snapshots.ts +72 -6
  158. package/src/hub/source-read.ts +160 -0
  159. package/src/hub/sources.ts +311 -0
  160. package/src/hub/task-shell-default.ts +3 -2
  161. package/src/hub/tasks.ts +1047 -115
  162. package/src/hub/workflow.ts +1 -1
  163. package/src/local/deny.ts +21 -0
  164. package/src/local/doc-read.ts +69 -0
  165. package/src/local/proxy.ts +4 -13
  166. package/src/local/sandbox.ts +3 -1
  167. package/src/local/tools.ts +37 -3
  168. package/src/local/web-fetch-config.ts +43 -0
  169. package/src/local/web-fetch.ts +183 -0
  170. package/src/memory/brief.ts +85 -13
  171. package/src/memory/capture.ts +1 -1
  172. package/src/rooms/application.ts +114 -0
  173. package/src/rooms/boundary.ts +103 -0
  174. package/src/rooms/commands.ts +294 -0
  175. package/src/rooms/crypto.ts +371 -0
  176. package/src/rooms/link.ts +584 -0
  177. package/src/rooms/policy.ts +82 -0
  178. package/src/rooms/remote-peer.ts +80 -0
  179. package/src/rooms/service.ts +426 -0
  180. package/src/rooms/status.ts +146 -0
  181. package/src/rooms/store.ts +1210 -0
  182. package/src/rooms/wire.ts +465 -0
  183. package/src/ui/index.html +91 -4
  184. package/templates/packs/example.pack.json +13 -0
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.19.0 and control protocol 16. Live verification
3
+ This guide describes ahub 0.21.0 and control protocol 16. Live verification
4
4
  results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
5
5
 
6
6
  ## Agent-operated live verification
@@ -67,7 +67,7 @@ smoke ledger; the presence of this option proves no outstanding #273 live leg.
67
67
  `status --all` uses the projects table. `queue show`, `models status` and `budget execution status` show labelled fields; add `--json` for the raw fetched document.
68
68
  `report --by task` keeps the attribution and coverage sentences with section headings and aligned counters.
69
69
  Use `--json` for scripts; it returns the fetched data without color, and `status --json` keeps its existing document.
70
- `sdlc status`, `sdlc explain`, `sdlc queue`, `sdlc release`, `sdlc metrics`, `sdlc costs` and `sdlc authorize-release` print readable output by default (#814: aligned columns, wrapped text, readable times, `--color=auto|always|never`). A script that parsed their JSON must pass `--json`, which prints exactly the document they printed before. `sdlc export` is a data format and stays JSON, as do `export`, `research export` and `bench export`.
70
+ `sdlc status`, `sdlc explain`, `sdlc queue`, `sdlc release`, `sdlc metrics`, `sdlc costs`, `sdlc merge` and `sdlc authorize-release` print readable output by default (#814, #899: aligned columns, wrapped text, readable times, `--color=auto|always|never`). A script that parsed their JSON must pass `--json`, which prints exactly the document they printed before. `sdlc export` is a data format and stays JSON, as do `export`, `research export` and `bench export`.
71
71
  `restart` and `upgrade` off an interactive terminal (a pipe, `--dry-run`), `recovery status` and the results of `recovery reattach`, `resume`, `abort` and `dispose`, `models setup` and `start`, `queue resolve`, and `budget execution configure` and `disable` print readable text by default (#814); a script that parsed their JSON must pass `--json`, which prints exactly the document they printed before, including `restart --json --yes`. A readable plan lists each project, each peer with how it runs and what happens to it, every blocker, every reconnect-only session and every fresh start, and says nothing was changed; `--json` keeps those as stderr lines. A pipe wraps at `COLUMNS` when it is set, else at 80 columns.
72
72
  `task show`, `task cancel`, `backlog groom`, `backlog return|plan|pin|split|undo|pull-ready` and the `release` commands (`list`, `show`, `new`, `plan`, `activate|freeze|ship|drop`) print a readable block by default (#814): the task or release as the hub holds it, a table for lists, every identifier whole, long text wrapped to the terminal. A script that parsed their JSON must pass `--json`, which prints the hub tool's text exactly as before. A private task arrives from the hub as its stub and is shown as that stub. Because these commands also take free text, `--json` and `--color=` count as flags only at the start or the end of the arguments: `--json` in the middle of a reason is part of the reason, and a value flag's value (`task cancel --reason --json`) is never a flag. A reason that ends in a bare `--json` loses it to the flag. `backlog add|pull`, `task escalate|assign|unassign` already answered one sentence and are unchanged.
73
73
  In `projects --json` and `status --all --json`, an unavailable hub that explicitly refuses an inspection for SQLite contention also carries `failure: { "code": "database_busy" }`; it is additive metadata alongside the neutral `error` sentence.
@@ -219,20 +219,21 @@ the stream; only the header is tab-flattened before tokenizing. Body lines retai
219
219
  the previous rendering and stay plain, as does command output; colour never changes
220
220
  what the panel's text says.
221
221
 
222
- Peers, Approvals, Tasks, Queue, Settings and Usage are tables with a header row. A column is as
222
+ Peers, Approvals, Tasks, Queue, Settings, Usage and Rooms are tables with a header row. A column is as
223
223
  wide as its widest cell, up to a third of the terminal; the last column takes
224
224
  the rest and is cut with `...`. In Tasks, TITLE takes the rest and the STAGE
225
225
  meter keeps a fixed column at the right edge. In Usage, LAST RESET is the column that gives way on a narrow terminal.
226
226
 
227
227
  | Panel | Columns |
228
228
  |---|---|
229
- | Peers | PEER STATE MODE LINK Q ! WIP REVIEW PAUSE QUOTA MODEL |
229
+ | Peers | PEER STATE MODE LINK Q ! WIP REVIEW PAUSE QUOTA MODEL DOING |
230
230
  | Approvals | ID PEER LEFT TITLE |
231
231
  | Tasks | ID STATE OWNER REVIEWER CLASS AGE TITLE STAGE |
232
232
  | Tasks, backlog view (`b`) | ID PRI RESERVED DEPS CLASS AGE TITLE |
233
233
  | Queue | ID PEER STATE REV AGE |
234
234
  | Settings | GROUP KEY VALUE SOURCE APPLIES |
235
235
  | Usage | PEER TODAY 7D LIFETIME PAUSE QUOTA LAST RESET |
236
+ | Rooms | ROOM LINK MEMBERS REMOTE |
236
237
 
237
238
  In Tasks, `b` toggles open work and backlog. The backlog heading counts its rows;
238
239
  rows sort by rank, then id. PRI shows priority/rank, RESERVED the reserved peer,
@@ -263,6 +264,20 @@ including `0/2`; a peer without an entry reads `-`. At or above the limit, the
263
264
  cell uses the attention tone. At narrow widths WIP gives way before PEER, STATE
264
265
  and QUOTA, whether or not MODE is present.
265
266
 
267
+ DOING (#878) is what a peer is working on, from signals the hub already receives:
268
+ a Claude session's title (its transcript's latest `ai-title` row) and the
269
+ in-progress item of its `TodoWrite` list or the task `TaskUpdate` put in
270
+ progress; a Codex thread's name and its plan's in-progress step; an ACP agent's
271
+ plan entry in progress. The current item wins over the title, cut to 120
272
+ characters. A peer with no such source, or one that is offline, shows nothing,
273
+ and the column appears only when some peer has a value; it is the first column
274
+ to give way on a narrow terminal. The value is a `[private]` stub while the peer
275
+ owns or reviews a PII task or is in a private turn, when it was observed then,
276
+ or when the public metadata screen refuses it or fails. `ahub status`, the
277
+ dashboard's peer rows and the conductor's `hub_status` show the same value; it
278
+ lives in the daemon's memory only, never in `status.json`, a log, an event or
279
+ memory capture. Board WIP is unchanged.
280
+
266
281
  In Peers, select a row and press `w` to edit `tasks.wip_limits.<peer>`, prefilled
267
282
  with its current limit. The editor stays on Peers and uses the existing Settings
268
283
  value, daemon preview, nonempty reason and `y` confirmation flow; Esc cancels without writing. The
@@ -343,15 +358,16 @@ The local worker and headless ACP peers, including Jcode, receive tools only ins
343
358
  Every console resolution needs a reason. Queue `discard` removes queued originals or
344
359
  settles a needs_review receipt; it never cancels a task or native turn.
345
360
 
346
- `ahub task unassign <id> [--role owner|reviewer] [--revision N]` is the terminal
347
- equivalent. Owner removal is allowed in `proposed`, `in_progress` and
361
+ `ahub task unassign <id> [--role owner|reviewer] [--revision N]` is available to
362
+ the person or configured conductor. The board records the caller that performed
363
+ the unassign. Owner removal is allowed in `proposed`, `in_progress` and
348
364
  `changes_requested`, returns active work to `proposed`, and clears any owner
349
365
  reservation. It is refused in `in_review`, `approved` and `backlog`. Reviewer
350
366
  removal retains the state, including `in_review`, and refuses to remove the human
351
367
  reviewer of PII work. No replacement is routed and no native process or external
352
- effect is rolled back. A person-unassigned task stays out of automatic owner
368
+ effect is rolled back. An owner-unassigned task stays out of automatic owner
353
369
  offers across dependency release, planner-pull processing, release timers and
354
- restarts until an explicit assignment or reservation. The later unassign also
370
+ restarts until a later authorized explicit assignment or reservation. The later unassign also
355
371
  fences planner undo, so undo and re-pull cannot restore the removed reservation.
356
372
  Former queued assignment or review requests are withdrawn without resolving
357
373
  uncertain deliveries, and the former agent loses current authority. A withdrawal
@@ -370,8 +386,8 @@ the time); `dispatching` and `accepted` deliveries stay refused, and `retry` kee
370
386
  `stale revision`, `unknown delivery`, `already resolved`, `queued delivery is already scheduled`, `recipient queue is full`, `delivery journal unavailable`), never one
371
387
  generic sentence (#713).
372
388
 
373
- `ahub queue discard --peer <id> --reason <text> [--state queued|needs_review] [--yes] [--json]` clears one peer's backlog and is for the person only (an agent identity is
374
- refused). Without `--yes` it only says how many deliveries it would discard; with `--yes` it discards each queued (default) or `needs_review` delivery of that peer through the
389
+ `ahub queue discard --peer <id> --reason <text> [--state queued|needs_review] [--yes] [--json]` clears one peer's backlog and is for the person or the configured conductor (any other agent identity is
390
+ refused, and a remote `peer@label` queue is the person's only). Without `--yes` it only says how many deliveries it would discard; with `--yes` it discards each queued (default) or `needs_review` delivery of that peer through the
375
391
  same resolution as `queue resolve --action discard`, each recorded with the reason, and prints `discarded N of M` with the causes of any it could not discard. Only the
376
392
  named peer's queue is read, so other peers are untouched; it is refused while recovery or shutdown holds queue mutations. In the console Queue panel, `D` on a row opens
377
393
  the same command for that row's peer, asks for a reason, then a `y` that names the count. No task is cancelled and no native turn is stopped.
@@ -551,16 +567,19 @@ and so is every help entry the gate has no class for yet;
551
567
  | `ahub release show <name>` | Allowed | public release metadata; private scope and internal history remain withheld |
552
568
  | `ahub projects [--json] [--color=...]` | Allowed | lists registered projects; `projects remove` stays Console |
553
569
  | `ahub models status [--json] [--color=...]` | Allowed | reads local model state; `setup\|start\|stop` stay Console |
554
- | `ahub recovery status [<op-id>] [--json]` | Allowed | reads a preflight operation; the bare interactive screen, `resume`, `abort` and `reattach` stay Console |
555
- | `ahub upgrade ... --dry-run` (no `--yes`) | Allowed | reviews the upgrade plan and applies nothing; `--yes` stays Console |
556
- | `ahub restart --dry-run [--json]` (no `--yes`) | Allowed | reviews the restart plan and applies nothing; `--yes` stays Console |
570
+ | `ahub recovery status [<op-id>] [--json]` | Allowed | reads a preflight operation; the bare interactive screen and `reattach` stay Console |
571
+ | `ahub upgrade ... --dry-run` (no `--yes`) | Allowed | reviews the upgrade plan and applies nothing; apply is a separate person or conductor operation |
572
+ | `ahub restart --dry-run [--json]` (no `--yes`) | Allowed | reviews the restart plan and applies nothing |
573
+ | `ahub task cancel\|label\|role <id> ...` | Allowed | the daemon decides per task (#933): `label` and `role` only on a task the caller owns, `role` not while it is in review, and `cancel` by the `hub_task_cancel` rule (the planner's own unaccepted backlog or proposed work) |
574
+ | `ahub deliverables [--task <id>] [--approved] [--check] [--json]` | Allowed | only the rows `hub_task_show` would show the caller, through the same public screening (#933) |
575
+ | `ahub room list [--json]`, `ahub room status [<name>] [--json]` | Allowed | room metadata only; every other `room` form, the inbox included, stays Console |
557
576
 
558
577
  `backlog list`, `release list` and `sdlc costs|metrics` retain their existing
559
578
  planner/person policy. Their CLI forms stay Console for ordinary agents; a
560
579
  configured planner can use the existing planner tools without borrowing console
561
580
  authority. All newly admitted reads use the ordinary tools connection and public
562
581
  views, never console identity. The person's full readback stays unchanged.
563
- Conductor operations are listed in [the operational command table](#conductor-operations-812-t2).
582
+ Conductor operations are listed in [the operational command table](#conductor-operations-812-t2-933-900).
564
583
 
565
584
  ## Conducting a team from Claude Code or Codex
566
585
 
@@ -596,8 +615,11 @@ cause an otherwise unassigned owner to claim duplicate work.
596
615
  The person answers approvals in the console, resolves `needs_review` deliveries
597
616
  with `ahub queue resolve`, stops a headless peer with `ahub stop`, and handles
598
617
  budget overrides and hub lifecycle.
599
- Running these commands from an agent shell is refused; the CLI retains the
600
- agent's identity even when invoked through a shell tool.
618
+ The configured conductor may itself run `queue resolve`, `stop <peer>` and the
619
+ other forms in [the operational command table](#conductor-operations-812-t2-933-900);
620
+ approvals and budget overrides stay the person's. From any other agent shell these
621
+ commands are refused; the CLI retains the agent's identity even when invoked
622
+ through a shell tool.
601
623
 
602
624
  `conductor.feed` is `own` by default, `all` for all tasks, or `off`. Ordinary
603
625
  milestones share the existing digest window and collapse repeated queued
@@ -908,13 +930,17 @@ directly under a `.git`, `commondir` below it, and `.agenthub` anywhere in the
908
930
  project, plus `config.worktree` beside each such `config`. A submodule's git
909
931
  directory gets the same `config` and `hooks` refusal under every `modules/`
910
932
  tree the project can reach: the common dir's, the worktree's own and every
911
- linked worktree's (#281, #518). Nothing else may be written under such a tree
933
+ linked worktree's (#281, task 518). Nothing else may be written under such a tree
912
934
  outside a discovered submodule git dir (#865). A command can therefore not
913
935
  create, rename or remove a `.git`: `git init`, `git clone` and
914
936
  `git worktree add` do not run inside the sandbox, and neither do
915
937
  `git worktree remove`, `git worktree prune` of a stale record,
916
938
  `git submodule update --init`, `git clean -ffd` over a nested repository or
917
- `rm -rf` of a directory that holds a `.git`. `git add`, `commit`, `stash`, `checkout` and `gc` work, and
939
+ `rm -rf` of a directory that holds a `.git`. The shared object store takes
940
+ only new loose objects (#864): `objects/info` (alternates, commit-graph) and
941
+ `objects/pack` are closed and an existing object cannot be changed, removed or
942
+ replaced, so `git gc`, `repack`, `fetch` and `maintenance` do not run inside
943
+ the sandbox either. `git add`, `commit`, `stash` and `checkout` work, and
918
944
  `.gitignore` and `.github/` stay writable; a commit that your git configuration
919
945
  signs with a key under `~/.ssh` or `~/.gnupg` fails, because the sandbox cannot
920
946
  read those directories. Run a refused command yourself, in a terminal.
@@ -1124,7 +1150,7 @@ listed and the rest are counted. The block is advice: deleted definitions are no
1124
1150
 
1125
1151
  The dashboard's task sheet has the same block as a "Code" group (#684 phase 2c): the changed symbols as buttons marked `matching` or `unplanned` against the plan, the plan names nothing changed under as `untouched`, and the same advisory line (deleted symbols are not reported, an incomplete observation may hide a changed one). The hub classifies them (`codeGroup` on each task of the UI snapshot, from the same public observation the console reads), so the page never matches names itself. A symbol opens the Memory tab with its canonical node (`symbol:<name>@<path>`) as the graph start; the list is capped at 40 with the count of what is left out, and a summary line counts matching, unplanned and untouched before the cap. A plan name the hub withholds (screened out or malformed) is counted, and the line then says a symbol marked unplanned may be in the plan. Without code facts the group reads `unknown (no code facts)`, and a private task has no group at all, so the sheet shows nothing for it.
1126
1152
 
1127
- Task detail also shows an `overlaps` block (#379), using the hub's read-only
1153
+ Task detail also shows an `overlaps` block (task 379), using the hub's read-only
1128
1154
  `symbolOverlaps` projection of its existing open-task/other-owner overlap selection.
1129
1155
  Each row names the other task ID and screened shared paths. A row says "same file,
1130
1156
  different symbols" only when the same current screened predicate used by completion
@@ -1149,10 +1175,10 @@ a reviewable path unaccounted is refused, and advise notes the gap in the verdic
1149
1175
  (#520). The hub
1150
1176
  records how each review turned out, per implementer, reviewer and class:
1151
1177
  approved; caught (changes were requested and the owner's redo was approved);
1152
- contradicted (within a week, work on the same file or symbol failed its check
1178
+ contradicted (within `tasks.contradiction_window_days`, default 7, work on the same file or symbol failed its check
1153
1179
  or review); escalated (after the reviewer asked for changes). A record counts
1154
1180
  tasks, not verdicts. `ahub task show <id>` lists a task's outcomes and `ahub
1155
- route explain` shows each reviewer's record with the implementer. `ahub route explain <id>` also prints the planner walk (`planner candidate X: ...` lines, then `planner: X (rotation | named | kept)`): the rotation among the rostered planner holders by task id (#525-3), shown for information, since nothing records or enforces a per-task planner yet. A peer that accepted or finished a task never reviews it; when every eligible reviewer did, the person reviews and the trace says so. With
1181
+ route explain` shows each reviewer's record with the implementer. `ahub route explain <id>` also prints the planner walk (`planner candidate X: ...` lines, then `planner: X (rotation | named | kept)`): the same planner walk used when a new or pulled task records its planner (#525-2, #525-3). The task detail, dashboard and console show the recorded planner and selection reason; only that effective planner or the person may change its planning. A peer that accepted or finished a task never reviews it; when every eligible reviewer did, the person reviews and the trace says so. With
1156
1182
  `"review": { "adaptive": true }` in `.agenthub/config.json`, reviewers with at
1157
1183
  least `min_reviews` (5) reviews of that implementer in the class are ordered by
1158
1184
  how their reviews held up, ahead of quota but after idle before busy; off by
@@ -1240,6 +1266,16 @@ ahub say @claude "[STATUS] the test run is complete"
1240
1266
  ahub say @kimi "[FYI] the result is recorded"
1241
1267
  ```
1242
1268
 
1269
+ The `hub_send` tool declares `to` as an array of peer ids (#963). The control `send`
1270
+ boundary also accepts one nonempty string as a single recipient; it never
1271
+ turns that string into a broadcast. Without `reply_to`, omitting `to` or sending
1272
+ an empty array keeps the broadcast meaning. A reply without recipients inherits
1273
+ the sender it answers. Other recipient shapes, including `null`, empty strings
1274
+ and arrays containing a non-string or empty item, are refused before publication.
1275
+ The tool reports `not sent: to must be an array of peer ids` (the control reply's
1276
+ error is `to must be an array of peer ids`). Valid arrays still deduplicate
1277
+ recipients and pass through the existing unknown-peer, roster and privacy gates.
1278
+
1243
1279
  What agents send is limited per sender (`limits` in `.agenthub/config.json`; a
1244
1280
  project config gets the values below unless it sets others, `0` turns one off):
1245
1281
 
@@ -1318,7 +1354,7 @@ rescans the whole file on each call.
1318
1354
  for the history it already spent. A task with no link, and a record with no task, are `unattributed`.
1319
1355
 
1320
1356
  Every number names its source: `native` (the provider's own counter), `hub count` (the hub's own `tokens` increments, which cover peers that
1321
- report no usage record, such as Jcode), or `unknown`. Kimi's rows are `native`: the hub reads Kimi's own per-request counters from its session wire log and records them as `kimi_wire` usage records (#285 phase 2). Unknown is never shown as 0. `TOTAL` includes cache reads and `WITHOUT CACHE`
1357
+ report no usage record), or `unknown`. Jcode's rows are `native` from 0.99.1 on: the hub records the per-turn buckets of its ACP prompt result as `jcode_acp` usage records (#801). Kimi's rows are `native`: the hub reads Kimi's own per-request counters from its session wire log and records them as `kimi_wire` usage records (#285 phase 2). Unknown is never shown as 0. `TOTAL` includes cache reads and `WITHOUT CACHE`
1322
1358
  removes them; Claude's total is the sum of its four reported counters because its transcript carries none (`ahub report` still shows it as unknown), and a source whose input already
1323
1359
  contains the cached part (Codex, the gateway) gets no derived total. A lifetime total that mixes a native and a hub-counted peer is labelled
1324
1360
  `hub count`. When a peer is unknown the overall figure is `partial, at least`: a lower bound that leaves the named peers out, never a sum that counts them
@@ -1363,7 +1399,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
1363
1399
 
1364
1400
  `"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
1365
1401
 
1366
- The exact-pinned `@staix/agent-mem` 0.10.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1402
+ The exact-pinned `@staix/agent-mem` 0.13.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
1367
1403
 
1368
1404
  The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
1369
1405
 
@@ -1388,7 +1424,7 @@ ahub goal # open ones
1388
1424
  ahub goal take 1a2b3c4d --force # take one over from another machine
1389
1425
  ```
1390
1426
 
1391
- Without a remote the periodic sync stays quiet; any other sync failure writes `hub memory sync failed: <reason>` to `hub.log` (#401).
1427
+ Without a remote the periodic sync stays quiet; any other sync failure writes `hub memory sync failed: <reason>` to `hub.log` (task 401).
1392
1428
 
1393
1429
  ## Benchmarks
1394
1430
 
@@ -1399,7 +1435,7 @@ task's commit, so never enable it in a project you work in. The project must be
1399
1435
  nothing tracked under `.agenthub/`, and the suite file lives outside its tree ([suite format and store](bench.md)).
1400
1436
 
1401
1437
  ```bash
1402
- ahub bench run ../suites/suite.json --arm baseline --repeat 5 # a person's: resets the tree, proposes the tasks
1438
+ ahub bench run ../suites/suite.json --arm baseline --repeat 5 # the person's or the conductor's: resets the tree, proposes the tasks
1403
1439
  ahub bench status # the run in progress
1404
1440
  ahub bench compare baseline ripwire-on # arms side by side, inconclusive below 5 attempts each
1405
1441
  ahub bench export --format csv
@@ -1440,6 +1476,13 @@ ahub undo <turn> --yes --context # Codex's latest turn: also drop it from Codex
1440
1476
  Nothing is restored then. It plans again right before restoring and stops if
1441
1477
  anything moved meanwhile. A turn that is already undone says so.
1442
1478
  - A file the turn created is deleted; a file it deleted comes back.
1479
+ - The hub does not observe inside a git submodule (#798). A submodule the turn
1480
+ left dirty is listed as `<path> (changed, contents unobserved)` in `ahub turns`
1481
+ (`unobserved` in `--json`), and `ahub undo` says its contents cannot be restored:
1482
+ undo them in the submodule itself. A task that declares a path inside a submodule
1483
+ gets a notice at propose and accept, never a refusal. Without `.gitmodules` no git
1484
+ call is added; with one, each turn end adds one bounded `git status` probe, and a
1485
+ probe that cannot answer records unknown, never none.
1443
1486
  - When HEAD moves during a turn, the hub reads the appended HEAD reflog (bounded to 64 moves and 128 KiB). The turn's own commits, amends, cherry-picks and rebase picks still count as its changes. Files brought in by a pull, merge, rebase start, branch checkout or fast-forward reset are left out, unless they are still dirty at the turn's end. An unreadable or ambiguous reflog, a rewinding reset, or a file touched by both kinds of move makes the turn's files unknown (#576).
1444
1487
  - `--context` asks Codex (`thread/revert`) to drop the turn, and every later one,
1445
1488
  from its thread's saved history before the files are restored; Codex receives
@@ -1664,8 +1707,8 @@ stays the default until an evaluation says otherwise (`docs/cooperbench.md`).
1664
1707
  other owner, while the overlapping task is not started; the routing record,
1665
1708
  and a cohort record formed as a task is assigned, are taken before the task is
1666
1709
  sent, so a busy routed peer is not available then) with no other open work (an overlapping task its owner
1667
- has started counts), and each has five measured tasks with no more than 30%
1668
- failures and comparable work times. The work stage of a task ends at its first
1710
+ has started counts), and each has `tasks.split_min_observations` (default five) measured tasks with no more
1711
+ than `tasks.split_max_failure_share` (default 30%) failures and comparable work times. The work stage of a task ends at its first
1669
1712
  `hub_task_done`, and the task itself is never one of its own observations.
1670
1713
  - `"experiments": {"stale_notices": "deliver"}` in `.agenthub/config.json`
1671
1714
  turns the stale-notice drop off, for a controlled comparison only (the #106
@@ -1810,7 +1853,8 @@ ahub settings set roster.kimi false --preview
1810
1853
  ahub settings set roster.kimi false --yes
1811
1854
  ```
1812
1855
 
1813
- These are person's commands and use the same settings preview, write and undo as
1856
+ These are the person's commands (`roster add|remove` also the configured conductor's,
1857
+ see [the operational command table](#conductor-operations-812-t2-933-900)) and use the same settings preview, write and undo as
1814
1858
  the dashboard's Roster settings. An ordinary dashboard session may remove a peer;
1815
1859
  adding requires `ahub ui --settings` or a person's terminal. Removal requires the
1816
1860
  peer id as confirmation. Without `--now`, a busy peer is refused. A conductor
@@ -1910,8 +1954,73 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
1910
1954
  | Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min`, `.no_writes_turns` | `config.local.json` | live; `.no_writes_turns` sets the turn threshold independently; other keys reschedule the sweep timer |
1911
1955
  | Routing | `tasks.wip_limits.<peer>` | `config.local.json` | live |
1912
1956
  | Routing | `tasks.escalate_after` (1-10, default 3: consecutive `changes_requested` before a task moves to the next `escalate_to` peer) | `config.local.json` | live; the next verdict uses it |
1957
+ | Routing | `tasks.groom_fyi_window_min` (1-1440 minutes, default 10) | `config.local.json` | live; the next planner-notice flush uses it; planning-change task notices are unchanged |
1958
+ | Routing | `tasks.demote_at` (0.5-20 decayed recent failures, default 1.5) | `config.local.json` | live; the next demotion read (routing, `ahub route explain`) uses it |
1959
+ | Routing | `tasks.demotion_half_life_h` (1-168 hours, default 24) | `config.local.json` | live; the next demotion read uses it |
1960
+ | Routing | `tasks.contradiction_window_days` (1-90 days, default 7) | `config.local.json` | live; the next changes_requested verdict uses it |
1961
+ | Routing | `tasks.rescreen_max` (1-100 attempts, default 10) | `config.local.json` | live; the next release-timer re-screen uses it |
1962
+ | Routing | `tasks.split_min_observations` (3-100 measured tasks, default 5) | `config.local.json` | live; the next shadow split prediction uses it |
1963
+ | Routing | `tasks.split_max_failure_share` (0-1, default 0.3) | `config.local.json` | live; the next shadow split prediction uses it |
1964
+ | Switches | `tasks.auto_pull` (default off: pull the top explicitly reserved ready card when a planner slot frees) | `config.local.json` | live; the next freed planner slot uses it |
1965
+ | Routing | `tasks.queue_backpressure.max_queued` (0-10000), `.max_oldest_s` (0-604800); 0, the default, disables each ([backpressure](#queue-backpressure-on-automatic-task-offers-800)) | `config.local.json` | live |
1913
1966
  | Roles | `planner` (one or up to eight planner holders) | `config.local.json` | at once |
1914
1967
 
1968
+ ### Operational numeric settings (#489 H1)
1969
+
1970
+ Numeric operational conversions use the existing settings writer, loader and
1971
+ reload path. A fixed row in `src/hub/operational-settings.ts` supplies the key,
1972
+ unit, default, bounds, apply class and console/dashboard metadata. The closed
1973
+ `config-apply.ts` schema still names each leaf explicitly; there is no dynamic
1974
+ registration, extra configuration file, environment knob or per-call override.
1975
+ `configApply` and the UI row consume the same metadata. The default enters the
1976
+ existing `DEFAULT_CONFIG`; unset therefore keeps the previous value.
1977
+
1978
+ The worked H0 conversion is `tasks.groom_fyi_window_min`, the spacing between
1979
+ state-only planner board-update FYIs. Its range is 1-1440 minutes and its
1980
+ default is the existing 10 minutes. A settings-session or terminal change takes
1981
+ effect at the next notice flush, without restarting a peer or altering task
1982
+ ownership. It does not delay new planning/dependency task notices. Removing a
1983
+ local value inherits the shared value or default through the ordinary settings
1984
+ undo/write path. This new key grants no conductor authority.
1985
+
1986
+ For a later operational group, add its metadata row, exact schema leaf,
1987
+ default and runtime config read, then its settings-table entry and independent
1988
+ pin against the original constant. Test schema/UI bounds and apply-class parity,
1989
+ inherit/undo and the real runtime boundary. H0 safety floors and contract
1990
+ constants stay outside this table. Exposing a floor requires schema bounds that
1991
+ permit tightening only, tested for that floor; UI `floor` metadata alone does
1992
+ not enforce a safety ceiling. This H1 example changes no safety floor, protocol,
1993
+ wire format or storage contract. The remaining H0 conversions belong to later
1994
+ area PRs.
1995
+
1996
+ #### Tasks and routing conversions (#489 H2)
1997
+
1998
+ Six more operational numbers use the same fixed rows, all `live`, `risk: raises`, config store, with the
1999
+ default equal to the constant it replaces (each pinned by a literal in `test/operational-settings.test.ts`):
2000
+ `tasks.demote_at` (DEMOTE_AT), `tasks.demotion_half_life_h` (DEMOTION_HALF_LIFE_MS),
2001
+ `tasks.contradiction_window_days` (CONTRADICTION_WINDOW_MS), `tasks.rescreen_max` (RESCREEN_MAX),
2002
+ `tasks.split_min_observations` (SPLIT_MIN) and `tasks.split_max_failure_share` (SPLIT_FAILURE_SHARE). Each is
2003
+ read where its constant was used, so a change applies at the next demotion read, verdict, re-screen or
2004
+ prediction without a restart. The split thresholds only feed the shadow prediction, which never changes
2005
+ assignment. None grants conductor authority.
2006
+
2007
+ The H0 operational items of `tasks.ts`, `routing.ts` and `task-sweep.ts` that stay as they are (42 listed, 6 converted here, 1 in H1):
2008
+
2009
+ - Already settings, so nothing to add: `task_sweep.interval_s`, `unaccepted_min`, `idle_min`, `review_min`, `ready_min`
2010
+ (both uses), `ladder_min` and `no_writes_turns` (the H0 rows are the defaults of the existing keys), `tasks.escalate_after`
2011
+ (ESCALATE_AFTER), `tasks.wip_limits` (the default of 1) and `tasks.groom_fyi_window_min` (H1).
2012
+ - Already `routing.toml` keys, not config-store: `pi.dgx_max_context_tokens`, `pi.mlx_max_context_tokens`, the
2013
+ `pi.efficient_wait_ms` bound and `long_context_tokens`. H1 reaches the config store only, and
2014
+ `routing.local.toml` may not hold them (`checkOverlay`), so converting them would need a different mechanism.
2015
+ - Resource bounds, not tunables: the 256-entry plan caches (`observedPlans`, `planReads`), the 1024-entry conditional
2016
+ map, the 500-task and 256-item grooming limits (`GROOMING_TASK_LIMIT`, `GROOMING_ITEM_LIMIT`, the digest's 500
2017
+ slices), and the 8, 16 and 32 read counts and 16-id lists of the digest, cost report and release cut. Raising any of
2018
+ them raises memory or work per call, so each would need a tighten-only schema bound and a test per floor
2019
+ (acceptance criterion 3), which this group does not add.
2020
+ - Host deadlines for the `gh` reads (1_000, 4_000 and 20_000 ms in five places) and the 5 000 ms release-window
2021
+ freshness check. They bound a whole call, a refusal of a stale host window protects the release, and none is
2022
+ independently tunable without changing what a partial result means.
2023
+
1915
2024
  - Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`,
1916
2025
  the machine-local files (`ahub init` ignores both in git). The shared
1917
2026
  `config.json` and the hand-written `routing.toml` are never rewritten, and a
@@ -1962,7 +2071,8 @@ Pi relay model aliases (`pi.dgx_coding`, `pi.dgx_fast`) and `routing.pi` retain
1962
2071
  limit to 0, turns `task_sweep.enabled` or `.auto_reassign` off and changes
1963
2072
  the other Switches. Everything
1964
2073
  else needs `ahub ui --settings`, whose session may raise for 15 minutes, or
1965
- `ahub settings` in a terminal. `never-ask` needs the peer id typed in the
2074
+ `ahub settings` in a terminal; the configured conductor may set only the
2075
+ thirteen keys in [the operational command table](#conductor-operations-812-t2-933-900). `never-ask` needs the peer id typed in the
1966
2076
  dashboard, or `--yes` in the terminal, and a stored never-ask default still
1967
2077
  waits for `y` in `ahub console` at each hub start. Tool approvals stay
1968
2078
  deny-only for Pi and local in every session.
@@ -1986,7 +2096,7 @@ Instances, the review and approval blocks and the lifecycle can be edited withou
1986
2096
  - `mode` and `required_checks` in `.agenthub/sdlc.toml`. Only those two lines change: comments and every other table stay as written, and a layout the editor cannot change faithfully (a key repeated, or one inside a multi-line string) is refused with its own reason.
1987
2097
 
1988
2098
  `roster.*`, `tasks.wip_limits.*`, `permission_modes`, `peers` and the rest of the registry keep their own `ahub settings set` path.
1989
- 3. **Executable and secret-locating keys** are console only: the rest of `NEVER_EDITABLE` (`kimi_cmd`, `jcode_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`, `omniroute`, `terminal`, `adapters`, `adapter_modules` and the like) and the Jcode instance fields `jcode_home`, `provider`, `model` and `provider_profile`. The console shows "this runs code with the daemon's rights, or chooses where secrets are read from" and writes only when the key's exact name is typed, one key at a time; `--yes` never confirms one. The dashboard lists each of them with the console command that changes it and nothing else: no value, no path, no input. A forged dashboard request for one is refused by the hub.
2099
+ 3. **Executable and secret-locating keys** are console only: the rest of `NEVER_EDITABLE` (`kimi_cmd`, `jcode_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`, `omniroute`, `terminal`, `schedules`, `folder_triggers`, `corpus`, `rooms`, `local.doc_read`, `local.web_fetch`, `adapters`, `adapter_modules` and the like) and the Jcode instance fields `jcode_home`, `provider`, `model` and `provider_profile`. The console shows "this runs code with the daemon's rights, or chooses where secrets are read from" and writes only when the key's exact name is typed, one key at a time; `--yes` never confirms one. The dashboard lists each of them with the console command that changes it and nothing else: no value, no path, no input. A forged dashboard request for one is refused by the hub.
1990
2100
 
1991
2101
  In the console (`ahub console`, Settings tab `6`): `c` asks what to edit (`1` an instance, `2` the lifecycle, `3` another key), asks each field, shows the hub's diff or its loader reason, asks for a reason, then `y`. Esc cancels at any step, and pasted text never confirms. `R` appears while a restart is pending and prefills `restart`; the command still asks its own `y`. In the dashboard, open `ahub ui --settings`: the Structure group has the same forms; outside a settings session it is read only.
1992
2102
 
@@ -2004,7 +2114,7 @@ Rules:
2004
2114
  - A whole instance that holds a native home, account or model (`jcode_home`, `provider`, `model`, `provider_profile`) is tier 3 to remove or replace, so a dashboard can neither see nor delete those values; an existing instance changes leaf by leaf (`instances.<id>.<field>`), and `clear` removes one field. A preview or write shows a tier 3 value only as `(set)`, and a write changes exactly one tier 3 key, counting the keys the file adds (removing two Jcode instances is two writes, each confirmed by its own typed name). The audit record gives a tier 3 key no value hash at all, because a model or provider name is easy to recover from an unsalted hash; the file revisions bind the change instead. An instance already in `config.local.json` is changed leaf by leaf even before a restart has started it. If `.agenthub` or `config.local.json` is a symlink, the Settings view reports its revision as `unavailable` and no edit is planned. An edit of `sdlc.toml` keeps the file's own line endings.
2005
2115
  - Every write records a `config_edit` event: the file, each key path with a short hash of its value before and after (never a value), the file revisions before and after, the tier, the source (`dashboard` or `terminal`) and `by: person`. The notice and `hub.log` name key paths only.
2006
2116
  - A changed instance declaration is restart-bound: it is listed as `pending restart` (`instances`) and takes effect at the next hub start. A changed `sdlc.toml` is read once at start and is listed as `sdlc.mode` or `sdlc.required_checks` until the running hub holds the same value. `review`, `snapshots`, `approvals` and `context.gate` publish through the one reload path, one key at a time.
2007
- - Person only: an agent shell or a hub tool is refused (`ahub settings` is a console command in the identity gate, and the three control requests answer only the console role). Recovery or shutdown holds every edit.
2117
+ - Person only: an agent shell or a hub tool is refused (`ahub settings edit` is a console command in the identity gate, and the three control requests answer only the console role). Recovery or shutdown holds every edit.
2008
2118
  - The hub must run this release: an older hub answers `unknown message` and the command says to upgrade and restart it.
2009
2119
 
2010
2120
  ## Start modes: TUI by default, headless as opt-in
@@ -2211,10 +2321,10 @@ Quota also shapes routing and handoffs:
2211
2321
  while it waits (a week window crosses the gate), its work is handed over after
2212
2322
  all. `ahub budget` shows each decision and why in the pause reason.
2213
2323
  - A peer whose recent failures in a class (failed checks, changes requested,
2214
- escalations by hand) reach 1.5 after decay, and outweigh its recent
2324
+ escalations by hand) reach `tasks.demote_at` (default 1.5) after decay, and outweigh its recent
2215
2325
  approvals there (a task without a reviewer counts when done), goes behind the
2216
2326
  other candidates in the same state for that class, `local` and `pi` included.
2217
- A failure counts half after a day. `ahub route explain` names demoted peers.
2327
+ A failure counts half after `tasks.demotion_half_life_h` (default 24 hours). `ahub route explain` names demoted peers.
2218
2328
 
2219
2329
  ## Durable delivery and queue resolution
2220
2330
 
@@ -2328,6 +2438,8 @@ ahub tail
2328
2438
  ahub kill
2329
2439
  ```
2330
2440
 
2441
+ `ahub down` is the same verified stop as `ahub kill`, named as the counterpart of
2442
+ `ahub up` (#957); both are the person's, refused from an agent shell.
2331
2443
  The daemon waits for shutdown work that must survive termination. Afterward,
2332
2444
  run `ahub status` or `ahub up` and read the project state before restarting
2333
2445
  peers. A stopped daemon does not delete task records or the durable journal.
@@ -2406,8 +2518,35 @@ releases backlog work (#785): a lower WIP for the owner or reserved peer adds a
2406
2518
  approval that leaves a backlog card with no unresolved dependency adds
2407
2519
  `backlog_ready: #<id>`. The lines go in one `Planner work ready:` task notice
2408
2520
  per planner, ids only; a newer one merges and replaces the unsettled copy, which
2409
- survives a restart. Nothing is pulled automatically: the planner reads the
2410
- board and decides.
2521
+ survives a restart. By default nothing is pulled automatically: the planner reads the
2522
+ board and decides. With `tasks.auto_pull` on (opt-in, see below) the hub itself pulls
2523
+ the top card explicitly reserved for the peer whose slot just freed.
2524
+
2525
+ When `hub_task_done` leaves a review whose exact-head CI is not yet green (the
2526
+ lifecycle must be on, the task linked to a PR and not private), the hub starts one watcher
2527
+ for that head (#785): one `gh pr view` a minute, ended by a passed CI result, by a
2528
+ 45 minute deadline, by a newer head, a verdict or a cancellation, or by hub
2529
+ shutdown; a restart does not restore it, and the next `hub_task_done` starts a new
2530
+ one. A failed or incomplete result is told once and the watcher keeps reading for a
2531
+ rerun of the same head: the first pending read after a newly told result renews the
2532
+ deadline once, and a deadline after a told result ends silently (#540). On the result the owner and the reviewer each get one
2533
+ `ci_completed: #<id> head <sha12> passed|failed|incomplete` task notice (ids only, the
2534
+ head cut to 12 characters; a person reviewer gets a console line instead), and
2535
+ unless CI failed, a review the hub held as premature for that head is offered
2536
+ once more after a fresh readiness read. A deadline tells the console only. No peer turn waits for
2537
+ CI, and nothing is polled outside a watcher.
2538
+
2539
+ `tasks.auto_pull` (default off, machine-local, live) lets the hub act on that
2540
+ wake-up (#785). When a write frees a peer's planner slot, the hub pulls the one top
2541
+ backlog card explicitly reserved for that peer, through the same planner pull a
2542
+ planner makes: revision, dependency, reservation, pinned, frozen-release, P0 order
2543
+ and WIP checks all apply unchanged, and a refusal becomes a console notice naming
2544
+ why. Unreserved cards, cards reserved for another peer and private (PII) cards are
2545
+ never pulled. One card per release, as the hub for the card's own planner of record:
2546
+ the history entry reads `by: hub` with that planner and the reason
2547
+ `auto_pull: capacity released for <peer>; hub acted for planner <planner>`. A hub that
2548
+ is paused for recovery pulls nothing, also when the setting is turned off or recovery begins while a pull is already waiting on its screening: that pull writes nothing, and a recovery waits for pulls in flight. A committed `config.json` cannot turn it on: like
2549
+ the other machine-local policies it is ignored there with a log line.
2411
2550
 
2412
2551
  ## Resetting a project's hub
2413
2552
 
@@ -2540,6 +2679,7 @@ Agent CLIs update themselves often, and a hub release cannot follow every one. O
2540
2679
  - What trust cannot prove is printed when you trust.
2541
2680
  - `ahub doctor` shows one line for each installed CLI that is not on a verified version.
2542
2681
  - A daily workflow (`.github/workflows/agent-compat.yml`) runs `scripts/agent-compat-watch.ts`, which reads the latest published version of every gated CLI that has a public source: the npm packages `@anthropic-ai/claude-code`, `@openai/codex`, `@moonshot-ai/kimi-code`, `@qwen-code/qwen-code` (for both `qwen` and `qwen-dual`) and `@earendil-works/pi-coding-agent` (for `pi`, since #847 gave it a COMPAT entry), and the GitHub releases of Jcode. agy has no public source, so its row reads "no public source; local drift only" and is never a failure. Every COMPAT entry names the receipt to repeat (`receipt`), and a test requires that file to exist and the entry to have a watcher source. A version that cannot be read reads as unknown, not as a crash. The workflow keeps one `agent-compat` issue current while a source is ahead and closes it when none is.
2682
+ - **Tracking issue identity (#964):** only the exact title `agent CLI versions ahead of the verified set` identifies the issue; the `agent-compat` label is for people. The workflow reads open issues without filtering by label, then `scripts/agent-compat-issue.ts` selects one valid issue number or confirmed absence. Multiple exact-title matches fail with their issue numbers before any label or issue write. A failed read, malformed inventory or full 1000-issue result also stops without mutation. With no match, an ahead report creates the tracker and an all-verified report does nothing; unrelated labelled issues and their bodies are never selected.
2543
2683
  - **Maintenance loop (#843):** the daily upstream watch is the trigger; `ahub doctor` is the local drift notice for versions installed on a machine, and the hub itself tells the planner (below). The planner files one receipt task per (id, version): the implementer repeats the procedure in the entry's last `docs/verification/` receipt, writes a new one, and adds the version to `verified` in that same PR; the next release ships the bump. A person's `ahub agents trust` stays the stopgap that keeps a machine running meanwhile, and the person may revoke it once the bump lands.
2544
2684
  - **Local drift notice (#843 AC3):** at hub start and once a day the daemon reads each gated CLI's installed version with the probe `ahub agents` uses (no network, no install). For every installed CLI on a version that is not `verified` (in-line, trusted or refused) the effective planner gets one notice per (id, version): the id, the version, the state and the last receipt path, nothing the CLI printed. A CLI that is not installed, or whose version cannot be read, is not drift. The marker is kept per (id, version, planner) in `hub.db` (`agent_drift_notices`), so a restart repeats nothing, a planner whose notice could not be queued (or who joins later) is retried alone without telling the others again, and a new version is a new notice; with no rostered planner nothing is recorded. `ahub status` shows one `agent CLI drift` attention line per drifted id until the installed version is verified.
2545
2685
 
@@ -2562,7 +2702,7 @@ section is what they run.
2562
2702
 
2563
2703
  Upgrade running projects with the target release's own coordinator. It accepts
2564
2704
  a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
2565
- 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.19.0), and only
2705
+ 11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.21.0), and only
2566
2706
  a target on its own protocol, so the target's coordinator fits every supported
2567
2707
  source and carries every recovery fix released up to it. Protocol 8 and older
2568
2708
  (0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
@@ -2576,7 +2716,7 @@ bunx --package @staix/agent-hub@0.12.43 ahub upgrade --to 0.12.43 --yes
2576
2716
  | Running now | Coordinator to use |
2577
2717
  | --- | --- |
2578
2718
  | 0.6.x (protocol 9) | the target's, through `bunx` as above |
2579
- | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.19.0 (protocol 16) | the target's, through `bunx` as above |
2719
+ | 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.21.0 (protocol 16) | the target's, through `bunx` as above |
2580
2720
  | any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
2581
2721
  | 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
2582
2722
 
@@ -3481,7 +3621,15 @@ output through unchanged and appends one byte to a progress file for each of tho
3481
3621
  the watchdog polls that file's size. On a stop it prints where the run is and the process
3482
3622
  table below it first. `AHUB_CHECK_HANG_S` (960 by default) is only the backstop for a run
3483
3623
  that keeps printing results while hung; hosted CI sets 1500 on Linux inside a 35-minute
3484
- job limit and 1800 on macOS inside a 40-minute one (#378, #593, #752, #516).
3624
+ job limit (#378, #593, #752, task 516). The macOS leg runs as four shards (#548): `AHUB_CHECK_SHARD=i/n`
3625
+ makes `scripts/check.sh` run slice `i` of the full suite through `bun test --shard=i/n --timings=scripts/test-timings.json`, which puts
3626
+ every test file in exactly one shard and balances them by the recorded per-file times; shard 1 also runs the docs check, typecheck, role check, bundle freshness and
3627
+ package contents, so each of those runs once per gate, and the others install and test. Each shard runs under the same 1500 s backstop as Linux inside a 25-minute job
3628
+ limit, and its last line is `check: OK (shard i/n)`. The required check `check (macos-latest)` is an aggregator job that passes only when every shard passed (or an
3629
+ identical-tree full proof was reused), so branch protection and the reuse proof read the same names as before. `AHUB_CHECK_SHARD` is the full suite only: it cannot be
3630
+ combined with `--changed`. A shard execs the system git once before its tests (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`; a failure of it is ignored), because the first execs of `/usr/bin/git` on a fresh hosted macOS runner outlast the 1000 ms some tests give it. `scripts/test-timings.json` is advice for balance, never a list of tests: a file missing from it still runs (Bun gives it the median time).
3631
+ Regenerate it from a green macOS leg when the shards drift apart: `gh api --allow-escape-sequences repos/<owner>/<repo>/actions/jobs/<macOS shard job id>/logs`
3632
+ for each shard, or from one unsharded leg's log, then `bun scripts/make-test-timings.ts <log>`.
3485
3633
  The `closing-refs` workflow fails a PR whose title, body or commit messages would make
3486
3634
  GitHub close an issue the text only mentions (`Refs #N`, a negated or qualified keyword);
3487
3635
  an issue linked only in the sidebar passes (#760).
@@ -3529,6 +3677,7 @@ own 5 h and weekly ratios stay unknown. Not yet verified live against Kimi's `/u
3529
3677
  update whenever it names the current session, because 2.1.1's source text
3530
3678
  emits it after the turn settles (read from the source, not yet observed live
3531
3679
  through the hub; docs/smoke.md), so the peer may already be idle.
3680
+ Jcode's context occupancy comes from its ACP `usage_update` (`{used, size}`, source `jcode_usage_update`, bound to the adapter's own session id) on the verified 0.99.1 shape; its consumption is one `usage` event per prompt (source `jcode_acp`: the prompt result's `inputTokens`, `outputTokens`, `cachedReadTokens` and `cachedWriteTokens` as the hub's buckets, never Jcode's `totalTokens`, which counts the cached read twice for OpenAI-style providers) and never a `tokens` event or a window. Jcode has no native quota reader: `jcode usage --json` carries a free-text limit name and no window length, so `ahub budget` keeps `no reading` for it and `ahub budget set` stays the way in (receipt: `docs/verification/2026-10-28-jcode-0.99.1-usage.md`).
3532
3681
  Pi, Qwen (until a native source is verified, #625) and other unsupported surfaces show unknown.
3533
3682
  A stale or disconnected reading is unknown, not 0%. Codex's accumulated session
3534
3683
  usage is never used as context occupancy. In the tail (`ahub tail` and the
@@ -3566,6 +3715,32 @@ nor hands work over. Continue normally or deliberately restart into a fresh
3566
3715
  session with your chosen checkpoint as preface. No automatic restart or native
3567
3716
  compaction override is performed.
3568
3717
 
3718
+ ## Context headroom admission for new work (#802)
3719
+
3720
+ `context.max_assignment_used` is a separate, default-off admission threshold.
3721
+ In an untracked `.agenthub/config.local.json`, set it to a fraction in `(0, 1]`,
3722
+ for example `{"context":{"max_assignment_used":0.85}}`. Zero disables it.
3723
+ Tracked configuration cannot enable it on another machine. Restart deliberately,
3724
+ or preview and confirm `ahub settings reload` as the person; it applies to the
3725
+ next routing decision. `context.stale_min` is the freshness limit.
3726
+
3727
+ A fresh native occupancy at or above the threshold skips that peer for a new
3728
+ automatic owner offer. `ahub route explain` uses the same pure decision and
3729
+ prints the measured fraction and limit. Missing, malformed, future, stale,
3730
+ detached or replaced-session evidence is explicitly unknown: it neither blocks
3731
+ nor establishes spare capacity, and the existing routing rules still decide.
3732
+ Only adapters with declared native context readers supply evidence, including
3733
+ Jcode's session-bound reader; unsupported surfaces stay unknown.
3734
+
3735
+ A name or reservation alone does not bypass admission. Explicit assignments by
3736
+ the person or current configured conductor, and a peer's own self-claim, may
3737
+ bypass this context limit; roster, privacy, quota, capability and other gates
3738
+ still apply. Reviewer selection is unchanged, including review-class owners.
3739
+ The rule never pauses, compacts, replaces or moves an active session or task.
3740
+ It leaves `context.gate` checkpoint events and requests unchanged, and does not
3741
+ add a telemetry-driven reassignment or retry timer. Re-evaluate or explicitly
3742
+ assign an unassigned task when its owner's context has changed.
3743
+
3569
3744
  ## Pi tool refusals, exit causes and idle automatic restart
3570
3745
 
3571
3746
  Pi hub-task/conductor refusals proven before any task or board effect return
@@ -3659,8 +3834,9 @@ request and does not apply to model-origin bash calls.
3659
3834
 
3660
3835
  `ahub stop kimi`, `ahub stop pi` (headless mode), or `ahub stop local` stops
3661
3836
  that hub-owned runtime through its adapter and prints `<peer>: offline`.
3662
- The hub and its other peers keep running. The command is human-only;
3663
- agent shells and tools-role connections cannot use the `peer_stop` request.
3837
+ The hub and its other peers keep running. The command is the person's or the
3838
+ configured conductor's (an audited operational admission); other agent shells and
3839
+ tools-role connections are refused at the `peer_stop` request.
3664
3840
  An older hub that does not know the request needs an upgrade before this
3665
3841
  command is available.
3666
3842
 
@@ -3726,7 +3902,7 @@ that the file is untracked; a dropped value is reported by the settings result.
3726
3902
 
3727
3903
  ## SDLC artifact receipts and gates
3728
3904
 
3729
- The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). `ahub sdlc metrics [--since <YYYY-MM-DD|ISO time>]` (the planner tool `hub_sdlc_metrics`, planner or person, read-only) reports lead times, first-pass approval and check rates, rework rounds, stale-head re-reviews and open stalls from board history, with `unknown` counted and never 0; optional `[bands.<metric>]` in `sdlc.toml` add control bands that the daemon checks every five minutes in advise or enforce, writing a hub notice (`log`), a read-only `triage` backlog task (`diagnose`) or a `plan` backlog card (`propose`), one per breach episode and never a GitHub issue (S7, see [metrics](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). With the lifecycle on, a task's issue identity is linked when it is proposed or accepted, from the one issue it names (an imported GitHub issue, or a `#N`, `owner/repo#N` or issue URL in its title); a title that names a sibling repository (`agent-mem#13`) links only when `refs.repo` names it, and without `refs.repo` the propose result says to set it. `ahub backlog groom` also backfills up to 32 links per call, within 1 s (#723). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4 [test integrity](sdlc.md#test-integrity-481-s4) and S6 [release authorization](#release-approval-and-optional-tag-guard) are in source too. S5 continuous evals, release metrics and bands, and a full live advise/release cycle remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3905
+ The playbook lifecycle (#257, #481) is off without `.agenthub/sdlc.toml`. When enabled its stages are intent, spec, plan, build, test, review, merge, release and maintain, grouped as Plan, Design, Build, Test, Deploy and Maintain. Advise shows blockers; enforce checks implemented head-bound review and approval gates. Stored implement/verify receipts read as build/test; new enabled receipts use canonical ids. See [the SDLC contract](sdlc.md) for gates, required check names, local receipts, console-only readbacks and the S0-S8 delivery table. A person accepts a spec issue with `ahub sdlc accept <task> --issue <n>` (S1). The planner records plan reviews with review and CI scope (`hub_plan_review`, `ahub sdlc plan-review`) and grooms with `ahub backlog groom`; `ahub backlog return <task> <revision> <reason>` returns accepted, untouched work (S9). `ahub sdlc costs [owner/repo]` reports review rounds and CI runs of merged PRs without writing the board (S10). `ahub sdlc metrics [--since <YYYY-MM-DD|ISO time>]` (the planner tool `hub_sdlc_metrics`, planner or person, read-only) reports lead times, first-pass approval and check rates, rework rounds, stale-head re-reviews and open stalls from board history, with `unknown` counted and never 0; optional `[bands.<metric>]` in `sdlc.toml` add control bands that the daemon checks every five minutes in advise or enforce, writing a hub notice (`log`), a read-only `triage` backlog task (`diagnose`) or a `plan` backlog card (`propose`), one per breach episode and never a GitHub issue (S7, see [metrics](sdlc.md#sdlc-metrics-and-control-bands-481-s7)). With the lifecycle on, a task's issue identity is linked when it is proposed or accepted, from the one issue it names (an imported GitHub issue, or a `#N`, `owner/repo#N` or issue URL in its title); a title that names a sibling repository (`agent-mem#13`) links only when `refs.repo` names it, and without `refs.repo` the propose result says to set it. `ahub backlog groom` also backfills up to 32 links per call, within 1 s (#723). S11 release trains ship in 0.12.37 (see [release scheduling](#planner-release-scheduling)). S3 structured review findings (an open Important finding blocks approval in enforce; `max_nits` in `sdlc.toml` caps nits) and S2 literal-path plan conformance with the person-only `ahub sdlc accept-departure <task> <head> <reason...>` are in source; see [findings](sdlc.md#structured-review-findings-481-s3) and [plan conformance](sdlc.md#plan-conformance-481-s2). S4 [test integrity](sdlc.md#test-integrity-481-s4) and S6 [release authorization](#release-approval-and-optional-tag-guard) are in source too. S5 is in source as an [eval gate mechanism](sdlc.md#eval-gate-for-agent-configuration-481-s5) with no suite chosen, and S7 adds merge-time readback, [release metrics](sdlc.md#release-metrics-481-ac12) and an opt-in security baseline; a full live advise/release cycle and the per-release gates-run record remain pending. Owners/reviewers record bounded identities through `hub_sdlc_record`; these grant no additional authority. Off output, receipt and schema shapes stay unchanged.
3730
3906
  ## Optional Qwen Code peer
3731
3907
 
3732
3908
  Select Qwen per project with `ahub roster add qwen`; it defaults off and is absent
@@ -3741,7 +3917,7 @@ selected roster and existing capabilities; the TUI is always a person's start.
3741
3917
  `ahub qwen --mode tui` runs `qwen --input-file <in> --json-file <out> --mcp-config <hub server> --approval-mode default` in the terminal you ran it from. The daemon prepares the two files (mode 0600, under `<state dir>/qwen/qwen-dual/`) and returns the launch; the hub then appends one `submit` line per delivery and tails the event file from a saved offset. It needs a Qwen the dual-output compat entry verifies (`ahub agents` shows `qwen-dual`, verified 0.26.1 only; the headless `qwen` entry is separate and unchanged) and refuses another version before it creates a file. The TUI is explicit only: `ahub qwen` without `--mode` is still headless, Qwen is not a start-mode peer, and the hub never opens this terminal by itself. The TUI start is refused for an extra Qwen instance, off roster, outside a terminal, and while a headless Qwen runs (`ahub stop qwen` first); a headless start is refused while the TUI holds its terminal.
3742
3918
 
3743
3919
  - What the hub does: a delivery becomes a `submit`; the last assistant message of the turn (an `assistant` event whose `stop_reason` is not `tool_use`, then `message_stop`) is shared as the answer; busy and idle follow the events. A `can_use_tool` request goes to the console approval path (`ahub permit`, the dashboard), and the hub's own tools are approved only by their exact `mcp__agent-hub__<tool>` name. If you answer a prompt in the Qwen terminal first, the hub's pending approval is withdrawn.
3744
- - **Limit 1, a deny ends the turn without an answer.** Qwen emits nothing when the hub denies: it cancels the whole turn. The hub ends the turn itself, shares no answer, settles the delivery as `completed` (a deny is a known outcome, and a `needs_review` would hold every later message to Qwen until a person resolved it) and says so in a notice. A turn whose outcome is not known (Qwen exited, an Esc followed by a new prompt, an event file that became unreadable) is the one that settles `needs_review`. Qwen's own notice that a background shell finished (a `user` event that is wholly one or more `<task-notification>` envelopes) is not a new prompt: it ends no hub turn and leaves an idle peer idle (#517).
3920
+ - **Limit 1, a deny ends the turn without an answer.** Qwen emits nothing when the hub denies: it cancels the whole turn. The hub ends the turn itself, shares no answer, settles the delivery as `completed` (a deny is a known outcome, and a `needs_review` would hold every later message to Qwen until a person resolved it) and says so in a notice. A turn whose outcome is not known (Qwen exited, an Esc followed by a new prompt, an event file that became unreadable) is the one that settles `needs_review`. Qwen's own notice that a background shell finished (a `user` event that is wholly one or more `<task-notification>` envelopes) is not a new prompt: it ends no hub turn and leaves an idle peer idle (task 517).
3745
3921
  - **Limit 2, the hub cannot stop a Qwen turn.** The input protocol has only `submit` and `confirmation_response`, and a signal does not stop a running turn. The inactivity watchdog therefore never cancels: after `watchdog_ms` of silence it marks the turn stalled (`ahub status`, the console and the dashboard say "press Esc in the Qwen terminal") and keeps the peer busy. The hub never types into your terminal and never signals Qwen to stop a turn.
3746
3922
  - A Qwen write or shell command that names the hub state directory is refused (see [security](security.md): the input file is a prompt channel).
3747
3923
  - If the hub cannot read the event file the peer is `detached`, shown as offline with a notice, never idle: Qwen itself keeps working, and the hub reattaches when the file is readable again.
@@ -3836,7 +4012,7 @@ Configured kinds are cloud peers and never receive PII work. Quota and context a
3836
4012
 
3837
4013
  ## External adapter modules (#451 A4)
3838
4014
 
3839
- A bundled adapter module is authorized by the person with `adapter_modules` (at most 16 rows of `path` and `sha256`) in an untracked `.agenthub/config.local.json`; in `config.json` or a tracked file it is ignored with a notice, and settings never edit it. Only daemon startup imports a module: settings, reload, previews and CLI reads never do, so a change needs a restart. The module must be a single self-contained ESM file inside the project (no symlinked, world-writable or oversized file) whose bytes match the pin, and it may import only Node/Bun builtins and `@staix/agent-hub/adapter`; the scanner also refuses any `require`, `eval` or `Function` word token. Each import must settle within 2 s. A module that fails any check is refused on its own, with a bounded reason in the status reply, while startup and the other adapters continue; its roster row stays inert. A loaded kind is headless, cloud (never PII), off roster until `ahub roster add <kind>`, and has no controlled recovery: stop it and start it again with its recorded session id. While modules are declared, the upgrade or restart source and target must advertise `external_adapter_modules`. Bundling, the accepted definition subset and the conformance kit (`@staix/agent-hub/conformance`, for module CI only) are in [Adding an AI tool](adding-an-ai-tool.md#5-external-adapter-modules-451-a4). A pin authorizes code that runs with the daemon's rights; it is not a sandbox ([security](security.md)).
4015
+ A bundled adapter module is authorized by the person with `adapter_modules` (at most 16 rows of `path` and `sha256`) in an untracked `.agenthub/config.local.json`; in `config.json` or a tracked file it is ignored with a notice, and settings never edit it. Only daemon startup imports a module: settings, reload, previews and CLI reads never do, so a change needs a restart. The module must be a single self-contained ESM file inside the project (no symlinked, world-writable or oversized file) whose bytes match the pin, and it may import only Node/Bun builtins and `@staix/agent-hub/adapter`; the scanner also refuses any `require`, `eval` or `Function` word token. Each import must settle within 2 s. A module that fails any check is refused on its own, with a bounded reason in the status reply, while startup and the other adapters continue; its roster row stays inert. A loaded kind is headless, cloud (never PII), off roster until `ahub roster add <kind>`, and has no controlled recovery: stop it and start it again with its recorded session id. While modules are declared, the upgrade or restart source and target must advertise `external_adapter_modules`. Extra `instances` of a module kind (#754) exist only when the module opts in with `headlessInstances` (headless; implementer, verifier or continuing reviewer) and the person declares them in `config.local.json`; each needs its own worktree, is bound to what startup verified, and needs `external_adapter_instances` on both sides of a restart or upgrade ([security](security.md)). Bundling, the accepted definition subset and the conformance kit (`@staix/agent-hub/conformance`, for module CI only) are in [Adding an AI tool](adding-an-ai-tool.md#5-external-adapter-modules-451-a4). A pin authorizes code that runs with the daemon's rights; it is not a sandbox ([security](security.md)).
3840
4016
 
3841
4017
  # Headless ACP watch (#286)
3842
4018
 
@@ -3849,7 +4025,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3849
4025
  # Acceptance-criteria checklist (#481)
3850
4026
 
3851
4027
  - The task reviewer/person records a head-bound `criteria` receipt with criterion ids matching the issue's AC ids, required/result fields and hashed evidence refs. This checklist belongs to the Compliance review pass. Use `ahub sdlc record <id> '<receipt JSON>'`; [schema and example](sdlc.md#acceptance-criteria-checklist).
3852
- - Deliverables (#509a, docs/sdlc.md, Deliverable records): `hub_task_done` and `hub_sdlc_record` take `deliverables: [{path, format, version?}]`; the hub hashes the files itself (a claimed hash is refused), in a plain folder with no git or PR, under the owner's execution root (its own worktree for a declared worktree instance; an unproven root refuses and reads `unknown`), and a reviewer's approval is refused with `deliverable changed since it was delivered` when a file changed since delivery (the owner calls `hub_task_done` again, or the person approves the current bytes). `ahub task show <id>` shows each file's state (`unchanged`, `changed`, `missing`, `unknown`) and `changedSinceReview`; a PII task or `pii.paths` file shows id, hash and size only. The person lists every task's delivered and approved files with `ahub deliverables [--task <id>] [--approved] [--check] [--json]`: without `--check` it reads the board's records and opens no file; `--check` measures them now and marks a row `changed since review`, `a file is missing` or `could not be measured`. The dashboard card shows a `Deliverables n` chip and the task sheet a Deliverables group from the same records; it shows a change only after a live check. An agent's shell is refused (#317).
4028
+ - Deliverables (#509a, docs/sdlc.md, Deliverable records): `hub_task_done` and `hub_sdlc_record` take `deliverables: [{path, format, version?}]`; the hub hashes the files itself (a claimed hash is refused), in a plain folder with no git or PR, under the owner's execution root (its own worktree for a declared worktree instance; an unproven root refuses and reads `unknown`), and a reviewer's approval is refused with `deliverable changed since it was delivered` when a file changed since delivery (the owner calls `hub_task_done` again, or the person approves the current bytes). `ahub task show <id>` shows each file's state (`unchanged`, `changed`, `missing`, `unknown`) and `changedSinceReview`; a PII task or `pii.paths` file shows id, hash and size only. The person lists every task's delivered and approved files with `ahub deliverables [--task <id>] [--approved] [--check] [--json]`: without `--check` it reads the board's records and opens no file; `--check` measures them now and marks a row `changed since review`, `a file is missing` or `could not be measured`. The dashboard card shows a `Deliverables n` chip and the task sheet a Deliverables group from the same records; it shows a change only after a live check. An agent's shell gets only the tasks `hub_task_show` would show it (#933).
3853
4029
  - The review request lists the recorded criteria with their ids and results, and a `hub_review` unmet item that starts with a recorded id (`AC2: ...`) is recorded on the verdict as `unmetCriteria` (#507b); `ahub task show` lists those ids in the history. A task with no checklist, and a PII task, are unchanged.
3854
4030
  - Advise shows incomplete/stale checklist blockers; enforce checks them at approval. The `criteria` gate does not need a workflow selector. Dependencies stay with `ahub task propose --after`; checklist task import is removed.
3855
4031
  - `status <id>`, `explain <id>` and `export <id>` use the same current board/config projection. Export keeps sanitized state and source hashes, with no external text, extra authority or verification claim.
@@ -3863,7 +4039,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3863
4039
 
3864
4040
  # Project profiles (#259)
3865
4041
 
3866
- - Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it.
4042
+ - Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it. `--profile <pack>` applies a domain pack (`.agenthub/roles/<name>.pack.json` or a shipped one) as review-checklist and example-check overrides; `--dry-run` previews ids only, and a pack is data, never a command ([domain packs](profiles.md#domain-packs-513a), #513a).
3867
4043
  - Non-dev plans declare paths, section anchors, source ids and claims. Configure completion checks explicitly in machine-local config; selecting a profile never starts a check or study. Schema, examples and pending converter/policy slices: [profiles](profiles.md).
3868
4044
 
3869
4045
  # Private and read-only data paths (#259)
@@ -3883,7 +4059,7 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
3883
4059
  # Person catalog-role assignment (#295)
3884
4060
 
3885
4061
  - Use `ahub role assign <peer> <id>` or `ahub role clear <peer> [<id>]`, or the dashboard Roles settings group. The selection is bounded to two compatible catalog ids, writes only machine-local settings and applies at the next peer start. Inherit and explicit no roles are distinct.
3886
- - Change an open task's guidance with `ahub task role <id> <role|none>`. Agent shells and peer/tools control clients cannot use these mutation commands. Details and unconfirmed native consumption: [roles](roles.md).
4062
+ - Change an open task's guidance with `ahub task role <id> <role|none>`. Agent shells and peer/tools control clients cannot use `role assign|clear`; an agent may run `task role` on a task it owns while it is not in review, and a child, read-only or private-context connection is refused (#933). Details and unconfirmed native consumption: [roles](roles.md).
3887
4063
 
3888
4064
  # Document projection review and undo
3889
4065
 
@@ -4072,7 +4248,7 @@ The manager allows split screening, the bounded database wait and forwarding mar
4072
4248
  ## One-way GitHub intake (#263)
4073
4249
 
4074
4250
  `ahub board import github --label <label> [--milestone <milestone>]
4075
- [--repo <owner/repo>]` is person-only. Omitted repo is resolved by gh in the
4251
+ [--repo <owner/repo>]` is the person's or the configured conductor's (#933; the conductor is recorded as the actor). Omitted repo is resolved by gh in the
4076
4252
  project directory. The command reads open issues with fixed bounded argv and
4077
4253
  stages up to 32 into unoffered Backlog, retaining canonical repo/issue identity
4078
4254
  across restarts. Repeating intake does not refresh or overwrite existing cards.
@@ -4340,7 +4516,7 @@ work. `ahub status` and the dashboard show the provider, model and profile as
4340
4516
  declared, and `ahub doctor` lists the MCP servers each rostered instance's home would
4341
4517
  load.
4342
4518
 
4343
- Upgrade note (#368): the project's `.jcode/mcp.json` entry for Jcode no longer names
4519
+ Upgrade note (task 368): the project's `.jcode/mcp.json` entry for Jcode no longer names
4344
4520
  `AGENTHUB_PEER_ID`, because the file is shared by every Jcode in the project and Jcode
4345
4521
  gives an entry's own env precedence over the process env. Each hub-started Jcode now
4346
4522
  supplies its own peer id and a launch id, and the hub admits a hub-tools connection
@@ -4357,7 +4533,7 @@ An extra Claude instance is one you start yourself: declare it with `roles` (for
4357
4533
  terminal. It keeps its own `claude-*.claude-review.json` state files. A planner or reviewer instance reads the
4358
4534
  project checkout and refuses `worktree`. From #525-1 an `implementer` or `verifier` instance is accepted and
4359
4535
  requires its own `worktree` (for example `"claude-impl": { "kind": "claude", "roles": ["implementer"],
4360
- "worktree": ".worktrees/claude-impl" }`). `ahub claude --as claude-impl` validates that registered branch worktree and starts Claude there while keeping its canonical hub connection. Its private launch record stores the execution root. The channel reports its own canonical cwd (verified model-free with Claude 2.1.309); the daemon grants owner isolation only when that root, the current launch id, the recorded root and its own worktree validation all agree. Missing, legacy or mismatched evidence still permits a channel attachment, but write-owner routing is refused with `owner admission: declared write owner requires a verified isolated execution root`. Status shows the isolation refusal. A detach, replacement claim or launch-record change invalidates the proof. Facts and permission grants use the verified execution root; an unverified implementer gets no automatic grant. Recovery records retain that execution root, checked against the current declaration, separately from the Orca grouping root. This is same-user launch evidence, not kernel filesystem isolation. A Claude instance holding only planner or reviewer roles still uses the canonical checkout. A headless Claude reviewer retains its reviewer-only declaration. A headless implementer is available with the separate declaration below (#525-4).
4536
+ "worktree": ".worktrees/claude-impl" }`). `ahub claude --as claude-impl` validates that registered branch worktree and starts Claude there while keeping its canonical hub connection. Its private launch record stores the execution root. The channel reports its own canonical cwd (verified model-free with Claude 2.1.309); the daemon grants owner isolation only when that root, the current launch id, the recorded root and its own worktree validation all agree. Missing, legacy or mismatched evidence still permits a channel attachment, but write-owner routing is refused with `owner admission: declared write owner requires a verified isolated execution root`. Status renders the last verified binding from memory, without workspace-Git revalidation; this display is not permission or owner-admission authority. Detach or replacement claims invalidate the binding. A launch-record or filesystem/branch change is checked at the next routing admission, facts permission reply or recovery owner proof, which revokes a mismatch before effects. Facts and permission grants use the verified execution root; an unverified implementer gets no automatic grant. Recovery records retain that execution root, checked against the current declaration, separately from the Orca grouping root. This is same-user launch evidence, not kernel filesystem isolation. A Claude instance holding only planner or reviewer roles still uses the canonical checkout. A headless Claude reviewer retains its reviewer-only declaration. A headless implementer is available with the separate declaration below (#525-4).
4361
4537
 
4362
4538
  A headless Claude implementer (#525-4) is declared separately:
4363
4539
 
@@ -4377,11 +4553,13 @@ The task's `refs.branch` is authoritative. Otherwise the first eligible task-com
4377
4553
 
4378
4554
  Check/test/build cannot write the project Git common/current directory at all, including objects, HEAD/index, refs/logs, other worktrees and either submodule tree. Closed host add/commit reuse core Git hardening, ask with configured filters/external drivers/textconv, and force signing/automatic maintenance off. A global Git LFS installation does not disable push/gh grants; only add/commit keep person approval so LFS conversions are not silently bypassed. Automatic push still does not run pre-push hooks. Config includes and partial clones refuse privileged Git/gh. Their command/paths/current task/branch are re-proved before execution. The host pins the default branch from the remote at startup, using a bounded isolated-config Git symref probe against the captured GitHub repository. Local origin/HEAD changes cannot redefine it. No credentials are supplied to that probe; unavailable/private-remote evidence or an unsupported Git layout asks. local.bash_network "direct" gives code direct network access. A setsid-detached code child can outlive owned-group cleanup while retaining its sandbox and permitted worktree writes.
4379
4555
 
4556
+ Default-branch capture runs only at daemon startup when a Claude implementer is declared. If the first implementer is added through config reload, automatic task-command grants remain unavailable and those commands ask. Restart the daemon with that implementer declared to capture the remote default branch. Reload does not retry a failed startup probe or refresh the captured default branch.
4557
+
4380
4558
  Project Git retains the core policy that clears credential.helper and core.askpass; an HTTPS push needing those helpers may fail, rather than importing credentials or relaxing that policy. Task branch/config proof reads are bounded synchronous reads (1.5 seconds per reader); slow or unknown evidence asks. The existing sandbox profile discovery is also synchronous; moving it to an asynchronous inventory proof with a shared deadline remains a follow-up. The nonce is visible in a permission response, so it proves a bounded grant rather than native-dispatch provenance.
4381
4559
 
4382
4560
  The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) covers Claude Code 2.1.309, and the [2.1.310 receipt](verification/2026-10-26-claude-implementer-2.1.310.md) repeats it on 2.1.310 (with 2.1.309 as a control), each with a deterministic loopback fixture and no real account/model call. It is not sandbox proof or a runtime receipt gate. The real account/model/person interaction remains a separate live leg.
4383
4561
 
4384
- A headless Claude reviewer is run by the hub instead (#223): declare
4562
+ A headless Claude reviewer is run by the hub instead (task 223): declare
4385
4563
  `"claude-review": { "kind": "claude", "roles": ["reviewer"], "start_mode": "headless" }`
4386
4564
  and start it with `ahub claude --as claude-review`, which asks the running hub to start it (nothing runs in that terminal); `ahub stop claude-review` stops it. It holds no process while idle; each
4387
4565
  review request runs one new `claude -p` session (never resumed) that may only read
@@ -4415,7 +4593,7 @@ From 0.12.37 reviews can rotate across fresh headless Claude and Codex reviewers
4415
4593
 
4416
4594
  A reviewer has a review scope (#852): `full` (reads the code, runs the affected tests, reads CI) or `code` (reads the code only). The fresh Codex and Kimi reviewers run in a sandbox that cannot run `bun test` or read CI, so they default to `code`; every other reviewer is `full`. A `code` reviewer's review request replaces the check and exact-head CI lines with one: its sandbox cannot run tests or read CI, review the code against the plan, and the merger gates on exact-head CI. Findings are only defects in the reviewed code, docs or tests; a missing check result, CI result, test run or PR/branch identity belongs in `unmet`, never in `findings`. A `code` reviewer's approval and verdicts record `reviewScope: "code"` in the task history. Its `changes_requested` is always recorded and never refused: with at least one structured finding whose disposition is `open` it counts as a rejection as usual; without one it is recorded as evidence-only, which does not count toward `rejections` or escalation, still returns the task to its owner, and sends the conductor one task notice with ids only. The review request therefore asks a `code` reviewer to report every code defect as a structured finding with an id. A person's verdict is never evidence-only. The scope is a fixed property of the definition. To change it for one instance, set `"review_scope": "full"` or `"code"` on that fresh-reviewer instance in the untracked `config.local.json` (a tracked declaration is ignored with a reason; the key is tier 3, so the dashboard and settings sessions cannot read or change it, and a hub daemon restart applies it). `ahub status` shows `review scope` on each reviewer row. A task approved by a `code` reviewer is merged only after exact-head CI is green and no review thread is unresolved, and the merge note names the CI run.
4417
4595
 
4418
- Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its #223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4596
+ Start them with `ahub claude --as claude-review`, `ahub codex --as codex-review` and `ahub kimi --as kimi-review`; stop with `ahub stop <id>`. For the Claude and Codex reviewers the command asks the running hub to start the reviewer and returns: nothing runs in that terminal, it takes no launch arguments, and `--print-command` or `--dry-run` prints a hub-start note instead of a launch (#720). Fresh Claude and Codex variants share the project checkout; the Kimi variant runs in a clean temporary working directory and reviews only the screened brief and host facts. All fresh variants refuse a custom worktree or permission mode. Codex and Kimi require `review_mode: "fresh"`; without it, an existing headless instance with the reviewer role keeps its native app-server/ACP behavior, worktree and permission options and does not join this pool. Claude retains its task 223 automatic headless-reviewer declaration. The agy reviewer slot remains automatically unavailable (`agy is an implementer peer (#485)`). A failed or unsupported fresh start never downgrades to a native continuing session. Ordinary implementer instances retain their own adapters. The default logical pool order is Claude, Codex, Kimi. Only configured kinds with at least one declared reviewer count as slots, including declared ones that are offline, paused, quota-limited or failing, so availability never renumbers them; configured kinds with no declaration are listed after the walk as `no declared reviewer`. `review.rerotate` defaults to false. Pool selection starts at PR number modulo the number of declared kind slots, or task id when there is no PR, then skips the implementer's family and unavailable candidates. Delta review prefers the preceding eligible independent family in a fresh child. A preceding same-family fallback does not outrank a newly available independent family. `ahub route explain` shows the same pure walk.
4419
4597
 
4420
4598
  Codex requires a verified `codex-cli` (0.146.0, 0.163.0 or 0.164.0) or one the person trusts on this machine, and a passing bounded runtime receipt before its model child starts. 0.146.0 and 0.163.0 passed the [2026-10-22 model-free production admission](verification/2026-10-22-codex-reviewer-isolation.md) and 0.164.0 the [2026-10-24 one](verification/2026-10-24-codex-0164-reviewer-isolation.md). A refusal names the installed and verified versions. Either set `codex_bin` to a verified executable, or run `ahub agents trust codex-review <version>`: trust lifts only the version pin, and every proof below still runs before each child (#691). Each launch approves exactly `hub_review` and `hub_task_show` per tool (`approval_mode = "approve"`), never through a server-wide default: without it Codex 0.164.0 listed the tools but refused the call, so reviews ended with no verdict (#783). A model-free receipt proves the inventory, not that a verdict reaches the hub; a [model-backed 0.164.0 run](verification/2026-10-25-codex-0164-reviewer-tool-call.md) recorded one verdict through that path. The producer checks native binary/config stability, configured MCP inventory with only the two hub tools, effective Apps/plugins/hooks disablement, an explicit empty legacy `notify` command and a named filesystem profile. Apps needs separate feature readback because configured inventory omits runtime connectors. Web search is requested disabled; its effective turn mode is not read back and managed requirements can select Cached (see the verification ceiling). Each launch proves public-read, exact/glob/symlink private-denial, standard host credential denial and write-denial in temporary fixtures. Standard credential and configured tool-home/XDG subtrees are denied independently of project `local.deny`. Its sandboxed shell is filesystem read-only with private paths denied and network configured disabled; it is not Claude's Read/Grep/Glob-only tool whitelist. No credential home is copied.
4421
4599
 
@@ -4425,9 +4603,9 @@ A dangling or unreadable standard/configured credential root makes the Codex rev
4425
4603
 
4426
4604
  agy is not a reviewer (person decision, 2026-10-19): it is an optional implementer peer (see [Optional Antigravity peer](#optional-antigravity-agy-peer)), and its retained reviewer slot is outside the default pool and always unavailable with `agy is an implementer peer (#485)`. A manual pool accepts only registered reviewer kinds (Claude, Codex, Kimi, agy), so local/Pi are refused. An explicit pool without declared candidates blocks for person action; an omitted pool retains legacy routing without declarations and falls back to an eligible planner when fresh candidates are exhausted. The exhausted pool walk remains recorded; without an eligible planner, the person reviews. A manual pool may name agy, or a Kimi reviewer whose preparation fails; the reason appears as an unavailable skip, and they never run or count as approval. Do not substitute plan mode for isolation.
4427
4605
 
4428
- A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A request that went stale before its child started (a new head, review revision or reviewer) is skipped with one `hub.log` line; a still-current request whose review policy changed moves on as `no_verdict`. Neither marks the reviewer unavailable (#781). A child that ends without a verdict leaves one bounded `hub.log` line: its exit code and the hub tools it called, screened like any refusal, never tool arguments or model text. Its delivery completes only once the re-offer is confirmed; an unconfirmed re-offer stays `needs_review`. A manually named reviewer's no-verdict, and one with no declared pool, goes to the person, never to an agent the person did not choose (#453). A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Its reason names one cause from a closed list (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) with a fixed hint, on the peer's `reviewer` line in `ahub status` and in a `hub.log` line, and never a path, native CLI text or token (#722). Only the Codex preparation classifies its failures; other reviewers report `unknown`. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4606
+ A failed start or any child ending without a saved verdict advances to the remaining eligible candidates; no quota meaning is guessed from text. A request that went stale before its child started (a new head, review revision or reviewer) is skipped with one `hub.log` line; a still-current request whose review policy changed moves on as `no_verdict`. Neither marks the reviewer unavailable (#781). A child that ends without a verdict leaves one bounded `hub.log` line: its exit code and the hub tools it called, screened like any refusal, never tool arguments or model text. Its delivery completes only once the re-offer is confirmed; an unconfirmed re-offer stays `needs_review`. A manually named reviewer's no-verdict, and one with no declared pool, goes to the person, never to an agent the person did not choose (task 453). A same-family fallback is a distinct fresh reviewer session. Exhaustion records `blocked: no reviewer can run`; with no explicit pool the eligible planner fallback is retained, while an explicit pool or absent eligible planner leaves person action. It never auto-approves. A per-review isolation preparation failure excludes that peer from later tasks until a verified restart. Its reason names one cause from a closed list (`version`, `binary`, `mcp_inventory`, `plugins_hooks_apps`, `sandbox_proof`, `credential_root`, `timeout`, `unknown`) with a fixed hint, on the peer's `reviewer` line in `ahub status` and in a `hub.log` line, and never a path, native CLI text or token (#722). Only the Codex preparation classifies its failures; other reviewers report `unknown`. Recovery targets must advertise `reviewer_pool` when a pool is explicitly configured or fresh reviewer instances are declared. Verdict history retains `reviewContext` and adds `reviewerFamily` and `reviewerReason` from the host.
4429
4607
 
4430
- Fake-CLI tests and CI cover these host contracts. [Offline isolation evidence](verification/2026-10-19-reviewer-isolation.md) and fake-CLI tests do not prove an account/model review or observed network denial. Native acceptance remains a separate [manual smoke leg](smoke.md#reviewer-pool-native-legs-519-2026-10-19); existing #223 Claude behavior has no new live proof here. Kimi's authenticated fresh-verdict receipts, and its still-open interactive-TUI coexistence and usage checks, are tracked in [Kimi smoke legs](smoke.md#kimi-reviewer-and-task-bound-mcp-533).
4608
+ Fake-CLI tests and CI cover these host contracts. [Offline isolation evidence](verification/2026-10-19-reviewer-isolation.md) and fake-CLI tests do not prove an account/model review or observed network denial. Native acceptance remains a separate [manual smoke leg](smoke.md#reviewer-pool-native-legs-519-2026-10-19); existing task 223 Claude behavior has no new live proof here. Kimi's authenticated fresh-verdict receipts, and its still-open interactive-TUI coexistence and usage checks, are tracked in [Kimi smoke legs](smoke.md#kimi-reviewer-and-task-bound-mcp-533).
4431
4609
 
4432
4610
  An additional implementer must have an existing registered Git worktree and
4433
4611
  attached branch at `.worktrees/<instance-id>`, named explicitly by `worktree` in
@@ -4466,13 +4644,13 @@ Run `ahub sdlc authorize-release <version>` from the running daemon's release-ca
4466
4644
 
4467
4645
  `ahub init --release-guard` installs a managed Git pre-push guard and release instructions (`--dry-run --json` previews them). It preserves an existing default hook as `pre-push.agent-hub-original` and passes original argv/stdin to it only after approval matches. Unknown or mismatched `v*` tag pushes fail closed, including when the daemon is down. Non-version branch pushes remain independent of the daemon. Custom `core.hooksPath` and linked/hardlinked hook files require manual integration. Re-run init after changing the Bun/package location. The person's `git push --no-verify` remains an explicit bypass; agents are instructed never to use it.
4468
4646
 
4469
- For `gh release create` and `npm publish`, use the exact literal command after `ahub sdlc release-guard --`. This preflight reads evidence and never executes the command. New GitHub tags require an explicit full commit target; alternate-repository, workspace/archive and bulk target forms refuse. GitHub/npm adapter enforcement is deferred to #312/#313. This consent record does not certify a tarball, native acceptance or live publication. Detailed limits: [S6 authorization](sdlc.md#person-release-authorization-481-s6).
4647
+ For `gh release create` and `npm publish`, use the exact literal command after `ahub sdlc release-guard --`. This preflight reads evidence and never executes the command. New GitHub tags require an explicit full commit target; alternate-repository, workspace/archive and bulk target forms refuse. GitHub/npm adapter enforcement is deferred to tasks 312/313. This consent record does not certify a tarball, native acceptance or live publication. Detailed limits: [S6 authorization](sdlc.md#person-release-authorization-481-s6).
4470
4648
 
4471
4649
  ## Planner release scheduling
4472
4650
 
4473
4651
  Use `ahub sdlc train report <release-id>` for scope readiness and recorded release metrics. The planner creates future release rows, activates one train, and assigns tasks through the release-only hub tools. A due date requests a cut review; it never clears unfinished work. Move missed tasks to the next row with a reason, then use `ahub sdlc train cut <release-id> <revision>` from the person's terminal or `hub_release_cut` as the planner. The cut retains a short host admission lease through freeze and defers on held gate slots or unknown/quota-limited peer windows with a real quota source. Accounts without a quota reader (Kimi included when `kimi_tokens_5h` is zero) report `no quota source`; manual and budget-record pauses still defer. It does not run release preparation or publish anything. Schedule bundle/check/docs work under separate heavy admission and retain the person's native verification, authorization and upgrade window. A hotfix records its enumerated cause and keeps every gate.
4474
4652
 
4475
- The per-release "gates run" record is not built: S7 shipped task metrics and bands without it, and release metrics with the gates-run record are the next slice. Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4653
+ The per-release "gates run" record is not built: it needs gate-execution evidence the S6 authorization path would supply, and a cut record is not that evidence. Release interval, approval-to-release lead time and hotfix rate are shown by the release report (#442). Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
4476
4654
 
4477
4655
 
4478
4656
  ## Codex cancellation identity notice
@@ -4490,7 +4668,24 @@ The design for planner, implementer and reviewer instances per stage is in the s
4490
4668
  - 525-4: a headless Claude implementer runs one owned child per turn in its declared worktree, with a separate persisted session per task (`start_mode: "headless"`, permission `ask` or `ask-when-needed`; `never-ask` refused).
4491
4669
  - 525-5: terminal Claude implementer instances own write-class tasks once the daemon has verified their execution root.
4492
4670
 
4493
- The operator steps for 525-1, 525-4 and 525-5 are in "Additional headless peer instances" above. Slice 525-2 adds the planner settings, task/release bindings, independent plan review and recovery floor described in "The planner" above. Slice 525-6 (Codex planner-only visibility) remains separate.
4671
+ For a Codex planner, put a declaration such as the following in the untracked `.agenthub/config.local.json`, then restart the daemon. A planner instance holds `planner` and neither `implementer` nor `verifier`; adding `reviewer` does not grant code-write ownership. It uses the project checkout, so `worktree` is refused.
4672
+
4673
+ ```json
4674
+ {
4675
+ "instances": {
4676
+ "codex-plan": { "kind": "codex", "roles": ["planner"], "start_mode": "headless" }
4677
+ },
4678
+ "planner": ["claude", "codex-plan"]
4679
+ }
4680
+ ```
4681
+
4682
+ Run `ahub codex --as codex-plan` from the person's terminal. The hub opens the app-server proxy without creating a synthetic host thread. Each native `thread/start` or `thread/resume` is requested read-only. The host rechecks the current settled roles when each thread is admitted and before hub injection, so changing a running instance from a continuing reviewer to a planner cannot reuse an unconfirmed writable thread. A live planner transition with a declared writing worktree is refused until the declaration is corrected and the daemon restarted. A reply that does not positively confirm read-only refuses that RPC and closes the unconfirmed link before the hub adopts the thread or delivers work; missing or malformed evidence is not read-only. The attached TUI belongs to the person and can change its native sandbox later: this is an initial thread ceiling, not a claim that the hub confines the person's TUI. A planner can own `plan` work but cannot become the code owner without the implementer role.
4683
+
4684
+ A planner-only Claude instance stays a channel instance on the project checkout, with no `worktree` or headless form. Its facts hook gives no automatic grant for mutating file tools or shell, whatever the hub permission mode. The daemon also returns `ask` for those calls in the existing grant reply field, protecting older hooks that do not recognize the added ceiling flag; the person's runtime mode itself is unchanged. A native choice in that person's terminal remains the person's; the builtin `claude` keeps its existing behavior.
4685
+
4686
+ Inspect the running holders with `ahub status` and the dashboard's Agents view. Each peer's `roleInfo` includes its running `form`, `capableRoles` and effective `flowRoles`; a planner holder also names its planner source. `form` distinguishes a channel, headless variant, terminal implementer or builtin form. Capability describes the allowed ceiling, not an extra assigned role. Each declared extra shows owner isolation as `isolated` or `not isolated: <reason>`; a planner using the canonical checkout is not an isolated write owner by design. Missing proof never counts as isolation. The isolation fields and status stall projection are cached display evidence; they may still show the last verified root before an effect point checks a filesystem change. Actual assignment, facts grants and recovery never authorize from this display cache.
4687
+
4688
+ Use `ahub task show <id>`, the console Tasks panel and the dashboard task detail to inspect planner, owner and reviewer with their recorded reasons. `ahub task show <id> --json` and `ui_snapshot` carry `planner` and `plannerReason` as additive fields; a legacy null planner stays unbound. Compare the holder walk with `ahub route explain <id>`. Use `ahub task assign <id> <peer> --role planner` from the person's terminal to name another eligible holder without moving owner or reviewer. Planner settings, bindings, independent plan review and the recovery floor are described in "The planner" above; implementer/reviewer setup is in "Additional headless peer instances". The [six-instance person smoke leg](smoke.md#six-role-instances-525-6) remains unverified until it is actually run.
4494
4689
 
4495
4690
  ## Daemon responsiveness and launch connects (#719)
4496
4691
 
@@ -4500,7 +4695,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
4500
4695
 
4501
4696
  ## amem runtime (agent-mem#13 P4)
4502
4697
 
4503
- The hub exact-pins @staix/agent-mem 0.10.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
4698
+ The hub exact-pins @staix/agent-mem 0.13.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
4504
4699
 
4505
4700
  ## Backlog issue identity and notes
4506
4701
 
@@ -4532,7 +4727,7 @@ Optional code work runs only while Memory is open and visible. A cold console co
4532
4727
 
4533
4728
  ## Authority by command
4534
4729
 
4535
- ### Conductor operations (#812 T2)
4730
+ ### Conductor operations (#812 T2, #933, #900)
4536
4731
 
4537
4732
  The selected project's configured conductor may run the commands below through its ordinary peer identity. Other peers retain the guidance "the person runs it in ahub console or a terminal". A missing or dead descriptor refuses promptly. For a live descriptor the connect and local authorization handshakes each keep a 3-second bound; connection unavailability is not presented as proof that the peer lacks the conductor role. Actual authority refusals are audited as refused, while ordinary operation failures retain their original cause. The daemon checks the authenticated connection, current role and project membership before effects and after asynchronous boundaries. Roster handoffs carry that same authority check through plan admission, owner delivery and each later task; a saved assignment is never rolled back when delivery is interrupted. Effectful operations and operational history reads retain an `agent_cli` admission with the initiating peer; audit failure refuses those operations. Authorization negotiation, `settings_preview` and `queue show` still check current authority but emit no extra admission record or conductor notice. Admission records are not a unique invocation counter.
4538
4733
 
@@ -4541,15 +4736,355 @@ The selected project's configured conductor may run the commands below through i
4541
4736
  | roster add/remove | Existing roster lifecycle and confirmations; no role or permission-mode grant |
4542
4737
  | stop peer | Existing hub-owned peer stop, ownership and recovery guards |
4543
4738
  | queue show/resolve/discard | Public queue projection and existing revision/confirmation guards |
4544
- | task cancel | Existing hub_task_cancel with the real peer; eligibility follows the task-board policy |
4545
- | doctor --orphans --kill | Selected project's positively identified hub-owned process only; no cross-project registrations or unsigned process files |
4739
+ | board import / intake approve | Existing import and intake admission; the actual conductor remains the actor |
4740
+ | schedule run/pause/resume | Existing machine-local schedule controls; no role or policy changes |
4741
+ | research backfill / bench run / source add / goal link | Existing bounded CLI or daemon paths, with fixed audit labels |
4742
+ | start Jcode | Launcher-only peer the conductor is configured to start |
4743
+ | task unassign <id> [--role owner\|reviewer] [--revision N] | The actual conductor is recorded; no replacement is routed and no native work is stopped |
4744
+ | restart --yes | Only with a source-issued continuation proof for this project's own operation |
4745
+ | upgrade --yes --to <exact-version> | Only with proof for this project's supported exact-version upgrade; newer coordinator handoff is person-only |
4746
+ | recovery resume\|abort [<op-id>] | Only the initiating conductor's own restart/authorized-upgrade operation; the person keeps the normal recovery path |
4747
+ | doctor --orphans --kill | Selected project's positively identified hub-owned process only; no cross-project registrations or unsigned process files. Prints the stopped pids for a person; `--json` prints the hub's reply |
4546
4748
  | logs / export | Bounded daemon projections, with unsupported/private records withheld; logs -f ends when authority ends |
4547
- | settings set | Only the twelve existing live registry keys listed below |
4749
+ | settings set | Only the thirteen existing live registry keys listed below |
4548
4750
 
4549
- The exact settings keys are `task_sweep.enabled`, `task_sweep.auto_reassign`, `task_sweep.no_writes_turns`, `task_sweep.interval_s`, `task_sweep.unaccepted_min`, `task_sweep.idle_min`, `task_sweep.review_min`, `task_sweep.ready_min`, `task_sweep.ladder_min`, `snapshots.enabled`, `research.enabled` and `approvals.notify`. New keys receive no authority by prefix. Generic roster setting writes still use the dedicated roster lifecycle. Conductor setting records name the initiating peer, and conductor writes preserve the person's pending undo record. An intervening write can still invalidate that undo through the existing file-digest guard; preserving the record does not bypass that guard. Permission modes, roles, `pet.window`, arbitrary configuration reload and undo remain person commands.
4751
+ The exact settings keys are `task_sweep.enabled`, `task_sweep.auto_reassign`, `task_sweep.no_writes_turns`, `task_sweep.interval_s`, `task_sweep.unaccepted_min`, `task_sweep.idle_min`, `task_sweep.review_min`, `task_sweep.ready_min`, `task_sweep.ladder_min`, `snapshots.enabled`, `research.enabled`, `approvals.notify` and `tasks.escalate_after`. New keys receive no authority by prefix. Generic roster setting writes still use the dedicated roster lifecycle. Conductor setting records name the initiating peer, and conductor writes preserve the person's pending undo record. An intervening write can still invalidate that undo through the existing file-digest guard; preserving the record does not bypass that guard. Permission modes, roles, `pet.window`, `models start|stop`, arbitrary configuration reload and undo remain person commands. `task cancel` remains a separate own-task `hub_task_cancel` permission, not a conductor operation.
4550
4752
 
4551
4753
  Log history is limited to the last 100 retained lines and export to the last 1000 retained events, each within a 128 KiB read. Truncation is explicit. An incomplete public projection reports one bounded reason on stderr, preserving export JSON on stdout. The complete privacy inventory has no 500-task cutoff and is cached until board or privacy/config revisions change, so log-follow does not rebuild it for every line. Export accepts closed metadata shapes; unknown fields and nested records are withheld rather than copied. Task titles, paths and private delivery bodies are not part of this projection. It does not change the person's ordinary logs/export output.
4552
4754
 
4553
4755
  Orphan cleanup requires a missing selected root, signed process identity and the host-known hub executable/entrypoint and argv. An inaccessible root or unavailable proof remains unknown; there is no offline config-based role grant. The daemon rechecks authority and process ownership before signals. A confirmed self candidate uses the existing lifecycle stop after its reply. A roster write already committed before authority ends is acknowledged as saved, without authorizing further lifecycle work or commands.
4554
4756
 
4555
- `restart --yes` and `recovery resume/abort` await a project-scoped continuation proof. `task unassign` follows #481, and legacy models start/stop awaits a project that needs it. This slice adds no task eligibility rule and no MLX/recovery-driver changes. Person authority and the separately authorized `--live-test` path retain their existing behavior.
4757
+ ### Conductor-bound restart and recovery continuation (task 535, #900)
4758
+
4759
+ The configured conductor may start a restart with `ahub restart --yes` or an
4760
+ upgrade with `ahub upgrade --yes --to <exact-version>` only after the source hub
4761
+ issues a bounded, fixed-shape private continuation record for that exact operation. The record is
4762
+ separate from public recovery JSON and its `delegatedAudits` history. A
4763
+ source-issued HMAC binds the operation id, initiating peer, project id and
4764
+ canonical root, source daemon instance, target root and package bytes, plan
4765
+ fingerprint, and a version-neutral digest of bounded source inputs and fresh
4766
+ actor/privacy evidence. A public operation entry or audit label alone grants
4767
+ nothing. The digest covers raw config-layer and routing-file texts, plus the
4768
+ source-resolved public routing template bytes when no project routing file
4769
+ exists; it does not hash expanded defaults, default roles or parsed routing.
4770
+ The public `conductorRoutingDefaults` map is advisory; the target inherits the
4771
+ sealed source fallback. The private record stores digests, not raw config,
4772
+ routing contents or the HMAC key.
4773
+
4774
+ Every planned project is preflighted for source recovery support and current
4775
+ authority before staging begins. An upgrade then stages and verifies its exact
4776
+ target; restart has no package stage. The source issues the private HMAC record
4777
+ after the target bytes and feature floor are verified, before publishing the
4778
+ public operation receipt, taking the lock or applying runtime holds. A source
4779
+ preflight refusal prevents staging; an unsupported staged target or failed
4780
+ proof prevents the public operation and its holds. A target requiring handoff
4781
+ to a newer coordinator remains person-only. At every effect boundary, the host
4782
+ rechecks the same peer's live conductor role and roster membership, project
4783
+ authority, read-only status and current non-PII/privacy policy. Offline
4784
+ continuation uses only bounded current actor/privacy checks; unrelated public
4785
+ board writes do not invalidate it. Changed or unreadable authority evidence
4786
+ stops the next effect and leaves the operation for the person.
4787
+
4788
+ An upgrade digest includes the exact staged `@staix/agent-mem` package from its
4789
+ hoisted install; a restart digest includes its verified source dependency.
4790
+ Protected or restart-bound source config/routing edits that have not been
4791
+ accepted remain person-only. The replacement validates the captured inputs
4792
+ before adapter imports and uses the sealed source fallback for startup routing.
4793
+
4794
+ Captured TUI terminal restoration remains person-only: a delayed native
4795
+ launcher cannot re-prove the conductor at its eventual spawn. Headless
4796
+ restoration uses the target-owned snapshot through a tools connection.
4797
+
4798
+ An unmanaged reconnect-only Claude channel may reattach itself without a native launch. The source keeps a SHA-256 fingerprint of its observed session identity separate from restoration metadata (the raw unmanaged session id is omitted from the plan) and verifies the exact peer, kind and attachment against the live channel. This exception requires no terminal or restoration arguments, no managed launch proof, and an absent or confirmed-ended launcher; unknown launcher evidence refuses. Other peer identities still compare exactly.
4799
+
4800
+
4801
+ Only the conductor that created the restart or authorized upgrade may run
4802
+ `recovery resume|abort` for that operation. A different conductor cannot take
4803
+ over; the person can use the ordinary recovery commands. The detached
4804
+ `recovery-run` and target launcher are internal continuations bound to the
4805
+ recorded operation and captured target arguments. They do not grant arbitrary
4806
+ console CLI commands. The existing `--live-test` path remains separate and does
4807
+ not create a conductor continuation.
4808
+
4809
+ `task unassign` runs as the conductor's own actor and does not route a
4810
+ replacement or stop native work. An owner unassign suppresses automatic owner
4811
+ offers until an authorized explicit assignment or reservation clears that
4812
+ suppression. This behavior has fixture coverage only; it does not establish a
4813
+ current installed-release or live-account receipt.
4814
+
4815
+ ## Standalone ciphertext broker (#446 X2)
4816
+
4817
+ From the operator's terminal, run `ahub broker serve --port 8788 --state-dir <private-directory>`.
4818
+ Default bind is 127.0.0.1; default storage is `broker/` under the selected hub state
4819
+ directory. Ctrl+C or SIGTERM closes the listener and SQLite handle together.
4820
+ Accepted ciphertext remains across restart. `GET http://127.0.0.1:8788/health`
4821
+ returns fixed version/status data.
4822
+
4823
+ For another machine, configure TLS termination and use
4824
+ `--public-url wss://relay.example/ --owner <owner-signing-fingerprint>`.
4825
+ Repeat `--owner` for each approved creator. Public WSS audiences, even on a loopback
4826
+ listener behind a proxy, require this allowlist. It controls new rooms only;
4827
+ existing member revocation still uses the owner's signed roster. Omitting the
4828
+ allowlist is allowed only in the loopback/no-public-audience development mode;
4829
+ do not expose that open mode through a proxy.
4830
+ The audience must be a canonical root URL without credentials, query or fragment.
4831
+ Proxy `/v1/rooms/<room>` as binary WebSocket, preserve Origin and expose WSS only.
4832
+ A non-loopback bind also needs `--hostname <address>` and an explicit WSS URL.
4833
+ The command configures no proxy, certificate, firewall or hosting account; live
4834
+ TLS and two-machine operation need their own operator receipts.
4835
+
4836
+ Keep `broker.db` and its directory outside version control. There is one process
4837
+ per database, enforced by SQLite's kernel lock; a second instance reports busy.
4838
+ A crash releases that lock. Do not delete the database as lock recovery: owner/
4839
+ epoch pins protect membership. Empty/corrupt/unsupported state refuses rather than
4840
+ forgetting pins. Restore a known valid backup or arrange a fresh room lifecycle
4841
+ explicitly; X3 clients must retain their own owner/epoch/replay state.
4842
+
4843
+ The first room connection proves its signed roster's owner identity; later ones
4844
+ prove current membership. Room create/invite/join/expose and the hub link are X3;
4845
+ ordinary chat forwarding and local policy are X4. A stored or queue_ack packet is
4846
+ transport/storage only; application delivery needs the recipient's separate signed
4847
+ receipt. Queue-full/backpressure refuses new work without evicting accepted data.
4848
+ See the [protocol and caps](specs/2026-09-19-agent-hub-design.md#encrypted-rooms-x2-blind-broker-446).
4849
+
4850
+
4851
+ ### Reclaim one broker room
4852
+
4853
+ Stop the broker first, then run
4854
+ `ahub broker room drop <room> --yes --state-dir <private-directory>` from the
4855
+ operator's terminal. Online attempts report busy; agent markers and `--live-test`
4856
+ delegation refuse. The directory/database must already exist. This forgets only
4857
+ that room's pins, sender floors and pending ciphertext, freeing its slot; it keeps
4858
+ other rooms and their pins. It is a destructive room reset, not membership
4859
+ revocation, secure erasure or confirmation that pending messages were delivered.
4860
+ Clients must retain their own owner/epoch/replay state. Remove an unwanted owner
4861
+ from the startup allowlist before restarting so it cannot recreate the dropped id.
4862
+ Never delete the whole database to free one room.
4863
+
4864
+ Existing populated state directories must contain only recognized broker files
4865
+ and already be 0700. Other populated directories, including a checkout, refuse
4866
+ without chmod. Empty directories may be tightened. Schema v1 migrates to v2 while
4867
+ preserving signed pins, floors and queued ciphertext. The 4 MiB per-room admission
4868
+ share prevents one room taking the whole aggregate byte budget; valid older larger
4869
+ queues stay available but block new admission until drained or expired.
4870
+
4871
+
4872
+ ## Outbound room membership (#446 X3)
4873
+
4874
+ Run room mutations from the person's terminal, never an agent shell or `--live-test`.
4875
+ Start a self-hosted broker as described above, then create a room with
4876
+ `ahub room create team --broker wss://broker.example/`. The owner fingerprint must
4877
+ be on a public broker's `--owner` allowlist. `room list` prints that fingerprint.
4878
+ No room joins or exposure are loaded from committed configuration.
4879
+
4880
+ Exchange these artifacts privately out of band:
4881
+
4882
+ 1. Owner: `ahub room invite team`, send the invitation token to the joining person.
4883
+ 2. Joiner: `ahub room join <token> --name team`, send the printed request back.
4884
+ 3. Owner: `ahub room invite team --accept <request>`, send the printed signed roster.
4885
+ 4. Joiner: `ahub room join team --roster <signed-roster>`.
4886
+
4887
+ Both consoles print owner and member fingerprints before installing/consuming.
4888
+ Compare them out of band, then answer the TTY's y/N prompt (default No). A non-TTY
4889
+ must pass `--confirm <member-fingerprint-prefix>` with 16..64 matching lowercase
4890
+ hex characters; it does not execute on a missing or mismatched confirmation.
4891
+ This flag does not delegate to an agent or bypass the `--live-test` refusal. Only the invitation token contains its one-time secret.
4892
+ The join request/roster contain no private key or invitation secret. Keep tokens
4893
+ out of shell transcripts, shared logs and source control. A saved pending join can
4894
+ be retried with the same token/name/label; `room leave team` cancels it if needed.
4895
+
4896
+ `room expose team codex` sets the untracked local exposure policy. On a non-owner
4897
+ it prints a signed exposure request: send it through the same owner `invite --accept`
4898
+ and joiner `join --roster` round trip. Empty exposure is the default. `room status`
4899
+ shows ids and closed states. The X4 chat workflow is below.
4900
+
4901
+ `room revoke team <fingerprint>` rotates the owner roster, giving no new key to the
4902
+ removed member. `room leave team` disconnects locally and retains replay/pin state;
4903
+ ask the owner to revoke membership as well. Hub shutdown closes links but keeps
4904
+ membership. Never delete rooms.db to repair a pin or sequence failure. Unknown or
4905
+ corrupt state disables rooms, while local hub work remains available. Real TLS,
4906
+ proxy forwarding and two-machine smoke remain unverified until the person runs X7.
4907
+
4908
+ ## Queue backpressure on automatic task offers (#800)
4909
+
4910
+ WIP and a peer's waiting messages measure different constraints. Backpressure is
4911
+ off by default. A person may opt in through untracked local settings:
4912
+
4913
+ ```sh
4914
+ ahub settings set tasks.queue_backpressure.max_queued 20
4915
+ ahub settings set tasks.queue_backpressure.max_oldest_s 600
4916
+ ```
4917
+
4918
+ The bounds are 10,000 messages and seven days; zero disables that threshold.
4919
+ Values strictly above a threshold hold new automatic owner/reviewer offers.
4920
+ `ahub route explain <task>` uses the actual assignment rule. `ahub status` shows
4921
+ queued count, oldest age and a fixed hold reason separately from active,
4922
+ review-wait and reserved WIP. Unknown age is shown as unknown and holds an offer
4923
+ when the age threshold is on. Existing queued work still drains normally.
4924
+
4925
+ After enabling age policy, old or recovered entries can lack host arrival proof;
4926
+ resaving/restarting does not make them fresh. Drain them or explicitly assign the
4927
+ particular task as the person/conductor. That override applies only to the named
4928
+ role, not other automatic choices, and does not waive privacy/independence/runtime
4929
+ admission. Automatic reserved planner pulls are still guarded.
4930
+
4931
+ Queue-origin holds are retried after committed queue/state/policy changes, without
4932
+ routing work a person explicitly unassigned. Automatic decline/budget reroutes
4933
+ retain their retry intent across planning updates; a named proposal keeps its
4934
+ target. A person reservation waits under pressure rather than moving to another
4935
+ peer. Self-claims enqueue no owner request and skip only that queue guard.
4936
+ Continuing reviewers remain bound unless their own queue refuses the new offer.
4937
+ Setting both values to zero returns
4938
+ to ordinary automatic assignment. This PR changes no live repository setting or
4939
+ routing template; real-use adoption metrics remain separate from fixture checks.
4940
+
4941
+
4942
+ ## Screened room chat (#446 X4)
4943
+
4944
+ After joining and confirming fingerprints, expose each receiving native peer with
4945
+ `ahub room expose team codex`. A non-owner returns the signed exposure request to
4946
+ the owner and installs the resulting roster as described above. Exposure is empty
4947
+ by default; this repository enables no room or exposure automatically.
4948
+
4949
+ `ahub room status team` lists remote addresses such as `codex@hub-guest`. Send to
4950
+ one explicitly:
4951
+
4952
+ ```sh
4953
+ ahub say @codex@hub-guest "Please inspect the public example"
4954
+ ```
4955
+
4956
+ A local exposed agent can use `hub_send` with that exact address. Native peers
4957
+ can exchange round-trips. X5b below adds the person's durable inbox for replies
4958
+ to `user@label`. An older X4-only receiver returns signed `failed_safe` with
4959
+ "person inbox not supported yet (X5)", rather than claiming admission. One remote plus
4960
+ local recipients is supported. X4 refuses multiple remote recipients before any
4961
+ delivery: send separately. Ordinary broadcasts stay local. Unknown or ambiguous
4962
+ addresses refuse. Private turns, PII task references and private envelopes do not
4963
+ cross; secret/path/pattern/model-screen failures produce an ids-only stub.
4964
+
4965
+ Room status and `ahub queue show <delivery-id>` distinguish accepted ciphertext
4966
+ from **completed (admitted)**, meaning the remote hub saved the chat to its local
4967
+ peer queue. Native/model execution is independent. Incoming messages remain
4968
+ untrusted text and grant no local task, role, approval, tool or control authority.
4969
+
4970
+ Offline queues are capped at 200 per room. In untracked config.local.json, optional
4971
+ `rooms.ack_timeout_s` defaults to 600 and `rooms.queue_ttl_h` to 24. A restart,
4972
+ expired deadline or uncertain publication leaves `needs_review`. It is not
4973
+ replayed automatically. A late signed receipt is visible as evidence in queue
4974
+ inspection; the person's disposition is still required. Old text cannot be
4975
+ retried to a replacement fingerprint reusing a label. An unproved terminal retry
4976
+ refuses and requires a deliberately authored new message.
4977
+ An expired or missing original enqueue deadline likewise refuses retry without
4978
+ recording a successful disposition. Send a new message deliberately instead.
4979
+
4980
+ The ACK timeout starts when the application is handed to the room link, even if
4981
+ the broker never confirms storage. A reconnect or restart requests fresh signed
4982
+ presence from the other hub without waiting for its peer state to change.
4983
+ Capacity refusal of one incoming chat retains that pending chat while allowing
4984
+ later packets and receipts through; a later online or person-disposition event
4985
+ rechecks admission. Merely reading room status applies no disposition.
4986
+ Up to eight matching receipt payloads per room have a separate 1,024-byte-per-row
4987
+ durable reserve so a full chat inbox can still settle outstanding deliveries.
4988
+ The normal chat cap is unchanged; unrelated receipts cannot use that reserve.
4989
+ Presence retired at the ordinary cap is re-fetched once after capacity frees,
4990
+ so that a dropped state update does not leave the remote view stale indefinitely.
4991
+ Fresh authenticated chat that reaches the full ordinary inbox is refused and
4992
+ transport-ACKed without admission or a signed receipt. The sender's application
4993
+ expires to `needs_review`; receipt traffic behind the refused chat continues.
4994
+
4995
+ For an incoming uncertain admission, copy its frame id from room status:
4996
+
4997
+ ```sh
4998
+ ahub room resolve team --frame FRAME_DIGEST --action accept
4999
+ ahub room resolve team --frame FRAME_DIGEST --action discard
5000
+ ```
5001
+
5002
+ Both forms show the owner/member fingerprints before asking y/N. Non-TTY use
5003
+ requires `--confirm` with a matching member fingerprint prefix. Accept records
5004
+ that the person vouches the local peer got it; discard records dropping it.
5005
+ Neither sends, re-admits, or produces a signed completion. The displayed state is
5006
+ **disposed (accept|discard, by person)**. These mutations refuse agent shells and
5007
+ `--live-test`, including after leaving a room.
5008
+
5009
+ Application-id and trace metadata retain 30 days/10,000 per room, evicting settled
5010
+ rows first. Protected uncertainty remains until person disposition. Outside that
5011
+ retention no historical app-id or trace claim is made; permanent seq floors and
5012
+ MAX_HOP 3 still apply. Status prioritizes uncertainty and bounds each direction's
5013
+ rows to 200; queue inspection remains the delivery-history entry point. Broker,
5014
+ TLS/proxy and real two-machine/account receipts remain separate live proof.
5015
+
5016
+ ## Room metadata views (#446 X5)
5017
+
5018
+ Press `8` in the panel console for Rooms, or open Agents > Rooms in the dashboard.
5019
+ The views show local room membership, owner/self fingerprints, member count,
5020
+ link state and exposed remote peer ids/states. They are hidden when an older hub
5021
+ supplies no room field or no rooms are configured. An unavailable read clears
5022
+ old metadata and shows an unavailable state. Reading status creates no room
5023
+ storage or keys.
5024
+
5025
+ Relay figures are **since daemon startup**, not lifetime totals: outbound is
5026
+ successful handoffs, inbound is confirmed local admissions, and withheld is a
5027
+ subset of outbound handoffs. Unknown values display as unknown, not zero. A
5028
+ daemon restart resets the counters; neither a handoff nor these totals means a
5029
+ native agent completed its work.
5030
+
5031
+ These views contain no message body/title, reason, broker URL, key material or delivery
5032
+ history. Use the existing queue inspection workflow for delivery uncertainty.
5033
+ The metadata-only views never show inbox bodies; use the separate person-only
5034
+ CLI below.
5035
+
5036
+ ## Person-only room inbox (#446 X5b)
5037
+
5038
+ List metadata for all rooms or one named room:
5039
+
5040
+ ```sh
5041
+ ahub room inbox [<name>] [--json]
5042
+ ```
5043
+
5044
+ The list contains local inbox ids, room/sender metadata, times, byte counts and
5045
+ states, never body previews. One operand is always a room name, even `show`,
5046
+ `done` or `discard`. Only an action plus an exact local UUID acts on a message:
5047
+
5048
+ ```sh
5049
+ ahub room inbox show <id>
5050
+ ahub room inbox done <id>
5051
+ ahub room inbox discard <id>
5052
+ ```
5053
+
5054
+ Only list accepts `--json`; none of these commands accepts `--confirm` or asks
5055
+ for a fingerprint. All inbox forms refuse agent shells, conductors and
5056
+ `--live-test`. They also refuse during recovery or a Bus hold. Show prints one
5057
+ sanitized, terminal-safe untrusted body and does not mark it done. Done/discard
5058
+ immediately purge the body and keep a metadata stub. Their acknowledgement is
5059
+ sent before any background room retry. Expiry does the same;
5060
+ there is no archive or read-after-done command. Capacity released by done/discard
5061
+ or a real TTL purge wakes already-deferred X4 traffic; no new receipt is sent for
5062
+ the purged entry, and idle ticks never poll queued plaintext. Dashboard, status, logs, memory,
5063
+ models and observers never receive the body.
5064
+
5065
+ The sender's **completed (inbox)** receipt means durable admission, not that the
5066
+ person read or completed the request. Reading, disposing or expiring the local
5067
+ copy never changes that receipt. Inbound session counts increment once at
5068
+ admission. Older X4 senders cannot parse the new inbox receipt reasons and stay
5069
+ uncertain; do not treat mixed-version traffic as verified success.
5070
+
5071
+ Limits are 100 unread messages per room, 64 KiB decoded body per message and
5072
+ 4 MiB canonical payload bytes retained globally, including JSON overhead and
5073
+ escaping (the displayed size remains decoded UTF-8 body bytes). The unchanged 64 KiB entire-wire cap
5074
+ leaves less than 64 KiB for a sendable body. Schema 4 starts the 30-day TTL at the
5075
+ first durable local crypto arrival, not remote time or restart. Legacy pending
5076
+ person traffic without that age gets fixed `failed_safe/inbox_age_unavailable`;
5077
+ native peer traffic is unchanged. Full, oversized or already-expired person
5078
+ admissions return fixed `inbox_full`, `inbox_oversize` or `inbox_expired` refusals.
5079
+
5080
+ Up to 1,000 settled stubs per room remain for 30 days after settlement, oldest
5081
+ first. Unread messages and their admission identities are protected, along with
5082
+ unsettled identities at the shared 10,000-per-room application cap. One loaded
5083
+ daemon expiry hook runs every 30 seconds, including for left rooms; it creates
5084
+ no empty room database or keys. Bodies remain plaintext in the protected local
5085
+ store, with no new encryption-at-rest or secure-erasure guarantee.
5086
+
5087
+ Lists are complete up to 1,100 rows. If an all-room list exceeds that limit, the
5088
+ CLI explicitly asks you to choose a room name; it never silently truncates.
5089
+ ponytail: pagination is later work; a filtered room's 100 unread plus 1,000
5090
+ settled stubs fit the current response ceiling.