@staix/agent-hub 0.29.0 → 0.31.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 (154) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +15 -7
  3. package/docs/agent-notes/README.md +13 -0
  4. package/docs/agent-notes/adapters.md +7 -3
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +10 -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 +10 -4
  10. package/docs/agent-notes/tests.md +8 -4
  11. package/docs/design/adapters.md +108 -0
  12. package/docs/design/agent-hub-architecture.md +58 -0
  13. package/docs/design/bus.md +147 -1
  14. package/docs/design/task-board.md +100 -0
  15. package/docs/dogfood.md +29 -8
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +410 -86
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +25 -5
  21. package/docs/sdlc.md +1 -1
  22. package/docs/security.md +182 -62
  23. package/docs/smoke.md +13 -2
  24. package/docs/specs/2026-09-19-agent-hub-design.md +84 -14
  25. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  26. package/docs/specs/2026-10-12-office-view-design.md +86 -3
  27. package/docs/verification/2026-11-03-0.30.0.md +54 -0
  28. package/docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md +42 -0
  29. package/docs/verification/2026-11-03-agy-1.4.12.md +90 -0
  30. package/docs/verification/2026-11-03-agy-tui-close.md +49 -0
  31. package/docs/verification/2026-11-03-grok-1.0.62-t0.md +150 -0
  32. package/docs/verification/2026-11-03-pi-150-compat.md +40 -0
  33. package/docs/verification/2026-11-03-pi-title-api.md +26 -0
  34. package/docs/verification/2026-11-03-qwen-0265-acp.md +55 -0
  35. package/docs/verification/2026-11-03-qwen-0265-dual-output.md +40 -0
  36. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  37. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  38. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  39. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +257 -0
  40. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  41. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  42. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  43. package/docs/verification/dogfood/2026-11-03.md +3095 -0
  44. package/docs/verification/pi-150-model-free.ts +57 -0
  45. package/docs/verification/pi-150-native.mjs +21 -0
  46. package/docs/verification/pi-150-title-api.mjs +31 -0
  47. package/docs/verified.json +14 -14
  48. package/package.json +3 -4
  49. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  50. package/plugins/agent-hub/server.js +4565 -3617
  51. package/src/adapters/acp.ts +5 -4
  52. package/src/adapters/channel-peer.ts +72 -3
  53. package/src/adapters/claude-channel-evidence.ts +52 -0
  54. package/src/adapters/claude-channel.ts +61 -18
  55. package/src/adapters/claude-implementer.ts +29 -7
  56. package/src/adapters/claude-print-child.ts +12 -2
  57. package/src/adapters/codex-appserver.ts +13 -12
  58. package/src/adapters/compat.ts +13 -13
  59. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  60. package/src/adapters/definitions/qwen.ts +1 -0
  61. package/src/adapters/local-worker.ts +82 -23
  62. package/src/adapters/pi.ts +140 -16
  63. package/src/adapters/registry-types.ts +16 -0
  64. package/src/adapters/registry.ts +4 -0
  65. package/src/adapters/reviewer-print.ts +50 -11
  66. package/src/broker/server.ts +44 -13
  67. package/src/broker/store.ts +38 -8
  68. package/src/cli/answer-args.ts +50 -0
  69. package/src/cli/attention.ts +17 -2
  70. package/src/cli/autonomy.ts +66 -1
  71. package/src/cli/backlog-return-args.ts +37 -0
  72. package/src/cli/console-question.ts +110 -0
  73. package/src/cli/console-state.ts +82 -17
  74. package/src/cli/console.ts +54 -1
  75. package/src/cli/handoff-args.ts +15 -5
  76. package/src/cli/handoff-output.ts +15 -2
  77. package/src/cli/help.ts +10 -5
  78. package/src/cli/identity.ts +21 -4
  79. package/src/cli/main.ts +167 -16
  80. package/src/cli/memory.ts +107 -3
  81. package/src/cli/output.ts +29 -6
  82. package/src/cli/pet.ts +4 -4
  83. package/src/cli/results-args.ts +30 -0
  84. package/src/cli/runtime-version-warning.ts +50 -0
  85. package/src/cli/status-lines.ts +41 -1
  86. package/src/cli/tail-render.ts +1 -0
  87. package/src/cli/upgrade-interactive.ts +14 -2
  88. package/src/cli/worktree-tidy.ts +158 -0
  89. package/src/cli/worktrees-output.ts +43 -0
  90. package/src/hub/action-class.ts +9 -1
  91. package/src/hub/board.ts +396 -20
  92. package/src/hub/bus.ts +497 -57
  93. package/src/hub/completion-detail.ts +50 -0
  94. package/src/hub/completions-store.ts +168 -0
  95. package/src/hub/completions.ts +120 -0
  96. package/src/hub/conductor.ts +4 -3
  97. package/src/hub/config-apply.ts +8 -5
  98. package/src/hub/config-reload.ts +10 -1
  99. package/src/hub/control-client.ts +40 -4
  100. package/src/hub/daemon.ts +1795 -142
  101. package/src/hub/delivery-failure.ts +14 -0
  102. package/src/hub/delivery-hints.ts +14 -4
  103. package/src/hub/delivery-journal.ts +751 -42
  104. package/src/hub/delivery-superseded.ts +4 -1
  105. package/src/hub/dogfood-read.ts +62 -9
  106. package/src/hub/envelope.ts +3 -3
  107. package/src/hub/events.ts +6 -0
  108. package/src/hub/handoff.ts +145 -6
  109. package/src/hub/hub-tools.ts +66 -11
  110. package/src/hub/manager.ts +67 -3
  111. package/src/hub/memory-policy.ts +44 -1
  112. package/src/hub/memory-sync-policy.ts +109 -0
  113. package/src/hub/memory-sync-state.ts +145 -0
  114. package/src/hub/no-writes.ts +1 -1
  115. package/src/hub/operational-settings.ts +11 -2
  116. package/src/hub/operational-view.ts +12 -1
  117. package/src/hub/peer-doing.ts +33 -3
  118. package/src/hub/peers.ts +32 -2
  119. package/src/hub/planning.ts +6 -1
  120. package/src/hub/question-desk.ts +201 -0
  121. package/src/hub/question-detach.ts +6 -0
  122. package/src/hub/questions.ts +260 -0
  123. package/src/hub/research-evidence.ts +106 -0
  124. package/src/hub/restart.ts +14 -0
  125. package/src/hub/review-anchor.ts +181 -0
  126. package/src/hub/review-brief.ts +13 -0
  127. package/src/hub/review-comments.ts +115 -0
  128. package/src/hub/review-findings.ts +7 -2
  129. package/src/hub/review-material.ts +76 -0
  130. package/src/hub/routing.ts +3 -1
  131. package/src/hub/settings.ts +3 -0
  132. package/src/hub/task-sweep.ts +2 -0
  133. package/src/hub/tasks.ts +607 -71
  134. package/src/hub/ui.ts +2 -0
  135. package/src/hub/worktree-prs.ts +100 -0
  136. package/src/hub/worktree-shelf.ts +246 -0
  137. package/src/memory/capture.ts +5 -2
  138. package/src/pet/config.ts +25 -13
  139. package/src/pi/extension.ts +43 -3
  140. package/src/rooms/link.ts +209 -21
  141. package/src/rooms/remote-peer.ts +186 -19
  142. package/src/rooms/service.ts +338 -22
  143. package/src/rooms/store.ts +437 -65
  144. package/src/rooms/wire.ts +24 -6
  145. package/src/ui/index.html +271 -17
  146. package/src/ui/needs.ts +10 -4
  147. package/src/ui/office-model.ts +64 -8
  148. package/src/ui/office-space.ts +1 -0
  149. package/src/ui/plugin-questions.ts +56 -0
  150. package/src/ui/plugin-results.ts +81 -0
  151. package/src/ui/plugin.ts +219 -28
  152. package/src/pet/window.html +0 -114
  153. package/src/pet/window.jxa.js +0 -122
  154. package/src/pet/window.ts +0 -154
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,20 @@ 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
+
168
182
  ## What is not defended
