@staix/agent-hub 0.30.0 → 0.32.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 (130) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -7
  3. package/docs/agent-notes/README.md +1 -0
  4. package/docs/agent-notes/adapters.md +5 -2
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +11 -4
  7. package/docs/agent-notes/daemon.md +16 -8
  8. package/docs/agent-notes/local-worker.md +1 -0
  9. package/docs/agent-notes/tasks.md +7 -3
  10. package/docs/agent-notes/tests.md +6 -4
  11. package/docs/design/adapters.md +52 -0
  12. package/docs/design/agent-hub-architecture.md +46 -0
  13. package/docs/design/bus.md +178 -1
  14. package/docs/design/task-board.md +82 -0
  15. package/docs/dogfood.md +23 -4
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +355 -78
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +24 -5
  21. package/docs/security.md +178 -60
  22. package/docs/specs/2026-09-19-agent-hub-design.md +127 -14
  23. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  24. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  25. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  26. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  27. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +311 -0
  28. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  29. package/docs/verification/2026-11-06-0.32.0.md +149 -0
  30. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  31. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  32. package/docs/verified.json +14 -14
  33. package/package.json +3 -4
  34. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  35. package/plugins/agent-hub/server.js +4393 -3609
  36. package/src/adapters/acp.ts +5 -4
  37. package/src/adapters/channel-peer.ts +72 -3
  38. package/src/adapters/claude-channel-evidence.ts +52 -0
  39. package/src/adapters/claude-channel.ts +61 -18
  40. package/src/adapters/claude-implementer.ts +29 -7
  41. package/src/adapters/claude-print-child.ts +12 -2
  42. package/src/adapters/codex-appserver.ts +13 -12
  43. package/src/adapters/compat.ts +3 -3
  44. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  45. package/src/adapters/definitions/qwen.ts +1 -0
  46. package/src/adapters/local-worker.ts +61 -18
  47. package/src/adapters/pi.ts +48 -6
  48. package/src/adapters/registry-types.ts +12 -0
  49. package/src/adapters/registry.ts +2 -0
  50. package/src/adapters/reviewer-print.ts +50 -11
  51. package/src/broker/server.ts +44 -13
  52. package/src/broker/store.ts +38 -8
  53. package/src/cli/answer-args.ts +50 -0
  54. package/src/cli/attention.ts +17 -2
  55. package/src/cli/backlog-return-args.ts +37 -0
  56. package/src/cli/console-question.ts +110 -0
  57. package/src/cli/console-state.ts +78 -17
  58. package/src/cli/console.ts +54 -1
  59. package/src/cli/handoff-args.ts +15 -5
  60. package/src/cli/handoff-output.ts +15 -2
  61. package/src/cli/help.ts +8 -5
  62. package/src/cli/identity.ts +13 -3
  63. package/src/cli/main.ts +128 -16
  64. package/src/cli/memory.ts +107 -3
  65. package/src/cli/output.ts +29 -6
  66. package/src/cli/pet.ts +4 -4
  67. package/src/cli/results-args.ts +30 -0
  68. package/src/cli/room.ts +20 -10
  69. package/src/cli/runtime-version-warning.ts +50 -0
  70. package/src/cli/status-lines.ts +41 -1
  71. package/src/cli/tail-render.ts +1 -0
  72. package/src/cli/upgrade-interactive.ts +14 -2
  73. package/src/hub/action-class.ts +9 -1
  74. package/src/hub/board.ts +369 -18
  75. package/src/hub/bus.ts +511 -53
  76. package/src/hub/completion-detail.ts +50 -0
  77. package/src/hub/completions-store.ts +168 -0
  78. package/src/hub/completions.ts +120 -0
  79. package/src/hub/config-apply.ts +7 -5
  80. package/src/hub/config-reload.ts +10 -1
  81. package/src/hub/control-client.ts +40 -4
  82. package/src/hub/daemon.ts +1576 -139
  83. package/src/hub/delivery-failure.ts +14 -0
  84. package/src/hub/delivery-hints.ts +14 -4
  85. package/src/hub/delivery-journal.ts +751 -42
  86. package/src/hub/delivery-superseded.ts +4 -1
  87. package/src/hub/envelope.ts +3 -3
  88. package/src/hub/events.ts +6 -0
  89. package/src/hub/handoff.ts +145 -6
  90. package/src/hub/hub-tools.ts +56 -7
  91. package/src/hub/manager.ts +59 -3
  92. package/src/hub/memory-policy.ts +44 -1
  93. package/src/hub/memory-sync-policy.ts +109 -0
  94. package/src/hub/memory-sync-state.ts +145 -0
  95. package/src/hub/no-writes.ts +1 -1
  96. package/src/hub/operational-settings.ts +11 -2
  97. package/src/hub/operational-view.ts +12 -1
  98. package/src/hub/peers.ts +25 -2
  99. package/src/hub/planning.ts +6 -1
  100. package/src/hub/question-desk.ts +201 -0
  101. package/src/hub/question-detach.ts +6 -0
  102. package/src/hub/questions.ts +260 -0
  103. package/src/hub/research-evidence.ts +106 -0
  104. package/src/hub/restart.ts +14 -0
  105. package/src/hub/review-anchor.ts +181 -0
  106. package/src/hub/review-brief.ts +13 -0
  107. package/src/hub/review-comments.ts +115 -0
  108. package/src/hub/review-findings.ts +7 -2
  109. package/src/hub/review-material.ts +76 -0
  110. package/src/hub/routing.ts +3 -1
  111. package/src/hub/task-sweep.ts +2 -0
  112. package/src/hub/tasks.ts +577 -63
  113. package/src/memory/capture.ts +5 -2
  114. package/src/pet/config.ts +25 -13
  115. package/src/rooms/commands.ts +61 -16
  116. package/src/rooms/link.ts +338 -36
  117. package/src/rooms/liveness.ts +11 -0
  118. package/src/rooms/remote-peer.ts +186 -19
  119. package/src/rooms/service.ts +475 -35
  120. package/src/rooms/status.ts +8 -1
  121. package/src/rooms/store.ts +447 -65
  122. package/src/rooms/wire.ts +24 -6
  123. package/src/ui/index.html +142 -9
  124. package/src/ui/needs.ts +10 -4
  125. package/src/ui/plugin-questions.ts +56 -0
  126. package/src/ui/plugin-results.ts +81 -0
  127. package/src/ui/plugin.ts +207 -27
  128. package/src/pet/window.html +0 -114
  129. package/src/pet/window.jxa.js +0 -122
  130. package/src/pet/window.ts +0 -154
package/docs/profiles.md CHANGED
@@ -25,6 +25,41 @@
25
25
  - Experiment attempts recorded on a study unit are read with `ahub study experiments --campaign <id>` (or `--attempt`, or `--plan` with `--baseline` and `--variant`) and `hub_experiment_show`. Each value shows its source: `declared` is the agent's own word, and only `host-observed`, `runner-attested` or `person-attested` rows are evidence, none of which proves the science. A rerun by a person attaches through an existing check, a hashed output file or a registered runner's signed receipt, as the checklist in `docs/operations.md` ("Experiment readback") says; without one the experiment stays declared and its sealing unverified. The synthetic CPU-only fixture is `test/research-experiments-fixture.test.ts`; no account, GPU or native model has run an experiment here.
26
26
  - The deterministic three-source, two-peer example (`test/study-daemon.test.ts`, "three-source, two-peer example"): one contract names sources S0, S1 and S2 and two discovery units, `alpha` (S0 and S1, owned by codex) and `beta` (S2, an independent check, owned by kimi). After `ahub study approve` both units read `none` and `0/2 covered`, `0/1 covered`. A handoff for S0 only moves `alpha` to `incomplete` with `1/2 covered`. A handoff for both S0 and S1, and one for S2, make both `complete` and the discovery round complete. Another peer's handoff for `alpha` is refused. This runs with fake peers: it shows the hub's rules, not that a native model follows the guidance.
27
27
 
