@staix/agent-hub 0.12.30 → 0.12.32

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 (102) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +29 -9
  3. package/docs/agent-notes/adapters.md +9 -5
  4. package/docs/agent-notes/budget.md +1 -1
  5. package/docs/agent-notes/bus.md +5 -0
  6. package/docs/agent-notes/daemon.md +14 -8
  7. package/docs/agent-notes/local-worker.md +3 -1
  8. package/docs/agent-notes/tasks.md +7 -1
  9. package/docs/agent-notes/tests.md +6 -2
  10. package/docs/cooperbench.md +1 -1
  11. package/docs/gsd-collaboration-analysis.md +1 -1
  12. package/docs/memory.md +2 -2
  13. package/docs/operations.md +236 -37
  14. package/docs/quickstart.md +24 -5
  15. package/docs/security.md +42 -10
  16. package/docs/smoke.md +34 -0
  17. package/docs/specs/2026-09-19-agent-hub-design.md +241 -3
  18. package/docs/specs/2026-09-20-upgrade-recovery-design.md +275 -10
  19. package/docs/verification/2026-10-17-0.12.31.md +71 -0
  20. package/docs/verification/2026-10-17-418-probes.md +21 -0
  21. package/docs/verification/2026-10-18-0.12.32.md +54 -0
  22. package/docs/verified.json +20 -15
  23. package/package.json +2 -2
  24. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  25. package/plugins/agent-hub/server.js +1361 -44
  26. package/src/adapters/acp.ts +5 -6
  27. package/src/adapters/claude-channel.ts +29 -11
  28. package/src/adapters/definitions/builtin-metadata.ts +121 -0
  29. package/src/adapters/definitions/kimi-native.ts +4 -0
  30. package/src/adapters/definitions/qwen-native.ts +24 -0
  31. package/src/adapters/definitions/qwen.ts +71 -0
  32. package/src/adapters/local-worker.ts +2 -2
  33. package/src/adapters/pi.ts +11 -2
  34. package/src/adapters/registry-types.ts +174 -0
  35. package/src/adapters/registry.ts +36 -0
  36. package/src/cli/attention.ts +5 -1
  37. package/src/cli/console-state.ts +130 -17
  38. package/src/cli/console.ts +54 -8
  39. package/src/cli/help.ts +3 -1
  40. package/src/cli/identity.ts +16 -5
  41. package/src/cli/jcode-mcp.ts +1 -1
  42. package/src/cli/launch.ts +1 -6
  43. package/src/cli/main.ts +208 -40
  44. package/src/cli/output.ts +15 -2
  45. package/src/cli/preview.ts +8 -5
  46. package/src/cli/recovery-guidance.ts +137 -0
  47. package/src/cli/recovery-presentation-options.ts +8 -0
  48. package/src/cli/recovery-session-file.ts +18 -0
  49. package/src/cli/terminal-recovery.ts +48 -0
  50. package/src/cli/upgrade-interactive.ts +129 -74
  51. package/src/cli/upgrade-labels.ts +30 -0
  52. package/src/cli/upgrade-presentation-data.ts +41 -0
  53. package/src/cli/upgrade-presentation-flags.ts +31 -0
  54. package/src/cli/upgrade-presentation.ts +217 -0
  55. package/src/cli/upgrade-restore-state.ts +137 -0
  56. package/src/cli/upgrade-runtime.ts +430 -175
  57. package/src/cli/upgrade.ts +325 -49
  58. package/src/hub/board.ts +82 -24
  59. package/src/hub/bus.ts +89 -29
  60. package/src/hub/check-runner.ts +514 -0
  61. package/src/hub/child-process.ts +1 -0
  62. package/src/hub/cohorts.ts +2 -2
  63. package/src/hub/config-apply.ts +242 -0
  64. package/src/hub/config-reload.ts +120 -0
  65. package/src/hub/config-trust.ts +11 -2
  66. package/src/hub/control-client.ts +5 -1
  67. package/src/hub/crash.ts +8 -6
  68. package/src/hub/daemon.ts +931 -209
  69. package/src/hub/delivery-hints.ts +30 -0
  70. package/src/hub/delivery-journal.ts +185 -12
  71. package/src/hub/delivery-superseded.ts +52 -0
  72. package/src/hub/envelope.ts +1 -1
  73. package/src/hub/events.ts +2 -2
  74. package/src/hub/host-executable.ts +11 -0
  75. package/src/hub/hub-tools.ts +9 -1
  76. package/src/hub/manager.ts +1 -1
  77. package/src/hub/peer-catalog.ts +13 -13
  78. package/src/hub/peers.ts +12 -2
  79. package/src/hub/permission-mode.ts +5 -3
  80. package/src/hub/recovery-continuity.ts +152 -0
  81. package/src/hub/recovery-store.ts +10 -4
  82. package/src/hub/routing.ts +35 -16
  83. package/src/hub/sdlc.ts +5 -1
  84. package/src/hub/settings.ts +52 -37
  85. package/src/hub/settled-roles.ts +35 -0
  86. package/src/hub/spec-read.ts +120 -0
  87. package/src/hub/supervision.ts +24 -8
  88. package/src/hub/task-sweep.ts +7 -6
  89. package/src/hub/tasks.ts +199 -40
  90. package/src/hub/ui.ts +1 -1
  91. package/src/memory/brief.ts +6 -5
  92. package/src/memory/capture.ts +2 -1
  93. package/src/pi/session.ts +23 -4
  94. package/src/ui/index.html +35 -16
  95. package/src/ui/needs.ts +7 -5
  96. package/src/ui/office-model.ts +4 -1
  97. package/src/ui/task-board.ts +1 -1
  98. package/src/ui/task-progress.ts +6 -2
  99. package/templates/AGENTS.block.md +1 -1
  100. package/templates/profiles/general.json +1 -1
  101. package/templates/profiles/research.json +1 -1
  102. package/templates/profiles/writing.json +1 -1
