@staix/agent-hub 0.28.0 → 0.30.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 (90) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +5 -3
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +2 -1
  5. package/docs/agent-notes/bus.md +1 -0
  6. package/docs/agent-notes/daemon.md +4 -1
  7. package/docs/agent-notes/local-worker.md +3 -1
  8. package/docs/agent-notes/tasks.md +7 -3
  9. package/docs/agent-notes/tests.md +3 -0
  10. package/docs/design/adapters.md +56 -0
  11. package/docs/design/agent-hub-architecture.md +12 -0
  12. package/docs/design/bus.md +8 -0
  13. package/docs/design/task-board.md +19 -1
  14. package/docs/dogfood.md +13 -7
  15. package/docs/operations.md +177 -20
  16. package/docs/quickstart.md +18 -3
  17. package/docs/sdlc.md +2 -2
  18. package/docs/security.md +45 -4
  19. package/docs/smoke.md +13 -2
  20. package/docs/specs/2026-10-12-office-view-design.md +86 -3
  21. package/docs/verification/2026-11-03-0.29.0.md +59 -0
  22. package/docs/verification/2026-11-03-0.30.0.md +54 -0
  23. package/docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md +42 -0
  24. package/docs/verification/2026-11-03-agy-1.4.12.md +90 -0
  25. package/docs/verification/2026-11-03-agy-tui-close.md +49 -0
  26. package/docs/verification/2026-11-03-control-write-stall-windows.json +241 -0
  27. package/docs/verification/2026-11-03-control-write-stall-windows.md +20 -0
  28. package/docs/verification/2026-11-03-grok-1.0.62-t0.md +150 -0
  29. package/docs/verification/2026-11-03-live-board-latency.json +556 -0
  30. package/docs/verification/2026-11-03-live-board-latency.md +64 -0
  31. package/docs/verification/2026-11-03-pi-150-compat.md +40 -0
  32. package/docs/verification/2026-11-03-pi-title-api.md +26 -0
  33. package/docs/verification/2026-11-03-qwen-0265-acp.md +55 -0
  34. package/docs/verification/2026-11-03-qwen-0265-dual-output.md +40 -0
  35. package/docs/verification/dogfood/2026-11-03.md +3095 -0
  36. package/docs/verification/pi-150-model-free.ts +57 -0
  37. package/docs/verification/pi-150-native.mjs +21 -0
  38. package/docs/verification/pi-150-title-api.mjs +31 -0
  39. package/docs/verified.json +14 -14
  40. package/package.json +2 -2
  41. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  42. package/plugins/agent-hub/server.js +715 -45
  43. package/src/adapters/compat.ts +10 -10
  44. package/src/adapters/definitions/builtin-metadata.ts +2 -2
  45. package/src/adapters/local-worker.ts +22 -6
  46. package/src/adapters/pi.ts +92 -10
  47. package/src/adapters/registry-types.ts +4 -0
  48. package/src/adapters/registry.ts +2 -0
  49. package/src/cli/autonomy.ts +66 -1
  50. package/src/cli/console-state.ts +6 -2
  51. package/src/cli/help.ts +3 -1
  52. package/src/cli/identity.ts +8 -1
  53. package/src/cli/main.ts +39 -0
  54. package/src/cli/output.ts +5 -0
  55. package/src/cli/recovery-package.ts +9 -0
  56. package/src/cli/worktree-tidy.ts +158 -0
  57. package/src/cli/worktrees-output.ts +43 -0
  58. package/src/hub/board.ts +31 -5
  59. package/src/hub/bus.ts +4 -4
  60. package/src/hub/conductor.ts +4 -3
  61. package/src/hub/config-apply.ts +2 -1
  62. package/src/hub/control-client.ts +16 -1
  63. package/src/hub/control-write-trace.ts +409 -0
  64. package/src/hub/daemon.ts +408 -66
  65. package/src/hub/dogfood-read.ts +62 -9
  66. package/src/hub/hub-tools.ts +12 -6
  67. package/src/hub/manager.ts +9 -1
  68. package/src/hub/peer-doing.ts +33 -3
  69. package/src/hub/peers.ts +7 -0
  70. package/src/hub/routing.ts +9 -2
  71. package/src/hub/runtime-capture-prune.ts +4 -1
  72. package/src/hub/sdlc-release-authorization.ts +13 -4
  73. package/src/hub/sdlc-release.ts +23 -10
  74. package/src/hub/sdlc.ts +15 -2
  75. package/src/hub/settings.ts +3 -0
  76. package/src/hub/task-worktree.ts +331 -0
  77. package/src/hub/tasks.ts +192 -25
  78. package/src/hub/ui.ts +2 -0
  79. package/src/hub/worktree-prs.ts +100 -0
  80. package/src/hub/worktree-shelf.ts +246 -0
  81. package/src/local/deny.ts +49 -0
  82. package/src/local/tools.ts +4 -2
  83. package/src/memory/brief.ts +5 -0
  84. package/src/memory/capture.ts +1 -1
  85. package/src/memory/client.ts +5 -2
  86. package/src/pi/extension.ts +43 -3
  87. package/src/ui/index.html +133 -10
  88. package/src/ui/office-model.ts +64 -8
  89. package/src/ui/office-space.ts +1 -0
  90. package/src/ui/plugin.ts +14 -3
package/docs/security.md CHANGED
@@ -7,6 +7,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
7
7
  - **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
