@staix/agent-hub 0.12.43 → 0.12.44

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 (89) hide show
  1. package/CHANGELOG.md +16 -3
  2. package/README.md +3 -3
  3. package/docs/adding-an-ai-tool.md +140 -0
  4. package/docs/agent-notes/README.md +5 -0
  5. package/docs/agent-notes/adapters.md +8 -3
  6. package/docs/agent-notes/budget.md +2 -2
  7. package/docs/agent-notes/daemon.md +9 -5
  8. package/docs/agent-notes/tasks.md +6 -2
  9. package/docs/agent-notes/tests.md +3 -1
  10. package/docs/operations.md +64 -5
  11. package/docs/quickstart.md +6 -2
  12. package/docs/security.md +5 -3
  13. package/docs/specs/2026-09-19-agent-hub-design.md +160 -4
  14. package/docs/verification/2026-10-21-0.12.44.md +54 -0
  15. package/docs/verification/2026-10-21-grok-config-acp-t0.md +30 -0
  16. package/docs/verified.json +34 -19
  17. package/package.json +1 -1
  18. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  19. package/plugins/agent-hub/server.js +743 -229
  20. package/src/adapters/acp-owned-group.ts +124 -0
  21. package/src/adapters/acp.ts +92 -25
  22. package/src/adapters/agy/definition.ts +1 -3
  23. package/src/adapters/agy/entry.ts +4 -1
  24. package/src/adapters/channel-peer.ts +143 -0
  25. package/src/adapters/claude-channel.ts +7 -7
  26. package/src/adapters/codex-appserver.ts +16 -2
  27. package/src/adapters/configured-acp.ts +124 -0
  28. package/src/adapters/definitions/builtin-metadata.ts +37 -33
  29. package/src/adapters/definitions/native-channel-state.ts +41 -0
  30. package/src/adapters/definitions/native-cli-entry-kimi.ts +3 -1
  31. package/src/adapters/definitions/native-cli-preview-qwen.ts +1 -1
  32. package/src/adapters/definitions/native-identity-defaults.ts +14 -0
  33. package/src/adapters/definitions/qwen.ts +3 -8
  34. package/src/adapters/definitions/reviewer-variants.ts +3 -7
  35. package/src/adapters/jcode.ts +4 -0
  36. package/src/adapters/local-worker.ts +4 -0
  37. package/src/adapters/pi.ts +4 -0
  38. package/src/adapters/registry-types.ts +18 -16
  39. package/src/adapters/registry.ts +68 -6
  40. package/src/adapters/reviewer-print.ts +4 -0
  41. package/src/cli/agy-mcp.ts +3 -2
  42. package/src/cli/configured-acp-entry.ts +58 -0
  43. package/src/cli/console-state.ts +9 -4
  44. package/src/cli/facts-hook.ts +2 -1
  45. package/src/cli/help.ts +6 -0
  46. package/src/cli/identity.ts +7 -6
  47. package/src/cli/launch.ts +5 -4
  48. package/src/cli/main.ts +78 -27
  49. package/src/cli/memory.ts +13 -10
  50. package/src/cli/output.ts +1 -0
  51. package/src/cli/preview.ts +9 -0
  52. package/src/cli/recovery-guidance.ts +1 -1
  53. package/src/cli/terminal-recovery.ts +16 -13
  54. package/src/cli/upgrade-restore-state.ts +3 -2
  55. package/src/cli/upgrade-runtime.ts +21 -6
  56. package/src/cli/upgrade.ts +3 -2
  57. package/src/hub/board.ts +9 -6
  58. package/src/hub/budget.ts +9 -2
  59. package/src/hub/bus.ts +6 -2
  60. package/src/hub/claude-state.ts +2 -37
  61. package/src/hub/conductor.ts +8 -6
  62. package/src/hub/config-apply.ts +26 -14
  63. package/src/hub/config-reload.ts +39 -11
  64. package/src/hub/config-trust.ts +1 -1
  65. package/src/hub/context-window.ts +6 -0
  66. package/src/hub/control-client.ts +1 -1
  67. package/src/hub/crash.ts +8 -6
  68. package/src/hub/daemon.ts +341 -313
  69. package/src/hub/hub-tools.ts +36 -0
  70. package/src/hub/instance-config.ts +20 -11
  71. package/src/hub/instance-execution.ts +4 -4
  72. package/src/hub/instances.ts +45 -21
  73. package/src/hub/lifecycle-schema.ts +5 -3
  74. package/src/hub/memory-profile.ts +16 -3
  75. package/src/hub/no-writes.ts +50 -0
  76. package/src/hub/permission-mode.ts +9 -7
  77. package/src/hub/roster.ts +7 -4
  78. package/src/hub/routing.ts +21 -20
  79. package/src/hub/settings.ts +50 -16
  80. package/src/hub/snapshots.ts +26 -2
  81. package/src/hub/start-mode.ts +10 -8
  82. package/src/hub/task-sweep.ts +8 -2
  83. package/src/hub/tasks.ts +85 -17
  84. package/src/memory/capture.ts +2 -1
  85. package/src/ui/index.html +5 -0
  86. package/templates/AGENTS.block.md +1 -1
  87. package/templates/profiles/general.json +1 -1
  88. package/templates/profiles/research.json +1 -1
  89. package/templates/profiles/writing.json +1 -1
@@ -1588,7 +1588,7 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
1588
1588
  | Routing | `routing.stay_switch`, `routing.classes.<class>.peers`, `.escalate_to`, `.route`, `.pi_backend` | `routing.local.toml` | at once |
1589
1589
  | Switches | `research.enabled`, `approvals.notify`, `snapshots.enabled` | `config.local.json` | live, without disturbing an active turn |
1590
1590
  | Switches | `coordination` | `config.local.json` | newly created tasks only |
1591
- | Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min` | `config.local.json` | live; reschedules the sweep timer |
1591
+ | Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min`, `.no_writes_turns` | `config.local.json` | live; `.no_writes_turns` sets the turn threshold independently; other keys reschedule the sweep timer |
1592
1592
  | Routing | `tasks.wip_limits.<peer>` | `config.local.json` | live |
1593
1593
  | Routing | `tasks.escalate_after` (1-10, default 3: consecutive `changes_requested` before a task moves to the next `escalate_to` peer) | `config.local.json` | live; the next verdict uses it |
1594
1594
  | Roles | `planner` (the one peer that plans) | `config.local.json` | at once |
