@staix/agent-hub 0.12.43 → 0.12.45
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 +38 -3
- package/README.md +3 -3
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/docs/adding-an-ai-tool.md +228 -0
- package/docs/agent-notes/README.md +14 -0
- package/docs/agent-notes/adapters.md +61 -42
- package/docs/agent-notes/budget.md +2 -2
- package/docs/agent-notes/daemon.md +16 -10
- package/docs/agent-notes/local-worker.md +4 -3
- package/docs/agent-notes/tasks.md +13 -6
- package/docs/agent-notes/tests.md +9 -7
- package/docs/operations.md +163 -12
- package/docs/quickstart.md +10 -2
- package/docs/sdlc.md +26 -8
- package/docs/security.md +11 -6
- package/docs/specs/2026-09-19-agent-hub-design.md +273 -8
- package/docs/verification/2026-10-21-0.12.44.md +54 -0
- package/docs/verification/2026-10-21-469-r1-dashboard-tokens.md +43 -0
- package/docs/verification/2026-10-21-469-r2-dashboard-shell.md +54 -0
- package/docs/verification/2026-10-21-469-r3-dashboard-board.md +51 -0
- package/docs/verification/2026-10-21-469-r4-dashboard-views.md +77 -0
- package/docs/verification/2026-10-21-469-r6-dashboard-motion-states.md +82 -0
- package/docs/verification/2026-10-21-grok-config-acp-t0.md +30 -0
- package/docs/verification/2026-10-22-0.12.45.md +60 -0
- package/docs/verification/assets/469-r1-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r1-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r1-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r1-board-390-light.png +0 -0
- package/docs/verification/assets/469-r2-agents-1440-dark.png +0 -0
- package/docs/verification/assets/469-r2-agents-768-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-1024-light.png +0 -0
- package/docs/verification/assets/469-r2-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r2-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r2-board-390-light.png +0 -0
- package/docs/verification/assets/469-r2-board-768-light.png +0 -0
- package/docs/verification/assets/469-r3-board-1440-dark.png +0 -0
- package/docs/verification/assets/469-r3-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-board-390-light.png +0 -0
- package/docs/verification/assets/469-r3-detail-1440-dark.png +0 -0
- package/docs/verification/assets/469-r3-detail-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-detail-390-dark.png +0 -0
- package/docs/verification/assets/469-r3-plan-1440-light.png +0 -0
- package/docs/verification/assets/469-r3-plan-390-light.png +0 -0
- package/docs/verification/assets/469-r4-agents-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-agents-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-agents-390-dark.png +0 -0
- package/docs/verification/assets/469-r4-memory-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-memory-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-memory-390-dark.png +0 -0
- package/docs/verification/assets/469-r4-message-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-office-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-office-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-settings-1440-dark.png +0 -0
- package/docs/verification/assets/469-r4-settings-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-stream-1440-light.png +0 -0
- package/docs/verification/assets/469-r4-stream-390-light.png +0 -0
- package/docs/verification/assets/469-r6-agents-empty-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-agents-empty-390-light.png +0 -0
- package/docs/verification/assets/469-r6-board-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-board-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-board-empty-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-board-empty-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-disconnected-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-skeleton-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-skeleton-390-dark.png +0 -0
- package/docs/verification/assets/469-r6-toasts-1440-dark.png +0 -0
- package/docs/verification/assets/469-r6-toasts-1440-light.png +0 -0
- package/docs/verification/assets/469-r6-toasts-390-light.png +0 -0
- package/docs/verified.json +38 -20
- package/package.json +5 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +10287 -9560
- package/src/adapters/acp-owned-group.ts +124 -0
- package/src/adapters/acp.ts +92 -25
- package/src/adapters/adapter-api.ts +2 -0
- package/src/adapters/agy/definition.ts +4 -4
- package/src/adapters/agy/entry.ts +4 -1
- package/src/adapters/channel-peer.ts +143 -0
- package/src/adapters/claude-channel.ts +21 -12
- package/src/adapters/codex-appserver.ts +17 -2
- package/src/adapters/configured-acp.ts +124 -0
- package/src/adapters/conformance/acp.ts +114 -0
- package/src/adapters/conformance/codex.ts +191 -0
- package/src/adapters/conformance/enrollment.ts +43 -0
- package/src/adapters/conformance/factories.ts +17 -0
- package/src/adapters/conformance/fake-paths.ts +12 -0
- package/src/adapters/conformance/fakes/acp-server.ts +267 -0
- package/src/adapters/conformance/fakes/acp-watchdog-agent.ts +10 -0
- package/src/adapters/conformance/fakes/app-server.ts +119 -0
- package/src/adapters/conformance/fakes/codex-bin.ts +15 -0
- package/src/adapters/conformance/fakes/jcode-agent.ts +91 -0
- package/src/adapters/conformance/fakes/model-server.ts +39 -0
- package/src/adapters/conformance/fakes/pi-rpc.ts +32 -0
- package/src/adapters/conformance/index.ts +11 -0
- package/src/adapters/conformance/kit.ts +50 -0
- package/src/adapters/conformance/local-pi.ts +100 -0
- package/src/adapters/conformance/owned-acp.ts +44 -0
- package/src/adapters/definitions/builtin-metadata.ts +60 -39
- package/src/adapters/definitions/native-channel-state.ts +41 -0
- package/src/adapters/definitions/native-cli-entry-kimi.ts +9 -1
- package/src/adapters/definitions/native-cli-entry.ts +7 -19
- package/src/adapters/definitions/native-cli-preview-qwen.ts +1 -1
- package/src/adapters/definitions/native-cli-preview.ts +2 -11
- package/src/adapters/definitions/native-identity-defaults.ts +14 -0
- package/src/adapters/definitions/qwen.ts +7 -9
- package/src/adapters/definitions/reviewer-variants.ts +3 -7
- package/src/adapters/external-metadata.ts +48 -0
- package/src/adapters/external-module-scan.ts +126 -0
- package/src/adapters/external-modules.ts +240 -0
- package/src/adapters/jcode.ts +4 -0
- package/src/adapters/local-worker.ts +4 -0
- package/src/adapters/pi.ts +6 -0
- package/src/adapters/public.ts +7 -0
- package/src/adapters/registry-types.ts +28 -16
- package/src/adapters/registry.ts +115 -17
- package/src/adapters/reviewer-print.ts +4 -0
- package/src/cli/agy-mcp.ts +3 -2
- package/src/cli/configured-acp-entry.ts +58 -0
- package/src/cli/console-state.ts +9 -4
- package/src/cli/facts-hook.ts +2 -1
- package/src/cli/help.ts +27 -16
- package/src/cli/identity.ts +8 -7
- package/src/cli/launch.ts +5 -4
- package/src/cli/main.ts +101 -32
- package/src/cli/memory.ts +13 -10
- package/src/cli/output.ts +1 -0
- package/src/cli/preview.ts +9 -0
- package/src/cli/recovery-guidance.ts +1 -1
- package/src/cli/terminal-recovery.ts +16 -13
- package/src/cli/upgrade-labels.ts +1 -1
- package/src/cli/upgrade-presentation.ts +1 -1
- package/src/cli/upgrade-restore-state.ts +6 -4
- package/src/cli/upgrade-runtime.ts +25 -7
- package/src/cli/upgrade.ts +3 -2
- package/src/hub/board.ts +37 -8
- package/src/hub/budget.ts +9 -2
- package/src/hub/bus.ts +6 -2
- package/src/hub/claude-state.ts +2 -37
- package/src/hub/conductor.ts +8 -6
- package/src/hub/config-apply.ts +37 -17
- package/src/hub/config-reload.ts +42 -11
- package/src/hub/config-trust.ts +1 -1
- package/src/hub/context-window.ts +6 -0
- package/src/hub/control-client.ts +4 -1
- package/src/hub/crash.ts +8 -6
- package/src/hub/daemon.ts +412 -328
- package/src/hub/facts.ts +76 -13
- package/src/hub/hub-tools.ts +57 -5
- package/src/hub/instance-config.ts +33 -12
- package/src/hub/instance-execution.ts +23 -11
- package/src/hub/instances.ts +47 -22
- package/src/hub/lifecycle-schema.ts +5 -3
- package/src/hub/memory-profile.ts +16 -3
- package/src/hub/no-writes.ts +50 -0
- package/src/hub/peers.ts +9 -0
- package/src/hub/permission-mode.ts +9 -7
- package/src/hub/plan-conformance.ts +83 -0
- package/src/hub/review-findings.ts +104 -0
- package/src/hub/roster.ts +35 -6
- package/src/hub/routing.ts +21 -20
- package/src/hub/sdlc-queue.ts +19 -4
- package/src/hub/sdlc-release.ts +16 -1
- package/src/hub/sdlc.ts +27 -12
- package/src/hub/settings.ts +54 -16
- package/src/hub/snapshots.ts +32 -6
- package/src/hub/start-mode.ts +10 -8
- package/src/hub/task-sweep.ts +8 -2
- package/src/hub/tasks.ts +358 -60
- package/src/hub/ui.ts +2 -2
- package/src/memory/capture.ts +2 -1
- package/src/ui/index.html +563 -77
- package/src/ui/office-model.ts +3 -1
- package/src/ui/peer-palette.ts +44 -0
- package/templates/AGENTS.block.md +1 -1
- package/templates/profiles/general.json +1 -1
- package/templates/profiles/research.json +1 -1
- package/templates/profiles/writing.json +1 -1
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.
|
|
@@ -112,7 +112,7 @@ and flag. The current daemon emits `agent_cli` with `delegated: true` and
|
|
|
112
112
|
`refused: false` for the negotiated outcome. Allowed-class commands retain the peer identity and capability checks, including
|
|
113
113
|
messages, proposals and reviews. Peer starts, pause and resume may delegate
|
|
114
114
|
operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
|
|
115
|
-
person-authored planning/release/memory/goal writes, SDLC record, benchmark run,
|
|
115
|
+
person-authored planning/release/memory/goal writes, SDLC record, spec and plan-departure acceptance, benchmark run,
|
|
116
116
|
`ahub recovery reattach`, a `--fresh-session` dispose that would replace a repaired
|
|
117
117
|
session's conversation (or whose scope cannot be read), and the pet
|
|
118
118
|
composer are refused; presentation flags such as `--color` or `--verbose` before
|
|
@@ -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
|
|
|
@@ -216,7 +216,8 @@ Claude, Codex and Kimi still follow their own file permissions and can edit a
|
|
|
216
216
|
config file; such an edit changes participation only at the next hub start. A
|
|
217
217
|
live settings write or undo applies its own roster key and does not adopt other
|
|
218
218
|
file edits, and a configuration reload refuses roster changes. External ids outside the known native catalog keep their prior
|
|
219
|
-
admission rules.
|
|
219
|
+
admission rules. A roster key left by an undeclared instance loads as inert and grants
|
|
220
|
+
nothing; the only change it accepts is its removal from `config.local.json`.
|
|
220
221
|
|
|
221
222
|
Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
|
|
222
223
|
merges/removes only the hub MCP entry; the daemon never edits that file and
|
|
@@ -254,7 +255,11 @@ this production table. Startup diagnostics withhold native host/payload details.
|
|
|
254
255
|
|
|
255
256
|
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
257
|
|
|
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.
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
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.
|
|
261
|
+
|
|
262
|
+
External adapter modules (#451 A4) are the one way to run adapter code that ships outside the hub, and the pin is code authorization, not a sandbox. `adapter_modules` is read only from a Git-confirmed untracked `.agenthub/config.local.json` (never from a shared, tracked or cloned config, and never written by settings or a hub tool). Only daemon startup imports a module, from the captured bytes that matched the `sha256` pin, not by reopening the path; the file must sit inside the project with no symlinked component and not be world-writable. Its imports are bound to the running hub's own `@staix/agent-hub/adapter` and builtins (`bun:ffi` and `bun:test` excluded), and dynamic imports, `require`, `eval` and `Function` are refused by a conservative scan. Once imported, the module runs with the daemon's privileges: the 2 s import deadline stops waiting but does not cancel its code or undo side effects, and the host's default against hub-tool title auto-approval holds only while the module keeps the factory options it is given. Inspect the whole bundle before recording its hash. Refusal reasons withhold source and module error text. A loaded kind keeps its own family, cloud trust (never PII), headless start and off-roster default, and fixed-builtin capabilities are refused.
|
|
258
263
|
|
|
259
264
|
# Transient ACP activity (#286)
|
|
260
265
|
|
|
@@ -363,7 +368,7 @@ The fresh Codex reviewer (#519) needs `review_mode: "fresh"` and an installed Co
|
|
|
363
368
|
|
|
364
369
|
The fresh Kimi reviewer (#533) needs `review_mode: "fresh"` and Kimi Code 2.2.0 with its native OAuth login; any other version, or a failed preparation, is unavailable. It is a native tool-authority boundary, not a sandbox: no Seatbelt wraps it, and same-user native code is not isolated from the filesystem or network. Each review starts a new child in a private `KIMI_CODE_HOME` with managed-provider-only config, a clean temporary working directory and an empty skills directory. Its agent may use only `hub_review` and `hub_task_show`, served by a loopback MCP session bound to the task, head and review revision that never carries the hub's control token; it has no native read, file or shell tool and sees only the screened review brief and host facts. Its `credentials/` and `oauth/` directories and `device_id` link to the person's native ones, so refresh keeps Kimi's own lock and atomic rename; no token is copied and `KIMI_DISABLE_OAUTH_LOCK` is never set. A stop revokes its verdict authority before termination; killing it mid-refresh may require a native re-login. Private tasks are never given to it, and a child that ends without a saved verdict is never approval.
|
|
365
370
|
|
|
366
|
-
The review brief (#520) withholds private changes whole (both sides of a move) and counts them; a rule file that fails the private-data screen is named, not shown. Attributes and rules are read at the merge base, so a change cannot loosen its own review. Spec acceptance, plan review, premature-review and cost reads fetch GitHub metadata only (edit times, check states, thread authorship and resolution), never issue bodies or
|
|
371
|
+
The review brief (#520) withholds private changes whole (both sides of a move) and counts them; a rule file that fails the private-data screen is named, not shown. Attributes and rules are read at the merge base, so a change cannot loosen its own review. Spec acceptance, plan review, plan conformance, premature-review and cost reads fetch GitHub metadata only (edit times, check states, thread authorship and resolution, changed file names), never issue bodies, comments or patches. The CI workflow keeps `contents: read` and `actions: read`.
|
|
367
372
|
|
|
368
373
|
## Bounded task specs and isolated checks (#418)
|
|
369
374
|
|
|
@@ -1387,7 +1387,7 @@ upgrade sources; ordinary clients must use protocol 12 (13 from 0.12.4, with 12
|
|
|
1387
1387
|
|
|
1388
1388
|
## Amendment: turn-free facts at tool boundaries (issue #108)
|
|
1389
1389
|
|
|
1390
|
-
- `src/hub/facts.ts` keeps three things apart per peer: what the hub observed in the tree (latest version and transitions per file), what it offered (bounded offers with their file versions and plans), and what the peer acknowledged (its view). Only an acknowledgement moves a view; a later boundary offers everything since the view again. Files are the paths every task of each live cohort in which the peer has an open task names (a directory expands to git's changed files against HEAD, staged or not, and its new and deleted files, 200 at most, with a note when more were cut, told until it is read back and again in a new session), contained in the project as real paths and never `.git` or anything the denylist (`src/local/deny.ts`, `local.deny`) keeps from agents, plus the last 64 it touched in its current session; a file that falls out of those is named in every offer until one naming it is read back (#112). A file the peer touched before it had a view of it is compared with its version at that touch, so a change in between is shown, not absorbed into a first look; a file it saw under a named directory stays covered after git stops listing it (put back to HEAD's bytes, or the directory moved away; 200 at most, the rest named as no longer followed until that is read back; a new session hears only the drops of its own). A directory file it has neither seen nor touched is named without a diff when it appears with bytes that differ from HEAD's (a rewrite with HEAD's bytes, listed until git refreshes its index, is no change, for the fact and for whether the peer is current; a file too large to read is compared by git's blob id of its bytes, hashed in-process, while all such files in one comparison total 64 MiB or less, and is not compared otherwise (so the answer never depends on what an earlier comparison hashed); the bytes are the raw working-tree bytes, without git's filters, so with an end-of-line conversion or LFS such a file reads as changed and is named; #112) (diffing it against HEAD would show what changed while tracking was off, a PII window, and credit unseen changes by elimination), and counts as not yet shown until that fact is read back. Only regular files of 256 KB or less are read, re-resolved at every read and opened without following a final link or blocking. A history longer than the 64 kept transitions makes the change's attribution unknown.
|
|
1390
|
+
- `src/hub/facts.ts` keeps three things apart per peer: what the hub observed in the tree (latest version and transitions per file), what it offered (bounded offers with their file versions and plans), and what the peer acknowledged (its view). Only an acknowledgement moves a view; a later boundary offers everything since the view again. Files are the paths every task of each live cohort in which the peer has an open task names (a directory expands to git's changed files against HEAD, staged or not, and its new and deleted files, 200 at most, with a note when more were cut, told until it is read back and again in a new session), contained in the project as real paths and never `.git` or anything the denylist (`src/local/deny.ts`, `local.deny`) keeps from agents, plus the last 64 it touched in its current session; a file that falls out of those is named in every offer until one naming it is read back (#112). A file the peer touched before it had a view of it is compared with its version at that touch, so a change in between is shown, not absorbed into a first look; a file it saw under a named directory stays covered after git stops listing it (put back to HEAD's bytes, or the directory moved away; 200 at most, the rest named as no longer followed until that is read back; a new session hears only the drops of its own). A directory file it has neither seen nor touched is named without a diff when it appears with bytes that differ from HEAD's (a rewrite with HEAD's bytes, listed until git refreshes its index, is no change, for the fact and for whether the peer is current; a file too large to read is compared by git's blob id of its bytes, hashed in-process, while all such files in one comparison total 64 MiB or less, and is not compared otherwise (so the answer never depends on what an earlier comparison hashed); the bytes are the raw working-tree bytes, without git's filters, so with an end-of-line conversion or LFS such a file reads as changed and is named; #112) (diffing it against HEAD would show what changed while tracking was off, a PII window, and credit unseen changes by elimination), and counts as not yet shown until that fact is read back. Only regular files of 256 KB or less are read, re-resolved at every read and opened without following a final link or blocking; a larger file is compared by size and mtime, except that one whose last change (ctime) is under 2 s old and 4 MiB or less is named by git's blob id of its bytes (git's racily-clean rule: Linux before 6.13 stamps files from a coarse clock, so a same-size rewrite within one tick keeps every stat field; #428), hashed once per version in a public entry point and 16 MiB in all per store per event-loop turn, the stat stamp alone beyond either; the HEAD comparison above keeps its own 64 MiB bound and is not limited by these. A version seen racy keeps its version once it settles with the same bytes, and an integration target (`tree()`) counts large files by their stat stamp only, so it does not move when one settles. Known ceilings: a racy file over the cap or past the budget misses a same-tick same-size rewrite; a racy file first seen past the budget (kept stat-only) reads as changed once when a later turn hashes it with the same bytes; a file written continuously is hashed at every look within the budget; a settled file made racy with the same bytes (chmod, `cp -p`) reads as changed once; a version first seen racy costs an open and a stat at every later look. A history longer than the 64 kept transitions makes the change's attribution unknown.
|
|
1391
1391
|
- Attribution: a transition is credited only with effect evidence: a Claude Edit, MultiEdit or Write whose result equals its input applied to the file as observed at PreToolUse with no observation in between, or a Codex `fileChange` whose changed lines equal the observed diff. Everything else is shown with its attribution unknown; an agent's own verified writes advance its view and are not shown back. A Claude Read advances its view only when it returned the whole file (no offset or limit, at most 2000 lines, none over 2000 characters); a Codex read action never does (it may be partial). A diff that matches a PII pattern is replaced by a note naming the file, and a changed file whose name matches one is counted without its name.
|
|
1392
1392
|
- Acknowledgement and capability: Claude's hook runs before and after every tool and at Stop (`ahub claude` injects it in a turn-free project); the pre answer's text becomes `additionalContext`, and the row Claude Code writes in its transcript for it (matched by tool use id and offer id) is the readback. Codex's offer goes in with `steerText` (bound to the running turn), and the steered input coming back as a `userMessage` item is the readback. A readback marks the peer's path verified; a new session or thread, the peer going offline, or three offers past a minute without one (checked at boundaries and every 30 seconds; facts sent with an integration request wait for the next done and never count, a refused steer is dropped, not unread, and an unanswered one stays pending, since it may have gone in) unverifies it. Until verified, a peer gets a one-line probe at most three times per session, and no offers at all once three are unread; a Claude session whose transcript the hub cannot find gets none at all and loses a verified path.
|
|
1393
1393
|
- The control contract is protocol 13: `facts` carries the phase, tool, input, tool use id, session id and transcript path; `silenced` serves check-path; `send` results name held-back recipients. Protocol 12 stays an authenticated recovery source.
|
|
@@ -2007,6 +2007,18 @@ push `accepted` and show none of them.
|
|
|
2007
2007
|
detach and pull-only attach remove that capability. Ordinary messages remain
|
|
2008
2008
|
queued while busy. A notification write alone is not proof that the native
|
|
2009
2009
|
model saw it; the earlier print-mode probe was inconclusive.
|
|
2010
|
+
- Known limit and hint behaviour (#221). Mid-turn consumption of an important
|
|
2011
|
+
steer was proven only on Claude Code 2.1.302, yet push-capable `WsPeer` offers
|
|
2012
|
+
steer to every push-capable Claude session. On an older client a dropped
|
|
2013
|
+
mid-turn notification stays `accepted` until a person resolves it. No client
|
|
2014
|
+
version gate is added: the hello does not carry the client version, and the
|
|
2015
|
+
oldest version on which steer works was never probed, so a gate would be a
|
|
2016
|
+
guess that could disable a working path. A gate needs a probe of the older
|
|
2017
|
+
client and an optional hello field. A tool result that carries a held-delivery
|
|
2018
|
+
hint keeps the queued and important counts but drops the advice to call
|
|
2019
|
+
`hub_inbox`, because `Bus.pull` refuses under that hold. A `hub_inbox` result
|
|
2020
|
+
that already states how many messages remain unread adds only the important
|
|
2021
|
+
count.
|
|
2010
2022
|
|
|
2011
2023
|
## Amendment: owner and reviewer read their task (issue #208)
|
|
2012
2024
|
|
|
@@ -3493,9 +3505,9 @@ load daemon config or infer executable commands from environment text.
|
|
|
3493
3505
|
the host still performs each probe, while definitions validate or display it.
|
|
3494
3506
|
`cli.authority` and `cli.personOnlyFlags` can only restrict existing authority;
|
|
3495
3507
|
identity.ts stays authoritative and missing/unknown metadata retains today's
|
|
3496
|
-
rule. `start.passthrough` is data only.
|
|
3497
|
-
existing
|
|
3498
|
-
|
|
3508
|
+
rule. `start.passthrough` is data only. MCP installation, ordering fences and
|
|
3509
|
+
finally cleanup remain in the existing CLI host; A2b removes the unconsumed
|
|
3510
|
+
`init.mcpPlan` sketch instead of advertising another installation path.
|
|
3499
3511
|
|
|
3500
3512
|
### A2 internal native host projections (2026-10-18)
|
|
3501
3513
|
|
|
@@ -3658,6 +3670,87 @@ import/reexport bindings through lexical symbols before classifying comparisons.
|
|
|
3658
3670
|
Shadowed and unrelated bindings remain independent; named non-peer enum exceptions
|
|
3659
3671
|
keep their exact scope. It performs no filesystem or package lookup.
|
|
3660
3672
|
|
|
3673
|
+
### A2b consumed contracts and conformance (2026-10-21, #451, #625 phases 2-3)
|
|
3674
|
+
|
|
3675
|
+
Runtime `start.launch` is optional and supplies only real native argv planners.
|
|
3676
|
+
Kimi, Pi, local, Jcode and fresh-review children use verified host startup ports,
|
|
3677
|
+
not unused CLI-wrapper plans. The frozen A1 export retains its old compatibility
|
|
3678
|
+
shape under a separate legacy type and is never selected by runtime consumers.
|
|
3679
|
+
Remove unused `start.ownsTerminal`, `init`, `QuotaReader.resetOnWindow` and hooks
|
|
3680
|
+
`agentIdentity`, `onAttach`, `onDetach`, `conversationRevert`, `replaceSession`.
|
|
3681
|
+
Actual terminal ownership, identity, revert, replacement and MCP writes retain
|
|
3682
|
+
their existing transport/host paths. The consumed native facts hook remains.
|
|
3683
|
+
Remove unconsumed runtime `permission.modes` and `permission.readOnly`; native mode
|
|
3684
|
+
mappings, host approval gates and fresh-review isolation receipts remain
|
|
3685
|
+
unchanged and authoritative. Missing metadata never grants permission.
|
|
3686
|
+
|
|
3687
|
+
`attach.externalHello` explicitly admits an authenticated external process.
|
|
3688
|
+
`instances.mode` separately selects external or headless declaration policy,
|
|
3689
|
+
with an optional fixed role ceiling. MCP configuration shape grants neither.
|
|
3690
|
+
Claude keeps human-owned external planner/reviewer instances and its fresh
|
|
3691
|
+
headless reviewer exception; agy now refuses ordinary extra instances at config load (older declarations could load as inert pull-only extras).
|
|
3692
|
+
Qwen context is now unknown because it has no verified occupancy source; it no
|
|
3693
|
+
longer borrows Kimi's context parser.
|
|
3694
|
+
Declaration admission never proves execution-root ownership or isolation.
|
|
3695
|
+
|
|
3696
|
+
Daemon normalization dispatches through the resolved definition's `budget.reader`
|
|
3697
|
+
and `budget.context`. Preserve source timestamps and current session, launch,
|
|
3698
|
+
generation and privacy fences. Missing readers stay unknown, including ACP kinds
|
|
3699
|
+
without a verified occupancy source; never borrow another kind's parser or
|
|
3700
|
+
manufacture empty quota readings or zero occupancy.
|
|
3701
|
+
|
|
3702
|
+
The fixed-builtin restrictions above remain ceilings: `acpCommand` selects the
|
|
3703
|
+
Kimi-specific host startup path; `attach.admission` consumes its native TUI launch
|
|
3704
|
+
receipt; `resetSoftTokensOnResume` resets host-owned soft accounting;
|
|
3705
|
+
`crashIdentity` validates released native recovery records; `pi-rpc` owns the
|
|
3706
|
+
verified relay, sandbox and process-monitor lifecycle. Configured definitions
|
|
3707
|
+
cannot opt into these host-global assumptions. Their upgrade path is typed,
|
|
3708
|
+
kind-neutral host ports before external loading, not removal of scope guards.
|
|
3709
|
+
|
|
3710
|
+
Registry coherence validates base and reviewer-variant attach, factory and reader
|
|
3711
|
+
structure without invoking a reader or performing I/O. Conformance enumerates
|
|
3712
|
+
every definition and variant with distinct enrollment or an unsupported reason
|
|
3713
|
+
naming dedicated coverage. Driven fixtures use `definition.start.create`; Claude
|
|
3714
|
+
uses its real external channel. Jcode exercises its owned-ACP watchdog and Kimi
|
|
3715
|
+
separately exercises TUI pull-only queue behavior. Unsupported native permission
|
|
3716
|
+
or quota legs never imply readiness.
|
|
3717
|
+
|
|
3718
|
+
The AST guard additionally checks native defaults, source-proven peer/kind call
|
|
3719
|
+
arguments, exact native regexes, startsWith/includes/hasOwn predicates (including
|
|
3720
|
+
constant bracket spellings), and adapter-origin native instanceof dispatch.
|
|
3721
|
+
Aliases and shadowing remain source-bound; generic hub classes, dynamic keys and
|
|
3722
|
+
prose are not native identity policy. Keep the frozen core baseline empty and
|
|
3723
|
+
existing seed entries unchanged except the reviewed search/replacement-only
|
|
3724
|
+
reanchors of `roster-detached-pi-recovery` and `kimi-watch-live-owner` from
|
|
3725
|
+
instanceof to the equivalent native predicates. Their identity and named tests
|
|
3726
|
+
stay unchanged, with green-to-named-assertion proof through the seed runner. Add a
|
|
3727
|
+
registry START_MODE_PEERS membership mutation seed.
|
|
3728
|
+
Native type predicates and the external channel implementation live in adapters;
|
|
3729
|
+
authentication and lifecycle authority remain in the daemon. Stable hub leaf
|
|
3730
|
+
exports preserve source compatibility for native state readers.
|
|
3731
|
+
|
|
3732
|
+
After unidentifiable Codex cancellation, status carries a fixed
|
|
3733
|
+
`unidentified-cancellation` notice while its existing offline latch holds. Late
|
|
3734
|
+
completion cannot clear it; verified native reattachment does. No native ids or
|
|
3735
|
+
provider text reach the notice.
|
|
3736
|
+
|
|
3737
|
+
### Peer capability ports (#625 phase 6, slice 1)
|
|
3738
|
+
|
|
3739
|
+
`PeerAdapter` optionally exposes a readonly `sessionGeneration` (string or number),
|
|
3740
|
+
readonly `permissionModeState` (a permission mode, unknown or unmanaged), and an
|
|
3741
|
+
acknowledged `setPermissionMode(mode)` operation. Generic runtime permission and
|
|
3742
|
+
generation dispatch uses these ports without native-class enrollment. An absent
|
|
3743
|
+
generation port retains the existing recovery-session/owner-token fallback; a
|
|
3744
|
+
present but undefined generation is unknown and refuses bounded checks.
|
|
3745
|
+
Checkpoints fence a present generation unless `checkpointUsesSessionGeneration`
|
|
3746
|
+
is explicitly false. Pi keeps that exception so its owner/session-only checkpoint
|
|
3747
|
+
behavior is unchanged. BasePeer installs no default generation property.
|
|
3748
|
+
Read-only and unmanaged ceilings, never-ask confirmation, serialization and the
|
|
3749
|
+
current-owner/not-offline check after acknowledgement remain authoritative.
|
|
3750
|
+
Codex proxy/startup/restoration-debt and Claude launch-generation/unattended-hook
|
|
3751
|
+
proofs remain native branches; typed semantic ports for those proofs belong to a
|
|
3752
|
+
later slice.
|
|
3753
|
+
|
|
3661
3754
|
## Amendment: playbook lifecycle and criteria checklist, 2026-10-18 (#481 S0/S8)
|
|
3662
3755
|
|
|
3663
3756
|
- 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.
|
|
@@ -3669,6 +3762,10 @@ keep their exact scope. It performs no filesystem or package lookup.
|
|
|
3669
3762
|
- `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.
|
|
3670
3763
|
- 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.
|
|
3671
3764
|
- 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.
|
|
3765
|
+
### S3 structured findings clarification (#481, hub task #196)
|
|
3766
|
+
|
|
3767
|
+
Open Important findings carry across reviewed heads until the reviewer explicitly marks fixed or false_positive, or the person accepts with a rationale; nits do not carry. Review requests list carried public ids, pass, severity and optional path, with private details limited to counts. Use a shared valid-head resolver (PR, reviewed, local, then valid commit metadata); enforce refuses a non-empty report with no resolvable head. Recurrence uses semantic pass/id pairs excluding false positives and a durable card marker rather than its title.
|
|
3768
|
+
|
|
3672
3769
|
## Reviewer separation amendment (#450, hub task #223)
|
|
3673
3770
|
|
|
3674
3771
|
Owner decision 2026-10-18 (#450 comment 6248184754), R1-R5. R3's mechanism, chosen by the person: a new headless Claude session per review request.
|
|
@@ -3724,7 +3821,7 @@ ahub builds the review brief that the workspace gate used to take from `ocr dele
|
|
|
3724
3821
|
|
|
3725
3822
|
## Claude peer instances I3 amendment (#450)
|
|
3726
3823
|
|
|
3727
|
-
-
|
|
3824
|
+
- 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
3825
|
- 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
3826
|
- 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
3827
|
- 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.
|
|
@@ -3751,7 +3848,13 @@ never initializes that app-server on the TUI's behalf. `ahub <kind> --as <id>`
|
|
|
3751
3848
|
selects the declared identity; selection is removed from native passthrough.
|
|
3752
3849
|
Missing `peer_instances` evidence on an old daemon/upgrade target is a refusal
|
|
3753
3850
|
before instance runtime effects, rather than a guess that the instance is a base
|
|
3754
|
-
peer
|
|
3851
|
+
peer, but only while a declared instance is live or unreadable: a source whose
|
|
3852
|
+
status shows every declared id offline proceeds, because there is no extra
|
|
3853
|
+
identity left to reinterpret. The refusal asks the person to end extra native
|
|
3854
|
+
sessions and, only when an extra session cannot be ended, to remove the
|
|
3855
|
+
declarations temporarily. A roster key left behind by an undeclared instance
|
|
3856
|
+
loads as inert and `ahub roster remove <id>` deletes it; any other unknown key
|
|
3857
|
+
is still an unsupported peer.
|
|
3755
3858
|
|
|
3756
3859
|
Each additional implementer requires an explicit registered Git worktree at
|
|
3757
3860
|
`<project>/.worktrees/<id>` with an attached branch. I2 creates no worktree or
|
|
@@ -3889,9 +3992,19 @@ S9 privacy and notice ceilings: private build work with an issue ref remains ina
|
|
|
3889
3992
|
|
|
3890
3993
|
- Task plans carry explicit review scope (none/delta/full, bounded reviewer count) and CI scope (focused/full/reuse); S9 receipts copy the planner-selected values. None uses zero reviewers; other scopes use one to eight. Legacy scope is unspecified, readable and never guessed. Advise reports the gap; new enforce receipts require both scopes, while old scope-less receipts remain valid to their next non-release planning revision under the complete-history S11 exception. Omitted cost keys on an owner file-list update retain the reviewed scope; explicit prospective drift refuses enforce acceptance. Scope does not change reviewer authority or bypass CI; S2 owns actual conformance comparison.
|
|
3891
3994
|
- Review admission is separate from #520 brief/coverage. Before done/cohort writes, enforce requires the task's exact PR head, successful configured checks (native default: both OS checks and seeded guards), and no unanswered pending bot thread. Resolution settles a thread; an unresolved thread needs a user reply after the last bot comment. Only metadata is fetched. Advise reports gaps, including a generic unknown-readiness notice when a private turn finishes its own PII task without remote metadata; ordinary work remains refused during a private turn. Off adds no readback. Identity/privacy/owner and runtime authority are rechecked across awaits and each external read. Background role maintenance may retain a saved role while holding an unadmitted review envelope; a manual reviewer resend performs fresh admission.
|
|
3892
|
-
-
|
|
3995
|
+
- CI controllers are serialized per pull request (`check-pr-<number>`, `queue: single`: a newer pending run replaces the older pending one) and for main pushes and ordinary dispatches (`check-main`, `queue: max`; a macOS timing dispatch has its own `macos-timing` group), for the whole workflow and without cancelling an admitted controller (#609). A superseded PR head that is already running completes; this is a cost ceiling, not a proof gap. Two groups admitting one tree at the same instant can run it twice; the duplicate costs one run and never yields a false green. A visible active same-tree gate in any group still refuses admission. Required check names and read-only permissions are unchanged. Identical-tree successful full proof is reused for PR and push. Reuse expands both OS matrix names and reports the required seeded aggregate, using only a `Reuse verified identical-tree full gate` marker step with the original run id. These successful reporting jobs and skipped jobs are not fresh full proof; duplicate active full gates and unknown active coverage refuse admission. Pending controllers do not run gates. CI proof searches are bounded; missed older success can require a new gate, never invent green evidence.
|
|
3893
3996
|
- Cost reporting is read-only over bounded current public board links plus merged-PR/check metadata: review handoff rounds and elapsed time, CI run ids and extra attempts, full re-reviews versus deltas, and unknown scope. Private/relinked counterparts are excluded at each read and projection. Pure band-breach proposal data is delivered here; the S7 baseline/configuration/automatic board-card scheduler is deliberately not implemented in S10.
|
|
3894
|
-
-
|
|
3997
|
+
- The nonblocking policy ceilings deferred to hub task #230 are settled in the follow-up below.
|
|
3998
|
+
|
|
3999
|
+
### S10 policy ceilings follow-up (#481, hub task #230)
|
|
4000
|
+
|
|
4001
|
+
These rules are settled here and land with hub tasks #284-#286; until then the S10 behaviour above holds.
|
|
4002
|
+
|
|
4003
|
+
- CI admission separates lost proof from active-coverage uncertainty. If a commit or jobs read for one prior successful run is missing (404), times out on its own per-read limit or is malformed, that run is dropped as reuse proof; if the success list itself fails that way, all reuse proof is dropped. Admission then continues and can require a fresh full gate, never reuse. Any other read failure (5xx, 403, 429, the response size bound, authentication) stays unknown, and an exhausted read budget or overall deadline is unknown even when it surfaces as a per-read timeout. Unknown active coverage still refuses admission, as before: an unreadable, truncated or unidentifiable in-progress inventory, or an exhausted read budget or deadline. The release reader (`--require`) still refuses without a verified identical-tree proof. Degrading unknown active coverage to a fresh run is not adopted; it would need a person ruling against #609.
|
|
4004
|
+
- Review-thread authors are classified only by `__typename`. A `User` comment answers. Every other author counts as a pending bot comment: `Bot`, a null or deleted author, `Organization`, `Mannequin`, `EnterpriseUserAccount` or any unlisted kind. An unresolved thread is therefore settled only by resolution or by a `User` comment after its last non-`User` comment. Invalid timestamps and truncated pages remain unavailable evidence. No comment body, login or other author field is read.
|
|
4005
|
+
- In advise, a done or delayed-completion review-admission readback during which only quiet events (`QUIET_EVENTS`: answer, reviewer changed, idle sweep, memory reference started) were appended to the task is discarded, and the transition continues with a generic unknown-readiness notice and no further external read. A quiet `reviewer changed` entry, or a changed reviewer field, does not refuse in advise: the transition re-derives the reviewer from the current task before it sends the review. Any other drift still refuses in advise: another history event, a change of mode, owner, privacy or receipt, a revoked conductor authority, or a private turn on ordinary work. Each transition's final owner/state/check fences run before its writes. Enforce refuses any drift, unchanged. A manual reviewer assignment or retry keeps its own readback rules.
|
|
4006
|
+
- A delayed check whose completion is refused tells the owner, privacy-screened, to call `hub_task_done` again, but only when the task is still `in_progress` and its owner is the one that started the check (the same condition as the late-result branch of `finishChecked`); otherwise the console notice alone is recorded.
|
|
4007
|
+
- Fixtures pin each rule before its implementation lands (`test/ci-reuse.test.ts` for admission: a lost row then proof reuses, lost rows without proof run, active-scan failures and budget exhaustion stay unknown, the release refusal stays; `test/cost-governance.test.ts` for thread authors and advise readbacks). The dead `findVerifiedRun` reader is removed with the admission change.
|
|
3895
4008
|
|
|
3896
4009
|
### S11 release-train amendment (#481, hub task #216)
|
|
3897
4010
|
|
|
@@ -3923,3 +4036,155 @@ The P1 host introduces additive `native_hook_launch` and `native_facts` control
|
|
|
3923
4036
|
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
4037
|
|
|
3925
4038
|
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.
|
|
4039
|
+
|
|
4040
|
+
|
|
4041
|
+
## Amendment: no-write owner advisories (#498)
|
|
4042
|
+
|
|
4043
|
+
- 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.
|
|
4044
|
+
- 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.
|
|
4045
|
+
- 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.
|
|
4046
|
+
- 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.
|
|
4047
|
+
|
|
4048
|
+
## Amendment: config-only ACP adapters, A3 (#451, #486)
|
|
4049
|
+
|
|
4050
|
+
A3 adds declarative ACP definitions. It does not add A4 module loading, executable
|
|
4051
|
+
callbacks or config-selected imports. `adapters.<kind>` is accepted only from a
|
|
4052
|
+
Git-confirmed untracked `.agenthub/config.local.json`; the entire declaration is
|
|
4053
|
+
machine-local authority, not just its command and arguments. A tracked local file
|
|
4054
|
+
or a shared `config.json` cannot introduce one. Validate plain own-data objects
|
|
4055
|
+
and bounded arrays before constructing definitions. Unknown fields, accessors,
|
|
4056
|
+
inherited authority and collisions with builtin or reserved identities refuse.
|
|
4057
|
+
|
|
4058
|
+
Each declaration names `transport: "acp"`, `command`, optional `args`,
|
|
4059
|
+
`displayName` and `roles`, with optional `identity: { name, version }`,
|
|
4060
|
+
`permission` (hub-mode to native-mode mapping) and `readOnly` (native mode id).
|
|
4061
|
+
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
|
|
4062
|
+
immutable catalog at startup; declared kinds and their explicitly declared
|
|
4063
|
+
instances resolve through that catalog, never a mutable global registry. Adapter
|
|
4064
|
+
changes, including removal and their derived defaults, require a restart.
|
|
4065
|
+
|
|
4066
|
+
Configured kinds are optional, cloud and headless-only, off roster by default.
|
|
4067
|
+
They do not change default routing or inherit another kind's identity, quota,
|
|
4068
|
+
context parser or privileged capability. A configured kind may join the roster
|
|
4069
|
+
through the existing person-controlled path. Roles, exact nominations and
|
|
4070
|
+
reservations, family independence, instance execution-root admission and PII
|
|
4071
|
+
refusal use the same host routing rules as builtin definitions. Kind preferences
|
|
4072
|
+
may expand to declared instances; an explicit instance or canonical id remains
|
|
4073
|
+
exact. Absence of a permission map means unmanaged native policy. Absence of a
|
|
4074
|
+
budget block exempts the registered kind from quota accounting; it is not a zero
|
|
4075
|
+
reading, while unknown external ids retain existing manual accounting.
|
|
4076
|
+
|
|
4077
|
+
The identity pin is optional in the declaration schema so an unverified adapter
|
|
4078
|
+
can be described, but a start without it refuses before a prompt. Initialize must
|
|
4079
|
+
return the pinned agentInfo name and version. A resumed `session/load` must echo
|
|
4080
|
+
the requested sessionId; a missing or different echo refuses before a prompt.
|
|
4081
|
+
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
|
|
4082
|
+
selects a leader or attaches to another native process is refused at load.
|
|
4083
|
+
|
|
4084
|
+
Owned-process admission has a deliberately bounded proof. The pinned native
|
|
4085
|
+
foreground contract must be supported by a T0 receipt; the spawned pid, original
|
|
4086
|
+
process group and process signature must have positive current evidence before
|
|
4087
|
+
admission and before stop. Unknown ownership refuses. Stop signals only that
|
|
4088
|
+
original verified owned group, never a pre-existing leader or any process outside
|
|
4089
|
+
it. ACP provides no generic kernel-bound responder attestation, and A3 adds no
|
|
4090
|
+
protocol extension pretending to provide one. A native agent that delegates its
|
|
4091
|
+
responses to an unowned process is unsupported by this boundary.
|
|
4092
|
+
|
|
4093
|
+
The Grok proof target is the config-only `grok agent stdio` command, with no
|
|
4094
|
+
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
|
|
4095
|
+
scratch project, preserve the person's config bytes, record the pinned build's
|
|
4096
|
+
process tree, and inspect sockets/IPC while a prompt runs for links to processes
|
|
4097
|
+
outside the owned group. It must separately record initialize identity, load echo,
|
|
4098
|
+
native mode support or absence, usage unknowns and an actual hub tool receipt.
|
|
4099
|
+
If in-process service and the no-external-IPC boundary cannot be established,
|
|
4100
|
+
Grok remains explicitly unsupported; the generic ACP path may still ship with a
|
|
4101
|
+
fake ACP transport receipt. This amendment describes the implementation contract,
|
|
4102
|
+
not completed Grok live verification. Fixture results are not a native T0 receipt.
|
|
4103
|
+
|
|
4104
|
+
The optional definition `readOnly` capability names a native mode; it does not
|
|
4105
|
+
select that mode for every instance. An instance selects it explicitly with
|
|
4106
|
+
`readOnly: true`. Admission requires the native mode to be advertised and its
|
|
4107
|
+
selection acknowledged before a prompt, plus the host's read-only kind/tool gate
|
|
4108
|
+
and MCP mutation refusal. The selection is non-raisable: runtime and stored
|
|
4109
|
+
permission changes cannot lift it. A missing mode, failed acknowledgement or
|
|
4110
|
+
unknown policy refuses rather than falling back to an ordinary permission mode.
|
|
4111
|
+
Mapped ordinary modes likewise require advertised native support and an
|
|
4112
|
+
acknowledged selection; no map remains unmanaged and grants no hub sandbox claim.
|
|
4113
|
+
|
|
4114
|
+
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`.
|
|
4115
|
+
|
|
4116
|
+
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.
|
|
4117
|
+
|
|
4118
|
+
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.
|
|
4119
|
+
|
|
4120
|
+
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.
|
|
4121
|
+
|
|
4122
|
+
## Adding an AI tool guide and adapter definitions (#451 A5)
|
|
4123
|
+
|
|
4124
|
+
- 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:
|
|
4125
|
+
- 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.
|
|
4126
|
+
- Transport selection: `acp` (stdio JSON-RPC), `codex-appserver`, `claude-channel`, `ws-pull` (pull-only workspace MCP plugin), and `hub-native`.
|
|
4127
|
+
- 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).
|
|
4128
|
+
- Review and verification checklist: T0 receipt, path protection (`AGENT_CONFIG_SEGMENTS`), PII safety (`trust.onPrem`), process isolation, and documentation freshness.
|
|
4129
|
+
- 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) follow the A4 trust boundary below.
|
|
4130
|
+
- `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.
|
|
4131
|
+
|
|
4132
|
+
|
|
4133
|
+
### A4 external module trust boundary (2026-10-21, #451)
|
|
4134
|
+
|
|
4135
|
+
`adapter_modules: [{ path, sha256 }]` is accepted only from an untracked,
|
|
4136
|
+
machine-local `.agenthub/config.local.json`. Shared, tracked, cloned or unknown
|
|
4137
|
+
provenance never evaluates module code. The declaration is person-owned,
|
|
4138
|
+
never settings-editable, and restart-bound. The daemon imports modules at
|
|
4139
|
+
startup; a CLI read, preview, reload or watcher never imports them.
|
|
4140
|
+
|
|
4141
|
+
Each pinned entry is a self-contained JavaScript bundle inside the canonical
|
|
4142
|
+
project root. Refuse symlink components, a file outside that root, a
|
|
4143
|
+
world-writable or nonregular entry, an unreadable/oversized entry and a SHA-256
|
|
4144
|
+
mismatch. A module bundles its own dependencies; core adds no third-party
|
|
4145
|
+
runtime dependency. Runtime permits static imports of Node/Bun builtins except
|
|
4146
|
+
`bun:ffi` and `bun:test`, plus the public `@staix/agent-hub/adapter` entry only.
|
|
4147
|
+
The `/conformance` export is for the module author's CI, not runtime modules.
|
|
4148
|
+
Relative imports, other package imports, dynamic/nonliteral imports, require,
|
|
4149
|
+
eval and Function-based code loading that the loader can detect are refused.
|
|
4150
|
+
Dependencies must be bundled as ESM; `import.meta.require` and CommonJS loading
|
|
4151
|
+
are refused. Use Bun.Transpiler's import records and token inspection before
|
|
4152
|
+
evaluation. After checking the original content pin, compile its scanned static
|
|
4153
|
+
SDK imports to file URLs of the running daemon's own adapter entry, never a
|
|
4154
|
+
project-resolved package copy. Re-hash the original captured bytes immediately
|
|
4155
|
+
before importing the rewritten result; never reopen the mutable source path.
|
|
4156
|
+
A pin is code authorization, not
|
|
4157
|
+
a sandbox: approved module code runs with daemon privileges; scanner rejection
|
|
4158
|
+
is deliberately conservative and is not a proof against obfuscated code.
|
|
4159
|
+
Refuse a failed module independently with a bounded, body-free status reason;
|
|
4160
|
+
keep builtins and other verified modules available. When trusted module
|
|
4161
|
+
declarations exist, unknown roster ids remain inert, including on CLI config
|
|
4162
|
+
reads while no daemon metadata is available; a roster row grants no adapter identity.
|
|
4163
|
+
|
|
4164
|
+
A module exports `definition` with the current `ADAPTER_API`. Validate the
|
|
4165
|
+
accepted structural subset and fixed-builtin capability exclusions before
|
|
4166
|
+
publishing it in the immutable project catalog. The initial accepted executable
|
|
4167
|
+
subset is a headless ACP definition (`transport: "acp"`, `start.daemonPort:
|
|
4168
|
+
"acp"`), with a factory returning the public AcpPeer transport for its bound id.
|
|
4169
|
+
Its own family, optional/off-roster defaults and cloud/unsandboxed trust are
|
|
4170
|
+
mandatory. Native hooks, budgets, configuration callbacks, reviewer backends,
|
|
4171
|
+
instance policies and other fixed-native fields are refused rather than
|
|
4172
|
+
advertised as usable extension points. A broader transport needs a reviewed
|
|
4173
|
+
kind-neutral host-port contract first (#625 phase 6). Builtin collisions, duplicate
|
|
4174
|
+
kinds, incompatible API and unsupported fields refuse the module. Never add
|
|
4175
|
+
a module to the process-global builtin registry. Builtin and configured instance
|
|
4176
|
+
namespaces (`<kind>-*`) are reserved against external module kinds.
|
|
4177
|
+
|
|
4178
|
+
The CLI receives external kinds only as bounded id/display metadata from the
|
|
4179
|
+
running daemon. That projection cannot grant permissions or register code.
|
|
4180
|
+
A4 refuses declared instances of external kinds with an explicit unsupported
|
|
4181
|
+
reason; their execution-root, recovery and CLI declaration contracts are a
|
|
4182
|
+
follow-up. The exported Bun conformance entry exposes callable transport
|
|
4183
|
+
registrations and packaged fake transports without executing those fakes on
|
|
4184
|
+
import. Builtins keep their existing applicable contract tests and explicit
|
|
4185
|
+
unsupported-leg reasons; an external module exercises the same contracts in
|
|
4186
|
+
its own CI.
|
|
4187
|
+
|
|
4188
|
+
### S2 plan conformance boundary (#481)
|
|
4189
|
+
|
|
4190
|
+
Optional proof/risks lists are bounded to 32 strings of 512 characters. Literal file/directory-prefix coverage uses complete current PR file metadata, never patches or glob execution. Root plan entries never cover all files; renamed/copied files whose old identity is unavailable make the inventory unknown. Untouched paths are advisory; uncovered changed paths block enforce approval until plan amendment or person acceptance. Host content revisions are separate from backlog planningRevision. "Plan revisions after the first observed push" binds the first observed remote head to plan fingerprint/revision; earlier pushes are unknown. Pending observations persist only at successful owner/reviewer transitions; status and queue reads never append history, and off mode captures no S2 observations or content revisions. Lost pending intervals remain unknown. The ceiling is no true push-event source; the upgrade path is a webhook or Actions push receipt. Person-only `ahub sdlc accept-departure <task> <head> <reason...>` records complete current repo/PR/head inventory and plan fingerprint/revision, with bounded sanitized privacy-screened rationale; changed identity, plan or inventory invalidates it, including plan edits while reporting is off. Departure consent expires on host restart to prevent unpersisted invalidations from reviving; the upgrade path is a durable host observation journal. Private projections reveal only existence, and unavailable private inventories stay unknown.
|
|
@@ -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,43 @@
|
|
|
1
|
+
# #469 R1: dashboard design tokens, screenshots and layout check
|
|
2
|
+
|
|
3
|
+
Task #170, branch `feat/469-r1-tokens`. R1 changes the style block, the peer palette variants and the contrast test. It changes no
|
|
4
|
+
layout, view, key or behaviour.
|
|
5
|
+
|
|
6
|
+
## Source of the screenshots
|
|
7
|
+
|
|
8
|
+
A scratch hub created under a `mktemp`-style directory outside any git repository (its own `AGENTHUB_HOME`, never the person's
|
|
9
|
+
`~/.agenthub` or live hub), with five fake pull-only peers (claude, codex, kimi, pi, local), eight sample tasks (one in progress,
|
|
10
|
+
one backlog) and one console message. Rendered by a headless Chromium (Chrome for Testing 156) over the DevTools protocol with
|
|
11
|
+
`prefers-color-scheme` emulated, at 1440x1000 and 390x900. The Board view is shown: it is the default view, and the one R1's rules
|
|
12
|
+
touch most. The other views use the same tokens.
|
|
13
|
+
|
|
14
|
+
| | light | dark |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| 1440 px | `assets/469-r1-board-1440-light.png` | `assets/469-r1-board-1440-dark.png` |
|
|
17
|
+
| 390 px | `assets/469-r1-board-390-light.png` | `assets/469-r1-board-390-dark.png` |
|
|
18
|
+
|
|
19
|
+
Computed `body` background in the run: light `rgb(238, 238, 241)`, dark `rgb(18, 19, 22)`; `scrollWidth` equals the viewport width at
|
|
20
|
+
both sizes in both themes (no sideways scroll).
|
|
21
|
+
|
|
22
|
+
## "No layout change", measured
|
|
23
|
+
|
|
24
|
+
The same sample hub was rendered from `origin/main` (c4b1c998) and from this branch, and the rectangles of the header, the nav, its
|
|
25
|
+
buttons, the board section, the progress strip, the agent slots, the columns and the cards were compared (148 rectangles over the
|
|
26
|
+
four size and theme combinations).
|
|
27
|
+
|
|
28
|
+
- Exact pixel equality does not hold, and is not claimed. R1 snaps spacing to the 4px scale, so three paddings grew from 10px to
|
|
29
|
+
12px: the board column (`.board-column`, 10px to 12px: columns 600 px to 635 px tall at 1440), the agent slot
|
|
30
|
+
(`.agent-slot`, 10px to 12px: 166 px to 178 px) and the 14px/18px section and row paddings moved to 12px/16px (`.section-head` 14px 18px to 12px 16px). Cards keep their height
|
|
31
|
+
except one empty-state card (47 px to 68 px) and are 3 px narrower at 1440 because their column is 4 px narrower inside. Nav
|
|
32
|
+
buttons are up to 4 px wider (control padding 6px/10px to 8px/12px; their 44 px minimum height hides the vertical change). The whole 390 px page is 78 px taller (2905 to
|
|
33
|
+
2983) and the 1440 px page 16 px taller (1595 to 1611).
|
|
34
|
+
- Structure is identical in all 16 checks (4 selectors x 4 combinations): the same number of columns and rows, the same reading
|
|
35
|
+
order, 5 board columns at 1440 and 1 at 390, 8 nav buttons in 1 row at 1440 and in 2 rows at 390, the same 3 card columns.
|
|
36
|
+
- Nothing scrolls sideways at 390 or 1440 px after the change.
|
|
37
|
+
|
|
38
|
+
## Not covered
|
|
39
|
+
|
|
40
|
+
- Only the Board view was photographed. Office, Stream, Agents, Message, Memory and Settings were not, so their light and dark
|
|
41
|
+
rendering is covered by the token and contrast tests and by reading the CSS, not by a picture.
|
|
42
|
+
- 768 and 1024 px were not photographed (they belong to R2's AC3).
|
|
43
|
+
- Chrome only; no Firefox or Safari rendering was run.
|