8
8
  - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. The bearer header a Codex TUI sends to the proxy carries only the terminal launch id (#586): it binds terminal ownership and is not a credential. External clients cannot claim the console user's id or a hub-managed peer's id.
9
9
  - **Write retry keys grant no authority (#1078).** The `tool_write_idempotency` feature binds top-level control `requestId` keys to authenticated actor/role/launch/generation/execution scope and canonical business arguments. For task proposals and reviews, MCP `request_id` is an explicit retry argument, independent of JSON-RPC correlation; it is removed before dispatch. A timeout or connection loss remains `outcome: unknown`, with a settling read, while a pre-admission refusal says this attempt did not run. Reusing another scope's key, changed arguments, expired or old-instance IDs never starts a new write. Current authority and privacy are rechecked before a cached result is returned, including the PII status of overlap references. Request payloads are hashed rather than retained; result records are bounded and remain in daemon memory. Limits are per authenticated actor (512 entries/15 minutes), not one shared admission budget. Delivery acknowledgement uses the journal's existing idempotence and socket/generation ownership checks without consuming that cache, so another actor's filled window cannot prevent settlement. Legacy clients without keys keep their path; current clients refuse explicit replay to an older daemon that would ignore it. See [safe retry and refusal semantics](operations.md#control-write-outcomes-and-safe-retries-1078).
10
+ - **Control-write diagnostics carry metadata only (#1126).** The daemon's write spans and event-loop probe record a closed set of operation kinds, phases, task numbers and durations, never task text, paths, ids of transports, actors or sessions, credentials, error text or external output, and nothing for a PII task or a private turn. A frame from a client that has not authenticated is never inspected for them. Retention is capped (128 requests, 256 spans, 512 recent intervals, five-minute expiry) and a record in `hub.log` is at most 2,048 bytes; client timing on stderr is off unless `AHUB_CONTROL_WRITE_TIMING=1`.
10
11
  - **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits, and a headless Claude implementer's proven shell commands through the host wrapper (#845, below); Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation, a person's console `y` for a config default at hub start, or retained autonomy consent bound to that peer's exact never-ask value in the untracked `config.local.json` (#999, below). Without one of these the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. A call the always-ask policy classes (below) is never approved this way, whatever its title. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. Configured ACP kinds (#451) never get this exception: a request whose title spells a hub tool exactly still goes to the person approval path, and only a mapped native mode changes what the agent itself asks. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default; a classed call from a hub-owned ACP peer, the local worker or Pi still waits for a person under it.
11
12
  - **A committed config cannot choose launch commands, credential files, data endpoints, permission modes or a wider sandbox.** The machine-local fields (`kimi_cmd`, `permission_modes`, `codex_bin`, `pi.cmd`, `checks`, `mlx.bin`, `mlx.runtimeDir`, `mlx.modelPath`, `omniroute.urls`, `omniroute.access_hosts`, the `omniroute` key files, `memory.worker_url` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules` (the `every`, `cron` and `tz` forms alike), `folder_triggers`, `corpus.paths`, `invocation`, `start.agents`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, which agents `ahub up` opens terminals for, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
12
13
  - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. Budget pauses, resumes and quota resets carry the peer, window, times and the hub's own reason text, and a person's release authorization its commit, version and docs digest. The usage roll-up (#683) folds the usage, token and limit records into `hub.db` and joins issue, PR and release from the board when read; `ahub usage` and the console's Usage panel answer only the console and a configured planner, and a planner not during a private turn. Kimi's token records (#285) are counted from its own session wire log under its home: only the four token integers, the model label and the row time are copied, and the record id is an opaque hash, never a path, session id or text. The log stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
@@ -14,7 +15,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
14
15
  - **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
15
16
  - **The session record holds identities only.** `.agenthub/state/sessions.json` (issue #37, mode 600) keeps each attached peer's recovery metadata: launch options, session and thread ids, Pi's session file path. No message or task text; loss notices name deliveries by id, sender and public task title. The Pi continuity bookmark that recovery keeps across stop and archive (`.agenthub/state/recovery-continuity.json`, mode 600, at most 16 KiB) holds identities only too: operation and project ids, the project root, and Pi's session id, session file, backend and model name; a bookmark that is not a private regular file, is larger than that or names another project or operation reads as invalid, which is kept distinct from no bookmark.
16
17
  - **Context checkpoints have a separate text record.** Native occupancy readings carry numeric metadata only. An optional context checkpoint persists its summary in `.agenthub/state/context-checkpoint-<peer>.json` with mode 0600 and, when memory is enabled, proposes it as a machine-local memory candidate that only a person can accept. Completion requires the outstanding request id, current peer/native session and transport generation; new connection claims invalidate old requests. Private turns, every open PII task associated with the peer as owner or reviewer (including pending review), and PII-pattern summaries are refused before persistence or memory. Its text is never broadcast, and context pressure does not pause, reassign or replace the session.
17
- - **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project, and name-based refusals (the denylist, `.git`, `.agenthub`) judge both the resolved name and the name typed inside the project, because Bun's realpath can name another hardlink of the same file (#541; facts, deliverables and `ahub source add` too, #552); a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git` name at any depth, closing the creation, rename, symlink and gitfile routes to a planted repository, no `config`, `config.worktree` or `hooks` directly under one, nor in any submodule git directory (every modules tree the project can reach is covered: the common dir's, each linked worktree's own under `<common>/worktrees/<wt>/modules/`, matched by pattern so a tree or worktree that does not exist yet is covered too, and each discovered submodule git directory's own `modules/`, nested submodules and a submodule's own linked worktrees included; under a modules tree every write that is not inside a discovered submodule git directory is refused, whatever its name, so a fake git directory cannot be planted for a later unsandboxed `git submodule update --init` to reuse; inside each discovered one its own `config`, `config.worktree` and `hooks` and its linked worktrees' `config.worktree` are refused, while refs, logs, objects, the index and `HEAD` stay writable, so commit, branch, tag, gc and rebase work; a linked worktree's git directory under `<common>/worktrees/` gets the same `config`, `config.worktree` and `hooks` refusal; every term is anchored at the discovered path, `hooks` by depth, so a directory named `config` or `hooks` above the project neither disables the rule nor trips it; the discovered git directories, the modules roots, the worktrees container, each worktree git directory and every component in between are also refused as targets, closing the rename, replace and symlink routes to the same files), no `commondir` at any depth below one, with every such name matched folded against the code points HFS+ ignores (the fold covers the security-relevant names; on a volume that folds names, folded spellings of the components between `.git` and a submodule's git directory are not matched — the 1024-byte SBPL literal cannot hold a folded path — the same residual the external git dir rules carry since #270), and no `.agenthub` anywhere in the project (in an external git dir the `config`/`config.worktree`/`hooks` rules cover only its own children and the hooks directory's contents, so refs named `config` or `hooks` still work); a nested repository in bare layout stays open — the directory is the git dir, with no `.git` name to anchor a rule on, and the hub never runs git there, whatever the project root is: its calls are all `-C <project root>` or `--no-index` on temp files, and none descends into a nested directory's git layout, so a planted hook fires only if a person or another tool runs git in that directory themselves; the rules are probed on HFS+, which folds the 16 code points the patterns tolerate, and on a case-sensitive APFS image, which folds nothing (a folded spelling is an inert new file there and the real names stay refused); the shared object store takes only new loose objects (#864): `objects/info` (alternates, http-alternates, commit-graph) and `objects/pack` are refused, and so are the `objects`, `objects/info` and `objects/pack` entries, moving a fan-out directory, and changing, removing or replacing an existing loose object, because git reads packs first and never rehashes what it reads, so a planted pack or alternate would change what every checkout and the implementer's host add, commit and push (#845) see; a hardlink to an existing object is refused as well; an object id that does not exist yet can still be planted before git writes it (its exact content must be predicted), which a local checkout then shows, while a push of it is rejected by the receiving side's rehash; the cost is that `git init`, `git clone` and `git worktree add` do not run inside the sandbox (add, commit, stash and checkout still do; `git gc`, `repack`, `fetch` and `maintenance` run outside it, #864), `git submodule update --init` does not either (its git directory's `config` write is refused), and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` in a project with a nested repository, or `rm -rf` of a directory that holds a `.git` (refusing the unlink is what stops the rename-out route); no network, loopback included, unless `local.bash_network`; with it, only through the hub's egress proxy to the hosts in `local.network_allow` (issue #65), which refuses names that resolve to internal addresses (behind NAT64 with the well-known prefix, by the IPv4 address the answer carries; a network-specific prefix is not recognised), so claude-mem and the Codex app-server stay out of reach (`"direct"` opens everything, until 0.13.0). One channel stays open: `trustd`, which TLS clients need, can fetch a certificate's AIA or OCSP URL on a command's behalf, outside the proxy. The allow-default profile of 0.9 and earlier was removed in 0.12.0 (issue #83); a `local.sandbox` setting is ignored with a note. The shared temp dirs (the user's and `/private/tmp`) are closed. Without the sandbox there is no `bash` tool. Two opt-in tools, both machine-local and off by default, work outside the command sandbox: `doc_read` (`local.doc_read`) reads a PDF, DOCX or HWPX file through the path guard and the `pii.paths` screen, converts it with the person's `text_projection` and screens the text before the model sees it; `web_fetch` (`local.web_fetch.allow`) sends a bare https GET only to the listed hosts, through its own egress proxy (never the shell's), re-checking every redirect, and is refused for the whole turn of a PII task. Neither result is captured to memory.
18
+ - **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project, and name-based refusals (the denylist, `.git`, `.agenthub`) judge both the resolved name and the name typed inside the project, because Bun's realpath can name another hardlink of the same file (#541; facts, deliverables and `ahub source add` too, #552); a secrets denylist (`.env*`, keys, credential files, the hub's own state and the state archives `ahub reset --all` leaves) applies to its file tools, its git arguments and its memory capture alike; `.git` and `.agenthub` are not writable. Writes, edits, shell commands and mutating git wait for approval unless the operator's permission mode grants them (writes and edits inside the grant filter in `ask-when-needed`, all four in `never-ask`; see below), and the approver sees what will be written or run, with control characters escaped. Everything it executes runs under the macOS sandbox, attended or not. Since 0.10 the profile starts from deny default (issue #39): commands run and read only the system, toolchain and project directories (and the project's git dir and the selected developer dir; with network on, the public CA bundles); no writes outside the project, its git dir and a temp dir of its own (`TMPDIR`, made for each command and removed when it ends; issue #63); no `.git` name at any depth, closing the creation, rename, symlink and gitfile routes to a planted repository, no `config`, `config.worktree` or `hooks` directly under one, nor in any submodule git directory (every modules tree the project can reach is covered: the common dir's, each linked worktree's own under `<common>/worktrees/<wt>/modules/`, matched by pattern so a tree or worktree that does not exist yet is covered too, and each discovered submodule git directory's own `modules/`, nested submodules and a submodule's own linked worktrees included; under a modules tree every write that is not inside a discovered submodule git directory is refused, whatever its name, so a fake git directory cannot be planted for a later unsandboxed `git submodule update --init` to reuse; inside each discovered one its own `config`, `config.worktree` and `hooks` and its linked worktrees' `config.worktree` are refused, while the index, logs and objects stay writable and, since #1123, its `refs`, `packed-refs`, `HEAD` and `worktrees` are refused like the project's own, so commit, branch, tag and rebase fail there too; a linked worktree's git directory under `<common>/worktrees/` gets the same `config`, `config.worktree` and `hooks` refusal; every term is anchored at the discovered path, `hooks` by depth, so a directory named `config` or `hooks` above the project neither disables the rule nor trips it; the discovered git directories, the modules roots, the worktrees container, each worktree git directory and every component in between are also refused as targets, closing the rename, replace and symlink routes to the same files), no `commondir` at any depth below one, with every such name matched folded against the code points HFS+ ignores (the fold covers the security-relevant names; on a volume that folds names, folded spellings of the components between `.git` and a submodule's git directory are not matched — the 1024-byte SBPL literal cannot hold a folded path — the same residual the external git dir rules carry since #270), and no `.agenthub` anywhere in the project (in an external git dir the `config`/`config.worktree`/`hooks` rules cover only its own children and the hooks directory's contents, so refs named `config` or `hooks` still work); a nested repository in bare layout stays open — the directory is the git dir, with no `.git` name to anchor a rule on, and the hub never runs git there, whatever the project root is: its calls are all `-C <project root>` or `--no-index` on temp files, and none descends into a nested directory's git layout, so a planted hook fires only if a person or another tool runs git in that directory themselves; the rules are probed on HFS+, which folds the 16 code points the patterns tolerate, and on a case-sensitive APFS image, which folds nothing (a folded spelling is an inert new file there and the real names stay refused); the shared object store takes only new loose objects (#864): `objects/info` (alternates, http-alternates, commit-graph) and `objects/pack` are refused, and so are the `objects`, `objects/info` and `objects/pack` entries, moving a fan-out directory, and changing, removing or replacing an existing loose object, because git reads packs first and never rehashes what it reads, so a planted pack or alternate would change what every checkout and the implementer's host add, commit and push (#845) see; a hardlink to an existing object is refused as well; an object id that does not exist yet can still be planted before git writes it (its exact content must be predicted), which a local checkout then shows, while a push of it is rejected by the receiving side's rehash; the cost is that `git init`, `git clone` and `git worktree add` do not run inside the sandbox (`git add`, `restore`, `checkout -- <file>` and the read commands still do; since #1123 `commit`, `stash`, `branch`, `tag`, `switch`, `merge`, `rebase` and every other command that moves a ref or HEAD do not, see "Refs, HEAD and worktree admin writes" below; `git gc`, `repack`, `fetch` and `maintenance` run outside it, #864), `git submodule update --init` does not either (its git directory's `config` write is refused), and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` in a project with a nested repository, or `rm -rf` of a directory that holds a `.git` (refusing the unlink is what stops the rename-out route); no network, loopback included, unless `local.bash_network`; with it, only through the hub's egress proxy to the hosts in `local.network_allow` (issue #65), which refuses names that resolve to internal addresses (behind NAT64 with the well-known prefix, by the IPv4 address the answer carries; a network-specific prefix is not recognised), so claude-mem and the Codex app-server stay out of reach (`"direct"` opens everything, until 0.13.0). One channel stays open: `trustd`, which TLS clients need, can fetch a certificate's AIA or OCSP URL on a command's behalf, outside the proxy. The allow-default profile of 0.9 and earlier was removed in 0.12.0 (issue #83); a `local.sandbox` setting is ignored with a note. The shared temp dirs (the user's and `/private/tmp`) are closed. Without the sandbox there is no `bash` tool. Two opt-in tools, both machine-local and off by default, work outside the command sandbox: `doc_read` (`local.doc_read`) reads a PDF, DOCX or HWPX file through the path guard and the `pii.paths` screen, converts it with the person's `text_projection` and screens the text before the model sees it; `web_fetch` (`local.web_fetch.allow`) sends a bare https GET only to the listed hosts, through its own egress proxy (never the shell's), re-checking every redirect, and is refused for the whole turn of a PII task. Neither result is captured to memory.
18
19
  - **Capabilities are enforced, not suggested.** `capabilities` (issue #39) is checked by the daemon where task operations and messages arrive, so a peer cannot get round it by phrasing. A peer can never answer a permission request: the control link takes `permit` from the console role only, and a message that quotes a permit command is just text. Both bind the hub's own tool paths: a vendor agent with its own shell in the project (Codex, Claude, Kimi) can read `.agenthub/state/control-token` and connect as the console, which only the local worker's and Pi's sandbox prevents.
19
20
  - **PII has an enforced path.** A task matching `signals.pii_patterns` goes to `local` or to nobody; its text is absent from other peers' envelopes, the console stream, the log and the board listing; the console user reviews it; `local` answers such a turn to the console only, keeps it out of its history, refuses it when the only gateway is off campus, and may not save notes or spin off tasks during it. Nothing about it enters hub-managed memory; local storage does not relax this privacy fence.
20
21
  - **A model screen can add to the patterns (issue #198).** With `signals.pii_screen = "local"` in `routing.toml` (default `off`; it applies while `constraints.pii = "local_only"`), a newly proposed task is read by a model before the hub does anything else with it: until the verdict it is on no board, in no envelope, before no triage model, and the hub makes no brief or note call for it. The local worker's amem capture leaves its hub tool calls out (issue #230), so a proposal it makes in an ordinary turn cannot reach shared memory ahead of the verdict that way. The screen runs on the model on this machine (MLX or Ollama, loopback only) or, without one, on the gateway only while `onCampus()` holds, and the gateway call is refused once more right before transport if the gateway is behind Access; task text never goes off campus to be screened. When the on-device generation slot is taken (Pi generating, another screen), the screen goes to the campus gateway instead of waiting, and on campus a device that fails or is still loading after 60% of the deadline hands over to the gateway too; off campus it waits for the slot and the device within the deadline. With the legacy MLX provider the screen may start an `mlx_lm.server`, as Pi's relay does, and it stays running until `ahub models stop`. Its answer is `pii` or `clear` plus a category from a closed list (name, student id, phone, address, grade, health, other). Anything else, a timeout (8 s, one call and one generation slot per item), a missing or off-campus model, or text longer than 6000 UTF-8 bytes (about 2000 Hangul syllables) is `unknown`, and an unknown task is a PII task: this is the hub's one model call that fails closed, so with the screener down every new task waits for `local`. An unknown verdict has the costs of any PII task, even when a second look clears it later: opening it lifts every silent turn-free cohort for good and turns facts off while it is open, and off campus a proposal without a class is refused (triage is not asked for a PII task there), with an error that says so. Off campus with the on-device slot taken, a task stays `unknown` until it is screened again: while it is still proposed and no peer ever owned it, the hub's release timer (every 60 s, one task at a time, the least tried first, at most `tasks.rescreen_max` (default 10) times per task in a hub run; never text too long to read) asks again, and `clear` lifts its `pii` signal and routes it through the class peers (a named owner is not remembered; a reserved one is offered first, as always). A task `local` took keeps its PII path, also once it is declined back to nobody. A pattern match is PII without a call. The task's history records the source of its `pii` signal (`regex`, `screen` with the category, or `unknown` with the reason), an unknown verdict is a console line naming the task id only, and `events.jsonl` gets a `pii_screen` record with the item kind, label, source, category, reason and latency, never the text. A routing explanation of a draft does not call the screen and says so.
@@ -672,7 +673,7 @@ withholds the scoped mode on these writes so an older hook grants nothing too.
672
673
 
673
674
  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.
674
675
 
675
- 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)
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, 0.26.4 and 0.26.5)
676
677
  and advertised mode ids before setting a policy or starting work. Its own tools
677
678
  have no hub sandbox; auto-edit approves file edits, yolo every tool. Configured
678
679
  never-ask still requires the person's confirmation. qwen_cmd is machine-local and
@@ -681,7 +682,7 @@ this production table. Startup diagnostics withhold native host/payload details.
681
682
 
682
683
  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.
683
684
 
684
- 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.
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, 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.
685
686
 
686
687
  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.
687
688
 
@@ -826,7 +827,23 @@ SDLC metrics and control bands (#481 S7). `ahub sdlc metrics` and `hub_sdlc_metr
826
827
 
827
828
  Design specs (#1097, #1102, #1107). `hub_specs` is read-only and refused during a private or PII turn. PII tasks and private turns publish no spec paths through task notices. `spec_ack` on `hub_task_done` is refused for private work and while specs review is off. Each id and reason must be public metadata, a withheld reason is refused, and the acknowledgements are recorded at the exact head. `ahub specs stamp` is person-only: refused for an agent identity and under `--live-test`, and refused without a terminal even with `--yes`. After the person answers, it rechecks HEAD, the state of each covered path and every target file's bytes, and any change refuses the whole run.
828
829
 
829
- Declared task needs (#1081). A task's `needs_network` or `needs_worktree` is set only by an explicit true, never inferred from its text. Routing, assignment and accept keep such work from a peer whose capability the hub knows is missing (a hub-sandboxed Pi or local worker without `local.bash_network`, or one that cannot run `git worktree add` inside its sandbox, #270), and the accept refusal comes before any write, so no branch ref is left. An unknown capability is never a refusal, so this is advice for routing, not a sandbox boundary; the sandbox itself is unchanged, and a hub-made writable worktree is a separate design (#1115).
830
+ Declared task needs (#1081). A task's `needs_network` or `needs_worktree` is set only by an explicit true, never inferred from its text. Routing, assignment and accept keep such work from a peer whose capability the hub knows is missing (a hub-sandboxed Pi or local worker without `local.bash_network`, or one that cannot run `git worktree add` inside its sandbox, #270), and the accept refusal comes before any write, so no branch ref is left. An unknown capability is never a refusal, so this is advice for routing, not a sandbox boundary; the sandbox profile itself is unchanged.
831
+
832
+ Hub-made task worktree (#1115, #1129, `src/hub/task-worktree.ts`). For write-class work (`implement`, `bulk_edit`) and for any work that declares `needs_worktree`, the hub makes the worktree of a hub-sandboxed owner (Pi, the local worker) at the fixed root `.worktrees/<peer>-<task>` on a new branch `task/<task>-<peer>`, before the accept write, through the hardened git helper (`hubGitSync`, no hook, no helper, no prompt, `repositoryIsolation`). The base is `origin/HEAD` when the repository has one, else the main checkout's HEAD, with no fetch, and it is recorded in the hub's state dir (`task-worktrees.json`), which no sandbox root contains. A refused accept (not a git repository, the branch or path is taken, the repository's own configuration names a filter, include, textconv, ssh command or remote override) creates nothing and leaves no ref. That guard reads only what a repository or a peer can plant: the shared `.git/config` (`git config --local`, which a linked worktree reads too) and, where `extensions.worktreeConfig` is on, the worktree's `config.worktree`. The person's global and system git config is theirs and is not read by it, because git-lfs alone puts `filter.lfs.*` there on most machines and refusing it would switch the feature off for them; the hardened git helper's isolation still applies to that config. The sandbox profile is not rebuilt per task and not widened: `test/task-worktree-sandbox.test.ts` compares `profile()` before and after the hub makes a worktree.
833
+
834
+ What the sandbox can and cannot write is stated here so nobody relies on more. A sandboxed command CAN write files in the worktree and it can add NEW loose objects (#864). It CANNOT write a `.git` gitfile, `config`, `config.worktree`, `hooks`, `commondir`, packs or `objects/info`, and since #1123 it cannot write `refs/**`, `packed-refs`, `HEAD` or a worktree's admin dir (`.git/worktrees/<name>/`), so its own `git commit` or `git branch` fails (see "Refs, HEAD and worktree admin writes" below). The peer is also told not to run git. The hub does not rely on either: it never trusts what is under `.git` for this flow, whatever the profile of the day allows (the profile of #1115 alone did not refuse ref writes, and a rule can be loosened later). At `hub_task_done` it commits only when the worktree is still registered at its fixed path, HEAD is still the task branch, and the branch tip still equals the base the hub recorded (or the hub's own previous commit); a peer that moved the branch to another existing commit is refused with the cause and the next step, and nothing is committed or pushed. It then rebuilds the index from the recorded tip (the peer can write the worktree index), stages the working tree, and refuses the whole commit when any changed path is on the denylist or is a `.git`, `.agenthub`, agent-configuration or protected instruction name, or is a submodule, or when more than 512 paths changed. The commit is the hub's, in the peer's name, with `--no-verify` and no signing, and its message is the task's title. The push is one explicit refspec `refs/heads/<branch>:refs/heads/<branch>`, only after every `origin` URL names this project's GitHub repository, with followTags, mirror and submodule recursion off. Fetch and push stay with the hub. A retry after a failed push pushes the commit the hub already made and never makes a second one.
835
+
836
+ 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
+
838
+ 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.
839
+
840
+ 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.
841
+
842
+ 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.
843
+
844
+ What stops working in the sandbox, stated so nobody is surprised: `git commit`, `branch`, `tag`, `stash`, `switch`, `checkout -b`, `merge`, `rebase`, `cherry-pick`, `revert`, `reset` and `update-ref`, anywhere, and `git add` inside a linked worktree. A sandboxed peer's work is therefore committed by the hub: for every write-class task (`implement`, `bulk_edit`) it owns, and for any task that declares `needs_worktree`, the hub stages, commits and pushes the hub-made worktree at `hub_task_done` (#1115, #1129), whether or not the task was flagged. Read-only classes (`test`, `summarize`, `triage`, `review`, `plan`) and PII tasks get no worktree and no hub commit, because they commit nothing; a hub-sandboxed peer on a project where the hub cannot make a worktree is not offered write-class work at all, and the refusal names the cause (`no worktree: ...`). The local `git` tool says so in its result when a command is refused this way, and its pointer to `hub_task_done` is true for the work that can reach it.
845
+
846
+ Submodules (#1123, the person's ruling of 2026-11-03: deny `.git` writes, submodule git dirs included). Every DISCOVERED submodule git dir gets the same four refusals as the project's own: `.git/modules/<name>`, nested ones (`.git/modules/<a>/modules/<b>`) and a linked worktree's own (`.git/worktrees/<wt>/modules/<name>`, nested too) refuse `refs/**`, `packed-refs*`, `HEAD*` and `worktrees/**`, so a sandboxed `git commit`, `branch`, `tag`, `stash` or `switch` inside a submodule fails with no ref moved (`test/sandbox-git-refs.test.ts` runs commit, branch and tag in a submodule, a nested one and a linked worktree's, with the real sandbox-exec). A submodule git dir that is not discovered is refused whole by #518 and #520, so nothing is left open by not being found. What stays writable there is what a read needs and nothing that moves a ref or runs code: `index` and `index.lock` (`git status` refreshes it), `logs/HEAD`, `ORIG_HEAD`, `MERGE_HEAD`, `COMMIT_EDITMSG` and `info/exclude` (a test writes each in a submodule git dir), and new loose objects as for the project (#864; not separately tested for a submodule). The working tree of a submodule stays writable and its `status`, `diff`, `log` and `show` work. The `.git` writes that stay open (the main checkout's `index`, `logs`, `ORIG_HEAD`, `MERGE_HEAD`) can be used to corrupt local state the person later reads, not to run code or to move a ref. Consequence for work inside a submodule: the sandbox cannot commit there, and the hub's commit (#1115) is a commit of the project's own repository, which records a submodule only as a commit id. So edits made inside a submodule's working tree are not part of the hub's commit and stay uncommitted in that submodule, while a changed submodule pointer (a gitlink) makes the hub refuse the whole commit, naming the path. A task that edits inside a submodule therefore needs a person or a vendor agent (Claude, Codex, Kimi) to commit that part.
830
847
 
831
848
  ## Research campaigns and study tools (#930, #932)
832
849
 
@@ -1161,3 +1178,27 @@ path and no polling when no real cleanup occurred.
1161
1178
  Done/discard acknowledgement precedes the background room retry. A network retry
1162
1179
  failure or socket loss during that later work cannot report a committed local
1163
1180
  disposition as failed.
1181
+
1182
+ ## Hub-owned session titles
1183
+
1184
+ `hub_set_title` (#1055) is offered only to fixed Pi/local definitions with a
1185
+ hub-owned title slot. Publication grants no authority: the daemon checks the
1186
+ current adapter/session, private turn, open PII ownership or review and public
1187
+ metadata policy. An agent may write only while the native/current slot remains
1188
+ unnamed. A person's console rename may replace it, but cannot bypass privacy.
1189
+ The title and any unnamed display label are screened again when shown; unknown
1190
+ screening is `[private]`. Title tool inputs/results are excluded from capture
1191
+ and progress observations. No title crosses rooms, changes task/Git/instance
1192
+ records, or causes an extra model call.
1193
+
1194
+ A fallback label is never persisted. Its original message has the existing
1195
+ journal retention; the separate `session-title-inputs.json` stores only peer,
1196
+ title-session, delivery and envelope IDs written by positive delivery
1197
+ acknowledgement, never by a title tool. Restart resolves a still-retained
1198
+ original only for the same title-session ID. A missing or different session,
1199
+ invalid binding or pruned original yields no inherited label. Live status may
1200
+ show it only to the console or configured conductor; other readers see
1201
+ `unnamed`. Metadata screening does not grant audience membership. The label's
1202
+ source note identifies incoming-message text and never attributes it as the
1203
+ recipient peer's own speech. `status.json`, events, logs and memory never
1204
+ receive a derived copy. Removed-instance and Local bindings are not retained.
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.
@@ -2,7 +2,7 @@
2
2
  title: Office view of live agent work in the dashboard
3
3
  date: 2026-10-12
4
4
  project: agent-hub
5
- status: phases 1-4 implemented (Flat and Isometric office with motion and turn/checking/link fields); phase 5 pending #263
5
+ status: phases 1-5 implemented (Flat and Isometric office, backlog wall, release docks and planner marks); AC9 live browser look remains unverified
6
6
  issue: 264
7
7
  ---
8
8
 
@@ -55,7 +55,8 @@ asset is copied.
55
55
  latch, the settle rule. No daemon change.
56
56
  3. Optional snapshot fields `turn`, `checking`, `links` and what they draw.
57
57
  4. The Isometric projection of the same model.
58
- 5. Backlog wall, release docks and the planner's desk, once #263 lands.
58
+ 5. Backlog wall, release docks and the planner's desk, from the board
59
+ planning fields that #263 and #469 added (decisions below).
59
60
 
60
61
  Phase 1 decisions within the spec:
61
62
 
@@ -209,6 +210,73 @@ Phase 4 decisions within the spec:
209
210
  - Existing theme tokens, public-view privacy, counts/placement classifications,
210
211
  animations, reduced motion, keyboard activation and mode cookie remain.
211
212
 
213
+ Phase 5 decisions within the spec (designed 2026-11-03, hub task 311;
214
+ implemented by hub task 682, with AC9 live browser look still open):
215
+
216
+ - Phase 5 needs no daemon change. `ui_snapshot` already carries everything:
217
+ - every board task, backlog cards included, with `rank`, `priority`,
218
+ `reserved`, `release`, `pinned`, `plannedStart` and `deps` next to the
219
+ public title;
220
+ - `releases[]`, the `tasks.releaseList` rows, with `id`, `name` (already
221
+ `[withheld]` when not nameable), `state`, `order`, `cut` and the scope
222
+ `tasks[]` as `{ id, state, deps }`;
223
+ - `status.roleInfo[peer].flowRoles`, which names the planner.
224
+ The model reads these by allowlist. It never reads a release `goal`, `train`,
225
+ the `afterCut` lists, task `detail`, `lifecycle` or `codeGroup`, and the
226
+ marker test grows to cover them.
227
+ - **Backlog wall.** A room named "Backlog" before To do in the fixed room
228
+ order, with placement `room:backlog`. Today `counts.backlog` is counted and
229
+ placed nowhere; from phase 5 these tasks get placed.
230
+ - Cards are ordered by `rank` ascending (no rank last), then `priority`
231
+ ascending (none last), then id. The cap is 8 cards, as in every room; the
232
+ rest is "+N more", which sets the board's existing `backlog` state filter.
233
+ - A card shows its id and public title, like other room cards. It adds up to
234
+ three marks, each with a shape and text in the accessible name:
235
+ - the reserved peer's colour chip and name;
236
+ - a pin for `pinned`;
237
+ - "waits", when a dep is not approved or `plannedStart` is in the future
238
+ (the date only, never the time).
239
+ - The wall count equals `taskProgress(tasks).counts.backlog`, pinned by the
240
+ existing counts test.
241
+ - A pulled card moves from the wall to To do through the phase-2 transform
242
+ transition, with no new motion code.
243
+ - **Release docks.** A strip below the rooms, beside the Done shelf. It has
244
+ one dock per release in state `planning`, `active` or `frozen`, ordered by
245
+ `order` then id; `shipped` and `dropped` releases get none. The cap is 3
246
+ docks, then "+N releases", which switches the board's View to "By release".
247
+ - A dock shows the release name, its state as a word, `approved / scope`
248
+ from its `tasks[]`, and a thin bar of the same ratio. It also shows the
249
+ `cut` date when set.
250
+ - Cards are not duplicated into docks: a card stays in its room. A card in
251
+ a docked release carries the release's ordinal (1 to 3) as a small tag
252
+ with the name in its accessible name.
253
+ - Activating a dock sets the board's existing `task-release-filter` to that
254
+ release and scrolls to the board, as card activation does for its row.
255
+ - **Planner's desk.** The desk of each peer whose `flowRoles` include
256
+ `planner` gets a planner badge: a shape plus "planner" in its accessible
257
+ name. A count joins it: the backlog cards with no `rank` and no `reserved`,
258
+ that is, the cards still waiting for planning. A peer with no desk gets no
259
+ badge, and the count then appears on the wall's header. The planner is a
260
+ mark on the existing desk. It is not a separate desk and claims no seat.
261
+ - **Isometric.** The wall is an ordinary room tile and the docks are slab
262
+ tiles like the shelf, through the same `officeProject`. Phase 4's fit rule
263
+ applies: cards that do not fit are counted in "+N more".
264
+ - **Privacy.** A PII backlog card is already a `[pii]` stub in the snapshot;
265
+ its reserved chip and marks still show, because they carry ids only. A
266
+ release name that is not nameable stays `[withheld]`.
267
+ - **Page-size bound.** This replaces the issue's original 30 KB office budget,
268
+ which was historical (see the phase 4 accounting) and is long exceeded by
269
+ unrelated features. The served page is `renderDashboard().html` with the
270
+ models injected. Measured on main a51c8fc7 (2026-11-03): 259,254 bytes raw,
271
+ 70,237 gzip; `src/ui/index.html` alone is 236,071 bytes.
272
+ - The phase-5 PR measures the served page at its merge base and at its head,
273
+ raw and gzip, and states both deltas in the PR body.
274
+ - Phase 5 may add at most 12,000 bytes raw and 3,500 bytes gzip. Beyond
275
+ that, the PR drops art polish first, then trims the dock bar, before it
276
+ asks the person to raise the bound.
277
+ - No standing page-size test is added. Other dashboard features grow the
278
+ same page, and a global cap would block them for an office budget.
279
+
212
280
  ## Verification
213
281
 
214
282
  - `test/office-model.test.ts`: mapping fixture, counts equality with
@@ -239,8 +307,23 @@ Phase 4 decisions within the spec:
239
307
  and overlap/cohort links against a real daemon, pins `PROTOCOL` (16) and
240
308
  `EVENTS_SCHEMA` (1), and proves a PII task keeps its stub, refuses a
241
309
  non-local owner and joins no link.
310
+ - Phase 5 (implemented with the model and renderer regression tests):
311
+ - wall placement and order (rank, priority, id) with the cap and "+N more";
312
+ - wall count equals `counts.backlog`;
313
+ - the reserved, pin and waits marks;
314
+ - dock selection by state and order with the 3-dock cap and the approved /
315
+ scope ratio;
316
+ - the release ordinal tag on cards;
317
+ - the planner badge and the unplanned count, including the no-desk fallback
318
+ to the wall header;
319
+ - the marker test extended to release `goal`, `train`, `afterCut`, task
320
+ `lifecycle` and `codeGroup`;
321
+ - in `test/office-view.test.ts`, "+N more", dock activation setting
322
+ `task-release-filter` and the "By release" switch, plus a pulled card
323
+ animating from the wall to To do.
242
324
  - The live browser look (two agents working, both themes, console open) is
243
- recorded in `docs/smoke.md` as unverified for phase 1.
325
+ recorded in `docs/smoke.md` as unverified for phase 1. #273 is closed, so
326
+ AC9's browser look stays with #264 itself.
244
327
 
245
328
 
246
329
  ## Merge review corrections
@@ -0,0 +1,59 @@
1
+ # 0.29.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.29.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.29.0, prepared from main 5943bc6c (11 commits since v0.28.0, 4 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.28.0: #1129, #1132, #1133, #1134, #1131, #1128, #1123, #1130, #1115, #1127, #1125, #1124, #1120, #1122.
12
+
13
+ Each merged with `gh pr merge --squash --match-head-commit <head>` after a claude `hub_review` approval, with exact-head CI green on ubuntu and macOS (seeded guards included), no unresolved review thread and no pending change request. Every approval is bound at the merged head (`sdlc.reviewedHead` equals the PR head):
14
+
15
+ - #1122 (task 667, #1120) at 2be1975f; #1124 (668, #1121) at 20544df9; #1125 (669, #1078 findings, docs only) at c45d5999.
16
+ - #1127 (670, #1115) at a274bf04, after a macOS-only failure of a test that asserted a sandboxed `git commit` fails; that failure came from a local signing config, and the assertion moved to #1130.
17
+ - #1130 (672, #1123) at 3885038c; #1128 (671, #1126) at 57820fca, rebased once after #1127 and #1130 with every commit unchanged in range-diff.
18
+ - #1131 (675, agent-mem 0.20.0) at 2a28c2bb; a duplicate handoff of the same pin raise (task 677) was cancelled by the person.
19
+ - #1134 (678) at e7bdab10: a Qwen fixture whose synthetic PIDs could equal the runner's PID (seen once in a dgx02 gate), fixed in the fixture under an explicit test-edit ruling.
20
+ - #1133 (674, #1126 follow-up) at 870ddde7, after a codex-connector thread showed an always-true privacy stub in an extracted fixture (tests.md rule); fixed with the real guards and resolved.
21
+ - #1132 (673, #1129) at 6f7f65f0, after two hosted-CI failures: daemon fixtures that route implement work to Pi or local outside a repository, then two tests run in the agent-hub checkout, where the runner's `includeIf` credentials entry makes the hub refuse a worktree. Both fixed in fixtures only; the second was reproduced in a scratch clone first.
22
+ - No merge was bound to an approval at an earlier head.
23
+
24
+ ## Native acceptance receipts
25
+
26
+ Receipts added under `docs/verification/` since v0.28.0: `docs/verification/2026-11-03-control-write-stall-windows.json`, `docs/verification/2026-11-03-control-write-stall-windows.md`, `docs/verification/2026-11-03-live-board-latency.json`, `docs/verification/2026-11-03-live-board-latency.md`.
27
+
28
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.29.0`, recorded by the person or the unique configured conductor only on the person's explicit release instruction naming the release in its own session (never a hub/task/review/file/peer relay), and is not recorded in this file. The tag run refuses without it.
29
+
30
+ ## Document review
31
+
32
+ Stale at preparation: README.md, docs/agent-notes/{README,adapters,benchmarks,budget,bus,daemon,local-worker,tasks,tests}.md, docs/operations.md, docs/quickstart.md and docs/security.md. Each was read against the commits since its stamp (3efa678d for most, b26fcd12 for operations.md, aaccc30b for local-worker.md, 36455498 for tests.md). src/cli/help.ts was not stale: no command or flag changed (the only CLI source change is the `ahub status --full` counter line in src/cli/output.ts).
33
+
34
+ - README.md: Status line (release-prepare) and the report link moved to this file.
35
+ - docs/quickstart.md: v0.29.0 install tag, an "Upgrading to 0.29.0" section (amem upgrade together, the sandbox git refusal and the hub commit, control-write diagnostics), and the agent-mem pin 0.19.0 -> 0.20.0 in two places.
36
+ - docs/operations.md: guide header and protocol-window row (0.26.0 through 0.29.0; window unchanged at 16 through 17). The #1115, #1123, #1126 and #1129 sections were written by their PRs and checked.
37
+ - docs/security.md: a control-write diagnostics bullet (metadata only, unauthenticated frames never inspected, caps, opt-in client timing). The #1115, #1123 and #1129 paragraphs were written by their PRs and checked.
38
+ - docs/agent-notes/local-worker.md: the #1115 line still said a sandboxed command can write `.git/refs/heads/*` and worktree admin files and that deny.ts stays unchanged; corrected for #1123, and the list of what the sandbox cannot write now names refs, packed-refs, HEAD and worktree admin files.
39
+ - docs/agent-notes/tasks.md: a stale-head re-file rule (#1121, #1124).
40
+ - docs/agent-notes/daemon.md: a control-write diagnostics rule (#1126: wrapper name for the native-branch ratchet, no traversal before admission, primitive guards, lazy screens, no timeout or authority on this path).
41
+ - docs/agent-notes/tests.md: a rule that a daemon fixture routing write-class work to Pi or local runs in a `gitProject()` repository, never the agent-hub checkout (#1129).
42
+ - docs/agent-notes/{README,adapters,benchmarks,budget,bus}.md: read against the daemon.ts, deny.ts and tools.ts changes; no statement became wrong (the native-branch ratchet is unchanged because the telemetry wrapper keeps the original `onMessage`).
43
+
44
+ All thirteen were restamped with `--reviewed` at 0d0a362c, the branch commit holding these edits.
45
+
46
+ ## Checks
47
+
48
+ - `node scripts/check-docs.mjs --require-fresh` before the merge: 0 stale documents (it refuses only for the missing release merge commit, by design).
49
+ - `node scripts/check-package.mjs`: `package: OK (753 files, @staix/agent-hub@0.29.0)`.
50
+ - `bun scripts/build.mjs --check`: the bundle matches the pinned Bun 1.4.2 build.
51
+ - `bun x tsc --noEmit` and the full suite on ubuntu and macOS with the seeded guards: run by the release PR's exact-head CI (heavy checks run only on CI or dgx02 on this machine).
52
+
53
+ ## Known gaps, not blocking
54
+
55
+ - No installation and no running-hub upgrade to 0.29.0 is claimed. The running hub on this machine is a 0.27.0 capture, so the #1126 diagnostics have recorded nothing live yet, and recording consent for this release needs the person-approved temporary copy of `scripts/check-docs.mjs` into that capture (#1120 is fixed by this release, so captures of 0.29.0 and later carry the checker).
56
+ - No live receipt for a Pi or local worker accepting, editing and finishing write-class work through the hub worktree and commit (#1115, #1129), nor for the sandbox refusals with a real Pi turn (#1123); each is covered by real git and real `sandbox-exec` fixture tests only (`sandbox-exec` tests run on macOS only).
57
+ - #1078: the cause of the live write stalls is still unknown; the diagnostics that should name it ship in this release.
58
+ - #676 (TaskWorktrees.create runs about 14 sync git calls, about 210 ms per sandboxed write-class accept) was deferred: making it async would break the synchronous accept gate (#1080). Measurements are in hub memory.
59
+ - amem 0.20.0 activation on installed hooks and CLIs is the person's step and is not part of this release.
@@ -0,0 +1,54 @@
1
+ # 0.30.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.30.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.30.0, prepared from main 397b1bd7 (20 commits since v0.29.0, 7 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.29.0: #1160, #1159, #1158, #1157, #1156, #1155, #1153, #1150, #1151, #1149, #1152, #1148, #1146, #1142, #1141, #1140, #1138, #1139, #1136.
12
+
13
+ Each merged with `gh pr merge --squash --match-head-commit <head>` after a `hub_review` approval, with exact-head CI green on ubuntu and macOS (19 checks pass, 2 skipped, seeded guards included), no unresolved review thread and no pending change request. Approvals by claude unless named:
14
+
15
+ - #1136 (task 679, agent-mem 0.20.1) at 1346c687; #1138 (680, #1137) at 756d72e5; #1139 (325, #635 T0) at f83ea324; #1141 (681) at a283bf4a; #1151 (683, #1144) at 4930af65; #1152 (682, #264) at e6878d11; #1155 (687, #1154) at 6717f1a1; #1159 (691) at 15d25ae6; #1160 (692) at ea3ee6ab.
16
+ - After one changes_requested round each: #1153 (686, #1147) at fd8a3286; #1156 (688, #1057 T1-T2) at 58afb35b; #1157 (689, #1055) at bd79846b; #1158 (690, #1057 T3) at a56ff7f6.
17
+ - Approved by the person, since claude owned or the hub named the person as reviewer: #1140 (443) at 4b652581, #1142 (311, design) at 0f0e805c, #1149 (685, dogfood receipt) at aa20ae95.
18
+ - Task 684 shipped three PRs under one approval bound at #1146's head d303a050. #1148 and #1150 were read in that review at earlier heads (f281e23b, 3f19f9fc), then rebased for conflicts and merged at 26766aaa and 3da9d6d6 after their own exact-head CI. The commits added after the read are a one-line receipt wording fix (26766aaa, `docs/verification/2026-11-03-pi-150-compat.md`) and a regenerated bundle hunk (3da9d6d6, `plugins/agent-hub/server.js`, 3 lines); every other commit is the reviewed change rebased. These two are the only merges bound to an approval at an earlier head.
19
+
20
+ ## Native acceptance receipts
21
+
22
+ Receipts added under `docs/verification/` since v0.29.0: `docs/verification/2026-11-03-agy-1.4.12-launcher-nonce.md`, `docs/verification/2026-11-03-agy-1.4.12.md`, `docs/verification/2026-11-03-agy-tui-close.md`, `docs/verification/2026-11-03-grok-1.0.62-t0.md`, `docs/verification/2026-11-03-pi-150-compat.md`, `docs/verification/2026-11-03-pi-title-api.md`, `docs/verification/2026-11-03-qwen-0265-acp.md`, `docs/verification/2026-11-03-qwen-0265-dual-output.md`, `docs/verification/dogfood/2026-11-03.md`, `docs/verification/pi-150-model-free.ts`, `docs/verification/pi-150-native.mjs`, `docs/verification/pi-150-title-api.mjs`.
23
+
24
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.30.0`, recorded by the person or the unique configured conductor only on the person's explicit release instruction naming the release in its own session (never a hub/task/review/file/peer relay), and is not recorded in this file. The tag run refuses without it.
25
+
26
+ ## Document review
27
+
28
+ `node scripts/check-docs.mjs --require-fresh` reported 14 stale documents. Each was read against `git log 0d0a362c..HEAD -- <its paths>` (`src/cli/help.ts` against its own stamp 3efa678d), the fixes went in as 8612cfd7, and all 14 were restamped at 8612cfd7:
29
+
30
+ - `README.md`: the Operations list names the worktree shelf; the report link points at this file.
31
+ - `docs/quickstart.md`: install line `#v0.30.0`, an "Upgrading to 0.30.0" section, the agent-mem pin 0.20.1 in two places, and an `ahub worktrees` row in the working-day table.
32
+ - `docs/operations.md`: 0.30.0 in the header and the protocol table (protocol unchanged, 16 through 17), agent-mem 0.20.1 in two places that still said 0.18.0, agy 1.4.12 verified with `AGY_CLI_DISABLE_AUTO_UPDATE=1`, a Settings row for `worktrees.quiet_days` and `worktrees.alert_over`, the PII row and `--no-optional-locks` notes in the shelf section, and an Office phase 5 paragraph.
33
+ - `docs/security.md`: agy 1.4.12 receipt, and a worktree shelf paragraph (removal only in the person's CLI, never forced; console-only overrides and refresh; the refusal list; PII rows).
34
+ - `src/cli/help.ts`: `tidy --confirm <fingerprint> [--json]`, which `main.ts` honours. `ahub worktrees` and `ahub title` were already listed and match.
35
+ - `docs/agent-notes/adapters.md`: agy 1.4.12, and the session-title writer rule (#1055).
36
+ - `docs/agent-notes/daemon.md`: the conductor frame of an incoming-label DOING row, the label audience and `session-title-inputs.json`, and a worktree shelf rule.
37
+ - `docs/agent-notes/tasks.md`: `hub_set_title` tool placement, and the plan-sources validation rule (#1144).
38
+ - `docs/agent-notes/tests.md`: the #1057 tests that cut `daemon.ts`, `index.html` and `sdlc.ts` by text.
39
+ - No change needed: `docs/agent-notes/README.md`, `benchmarks.md`, `budget.md`, `bus.md`, `local-worker.md` (each read; the commits in range do not change what they state).
40
+
41
+ ## Checks
42
+
43
+ - `node scripts/check-docs.mjs --require-fresh`: `docs: OK (0 stale documents)` after the restamp at 8612cfd7.
44
+ - Full `scripts/check.sh` on dgx02 (`remote_dev.py verify`) at b39870aa (tree 4fef8b16): typecheck (`bun x tsc --noEmit`), the bundle check (`bun scripts/build.mjs --check`), `package: OK (770 files, @staix/agent-hub@0.30.0)`, `roles: OK`, and 11229 pass, 101 skip, 0 fail across 872 files; `check: OK`. Later commits on the release branch change only this report.
45
+ - The release PR's exact-head CI on ubuntu and macOS is the gate for the merge.
46
+
47
+ ## Known gaps, not blocking
48
+
49
+ - No installation, running-hub upgrade to 0.30.0 or live TUI launch was done for this record.
50
+ - Office phase 5's live two-agent, both-theme browser look (#264 AC9) is unverified.
51
+ - #1057: the owner confirms `worktrees.quiet_days` after a week of use; the first real `ahub worktrees tidy --confirm` on this repository has not run.
52
+ - #1055 AC7, a live managed Pi restart with a named session, stays in #1143.
53
+ - agy: `hub_send` and a board call through a real-account TUI (#485 AC3) are unverified. qwen-dual 0.26.5 is not promoted (Q2 fails, identically on the 0.26.1 control).
54
+ - `splitBacklog` stores its parts' plans without `validatePlanSources`; acceptance rechecks the stored plan, so an unknown source is still refused before work starts, but a split child can wait in Backlog holding one. Not a release blocker; filed as #1161.
@@ -0,0 +1,42 @@
1
+ # Antigravity 1.4.12 actual-hub nonce leg through the `ahub agy` launcher (#888)
2
+
3
+ Date: 2026-11-03. Task #684, Refs #888. The [1.4.12 receipt of task #681](2026-11-03-agy-1.4.12.md) passed the CLI surface, the synthetic plugin MCP and hook contract and the normal TUI close, and failed the actual-hub nonce leg. This receipt re-runs only that leg, and drives it the way the person's launcher does.
4
+
5
+ **Decision: add 1.4.12 to agy `verified`.** Every account-free leg now has a pass on the same binary: the three legs from the #681 receipt, plus the nonce leg below. The accepted 1.4.x line is unchanged.
6
+
7
+ ## Why the #681 nonce leg failed, and what changed
8
+
9
+ The #681 probe wrote its own plugin files and ran the bundle itself. It never asked the daemon for a launch slot (`native_hook_launch begin`), so the bundle's hub-tools socket was not an owned agy launch and joined as a distinct `agy~...` session. That session is not the project's agy and rightly got no preface. This is a defect of that producer, not an agy 1.4.12 regression. Here `ahub --live-test --project <scratch> agy` writes the managed plugin and holds the slot, as the person's launch does.
10
+
11
+ ## Environment and isolation
12
+
13
+ - Native: the installed `~/.local/bin/agy`, `--version` `1.4.12`. SHA-256 `34423fc9b0af3841e7646aa72a2d40ffeab8218b22d1cb99eac7355f9eba31e3` before and after the run; the directory held the same two entries (`agy`, `agy.<n>.old`) before and after. The binary was not touched, and `AGY_CLI_DISABLE_AUTO_UPDATE=1` was set on every agy invocation.
14
+ - Launcher: this repository at `9eb10e34e8fa3624669d08828ac0ca65b8ec2041` (agent-hub 0.29.0, agent-mem 0.20.1), `src/cli/main.js --live-test --project <scratch> agy`, Bun 1.4.3, macOS 27.0.1 arm64. The committed plugin bundle was the one the daemon reported.
15
+ - Scratch daemon: started in the probe process with `startDaemon` on a loopback control port, in a disposable Git project under the per-user temporary directory (outside the person's home). Memory and native capture, inference, snapshots, MLX, design specs and Pi auto-start were off; `agy` was on the roster. The nonce `T0_LAUNCHER_NONCE_<uuid>` was held only in the scratch bus preface (`bus.preface("agy", ...)`).
16
+ - Every process had `HOME`, `AGENTHUB_HOME`, `AMEM_HOME`, `AGENTHUB_STATE_DIR`, `AGENTHUB_PROJECT_DIR`, `TMPDIR` and `XDG_CONFIG_HOME` set to scratch paths before any hub module loaded. The shell's live-hub locations were never used and the live hub was never contacted. The inherited agent marker stayed in place, as the live-test procedure requires.
17
+ - **Seatbelt:** agy itself ran under the `profile()` from `src/local/sandbox.ts` plus an explicit read/write deny of the person's home, through a `PATH` shim that `exec`s `sandbox-exec -f <profile> agy`. The launcher (`ahub agy`) and `bun` ran outside it. Checks: under the profile `cat` of the real `~/.gemini/config/config.json` exited 1 with `Operation not permitted`, and `/usr/bin/true` exited 0. The provider was a loopback fixture (`GEMINI_API_KEY=fixture-key`, `GOOGLE_GEMINI_BASE_URL` on loopback). No account and no cloud model call.
18
+ - The five real Gemini config entries (`antigravity-cli/settings.json`, `config/config.json`, `config/mcp_config.json`, `config/import_manifest.json` (absent), legacy `settings.json`) had the same SHA-256 or absence before and after.
19
+ - The daemon stopped, the fixture server stopped, no agy or scratch process remained. The original probe copies were not edited; the new probe lives in the local scratch workspace only.
20
+
21
+ ## Result
22
+
23
+ | Observation | Value |
24
+ | --- | --- |
25
+ | Launcher exit | 0, after `/exit` and Enter; agy closed by itself and printed its resume line |
26
+ | Managed plugin while the TUI ran | `hooks.json`, `mcp_config.json`, `plugin.json` |
27
+ | Managed plugin after the close | empty: all three removed |
28
+ | Peers on the scratch bus | exactly `agy` (state `idle`); no `agy~...` session |
29
+ | Provider requests | 4; tool declarations 0, 12, 15, 15 (`call_mcp_tool` from the second agent request, the lazy attach of 1.4.7 and 1.4.3) |
30
+ | MCP call | one `call_mcp_tool` for `agent-hub_agent-hub/hub_inbox`; the TUI printed `Called 1 MCP tool (agent-hub_agent-hub/hub_inbox)` |
31
+ | Hub log | `agy read 1 queued message(s) through hub_inbox` (the preface), then `agy -> idle`, later `agy -> offline` |
32
+ | **Nonce readback** | **`nonceInModelRequest: true`**: the nonce from the scratch bus preface appeared in a provider request after the tool call |
33
+
34
+ ## Not verified
35
+
36
+ - A real account, a real model's tool choices and agy's own permission prompts (the provider is a fixture).
37
+ - `hub_send` and board calls through the TUI, the IDE form and Linux.
38
+ - Seatbelt around the launcher itself: only agy ran under it.
39
+
40
+ ## Promotion
41
+
42
+ `src/adapters/compat.ts` adds `1.4.12` to agy `verified` and points `receipt` at this file; `test/agy-compat-receipt.test.ts` and `test/compat.test.ts` pin both. Other 1.4.x patches stay in-line and unverified; 1.5.0 stays refused.