package/docs/security.md CHANGED
@@ -11,7 +11,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
11
11
  - **Telemetry holds no bodies.** `.agenthub/state/events.jsonl` (issue #40) records envelope ids, routing and sizes, task ids and states, overlapping paths and token counts, and the task id (with a PII flag) that each usage and token record is attributed to. It never records a message body, a task title or detail, and marks private (PII) envelopes and tasks as such. It stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
12
12
  - **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
13
13
  - **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
14
- - **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.
14
+ - **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.
15
15
  - **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 attempts shared-memory storage when enabled. 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 cloud memory. Its text is never broadcast, and context pressure does not pause, reassign or replace the session.
16
16
  - **The local worker is boxed in.** Paths are resolved through symlinks and must stay inside the project; 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` or `hooks` directly under one nor below the discovered modules root of the project's own `.git` (a submodule's git directory, nested submodules included; every term is anchored below that root, so a directory named `refs`, `config` or `hooks` above the project neither disables the rule nor trips it; a `/refs/` or `/logs/` segment below it keeps refs named `config` or `hooks` writable, and leaves a submodule whose own path contains such a segment uncovered; a submodule whose path has `hooks` as a whole component (`tools/hooks` included) fares worse: the rule matches its whole git directory, so every write there is refused; the discovered git directories, the modules root 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`/`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 cost is that `git init`, `git clone` and `git worktree add` do not run inside the sandbox (add, commit, stash, checkout and gc still do), `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.
17
17
  - **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.
@@ -23,7 +23,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
23
23
 
24
24
  ## Native memory authority and profile selection
25
25
 
26
- The portable core is the exact-pinned first-party `@staix/agent-mem` 0.3.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.
26
+ The portable core is the exact-pinned first-party `@staix/agent-mem` 0.3.1 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.
27
27
 
28
28
  `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.
29
29
 
@@ -31,7 +31,7 @@ The portable core is the exact-pinned first-party `@staix/agent-mem` 0.3.0 packa
31
31
 
32
32
  The hub binds the selected root/profile at open. Invalid/missing generation metadata refuses native memory without a shared-v1 fallback; the bus and board continue. Explicit checks at hub operation entry and before sidecar writes stop later stale calls, but are not atomic against a selector change during an in-flight core operation. Migration therefore requires quiescing consumers and closing handles before changing selection, then reopening through the supported lifecycle. A public core pre-write hook is the upgrade path for atomic in-flight cutover.
33
33
 
34
- Before migrating any real home, every hub sharing it must first be on 0.12.30 with positive authenticated binding or unbound/quiesced proof from `ahub memory profile-check` in each relevant registry. An unknown or unreadable runtime is not proof of absence, and a matching ready binding still holds a handle that must be closed. Profile-check reports only its named registry inventory. Root paths, selector receipts and malformed-legacy counts are person-local diagnostics, excluded from ordinary peer status and allowed agent CLI reads. Source/fixture checks do not establish native capture, real migration or account/device-sync success.
34
+ Before migrating any real home, every hub sharing it must first be on 0.12.30 or later with positive authenticated binding or unbound/quiesced proof from `ahub memory profile-check` in each relevant registry. An unknown or unreadable runtime is not proof of absence, and a matching ready binding still holds a handle that must be closed. Profile-check reports only its named registry inventory. Root paths, selector receipts and malformed-legacy counts are person-local diagnostics, excluded from ordinary peer status and allowed agent CLI reads. Source/fixture checks do not establish native capture, real migration or account/device-sync success.
35
35
 
36
36
  ## What is not defended
37
37
 
@@ -82,7 +82,7 @@ Inside an agent session, `ahub` identifies the caller from `AGENTHUB_PEER_ID`,
82
82
  or the native Claude/Codex shell marker when the hub marker is absent. It connects
83
83
  as that peer in tools mode. Messages and task changes retain that actor;
84
84
  `say` defaults to status priority and important messages still require the peer's
85
- capability. Conflicting or malformed markers fail closed. There is no `--as-user`.
85
+ capability. Conflicting or malformed markers fail closed. Refusals name only the marker variable names actually present, never their values. There is no `--as-user`.
86
86
  By default, console operations are refused before connecting, including permission answers
87
87
  and permission modes, queue resolution, budget overrides, benchmark runs, research
88
88
  backfill and task labels, stopping a peer (`ahub stop`), lifecycle and recovery
@@ -98,8 +98,11 @@ if that audit cannot be written, the command refuses. The current daemon emits
98
98
  `agent_cli` with `delegated: true` and `refused: false`. Allowed-class commands retain the peer identity and capability checks, including
99
99
  messages, proposals and reviews. Peer starts, pause and resume may delegate
100
100
  operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
101
- person-authored planning/release/memory/goal writes, SDLC record, benchmark run
102
- and the pet composer are refused. No argument text is copied into this audit.
101
+ person-authored planning/release/memory/goal writes, SDLC record, benchmark run,
102
+ `ahub recovery reattach`, a `--fresh-session` dispose that would replace a repaired
103
+ session's conversation (or whose scope cannot be read), and the pet
104
+ composer are refused; presentation flags such as `--color` or `--verbose` before
105
+ the recovery action do not hide it from this check. No argument text is copied into this audit.
103
106
  Delegated dashboard restrictions are bound to its server-issued ticket/session:
104
107
  operational settings/start/stop controls remain, but message, proposal and
105
108
  workflow-history actions refuse even if the body tries to clear the restriction.
@@ -150,16 +153,17 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
150
153
  - 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.
151
154
  - 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.
152
155
  - The browser receives a bounded redacted event stream, peer states and queue counts, public task views, budget windows, 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, 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.
153
- - The closed action list includes permission response, peer pause/resume, console message, task proposal/assignment and the settings actions below (set, undo, preview). 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.
156
+ - The closed action list includes permission response, peer pause/resume, console message, task proposal/assignment and the settings actions below (set, undo, preview, reload). 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.
154
157
  - The listener stops with the daemon. Expired sessions must be reopened with `ahub ui`; the page does not silently obtain new credentials.
155
158
 
156
159
  ## Dashboard settings and the settings session (issue #269)
157
160
 
158
161
  - Settings are a closed registry (`src/hub/settings.ts`). The dashboard and `ahub settings` can write only its keys, each checked against its own closed values, and only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`. A file git tracks is never written, so the shared `config.json` and `routing.toml` stay byte-identical; in a repository where git cannot answer, nothing is written either. Commands and binaries (`kimi_cmd`, `codex_bin`, `pi.cmd`, `checks`, `mlx`), credential files and gateway URLs (`omniroute`, `memory.worker_url`), the local worker's reach (`local.read_allow`, `local.bash_network`, `local.network_allow`), `bench.enabled` and `terminal` are refused by name from every surface: they choose what the hub runs or where secrets and task text go.
159
162
  - `routing.local.toml` holds `stay_switch` and, per class, `peers`, `escalate_to`, `route` (an id `routing.toml` defines) and `pi_backend`, and nothing else: targets, routes, signals and the PII constraint cannot be set through it. A write is read by the hub's own loader before it replaces the file (temp file and rename), and one previous version is kept for undo.
160
- - An ordinary dashboard session (`ahub ui`) reads settings, lowers a mode to `ask`, turns `pi.auto_start` off and changes the safe switches (`research.enabled`, `approvals.notify`, `snapshots.enabled`, `coordination`). Whatever can widen what agents do without asking needs a settings session: a non-ask mode now or at hub start, routing, starting Pi with the hub, removing a machine-local value, and undo. `ahub ui --settings` opens one: its ticket marks the session for 15 minutes, after which it is an ordinary session. `never-ask` also needs the peer's id typed as the confirmation, and a stored never-ask default still waits for the console's `y` at each hub start. An undo never puts never-ask back, and removing a machine-local value that uncovers a never-ask in the shared file needs the same confirmation. "Lowering to `ask`" means handing the decision back to the agent's own rules: for Codex that is the approval policy the person configured natively, which the hub does not judge.
163
+ - An ordinary dashboard session (`ahub ui`) reads settings, lowers a mode to `ask`, turns `pi.auto_start`, `task_sweep.enabled` or `task_sweep.auto_reassign` off, sets a start mode back to `tui` or a WIP limit to 0, and changes the safe switches (`research.enabled`, `approvals.notify`, `snapshots.enabled`, `coordination`). Whatever can widen what agents do without asking needs a settings session: a non-ask mode now or at hub start, routing, starting Pi with the hub, removing a machine-local value, and undo. `ahub ui --settings` opens one: its ticket marks the session for 15 minutes, after which it is an ordinary session. `never-ask` also needs the peer's id typed as the confirmation, and a stored never-ask default still waits for the console's `y` at each hub start. An undo never puts never-ask back, and removing a machine-local value that uncovers a never-ask in the shared file needs the same confirmation. "Lowering to `ask`" means handing the decision back to the agent's own rules: for Codex that is the approval policy the person configured natively, which the hub does not judge.
161
164
  - A settings session is as strong as the console for these settings and no stronger elsewhere. A mode change is the console's own request with the same refusals; tool approvals stay deny-only for Pi and local; there is still no shell, file, task-detail or launch-command API. `ahub ui --settings` and `ahub settings` pass the same identity gate as `ahub permission`, which is an operating policy, not authentication. The hostile-agent boundary above applies unchanged: an agent with an unrestricted shell as the same user can impersonate the console, and so can open a settings session.
162
165
  - Every change writes a `settings` event (the key, the value before and after, the source `dashboard` or `terminal`) and a line in `hub.log` and open consoles; opening a settings session is logged too. The unified dashboard (`ahub ui --all`) forwards settings actions as an ordinary session and opens no settings session.
166
+ - Configuration reload (issue #439). The daemon watches `config.json`, `config.local.json`, `routing.toml` and `routing.local.toml` and re-reads the complete candidate after a change, as the dashboard's Reload configuration and `ahub settings reload` do; an invalid candidate applies nothing. Each changed key is then authorized on its own against its file's actual git provenance. A privacy tightening (local-only PII, the model screen on, added PII patterns, narrower roles or capability lists, unless it moves the planner or conductor) and a safe registered setting apply from any file. Every other change, machine-local fields included, keeps its running value when its file is tracked or git cannot answer. From an untracked file such a guarded change is adopted only by `ahub settings reload` in an interactive terminal, after it lists each guarded key with its full before and after values and the person types `y` within 30 seconds; the confirmation is bound to the file contents and accepted state it was shown for. File watches and dashboard reloads, a settings session's included, never confirm, and no reload changes the roster: membership moves only through the roster setting. Keys whose consumers are bound at start (memory, the gateway and MLX, checks, the privacy and sandbox policy of existing sessions, the terminal provider and others) stay pending until a restart.
163
167
  - Start modes. A start the hub makes itself (the dashboard's Start control, `peer_start` from a console child, the conductor's `hub_peer_start`, `pi.auto_start`) takes the peer's start mode. Through settings only a settings session or the terminal can set it to headless; a config file can also set `peers.<id>.start_mode`, the shared one included (it is not a machine-local field: headless was the only form for Pi, Kimi and the local worker before this change, a hub-made headless Codex start is the app-server the hub already ran for it, and a start mode grants nothing a permission mode or a capability controls). For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (and `--mode tui` for Pi): every word is the hub's own, the command line is those words each quoted for a POSIX shell, and no request field, tool argument or agent message is part of it. In a `terminal.open` template `{command}` is that quoted line and `{title}` and `{cwd}` are raw text for argv elements of their own; each placeholder is substituted once, so a project path that itself contains a placeholder, a quote or `$` stays one word. A provider's own error text goes to `hub.log`, not to the caller. The terminal comes from `terminal.open`, a machine-local command template that config trust refuses from a committed file and that no settings surface can write, or from Orca. The opened terminal's environment carries no agent marker, so `ahub` there has the console's identity. Without a provider the start is refused with the command to run by hand; it is never downgraded to headless. A conductor can start a peer but cannot choose its mode.
164
168
 
165
169
  ## Multi-project manager (protocol 7)
@@ -192,7 +196,7 @@ conductor holder is refused. Local and Pi scoped tools cannot write `.agenthub`.
192
196
  Claude, Codex and Kimi still follow their own file permissions and can edit a
193
197
  config file; such an edit changes participation only at the next hub start. A
194
198
  live settings write or undo applies its own roster key and does not adopt other
195
- file edits. External ids outside the known native catalog keep their prior
199
+ file edits, and a configuration reload refuses roster changes. External ids outside the known native catalog keep their prior
196
200
  admission rules.
197
201
 
198
202
  Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
@@ -215,7 +219,7 @@ are in [the configuration inventory](verification/2026-10-11-agent-config-invent
215
219
  Instruction files `AGENTS.md`, `CLAUDE.md` and `QWEN.md` remain grantable by default.
216
220
  A person can set `scoped_grants.protect_instructions` to true to require approval
217
221
  for scoped writes and edits through original or resolved spellings. It applies
218
- live after a settings write or undo, never changes reads or never-ask, and cannot
222
+ live after a settings write, undo or reload (turning it off from an edited file needs the confirmed terminal reload), never changes reads or never-ask, and cannot
219
223
  be set through an agent, tools or conductor surface. Claude's daemon response
220
224
  withholds the scoped mode on these writes so an older hook grants nothing too.
221
225
 
@@ -316,3 +320,31 @@ plain terminal pet reads and `pet --palette` remain available to agent shells;
316
320
  the palette write merges only the closed name into `.agenthub/config.local.json`,
317
321
  validated and replaced by temp file and rename. Signed pidfiles
318
322
  fence instance ownership and close; unknown owners are never signalled.
323
+
324
+ ## Planner, cancellation and delivery authority
325
+
326
+ The `planner` setting is person-controlled. A valid pin determines the planner without adding it 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. Planner guidance does not grant conductor authority or bypass roster, PII, WIP or owner capability checks.
327
+
328
+ `cancelled` is closed, never approval. The person can cancel non-approved work with a reason and optional supersession reference; the configured planner can cancel only its own proposed/backlog work with no historical acceptance. 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.
329
+
330
+ 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. A manually started older binary is outside this compatibility guarantee: it can display cancelled rows as open or fail to move them.
331
+
332
+ 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 completion of an uncertain delivery is not evidence that the offer was read.
333
+
334
+ `hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never completion/discard authority or pause removal. 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.
335
+
336
+ ## Bounded task specs and isolated checks (#418)
337
+
338
+ `hub_task_spec` reads only the GitHub issues named by a current owner's or reviewer's task in this project's origin repository, including the validated canonical GitHub intake source when its title/body has no issue token. Bare `#N` references count only in the title; detail references require `owner/repo#N` or `https://github.com/owner/repo/issues/N`. Bare detail numbers denote hub tasks, and arbitrary file paths are not parsed as issue references. A qualified reference cannot begin after a slash or a name character, so `src/hub/tasks.ts#12` is a file path. Routing uses this same parser. Without a GitHub project identity, bare title numbers do not require spec access, but explicit qualified/URL references, `refs.repo` and imported GitHub identity still fail closed. The daemon resolves authenticated and container executables outside the project, pins their absolute paths and uses fixed arguments; repository-writable PATH shims are refused. Runner Git resolves its host executable through a restricted PATH. For specs, the model receives no credential or arbitrary authenticated URL capability. Foreign/transferred issues, more than three references, bodies over 64 KiB and aggregate results over 80 KiB fail explicitly. Spec fetches share a 25-second budget inside the control client's 30-second cap; each gh call remains bounded to 15 seconds. Current PII, secret and private-path policy withholds the complete spec rather than returning a partial contract. PII turns and PII tasks refuse these tools. The independent `spec_read` and `check_run` capabilities apply when a peer has a narrowed capability list. Arguments and results are excluded from capture. Proven deterministic pre-effect guards return PreEffectToolRefusal so Pi settles the exact refusal receipt; generic transport/storage failures and failures after durable receipt/output changes remain uncertain, never reclassified from error text.
339
+
340
+ `hub_check_start` accepts only a current owner's in-progress task, its recorded branch/commit and either `typecheck` or one to 32 existing `test/*.test.ts` paths. `checks.runner = "container"` is a person opt-in in an untracked config file; a tracked config cannot grant it. Missing service/image, an unverified internal network, stale task/owner/native generation, unavailable dependencies or an invalid path fails closed. There is no host-shell, network or Seatbelt fallback.
341
+
342
+ The runner uses Apple `container`, pinned Bun 1.4.2 and a verified `hostOnly` internal network with DNS disabled, two CPUs and 4 GiB. It starts a named, labeled VM with a bounded idle process, copies a hub-owned snapshot into the running VM, then invokes a fixed `container exec` check. This run/copy/exec sequence is required because Apple `container copy` rejects a stopped container. No host directory, home, credential directory, hub state or socket is mounted. Source blobs are read from the exact Git commit; symlinks and gitlinks anywhere in the source refuse the run before exclusions. Directory prefixes and filenames are checked for NFC/Unicode case-fold collisions before staging writes, including denied entries; folding must reach a fixed point within a bounded number of passes or the run refuses. This is a conservative collision screen, not a claim of exact parity with every host filesystem. Denied and PII paths are excluded and counted. Dependency files require an equal lockfile, bounded regular files/directories and internal relative symlinks. No dependency installation or Internet access occurs in the VM.
343
+
344
+ Dependency copies detect observed inode, size and timestamp changes and refuse final-component symlinks using `O_NOFOLLOW`. A same-user writer can still exchange and restore a parent directory between observations; this is not an atomic immutable-dependency guarantee. Source commits remain immutable. Do not treat a dependency copy as protection against a hostile process running as the same host user.
345
+
346
+ Checks serialize through the repository's existing `ahub-heavy.lock`, with one active and at most two queued runs, a ten-minute execution ceiling after heavy-slot admission and a bounded heavy-slot wait. Container startup has a 120-second cap for cold VM boot, and copy has a 180-second cap for the bounded 512 MiB source plus 1 GiB dependency corpus. Each stage is clamped to the remaining ten-minute budget and uses the same cancellation signal; exec receives only the remaining budget. Network/probe commands retain a 15-second cap, also clamped and cancellable during an admitted run. Named-container cleanup has its separate bounded teardown commands and must still be confirmed before releasing the heavy slot. Queued cancellation settles its receipt and releases queue capacity immediately; active cancellation, timeout and shutdown stop only the named check container. Cleanup is confirmed through inventory before releasing the heavy slot; unknown cleanup keeps the slot and reports an error. Startup removes only this project's labeled containers whose signed recorded hub owner is confirmed gone. Unknown owners are reported for the person, never killed.
347
+
348
+ Only an unchanged task/owner/native-generation binding receives an agent result. Stale receipts are person-only. Public receipts include the exact source/tree/lockfile, runner/platform, command, exit and bounded output; they omit internal host paths, owner process identity and generation binding. The output tail is screened again before any tool response or hub envelope. For inactive valid receipts, local full output is bounded to 1 MiB and removed when the owner reads its result (person inspection does not consume it), after 24 hours or beyond the newest 20 outputs; at most 200 inactive valid receipts remain, with active runs excluded from retention. Unknown receipts are an explicit retention exception: malformed/unreadable receipt bytes, full output and source staging remain until ownership/inactivity can be established or the receipt is removed. Their ID-only warnings remain visible. Unknown payload maintenance performs no path-based deletion: a same-user process can swap a parent directory after an entry check, and no atomic descriptor-relative cleanup is available at this boundary. Maintenance never removes an unknown container. Removing the whole receipt directory counts as an empty record set, clears its ID warnings and allows ordinary expired unindexed-staging cleanup. Linux VM evidence is separate from macOS Seatbelt and managed-native acceptance.
349
+
350
+ Routing receives capability availability and its reason as pure inputs. Capability admission applies to task owners only: non-PII implement/test/bulk-edit owners require tests, and non-PII issue-backed owners in every class (including review-class owners) require spec access. Designated reviewers are not gated by these owner-only capabilities; roster, privacy, role and other eligibility rules still apply. PII tasks are exempt from these two capability requirements and retain the existing local-only owner and person reviewer rules; spec/check tools still refuse them. Explicit nominations and reservations cannot bypass these checks. Inactive sandboxed peers establish their GitHub probe at first spec use, and every explicit local/Pi start path refreshes it (including a prior authentication failure) with concurrent probes shared; host-provided embeddings can inject bounded probe implementations without implicitly contacting real accounts in fixtures. Neither path grants a model control over the probes.
package/docs/smoke.md CHANGED
@@ -1901,3 +1901,37 @@ The current source fixture at 6d311a51dccd41d3703079175cac65a487189b15 used the
1901
1901
  This supersedes earlier phone-fixture pending statements only for this source. The old 1bbced04 captures remain historical; no later byte-identity premise is used. Native planner/two-release, accounts, model ingestion and measured effects remain unverified.
1902
1902
 
1903
1903
  The later bounded release-audit preview text is verified by DOM tests, not by a refreshed phone capture. The6d311a51 PNGs remain evidence for their pinned source; see the later-audit note in the phone verification report.
1904
+
1905
+
1906
+ ## Settings reload and console fixture (#439, 2026-10-17)
1907
+
1908
+ Native real-terminal smoke is **UNVERIFIED**. The admitted remote install/test
1909
+ attempt exited 75 before running because Python pid 6672 (parent 6671) had no
1910
+ verified repository owner. No retry, process kill, user-hub settings change,
1911
+ installation or restart was performed. A scripted ConsoleTerminal is fixture
1912
+ evidence, not a native PTY or account result.
1913
+
1914
+ The implementation fixtures cover the following in disposable projects and temp
1915
+ memory homes:
1916
+
1917
+ - Console Settings tab, daemon preview, bounded reason, explicit y, undo, stale
1918
+ preview fencing and pasted-y refusal.
1919
+ - Dashboard reload/context, running/file values, pending restart reasons and
1920
+ stable drafts; older snapshots remain readable.
1921
+ - Real fixture daemon: live switches/WIP and undo, retained pause/queue/instance,
1922
+ actual sweep timer cancellation/rescheduling, atomic rename watcher, snapshot
1923
+ capture on a later turn and pending memory restart without opening a consumer.
1924
+ - Whole-candidate JSON/TOML/unknown-key refusal, one error notice, per-key retention
1925
+ of valid unauthorized edits, automatic privacy tightening, scoped write/undo,
1926
+ fingerprint-bound guarded reload confirmation and agent-role request refusals.
1927
+
1928
+ To complete the native leg, use a disposable project from a person terminal (or
1929
+ an explicitly authorized per-invocation delegation under docs/security.md).
1930
+ Open `ahub console`, select Settings with 6, edit a safe switch, inspect its
1931
+ preview, enter a reason, confirm y and verify running/file values. Preview and
1932
+ confirm undo; verify the restored value through `ahub settings get`. Paste y at
1933
+ confirmation and verify no write. Change a sweep interval and confirm the same
1934
+ hub instance stays running with its queue and pauses intact. Record the source
1935
+ head, terminal and daemon readbacks separately before claiming AC5 native smoke.
1936
+
1937
+ For the guarded CLI leg, edit a disposable untracked WIP limit, run `ahub settings reload`, inspect its complete before -> after preview, decline and verify the running limit is retained. Repeat and confirm y, then verify the same instance and queue/pause state. Record this separately from the scripted CLI fixture; the token proves freshness within console authority, and the CLI supplies the person-presence checks.
@@ -1287,7 +1287,9 @@ session modes (`default`, `plan`, `auto`, `yolo`) but nothing per server or tool
1287
1287
  ## Reliability follow-ups, 0.12.1 (#89-#95)
1288
1288
 
1289
1289
  - Delivery retries exhausted for an owned open task trigger escalation through `Tasks`, with a redacted reason and a console notice. Delivery health skips peers after three consecutive exhausted deliveries until a completed delivery clears the streak.
1290
- - `needs_review` continues to hold a peer's queue. Console notices, status and assignment explanations identify the hold and operator commands; moved or approved task references are context for the operator, never automatic discard authorization.
1290
+ - `needs_review` holds uncertain work that still matters. The hub settles a row `completed` only when every original is a hub presence item or a hub task/review notice provably moot for its recipient: an approved or cancelled task (except its cancellation stop notice), a review no longer in review for that reviewer, or a notice whose authoritative `refs.taskRecipient` audience (`owner` or `reviewer`) is no longer current. Hub producers tag current-owner notices, reviewer reminders and planner coordination separately; former-owner stop notices and former-owner/former-reviewer withdrawal notices use untagged kind `task` and stay held until task approval or cancellation, because their purpose is to tell the former role to stop. Task-kind reviewer reminders remain live while the recipient is that in-review reviewer. Planner notices and untagged legacy task-kind notices stay held until approval or cancellation: without an audience marker, sender/kind/task id cannot prove that an owner assignment moved. This conservative legacy ceiling preserves still-live coordination and guidance. Unknown tasks, live recipient notices and mixed peer/user rows remain held. Rows whose recorded reason starts with `requested stop:` are never completed automatically, even when their tasks become approved. `supersededDeliveryReason` alone authorizes completion; a separate pure `deliveryTaskContext` retains ids-only approved/moved task context for mixed rows without authorizing settlement. Status, console notices and console/peer/tool route explanations list every hold once, including sender, kind and age; public explanations screen appended hold lines through the current privacy policy and keep task context ids-only. Classify on entry, after restore, after the daemon releases its recovery hold and on board changes; use the existing resolve path with a recorded hub actor, preserving pauses and Tasks state. Hub completion restores presence originals through the same preface merge and transaction rollback path as retry, preserving shared notes for the next queued delivery; person completion remains consumed (#434).
1291
+ - A Claude session without channel support returns `failed_safe` before the push reaches the model, preserving originals for pull. If an older daemon rejects that receipt, the plugin falls back once to its prior generation-bound `needs_review` receipt instead of leaving the row dispatching. A thrown `server.notification` remains `needs_review`: the MCP SDK delegates to a transport whose stdout write can already have emitted bytes before an exception is observed, so the exception alone cannot prove no frame left. Existing proven pre-acceptance guards in Codex, ACP, Pi and the local worker remain unchanged. Console-only command refusals name only the inherited identity marker keys actually present (never their values), explain `!` commands, and direct the person to a separate terminal or `ahub console` (#434).
1292
+ - `hub_delivery_retry { delivery_id }` lets an authenticated recipient retry only its own current `needs_review` row. This option is offered only to transports with an independent tool turn (Claude channel/pull, Codex and Pi). The delivery-only local worker and headless ACP peers cannot start a tool turn through their own hold, so they do not advertise this tool and the daemon refuses a manual call before mutation. Their held hints ask the person to resolve the row; ordinary messages never bypass the hold. Kimi TUI remains pull-only and keeps the recipient option. An ephemeral delivery-only MCP marker controls exposure and hint copy only; the current adapter class is the authority. There is no action or reason input: `completed` and `discard` remain person-only. Any explicit retry in that recipient's lineage, including a person's retry, consumes this allowance; a redelivery held again stays for the person. The existing resolution transaction records `peer:<id>` and a fixed reason, keeps original envelopes unchanged and requeues them through the existing put-back path. A busy recipient may consent to this queue-only operation, but delivery waits for idle and all pause/recovery and active-delivery fences remain. The rendered clone reserves the existing journal body cap for the warning, clipping only the clone at a Unicode character boundary with an explicit cut marker when needed; originals remain unchanged. Jcode project MCP entries carry the same delivery-only exposure marker as other headless ACP peers, preserving their own configuration and peer identity rules. Final rendered output, after condensation and on pull or steer, starts with the fixed sanitized header `Hub redelivery warning: An earlier attempt of this delivery is uncertain; check for its effects before acting.` Explicit retry history is scoped by recipient and original envelope ids. A separate monotone consumed-proof projection records one proof id and warning provenance per original, leaving resolution audit rows unchanged. Explicit resolution and coalesced handoff inherit that proof atomically for every grouped non-context original; a rejected checkpoint consumes no fresh allowance. Hub presence originals carry mutable recall/shared context whose id survives merging, so their ids never participate in retry proof lookup, inheritance, warnings or unknown marking. A preface shared with earlier work cannot consume an unrelated new message's retry allowance; actual task/chat originals retain their consumed lineage. Safe-failure requeue, withdrawal of an earlier original, terminal-state changes, pruning, restart or older-writer audit replacement cannot renew the allowance. Older attempt-bearing rows backfill this projection on return. Recovery snapshots without history and unmappable pruned pre-feature retry records mark only their affected recipient/original ids unknown: those lineages require a person, while unrelated new messages keep their allowance. Tool results expose held metadata only (total count, ids, senders, kinds and age), at most 32 rows/4 KiB with explicit truncation; no body or task text enters the hint. These hints work independently of channel mode and do not wait behind the held queue (#434).
1291
1293
  - Local attach and idle model/route replacement require a reachable authenticated gateway, served models and successful minimal availability calls. Busy workers refuse replacement; validation failure leaves the current worker intact. Controlled recovery can reconstruct a manually paused local worker without gateway availability, but manual resume validates its restored choice before lifting the pause. Doctor checks the fixed model against the inventory.
1292
1294
  - Reviewer routing appends attached peers holding `roles.reviewer` after the review class's candidates. Without a reviewer, task results and console notices explain direct approval and the skipped candidates.
1293
1295
  - Conflict detection includes `in_review` work. Completed overlapping snapshots that share files generate one concurrent-edit notice per task pair/file, containing both turn ids and attributing the changes to neither peer. Private tasks and sensitive file names remain redacted.
@@ -1321,7 +1323,8 @@ to that socket, and a live accepted journal row. This acknowledgement is not pro
1321
1323
  of a native turn boundary. Failed notifications, disconnects, replaced sessions
1322
1324
  and interrupted daemon instances retain `needs_review`, with no automatic replay.
1323
1325
  A person inspects and resolves uncertain work through the existing revision-fenced
1324
- `ahub queue` commands. Older source protocols 9, 10 and 11 remain authenticated
1326
+ `ahub queue` commands; #434 also permits an addressed peer with an independent tool turn one explicit
1327
+ `hub_delivery_retry`, with the transport, warning and lineage limits above. Older source protocols 9, 10 and 11 remain authenticated
1325
1328
  upgrade sources; ordinary clients must use protocol 12 (13 from 0.12.4, with 12 a source).
1326
1329
 
1327
1330
  ## Amendment: stale overlap notices (issue #106)
@@ -1495,7 +1498,13 @@ Unicode cell widths, fall back below 80x24, and restore terminal state on exit.
1495
1498
  Tabs change with the digits 1 to 5 or with Left and Right, wrapping at both
1496
1499
  ends, with the digit's guards and resets (an arrow under a confirmation dismisses
1497
1500
  it like any key but y, without moving a tab); a read holding nothing but arrow
1498
- sequences is that many key presses, never a paste. An Events row's header is
1501
+ or Home/End sequences is that many key presses, never a paste. Home or `<` selects
1502
+ the first row of a panel and End or `>` the last (Home tops an open view); the
1503
+ Events panel follows new events while its selection is on the last row, keeps a
1504
+ row the person moved to, and opening it or changing its filter selects the newest
1505
+ row (#441). In panels `w` opens the project dashboard (`ahub ui`) and `o` the pet
1506
+ window (`ahub pet --window`) as a person's console command; the `:` line also
1507
+ accepts `ui` and `pet` (#452). An Events row's header is
1499
1508
  tokenized by the stream's own tokenizer on the same tab-flattened text, with the
1500
1509
  event's tone and kind; body lines stay plain, and selection changes only the
1501
1510
  `>` marker. The tail prints a peer's context reading once and repeats it only
@@ -3022,3 +3031,232 @@ One explicit profile binding is used by daemon Tasks, read tools, briefs/recall/
3022
3031
  Candidates/counters remain at the independent root `M/candidates.db` using the package's explicit sidecar home API. They are neither migrated into `G` nor synced. Selector changes fence Tasks candidate producers and retained telemetry callbacks as well as native note operations; asynchronous privacy work rechecks binding before side effects. Standalone generation `amem review` remains unsupported and refuses; this change adds neither candidate acceptance nor dashboard acceptance for generations.
3023
3032
 
3024
3033
  PII authority, complete-row host screening, capture exclusion, byte budgets and fail-open task delivery remain unchanged. Memory/goal domain JSON remains unchanged; `memoryProfile` is additive only in daemon status, profile-check and the decay object. Synthetic integration must show bridged legacy reads, cross-writes through the package CLI and daemon tools, failure without fallback for invalid/missing generation state, and no note/candidate/counter writes for calls begun after a selector change while bus service remains available. Source/fixture evidence does not authorize real installation, migration or session restart. Real rollout requires separately approved deployed versions and matching live root/profile/selector bindings, synthetic canaries and quiesced/closed/reopened handles. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Native capture, account/device sync and real memory receipts remain open until observed.
3034
+
3035
+ ### Bounded sandboxed-peer capabilities (#418)
3036
+
3037
+ The frozen issue #418 contract supplies task-bound `hub_task_spec` and the finite `hub_check_start/result/cancel` tools; memory reads reuse #412 P2. `checks.runner = "container"` remains an untracked, person-owned opt-in. Specs include validated canonical GitHub intake identity as well as bounded textual references and remain complete-or-refused; checks run immutable Git source in a credential-free, internal-network Linux VM with fixed commands and binding-scoped receipts. Default sandbox permissions remain unchanged.
3038
+
3039
+ Apple container 1.5.0 requires a running VM for `copy`. The implementation therefore uses `run --detach --rm` with the fixed bounded idle process, copies the validated source/dependencies, executes only the selected finite check argv and confirms cleanup. A create/copy/start probe failed on the stopped-container copy and left no container; the run/copy/exec probe passed. `--internal` is verified as network configuration `mode: hostOnly`, not an inferred network name. Inactive sandboxed peers establish the cached spec authentication probe at first start/use rather than making unrelated host-embedding fixtures contact real accounts. Source and dependency staging ceilings are 100,000 entries each, 512 MiB source and 1 GiB dependencies; exceeding one refuses the check.
3040
+
3041
+ Without a GitHub project identity, bare title issue numbers require no spec capability; qualified GitHub references, issue URLs, explicit repository refs and imported external issues still require it and fail closed at the repository boundary. Malformed receipts remain visible as unknown ids and never prevent startup. Unknown receipt bytes, full output and source staging are retained until ownership/inactivity can be established or the receipt is removed, an explicit exception to normal retention. Unknown containers require confirmed ownership/inactivity before cleanup; removing a receipt never authorizes their removal. Unknown payloads are never deleted through path-based entry checks, which cannot prevent a same-user parent-directory swap at this boundary. Removing the receipts directory is an empty record set, clearing obsolete unknown warnings and allowing normal expired staging cleanup.
3042
+
3043
+ Evidence: `docs/verification/2026-10-17-418-probes.md`. The feasibility probe is Linux/arm64 Apple-container evidence, not a managed-Pi or macOS security acceptance receipt. Issue #418 stays open until its managed-Pi live and independent review gates pass.
3044
+
3045
+ ## Amendment: designated planner (#455)
3046
+
3047
+ `config.roles` is derived from the policy roles (defaults, then each file's `roles` block) by `settlePlanner` in the daemon, in place, so Tasks reads the same object. A `planner` key (`config.json` or `config.local.json`, local wins; one of `PLANNER_PEERS`, which excludes the hub-sandboxed pi and local) makes that peer the only planner. With no key the `roles` planners stand; when none of them is on the roster, the first rostered `PLANNER_PEERS` entry plans (`automatic`), recomputed at start, on each policy refresh and after a roster change, never written to disk. The `planner` setting (group Roles, live, risk raises) is the one write path; status carries `planner: { peers, source }` and `roleInfo[peer].planner`. Planning authority and backlog notices follow at once; role text in a peer's instructions follows at its next start. A pinned planner that leaves the roster leaves the project without a planner until a person changes the setting. Planning operations that await a PII screen (pull, plan, split, new release) recheck planner authority after the await, before their board write.
3048
+
3049
+
3050
+ ## Amendment: task stalls, exact offer attention and cancellation (#438)
3051
+
3052
+ - Ready ownerless tasks retain the ordinary routing decision and reservation guards. A persisted cause/replanning signature admits one ids-only console and supervision notice for an ineligible reserved peer, no eligible peer or a cancelled dependency. Repeated release passes with the same cause add neither history nor notices; a changed cause or explicit replanning admits one new notice. The designated planner (the settled configured planner roles) receives bounded ids-only advice in its next preface, while the configured supervision stream receives the structured item under its existing scope. Advice does not manufacture an ordinary task turn beside a stale backlog batch. Without a planner, the console attention list remains the recipient. Existing owner/reviewer ladders remain unchanged.
3053
+ - A current proposed offer to a pull-only peer remains console attention until acceptance, decline, supersession or positive delivery evidence for that exact offer envelope and recipient. Tasks records the original envelope id, assignment revision and offer-time pull-only classification before publication. This classification survives detach and restart; current attachment is not evidence that an unread offer disappeared. Actual adapter completion, a correlated reply or a completed pull records positive evidence atomically with journal completion, surviving terminal pruning and reopening. Accepted-only receipts, unrelated inbox activity and operator/hub completion are not read evidence. Advisory queries never dequeue, steer or settle work. Decoded offer/stall candidates are cached by board revision and live eligibility inputs; positive journal read evidence is checked afresh. The derived revision advances atomically with task and stall writes, independently of unrelated delivery-journal writes.
3054
+ - The disabled-by-default sweep adds `ready-unassigned` with `ready_min: 60`. A ready proposed ownerless task follows the existing deterministic, restart-safe ladder, without treating quiet stall/offer bookkeeping as activity. A person-unassigned task stays suppressed until explicit person assignment or reservation. Planner-pulled work retains reserved-only/WIP admission, and a saved-but-undelivered reassignment does not stop the remaining sweep. Cancellation does not satisfy a dependency. This change does not enable any project's live sweep configuration.
3055
+ - `cancelled` is terminal and retained. The person may cancel backlog, proposed, in-progress, in-review or changes-requested work, never approved work. A configured planner tool may cancel only its own backlog/proposed task with no historical acceptance. `Tasks.cancel` validates the optional flow revision, screens the reason/reference and rechecks authority, revision and recovery/shutdown exclusion after asynchronous screening before the sole board mutation. History retains actor, reason and optional supersession reference. After the cancellation write commits, advisory cleanup and notices cannot change its successful result. No outcome memory, native cancellation, branch deletion or dependent release occurs.
3056
+ - Cancellation releases overlap/reservation participation and sends each distinct former owner/reviewer one stop notice. Its trusted cancellation revision marker keeps that stop notice relevant; earlier notices for the cancelled task become moot under #434. The classifier never uses timestamps alone to identify the stop notice. Dependents keep waiting and receive the cancelled-dependency cause. List, show, explain, dashboard and release projections treat cancellation as closed, retaining the current privacy screen and distinguishing cancellation from successful approval.
3057
+ - The daemon advertises `task_cancel` in hello and status features. Current clients refuse cancellation without that feature. Upgrade and restart persist the verified target's exported features with its immutable root/digest before prepare, including cached targets, and reuse that receipt on resume after verifying the same bytes. A legacy staged receipt without feature evidence is probed only while a project is not yet past commit; fully committed projects do not require another probe. This preserves #215: a transient probe failure cannot refuse a target that was already staged with verified evidence. Preflight checks only uncommitted sources and refuses a target lacking `task_cancel` while cancelled rows exist. Source prepare/commit recheck before holds/snapshots and around asynchronous work; cancellation cannot write through recovery preparation. Refusals name task ids and never rewrite rows. An older binary started manually can misclassify cancelled rows as open or fail to move them; this is the explicit unsupported compatibility ceiling. Never map cancellation to approval.
3058
+ - The post-release live step remains separate: do not cancel the historical task or enable the project's sweep from implementation tests. AC7 needs installed-runtime receipts.
3059
+
3060
+ ## Amendment: live configuration reload (issue #439)
3061
+
3062
+ The daemon validates one captured candidate containing config.json, config.local.json,
3063
+ routing.toml and routing.local.toml before applying any part of it. Missing files use
3064
+ existing defaults; unreadable, malformed, unknown or invalid configuration refuses the
3065
+ whole candidate. The accepted routing snapshot remains authoritative until another
3066
+ complete candidate succeeds; last-good parser fallbacks never authorize a reload.
3067
+ Settings writes, undo, explicit `ahub settings reload`, dashboard reload and debounced
3068
+ file changes share this path. One invalid candidate produces one bounded notice naming
3069
+ the file/key and validation error, without echoing configuration values.
3070
+
3071
+ Every setting and configuration block has one apply class. Leaf rules override block
3072
+ rules. `live` changes future decisions immediately; `next use` changes the next task,
3073
+ request, batch or peer start; `restart` retains the running value and reports its key
3074
+ and reason. The registry exposes the running and file values separately.
3075
+
3076
+ | Apply class | Configuration |
3077
+ |---|---|
3078
+ | live | runtime permission choices, authorized roster changes, roles/planner/conductor, instruction-file protection, approvals.notify, research.enabled, snapshots.enabled/keep, task_sweep, tasks.wip_limits, capabilities, routing classes/stay_switch/signals/constraints/hub_routes/max_switch_prefill_tokens |
3079
+ | next use | permission_modes and catalog_roles at the next peer start; peers.*.start_mode and native launch argv; coordination for newly created tasks; task release/review policy; new approval timeouts; bus batch/admission limits; budget/context/inference/limits; peer watchdog and step limits; recovery and experiment decisions; memory brief/injection/suggestion limits |
3080
+ | next use | pi.auto_start changes a later startup/recovery auto-start decision; reloading it never starts or replaces a running peer |
3081
+ | restart | memory.enabled/native/worker_url/short_ttl_days/sync_interval_min: open consumers, selected profile binding, client and sync timer must be quiesced together |
3082
+ | next use | routing targets/routes/local selection at the next local peer or sidecar creation; existing native sessions retain their model contract |
3083
+ | restart | pi.dgx_coding/dgx_fast and routing.pi: model catalogue and context publication belong to the existing Pi relay; a reload must not replace a live native relay |
3084
+ | restart | omniroute: credential and gateway clients are bound to relay ownership |
3085
+ | restart | mlx: model/runtime ownership and device resources belong to the running instance |
3086
+ | restart | checks: bounded runner and completion-check execution contracts are constructed together |
3087
+ | restart | pii, data and local deny/read/network policy: existing native sessions and generated sandbox/proxy contracts must not acquire a different trust boundary mid-session |
3088
+ | restart | profile/profile_overrides and text_projection: session tool schemas, role/profile contracts and converter/cache ownership are bound at startup |
3089
+ | restart | terminal.open: the terminal provider is supplied by the daemon lifecycle |
3090
+ | restart | bench.enabled: the benchmark execution environment is selected before work starts |
3091
+
3092
+ Listener ports and the state directory are startup options rather than editable
3093
+ settings; their already-bound sockets and state ownership require a new daemon.
3094
+ Changing the external memory selector likewise requires the existing quiesce/reopen
3095
+ lifecycle and is never a hot reload or automatic migration.
3096
+
3097
+ Trust is checked against the actual source files and their last accepted raw layers,
3098
+ not against a scratch project's Git status. Effective guarded changes are also
3099
+ checked when unchanged raw declarations acquire different Git provenance. A
3100
+ settings ticket never authorizes command/policy fields outside the editable registry.
3101
+ Changed MACHINE_LOCAL values and raising
3102
+ registry values are refused from tracked or unverifiable files. Ordinary dashboard
3103
+ and watcher authority cannot adopt a raise hidden in an unrelated local edit. A
3104
+ settings session or terminal may adopt an untracked value within its existing
3105
+ settings authority; neither bypasses the tracked-file rule or the never-ask and
3106
+ roster confirmation/lifecycle fences. Runtime permission mutation still uses
3107
+ permissionChange/changePermission exclusively.
3108
+
3109
+ A sweep reload validates its next-pass policy and reschedules its timer without
3110
+ changing a pass already in progress. Bus scheduling changes retain queued originals,
3111
+ prefaces, manual pauses, recovery holds and inflight delivery. Coordination preference
3112
+ is pinned when a task is created (including split children); changing it neither
3113
+ retroactively silences an existing task nor lifts an existing silent cohort. Current
3114
+ PII and capability fences remain authoritative. Snapshot enablement applies to a
3115
+ later turn; a running turn retains its capture decision and is never stopped.
3116
+
3117
+ The console's sixth Settings tab reads the same registry as the dashboard. It edits
3118
+ through the authenticated daemon client: value, preview, nonempty bounded reason,
3119
+ then explicit y; undo also previews and confirms. Stale preview replies cannot arm a
3120
+ write, and pasted confirmation text never executes one. The daemon accepts additive
3121
+ settings_reload and undo-preview requests only from console roles. The dashboard's
3122
+ setting_reload action uses its existing authenticated session; manager forwarding
3123
+ has ordinary authority. No client writes configuration files directly. Pending
3124
+ restart keys and reasons appear in status, console and dashboard. These requests and
3125
+ optional projections are additive; older daemons refuse unknown requests and the
3126
+ control protocol remains 16.
3127
+
3128
+ ### Reload authority and publication scope (#439 review amendment)
3129
+
3130
+ Malformed syntax, unknown keys and invalid values still refuse the complete candidate
3131
+ before any publication. A valid but unauthorized key is different: retain that key's
3132
+ accepted value, report its name and reason, and continue applying independently
3133
+ eligible changes. A refused key never wedges an unrelated setting write.
3134
+
3135
+ An individual setting write or undo publishes only its named registry path. Its
3136
+ preview and confirmation do not authorize other edits in either config file or either
3137
+ routing file. This includes catalog guidance: only the named peer is prepared (#295).
3138
+ Unapplied edits remain pending for a later explicit reload; accepting another key must
3139
+ not acknowledge them in the accepted source baseline.
3140
+
3141
+ Privacy tightening is automatic from tracked or untracked files: enabling local PII
3142
+ constraints/screening, adding PII patterns, removing nonholder role memberships and narrowing an explicit capability list cannot
3143
+ be mistaken for a raise. Deleting a restricted capability entry restores the default of all capabilities and is a guarded widening. Roster membership changes always use their named confirmation and peer lifecycle, even when removing a peer; generic reload never bypasses those steps. Registered floors and known semantic reductions in admission
3144
+ or permission likewise remain available without authorizing a wider policy. Other
3145
+ agent-relevant policy, including limits, supervision feed, inference, review and crash
3146
+ resume, receives a per-key authority check even outside the settings registry.
3147
+
3148
+ `ahub settings reload` is the explicit adoption path for guarded file edits. The daemon
3149
+ first returns the exact guarded keys and a confirmation token bound to the candidate
3150
+ bytes and accepted configuration revision. The person reviews that list and confirms
3151
+ from an interactive terminal; only then may the same candidate be adopted. Changed
3152
+ bytes or accepted state invalidate the token. An ordinary watcher or dashboard never
3153
+ confirms a raise; a settings session remains restricted to registry keys. Tracked or
3154
+ unverifiable MACHINE_LOCAL command and credential declarations cannot become trusted
3155
+ through confirmation. Refusal notices name each key and say to review and run
3156
+ `ahub settings reload` or revert the file; tracked machine-local declarations also
3157
+ require moving the declaration to an untracked machine-local source.
3158
+
3159
+ Coordination pinning is local to a daemon lifetime. A setting reload preserves each
3160
+ existing task's captured mode and existing cohort silence; a daemon restart re-pins
3161
+ retained task rows to the startup mode. This is an explicit compatibility ceiling,
3162
+ not durable task metadata or a migration.
3163
+
3164
+ Inheritance and undo use the actual destination source trust. They may restore an unchanged effective startup value after explicit consent, but a shadowed edit that was never authorized is not an accepted fallback. New lower-layer raises remain refused even when an unchanged local overlay previously hid them.
3165
+
3166
+ Reserved prototype path components are refused throughout nested routing extension tables before publication. Configuration path writes never traverse inherited object properties.
3167
+
3168
+ Switchyard targets/routes and hub-route dictionaries retain opaque quoted keys, including dots. Each dictionary is one guarded value for reload confirmation and publication; privacy fields and ordinary registry settings retain leaf-level decisions.
3169
+
3170
+ ### Settled authority and reviewable confirmation (#439 round-2 amendment)
3171
+
3172
+ A role-list subset is automatic only when the settled planner and conductor holder
3173
+ sets stay unchanged. Removing the last explicit planner can hand that authority to
3174
+ the automatic fallback; removing a settled holder therefore requires guarded
3175
+ confirmation even though the raw role list shrinks. The same pure role resolver
3176
+ serves publication and the authority comparison.
3177
+
3178
+ The console preview shows a complete bounded before -> after value for every guarded
3179
+ key, including full executable argv, and describes any settled holder change. Values
3180
+ stay out of notices and events. A value that cannot fit the review budget is retained
3181
+ with an actionable rejection, never silently truncated and authorized. Each before
3182
+ or after representation is at most 16 KiB, an effect description at most 2 KiB, and
3183
+ the complete confirmation at most 128 KiB.
3184
+
3185
+ The confirmation token proves candidate/state freshness. It does not independently
3186
+ prove a person's presence: console-role authority is the existing server trust
3187
+ boundary, while the CLI enforces interactive TTY, explicit y and the deadline.
3188
+ The roster exact-key mutation guard observes admission while teardown is held,
3189
+ before reload can repair a wrong whole-roster assignment. The roster authority seed
3190
+ covers the dashboard authority boundary; its separate leaf check is defence in depth.
3191
+
3192
+ Console undo binds its confirmation to the previewed setting key and the SHA-256
3193
+ of the exact undo record. A changed or missing record refuses without writing;
3194
+ same-key replacement also invalidates the preview. These optional request fields
3195
+ preserve protocol 16 for older direct CLI clients. The Settings panel never forces
3196
+ a busy roster removal: it names `ahub roster remove <peer> --now` and the active-turn
3197
+ stop/detach consequence so the person can make that choice explicitly in the CLI.
3198
+
3199
+
3200
+ ### Adapter registry A1 (#451)
3201
+
3202
+ A1 introduces API 1 definitions for the seven existing kinds and separates a peer
3203
+ id from its adapter kind through `kindOf`, `definitionOf` and `familyOf`. With no
3204
+ instance declarations, only builtin ids resolve. A suffix is never guessed. This
3205
+ phase does not enable instance configuration, account changes or new native modes.
3206
+ Existing external-client admission and lazy roster behaviour remain unchanged.
3207
+
3208
+ Qwen is the first migrated ACP kind. Its definition owns the existing headless
3209
+ launch/factory, exact 0.25.0 identity, permission-mode mapping and session rules.
3210
+ The direct `AcpPeer` `qwen025` option and public helper exports remain compatible;
3211
+ legacy benchmark ACP without that flag stays unmanaged. Other kinds retain their
3212
+ existing runtime paths until A2; their registry metadata is not proof of a running
3213
+ or authenticated native client.
3214
+ Unmigrated definitions delegate launch plans to the existing CLI and keep existing
3215
+ runtime permission handlers authoritative. Claude, Pi and local keep empty partial
3216
+ native-mode maps where API 1 cannot express their scoped write/edit hooks; this is
3217
+ an A1 compatibility ceiling, not an unmanaged policy or a refusal. A2 must migrate
3218
+ those handlers and their representation before interpreting a missing mapping.
3219
+ Display-only permission grants remain exact copies of the existing explanations.
3220
+ The bounded Qwen doctor CLI-version probe and its 0.25.0 pin remain unchanged in
3221
+ the transitional guard baseline. A2 moves all version probes together and amends
3222
+ `VersionContext` once; A1 adds no observed-version input or native readiness claim.
3223
+ The native Kimi mode-id constant moves to a dependency-free leaf while its public
3224
+ re-export remains intact, so either registry-first or definition-first imports work.
3225
+
3226
+ Two additive fields complete source inputs omitted from the A0 type sketch:
3227
+
3228
+ - `LaunchContext.nativeCommand?: readonly string[]` carries the already-validated
3229
+ native argv, including wrapper arguments. Only core config or the existing
3230
+ redacted preview supplies it; definitions never load daemon config or recover
3231
+ commands from environment text. Model tool schemas do not expose this field.
3232
+ - `permission.grants?: Partial<Record<PermissionMode, string>>` carries existing
3233
+ operator explanations as static data. It changes no approval decision, scope or
3234
+ native mode. The frozen `CreateContext` and transport-specific options stay intact.
3235
+
3236
+ The guard inventories AST comparisons, closed-list imports and vendor literals,
3237
+ including inline dashboard scripts, under stable file/symbol scopes. Its reviewed
3238
+ A1 baseline records the current source, rather than claiming the old A0 grep's
3239
+ 265 line matches equal AST node counts. A new group, count growth or stale entry
3240
+ fails; later phases may only shrink this baseline. Only real, unshadowed registry
3241
+ resolver imports exempt kind-aware comparisons. Native adapter implementations and
3242
+ explicit non-peer config semantics remain the narrow exceptions. Existing literal
3243
+ catalog exports used by older recovery source probes are retained until their
3244
+ replacement compatibility proof is available; a refactor must not silently make a
3245
+ Qwen-capable target appear unsupported.
3246
+
3247
+ The reviewed inventory distinguishes the historical grep from AST nodes: at
3248
+ `71253098`, the same AST scanner counts 364 sites in 142 groups (222 comparisons,
3249
+ 19 closed-list imports, 123 vendor literals). At A1's main base `4bcf62fa`, it counts
3250
+ 395 in 156 groups. The first A1 snapshot has 382 in 155 groups: thirteen Qwen/raw
3251
+ watch comparisons are removed, and the seven-entry role object becomes a
3252
+ seven-entry legacy-order list whose role values come from the registry. No raw
3253
+ Qwen comparison remains outside the native definitions. The initial baseline is
3254
+ reviewed data, never regenerated by the test; changes brought by a newer main
3255
+ must be reconciled explicitly before this first baseline ships.
3256
+
3257
+ Before the first A1 review, main `b9efa131` merged #463. Its recovery guidance and
3258
+ reattach logic add 33 sites in 16 groups, with 19 changed baseline entries. The
3259
+ same scanner reports 428/172 on that main and 415/171 on the combined A1 source.
3260
+ Every added or increased baseline entry matches that main-only delta; A1 still
3261
+ removes thirteen sites and one group. This explicit reconciliation precedes the
3262
+ first shipped baseline and does not permit later phases to grow it.