@staix/agent-hub 0.12.34 → 0.12.35
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.
- package/CHANGELOG.md +13 -0
- package/README.md +3 -3
- package/docs/agent-notes/adapters.md +2 -0
- package/docs/agent-notes/budget.md +3 -1
- package/docs/agent-notes/bus.md +2 -1
- package/docs/agent-notes/daemon.md +5 -1
- package/docs/agent-notes/local-worker.md +1 -1
- package/docs/agent-notes/tasks.md +4 -1
- package/docs/gsd-collaboration-analysis.md +5 -1
- package/docs/operations.md +67 -13
- package/docs/quickstart.md +8 -2
- package/docs/sdlc.md +59 -34
- package/docs/security.md +3 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +161 -6
- package/docs/verification/2026-10-18-0.12.35.md +49 -0
- package/docs/verification/2026-10-18-493-claude-busy-channel.md +91 -0
- package/docs/verified.json +21 -16
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +15438 -14863
- package/src/adapters/acp.ts +3 -1
- package/src/adapters/claude-channel.ts +59 -29
- package/src/adapters/codex-appserver.ts +1 -1
- package/src/adapters/definitions/native-cli-entry-codex.ts +13 -5
- package/src/adapters/definitions/native-cli-entry-jcode.ts +4 -1
- package/src/adapters/definitions/native-cli-entry-kimi.ts +16 -11
- package/src/adapters/definitions/native-cli-entry-local.ts +3 -2
- package/src/adapters/definitions/native-cli-entry-pi.ts +8 -6
- package/src/adapters/definitions/native-cli-entry-qwen.ts +3 -2
- package/src/adapters/definitions/native-cli-entry-types.ts +1 -0
- package/src/adapters/definitions/native-recovery.ts +2 -2
- package/src/adapters/definitions/qwen-native.ts +2 -2
- package/src/adapters/local-worker.ts +3 -2
- package/src/adapters/pi.ts +3 -1
- package/src/adapters/registry-types.ts +3 -0
- package/src/adapters/registry.ts +24 -8
- package/src/cli/identity.ts +3 -3
- package/src/cli/launch.ts +29 -4
- package/src/cli/main.ts +101 -28
- package/src/cli/preview.ts +16 -5
- package/src/cli/recovery-guidance.ts +39 -22
- package/src/cli/terminal-recovery.ts +68 -16
- package/src/cli/upgrade-restore-state.ts +16 -7
- package/src/cli/upgrade-runtime.ts +67 -39
- package/src/cli/upgrade.ts +43 -27
- package/src/hub/budget.ts +55 -3
- package/src/hub/bus.ts +1 -1
- package/src/hub/conductor.ts +16 -10
- package/src/hub/config-apply.ts +13 -2
- package/src/hub/config-reload.ts +11 -1
- package/src/hub/config-trust.ts +1 -1
- package/src/hub/control-client.ts +1 -1
- package/src/hub/crash.ts +14 -5
- package/src/hub/daemon.ts +296 -125
- package/src/hub/execution-budget.ts +3 -3
- package/src/hub/hub-tools.ts +17 -6
- package/src/hub/instance-budget.ts +73 -0
- package/src/hub/instance-config.ts +47 -0
- package/src/hub/instance-execution.ts +50 -0
- package/src/hub/instances.ts +100 -0
- package/src/hub/lifecycle-schema.ts +7 -3
- package/src/hub/permission-mode.ts +7 -7
- package/src/hub/roster.ts +7 -6
- package/src/hub/routing.ts +82 -18
- package/src/hub/sdlc-queue.ts +2 -2
- package/src/hub/sdlc.ts +84 -38
- package/src/hub/settings.ts +34 -6
- package/src/hub/snapshots.ts +40 -9
- package/src/hub/start-mode.ts +7 -4
- package/src/hub/status-roster.ts +8 -0
- package/src/hub/tasks.ts +53 -23
- package/src/hub/text-projection.ts +7 -3
- package/src/hub/workflow.ts +30 -52
- package/src/pi/launch.ts +4 -3
- package/src/ui/index.html +1 -1
- package/templates/AGENTS.block.md +2 -1
package/docs/security.md
CHANGED
|
@@ -7,7 +7,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
7
7
|
- **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
|
|
8
8
|
- **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. External clients cannot claim the console user's id or a hub-managed peer's id.
|
|
9
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.
|
|
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`) apply only from a config file git confirms nobody committed: `.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 has a 10 s cap). 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`).
|
|
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 has a 10 s cap). 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.
|
|
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.
|
|
@@ -163,13 +163,13 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
|
|
|
163
163
|
|
|
164
164
|
## Dashboard settings and the settings session (issue #269)
|
|
165
165
|
|
|
166
|
-
- 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 `
|
|
166
|
+
- 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`, `terminal` and `instances` are refused by name from every surface: they choose what the hub runs or where secrets and task text go.
|
|
167
167
|
- `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.
|
|
168
168
|
- 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.
|
|
169
169
|
- 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.
|
|
170
170
|
- 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.
|
|
171
171
|
- 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.
|
|
172
|
-
- 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.
|
|
172
|
+
- 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). A declared extra instance accepts only `headless` from any surface. 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.
|
|
173
173
|
|
|
174
174
|
## Multi-project manager (protocol 7)
|
|
175
175
|
|
|
@@ -1837,7 +1837,8 @@ push `accepted` and show none of them.
|
|
|
1837
1837
|
selection excludes pull-only peers; only explicit role-specific naming or an
|
|
1838
1838
|
honored owner reservation permits assignment.
|
|
1839
1839
|
- `hub_inbox` sends `inbox`. The daemon answers "not attached yet; retry"
|
|
1840
|
-
between welcome and attach
|
|
1840
|
+
between welcome and attach. Since #493 the current channel owner may also
|
|
1841
|
+
explicitly pull its queued work, including while its native turn is busy.
|
|
1841
1842
|
`Bus.pull` is held by every hold a drain respects (`Bus.held`: recovery, an
|
|
1842
1843
|
uncertain delivery, a console, budget or conductor pause), though unlike a
|
|
1843
1844
|
drain it does not wait for the peer to be idle; a held pull journals nothing
|
|
@@ -1876,6 +1877,45 @@ push `accepted` and show none of them.
|
|
|
1876
1877
|
- Control protocol 16 adds the hello `channel` field and `inbox`. Recovery
|
|
1877
1878
|
sources add protocol 15 (0.12.17 through 0.12.19); the transition from a real
|
|
1878
1879
|
0.12.19 hub is not yet proven.
|
|
1880
|
+
|
|
1881
|
+
## Amendment: channel queue visibility and explicit pull (issue #493)
|
|
1882
|
+
|
|
1883
|
+
- A channel session's `hub_inbox` reads the daemon queue through the same
|
|
1884
|
+
authenticated `peer` socket as a tools-only Claude session. The daemon checks
|
|
1885
|
+
current socket ownership and the shared roster, pause, recovery, lifecycle and
|
|
1886
|
+
uncertain-delivery holds. It does not require an idle native turn. Tools-role
|
|
1887
|
+
sockets cannot consume another transport's queue.
|
|
1888
|
+
- `Bus.pull` claims one batch synchronously and completes it with its bus
|
|
1889
|
+
checkpoint in the existing journal transaction. A later idle drain or another
|
|
1890
|
+
concurrent inbox call cannot hand those originals out again. Existing accepted
|
|
1891
|
+
pushes remain independent; a pull does not complete them. The hand-out-before-
|
|
1892
|
+
model-read ceiling documented above remains. Framing, `sanitize`, public PII
|
|
1893
|
+
projections and task-offer read evidence retain their existing paths.
|
|
1894
|
+
- The plugin also returns its existing bounded local buffer of failed pushes
|
|
1895
|
+
without journal ids. Journaled failed pushes stay in `needs_review`; the plugin
|
|
1896
|
+
never consumes them from this local buffer. A daemon refusal or an unavailable
|
|
1897
|
+
connection cannot prevent reading that local fallback.
|
|
1898
|
+
- `delivery_holds` adds optional same-peer `queued` and `important` counts, with
|
|
1899
|
+
no bodies or caller-selected target. Every agent-hub tool-call result can show
|
|
1900
|
+
fresh counts when important work waits. Channel and tools-mode sessions remain
|
|
1901
|
+
silent when an older daemon supplies no counts. Plain pull-only sessions keep
|
|
1902
|
+
their old advisory nudge compatibility. A current zero clears old nudges;
|
|
1903
|
+
unavailable metadata never changes a saved result or its effect classification.
|
|
1904
|
+
Inbox replies show counts without suggesting a repeated refused read, and a
|
|
1905
|
+
tools-mode hint does not grant an inbox capability to that transport.
|
|
1906
|
+
- `[FYI]` remains record-only. PR ready, done or submitted, ruling-needed,
|
|
1907
|
+
blocker and changed-contract/path messages require an unmarked or important
|
|
1908
|
+
update. `hub_task_done` and `hub_review` already notify the assigned audience;
|
|
1909
|
+
an extra chat with the same outcome is unnecessary.
|
|
1910
|
+
- Native busy-notification evidence and the decision about channel steering are
|
|
1911
|
+
recorded separately in `docs/verification/2026-10-18-493-claude-busy-channel.md`.
|
|
1912
|
+
Claude Code 2.1.302 / Sonnet 5.5 returned the random notification token before
|
|
1913
|
+
the original interactive turn ended. Push-capable `WsPeer` therefore exposes
|
|
1914
|
+
important-only steer through the existing journal/generation-bound push path;
|
|
1915
|
+
detach and pull-only attach remove that capability. Ordinary messages remain
|
|
1916
|
+
queued while busy. A notification write alone is not proof that the native
|
|
1917
|
+
model saw it; the earlier print-mode probe was inconclusive.
|
|
1918
|
+
|
|
1879
1919
|
## Amendment: owner and reviewer read their task (issue #208)
|
|
1880
1920
|
|
|
1881
1921
|
`hub_task_show` stays a conductor tool, and a task's current owner and current
|
|
@@ -2398,7 +2438,7 @@ the queue.
|
|
|
2398
2438
|
- Tasks owns optional board artifact receipts and every mutation. Repository, issue/PR ids, claimed local result/head and a reviewed full SHA are distinct from fresh external evidence. Only the owner/reviewer/person records; only the reviewer/person binds a review head. Invalid identities, unsupported externally supplied facts and private-turn cloud records are refused before effects.
|
|
2399
2439
|
- Enforce rechecks the current PR head, successful required checks, local receipt on that head, GitHub review and complete unresolved-thread readback before approval. Missing/unknown data blocks, a new head invalidates derived readiness, and changes requested needs no green CI. Advise exposes gates without additional blocks; off retains existing transitions. Historical board approval is not approval of a new head.
|
|
2400
2440
|
- CLI sdlc status/explain reads bounded structured metadata, not remote bodies. Shared console/dashboard views expose unknown external evidence and the next actor/action; they cannot certify freshness. No merge/release authority is granted.
|
|
2401
|
-
-
|
|
2441
|
+
- Later custom definitions and read-only merge/release reports are described below. #481 supersedes the original lifecycle vocabulary and workflow option; a full native advise cycle remains pending. See docs/sdlc.md.
|
|
2402
2442
|
## Amendment: optional Qwen Code 0.25.0 ACP peer (#256)
|
|
2403
2443
|
|
|
2404
2444
|
- `qwen` joins the known catalog with default participation off. Only the project's roster selects it; no second enabled switch or default routing preference is added. Its flow roles default to implementer/verifier, which is guidance, not a claim of native reliability.
|
|
@@ -2422,9 +2462,9 @@ the queue.
|
|
|
2422
2462
|
- Activity contains delivery ids/senders, message/thought chunks, bounded tool summaries, plans, permission request/outcome ids and turn end. It never enters persistent events/logs, UI, tools or cloud memory. Existing final answers still travel through the answer bus.
|
|
2423
2463
|
- Kimi TUI defaults remain pull-only. Qwen TUI and native visualizer support remain gated by unverified T0 checks. See [verification](../verification/2026-10-11-acp-watch-t0.md).
|
|
2424
2464
|
|
|
2425
|
-
#
|
|
2465
|
+
# Historical workflow amendment, 2026-10-11 (#257, superseded by #481)
|
|
2426
2466
|
|
|
2427
|
-
-
|
|
2467
|
+
- The original optional workflow selector used existing SDLC receipts and Tasks authority; its current replacement is the #481 amendment below. A reviewer/person-owned checklist stores bounded criterion ids, hashed evidence refs and verified/failed/unverified claims bound to the reviewed head.
|
|
2428
2468
|
- Existing task/predecessor ids must match current Tasks.after edges; missing/cyclic/private cross-links are refused before effects. Imports do not change task ownership, reservations or dependencies.
|
|
2429
2469
|
- Required unknown/failed criteria and stale heads keep readiness false. Advise remains nonblocking; enforce adds these only to existing approval gates. Route explain, console/dashboard lifecycle views and read-only hashed exports derive the same board/config state. No second writable workflow store, automatic advance, permissions, merge/release or study execution.
|
|
2430
2470
|
|
|
@@ -2508,12 +2548,12 @@ pii.paths and data.readonly accept exact paths/subtrees and *, ? and whole-segme
|
|
|
2508
2548
|
|
|
2509
2549
|
## Amendment: custom lifecycle data (#257)
|
|
2510
2550
|
|
|
2511
|
-
Optional ordered stage definitions map at most 32 bounded ids to the
|
|
2551
|
+
Optional ordered stage definitions map at most 32 bounded ids to the semantic bases, existing actor guidance and closed entry/exit gates (updated to nine bases and the criteria gate by #481 below). Explicit after edges name at most eight earlier configured ids; missing/self/forward/cyclic references refuse. Review and merge base destinations are required by existing Tasks completion transitions. Built-in names cannot change base. Existing holder/person authority and the full approval floor remain; enforce additionally checks an explicit custom stage-change actor without granting a new caller authority. Off/advise preserve claim behavior. Config-aware receipts, projections and every managed native/local task schema use the same stage ids. Historical removed definitions remain unknown, not silently remapped. No new state, executable gate, auto-advance, merge, release, import authority or native success is introduced.
|
|
2512
2552
|
|
|
2513
2553
|
|
|
2514
2554
|
### Custom lifecycle review corrections (#257)
|
|
2515
2555
|
|
|
2516
|
-
|
|
2556
|
+
The original phase gate validation is superseded by the criteria gate and compatibility window in #481 below. Authenticated daemon status gains optional lifecycleStages metadata, with no protocol bump. Separate MCP tools lists use that active snapshot; a live legacy daemon without the field keeps seven stages, while unknown/unavailable metadata withholds the lifecycle-record tool. Editing project data during a live daemon no longer changes its advertised stage ids.
|
|
2517
2557
|
|
|
2518
2558
|
|
|
2519
2559
|
## Backlog foundation (#263)
|
|
@@ -3502,3 +3542,118 @@ The guard resolves immutable peer-id constant aliases and supplied-source
|
|
|
3502
3542
|
import/reexport bindings through lexical symbols before classifying comparisons.
|
|
3503
3543
|
Shadowed and unrelated bindings remain independent; named non-peer enum exceptions
|
|
3504
3544
|
keep their exact scope. It performs no filesystem or package lookup.
|
|
3545
|
+
|
|
3546
|
+
## Amendment: playbook lifecycle and criteria checklist, 2026-10-18 (#481 S0/S8)
|
|
3547
|
+
|
|
3548
|
+
- 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.
|
|
3549
|
+
- Valid bases are the nine playbook stages plus the #257 names `implement` and `verify`, which mean build and test. A stored `implement` or `verify` receipt resolves to `build` or `test` in a playbook config; a write with the old id stores the id the config lists. Custom definitions keep loading with their old base names; a playbook-only built-in id (intent, build, test, maintain) cannot take another base. The test base takes the reviewer actor, like verify. Status carries `playbook` (the playbook stage) whenever the mode is not off; the dashboard shows it beside the stage.
|
|
3550
|
+
- The head-bound acceptance-criteria checklist (`criteria` in a receipt) belongs to the Compliance pass. It keeps id, head, 1..32 criteria with required, result and hashed refs, reviewer- or person-only writes and the reviewed-head binding; criterion ids may name issue AC ids (`AC8b`). Once recorded it adds its blockers in any enabled lifecycle; off ignores it unless the retired setting below asks for it. A `criteria` entry or exit gate needs no workflow selector: a missing checklist is a blocker the reviewer can clear.
|
|
3551
|
+
- Checklist task import is removed. A non-empty `tasks` list is refused before any write; dependencies stay with `Tasks.after` (`ahub task propose --after`). A receipt stored under `phase` reads as criteria, and its task list is shown with a note and never gates readiness. Board split-undo still treats such a stored list as a reference.
|
|
3552
|
+
- Deprecation window (constant `RETIRED_WORKFLOW_FROM`, 0.13.0): before it, `workflow = "none"` loads silently, `workflow = "gsd-lite"` loads with a `hub.log` warning and requires a criteria checklist on every task, the `phase` gate loads as an alias of `criteria` with a warning, and the `phase` receipt name is accepted silently. From 0.13.0 each is refused with a field-level reason; the aliases and their tests are then deleted.
|
|
3553
|
+
- Tool schema: when the mode is not off, daemon status carries `lifecycleCriteria: true`, and every managed surface (the channel and tools MCP servers and the local worker) advertises `criteria` instead of `phase` in `hub_sdlc_record`. Custom stage ids and the checklist rename are independent: off and older daemons, custom stage lists included, keep `phase`, because they accept only it. That off schema changes once, when the alias is removed at `RETIRED_WORKFLOW_FROM`.
|
|
3554
|
+
- `ahub sdlc export`: a plain off lifecycle (no retired setting) keeps the #257 export bytes (schema 1, `workflow: "none"`, `phase: null`). An enabled or retired-setting lifecycle emits schema 2: sanitized state (id, state, stage, criteria view or a private stub), `stateHash` and `sourceHash`, without the workflow field. In off, a `phase` receipt write also keeps its #257 bytes (`tasks: []`); task import stays retired in every mode, so a non-empty list is refused.
|
|
3555
|
+
- Contract amendment, seeds (419 before and after: 414 byte-unchanged, 3 modified, 2 removed, 2 added; the registry baseline is unchanged). Modified, each with the same id, test file, test name and mutation intent: `workflow-reviewer-only` (the criteria/phase check), plus `override-local-schema` and `lifecycle-native-schema`. The person approved the last two on 2026-10-18, because the `lifecycleCriteria` plumbing changed their call sites. Removed with the code they mutated: `workflow-exact-after` and `lifecycle-phase-view-required`. Added for the new guards: `criteria-task-import-retired` and `sdlc-workflow-window-closes`. Tests that pinned the retired gsd-lite behaviour (task import, phase naming, the workflow projection, the implement id stored in an enabled config) are amended in the same PR; every other test is unchanged.
|
|
3556
|
+
- S0/S8 implement none of S1-S7 and S9-S11. The issue stays the spec, Tasks owns mutations and GitHub owns PR, check and tag facts.
|
|
3557
|
+
## Headless peer instances I2 amendment (#450)
|
|
3558
|
+
|
|
3559
|
+
`instances` is a person-authored, machine-local object in an untracked
|
|
3560
|
+
`.agenthub/config.local.json`. Each additional `<kind>-<name>` id names its kind
|
|
3561
|
+
explicitly and may set `roles`, `start_mode`, `permission_mode` and `worktree`.
|
|
3562
|
+
The bound catalog belongs to one project/daemon; there is no process-global
|
|
3563
|
+
registration or suffix inference. Declarations are validated before trust
|
|
3564
|
+
filtering, cloned and frozen, and limited to 64 additional ids of at most 32
|
|
3565
|
+
ASCII characters. Settings cannot create or modify declarations. Identity,
|
|
3566
|
+
execution directory and account membership are restart-bound; a live reload
|
|
3567
|
+
cannot publish their derived defaults ahead of that restart.
|
|
3568
|
+
|
|
3569
|
+
I2 accepts definitions with a native headless factory and environment or ACP
|
|
3570
|
+
session MCP identity. Additional Claude/channel peers belong to I3. Jcode's
|
|
3571
|
+
project-file MCP identity is not isolated by this phase and is refused for extra
|
|
3572
|
+
instances. Additional instances start headlessly; the existing Codex native TUI
|
|
3573
|
+
still initializes and attaches to its instance's own app-server proxy. The hub
|
|
3574
|
+
never initializes that app-server on the TUI's behalf. `ahub <kind> --as <id>`
|
|
3575
|
+
selects the declared identity; selection is removed from native passthrough.
|
|
3576
|
+
Missing `peer_instances` evidence on an old daemon/upgrade target is a refusal
|
|
3577
|
+
before instance runtime effects, rather than a guess that the instance is a base
|
|
3578
|
+
peer.
|
|
3579
|
+
|
|
3580
|
+
Each additional implementer requires an explicit registered Git worktree at
|
|
3581
|
+
`<project>/.worktrees/<id>` with an attached branch. I2 creates no worktree or
|
|
3582
|
+
branch; I4 owns automatic worktree orchestration. A supplied reviewer worktree
|
|
3583
|
+
is checked by the same real-path, repository and registration boundary. Without
|
|
3584
|
+
a supplied directory, a reviewer may share the project checkout. The native
|
|
3585
|
+
process executes in that directory while its MCP environment retains the
|
|
3586
|
+
canonical project's hub binding. Launch/restore receipts keep canonical
|
|
3587
|
+
`projectRoot`, optional extra-instance `executionCwd` and observed `terminalRoot`
|
|
3588
|
+
separate. The terminal grouping root may be only that project root or the
|
|
3589
|
+
verified execution directory; exact live launcher, handle, incarnation and
|
|
3590
|
+
worktree evidence is still required. Native kind metadata alone never binds an
|
|
3591
|
+
extra instance. Revoked declarations or changed directories refuse reuse.
|
|
3592
|
+
Effective roles resolve from declaration roles, exact-id policy roles, kind policy
|
|
3593
|
+
roles, then definition defaults; launch validation uses the same order.
|
|
3594
|
+
|
|
3595
|
+
I2 recovery ceiling: a recovering extra Codex launcher records its launch row only
|
|
3596
|
+
after the authenticated start response supplies its execution-root binding. Until
|
|
3597
|
+
then, the coordinator retains durable pending terminal-creation and attachment-wait
|
|
3598
|
+
evidence; it never records an assumed checkout. Base Codex pre-start recording is
|
|
3599
|
+
unchanged. Earlier recording for extras requires independently verified exact-instance
|
|
3600
|
+
execution-root evidence. Past-commit resumes do not repeat pre-commit instance
|
|
3601
|
+
compatibility checks. Abort and stop-and-archive validate historical receipt structure
|
|
3602
|
+
and authenticated runtime ownership independently of mutable declarations; restore,
|
|
3603
|
+
fresh and reattach retain current declaration authority.
|
|
3604
|
+
|
|
3605
|
+
Routing expands a kind preference to its declared members and considers idle
|
|
3606
|
+
state, board WIP and queued load with stable ties. Exact manual candidates,
|
|
3607
|
+
reservations and nominations stay exact ids. Assignment and explain use the same
|
|
3608
|
+
pure decision and name the chosen instance. Reviewer independence compares
|
|
3609
|
+
families (the kind); an eligible other family takes precedence even when busy.
|
|
3610
|
+
Only when no other family is eligible may an assignment use its own family,
|
|
3611
|
+
recording `sameFamilyFallback=true` in assignment history. This independence rule
|
|
3612
|
+
also applies to an explicit same-family reviewer nomination. A declared owner of
|
|
3613
|
+
implement, bulk-edit or test work requires the current implementer role and positive
|
|
3614
|
+
host evidence tied to the actual adapter generation started in a verified noncanonical
|
|
3615
|
+
worktree; a role-only reload cannot promote a canonical reviewer process. External
|
|
3616
|
+
ids, including console recipients, retain their existing admission and explicit
|
|
3617
|
+
nomination behavior without acquiring native metadata or automatic kind expansion.
|
|
3618
|
+
|
|
3619
|
+
Quota readings, pause records and reset windows are shared by the definition's
|
|
3620
|
+
account (one account per kind in I2). Member telemetry retains its exact reporter
|
|
3621
|
+
id, native context remains per instance, and the shared account's quota pause,
|
|
3622
|
+
checkpoint, handoff and reset notice cover all its members. Existing manual
|
|
3623
|
+
pauses remain in force. A startup migration conservatively merges old alias
|
|
3624
|
+
pause rows, retaining their provenance and moves; no pause is discarded.
|
|
3625
|
+
Per-instance ports, native session paths, Pi tool ledgers and model relay
|
|
3626
|
+
callbacks do not borrow another instance's process or conversation.
|
|
3627
|
+
|
|
3628
|
+
Worktree facts and native turn snapshots use the validated execution checkout,
|
|
3629
|
+
while task and turn ids remain in the canonical hub database. An additive
|
|
3630
|
+
`turn_workspaces` receipt stores the exact extra-instance root and its snapshot
|
|
3631
|
+
fields atomically with turn creation. The legacy `turns` snapshot fields remain
|
|
3632
|
+
unknown for those rows, so an older CLI cannot restore their files into the
|
|
3633
|
+
canonical checkout. New readers hydrate the same public turn shape from the
|
|
3634
|
+
receipt. An absent receipt preserves historical external-peer undo behavior;
|
|
3635
|
+
an existing receipt requires the current trusted declaration and the same
|
|
3636
|
+
validated root before context changes or file restoration. Pruning a turn also
|
|
3637
|
+
prunes its workspace receipt. Review projections resolve converter programs, task
|
|
3638
|
+
paths and private-path policy in the execution checkout while their sandbox denies
|
|
3639
|
+
other canonical project file data, including when read_allow encloses that project.
|
|
3640
|
+
Overlap refusal remains conservative across
|
|
3641
|
+
separate worktrees; it may refuse an undo rather than assume independence.
|
|
3642
|
+
|
|
3643
|
+
The I2 seed contract amendment re-anchors only `roster-r-pi-override`,
|
|
3644
|
+
`roster-r-catalog`, `roster-result-stop-text`, `roster-detached-pi-recovery`,
|
|
3645
|
+
`data-pi-factory-wire`, `projection-local-trust`,
|
|
3646
|
+
`planning-reservation-supported` and `planner-reserved-status-row` to the
|
|
3647
|
+
contextual resolver, aggregate Pi ledger, execution directory and declared-id
|
|
3648
|
+
forms. Each keeps its id and named test and removes the same guarantee. The
|
|
3649
|
+
projection mutant still removes only `text_projection`; the status mutant still
|
|
3650
|
+
removes only reservations. The other 411 seeds and registry baseline are
|
|
3651
|
+
unchanged. The PR records every old and new search/replacement and its clean
|
|
3652
|
+
and mutant result, per the reviewer's explicit ruling.
|
|
3653
|
+
|
|
3654
|
+
## Amendment: roster membership in status, 2026-10-18 (#484)
|
|
3655
|
+
|
|
3656
|
+
- The authenticated `status` reply carries `roster: {peer, on}[]` for every supported peer, optional peers included, with the same `on` that `ahub roster list` shows. The field is additive, with no protocol bump; older CLIs ignore it.
|
|
3657
|
+
- Launchers that check membership before starting (`ahub jcode`, `ahub kimi --mode tui`) read it through `rosterInStatus` in `src/hub/status-roster.ts` (no imports, so the registry's native CLI entries add no cycle). A reply without the field (an older hub) is unknown: the launcher stops with an upgrade-or-restart remedy and starts nothing. It never reads unknown as off (the jcode bug) or as on (the dead kimi guard; daemon admission still refused off-roster Kimi, so that one was harmless).
|
|
3658
|
+
- `settings_get` stays console-only.
|
|
3659
|
+
- With declared headless instances, status includes each startup-bound extra id as well as every built-in and optional peer, with its own roster membership. No undeclared suffix acquires a row.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 0.12.35 release verification
|
|
2
|
+
|
|
3
|
+
This record covers release preparation and source review. It does not claim publication, installation, a running-hub restart or native/live acceptance.
|
|
4
|
+
|
|
5
|
+
## Candidate
|
|
6
|
+
|
|
7
|
+
Version 0.12.35, control protocol 16 (unchanged), from merged main `ac6ff4e382abcec45b5dffcc1fc85f402383dccf`. The person directed the release on 2026-10-18, first so that #493 takes effect, then to ship all open PRs once merged.
|
|
8
|
+
|
|
9
|
+
- #493 (PR #495): a busy `ahub claude` session reads its queue with `hub_inbox`, sees fresh queued and important counts, and receives important messages mid-turn; `[FYI]` is forbidden for actionable messages.
|
|
10
|
+
- #484 (PR #500): `status` carries roster membership, and `ahub jcode` and `ahub kimi --mode tui` read it; an older hub is unknown, not off.
|
|
11
|
+
- #219 task (PR #499): the protected-segment grant test no longer grows with the set (test-only).
|
|
12
|
+
- #450 I2 (PR #497): declared headless peer instances with their own native and MCP identity, worktree execution roots for extra implementers, kind routing across instances, and account-shared quota.
|
|
13
|
+
- #481 S0+S8 (PR #496): the playbook lifecycle as the enabled default, gsd-lite retired into the `criteria` checklist, and a deprecation window until 0.13.0. Off keeps its #257 bytes.
|
|
14
|
+
- Still open (unchanged): live native clients, installed managed-Pi #418 AC8 (#142), #438 AC7, the native console settings leg (#439 AC5), a real upgrade with reattach (#463), a real upgrade with a managed terminal and cancelled rows (#478), and the live advise cycle of #481 AC9.
|
|
15
|
+
- New open legs:
|
|
16
|
+
- After installation, a busy `ahub claude` session reads its queue with `hub_inbox` and sees a steered important message before its turn ends (#493 D3, installed).
|
|
17
|
+
- A live run of two instances of one kind (#450 I4, #138).
|
|
18
|
+
|
|
19
|
+
## Reviews
|
|
20
|
+
|
|
21
|
+
| PR | Final head | Review | CI |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| #495 (#493) | 3730bf20 | planner plus an independent OCR pass | exact head, success |
|
|
24
|
+
| #500 (#484) | b4a1bd6b | codex, approved | exact head, success |
|
|
25
|
+
| #499 (#219) | a67fd222 | codex, approved | exact head, success |
|
|
26
|
+
| #497 (#450 I2) | 254ba163 | two fresh-context reviews (round 1: changes requested, B1-B5); round-2 fresh review, approved; merge-delta review | exact head, success (macOS watchdog job rerun once) |
|
|
27
|
+
| #496 (#481 S0+S8) | b1120f96 | codex: round 1 five P2s, round 2 one P1 (off-mode PII screen), round 3 approved | exact head, success (macOS #463 attach-timeout job rerun once; passes locally) |
|
|
28
|
+
|
|
29
|
+
## Document review
|
|
30
|
+
|
|
31
|
+
Round 1 checked 10 documents against #495, round 2 against #499 and #500, and round 3 13 stale documents against #497 and #496, read by a fresh-context reviewer and applied by the planner.
|
|
32
|
+
|
|
33
|
+
| Document | Result | Checked claims and correction |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `README.md` | corrected (round 1) | Status 0.12.35; the Jcode entry sentence's version; the verification link. Nothing #497 or #496 invalidates. |
|
|
36
|
+
| `docs/quickstart.md` | corrected | `v0.12.35` install. Upgrade paragraphs for #493 (session restart), #484, the #481 deprecation (`workflow`, the `phase` gate, `criteria` receipts) and #450 I2 instances. |
|
|
37
|
+
| `docs/operations.md` | corrected (rounds 1 and 2) | Busy channel sessions; the Jcode roster read. The #497 instance and #496 lifecycle sections match the code. |
|
|
38
|
+
| `docs/security.md` | corrected | `instances` is machine-local (read only from an untracked `config.local.json`) and refused from every surface; extras accept only `headless`. |
|
|
39
|
+
| `docs/sdlc.md` | corrected | The off schema-1 export, and the `phase` tool schema of off or older daemons. |
|
|
40
|
+
| `docs/specs/2026-09-19-agent-hub-design.md` | corrected | The #481 deprecation bullet: only `workflow = "gsd-lite"` and the `phase` gate warn; the `phase` receipt name is accepted silently. |
|
|
41
|
+
| `docs/agent-notes/tasks.md` | corrected | `checklistOf` screening and off compatibility; instance id resolution and family fallback; owner isolation proof. |
|
|
42
|
+
| `docs/agent-notes/budget.md` | corrected | Account-keyed quota, pause, checkpoint and handoff with per-member fanout; scope includes `instance-budget.ts`. |
|
|
43
|
+
| `docs/agent-notes/daemon.md` | corrected | Startup-bound declarations, execution roots, instance recovery and undo; scope includes the instance modules. |
|
|
44
|
+
| `docs/agent-notes/adapters.md` | corrected | Spawn identity per instance; an ACP kind only from the host declaration. |
|
|
45
|
+
| `docs/agent-notes/local-worker.md` | corrected | Scope includes the sandbox root in `instance-execution.ts`; no rule changed. |
|
|
46
|
+
| `docs/agent-notes/bus.md` | corrected (round 1) | Channel pull while busy; `WsPeer.steer` scope. No #497/#496 rule. |
|
|
47
|
+
| `docs/agent-notes/tests.md`, `benchmarks.md`, `README.md` | unchanged | #497/#496 touched only shared paths; no rule is wrong. |
|
|
48
|
+
|
|
49
|
+
`AGENTS.md` area notes and their `docs/verified.json` paths now list `instances.ts`, `instance-config.ts`, `instance-execution.ts` and `instance-budget.ts`. A separate manifest commit restamps the reviewed documents. The release PR must be merged with a merge commit so the stamps stay ancestors of the tag.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Busy Claude channel notification receipt (#493)
|
|
2
|
+
|
|
3
|
+
## Result
|
|
4
|
+
|
|
5
|
+
Positive native interactive receipt: Claude Code 2.1.302, Sonnet 5.5 (the native
|
|
6
|
+
`sonnet` alias), consumed a channel notification during the original busy turn.
|
|
7
|
+
The model called the observation tool with the exact random token before its
|
|
8
|
+
first `end_turn`. This supports important-only channel steering in the tested
|
|
9
|
+
interactive mode. It does not prove an arbitrary notification was read merely
|
|
10
|
+
because its stdout write succeeded, nor does it prove delivery in print mode.
|
|
11
|
+
|
|
12
|
+
## Native experiment
|
|
13
|
+
|
|
14
|
+
- A new scratch project outside the repository used one explicitly selected
|
|
15
|
+
development MCP channel, `busy-probe`. The harness preserved the executing
|
|
16
|
+
agent marker, removed live hub binding variables, loaded no project/user
|
|
17
|
+
settings or hooks, and exposed only three fixed fixture tools. It connected
|
|
18
|
+
neither to the person's hub nor to another peer.
|
|
19
|
+
- The original instruction called `probe_hold`, then `probe_after_hold`, then
|
|
20
|
+
finished with `DONE`. It also asked the model to report any actually observed
|
|
21
|
+
channel token, without inventing one. The token was generated only after
|
|
22
|
+
`probe_hold` started; neither the prompt nor the hold result contained it.
|
|
23
|
+
- While that tool was pending, the MCP server wrote
|
|
24
|
+
`notifications/claude/channel` with `priority: important` and the random token.
|
|
25
|
+
The fixed hold result contained only `ready: true`.
|
|
26
|
+
- The interactive run used a real PTY. The operator confirmed that this was a
|
|
27
|
+
locally written development channel. No trust or tool-approval prompt was
|
|
28
|
+
answered automatically, and no second model prompt was sent. After the
|
|
29
|
+
original response completed, the operator exited with Ctrl-D.
|
|
30
|
+
|
|
31
|
+
| Evidence | UTC on 2026-10-18 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| Native tool barrier entered | 07:25:36.997 |
|
|
34
|
+
| Notification written while tool pending | 07:25:37.298 |
|
|
35
|
+
| Fixed hold result released | 07:25:38.801 |
|
|
36
|
+
| Correct token received by observation tool | 07:25:41.948 |
|
|
37
|
+
| First original assistant `end_turn`, `DONE` | 07:25:43.016 |
|
|
38
|
+
|
|
39
|
+
The native transcript's zero-based record positions were 29 for the hold call,
|
|
40
|
+
49 for the matching observation call, and 52 for the first original `end_turn`.
|
|
41
|
+
The observation was after the tool barrier released but before that original
|
|
42
|
+
turn ended. The TUI also displayed the channel notification during its busy
|
|
43
|
+
indicator. Native exit was 0; process-table verification found no remaining
|
|
44
|
+
probe-owned native, helper or MCP server processes. The 90-second deadline did
|
|
45
|
+
not fire.
|
|
46
|
+
|
|
47
|
+
The automatic resource controller initially deferred on ordinary Teams/Jump
|
|
48
|
+
Desktop processes with no repository attribution. Manual inspection identified
|
|
49
|
+
their application origins and confirmed normal memory/thermal state, CPU idle
|
|
50
|
+
above 50%, no competing repository job and one probe worker. No existing app
|
|
51
|
+
was stopped. Native account/session metadata writes were not suppressed or
|
|
52
|
+
audited; this receipt does not claim the native client made no home-directory
|
|
53
|
+
writes.
|
|
54
|
+
|
|
55
|
+
## Preserved evidence
|
|
56
|
+
|
|
57
|
+
Private run artifacts are retained under workspace-relative
|
|
58
|
+
`scratchpad/temp/ahub-493-probe/interactive-zaTWkS/`; the invocation receipt points
|
|
59
|
+
to the native client's own transcript. Raw account/profile paths and the random
|
|
60
|
+
token are not copied into this document.
|
|
61
|
+
|
|
62
|
+
| Artifact | SHA-256 |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `receipt.json` | `5108a491e3469baf9de67860d7d3fff37b5eb89b980fa9f1835ff28510f524b7` |
|
|
65
|
+
| `server-events.jsonl` | `43b079866ec2c8ac0437d51d2fd50549fec035b1a76feaa8c20142a58cdb1258` |
|
|
66
|
+
| `native-tui.log` | `2fc9182a355038c0b26d662054ee7abee9fda78569ea6b07893afeb44fd6da51` |
|
|
67
|
+
| `pty-events.jsonl` | `5ab8c672ea56512a6bdce111fe6960fc85ceb47ff88d7a1b17d528b1fdb10229` |
|
|
68
|
+
| Native transcript | `0acfd6f66ec20f0cb5ad4fe020d044e326c240289019785c15f3af64ca188ede` |
|
|
69
|
+
|
|
70
|
+
Token SHA-256:
|
|
71
|
+
`ea4326fdc248ddc69ba250005a472f5393c2250b1876fbe039cbef0561125d08`.
|
|
72
|
+
|
|
73
|
+
An earlier isolated print/stream-json run (`run-YwEcfC`) initialized the same
|
|
74
|
+
channel and wrote a notification while the hold tool was pending, then finished
|
|
75
|
+
without an observation call. That result remains inconclusive for interactive
|
|
76
|
+
delivery. It was not treated as a negative, and steering was added only after
|
|
77
|
+
the positive interactive receipt above.
|
|
78
|
+
|
|
79
|
+
## Implementation boundary
|
|
80
|
+
|
|
81
|
+
- A channel-capable attached `WsPeer` exposes steer only while it owns a push
|
|
82
|
+
socket; detach and pull-only attachment remove that capability. The bus admits
|
|
83
|
+
important work only, retains its existing pause/recovery/uncertainty holds and
|
|
84
|
+
removes the queued item before transport hand-off.
|
|
85
|
+
- A steered notification uses the ordinary delivery id, connection generation,
|
|
86
|
+
accepted receipt and explicit completion/correlated-reply contract. A transport
|
|
87
|
+
failure stays uncertain. An idle transition must not deliver it again.
|
|
88
|
+
- Queued ordinary work can be read through `hub_inbox` during a busy turn using
|
|
89
|
+
the existing atomic pull completion path. This does not settle a previously
|
|
90
|
+
accepted push. Fake-transport regressions cover these contracts separately
|
|
91
|
+
from this native observation.
|
package/docs/verified.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"documents": {
|
|
4
4
|
"README.md": {
|
|
5
|
-
"verifiedAgainst": "
|
|
5
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
6
6
|
"paths": [
|
|
7
7
|
"package.json",
|
|
8
8
|
"src/",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
]
|
|
12
12
|
},
|
|
13
13
|
"docs/security.md": {
|
|
14
|
-
"verifiedAgainst": "
|
|
14
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
15
15
|
"paths": [
|
|
16
16
|
"src/",
|
|
17
17
|
"templates/"
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"docs/operations.md": {
|
|
21
|
-
"verifiedAgainst": "
|
|
21
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
22
22
|
"paths": [
|
|
23
23
|
"package.json",
|
|
24
24
|
"src/",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"docs/quickstart.md": {
|
|
38
|
-
"verifiedAgainst": "
|
|
38
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
39
39
|
"paths": [
|
|
40
40
|
"package.json",
|
|
41
41
|
"src/",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"docs/agent-notes/adapters.md": {
|
|
46
|
-
"verifiedAgainst": "
|
|
46
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
47
47
|
"paths": [
|
|
48
48
|
"src/adapters/",
|
|
49
49
|
"src/pi/",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"docs/agent-notes/benchmarks.md": {
|
|
67
|
-
"verifiedAgainst": "
|
|
67
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
68
68
|
"paths": [
|
|
69
69
|
"scripts/benchmarks/",
|
|
70
70
|
"test/benchmarks/",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
"docs/agent-notes/budget.md": {
|
|
79
|
-
"verifiedAgainst": "
|
|
79
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
80
80
|
"paths": [
|
|
81
81
|
"src/hub/budget.ts",
|
|
82
82
|
"src/cli/statusline-tee.ts",
|
|
@@ -87,11 +87,12 @@
|
|
|
87
87
|
"src/hub/daemon.ts",
|
|
88
88
|
"src/adapters/definitions/native-quota.ts",
|
|
89
89
|
"src/adapters/definitions/native-context.ts",
|
|
90
|
-
"src/adapters/registry.ts"
|
|
90
|
+
"src/adapters/registry.ts",
|
|
91
|
+
"src/hub/instance-budget.ts"
|
|
91
92
|
]
|
|
92
93
|
},
|
|
93
94
|
"docs/agent-notes/bus.md": {
|
|
94
|
-
"verifiedAgainst": "
|
|
95
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
95
96
|
"paths": [
|
|
96
97
|
"src/hub/bus.ts",
|
|
97
98
|
"src/hub/envelope.ts",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
]
|
|
106
107
|
},
|
|
107
108
|
"docs/agent-notes/daemon.md": {
|
|
108
|
-
"verifiedAgainst": "
|
|
109
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
109
110
|
"paths": [
|
|
110
111
|
"src/hub/daemon.ts",
|
|
111
112
|
"src/hub/control-client.ts",
|
|
@@ -147,18 +148,22 @@
|
|
|
147
148
|
"src/adapters/definitions/native-recovery*.ts",
|
|
148
149
|
"src/adapters/definitions/native-policy-aux.ts",
|
|
149
150
|
"src/adapters/definitions/native-config-*.ts",
|
|
150
|
-
"src/adapters/definitions/native-cli-*.ts"
|
|
151
|
+
"src/adapters/definitions/native-cli-*.ts",
|
|
152
|
+
"src/hub/instances.ts",
|
|
153
|
+
"src/hub/instance-config.ts",
|
|
154
|
+
"src/hub/instance-execution.ts"
|
|
151
155
|
]
|
|
152
156
|
},
|
|
153
157
|
"docs/agent-notes/local-worker.md": {
|
|
154
|
-
"verifiedAgainst": "
|
|
158
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
155
159
|
"paths": [
|
|
156
160
|
"src/adapters/local-worker.ts",
|
|
157
161
|
"src/local/",
|
|
158
162
|
"src/memory/capture.ts",
|
|
159
163
|
"src/hub/facts.ts",
|
|
160
164
|
"src/hub/permission-mode.ts",
|
|
161
|
-
"src/hub/git.ts"
|
|
165
|
+
"src/hub/git.ts",
|
|
166
|
+
"src/hub/instance-execution.ts"
|
|
162
167
|
]
|
|
163
168
|
},
|
|
164
169
|
"docs/agent-notes/models.md": {
|
|
@@ -171,7 +176,7 @@
|
|
|
171
176
|
]
|
|
172
177
|
},
|
|
173
178
|
"docs/agent-notes/tasks.md": {
|
|
174
|
-
"verifiedAgainst": "
|
|
179
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
175
180
|
"paths": [
|
|
176
181
|
"src/hub/tasks.ts",
|
|
177
182
|
"src/hub/board.ts",
|
|
@@ -191,7 +196,7 @@
|
|
|
191
196
|
]
|
|
192
197
|
},
|
|
193
198
|
"docs/agent-notes/tests.md": {
|
|
194
|
-
"verifiedAgainst": "
|
|
199
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
195
200
|
"paths": [
|
|
196
201
|
"test/",
|
|
197
202
|
"scripts/check.sh",
|
|
@@ -210,7 +215,7 @@
|
|
|
210
215
|
]
|
|
211
216
|
},
|
|
212
217
|
"docs/agent-notes/README.md": {
|
|
213
|
-
"verifiedAgainst": "
|
|
218
|
+
"verifiedAgainst": "03209040585a57c7c0a1f2b81f6be814df88267c",
|
|
214
219
|
"paths": [
|
|
215
220
|
"src/cli/console-state.ts",
|
|
216
221
|
"src/cli/attention.ts",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-hub",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.35",
|
|
4
4
|
"description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Young Joon Lee",
|