169
183
 
170
184
  - **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 +376,7 @@ as that peer in tools mode. Messages and task changes retain that actor;
362
376
  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
377
  By default, console operations are refused before connecting, including permission answers
364
378
  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`
379
+ operations, the interactive terminals (`watch`, a launcher's person-only flags), and `ask`
366
380
  (#933 keeps the person's class to trust, money, attribution and interactive terminals).
367
381
  For live verification explicitly authorized by the user, an agent may invoke
368
382
  `ahub --live-test [--project <path|id>] <command> [args...]`. The flag delegates
@@ -389,8 +403,8 @@ messages, proposals and reviews. Peer starts, pause and resume may delegate
389
403
  operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
390
404
  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
405
  `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
406
+ session's conversation (or whose scope cannot be read), `ahub answer` with an
407
+ operand and the `ahub results` receipt forms are refused; presentation flags such as `--color` or `--verbose` before
394
408
  the recovery action do not hide it from this check. No argument text is copied into this audit.
395
409
  Delegated dashboard restrictions are bound to its server-issued ticket/session:
396
410
  operational settings/start/stop controls remain, but message, proposal and
@@ -465,9 +479,10 @@ start (Jcode). Memory changes and the other `goal` changes stay the person's
465
479
  while amem records their actor as the user. `task unassign` is a conductor form;
466
480
  it keeps the conductor as the board actor. `task cancel` remains separately