28
+ # Claim evidence links (#931 T1)
29
+
30
+ An active owner of an in-progress research-profile task can record
31
+ `hub_evidence_record`. The task need not belong to a campaign. Researcher role
32
+ guidance grants no extra authority, and the tool is absent from dev-profile and
33
+ read-only publication. T1 refuses private-turn, PII-task and private-source writes.
34
+
35
+ Read the task with `hub_task_show` or `ahub task show --json`, then copy its
36
+ text-free `claims[].claimId` and `claims[].claimRevision` into the record. The
37
+ view also gives each slot. Refresh that view after claims change; clients never
38
+ need to reproduce the internal plan encoding or hash. PII and dev tasks expose
39
+ no such bindings.
40
+
41
+ Claim references use #901's canonical identity: `claim:<task id>:<created>:<slot>`
42
+ with a 1-based slot, and `claimRevision` equal to the plan's existing ordered-list
43
+ hash (`claimListId`). Any edit or reordering of that list stales every bound link;
44
+ restoring the exact list keeps #901's existing semantics. No second claim or
45
+ verdict store is introduced. A per-claim revision is a later upgrade if the
46
+ conservative list-level binding invalidates too much work.
47
+
48
+ The flat record carries task/claim/source IDs, raw and projection SHA-256 hashes,
49
+ projection version, `locator: {start, end}` and `relation` (`supports`,
50
+ `contradicts` or `context`). The host stamps `recordedBy`. Initial revision is 1;
51
+ an identical replay writes nothing, while a changed link names its ID and next
52
+ revision. Revisions remain append-only. The public receipt contains only link ID,
53
+ revision and task ID; arguments and results are excluded from memory capture.
54
+
55
+ T1 checks registered source identity/raw hash and stores a proposed zero-based,
56
+ half-open scalar-value range of at most 4096. Projection hashes/version and
57
+ relation labels remain declared. It stores no claim text, excerpt, origin/path or
58
+ invented page coordinates. Source/Unicode locator resolution and privacy
59
+ revalidation are T2; `hub_evidence_read`, study evidence/check surfaces and
60
+ synthesis-readiness binding remain later slices. Neither a matching citation key
61
+ nor an evidence relation creates a person verdict or proves entailment.
62
+
28
63
  # General project example
29
64
 
30
65
  - Initialize with `ahub init --profile general`.
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.30.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.31.0
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -29,7 +29,22 @@ For managed launchers, two-agent task/review work, approvals, graceful shutdown,
29
29
  and upgrade or crash recovery, see the [operations guide](operations.md). To
30
30
  move a running project to a newer release later, run `ahub upgrade` in a
31
31
  terminal in that project: it reviews the plan with you, applies it and follows
32
- it; other projects keep their version.
32
+ it; other projects keep their version. It then names the `bun add -g` and
33
+ `ahub setup` step when your global CLI or plugin is older than the new daemon.
34
+
35
+ ## Upgrading to 0.32.0
36
+
37
+ Rooms on an ephemeral-capable broker now refresh signed presence every 30 seconds; three missed periods display advisory unknown without changing sending authority. A graceful stop attempts a signed offline snapshot within two seconds. Epoch-changing room commands list sealed outstanding delivery IDs before confirmation and immediately hold old-epoch work with the named cause. Upgrade the global CLI and plugin with the step below to see the new previews and advisory status: a 0.31.0 CLI ignores those additive fields, even in JSON. A broker without ephemeral support keeps state-change publication and reports liveness untracked, with no periodic aging. Protocol 16 through 17 and room v1 shapes remain; the N-1 baseline is published 0.31.0, with the same agent-mem 0.21.0 pin.
38
+
39
+ `ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.32.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
40
+
41
+ ## Upgrading to 0.31.0
42
+
43
+ The control protocol stays at 16 through 17 and hub.db keeps schema version 2, but this release adds the nullable `deliveries.failure_cause` diagnostic column. N-1 recovery ignores the extra snapshot key, and older rows without it remain unknown. The exact `@staix/agent-mem` pin moves from 0.20.1 to 0.21.0; upgrading does not activate placement or migrate a memory home. `ahub memory declined` now lists complete bounded physical IDs and safe rule categories when inspection succeeds. Automatic v3 sync remains unsupported until agent-mem#281 provides activation and fresh host attestation. The [N-1 statement](../CHANGELOG.md) distinguishes the hub 0.30.0 fixture from agent-mem's own 0.20.3 compatibility proof.
44
+
45
+ Two defaults are new. An accepted channel delivery that is never confirmed moves to needs-review after 10 minutes (`bus.accepted_settle_ms`). A Claude session that only inherits `AGENTHUB_CHANNEL=1`, without the native channel launch, attaches tools-only and reads its messages with `hub_inbox`; `ahub status` names the mode. A 0.30.0 hub sending a handoff here is refused when the spec issue already has an open task, until it is upgraded and passes `--duplicate-ok`.
46
+
47
+ `ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.31.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
33
48
 
34
49
  ## Upgrading to 0.30.0
35
50
 