@@ -1690,6 +1690,15 @@ A peer that has a TUI the hub attaches to starts in it by default (issue #269).
1690
1690
  never downgraded to headless. `ahub settings` lists each start mode, and the
1691
1691
  dashboard's Start control says which mode a start will use and where its
1692
1692
  terminal comes from.
1693
+ - **A headless start gets a watch pane** (#286 AC10): when the hub itself
1694
+ starts Kimi or Qwen headless (Kimi when its start mode is `headless`), it
1695
+ also opens `ahub watch <peer>` through the same provider, a fixed command
1696
+ with no caller text, at most once per 30 seconds per peer. The headless
1697
+ start has already succeeded, so with no provider, or one that fails, the
1698
+ start still stands and its result and the hub's notice give the command to
1699
+ run by hand. A start in TUI mode opens the peer's own terminal instead, as
1700
+ above; an already attached peer, a declared extra instance and a peer
1701
+ without the watch capability open no pane.
1693
1702
  - **Dashboard Start:** the control is locked while its request is in flight. A confirmed answer unlocks it immediately, so a retry reaches the hub's own 30-second terminal reopen guard and shows any known refusal. The unified dashboard waits 35 seconds, covering a terminal provider's 30-second cap plus a 5-second response margin; a slower headless start can still end unconfirmed while the hub continues. Check the Peers panel before retrying. An unconfirmed unified-dashboard start holds that peer's control for the shared 30-second reopen bound, or until its `starts` snapshot confirms attachment, across redraws and project switches within the same hub instance; the row states the retry bound. The project-local dashboard keeps its usual confirmed-answer behavior. Other forwarded actions keep their 10-second wait; the manager opens no settings session.
1694
1703
  - The conductor cannot choose the mode: `hub_peer_start` takes the peer's own.
1695
1704
  - Recovery restores each peer in the mode its recorded session had. After an
@@ -2899,6 +2908,8 @@ The daemon advertises `task_cancel`; a current CLI refuses cancellation against
2899
2908
 
2900
2909
  ## Task idle sweep
2901
2910
 
2911
+ Writing-owner no-op detection (#498) is independent of the time sweep: `task_sweep.no_writes_turns` defaults to 2, and 0 disables its notices. Known file changes, HEAD/shared-branch ref movement and task-tool effects reset the streak. A canonical snapshot with recorded branch, worktree-path or PR/head evidence is unknown. Negative canonical evidence also needs exclusive worktree scope and no `.worktrees` directory, so uncommitted work in a separate checkout is not reported as empty. Done advisories use the first acceptance in the current ownership span; plan updates do not discard earlier touches.
2912
+
2902
2913
  The between-turn task sweep (#186) is disabled by default. Set `task_sweep` in
2903
2914
  `.agenthub/config.json` (or its machine-local override) to enable it:
2904
2915
 
@@ -3063,7 +3074,7 @@ consumption, so no token total is counted from it; the adapter accepts the
3063
3074
  update whenever it names the current session, because 2.1.1's source text
3064
3075
  emits it after the turn settles (read from the source, not yet observed live
3065
3076
  through the hub; docs/smoke.md), so the peer may already be idle.
3066
- Pi and other unsupported surfaces show unknown.
3077
+ Pi, Qwen (until a native source is verified, #625) and other unsupported surfaces show unknown.
3067
3078
  A stale or disconnected reading is unknown, not 0%. Codex's accumulated session
3068
3079
  usage is never used as context occupancy. In the tail (`ahub tail` and the
3069
3080
  console's stream) a reading prints once and repeats only when the line changes
@@ -3281,7 +3292,7 @@ Modes: ask maps to default, scoped frequency maps to auto-edit, never-ask maps t
3281
3292
  yolo. Qwen yolo approves every tool outside any hub sandbox; its own vendor bounds
3282
3293
  apply. Auto and plan are not hub mode mappings. Unknown quota refusals are not
3283
3294
  classified as authoritative quota; until a real pinned refusal is recorded, pause
3284
- by hand. Occupancy is context with a source, never a consumption estimate. Roles
3295
+ by hand. Context occupancy shows unknown until a native source is verified; it no longer uses Kimi's usage parser (#625). Roles
3285
3296
  do not establish that Qwen can reliably replace Kimi before the live leg.
3286
3297
 
3287
3298
  Qwen controlled recovery and automatic crash resume load the recorded conversation with ACP session/load. The emitted launcher passes --session-id; a controlled plan with a missing or unsafe identity refuses before effects. Preview redacts the id. A custom executable argv keeps its wrapper arguments before appended model flags. Native recovery remains unverified.
@@ -3309,18 +3320,58 @@ Native policy is unmanaged and has NO hub sandbox; no permission_modes.jcode or
3309
3320
 
3310
3321
  ## Optional Antigravity (agy) peer
3311
3322
 
3312
- Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2 is the verified version, any other 1.4.x launches with a stderr warning (agy updates itself), and other versions are refused (#589). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and extra agy instances are unavailable. Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
3323
+ Antigravity is optional and off roster by default. Use `ahub roster add agy`, then run `ahub agy` in a terminal: it runs the Antigravity CLI as a fresh pull-only TUI in that terminal. It accepts the 1.4.x line: 1.4.2 is the verified version, any other 1.4.x launches with a stderr warning (agy updates itself), and other versions are refused (#589). The running hub must advertise `native_hook_launch`; an older hub refuses before any write with an upgrade-or-restart remedy. Only the person's launcher starts it: conductor starts are refused, there is no controlled restore or resume, and a declared agy extra instance is refused when the config loads (#625). Headless/print, resume (`--conversation`, `--continue`, `--new`), `--mode`, `--dangerously-skip-permissions` and `--unattended` are refused until their native mapping is verified; `--print-command` previews without writing.
3313
3324
 
3314
3325
  The launcher writes exactly `plugin.json`, `mcp_config.json` and `hooks.json` in `.agents/plugins/agent-hub/`, refusing linked or foreign entries before any write, and removes only its own matching files when the TUI ends. It never writes project-level `.agents/hooks.json` or the person's `~/.gemini` settings. Run `ahub init` afterwards to ignore the untracked plugin directory; a tracked plugin is written with a warning. The native model sees the hub tools under the server name `agent-hub_agent-hub`.
3315
3326
 
3316
3327
  Like Kimi's TUI, agy is pull-only: it reads `hub_inbox` and shares results with `hub_send`, messages do not interrupt it, and automatic routing never picks it. Its roles are capped at implementer and verifier; a plan or review assignment and PII work are refused. Native permissions are unmanaged, with no hub sandbox, hub permission mode, quota source or session resume. Plugin hooks bound to that launch carry the hub's turn facts and report each completed native turn. A native read is recorded as a peer `Read` fact: `view_file` (`AbsolutePath`) in 1.4.2 and `read_file` (`Path`) in 1.4.3; a `StartLine`/`EndLine` range counts as a partial read, never a whole-file view (#555). The live TUI/account leg is unverified ([T0 receipt](verification/2026-10-19-agy-p1-t0.md)). The 1.4.3 T0 re-run keeps 1.4.3 unverified: its `call_mcp_tool` schema and native tool names changed, and the TUI attaches the hub tools only from the second model turn ([1.4.3 receipt](verification/2026-10-21-agy-1.4.3-t0.md), #607).
3317
3328
 
3329
+ ## Configured ACP adapters (#451 A3)
3330
+
3331
+ A tool that speaks standard ACP over stdio can join a project without a code change. The declaration is machine-local authority: put it only in an untracked `.agenthub/config.local.json` (in `config.json` or a tracked file it is ignored with a notice), then restart the hub. The hub compiles its adapter catalog once at startup, so adding, changing or removing a declaration needs a restart; settings list the change as pending restart and never edit `adapters`.
3332
+
3333
+ {
3334
+ "adapters": {
3335
+ "myagent": {
3336
+ "transport": "acp",
3337
+ "command": "/opt/myagent/bin/myagent",
3338
+ "args": ["acp"],
3339
+ "displayName": "My Agent",
3340
+ "roles": ["implementer", "verifier"],
3341
+ "identity": { "name": "<initialize agentInfo.name>", "version": "<agentInfo.version>" },
3342
+ "permission": { "ask": "<native mode id>", "ask-when-needed": "<native mode id>", "never-ask": "<native mode id>" },
3343
+ "readOnly": "<native read-only mode id>"
3344
+ }
3345
+ }
3346
+ }
3347
+
3348
+ - The kind matches `[a-z][a-z0-9-]`, has at most 32 characters, and may not collide with a builtin peer, an `ahub` command or a reserved id. A project can declare at most 64 kinds. Unknown fields refuse the config.
3349
+ - `roles` is a non-empty subset of `planner`, `implementer`, `verifier` and `reviewer`. `conductor` is refused here; only explicit project role policy can appoint one.
3350
+ - `identity` is the name and version that ACP `initialize` reports. A start without it, or with a mismatch, refuses before any prompt.
3351
+ - `args` may not select a leader, attach, connect or remote mode (`--leader*`, `--attach*`, `--connect*`, `--remote*`): only a foreground process the hub owns is supported. A Grok declaration (kind `grok`, a `grok` executable or identity name `grok`) must also pass a standalone `--no-leader` before any `--`. Grok 1.0.62 itself is not supported ([T0 receipt](verification/2026-10-21-grok-config-acp-t0.md)).
3352
+ - `permission` maps hub modes to native ACP mode ids. Only mapped modes can be selected, and the session must advertise and acknowledge each one. Without a map (or with an empty one) native policy is unmanaged: `ahub permission <kind> <mode>` and `permission_modes.<kind>` are refused. No hub sandbox applies either way.
3353
+ - `readOnly` names the native mode that read-only instances use (below). It is not applied to the kind itself.
3354
+
3355
+ ahub roster add myagent # configured kinds are off roster by default
3356
+ ahub myagent # headless; asks the running hub to start it
3357
+ ahub myagent --session-id <id> # load a recorded native session; the agent must echo the id
3358
+ ahub myagent --print-command # preview: executable, argv and session id are withheld
3359
+ ahub stop myagent
3360
+
3361
+ `ahub <kind>` accepts only `--mode headless`, `--as <instance>`, `--session-id <id>` and one of `--print-command`/`--dry-run`; model, route, unattended and other launch overrides are refused. The running hub must advertise `configured_acp` and list the kind as loaded; otherwise the command tells you to upgrade or restart. If the file changed since the hub started, the start warns and uses the running definition. A conductor may start the kind with `hub_peer_start` (headless, with no session override). There is no TUI form: `peers.<kind>.start_mode` accepts only `headless`, and `ahub watch` is not available. Default routing never picks a configured kind; it gets work through its roles, an explicit owner or reviewer, or a reservation.
3362
+
3363
+ Before admission the hub verifies the native identity, a valid session id, the requested native mode and positive ownership of the spawned process and its process group. A failure refuses the start with a generic reason; the agent's stderr and error text are withheld from the log. `ahub stop` signals only that original verified group. If teardown cannot be confirmed, the peer stays offline but held, and new starts are refused until `ahub stop <kind>` succeeds.
3364
+
3365
+ Extra instances use the existing `instances` block. For example, `"myagent-review": { "kind": "myagent", "roles": ["reviewer"], "readOnly": true }` is started with `ahub myagent --as myagent-review`. Unlike the kind, a declared instance is on the roster by default. `readOnly: true` requires the kind's `readOnly` mode, and the session must advertise and acknowledge that mode before a prompt. Native permission requests other than read, search and think are rejected. The instance is offered only `hub_send`, `hub_inbox`, `hub_delivery_done`, `hub_review`, `hub_task_show`, `hub_task_list`, `hub_memory_search|show|graph` and `hub_status`; the hub refuses every other hub tool. Its permission mode is locked: settings show it as read-only, no write, undo or `ahub permission` changes it, and `permission_mode` next to `readOnly` is refused.
3366
+
3367
+ Configured kinds are cloud peers and never receive PII work. Quota and context are unknown, and a kind with no budget source is exempt from quota accounting, so pause it by hand if its provider refuses. After a crash the hub resumes a recorded session through `session/load` only when the agent echoes the same id, and it never reuses a recorded model. Controlled recovery (`ahub upgrade`, `restart`, `recovery`) refuses a running configured kind: stop it, then start it again with its session id. While the project declares adapters, the upgrade or restart target must advertise `configured_acp`.
3368
+
3318
3369
  # Headless ACP watch (#286)
3319
3370
 
3320
3371
  - A person opens `ahub watch kimi` or `ahub watch qwen` in a second terminal. `--no-thoughts` hides thought items; `--color=auto|always|never` controls colour. `q` or Ctrl+C leaves the view, keeping the peer running. Answer approvals in `ahub console` or with `ahub permit`.
3321
3372
  - Watch replays bounded memory history from this daemon, then displays live updates. It is the hub's read-only view, with no prompt input. It does not read native session files.
3322
3373
  - Kimi's default is its own pull-only TUI (#316). Watch requires `ahub kimi --mode headless`; its own TUI remains visible in its native terminal. Qwen requires roster participation and verified ACP 0.25.0. `ahub qwen --mode tui` remains refused.
3323
- - Protocol stays 16; a same-protocol older daemon answers the new request as unknown, which the CLI reports. No automatic pane opening is included in this slice.
3374
+ - Protocol stays 16; a same-protocol older daemon answers the new request as unknown, which the CLI reports. Since 0.12.44 a headless start that the hub itself makes for Kimi or Qwen opens this view automatically through the terminal provider (see Start modes; #286).
3324
3375
  - Native T0 limits: [ACP watch verification](verification/2026-10-11-acp-watch-t0.md).
3325
3376
 
3326
3377
  # Acceptance-criteria checklist (#481)
@@ -3827,3 +3878,11 @@ blocks that undo until the identity and workspace can be reviewed.
3827
3878
  Use `ahub sdlc train report <release-id>` for scope readiness and recorded release metrics. The planner creates future release rows, activates one train, and assigns tasks through the release-only hub tools. A due date requests a cut review; it never clears unfinished work. Move missed tasks to the next row with a reason, then use `ahub sdlc train cut <release-id> <revision>` from the person's terminal or `hub_release_cut` as the planner. The cut retains a short host admission lease through freeze and defers on held gate slots or unknown/quota-limited peer windows with a real quota source. Accounts without a quota reader (Kimi included when `kimi_tokens_5h` is zero) report `no quota source`; manual and budget-record pauses still defer. It does not run release preparation or publish anything. Schedule bundle/check/docs work under separate heavy admission and retain the person's native verification, authorization and upgrade window. A hotfix records its enumerated cause and keeps every gate.
3828
3879
 
3829
3880
  The per-release "gates run" record is deferred to S6/S7. Person ruling 2026-10-19 supersedes the earlier S11 upgrade-admission ruling: upgrades use pre-S11 recovery readiness (no busy peers, pending approvals resolved, completion checks settled) and retain the existing operation checks. S11 adds no host-admission, quota or all-idle requirement to upgrade. An active/frozen configured train still requires recovery-target `release_trains` compatibility. Release-cut admission remains unchanged: follow its refusal's applicable exit by stopping or resuming the paused peer, adjusting `ahub budget set`, or using `ahub release drop`. Missing/shadowed `dot` requires the resource controller or a PATH correction for the cut; nested admission requires retrying the cut outside the outer lease.
3881
+
3882
+
3883
+ ## Codex cancellation identity notice
3884
+
3885
+ If Codex is offline with `statusNotice.code = unidentified-cancellation`, the hub
3886
+ could not identify a cancelled native turn. Reattach the native session before
3887
+ further hub delivery. A late completion keeps this hold; verified native
3888
+ reattachment or TUI detachment clears it. The notice contains fixed guidance, without native ids.
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.12.43
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.12.44
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -30,6 +30,10 @@ and upgrade or crash recovery, see the [operations guide](operations.md). To
30
30
  move running projects to a newer release later, run `ahub upgrade` in a
31
31
  terminal: it reviews the plan with you, applies it and follows it.
32
32
 
33
+ ## Upgrading to 0.12.44
34
+
35
+ Run `ahub init` once per project to refresh the hub block in `AGENTS.md`: it tells agents in the agy TUI not to report the hub missing when no `hub_*` tool is listed on the first turn (#607). agy no longer shows Claude's context reading or Claude's transcript usage as its own (#624). When the hub itself starts Kimi or Qwen headless, it also opens `ahub watch <peer>` through its terminal provider; with no provider, or a failing one, the start still succeeds and reports the command to run (#286). When an `implement` or `bulk_edit` owner completes two turns in a row with no recorded writes or task-tool effects, the planner gets a notice, and a done report with no writes since acceptance says so in the review request; set `task_sweep.no_writes_turns` to change the count, or 0 to turn it off (#498). A declared agy extra instance is refused when the config loads, and Qwen's context occupancy shows unknown until a native source is verified (#625). A standard ACP agent can now be declared as a config-only adapter (`adapters.<kind>` in untracked `.agenthub/config.local.json`); restart the hub, then run `ahub roster add <kind>` and `ahub <kind>` (see the operations guide, #451). A project that declares adapters can be upgraded or restarted only to a release that advertises `configured_acp`. A `peers.<id>` block now accepts only `start_mode`: any other field stops the config from loading at hub start, where 0.12.43 ignored it at startup. Restart Claude sessions so they load the 0.12.44 plugin. Coming from 0.12.42 or older, the notes below apply too.
36
+
33
37
  ## Upgrading to 0.12.43
34
38
 
35
39
  A busy hub no longer stalls when the checkout's HEAD moves under open turns: snapshot Git work in one burst shares a 250 ms budget, and incoming checkouts, merges and fast-forwards are told apart from a peer's own commits through the HEAD reflog, so they no longer raise false concurrent-edit warnings (#576). `ahub task assign` and `hub_task_assign` now refuse with the reason when the named peer cannot take the task (#601). `ahub completion zsh|bash|fish` prints a shell completion script (#603). The console's Peers tab shows each peer's planner WIP, and `w` edits its limit (#532). agy native file reads are recorded as peer facts on 1.4.2 and 1.4.3 (#555); 1.4.3 stays unverified, and in its TUI the hub tools appear from the second turn (#267, #607). Coming from 0.12.41 or older, the notes below apply too.
@@ -44,7 +48,7 @@ A busy hub no longer stalls when the checkout's HEAD moves under open turns: sna
44
48
 
45
49
  ## Upgrading to 0.12.40
46
50
 
47
- Restart Claude sessions after upgrading: each keeps the plugin server it started with until restarted, so a restart loads the 0.12.43 plugin. That plugin asks Claude Code to list the hub tools again when a hub reconnect changes them (#490), and one from an older `ahub claude` launch is refused and stops reconnecting instead of reclaiming the peer (#551).
51
+ Restart Claude sessions after upgrading: each keeps the plugin server it started with until restarted, so a restart loads the current plugin. That plugin asks Claude Code to list the hub tools again when a hub reconnect changes them (#490), and one from an older `ahub claude` launch is refused and stops reconnecting instead of reclaiming the peer (#551).
48
52
 
49
53
  `ahub jcode` opens its TUI again; on 0.12.39 it stopped after the first hub turn (#564). A busy Jcode now takes an `important` message into its running turn (#572), and `ahub doctor` lists the extra MCP servers the hub-owned Jcode loads from its config files. See [Jcode operations](operations.md#optional-jcode-peer).
50
54
 
package/docs/security.md CHANGED
@@ -6,7 +6,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
6
6
 
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
- - **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; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation 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. 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. 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.
9
+ - **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; Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation or a person's console `y` for a config default at hub start. Until that config confirmation 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. 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.
10
10
  - **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`, `local.read_allow`, `local.bash_network`, `local.network_allow`, `terminal.open`, `instances`) apply only from a config file git confirms nobody committed (`instances` is read only from an untracked `.agenthub/config.local.json`; in `config.json` it is 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, 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`).
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.
@@ -183,7 +183,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
183
183
  - 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.
184
184
  - 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.
185
185
  - 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.
186
- - 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, and for Jcode before 0.12.39, 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). A declared extra instance accepts only `headless` from any surface; a Claude channel instance accepts no start mode, and a headless Claude instance must have roles exactly `["reviewer"]` and no worktree. For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (with `--mode tui` for Pi, Kimi and Jcode): 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.
186
+ - 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, and for Jcode before 0.12.39, 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). A declared extra instance accepts only `headless` from any surface; a Claude channel instance accepts no start mode, and a headless Claude instance must have roles exactly `["reviewer"]` and no worktree. For a TUI the hub opens a terminal running `<runtime> <its own entry point> --project <this project> <peer>` (with `--mode tui` for Pi, Kimi and Jcode): 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. After a successful headless start of a watch-capable peer (Kimi, Qwen) the hub also opens `<runtime> <its own entry point> --project <this project> watch <peer>` through the same provider under the same fixed-words rule, at most once per peer per 30 s and never for an already attached peer or a declared instance; the pane has the console's identity, and a missing or failing provider leaves the start standing and reports the command (#286). A conductor can start a peer but cannot choose its mode.
187
187
 
188
188
  ## Multi-project manager (protocol 7)
189
189
 
@@ -254,7 +254,9 @@ this production table. Startup diagnostics withhold native host/payload details.
254
254
 
255
255
  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 only a plain jcode release from 0.98.0 up to, not including, 1.0.0 (#529), 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. 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.
256
256
 
257
- 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 has a native receipt, 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). 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. 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.
257
+ 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 has a native receipt, 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). 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.
258
+
259
+ 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.
258
260
 
259
261
  # Transient ACP activity (#286)
260
262
 
@@ -3493,9 +3493,9 @@ load daemon config or infer executable commands from environment text.
3493
3493
  the host still performs each probe, while definitions validate or display it.
3494
3494
  `cli.authority` and `cli.personOnlyFlags` can only restrict existing authority;
3495
3495
  identity.ts stays authoritative and missing/unknown metadata retains today's
3496
- rule. `start.passthrough` is data only. The MCP hook is a pure plan over the
3497
- existing project/state/server-entrypoint/form tuple; every write, ordering fence
3498
- and finally cleanup remains in the CLI host.
3496
+ rule. `start.passthrough` is data only. MCP installation, ordering fences and
3497
+ finally cleanup remain in the existing CLI host; A2b removes the unconsumed
3498
+ `init.mcpPlan` sketch instead of advertising another installation path.
3499
3499
 
3500
3500
  ### A2 internal native host projections (2026-10-18)
3501
3501
 
@@ -3658,6 +3658,70 @@ import/reexport bindings through lexical symbols before classifying comparisons.
3658
3658
  Shadowed and unrelated bindings remain independent; named non-peer enum exceptions
3659
3659
  keep their exact scope. It performs no filesystem or package lookup.
3660
3660
 
3661
+ ### A2b consumed contracts and conformance (2026-10-21, #451, #625 phases 2-3)
3662
+
3663
+ Runtime `start.launch` is optional and supplies only real native argv planners.
3664
+ Kimi, Pi, local, Jcode and fresh-review children use verified host startup ports,
3665
+ not unused CLI-wrapper plans. The frozen A1 export retains its old compatibility
3666
+ shape under a separate legacy type and is never selected by runtime consumers.
3667
+ Remove unused `start.ownsTerminal`, `init`, `QuotaReader.resetOnWindow` and hooks
3668
+ `agentIdentity`, `onAttach`, `onDetach`, `conversationRevert`, `replaceSession`.
3669
+ Actual terminal ownership, identity, revert, replacement and MCP writes retain
3670
+ their existing transport/host paths. The consumed native facts hook remains.
3671
+ Remove unconsumed runtime `permission.modes` and `permission.readOnly`; native mode
3672
+ mappings, host approval gates and fresh-review isolation receipts remain
3673
+ unchanged and authoritative. Missing metadata never grants permission.
3674
+
3675
+ `attach.externalHello` explicitly admits an authenticated external process.
3676
+ `instances.mode` separately selects external or headless declaration policy,
3677
+ with an optional fixed role ceiling. MCP configuration shape grants neither.
3678
+ Claude keeps human-owned external planner/reviewer instances and its fresh
3679
+ headless reviewer exception; agy now refuses ordinary extra instances at config load (older declarations could load as inert pull-only extras).
3680
+ Qwen context is now unknown because it has no verified occupancy source; it no
3681
+ longer borrows Kimi's context parser.
3682
+ Declaration admission never proves execution-root ownership or isolation.
3683
+
3684
+ Daemon normalization dispatches through the resolved definition's `budget.reader`
3685
+ and `budget.context`. Preserve source timestamps and current session, launch,
3686
+ generation and privacy fences. Missing readers stay unknown, including ACP kinds
3687
+ without a verified occupancy source; never borrow another kind's parser or
3688
+ manufacture empty quota readings or zero occupancy.
3689
+
3690
+ The fixed-builtin restrictions above remain ceilings: `acpCommand` selects the
3691
+ Kimi-specific host startup path; `attach.admission` consumes its native TUI launch
3692
+ receipt; `resetSoftTokensOnResume` resets host-owned soft accounting;
3693
+ `crashIdentity` validates released native recovery records; `pi-rpc` owns the
3694
+ verified relay, sandbox and process-monitor lifecycle. Configured definitions
3695
+ cannot opt into these host-global assumptions. Their upgrade path is typed,
3696
+ kind-neutral host ports before external loading, not removal of scope guards.
3697
+
3698
+ Registry coherence validates base and reviewer-variant attach, factory and reader
3699
+ structure without invoking a reader or performing I/O. Conformance enumerates
3700
+ every definition and variant with distinct enrollment or an unsupported reason
3701
+ naming dedicated coverage. Driven fixtures use `definition.start.create`; Claude
3702
+ uses its real external channel. Jcode exercises its owned-ACP watchdog and Kimi
3703
+ separately exercises TUI pull-only queue behavior. Unsupported native permission
3704
+ or quota legs never imply readiness.
3705
+
3706
+ The AST guard additionally checks native defaults, source-proven peer/kind call
3707
+ arguments, exact native regexes, startsWith/includes/hasOwn predicates (including
3708
+ constant bracket spellings), and adapter-origin native instanceof dispatch.
3709
+ Aliases and shadowing remain source-bound; generic hub classes, dynamic keys and
3710
+ prose are not native identity policy. Keep the frozen core baseline empty and
3711
+ existing seed entries unchanged except the reviewed search/replacement-only
3712
+ reanchors of `roster-detached-pi-recovery` and `kimi-watch-live-owner` from
3713
+ instanceof to the equivalent native predicates. Their identity and named tests
3714
+ stay unchanged, with green-to-named-assertion proof through the seed runner. Add a
3715
+ registry START_MODE_PEERS membership mutation seed.
3716
+ Native type predicates and the external channel implementation live in adapters;
3717
+ authentication and lifecycle authority remain in the daemon. Stable hub leaf
3718
+ exports preserve source compatibility for native state readers.
3719
+
3720
+ After unidentifiable Codex cancellation, status carries a fixed
3721
+ `unidentified-cancellation` notice while its existing offline latch holds. Late
3722
+ completion cannot clear it; verified native reattachment does. No native ids or
3723
+ provider text reach the notice.
3724
+
3661
3725
  ## Amendment: playbook lifecycle and criteria checklist, 2026-10-18 (#481 S0/S8)
3662
3726
 
3663
3727
  - The AI-Native SDLC Playbook supplies the built-in stage model of an enabled lifecycle: intent (Plan), spec (Design), plan and build (Build), test (Test), review, merge and release (Deploy), maintain (Maintain). `mode` advise or enforce without `stages` selects this list. Off (no file, or `mode = "off"`) keeps the seven #257 stages, so its config, receipts, `hub_sdlc_record` schema and status output stay byte for byte. An explicit #257 list still loads: off keeps it as written, and an enabled lifecycle keeps its order under canonical ids (spec, plan, build, review, test, merge, release). Any other list needs definitions.
@@ -3724,7 +3788,7 @@ ahub builds the review brief that the workspace gate used to take from `ocr dele
3724
3788
 
3725
3789
  ## Claude peer instances I3 amendment (#450)
3726
3790
 
3727
- - A declared instance of a channel-plugin kind (Claude) is admitted when its declaration names `roles`, reviewer and/or planner only. `start_mode` and `worktree` are refused: the instance starts only from the person's terminal with `ahub claude --as <id>`, so the hub never starts, watches or owns writes for it, and it is neither startable nor reserved from external admission.
3791
+ - Superseded by A2b `instances.mode` and explicit role policy (MCP shape grants no instance admission): a declared instance of a channel-plugin kind (Claude) was admitted when its declaration names `roles`, reviewer and/or planner only. `start_mode` and `worktree` are refused: the instance starts only from the person's terminal with `ahub claude --as <id>`, so the hub never starts, watches or owns writes for it, and it is neither startable nor reserved from external admission.
3728
3792
  - The launcher sets the child's `AGENTHUB_PEER_ID` to the instance id; the channel plugin and the statusline tee already key on it. Launch, session and context state files stay `claude-launch.json`, `claude-session.json` and `claude-context.json` for the builtin and become `<base>.<id>.json` for an instance (`src/hub/claude-state.ts`). Account usage stays shared. The daemon's context and usage readers, the permission-launch check, the facts handler, terminal recovery (project root, no worktree), the upgrade blocker check and `ahub reset` read each instance's own files.
3729
3793
  - The Claude version probe stays on the builtin peer (ponytail: instances are reviewers started by hand); zero-turn detection reads each peer's own evidence, as below.
3730
3794
  - Status, which the console and dashboard read, lists each declared instance right after its kind. `ahub <kind> --as <id>` starts only an instance the running daemon bound at startup (status `activeInstances`); otherwise it asks for a restart or an upgrade before any effect.
@@ -3923,3 +3987,95 @@ The P1 host introduces additive `native_hook_launch` and `native_facts` control
3923
3987
  The collected T0 proves the plugin, hook codec and read transport, not a native prompt override. P1 therefore keeps all runtime modes unmanaged and refuses unattended/headless/resume selectors until those individual mappings are proved. The old daemon refuses the additive launch request rather than silently weakening hook identity. An actual project TUI launch requires a running daemon with this feature; source/fixture verification does not claim that live rollout.
3924
3988
 
3925
3989
  Version line (#589, owner decision 2026-10-20): agy updates itself, so the launcher accepts the 1.4.x line instead of exactly 1.4.2. 1.4.2 launches silently; another 1.4.x patch launches with one stderr warning naming the observed version and the 1.4.2 receipt; any other version, unparseable output or a failing `--version` is refused before a hub connection or plugin write. A T0 re-run on a newer patch is a separate follow-up.
3990
+
3991
+
3992
+ ## Amendment: no-write owner advisories (#498)
3993
+
3994
+ - A completed native owner turn on an accepted `in_progress` `implement` or `bulk_edit` task counts only when its snapshot diff is known empty, HEAD and shared local/remote branch refs (`refs/heads/*`, `refs/remotes/*`) are known unchanged, and it has no task-tool effect. `task_sweep.no_writes_turns` is a live nonnegative safe integer, default 2; 0 disables it independently of the disabled-by-default time sweep. A change, successful mutation (including accept with a plan), pending or uncertain mutation, or unknown capture breaks the streak. Read-only tools do not reset it. Owner/acceptance changes start a new generation. Counters are run-local; structured threshold stalls persist through the existing stall store and deduplicate notices per cause. The planner notice names the public task title and consecutive count.
3995
+ - Done remains advisory. For writing classes only, the review request adds the exact line `no writes recorded since acceptance` when there are no branch refs, review heads (`refs.commit`, `sdlc.head`, `sdlc.localHead`) or recorded PR, no owner touches for this task since the first acceptance after its current ownership boundary, and a proven empty diff from that acceptance snapshot to now. Canonical-root D2 evidence is unknown when recorded elsewhere hints or the current worktree scope cannot exclude work elsewhere. Re-acceptance to revise a plan preserves this D2 baseline and earlier touches; D1 still fences each turn against the latest acceptance. The host records the resulting boolean on the done history entry; models cannot supply it. Other classes are exempt. Existing privacy screening and review authority remain unchanged.
3996
+ - The first explicit acceptance/self-claim of the ownership span captures its baseline through the guarded host `ownerAccepted` callback; later re-acceptance cannot move it. An acceptance manufactured by done has no pre-work baseline and leaves D2 unknown; it adds no capture ahead of the existing review projection. Acceptance and done snapshots use the existing hardened snapshot machinery and shared 250 ms burst budget. Declared instances require their current execution adapter/root binding; canonical-checkout dirtiness cannot substitute for an instance's evidence. Unknown, disabled, private, missing/pruned or restart-lost captures never become empty evidence. Native writing-turn boundaries hash shared branch refs with one budgeted `for-each-ref` probe, without exposing names. HEAD or any shared branch-ref movement is progress, including commit-only turns in ignored worktrees. A canonical-root turn with recorded branch/commit, `.worktrees` paths or SDLC PR/head evidence remains unknown when its visible tree/refs are empty; a canonical tree cannot disprove work elsewhere. Even without recorded hints, negative canonical evidence additionally requires a bounded `git worktree list --porcelain -z` proof of a sole worktree at both boundaries and no ignored `.worktrees` directory. Uncommitted/detached work elsewhere therefore remains unknown before its first ref movement. The shadow repository compares its own private Git directory as the registered bare main-worktree path, preserving the same metadata validation and unknown-on-failure rules. Builtin-owner D1 is deliberately inert while `.worktrees` exists; the upgrade path is binding each owner to its actual execution worktree. Both probes cap output at 1 MiB and share the same snapshot deadline; no raw ref names or worktree paths escape. Check start/cancel, escalation and memory correction are task-tool effects alongside existing mutations; read-only check results are not. No snapshot is added during a private snapshot window; a PII notice, if trusted evidence is available, uses only `publicTitle`. Capturing private evidence and restoring acceptance baselines across daemon restarts remain outside this advisory.
3997
+ - Structured `no-writes` stalls feed the S9 grooming digest and status. S7 consumers receive additive `stalls.noWrites` and `stalls.doneWithoutWrites` counts from cost reporting over that report's existing bounded, public, repository/PR-scoped tasks; they are not a count of every board row. No model call, automatic reassignment, done gate or new write endpoint is added.
3998
+
3999
+ ## Amendment: config-only ACP adapters, A3 (#451, #486)
4000
+
4001
+ A3 adds declarative ACP definitions. It does not add A4 module loading, executable
4002
+ callbacks or config-selected imports. `adapters.<kind>` is accepted only from a
4003
+ Git-confirmed untracked `.agenthub/config.local.json`; the entire declaration is
4004
+ machine-local authority, not just its command and arguments. A tracked local file
4005
+ or a shared `config.json` cannot introduce one. Validate plain own-data objects
4006
+ and bounded arrays before constructing definitions. Unknown fields, accessors,
4007
+ inherited authority and collisions with builtin or reserved identities refuse.
4008
+
4009
+ Each declaration names `transport: "acp"`, `command`, optional `args`,
4010
+ `displayName` and `roles`, with optional `identity: { name, version }`,
4011
+ `permission` (hub-mode to native-mode mapping) and `readOnly` (native mode id).
4012
+ Adapter role defaults cannot appoint a conductor; a person can still select one through explicit project role policy and its existing authority checks. Other kind-meaning capabilities remain builtin-only. A project captures one
4013
+ immutable catalog at startup; declared kinds and their explicitly declared
4014
+ instances resolve through that catalog, never a mutable global registry. Adapter
4015
+ changes, including removal and their derived defaults, require a restart.
4016
+
4017
+ Configured kinds are optional, cloud and headless-only, off roster by default.
4018
+ They do not change default routing or inherit another kind's identity, quota,
4019
+ context parser or privileged capability. A configured kind may join the roster
4020
+ through the existing person-controlled path. Roles, exact nominations and
4021
+ reservations, family independence, instance execution-root admission and PII
4022
+ refusal use the same host routing rules as builtin definitions. Kind preferences
4023
+ may expand to declared instances; an explicit instance or canonical id remains
4024
+ exact. Absence of a permission map means unmanaged native policy. Absence of a
4025
+ budget block exempts the registered kind from quota accounting; it is not a zero
4026
+ reading, while unknown external ids retain existing manual accounting.
4027
+
4028
+ The identity pin is optional in the declaration schema so an unverified adapter
4029
+ can be described, but a start without it refuses before a prompt. Initialize must
4030
+ return the pinned agentInfo name and version. A resumed `session/load` must echo
4031
+ the requested sessionId; a missing or different echo refuses before a prompt.
4032
+ No implicit fresh-session fallback repairs an identity failure. Explicit session selectors must be nonblank, bounded and control-free. A configured start in progress refuses another start rather than joining its result; an attached peer accepts an already-running result only for the same requested native session (or no selector). Config argv that
4033
+ selects a leader or attaches to another native process is refused at load.
4034
+
4035
+ Owned-process admission has a deliberately bounded proof. The pinned native
4036
+ foreground contract must be supported by a T0 receipt; the spawned pid, original
4037
+ process group and process signature must have positive current evidence before
4038
+ admission and before stop. Unknown ownership refuses. Stop signals only that
4039
+ original verified owned group, never a pre-existing leader or any process outside
4040
+ it. ACP provides no generic kernel-bound responder attestation, and A3 adds no
4041
+ protocol extension pretending to provide one. A native agent that delegates its
4042
+ responses to an unowned process is unsupported by this boundary.
4043
+
4044
+ The Grok proof target is the config-only `grok agent stdio` command, with no
4045
+ leader or attach flags and with explicit `--no-leader`. Config load refuses a Grok kind, Grok executable or Grok native-identity pin without that standalone opt-out before argv termination; valued forms such as `--no-leader=false` also refuse. Native `--leader` and `--leader-socket` remain refused even alongside the opt-out. This is necessary because the native default inherits `[cli] use_leader` from ambient config. Its T0 must record that default and run under isolated HOME and Grok home in a
4046
+ scratch project, preserve the person's config bytes, record the pinned build's
4047
+ process tree, and inspect sockets/IPC while a prompt runs for links to processes
4048
+ outside the owned group. It must separately record initialize identity, load echo,
4049
+ native mode support or absence, usage unknowns and an actual hub tool receipt.
4050
+ If in-process service and the no-external-IPC boundary cannot be established,
4051
+ Grok remains explicitly unsupported; the generic ACP path may still ship with a
4052
+ fake ACP transport receipt. This amendment describes the implementation contract,
4053
+ not completed Grok live verification. Fixture results are not a native T0 receipt.
4054
+
4055
+ The optional definition `readOnly` capability names a native mode; it does not
4056
+ select that mode for every instance. An instance selects it explicitly with
4057
+ `readOnly: true`. Admission requires the native mode to be advertised and its
4058
+ selection acknowledged before a prompt, plus the host's read-only kind/tool gate
4059
+ and MCP mutation refusal. The selection is non-raisable: runtime and stored
4060
+ permission changes cannot lift it. A missing mode, failed acknowledgement or
4061
+ unknown policy refuses rather than falling back to an ordinary permission mode.
4062
+ Mapped ordinary modes likewise require advertised native support and an
4063
+ acknowledged selection; no map remains unmanaged and grants no hub sandbox claim.
4064
+
4065
+ Configured ACP crash resume reads only a bounded, control-free session id and requires exact session/load echo; it never imports a model override from old launch metadata. Controlled recovery remains explicitly refused for configured ACP because no terminal/recovery driver has been verified. Recovery source and target capability checks require `configured_acp` before reinterpreting configured identities or taking recovery effects. The initial Grok 1.0.62 isolated receipt found no initialize agentInfo and a refused session/new, so it does not establish native readiness; see `docs/verification/2026-10-21-grok-config-acp-t0.md`.
4066
+
4067
+ MCP publication appends project start targets only from authenticated daemon catalog metadata, preserving the builtin default enum. Read-only instances publish only the R2 read/review tool set; every mutation remains independently refused at the host boundary. Incomplete catalog metadata withholds configured start targets. If active lifecycle metadata is unavailable, tool discovery may retain the ordinary list until current status arrives; the host still independently refuses read-only mutations, and discovery never grants authority. A malformed generic session/new identity refuses before admission, and failed spawns are released only on positive no-process/empty-group evidence.
4068
+
4069
+ Task/conductor MCP refusals retain their existing error text and also set the optional `isError: true` result flag, so SDK consumers observe an unsuccessful tool call instead of an ordinary text result.
4070
+
4071
+ Configured ACP permission requests never use hub-tool titles for automatic approval. Requests use the ordinary person approval path unless the explicit permission-mode policy authorizes them, even when a title spells a known hub tool; builtin Kimi/Qwen title handling stays confined to those verified contracts. Owned-group identity across exec is the live child plus PID, parent, process group and start time; command/comm changes are evidence, not a new owner. Group teardown deliberately freezes, re-verifies and kills the original group; unreadable re-verification sends no further signal and retains the owner for an explicit retry.
4072
+
4073
+ ## Adding an AI tool guide and adapter definitions (#451 A5)
4074
+
4075
+ - Human-oriented guide `docs/adding-an-ai-tool.md` documents today's builtin Adapter API 1 path (`AdapterDefinition` in `src/adapters/registry-types.ts` and `src/adapters/registry.ts`), covering:
4076
+ - Architecture and core independence: one frozen definition per kind; the core daemon, tasks, and routing inspect definition fields rather than hard-coded vendor name comparisons.
4077
+ - Transport selection: `acp` (stdio JSON-RPC), `codex-appserver`, `claude-channel`, `ws-pull` (pull-only workspace MCP plugin), and `hub-native`.
4078
+ - Conformance kit (`test/conformance/`): enrollment invariants, delivery occupancy, watchdog expiry, process-group teardown, and explicit reasons in `UNSUPPORTED` (per kind) and `UNSUPPORTED_LEGS` (per ACP leg).
4079
+ - Review and verification checklist: T0 receipt, path protection (`AGENT_CONFIG_SEGMENTS`), PII safety (`trust.onPrem`), process isolation, and documentation freshness.
4080
+ - Config-only generic ACP adapters (#451 A3, board task #217) follow the A3 amendment above, including its isolated native-proof boundary. External adapter modules (#451 A4, board task #135) remain planned; their hash-pinning and module trust rules land with A4.
4081
+ - `docs/agent-notes/adapters.md` keeps only the per-adapter rules that bind code, pointing to `docs/adding-an-ai-tool.md` and `src/adapters/registry-types.ts` for conceptual architecture and definitions.
@@ -0,0 +1,54 @@
1
+ # 0.12.44 release verification
2
+
3
+ This record covers release preparation and source review. It does not claim publication, installation, a running-hub restart or a live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.12.44, control protocol 16 (unchanged), from merged main. The person directed on 2026-10-21 to finish the work in progress and release it.
8
+
9
+ - Config-only ACP adapters (#451 A3, PR #638): `adapters.<kind>` in an untracked `.agenthub/config.local.json` declares a headless, off-roster ACP kind with a pinned native identity, a strict `session/load` echo, owned process-group proof at start and stop, no title-based auto-approval, a required project catalog on every instance and roster resolver, and an explicit `--no-leader` for Grok. Grok 1.0.62 itself stays unsupported (no `initialize` agentInfo; T0 receipt in `2026-10-21-grok-config-acp-t0.md`). The CLI no longer stops `permission` and `settings` on an unreadable local config, and `settings undo` resolves configured keys (PR #642).
10
+ - Adapter definitions are the dispatch point for quota and context readers, admission and instance policy, with registry-driven conformance enrollment and a widened vendor-literal guard (#451 A2b, PRs #630 and #631); the "Adding an AI tool" guide (PR #633).
11
+ - agy no longer reads Claude's state files (#624, PR #627); the agy first-turn tools gap is documented and the init block updated (#607, PR #623).
12
+ - No-write owner advisories (#498, PRs #626 and #629), the headless watch pane (#286 AC10, PR #628), the Kimi admission release on an invalid `kimi_cmd` (#549, PR #622), the recovery lease log nits (#479, PR #632), the injected memory store alias (#237 follow-up, PR #640), and two macOS-only test flakes (#634, PR #639).
13
+ - Still open: #558 (a checks-runner test that fails on hosted macOS after an earlier `upstream reset`), the #230 S10 rulings and their implementation (spec PR #641, PRs #643 and #644, merged after this release), #612, and every live leg listed in [the 0.12.40 record](2026-10-20-0.12.40.md).
14
+
15
+ ## Reviews
16
+
17
+ | PR | Final head | Review | CI |
18
+ | --- | --- | --- | --- |
19
+ | #622 (#549) | 356db622 | Board review before this release window (details in the PR) | exact head, success |
20
+ | #623 (#607) | 71ac68e9 | Author change; connector finding (profile templates) fixed and answered | exact head, success |
21
+ | #626 (#498) | 661183cc | Two rounds with independent read-only reviewers; R0 false-stall finding fixed; follow-ups in #279 | exact head, success |
22
+ | #627 (#624) | 44543a7e | OCR delegation review (no Important finding); two red-first daemon tests reproduce the field symptom | exact head, success |
23
+ | #628 (#286) | a0478105 | Reviewer read: fixed argv, no caller text, provider failure never fails the start | exact head, success |
24
+ | #629 (#498) | 507a4be2 | Reviewer read and affected tests 121/0; connector finding (case-insensitive `.worktrees` hint) fixed | exact head, success |
25
+ | #630 (#451) | b0be266f | Two independent read-only reviews (runtime; tests and guard) and 356 affected tests run by the reviewer; follow-ups in #280 | exact head, success |
26
+ | #631 (#451) | 5011c25d | Reviewer read and 525 affected tests; one macOS rerun of the #558 flake | exact head, success |
27
+ | #632 (#479) | 721c6514 | Reviewer read and 340 affected tests; one macOS rerun of the #558 flake | exact head, success |
28
+ | #633 (#451 A5) | 809b5264 | Two rounds: sections describing unshipped A3/A4 turned into planned notes; connector findings fixed | exact head, success |
29
+ | #638 (#451 A3) | d1d06f58 | Two rounds with independent trust-boundary and runtime reviews; title auto-approval, roster removal, optional-catalog fallback and exec ownership fixed; a surviving seed fixed (#283); a connector finding (empty permission map) fixed | exact head, success |
30
+ | #639 (#634) | 97feb6e0 | Reviewer read; connector finding (a gate that never opens) fixed | exact head, success |
31
+ | #640 (#237) | 1c080d15 | Codex review; a connector P1 (stale injected-store shard) fixed and re-read by the merger | exact head, success |
32
+ | #642 (#638 follow-up) | see release PR | Independent read-only review; its findings applied | exact head, success |
33
+
34
+ ## Document review
35
+
36
+ Fourteen documents were stale against the 0.12.43 stamps. Each was checked against `git log <stamp>..HEAD` over its paths by read-only reviewers, in three passes (user docs, area notes, and the #638/#640 delta), and the edits were applied on the release branch. All fourteen are restamped to the release branch commit `e185b9e6`, which includes #642, and the notes' stamp paths gained the new sources they describe.
37
+
38
+ | Document | Result | Checked claims and correction |
39
+ | --- | --- | --- |
40
+ | `README.md` | corrected | Status 0.12.44; watch pane, no-write advisories, agy extra-instance refusal and config-only ACP adapters in the 0.12.44 summary; verification link. |
41
+ | `docs/quickstart.md` | corrected | Install pin `v0.12.44`; "Upgrading to 0.12.44" (init refresh, agy fix, watch pane, no-write notice, config-only ACP, `configured_acp` upgrade floor, stricter `peers.<id>`); plugin restart wording. |
42
+ | `docs/operations.md` | corrected | New "Configured ACP adapters" operator section; automatic watch pane; Qwen context unknown; agy extra-instance refusal. |
43
+ | `docs/security.md` | corrected | Watch pane under the fixed-words rule; configured ACP trust boundary; no title auto-approval for configured kinds; agy extra-instance refusal. |
44
+ | `src/cli/help.ts` | no change | The configured-kind usage row matches the CLI parser; no other command, subcommand or flag changed. |
45
+ | `docs/agent-notes/adapters.md` | corrected | Configured-kind roles, error withholding and CLI flags; no title auto-approval and the read-only kind filter; `AcpOwnedGroup` stop rules. |
46
+ | `docs/agent-notes/daemon.md` | corrected | `configured_acp` recovery floor; project adapter catalog binding and locked rows; configured ACP start rules; injected-store memory alias. |
47
+ | `docs/agent-notes/tasks.md` | corrected | Project catalog resolution; hub tool publication never authority; `no-writes.ts` in scope. |
48
+ | `docs/agent-notes/budget.md` | corrected | Declared reader dispatch; `Budget.report` resolves through the project catalog. |
49
+ | `docs/agent-notes/tests.md` | corrected | Conformance enrollment maps; gated fake actions; owned-group test ports. |
50
+ | `docs/agent-notes/README.md`, `benchmarks.md`, `bus.md`, `local-worker.md` | no change | Rules checked against the diffs; no contradiction. |
51
+
52
+ ## Release mechanics
53
+
54
+ Merge the release PR with a merge commit (the stamps name release-branch commits), run `node scripts/check-docs.mjs --require-fresh` on the merged main, then tag `v0.12.44`.
@@ -0,0 +1,30 @@
1
+ # Grok config-only ACP T0, 2026-10-21
2
+
3
+ This receipt separates the generic ACP implementation from native Grok readiness. It does not establish #486 AC1-AC6 or #451 AC3 as complete.
4
+
5
+ ## Observed native build and isolation
6
+
7
+ - Local native binary reports `grok 1.0.62 (1474f9cbdd91)`.
8
+ - Version, agent help and stdio help exited 0 under a temporary HOME, Grok home, XDG config/cache/data directories and project directory outside a Git repository.
9
+ - The protocol probe used `grok agent --no-leader stdio`, with inherited agent markers removed and a probe-specific peer identity. The probe harness copied no personal config or credential files and wrote only to its isolated directories; no production hub was used. The exact native-file check below is bounded, and broader native credential state remains unverified.
10
+ - The saved agent help states that the leader default is `[cli] use_leader` in `config.toml`, rather than a guaranteed foreground default; `--no-leader` forces a new agent. A plain `grok agent stdio` argv therefore does not itself establish foreground ownership in an ambient home. `--leader-socket` is also an attach selector. The config compiler therefore requires explicit standalone `--no-leader` for recognized Grok declarations before any process starts, and still refuses `--leader` and `--leader-socket`; isolated HOME alone is not substituted for the opt-out.
11
+ - Before/after SHA-256 values of the exact real Grok `config.toml` and `version.json` matched. The additionally checked `config.json`, `settings.json` and `mcp.json` were absent before and after. This bounded check is not a claim about every native configuration path.
12
+
13
+ ## Protocol result
14
+
15
+ - Initialize replied and advertised `loadSession: true`, image/audio prompt support false, embedded context true, HTTP/SSE MCP support and session list/resume/close capabilities.
16
+ - Initialize did not provide `agentInfo`. The config-only adapter requires the observed native name/version pin to match initialize before creating a session or prompting. The native build therefore has no usable runtime identity pin under this contract.
17
+ - The separate raw protocol probe attempted `session/new` in the isolated project with an empty MCP server list. It received an RPC error. Its raw error payload was neither logged nor published; the cause remains unclassified.
18
+ - No model prompt or native MCP tool ran. Session/load echo, session/set_mode, usage/occupancy and a real hub-tool result are unverified. Advertising load or MCP support is not proof of those operations.
19
+ - Because no prompt ran, no process-tree and socket/IPC observation while a prompt was active exists. The required no-detached-leader evidence is unverified.
20
+
21
+ ## Owned group and teardown
22
+
23
+ - The runtime ownership helper positively verified the direct child, its original process group and shared process signature at start and immediately before teardown.
24
+ - The first probe's teardown returned `unconfirmed`. A fresh process-table read after the probe exited showed no Grok process. This is not recorded as a confirmed stop receipt.
25
+ - A post-SIGKILL defunct-row observation edge was fixed and covered by deterministic regression tests: after the last signal, wait for the original group to disappear within the bound, tolerating command/parent changes of a dying row while preserving PID/start/group identity. Pre-signal ownership checks remain strict, and no process outside the spawned group is signalled.
26
+ - A second isolated probe with the same native build again lacked initialize identity and had session/new refused. With the corrected observation fence, start and pre-stop group checks passed, group disappearance was confirmed, and a separate fresh process-table read found no Grok process. The exact real-config hashes again matched. This confirms teardown for this refused attempt, not foreground behavior during a model prompt.
27
+
28
+ ## Readiness decision
29
+
30
+ Grok remains unsupported by this receipt. The generic config-only ACP path is covered independently with the repository's fake ACP transport, including identity mismatch, load-echo refusal, owned-group checks, permission ACKs, read-only ceilings, task delivery and project isolation. Native Grok admission still needs a build with matching initialize identity plus successful isolated session creation, actual hub-tool execution, load-echo evidence and foreground tree/IPC proof during a prompt. No permission, quota, sandbox, planner or reviewer claim is inferred from these observations.