467
481
  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`,
482
+ (#933), and for the conductor only on an unaccepted task another project handed
483
+ off (#1175), not as a general conductor operation; `task role`, `task label` and
484
+ `ahub deliverables` retain their own-task checks. `models start|stop` remains
485
+ Console. `restart --yes`,
471
486
  `upgrade --yes --to <exact-version>` and `recovery resume|abort` have the
472
487
  separate continuity proof described below. Each conductor operation is admitted
473
488
  after a recheck of role and project membership; effectful operations and history
@@ -557,7 +572,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
557
572
  - 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
573
  - 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
574
  - 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.
575
+ - 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
576
  - 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
577
  - The listener stops with the daemon. Expired sessions must be reopened with `ahub ui`; the page does not silently obtain new credentials.
563
578
 
@@ -598,30 +613,38 @@ its existing basename/worktree aliases and provider accounts can remain shared.
598
613
 
599
614
  `@staix/agent-hub/ui-plugin` runs inside the agent-console (`acon`) process as the
600
615
  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.
616
+ the multi-project manager, which connects as the console with each project's
617
+ control token. Beyond what `ahub ui --all` has, it uses dedicated manager ports the
618
+ dashboard page cannot reach: `answer` (#1053), `receipt` (#1054) and the review port
619
+ (`review_comment_put|delete|send`, `review_comments`, `review_blob`, #1056). Every
620
+ write through them requires acon's verified `ActionMeta`, as described in those
621
+ sections. Every other action must be on the manager's closed list and every view one
622
+ of the seven view names. There is no generic control proxy, task-detail read, shell
623
+ or caller-chosen command, and the only file read is the bounded review material of a
624
+ public task (`review_blob`).
606
625
 
607
626
  - **Registration.** `ahub setup` writes `console/plugins/ahub.json` under the hub
608
627
  home (directory 0700, file 0600, atomic replace) with the module path and its
609
628
  sha256; acon refuses to load a module whose hash no longer matches.
610
629
  - **Confirmation.** Outward or destructive actions (`stop`, `permit`, `send`,
611
630
  `setting`, `setting_undo`, `setting_reload`, `config_edit_apply`, `start_peer`,
612
- `backlog_split` and the inbox's Allow once and Deny) first answer `needsConfirm`
631
+ `backlog_split`, `worktree_override_set`, `worktrees_refresh_prs`,
632
+ `review_comment_send`, `review_comment_delete` and the inbox's Allow once and Deny) first answer `needsConfirm`
613
633
  with a preview and a fingerprint, a sha256 of the request's own fields; the call
614
634
  runs only when it comes back with `confirmed` and that fingerprint. The
615
635
  fingerprint binds a confirmation to one exact request, so a changed request needs
616
636
  a new confirmation; it is not a secret, and the confirm step is acon's host UI
617
637
  contract, not authentication. A process that can load the module already has the
618
638
  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
639
+ - **Inbox.** Items are the pending approvals, the in-review tasks whose reviewer
640
+ is the person, open questions (#1053) and, for a console that gives receipts
641
+ person evidence, unread results (#1054), read from the same dashboard snapshot: a local-worker or
621
642
  Pi approval shows only the fixed private stub and offers Deny, and Allow once for
622
643
  it is refused with "answer this approval with ahub permit". Allow once and Deny
623
644
  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.
645
+ looked up again at the time of the call; nothing else is reachable from an approval
646
+ item. A question's Answer and a result's mark-read go only through the person-evidence
647
+ paths in "Questions a peer asks the person" and "Results a person's runs leave behind".
625
648
  Task titles are the public view's, so a PII task shows the `[pii]` stub.
626
649
  - **Space.** The Space scene is built only from the office model the dashboard
627
650
  already allowlists (#264): peer ids, conditions and counts, task ids, classes,
@@ -644,6 +667,15 @@ file edits, and a configuration reload refuses roster changes. External ids outs
644
667
  admission rules. A roster key left by an undeclared instance loads as inert and grants
645
668
  nothing; the only change it accepts is its removal from `config.local.json`.
646
669
 
670
+ Handing a peer to a standby session (#1174) is the person's: `peer_takeover` is
671
+ console-role only, and `ahub peer takeover` is refused to agent shells and
672
+ `--live-test`. A standby connection runs no peer operation until it owns the peer,
673
+ and the selected standby is rechecked for membership, launch and isolation,
674
+ lifecycle and claim generation after its preface. Standby ids and reported PIDs are
675
+ display metadata, never native-session or permission evidence. Takeover detaches
676
+ the old hub connection, not its native process; that session's later hub calls are
677
+ refused, and takeover never marks a delivery completed.
678
+
647
679
  Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
648
680
  merges/removes only the hub MCP entry; the daemon never edits that file and
649
681
  refuses headless starts while a stale entry exists. An attached Kimi TUI blocks
@@ -673,7 +705,7 @@ withholds the scoped mode on these writes so an older hook grants nothing too.
673
705
 
674
706
  Agent CLI versions (#691). One policy gates the Codex and Kimi fresh reviewers, the headless Claude implementer, Jcode, agy, Qwen (ACP and the dual-output TUI) and Pi (#843): a receipt-verified version passes, Jcode and agy also start an untested version inside their compatible line with a note, and any other version is refused unless the person trusts that exact version on this machine with `ahub agents trust <id> <version>`. The record is `agent-compat.json` in the hub home (`~/.agenthub`), written atomically with mode 600 and at most 64 entries; an unreadable, linked, oversized or invalid record trusts nothing. Agent shells and `--live-test` may read `ahub agents` but cannot trust or revoke. Trust lifts only the version pin: the fresh reviewers' runtime proofs still run before every child, and an untested version's native contract stays unverified.
675
707
 
676
- Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1 and 0.26.4)
708
+ Qwen Code's optional production ACP peer requires the qwen-code native identity, a compat-admitted version (receipt-verified 0.25.0, 0.26.0, 0.26.1, 0.26.4 and 0.26.5)
677
709
  and advertised mode ids before setting a policy or starting work. Its own tools
678
710
  have no hub sandbox; auto-edit approves file edits, yolo every tool. Configured
679
711
  never-ask still requires the person's confirmation. qwen_cmd is machine-local and
@@ -682,7 +714,7 @@ this production table. Startup diagnostics withhold native host/payload details.
682
714
 
683
715
  Jcode is optional, source/version-fenced and runs native tools without a hub sandbox or hub permission-mode mapping. .jcode joins the scoped-grant exclusion, including lexical/resolved aliases. Only a person writes its unique project MCP entry; foreign/override/symlink entries refuse. The hub owns a private daemon/socket and ACP group, never the global daemon, and copies no credentials. It accepts a plain jcode release from 0.98.0 up to, not including, 1.0.0 (#529; only 0.98.1, 0.99.0 and 0.99.1 are verified, and another version in that line starts with a note), or an exact version the person trusts (#691), and runs every owned jcode process (probe, serve, ACP and TUI) with `JCODE_NO_TELEMETRY=1`, because jcode's telemetry can report the provider account's email. Native diagnostics withhold payloads/hosts. Hub tools are bound to an owned launch (task 368): the shared project entry names no peer, each owned Jcode process carries its own peer id and launch id, the daemon admits a hub-tools connection for a Jcode peer only with the id of that peer's current owned launch, and a tools-mode server started without a peer id refuses every tool. Extra Jcode instances (#685, at most 8) are declared only through `instances` in an untracked `.agenthub/config.local.json`, start headless, and each needs its own `jcode_home`, which may not be the base home or another instance's, nor nested in or around one, however it is spelled. Unproved conversation loads refuse; no fresh replacement is called a restore. A finished task's advisory about the project root checkout names the branch only after the hub's whole public-text screen (`Tasks.publicMetadataText`: a PII pattern, a secret shape and a project-private path) passes it and it is a plain ref name; otherwise a generic sentence carries no part of it (#572). Native TUI launch exposes its private socket/session tuple only to the authenticated console, bound to the same live owned socket and session after read-only native persistence proof. Tools/status do not receive it. The TUI has no single-writer guard: person input may interleave with ACP. Native policy remains unmanaged and closing the frontend does not stop ACP. An `important` message reaches a busy Jcode as a soft interrupt written to the hub's own private server socket for the owned session (#572); the hub never cancels a turn to deliver, and a steer that was sent but not acknowledged, or whose turn ended first, is held as `needs_review`, never replayed. Jcode has no switch to choose its MCP servers, so the owned serve and ACP processes load every server named in `<JCODE_HOME>/mcp.json` and the project's `.jcode/mcp.json`, `.mcp.json` and `.claude/mcp.json`, besides the hub's own; `ahub doctor` lists their names only. Point `jcode_home` at a separate home, or remove a server from those files, to keep it out.
684
716
 
685
- Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2 and 1.4.7 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
717
+ Antigravity (`agy`, #485) is optional, cloud and off roster by default. The launcher accepts the Antigravity CLI 1.4.x line; only 1.4.2, 1.4.7 and 1.4.12 have a native receipt ([1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)), so another 1.4.x patch launches with a warning that it is unverified, and every other version is refused before a hub connection or plugin write (#589) unless the person trusts that exact version (#691). It runs native tools under its own unmanaged permissions, without a hub sandbox or hub permission-mode mapping; headless, resume, unattended and native permission flags are refused. Its roles are capped at implementer and verifier, so it never plans, reviews or receives PII work. Only the person's `ahub agy` launcher writes its three plugin files in `.agents/plugins/agent-hub/` (`plugin.json`, `mcp_config.json`, `hooks.json`): linked or foreign entries refuse before any write, cleanup removes only that launch's matching files, and it never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Conductor starts are refused, and a declared agy extra instance is refused at config load (#625). Hook authority (`native_hook_launch`) comes only from that launcher, belongs to the console socket that began it and ends when that socket closes; hook input over 1 MiB is refused, and observations are bound to the daemon, launch, native session and project.
686
718
 
687
719
  Configured ACP adapters (#451 A3) are declared only by the person, in a Git-confirmed untracked `.agenthub/config.local.json`. The whole declaration (command, argv, identity, roles and mode maps) is machine-local authority: a shared or tracked declaration is ignored with a notice, settings never edit it, and changes need a hub restart. The hub runs only the declared argv, never a module or callback. Argv that selects a leader, attach, connect or remote mode is refused, and a Grok declaration needs `--no-leader`. Each start requires the pinned native identity, an exact `session/load` echo on resume, and positive ownership of the spawned process and its original process group; unknown ownership refuses, and stop signals only that verified group. An agent that hands its work to a process outside that group is outside this boundary. Without a mode map native policy is unmanaged, and there is no hub sandbox either way. Configured kinds are cloud peers (never PII), headless only and off roster by default, and the hub makes no quota, context or controlled-recovery claims for them. A read-only instance needs the native read-only mode acknowledged before a prompt and is limited to read and review hub tools, both in the published MCP list and at the host; its native non-read tool requests are rejected, and no setting or runtime change can raise it. These agents' stderr and error text are withheld from logs, and previews redact the executable, argv and session id.
688
720
 
@@ -759,45 +791,47 @@ historical claude-mem database used only by the explicit read-only import.
759
791
  Sync starts only after a person configures a remote. Public GitHub remotes are
760
792
  refused; unknown visibility proceeds with a warning for the configured remote.
761
793
  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.
794
+ sync. Sync and direct long-term writes apply the configured agent-mem sync policy
795
+ narrowed by the hub's deny paths, PII patterns and project-private paths; a promotion
796
+ that fails it leaves the note short-term (#1173). Status exposes safe sync state and
797
+ counts, including verified withheld/exported counts from a successful SDK placement
798
+ result. Failure logs retain only the closed cause and known declined count.
799
+ `ahub memory declined` is person-only (refused to agent shells and `--live-test`)
800
+ and uses bounded physical-row inspection to show IDs and rule categories, never
801
+ note text or literal patterns; incomplete inspection is not a zero count.
802
+ Default-host v3 sync remains unsupported because the hub supplies no fresh
803
+ placement attestation. No activation proof is fabricated; the supported activation
804
+ and attestation path remains agent-mem#281. Suggested graph edges are off by default
805
+ and are not evidence until a person confirms them. Operational details and limits: [hub memory](memory.md).
806
+
807
+ ## Floating pet retirement (#1012)
808
+
809
+ The standalone JXA host, WKWebView, window bridge and its packaged assets are
810
+ removed. `ahub pet --window [--close]` only prints the `acon ui` Mini pointer and
811
+ exits successfully; it has no launcher, process-control or network effect and
812
+ is available to agent shells and `--live-test`. Legacy `pet.window` is ignored
813
+ as compatibility input and grants no configuration or conductor authority.
814
+
815
+ Terminal pet reads retain their existing behavior. The retired `pet --palette`
816
+ command validates its argument and prints the same Mini pointer without writing
817
+ `.agenthub/config.local.json`. Existing `pet.palette` values remain validated
818
+ compatibility input and have no rendering effect.
819
+
820
+ The **Console message** view already exists in the agent-hub acon plugin
821
+ (#935). Its existing `send` action retains message audience, PII and actor
822
+ checks; the retired window adds no alternative composer path.
789
823
 
790
824
  ## Planner, cancellation and delivery authority
791
825
 
792
826
  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
827
 
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.
828
+ `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
829
 