@@ -37,6 +52,8 @@ The hub now exact-pins `@staix/agent-mem` 0.20.1 (#1136). A setup receipt writte
37
52
 
38
53
  Nothing else changes until you use something new. `ahub worktrees` lists the project's live and settled worktrees (settled: its PR merged or closed with no newer work, or quiet for `worktrees.quiet_days`, default 7), and `ahub worktrees tidy` previews which settled ones are safe to remove, with the reasons for each refused row; only you remove them, with `ahub worktrees tidy --confirm <fingerprint>` in your own terminal, which runs `git worktree remove` and `git branch -d` and never forces (#1057). The dashboard's Agents panel gets a Worktrees section with Park, Pin and "Refresh PR states", and an inbox alert fires above `worktrees.alert_over` (default 10). Pi and the local worker name their own session with `hub_set_title`; `ahub title <peer> <text...>` or the console's `t` editor replaces the name (#1157). A plan's `sources` must now name registered `src:<16 hex>` ids (`ahub source list`), or the proposal or acceptance is refused (#1151). Run agy with `AGY_CLI_DISABLE_AUTO_UPDATE=1`: without it agy replaced its own binary during a launch (#1140).
39
54
 
55
+ `ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.30.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
56
+
40
57
  ## Upgrading to 0.29.0
41
58
 
42
59
  Upgrade every `amem` that shares your memory home together with the hub: the hub now exact-pins `@staix/agent-mem` 0.20.0, and an `amem` 0.19.0 hook or CLI cannot drain 0.20.0's new hook spool records and can retire them after seven days (#1131). Formats, shards, candidates and indexes are unchanged.
@@ -177,7 +194,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
177
194
 
178
195
  Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
179
196
 
180
- Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.20.1. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
197
+ Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.21.0. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
181
198
 
182
199
  From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
183
200
 
@@ -242,6 +259,8 @@ or set `OMNIROUTE_API_KEY`. The `.agenthub/routing.toml` that `ahub init` writes
242
259
  | `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
243
260
  | `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]` | tokens per group, each number with its source; `ahub usage limits` for pauses and resets |
244
261
  | `ahub worktrees` | live and settled worktrees; `ahub worktrees tidy` previews what is safe to remove, and only you confirm it with `--confirm <fingerprint>` |
262
+ | `ahub answer` | the questions agents asked you with `hub_ask`; `ahub answer <set> <question>=<option>` answers one set, or press `A` in `ahub console` |
263
+ | `ahub results [--unread]` | what the runs you started left behind, unread ones marked `*`; `--read <id>` or `--read-all` marks them read, listing never does |
245
264
  | `ahub agents` | which installed agent CLI versions the hub accepts; `ahub agents trust <id> <version>` accepts an untested one on this machine |
246
265
  | `ahub memory profile-check` | a person's read-only check of running memory bindings in the current registry |
247
266
  | `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
@@ -253,7 +272,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
253
272
 
254
273
  ## Shared memory
255
274
 
256
- The hub exact-pins the first-party `@staix/agent-mem` 0.20.1 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
275
+ The hub exact-pins the first-party `@staix/agent-mem` 0.21.0 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
257
276
 
258
277
  Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
259
278
 
@@ -265,7 +284,7 @@ benchmark runs every second. No build step, web server command or browser
265
284
  extension is needed.
266
285
 
267
286
  Use the page to pause/resume a peer, send a console message, propose/assign tasks,
268
- and allow/deny agent permission requests. Local-worker and Pi requests show no tool
287
+ allow/deny agent permission requests and mark results read. Local-worker and Pi requests show no tool
269
288
  contents: inspect them with `ahub tail`, then use `ahub permit <id> <option>` to
270
289
  allow. They can be denied on the page. PII task details remain available only via
271
290
  `ahub task show <id>` in the terminal. The page also starts a peer in its start
package/docs/security.md CHANGED
@@ -26,7 +26,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
26
26
 
27
27
  ## Native memory authority and profile selection
28
28
 
29
- The portable core is the exact-pinned first-party `@staix/agent-mem` 0.18.0 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
29
+ The portable core is the exact-pinned first-party `@staix/agent-mem` 0.21.0 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
30
30
 
31
31
  `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` take the project from the daemon, require `memory_read`, refuse private turns and validate UTF-8 input before logs or storage work. Complete records and graph metadata are screened before counts and truncation. Search is at most 20 lines/4 KiB (default 8); show accepts at most 8 ids and returns at most 8 KiB; graph is at most 120 nodes/240 edges/6 KiB (default 60 nodes), with no edge lacking an endpoint. Unknown, ambiguous and withheld ids share one neutral refusal. Arguments and results are excluded from hub-managed capture; this does not govern a vendor's independent transcript or memory hooks.
32
32
 
@@ -165,6 +165,44 @@ enrollment and X4 privacy/framing/role boundaries are in their own sections belo
165
165
  traffic metadata, availability, malicious owners and same-user compromise remain
166
166
  outside the encryption boundary.
167
167
 
168
+ Room presence uses a signed `ephemeral: true` header (#1204 T1). This reveals
169
+ presence traffic class to the blind broker, not peer state or body text. Only the
170
+ latest sender observation is retained for connected, ready recipients; offline
171
+ recipients and broker restart do not retain presence. Durable queue limits and
172
+ receiver replay/admission checks are unchanged. A `queue_full` storage refusal
173
+ names the verified frame digest; local bounded backoff retries that same sealed
174
+ frame and grants no completion authority.
175
+
176
+
177
+ The ephemeral advertisement is a transport capability, not authentication or
178
+ completion authority. Old brokers receive the legacy frame shape. The broker withholds ephemeral frames from older member
179
+ hubs until they upgrade; chat
180
+ frames remain compatible. See [room wire compatibility](design/bus.md#room-wire-compatibility-1204-t1).
181
+
182
+ Room liveness (#1204 T3) is advisory availability, never native execution or
183
+ process ownership proof. Periodic refresh and three-period expiry run only after
184
+ broker ephemeral negotiation. A legacy broker reports liveness untracked, with
185
+ no periodic refresh or aging. Unknown room presence adds no native `PeerState`
186
+ value or authority. Aging neither grants nor removes outbound admissibility;
187
+ signed roster, exposure, link and Bus proofs still govern sending. Explicit signed
188
+ offline presence keeps its existing handling. Freshness uses validated local
189
+ observations only; restored presence and rolled-back clocks cannot renew the
190
+ advisory liveness view, and status remains read-only.
191
+ Graceful stop fences publications synchronously and attempts one signed offline
192
+ frame in FIFO order within two seconds; durable intents/receipts remain, and
193
+ failure relies on supporting recipients' aging rather than remote process stop.
194
+
195
+ Before a person changes a room epoch, an ids-only preview binds already sealed
196
+ unresolved Store intents and the roster digest. The final commit rechecks both
197
+ with link publication quiesced. Old active intents are quarantined atomically
198
+ using the existing `boundary_refused` reason and schema, with sendable ciphertext
199
+ removed. T3's local Bus `needs_review` journal reason is the fixed
200
+ `room epoch rotated; inspect ahub queue show <id>` text; room status labels its
201
+ `holdReason` as `epoch rotated`. No body enters either reason. Bus-only queued work, terminal results and person
202
+ dispositions are unchanged. T2's existing crash-gap quarantine remains the
203
+ recovery boundary. No completion, replay, cancellation, new wire codec or new
204
+ native authority follows from the preview, rotation or liveness metadata.
205
+
168
206
  ## What is not defended
169
207
 
170
208
  - **A malicious local process running as you.** It can read the control token, `hub.db`, the project and your home directory without the hub's help. The token keeps web pages and other users out, not your own processes.
@@ -362,7 +400,7 @@ as that peer in tools mode. Messages and task changes retain that actor;
362
400
  capability. Conflicting or malformed markers fail closed. Refusals name only the marker variable names actually present, never their values. There is no `--as-user`.
363
401
  By default, console operations are refused before connecting, including permission answers
364
402
  and permission modes, agent version trust, budget overrides, role assignment, `--as-user`, recovery
365
- operations, the interactive terminals (`watch`, `pet --window`, a launcher's person-only flags), and `ask`
403
+ operations, the interactive terminals (`watch`, a launcher's person-only flags), and `ask`
366
404
  (#933 keeps the person's class to trust, money, attribution and interactive terminals).
367
405
  For live verification explicitly authorized by the user, an agent may invoke
368
406
  `ahub --live-test [--project <path|id>] <command> [args...]`. The flag delegates
@@ -389,8 +427,8 @@ messages, proposals and reviews. Peer starts, pause and resume may delegate
389
427
  operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
390
428
  person-authored planning/release/memory/goal writes, SDLC record, spec and plan-departure acceptance, release authorization, design-spec stamps (`ahub specs stamp`), agent version trust, benchmark run,
391
429
  `ahub recovery reattach`, a `--fresh-session` dispose that would replace a repaired
392
- session's conversation (or whose scope cannot be read), and the pet
393
- composer are refused; presentation flags such as `--color` or `--verbose` before
430
+ session's conversation (or whose scope cannot be read), `ahub answer` with an
431
+ operand and the `ahub results` receipt forms are refused; presentation flags such as `--color` or `--verbose` before
394
432
  the recovery action do not hide it from this check. No argument text is copied into this audit.
395
433
  Delegated dashboard restrictions are bound to its server-issued ticket/session:
396
434
  operational settings/start/stop controls remain, but message, proposal and
@@ -465,9 +503,10 @@ start (Jcode). Memory changes and the other `goal` changes stay the person's
465
503
  while amem records their actor as the user. `task unassign` is a conductor form;
466
504
  it keeps the conductor as the board actor. `task cancel` remains separately
467
505
  available through `hub_task_cancel` only on the caller's own eligible task
468
- (#933), not as a conductor operation; `task role`, `task label` and
469
- `ahub deliverables` retain their own-task checks. `models start|stop` and
470
- `pet --window` remain Console. `restart --yes`,
506
+ (#933), and for the conductor only on an unaccepted task another project handed
507
+ off (#1175), not as a general conductor operation; `task role`, `task label` and
508
+ `ahub deliverables` retain their own-task checks. `models start|stop` remains
509
+ Console. `restart --yes`,
471
510
  `upgrade --yes --to <exact-version>` and `recovery resume|abort` have the
472
511
  separate continuity proof described below. Each conductor operation is admitted
473
512
  after a recheck of role and project membership; effectful operations and history
@@ -557,7 +596,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
557
596
  - The exchange creates a separate random session, valid for one hour without renewal, in an `HttpOnly; SameSite=Strict; Path=/` cookie named for this listener's port. Sessions and tickets live only in memory and die with the daemon. The page itself keeps one more cookie, `agent-hub-theme` (issue #246): script-set, `SameSite=Strict`, kept for a year for the whole `127.0.0.1` host, and read back only as one of `system`, `light` or `dark`. A second script-set cookie, `agent-hub-office` (issue #264), keeps the Office view choice under the same attributes and is read back only as `off`, `flat` or `isometric`. The #391 view split adds two more script-set cookies under the same attributes: `agent-hub-view`, read back only as one of the seven view names, and `agent-hub-column-focus`, read back only as one of the five board columns or empty. HTTP is loopback-only; the cookie is not a substitute for the origin checks.
558
597
  - Every request must have the exact listener Host. Every data or action request, including the ticket exchange and snapshot polling, must be POST with the exact listener Origin and JSON content type. Foreign and missing origins, missing or expired sessions, oversized bodies and unknown actions are rejected. There is no CORS support. The only unauthenticated GET is the fixed, data-free HTML shell. No files or paths are served dynamically.
559
598
  - Responses are non-cacheable, cannot be framed, suppress referrers, and use a content security policy restricting scripts/styles to the shipped inline content and connections to this origin. Browser text is rendered with textContent, never interpreted as HTML.
560
- - The browser receives a bounded redacted event stream, peer states and queue counts, public task views (with a task's screened changed-symbol observation, #684: ids, paths, names and kinds, never source or line spans, and its Code group, #684 phase 2c: those symbols marked against the plan's names, each name re-screened by the public metadata screen; a private task has no group and its plan names never enter the snapshot), budget windows, each peer's DOING line (#878: a native session title or current plan item, at most 120 characters, kept in daemon memory only and screened when observed and again when shown; a failed screen, a private turn or the peer's open PII task shows `[private]`), pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text, the Usage view (#683 U4), computed only while the Agents view is open: peer ids, UTC days and token counts with their source, budget pause, resume and reset events without their reason text, and per-task rows of the task id, a PII flag and token counts, never a title, and the office view's optional fields (issue #264): each peer's open turn (start time, attributed task id, attribution kind), a `checking` flag on tasks whose check is running, and warning `links` of overlapping, conflicting or cohort tasks (task and peer ids only, never paths, symbols or reasons; a PII task never joins a link, and a private or PII-attributed turn exposes no task id). Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted.
599
+ - The browser receives a bounded redacted event stream, peer states and queue counts, public task views (with a task's screened changed-symbol observation, #684: ids, paths, names and kinds, never source or line spans, and its Code group, #684 phase 2c: those symbols marked against the plan's names, each name re-screened by the public metadata screen; a private task has no group and its plan names never enter the snapshot), budget windows, each peer's DOING line (#878: a native session title or current plan item, at most 120 characters, kept in daemon memory only and screened when observed and again when shown; a failed screen, a private turn or the peer's open PII task shows `[private]`), pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text, the Usage view (#683 U4), computed only while the Agents view is open: peer ids, UTC days and token counts with their source, budget pause, resume and reset events without their reason text, and per-task rows of the task id, a PII flag and token counts, never a title, and the office view's optional fields (issue #264): each peer's open turn (start time, attributed task id, attribution kind), a `checking` flag on tasks whose check is running, and warning `links` of overlapping, conflicting or cohort tasks (task and peer ids only, never paths, symbols or reasons; a PII task never joins a link, and a private or PII-attributed turn exposes no task id). Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted. An ordinary (not live-test) session's snapshot also carries the open questions with their full text, a private one included, for the person's answer form (#1053), and unread results with the detail built when shown (#1054). An explicit review refresh of one task adds its person review comment threads and placement, with PII and private-path comments as counts only (#1056).
561
600
  - The closed action list includes permission response, peer pause/resume, console message, task proposal/assignment and the settings actions below (set, undo, preview, reload, and the structural edit preview and apply of a settings session). Project-board actions add revision-fenced planning, Backlog pull/return/split/undo and release planning/state changes through Tasks; they do not tag, publish or write GitHub state. These use the existing daemon/task paths and budget pause rules. `start_peer` (issue #269) starts a peer from a closed list in the mode a person set, with a command the hub assembles: the request carries a peer id and nothing else. There is no generic control proxy, task-detail read, shell, file access, free-form configuration edit, budget override, launch of a caller-chosen command or daemon shutdown API.
562
601
  - The listener stops with the daemon. Expired sessions must be reopened with `ahub ui`; the page does not silently obtain new credentials.
563
602
 
@@ -598,30 +637,38 @@ its existing basename/worktree aliases and provider accounts can remain shared.
598
637
 
599
638
  `@staix/agent-hub/ui-plugin` runs inside the agent-console (`acon`) process as the
600
639
  same user. It holds no credential of its own: it reaches project hubs only through
601
- the multi-project manager's listing and forwarding above, which connect as the
602
- console with each project's control token, so it has exactly what `ahub ui --all`
603
- has. Every action must be on the manager's closed list and every view one of the
604
- seven view names, and there is no generic control proxy, task-detail read, shell,
605
- file access or caller-chosen command.
640
+ the multi-project manager, which connects as the console with each project's
641
+ control token. Beyond what `ahub ui --all` has, it uses dedicated manager ports the
642
+ dashboard page cannot reach: `answer` (#1053), `receipt` (#1054) and the review port
643
+ (`review_comment_put|delete|send`, `review_comments`, `review_blob`, #1056). Every
644
+ write through them requires acon's verified `ActionMeta`, as described in those
645
+ sections. Every other action must be on the manager's closed list and every view one
646
+ of the seven view names. There is no generic control proxy, task-detail read, shell
647
+ or caller-chosen command, and the only file read is the bounded review material of a
648
+ public task (`review_blob`).
606
649
 
607
650
  - **Registration.** `ahub setup` writes `console/plugins/ahub.json` under the hub
608
651
  home (directory 0700, file 0600, atomic replace) with the module path and its
609
652
  sha256; acon refuses to load a module whose hash no longer matches.
610
653
  - **Confirmation.** Outward or destructive actions (`stop`, `permit`, `send`,
611
654
  `setting`, `setting_undo`, `setting_reload`, `config_edit_apply`, `start_peer`,
612
- `backlog_split` and the inbox's Allow once and Deny) first answer `needsConfirm`
655
+ `backlog_split`, `worktree_override_set`, `worktrees_refresh_prs`,
656
+ `review_comment_send`, `review_comment_delete` and the inbox's Allow once and Deny) first answer `needsConfirm`
613
657
  with a preview and a fingerprint, a sha256 of the request's own fields; the call
614
658
  runs only when it comes back with `confirmed` and that fingerprint. The
615
659
  fingerprint binds a confirmation to one exact request, so a changed request needs
616
660
  a new confirmation; it is not a secret, and the confirm step is acon's host UI
617
661
  contract, not authentication. A process that can load the module already has the
618
662
  console's reach, and the hostile-agent boundary above applies unchanged.
619
- - **Inbox.** Items are the pending approvals and the in-review tasks whose reviewer
620
- is the person, read from the same redacted dashboard snapshot: a local-worker or
663
+ - **Inbox.** Items are the pending approvals, the in-review tasks whose reviewer
664
+ is the person, open questions (#1053) and, for a console that gives receipts
665
+ person evidence, unread results (#1054), read from the same dashboard snapshot: a local-worker or
621
666
  Pi approval shows only the fixed private stub and offers Deny, and Allow once for
622
667
  it is refused with "answer this approval with ahub permit". Allow once and Deny
623
668
  resolve to the hub's own `permit` with an option the pending request offers,
624
- looked up again at the time of the call; nothing else is reachable from an item.
669
+ looked up again at the time of the call; nothing else is reachable from an approval
670
+ item. A question's Answer and a result's mark-read go only through the person-evidence
671
+ paths in "Questions a peer asks the person" and "Results a person's runs leave behind".
625
672
  Task titles are the public view's, so a PII task shows the `[pii]` stub.
626
673
  - **Space.** The Space scene is built only from the office model the dashboard
627
674
  already allowlists (#264): peer ids, conditions and counts, task ids, classes,
@@ -644,6 +691,15 @@ file edits, and a configuration reload refuses roster changes. External ids outs
644
691
  admission rules. A roster key left by an undeclared instance loads as inert and grants
645
692
  nothing; the only change it accepts is its removal from `config.local.json`.
646
693
 
694
+ Handing a peer to a standby session (#1174) is the person's: `peer_takeover` is
695
+ console-role only, and `ahub peer takeover` is refused to agent shells and
696
+ `--live-test`. A standby connection runs no peer operation until it owns the peer,
697
+ and the selected standby is rechecked for membership, launch and isolation,
698
+ lifecycle and claim generation after its preface. Standby ids and reported PIDs are
699
+ display metadata, never native-session or permission evidence. Takeover detaches
700
+ the old hub connection, not its native process; that session's later hub calls are
701
+ refused, and takeover never marks a delivery completed.
702
+
647
703
  Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
648
704
  merges/removes only the hub MCP entry; the daemon never edits that file and
649
705
  refuses headless starts while a stale entry exists. An attached Kimi TUI blocks
@@ -759,45 +815,47 @@ historical claude-mem database used only by the explicit read-only import.
759
815
  Sync starts only after a person configures a remote. Public GitHub remotes are
760
816
  refused; unknown visibility proceeds with a warning for the configured remote.
761
817
  Short-term segments, machine identity and the derived index are excluded from
762
- sync. Suggested graph edges are off by default and are not evidence until a
763
- person confirms them. Operational details and limits: [hub memory](memory.md).
764
-
765
- ## Standalone pet window (#391)
766
-
767
- The macOS window reads local files only. Its detached host reuses `readPet`,
768
- including signed-daemon liveness and recovery-lock handling, and publishes an
769
- atomic counts-only `pet-view.json` (state, act/watch, known character, blink,
770
- palette from the closed list, built-in pixel frame). It excludes item ids,
771
- task titles/text, refs, paths,
772
- notices and approval prose. Missing or unconfirmed status produces only
773
- `{offline:true}`. Process and position files are separate from this view.
774
-
775
- The local WKWebView has a deny-by-default CSP, with only its shipped inline
776
- script/style allowed and network connections forbidden. Text never becomes
777
- HTML or executable source. The retained JXA bridge accepts fixed drag/send/
778
- palette/open/close operations; `NSTask` invokes the host-supplied Bun/CLI/project argv
779
- and appends `say`, `--` and a single message argument, or `pet`, `--palette`
780
- and one closed palette name. It does not evaluate a
781
- shell string. Message text is never logged by the window/host; command stdout
782
- and stderr go to the null device. `ahub say` keeps its existing console-message
783
- authority and recording behavior, and `ahub ui` opens its existing session.
784
- The CLI restricts `pet --window` (including close) to console identity, while
785
- plain terminal pet reads and `pet --palette` remain available to agent shells;
786
- the palette write merges only the closed name into `.agenthub/config.local.json`,
787
- validated and replaced by temp file and rename. Signed pidfiles
788
- fence instance ownership and close; unknown owners are never signalled.
818
+ sync. Sync and direct long-term writes apply the configured agent-mem sync policy
819
+ narrowed by the hub's deny paths, PII patterns and project-private paths; a promotion
820
+ that fails it leaves the note short-term (#1173). Status exposes safe sync state and
821
+ counts, including verified withheld/exported counts from a successful SDK placement
822
+ result. Failure logs retain only the closed cause and known declined count.
823
+ `ahub memory declined` is person-only (refused to agent shells and `--live-test`)
824
+ and uses bounded physical-row inspection to show IDs and rule categories, never
825
+ note text or literal patterns; incomplete inspection is not a zero count.
826
+ Default-host v3 sync remains unsupported because the hub supplies no fresh
827
+ placement attestation. No activation proof is fabricated; the supported activation
828
+ and attestation path remains agent-mem#281. Suggested graph edges are off by default
829
+ and are not evidence until a person confirms them. Operational details and limits: [hub memory](memory.md).
830
+
831
+ ## Floating pet retirement (#1012)
832
+
833
+ The standalone JXA host, WKWebView, window bridge and its packaged assets are
834
+ removed. `ahub pet --window [--close]` only prints the `acon ui` Mini pointer and
835
+ exits successfully; it has no launcher, process-control or network effect and
836
+ is available to agent shells and `--live-test`. Legacy `pet.window` is ignored
837
+ as compatibility input and grants no configuration or conductor authority.
838
+
839
+ Terminal pet reads retain their existing behavior. The retired `pet --palette`
840
+ command validates its argument and prints the same Mini pointer without writing
841
+ `.agenthub/config.local.json`. Existing `pet.palette` values remain validated
842
+ compatibility input and have no rendering effect.
843
+
844
+ The **Console message** view already exists in the agent-hub acon plugin
845
+ (#935). Its existing `send` action retains message audience, PII and actor
846
+ checks; the retired window adds no alternative composer path.
789
847
 
790
848
  ## Planner, cancellation and delivery authority
791
849
 
792
850
  The `planner` setting is person-controlled: one peer id or a list of up to eight (#525-2). A valid pin determines the planner holders without adding them to the roster; otherwise configured rostered planner roles apply, then the first rostered peer in the closed planner catalog. The automatic choice is not written to disk. Each new non-PII task and each planner-created release records one effective planner from the same pure walk `ahub route explain` shows; only that planner or the person changes its planning. A bound planner keeps that authority while busy, paused or offline; one removed from the holders or the roster falls back to the current holders, and older rows with no binding, PII tasks included, keep holder-wide authority. Only the person moves a task's planner (`ahub task assign <id> <peer> --role planner`). Another holder may record a plan review without gaining the task's other planning powers; an agent cannot review its own plan while another rostered holder is not offline, and otherwise the receipt records `selfReview: true` with `planAuthor`. Planner guidance does not grant conductor authority or bypass roster, PII, WIP or owner capability checks.
793
851
 
794
- `cancelled` is closed, never approval. The person can cancel non-approved work with a reason and optional supersession reference; a planner can cancel only its own proposed/backlog work with no historical acceptance, and only as that task's effective planner. Current task revision, privacy and recovery/shutdown holds are checked before writing and again after awaited screening. Cancellation preserves history, releases reservations and overlap membership, writes no outcome memory, and does not stop native turns or delete branches/worktrees. Owner/reviewer stop notices remain relevant; earlier task requests become moot. Dependents remain blocked and receive ids-only advice, including backlog dependents.
852
+ `cancelled` is closed, never approval. The person can cancel non-approved work with a reason and optional supersession reference; a planner can cancel only its own proposed/backlog work with no historical acceptance, and only as that task's effective planner. The configured, rostered conductor can cancel only a task another project handed this one (#1175), while it is proposed or backlog, nobody has accepted it, it has no execution evidence and it is not PII. Current task revision, privacy and recovery/shutdown holds are checked before writing and again after awaited screening. Cancellation preserves history, releases reservations and overlap membership, writes no outcome memory, and does not stop native turns or delete branches/worktrees. Owner/reviewer stop notices remain relevant; earlier task requests become moot. Dependents remain blocked and receive ids-only advice, including backlog dependents.
795
853
 
796
854
  Current daemons advertise `task_cancel` in hello and status metadata. A current CLI refuses cancellation against a daemon without that feature. Controlled upgrade/restart refuses an unsupported target while cancelled task ids exist; source prepare and commit recheck compatibility, and the rows are never rewritten as approved. In the same way it refuses a target without `task_planners` once a planner list or an instance planner is configured or any task or release planner binding or plan-review authorship is stored, even after that configuration is removed. A manually started older binary is outside this compatibility guarantee: it can display cancelled rows as open or fail to move them.
797
855
 
798
856
  Stall causes are persisted and deduplicated by cause and replanning revision. Planner advice joins its next delivery as bounded ids-only context; it does not manufacture an ordinary task turn. Pull-only offer attention clears only after acceptance, decline, supersession or durable positive evidence for that exact offer and recipient. Operator, hub or owner completion of an uncertain delivery is not evidence that the offer was read.
799
857
 
800
- `hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never pause removal. `hub_delivery_resolve` (#1112) lets a local agent mark only its own `needs_review` row `completed` or `discard`, at the revision `hub_inbox` showed, with a public, screened reason recorded under its actor. It never replays the originals or changes task state. An unknown id, another peer's row and a row that is not `needs_review` share one refusal, so the answer never confirms another peer's row. The person, the hub, a remote peer, a paused or held peer, and recovery, shutdown or storage holds are refused. A row that carries an invocation stays person-only, because its outcome is uncertain. An explicit `hub_inbox` read may return newly queued messages behind such a hold, never the held originals, and only when no recovery, pause, lifecycle or storage fence applies. Durable per-original evidence survives journal pruning; unknown retry history or a used retry requires the person. Hub presence prefaces are excluded from attempted-work identity, so a shared context preface does not consume an unrelated message's retry. Delivery-only local and ACP peers (including Jcode) cannot invoke recipient retry; tool exposure hints do not replace the daemon's actual-adapter guard. Their held work requires person resolution. Before transport handoff, storage failure preserves the queue and preface; after handoff an uncertain effect is never automatically replayed.
858
+ `hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never pause removal. `hub_delivery_resolve` (#1112) lets a local agent mark only its own `needs_review` row `completed` or `discard`, at the revision `hub_inbox` showed, with a public, screened reason recorded under its actor. An attached Claude push-channel session may also settle its own `accepted` row that way (#1172); any other peer's `accepted` row is refused. It never replays the originals or changes task state. An unknown id, another peer's row and a row in neither state share one refusal, so the answer never confirms another peer's row. A channel `accepted` row that gets neither native completion nor that disposition within `bus.accepted_settle_ms` (default ten minutes) becomes `needs_review` with no replay. With `conductor.feed` on, the configured conductor gets one ids-only notice for an important, task or review timeout. The person, the hub, a remote peer, a paused or held peer, and recovery, shutdown or storage holds are refused. A row that carries an invocation stays person-only, because its outcome is uncertain. An explicit `hub_inbox` read may return newly queued messages behind such a hold, never the held originals, and only when no recovery, pause, lifecycle or storage fence applies. Durable per-original evidence survives journal pruning; unknown retry history or a used retry requires the person. Hub presence prefaces are excluded from attempted-work identity, so a shared context preface does not consume an unrelated message's retry. Delivery-only local and ACP peers (including Jcode) cannot invoke recipient retry; tool exposure hints do not replace the daemon's actual-adapter guard. Their held work requires person resolution. Before transport handoff, storage failure preserves the queue and preface; after handoff an uncertain effect is never automatically replayed.
801
859
 
802
860
  A declared instance whose only role is reviewer gets review requests, task messages addressed to it and the person's messages; agent chat, digests and presence are discarded and journaled, and it gets no memory recall (task 223). The headless Claude reviewer runs one new `claude -p` per review with `--setting-sources ""`, `--strict-mcp-config`, `--no-session-persistence`, tools limited to Read, Grep, Glob, `hub_review` and `hub_task_show`, and `dontAsk`. Its prompt holds hub facts and a bounded diff, never the builder's summary, and its `hub_task_show` drops the builder's notes. Changes touching `local.deny` or `pii.paths` are withheld and passed as `Read` deny rules, which Claude enforces, not the hub. Private tasks are never given to it. Its `hub_review` counts only while the child started for that task still runs; a stop or exit revokes it first, and surviving processes hold the delivery as `needs_review` and keep the reviewer busy.
803
861
 
@@ -805,7 +863,7 @@ A terminal Claude implementer or verifier instance (#525-5) owns write-class tas
805
863
 
806
864
  A planner-only instance (#525-6: `planner` without `implementer` or `verifier`) plans on the project checkout, so a declared `worktree` is refused. A Codex planner's `thread/start` and `thread/resume` are requested read-only, and a thread is adopted only when Codex's reply positively confirms a read-only sandbox; an unconfirmed or malformed reply refuses that RPC and closes the link before the hub adopts the thread or delivers work, and the current roles are rechecked before each injection. This is an initial ceiling, not confinement of the person's attached TUI, which can change its native sandbox later. A declared planner-only Claude channel instance gets no automatic grant from its facts hook for any tool but Read, LS, Glob and Grep, whatever the permission mode.
807
865
 
808
- The headless Claude implementer (#525-4) runs one owned `claude -p` child per turn in its declared worktree, with `--setting-sources ""`, `--strict-mcp-config`, native permission mode `default`, the hub's managed PreToolUse hook and `--permission-prompt-tool mcp__agent-hub__hub_permission_request`; never `--bare`, `acceptEdits`, `dontAsk` or `bypassPermissions`, and `never-ask` is refused. Each task keeps its own persisted session; unscoped chat reuses one only while exactly one task is in progress, and a `hub_inbox` read refuses a batch attributed to another task. The approval path fails closed: `hub_permission_request` is accepted only from the current child's launch and generation, goes to the console's existing approval queue and `approvals.timeout_s`, and offers allow-once and deny only, with no allow-always or session grant. A timeout, a prompt failure, a disconnect, a hub that is down or refusing, or a stale child binding answers deny; a failed hook decides nothing, so the call still waits for a person. The approval title is the tool name and a JSON rendering of the arguments passed through `sanitize()` and the public-text screen (any PII, secret or private-path match withholds the whole value as `[withheld]`, before any cut), never task text, cut at 600 characters with a `[cut, N chars]` marker; the original arguments stay with the child and are not logged. The MCP wire cannot tell a native permission dispatch from a call the model makes itself; a person allow still refers only to its original arguments. Automatic Bash authorization uses the separate single-use host wrapper contract below, never a caller-supplied tool id as native provenance. A stop holds an uncertain delivery until the owned process group is confirmed gone. The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) is model-free on Claude Code 2.1.309, [repeated on 2.1.310](verification/2026-10-26-claude-implementer-2.1.310.md), [on 2.1.314](verification/2026-10-31-claude-implementer-2.1.314.md) and [on 2.1.316](verification/2026-10-31-claude-implementer-2.1.316.md); it is not sandbox proof, and a real account, model and person interaction remains a live leg.
866
+ The headless Claude implementer (#525-4) runs one owned `claude -p` child per turn in its declared worktree, with `--setting-sources ""`, `--strict-mcp-config`, native permission mode `default`, the hub's managed PreToolUse hook and `--permission-prompt-tool mcp__agent-hub__hub_permission_request`; never `--bare`, `acceptEdits`, `dontAsk` or `bypassPermissions`, and `never-ask` is refused. Each task keeps its own persisted session; unscoped chat reuses one only while exactly one task is in progress, and a `hub_inbox` read refuses a batch attributed to another task. The approval path fails closed: `hub_permission_request` is accepted only from the current child's launch and generation, goes to the console's existing approval queue and `approvals.timeout_s`, and offers allow-once and deny only, with no allow-always or session grant. A timeout, a prompt failure, a disconnect, a hub that is down or refusing, or a stale child binding answers deny; a failed hook decides nothing, so the call still waits for a person. The approval title is the tool name and a JSON rendering of the arguments passed through `sanitize()` and the public-text screen (any PII, secret or private-path match withholds the whole value as `[withheld]`, before any cut), never task text, cut at 600 characters with a `[cut, N chars]` marker; the original arguments stay with the child and are not logged. The MCP wire cannot tell a native permission dispatch from a call the model makes itself; a person allow still refers only to its original arguments. Automatic Bash authorization uses the separate single-use host wrapper contract below, never a caller-supplied tool id as native provenance. A stop holds an uncertain delivery until the owned process group is confirmed gone. The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) is model-free on Claude Code 2.1.309, [repeated on 2.1.310](verification/2026-10-26-claude-implementer-2.1.310.md), [on 2.1.314](verification/2026-10-31-claude-implementer-2.1.314.md), [on 2.1.316](verification/2026-10-31-claude-implementer-2.1.316.md) and [on 2.1.318](verification/2026-11-05-claude-implementer-2.1.318.md); it is not sandbox proof, and a real account, model and person interaction remains a live leg.
809
867
 
810
868
  The #845 host shell wrapper is the only automatic Bash path for a headless implementer. In `ask-when-needed`, a complete public input must pass the existing #816 read-only proof or the closed task-command proof, with an active owned task and registered worktree. Enabled action-policy classes still ask, and `ask`, PII handling and the `never-ask` refusal are unchanged. The native request and authenticated host response both name wrapper protocol 1, so an older plugin cannot drop the rewrite and execute an original command automatically.
811
869
 
@@ -881,6 +939,14 @@ role admission.
881
939
  separate `runner-attested` level described below; their fields never overwrite
882
940
  declared or host-measured evidence. Public experiment recording tools and the
883
941
  receipt CLI are the separate #602 slice.
942
+ - **Claim evidence links (#931 T1).** `hub_evidence_record` is published only to
943
+ research-profile peers. It admits only the current in-progress owner of a stored
944
+ research task, outside a private turn, on a non-PII task and a registered public
945
+ source whose raw hash matches. The checks repeat after screening and inside the
946
+ append transaction. Its relation, projection hash and locator are declared
947
+ provenance, never an entailment judgment or host verification; person verdicts
948
+ stay the only judgment record. Receipts carry the link id, revision and task id
949
+ only, and arguments and results are excluded from capture.
884
950
  - **Privacy.** A campaign whose parent task is PII, or that names a private source,
885
951
  is private, and so is an attempt with a private task, input or output path.
886
952
  Public views keep ids, counts, states and provenance and stub every question,
@@ -940,7 +1006,7 @@ A handoff crosses a project boundary, but not a security boundary: both projects
940
1006
  belong to the same machine user. The origin daemon reads the target's control
941
1007
  token file and connects to the target's daemon as the `console` role through
942
1008
  `ControlClient`, like every control message; the hello checks the target's project
943
- root and instance. It sends that connection only the three handoff messages, and
1009
+ root and instance. It sends that connection only the four handoff messages, and
944
1010
  it has no reach a same-user process lacks (see "What is not defended").
945
1011
 
946
1012
  - **Who may hand off.** `ahub task handoff` is Console in `classifyPeerCommand`;
@@ -950,21 +1016,28 @@ it has no reach a same-user process lacks (see "What is not defended").
950
1016
  the target's own `status.json` must advertise `task_handoff` and name no other project. A target
951
1017
  that is not running is refused with the action; nothing is queued.
952
1018
  - **No authority in the target.** The target answers `task_handoff`,
953
- `task_handoff_event` and `task_handoff_states` to a console connection only, and
1019
+ `task_handoff_event`, `task_handoff_by_key` and `task_handoff_states` to a console
1020
+ connection only, and
954
1021
  refuses a new handoff while it recovers or shuts down. The task is an ownerless proposal
955
1022
  its own hub makes and routes. The origin project and peer are a record in
956
1023
  `task_handoff_origin` and a line at the end of the detail, never an actor, a
957
1024
  roster peer or a role: the origin cannot accept, review, approve, assign or
958
1025
  cancel there.
959
1026
  - **What crosses.** To the target: a title (300 characters), a class, a detail
960
- (4,000), up to 16 repository-relative paths, a spec issue number, and the origin
961
- project id, peer id and task id, at most 8 KiB in all. The request is parsed
1027
+ (4,000), up to 16 repository-relative paths, a spec issue number, an optional
1028
+ `duplicate_ok` reason (300, kept in the target's detail), a handoff key (a UUID,
1029
+ sent only to a target that advertises `task_handoff_key`), and the origin project
1030
+ id, peer id and task id, at most 8 KiB in all. The request is parsed
962
1031
  closed on both sides: an unknown field, an accessor or a control character is
963
1032
  refused, never dropped, and the target sanitizes before it stores. No memory,
964
1033
  evidence, turn content or task history crosses. Back to the origin: an event
965
- name, a PR number and a full 40-hex head, never the target's title, notes or
966
- other text; the restart catch-up answers only for tasks whose recorded origin
967
- is the asking project, with the same fields. The origin peer gets that as a
1034
+ name, a sequence number, a PR number and a full 40-hex head, never the target's
1035
+ title, notes or other text; a duplicate-issue refusal names only the target's own
1036
+ open non-PII task ids. The catch-up (at start, every five minutes and when a
1037
+ target's descriptor changes) and the key lookup answer only for tasks whose
1038
+ recorded origin is the asking project, with the same fields. A key the target
1039
+ already holds answers with the task it made and never files a second one; the
1040
+ same key with a different request is refused. The origin peer gets that as a
968
1041
  hub-written `kind: task` message.
969
1042
  - **Privacy.** The origin refuses during a private turn and for a `[pii]` task
970
1043
  named as the source, before anything leaves. The text itself is checked by the
@@ -1078,9 +1151,20 @@ targets before any local effect; implicit broadcast never crosses the boundary.
1078
1151
 
1079
1152
  Signed completion proves remote durable queue admission only and is displayed as
1080
1153
  completed (admitted). Crypto inbox, application claim and Bus publication use
1081
- separate durable stages; a cross-DB failure is uncertainty, never an exactly-once
1082
- or native-execution claim. Late proof cannot auto-settle uncertainty. Person
1083
- accept/discard is local disposed metadata and never a signed application receipt.
1154
+ separate durable stages; a receiver cross-DB failure is uncertainty, never an
1155
+ exactly-once or native-execution claim. A late authenticated ordinary-chat receipt
1156
+ may settle its matching unresolved sender row under current room/member identity,
1157
+ without overriding a person disposition. Invocation uncertainty remains person-held
1158
+ and is never inferred from transport proof. Person accept/discard is local disposed
1159
+ metadata and never a signed application receipt.
1160
+
1161
+ Sender retries retain the application id, canonical content and original queue TTL.
1162
+ A distinct root delivery requires the same retained content hash and recipient,
1163
+ with no disposition or permanent hold; it inherits the original creation/expiry.
1164
+ Legacy unknown content hashes refuse cross-root reuse. Receipt ciphertext commits
1165
+ to the local durable outbox before chat transport ACK and replays until broker
1166
+ storage confirmation. Deferred admission may ACK its durable crypto inbox entry
1167
+ and commit its receipt when admission resumes. Neither path claims native execution.
1084
1168
 
1085
1169
  Schema3 reserves a separate durable lane for at most eight canonical receipt
1086
1170
  payloads per room, at most 1,024 bytes each and 512 KiB extra globally. A receipt
@@ -1100,12 +1184,15 @@ Capacity recovery requests fresh presence once per transition; runtime and
1100
1184
  other storage errors stay unACKed.
1101
1185
  Fresh, fully authenticated chat at the ordinary cap is likewise durably refused
1102
1186
  and transport-ACKed without admission, floor advance or signed receipt. Its
1103
- sender remains uncertain until the application ACK deadline; the refused chat
1104
- cannot hold matching receipt traffic behind it in the broker FIFO.
1187
+ sender retries at the ACK deadline under the same immutable application id, without
1188
+ renewing its queue TTL. Queue expiry or a permanent refusal ends automatic sending.
1189
+ The refused chat cannot hold matching receipt traffic behind it in the broker FIFO.
1105
1190
 
1106
1191
  Permanent signed sequence floors survive all application retention/eviction.
1107
1192
  Application-id and trace guards retain 30 days/10,000 rows per room, settled first;
1108
- outside that retention they make no historical claim. MAX_HOP 3 remains absolute.
1193
+ outside that retention they make no historical claim. Automatic resend can reach
1194
+ this settled-eviction ceiling and repeat admission under a fresh ciphertext sequence:
1195
+ exactly once within dedupe ceiling. MAX_HOP 3 remains absolute.
1109
1196
  All-unsettled capacity refuses with ids/counts, and person disposition releases
1110
1197
  settled capacity. This state protects against other users, not hostile code
1111
1198
  running as the same user with access to the private room keys. Native/account,
@@ -1202,3 +1289,34 @@ show it only to the console or configured conductor; other readers see
1202
1289
  source note identifies incoming-message text and never attributes it as the
1203
1290
  recipient peer's own speech. `status.json`, events, logs and memory never
1204
1291
  receive a derived copy. Removed-instance and Local bindings are not retained.
1292
+
1293
+ ## Questions a peer asks the person (#1053)
1294
+
1295
+ `hub_ask` lets any peer put a structured question (one to four questions, two to eight options each) to the person. A question is not a permission: it is its own record in `hub.db` (`questions`), has its own control messages, and never expires as a denial.
1296
+
1297
+ - **Who answers.** Only the console role. The control message `answer { questionSet, answers, surface? }` is refused, with a reply and no change to the row, for every `tools` or `peer` connection, the asking peer's own included. Nothing a peer writes counts as an answer: a `hub_send` body, a tool result, a file or an envelope that quotes one (the exact JSON `hub_ask` returns included) is text, and `test/questions-daemon.test.ts` pins that none of them changes a row. The answer envelope the peer receives is built by the hub (`kind: "answer"`, `from: "user"`), so its header cannot be forged: every body goes through `sanitize()`, and `kind`, never the sender, is what code keys on.
1298
+ - **One terminal transition.** `open` becomes `answered`, `superseded`, `cancelled` or `expired` by one compare-and-set (`UPDATE ... WHERE state = 'open'`). Of any number of concurrent answers, stops and expiries exactly one wins, and every other caller is told the row's real state. A second answer is refused and publishes nothing.
1299
+ - **Validation.** An answer is checked against the stored row before anything is written: every question answered once, only offered option ids, one option unless `multi`, free text only where `allowOther` and at most 500 characters. The refusal names the question by position and never echoes the offered or submitted text. Input is closed and bounded the same way (`parseAsk`).
1300
+ - **Answering from acon (#1053 T5).** The plugin accepts an `answer` action only when the console advertises `action.meta` and passes its own `meta` (`via: "console"`, a verified `identity` and `node`), which the page cannot set. Without it, or with page-supplied `identity`, `surface`, `questionSet`, `projectId` or `instanceId` fields, the action is refused before any request reaches the hub (`test/questions-plugin.test.ts`). The project and set come from the inbox item id the plugin built, never from the page. The plugin reaches the hub through a dedicated manager port (`projectConsole(...).answer`), not through the dashboard action whitelist, so the dashboard page of `ahub ui --all` and the manager's `ui_action` whitelist have no `answer` action, and a page can never answer for the person. The hub re-validates every answer against the stored row. The verified `identity` the plugin read from the console's own `meta` rides with the answer (`answer { ..., surface: "acon", person }`); the hub records it, one bounded line, on the answered row and on the `question` event, and reads `person` only for the `acon` surface, so a terminal or console answer cannot claim another person. It is never question or answer text. The acon types of the form and of the submitted answer (`ActionMeta`, `InboxVerb`, `VerbForm`, `InboxAnswers`) are type-only imports from the exact-pinned `@staix/agent-console/contract` entry, never hand-copied. `ahub answer` with any operand is refused in `liveTestRefusal` (a delegated `--live-test` run is promoted to console identity, so without it an agent shell could answer its own `hub_ask` as the person); only the bare listing is allowed there.
1301
+ - **Residual.** The same as `permit`: a vendor agent that can read the control token can act as the console (see "What is not defended"). This feature does not narrow that and does not widen it.
1302
+ - **Privacy.** Question and answer text live in `hub.db` and in the envelope addressed to the asking peer, nowhere else: never `hub.log` (an `answer` or question-notice envelope logs a fixed stub), never `events.jsonl` (the `question` event carries the set id, peer, event name, counts, latency, surface and a closed reason), never the console tail or the dashboard feed (`redactBusEvent` stubs the body), never memory capture (`QUESTION_TOOLS`) or the progress observer. A question asked in a PII turn, or about a PII task, is stored `private` and its late envelope is private too. The inbox title of a private question is a stub (`<peer> asks a question`); its text is only in the answer form, the person's own answer surface, and the status attention model, the stream line and the CLI audit carry ids only. `test/questions-console-daemon.test.ts` pins these with a PII task, the status reply, the dashboard snapshot, `hub.log`, `events.jsonl` and memory capture.
1303
+ - **Not defended.** A peer that is cancelled, superseded or restarted loses a pending question's wait, never its row, until the person or `questions.ttl_s` settles it. An answer a person gives is shown to the asking peer in full; do not ask the person for a secret.
1304
+
1305
+ ## Results a person's runs leave behind (#1054)
1306
+
1307
+ A result is the record that a run the person started has settled. Reading one and marking it read are separate acts, and only the person makes the second.
1308
+
1309
+ - **A receipt is the person's, on every surface.** `result_seen`, `results` (`read-all`, `unread-last`) and the dashboard's `result_seen`, `results_read_all` and `results_unread_last` write or delete a receipt, and each is refused unless the caller is the console role or a dashboard session the console opened: a peer's connection, a `hub_send` body, a tool result or an agent shell (`ahub results` is the person's command, and its receipt forms `--read`, `--read-all` and `--unread-last` are in `liveTestRefusal`, so a delegated `--live-test` run of them is refused too; the bare listing stays allowed there) is text and never reaches the store. A snapshot, a listing and the Results section's refresh never write one; a poll is not a person looking (`test/results-dashboard-1054.test.ts`).
1310
+ - **From acon.** The plugin accepts `result_seen`, `results_read_all` and `results_unread_last` only when the console advertises `action.meta` and passes its own `meta` (`via: "console"`, a verified `identity` and `node`), which the page cannot set. Without it, or with page-supplied `identity`, `surface`, `person` or a completion key, the action is refused before any request reaches the hub (`test/results-plugin-1054.test.ts`). The project and the result come from the inbox item id the plugin built or, from the panel, from the project it is showing; acon's `seenAt` and `explicit` are accepted and dropped, because the hub stamps its own time. The write goes through a dedicated port (`projectConsole(...).receipt`) as a console-role control message on the `acon` surface, never through the dashboard's forwarding list, and a plugin that is not given the person's meta cannot widen that.
1311
+ - **A live-test dashboard** gets no results and is refused every receipt, in two places that each hold alone: the page server's live-test allowlist and the daemon's own guard (`test/results-dashboard-1054.test.ts`).
1312
+ - **Residual.** The same as `permit`: a vendor agent that can read the control token can act as the console (see "What is not defended"), and so can write a receipt. This feature does not narrow that and does not widen it. A receipt is not an authority: it clears an unread need and nothing else.
1313
+ - **Privacy.** No prompt or reply text is stored: a completion row keeps envelope ids only. The one-line detail shown beside a result (the head of the starting prompt) is built when the person looks, from the envelope the bus still holds, after `sanitize` (so it cannot forge a hub header) and the hub's secret, PII and private-path screens; it is thrown away after it is shown and is in no `hub.db` row, `hub.log` line, `events.jsonl` event, status field or memory capture (canary tests: `test/results-dashboard-1054.test.ts`, `test/completions-daemon.test.ts`). A private run shows `a private request` without its body being read, a head the screens refuse shows a withheld stub, and a request the bus no longer holds shows nothing. The title is built from the peer and the outcome, never from request text, and the `completion_seen` event carries the id and the surface.
1314
+ - **Not defended.** The detail is the person's own request shown back to them on their own surfaces, so a request that holds something the screens do not recognise is shown to the person who typed it. Marking a result read does not mean the person read it: the hub takes the console's word, and acon's seeing rule (agent-console#27) is what makes that word mean the result was in view.
1315
+
1316
+ ## Anchored review comments (#1056)
1317
+
1318
+ Person comment writes (`review_comment_put`, `review_comment_delete`, `review_comment_send`) are console-role control messages, and their actor is always the person: the conductor, a planner and every peer are refused. From acon they require the console's verified `ActionMeta` (`via: "console"`, `identity`, `node`). Page-supplied `author`, `meta`, `personMeta`, `at`, `state` or `sentAt` fields are refused before any request, and delete and send also need acon's confirmation fingerprint. The standalone `ahub ui` page is read-only for comments.
1319
+
1320
+ - **Material.** Placement material comes only from guarded immutable Git snapshots at the task's recorded head (declared refs, plan paths, or same-head verified PR paths), bounded per operation to eight files and eight seconds; a sent package is at most 12,000 bytes. `review_blob` refuses a PII task, and periodic snapshots make no comment-material Git or GitHub read. PII tasks and private paths keep comments on the board and count-only in every public view.
1321
+ - **Delivery.** A sent package carries the host-only `refs.reviewPackage` marker: the intended owner gets the body, while logs, stream projections, incoming labels, memory capture and observers get an ids-only stub; review markers a peer supplies are discarded. Open sent comments, anchored findings and unsettled review deliveries keep the original recipient's responses out of shared memory and observers until resolution, dismissal, task close or explicit disposition (#1192, #1205). The marker grants no task, console or PII authority.
1322
+ - **Retry and recovery.** A send is never retried automatically, and a queued receipt proves publication, not that the owner read it. A restart, upgrade or recovery onto a daemon lacking `review_comments` is refused while anchored comments are stored.