796
830
  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
831
 
798
832
  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
833
 
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.
834
+ `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
835
 
802
836
  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
837
 
@@ -805,7 +839,7 @@ A terminal Claude implementer or verifier instance (#525-5) owns write-class tas
805
839
 
806
840
  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
841
 
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.
842
+ 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
843
 
810
844
  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
845
 
@@ -835,6 +869,8 @@ What the sandbox can and cannot write is stated here so nobody relies on more. A
835
869
 
836
870
  On decline or cancel the hub removes the worktree and deletes the branch while it is still the empty base. A worktree with uncommitted changes, or that cannot be checked, is never force-removed: it is kept with its branch and reported as a console notice. A branch the hub committed to is kept, because its commit may be pushed and under review. A peer that makes its own worktree (Claude, Codex, Kimi) is untouched, and a PII task never gets a hub worktree. Residual: a same-user process outside the sandbox can still plant a ref under `.git/refs/heads`; the sandbox no longer lets a sandboxed command do it (#1123), and the hub refuses to commit or push on top of a moved ref either way.
837
871
 
872
+ Worktree shelf (#1057). Apart from the empty hub-made task worktree above, the daemon never removes a worktree or deletes a branch; the shelf only reads and records the person's Park or Pin. `ahub worktrees` and the `ahub worktrees tidy` preview are reads an agent may run outside a private turn; Park, Pin, "Refresh PR states" and `tidy --confirm` are the person's (the console, or the dashboard session the manager forwards for it; a live-test dashboard is refused, and `tidy --confirm` is person-only for an agent identity and refused under `--live-test`). The only removal path is `ahub worktrees tidy --confirm <fingerprint>` in the person's own CLI process (`src/cli/worktree-tidy.ts`, which the daemon does not import). It re-reads every row and refuses the whole run when the fingerprint changed, operates only on a path git itself lists, and runs exactly `git worktree remove <path>` and `git branch -d <branch>`, never `--force` or `-D`. A row is refused when it is the main checkout, an instance worktree, outside the project, dirty, locked, without a branch, unpushed, without an upstream, not merged into the default branch, bound to a task that is not approved or cancelled, or on a branch that changed since the preview, and whenever git cannot tell; a parked row passes the same checks. No read makes a GitHub call; only the explicit refresh does, for at most 8 PRs. A worktree bound to a PII task keeps that binding on the host, so it stays live and tidy refuses it, but its row carries no task id, PR or task state.
873
+
838
874
  Refs, HEAD and worktree admin writes (#1123). A hub-sandboxed peer (Pi, the local worker) cannot move what a branch, `HEAD` or a linked worktree points at. The profile (`gitRefRules` in `src/local/deny.ts`, built into `hubWriteRegexes`) refuses every write to `refs/**` (branches, tags, `refs/stash`, remote refs), `packed-refs` (and its lock and new files), `HEAD` (and `HEAD.lock`) and `worktrees/**` (a linked worktree's admin dir: its `HEAD`, `index`, `logs`, anything new, and a new admin dir). The rules sit directly under the git dir, so a project below a directory that happens to be named `refs`, `worktrees`, `HEAD` or `packed-refs` keeps working. They cover a plain `.git`, and each external git dir (the common dir and a linked worktree's own, whether the worktree is inside the project or beside it, and a bare repository's common dir that is not named `.git`). Every name is folded for the code points HFS+ ignores like the other git names, and each regex literal stays under seatbelt's 1024-byte limit at any project depth (`test/sandbox-git-refs.test.ts`, run with the real sandbox-exec in the main checkout, in a hub-made worktree, in a worktree outside its project and in a bare repository's worktree). The hub's own git is hardened and runs outside the sandbox, so #1115's hub commit and push are unaffected.
839
875
 
840
876
  What a sandboxed command can still write under `.git`, each with its reason, probed with the real profile: the main checkout's `index` and `index.lock` (`git add` and `git restore` stage there), `logs/HEAD`, `ORIG_HEAD`, `FETCH_HEAD`, `MERGE_HEAD`, `COMMIT_EDITMSG`, `info/exclude` and `description` (none can run a program or move a branch), and NEW loose objects under `objects/` (a staged blob, #864; existing objects, `objects/info` and `objects/pack` stay refused). Still refused as before: a gitfile, `config`, `config.worktree`, `hooks`, `commondir` (#270). Inside a linked worktree the sandbox cannot write the worktree's own admin dir, so `git add` there fails on its `index.lock`; the working tree itself is writable and `git status`, `diff`, `log`, `show`, `blame`, `ls-files`, `rev-parse`, `for-each-ref` and `cat-file` all still work.
@@ -879,6 +915,14 @@ role admission.
879
915
  separate `runner-attested` level described below; their fields never overwrite
880
916
  declared or host-measured evidence. Public experiment recording tools and the
881
917
  receipt CLI are the separate #602 slice.
918
+ - **Claim evidence links (#931 T1).** `hub_evidence_record` is published only to
919
+ research-profile peers. It admits only the current in-progress owner of a stored
920
+ research task, outside a private turn, on a non-PII task and a registered public
921
+ source whose raw hash matches. The checks repeat after screening and inside the
922
+ append transaction. Its relation, projection hash and locator are declared
923
+ provenance, never an entailment judgment or host verification; person verdicts
924
+ stay the only judgment record. Receipts carry the link id, revision and task id
925
+ only, and arguments and results are excluded from capture.
882
926
  - **Privacy.** A campaign whose parent task is PII, or that names a private source,
883
927
  is private, and so is an attempt with a private task, input or output path.
884
928
  Public views keep ids, counts, states and provenance and stub every question,
@@ -938,7 +982,7 @@ A handoff crosses a project boundary, but not a security boundary: both projects
938
982
  belong to the same machine user. The origin daemon reads the target's control
939
983
  token file and connects to the target's daemon as the `console` role through
940
984
  `ControlClient`, like every control message; the hello checks the target's project
941
- root and instance. It sends that connection only the three handoff messages, and
985
+ root and instance. It sends that connection only the four handoff messages, and
942
986
  it has no reach a same-user process lacks (see "What is not defended").
943
987
 
944
988
  - **Who may hand off.** `ahub task handoff` is Console in `classifyPeerCommand`;
@@ -948,21 +992,28 @@ it has no reach a same-user process lacks (see "What is not defended").
948
992
  the target's own `status.json` must advertise `task_handoff` and name no other project. A target
949
993
  that is not running is refused with the action; nothing is queued.
950
994
  - **No authority in the target.** The target answers `task_handoff`,
951
- `task_handoff_event` and `task_handoff_states` to a console connection only, and
995
+ `task_handoff_event`, `task_handoff_by_key` and `task_handoff_states` to a console
996
+ connection only, and
952
997
  refuses a new handoff while it recovers or shuts down. The task is an ownerless proposal
953
998
  its own hub makes and routes. The origin project and peer are a record in
954
999
  `task_handoff_origin` and a line at the end of the detail, never an actor, a
955
1000
  roster peer or a role: the origin cannot accept, review, approve, assign or
956
1001
  cancel there.
957
1002
  - **What crosses.** To the target: a title (300 characters), a class, a detail
958
- (4,000), up to 16 repository-relative paths, a spec issue number, and the origin
959
- project id, peer id and task id, at most 8 KiB in all. The request is parsed
1003
+ (4,000), up to 16 repository-relative paths, a spec issue number, an optional
1004
+ `duplicate_ok` reason (300, kept in the target's detail), a handoff key (a UUID,
1005
+ sent only to a target that advertises `task_handoff_key`), and the origin project
1006
+ id, peer id and task id, at most 8 KiB in all. The request is parsed
960
1007
  closed on both sides: an unknown field, an accessor or a control character is
961
1008
  refused, never dropped, and the target sanitizes before it stores. No memory,
962
1009
  evidence, turn content or task history crosses. Back to the origin: an event
963
- name, a PR number and a full 40-hex head, never the target's title, notes or
964
- other text; the restart catch-up answers only for tasks whose recorded origin
965
- is the asking project, with the same fields. The origin peer gets that as a
1010
+ name, a sequence number, a PR number and a full 40-hex head, never the target's
1011
+ title, notes or other text; a duplicate-issue refusal names only the target's own
1012
+ open non-PII task ids. The catch-up (at start, every five minutes and when a
1013
+ target's descriptor changes) and the key lookup answer only for tasks whose
1014
+ recorded origin is the asking project, with the same fields. A key the target
1015
+ already holds answers with the task it made and never files a second one; the
1016
+ same key with a different request is refused. The origin peer gets that as a
966
1017
  hub-written `kind: task` message.
967
1018
  - **Privacy.** The origin refuses during a private turn and for a `[pii]` task
968
1019
  named as the source, before anything leaves. The text itself is checked by the
@@ -1076,9 +1127,20 @@ targets before any local effect; implicit broadcast never crosses the boundary.
1076
1127
 
1077
1128
  Signed completion proves remote durable queue admission only and is displayed as
1078
1129
  completed (admitted). Crypto inbox, application claim and Bus publication use
1079
- separate durable stages; a cross-DB failure is uncertainty, never an exactly-once
1080
- or native-execution claim. Late proof cannot auto-settle uncertainty. Person
1081
- accept/discard is local disposed metadata and never a signed application receipt.
1130
+ separate durable stages; a receiver cross-DB failure is uncertainty, never an
1131
+ exactly-once or native-execution claim. A late authenticated ordinary-chat receipt
1132
+ may settle its matching unresolved sender row under current room/member identity,
1133
+ without overriding a person disposition. Invocation uncertainty remains person-held
1134
+ and is never inferred from transport proof. Person accept/discard is local disposed
1135
+ metadata and never a signed application receipt.
1136
+
1137
+ Sender retries retain the application id, canonical content and original queue TTL.
1138
+ A distinct root delivery requires the same retained content hash and recipient,
1139
+ with no disposition or permanent hold; it inherits the original creation/expiry.
1140
+ Legacy unknown content hashes refuse cross-root reuse. Receipt ciphertext commits
1141
+ to the local durable outbox before chat transport ACK and replays until broker
1142
+ storage confirmation. Deferred admission may ACK its durable crypto inbox entry
1143
+ and commit its receipt when admission resumes. Neither path claims native execution.
1082
1144
 
1083
1145
  Schema3 reserves a separate durable lane for at most eight canonical receipt
1084
1146
  payloads per room, at most 1,024 bytes each and 512 KiB extra globally. A receipt
@@ -1098,12 +1160,15 @@ Capacity recovery requests fresh presence once per transition; runtime and
1098
1160
  other storage errors stay unACKed.
1099
1161
  Fresh, fully authenticated chat at the ordinary cap is likewise durably refused
1100
1162
  and transport-ACKed without admission, floor advance or signed receipt. Its
1101
- sender remains uncertain until the application ACK deadline; the refused chat
1102
- cannot hold matching receipt traffic behind it in the broker FIFO.
1163
+ sender retries at the ACK deadline under the same immutable application id, without
1164
+ renewing its queue TTL. Queue expiry or a permanent refusal ends automatic sending.
1165
+ The refused chat cannot hold matching receipt traffic behind it in the broker FIFO.
1103
1166
 
1104
1167
  Permanent signed sequence floors survive all application retention/eviction.
1105
1168
  Application-id and trace guards retain 30 days/10,000 rows per room, settled first;
1106
- outside that retention they make no historical claim. MAX_HOP 3 remains absolute.
1169
+ outside that retention they make no historical claim. Automatic resend can reach
1170
+ this settled-eviction ceiling and repeat admission under a fresh ciphertext sequence:
1171
+ exactly once within dedupe ceiling. MAX_HOP 3 remains absolute.
1107
1172
  All-unsettled capacity refuses with ids/counts, and person disposition releases
1108
1173
  settled capacity. This state protects against other users, not hostile code
1109
1174
  running as the same user with access to the private room keys. Native/account,
@@ -1176,3 +1241,58 @@ path and no polling when no real cleanup occurred.
1176
1241
  Done/discard acknowledgement precedes the background room retry. A network retry
1177
1242
  failure or socket loss during that later work cannot report a committed local
1178
1243
  disposition as failed.
1244
+
1245
+ ## Hub-owned session titles
1246
+
1247
+ `hub_set_title` (#1055) is offered only to fixed Pi/local definitions with a
1248
+ hub-owned title slot. Publication grants no authority: the daemon checks the
1249
+ current adapter/session, private turn, open PII ownership or review and public
1250
+ metadata policy. An agent may write only while the native/current slot remains
1251
+ unnamed. A person's console rename may replace it, but cannot bypass privacy.
1252
+ The title and any unnamed display label are screened again when shown; unknown
1253
+ screening is `[private]`. Title tool inputs/results are excluded from capture
1254
+ and progress observations. No title crosses rooms, changes task/Git/instance
1255
+ records, or causes an extra model call.
1256
+
1257
+ A fallback label is never persisted. Its original message has the existing
1258
+ journal retention; the separate `session-title-inputs.json` stores only peer,
1259
+ title-session, delivery and envelope IDs written by positive delivery
1260
+ acknowledgement, never by a title tool. Restart resolves a still-retained
1261
+ original only for the same title-session ID. A missing or different session,
1262
+ invalid binding or pruned original yields no inherited label. Live status may
1263
+ show it only to the console or configured conductor; other readers see
1264
+ `unnamed`. Metadata screening does not grant audience membership. The label's
1265
+ source note identifies incoming-message text and never attributes it as the
1266
+ recipient peer's own speech. `status.json`, events, logs and memory never
1267
+ receive a derived copy. Removed-instance and Local bindings are not retained.
1268
+
1269
+ ## Questions a peer asks the person (#1053)
1270
+
1271
+ `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.
1272
+
1273
+ - **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.
1274
+ - **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.
1275
+ - **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`).
1276
+ - **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.
1277
+ - **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.
1278
+ - **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.
1279
+ - **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.
1280
+
1281
+ ## Results a person's runs leave behind (#1054)
1282
+
1283
+ 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.
1284
+
1285
+ - **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`).
1286
+ - **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.
1287
+ - **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`).
1288
+ - **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.
1289
+ - **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.
1290
+ - **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.
1291
+
1292
+ ## Anchored review comments (#1056)
1293
+
1294
+ 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.
1295
+
1296
+ - **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.
1297
+ - **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.
1298
+ - **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.
package/docs/smoke.md CHANGED
@@ -38,7 +38,7 @@ Autonomy T4 (#999 AC8): [2026-11-02 live inventory](verification/2026-11-02-auto
38
38
  | Character glyphs in real terminals and tmux (#280) | Console attention; Dashboard character | agent-evidence / person-verdict | glyph rendering in physical terminals |
39
39
  | Console Settings panel in a native terminal (#439) | Settings reload and console fixture | agent-evidence / person-verdict | the panel as a person sees it |
40
40
  | Reviewer pool native legs (#519) | Reviewer pool native legs | agent-executable | after #783 ships |
41
- | Antigravity TUI close (#485) | Antigravity P1 | agent-executable | |
41
+ | Antigravity TUI close (#485) | Antigravity P1 | agent-executable | launcher close leg passed on 2026-11-03 ([receipt](verification/2026-11-03-agy-tui-close.md)); still open: `hub_send` and a board read or proposal through the TUI, a real account |
42
42
  | Managed Pi live receipts (#418 AC8) | none yet (issue #418) | person-only, then agent-executable | the person opts in to a check runner and answers the approvals |
43
43
  | Kimi credential probe (#285 AC9) | Kimi usage counting | person-only | the owner's approval of the probe |
44
44
  | Six role instances (#525-6, hub card #138 I4) | Six role instances (#525-6) | person-only | native terminal/account starts, the person's sandbox and approval choices, and the live six-instance verdict |
@@ -2025,7 +2025,8 @@ Kimi usage ceiling: 2.2.0 print stream-json exposes no token counts. Keep unobse
2025
2025
 
2026
2026
  ## Antigravity P1 (#485)
2027
2027
 
2028
- - Run agy on the 1.4.x line (1.4.2 and 1.4.7 verified; other patches warn, #589); verify its executable/version and keep the actual TUI/IDE leg separate from the account-free T0 transport receipts ([1.4.2](verification/2026-10-19-agy-p1-t0.md), [1.4.7](verification/2026-10-26-agy-1.4.7.md)).
2028
+ - Run agy on the 1.4.x line (1.4.2, 1.4.7 and 1.4.12 verified; other patches warn, #589); verify its executable/version and keep the actual TUI/IDE leg separate from the account-free T0 transport receipts ([1.4.2](verification/2026-10-19-agy-p1-t0.md), [1.4.7](verification/2026-10-26-agy-1.4.7.md), [1.4.12](verification/2026-11-03-agy-1.4.12.md) and its [launcher nonce leg](verification/2026-11-03-agy-1.4.12-launcher-nonce.md)).
2029
+ - The hub launcher's own close leg ran in a scratch project with the real `ahub agy` and a fixture provider ([2026-11-03 receipt](verification/2026-11-03-agy-tui-close.md)): the launch slot is held while agy starts and runs, a normal `/exit` releases it so the next launch is admitted, only the managed three-file plugin is removed, and the real Gemini config hashes are unchanged. Set `AGY_CLI_DISABLE_AUTO_UPDATE=1` for any further live agy run: without it agy replaced `~/.local/bin/agy` during that run.
2029
2030
  - With a reviewed compatible daemon running and agy explicitly on roster, the person runs `ahub agy` in a terminal. Confirm peer `agy` is pull-only, native permissions unmanaged and no hub sandbox/budget source is claimed.
2030
2031
  - In the native TUI call `hub_inbox`, `hub_send`, and a permitted board read/proposal within a non-PII task. Confirm the tool audience and actor remain `agy`, the native model consumes the tool results, and no unsolicited push or PII task text reaches it.
2031
2032
  - End the TUI normally. Confirm its group/socket detaches and only its matching managed three-file plugin is cleaned up; unrelated project/global settings remain unchanged.
@@ -2163,3 +2164,13 @@ account fixture needs the person's account setup. Ask the planner before any
2163
2164
  quota-heavy run. Never publish prompts, tokens, credentials, endpoints, native
2164
2165
  transcripts or session ids as evidence. This slice implements no task `exec`,
2165
2166
  routing policy or matrix runner.
2167
+
2168
+ ## Pi session title restart receipt (#1055, #1143)
2169
+
2170
+ - [ ] Unverified: in a real managed Pi session, call `hub_set_title`, verify its
2171
+ native name and DOING, restart into that same saved session and verify the
2172
+ name survives. A second agent call must return `already_named`; a person
2173
+ rename must replace the name and remain final against the agent.
2174
+ - The fake-free Pi 1.5.0 setter/readback/reopen API probe is recorded separately
2175
+ in `docs/verification/2026-11-03-pi-title-api.md`; it does not complete this
2176
+ model-backed managed-session restart receipt. Track the live leg in #1143.