@staix/agent-hub 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +10 -5
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +1 -1
  5. package/docs/agent-notes/daemon.md +6 -4
  6. package/docs/agent-notes/tasks.md +5 -4
  7. package/docs/agent-notes/tests.md +1 -0
  8. package/docs/dogfood.md +4 -0
  9. package/docs/operations.md +131 -14
  10. package/docs/quickstart.md +14 -4
  11. package/docs/security.md +51 -8
  12. package/docs/specs/2026-11-01-research-campaigns-design.md +9 -6
  13. package/docs/verification/2026-11-01-0.26.0.md +54 -0
  14. package/docs/verified.json +28 -16
  15. package/package.json +1 -1
  16. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  17. package/plugins/agent-hub/server.js +182 -95
  18. package/src/adapters/channel-peer.ts +4 -2
  19. package/src/adapters/definitions/builtin-metadata.ts +6 -6
  20. package/src/adapters/definitions/qwen.ts +2 -0
  21. package/src/adapters/registry-types.ts +2 -0
  22. package/src/adapters/registry.ts +1 -0
  23. package/src/cli/conductor-recovery.ts +3 -2
  24. package/src/cli/help.ts +7 -7
  25. package/src/cli/main.ts +62 -23
  26. package/src/cli/recovery-package.ts +94 -4
  27. package/src/cli/reset.ts +14 -4
  28. package/src/cli/runtime-version-warning.ts +28 -0
  29. package/src/cli/start-agents-run.ts +125 -0
  30. package/src/cli/task-output.ts +5 -0
  31. package/src/cli/upgrade-interactive.ts +4 -4
  32. package/src/cli/upgrade-runtime.ts +105 -57
  33. package/src/cli/upgrade.ts +90 -28
  34. package/src/hub/autonomy-defaults.ts +32 -7
  35. package/src/hub/board.ts +129 -13
  36. package/src/hub/budget.ts +3 -4
  37. package/src/hub/child-process.ts +4 -3
  38. package/src/hub/conductor.ts +3 -4
  39. package/src/hub/config-apply.ts +13 -2
  40. package/src/hub/config-edit-host.ts +1 -0
  41. package/src/hub/config-tier.ts +3 -0
  42. package/src/hub/config-trust.ts +10 -2
  43. package/src/hub/control-client.ts +30 -10
  44. package/src/hub/daemon.ts +82 -17
  45. package/src/hub/db-schema.ts +143 -0
  46. package/src/hub/delivery-journal.ts +3 -4
  47. package/src/hub/execution-budget.ts +3 -4
  48. package/src/hub/folder-trigger.ts +3 -4
  49. package/src/hub/invocations.ts +3 -4
  50. package/src/hub/lifecycle.ts +87 -29
  51. package/src/hub/manager.ts +49 -33
  52. package/src/hub/orca-env.ts +19 -0
  53. package/src/hub/project-runtime-state.ts +139 -0
  54. package/src/hub/project-runtime.ts +62 -0
  55. package/src/hub/protocol-window.ts +19 -0
  56. package/src/hub/recovery-store.ts +102 -12
  57. package/src/hub/registry.ts +5 -0
  58. package/src/hub/research-experiments.ts +33 -4
  59. package/src/hub/research-runner.ts +172 -0
  60. package/src/hub/restart.ts +9 -0
  61. package/src/hub/runtime-capture-prune.ts +415 -0
  62. package/src/hub/runtime-prune-health.ts +112 -0
  63. package/src/hub/schedules.ts +3 -4
  64. package/src/hub/sdlc-merge.ts +7 -0
  65. package/src/hub/sdlc-queue.ts +13 -1
  66. package/src/hub/settings.ts +4 -0
  67. package/src/hub/snapshots.ts +3 -4
  68. package/src/hub/start-agents.ts +44 -0
  69. package/src/hub/task-approval.ts +23 -0
  70. package/src/hub/tasks.ts +133 -34
  71. package/src/hub/usage-rollup.ts +3 -4
  72. package/src/pi/tool-receipts.ts +3 -4
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.25.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.26.0
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -27,8 +27,13 @@ From a clone instead: `git clone`, `bun install`, `bun link`.
27
27
 
28
28
  For managed launchers, two-agent task/review work, approvals, graceful shutdown,
29
29
  and upgrade or crash recovery, see the [operations guide](operations.md). To
30
- move running projects to a newer release later, run `ahub upgrade` in a
31
- terminal: it reviews the plan with you, applies it and follows it.
30
+ move a running project to a newer release later, run `ahub upgrade` in a
31
+ terminal in that project: it reviews the plan with you, applies it and follows
32
+ it; other projects keep their version.
33
+
34
+ ## Upgrading to 0.26.0
35
+
36
+ `ahub upgrade` now upgrades one project: the one in the current directory, or the one `--project <path|id>` names; other projects keep their running version (#1039, #1045). From a 0.25.0 CLI, run it in the project directory, since 0.25.0 refuses `--project` for upgrade; it hands over to the 0.26.0 coordinator as before. It no longer updates the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.26.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon. The daemon speaks control protocol 17 and still accepts 16, so a 0.25.0 plugin keeps connecting. Each project keeps its verified runtime across stop and start; start it with a 0.26.0 or later CLI, because an older CLI ignores that record (#1052). A daemon refuses a `hub.db` in a newer format and names the version it needs. `ahub up` now also starts `claude` and `codex` (see [First session](#first-session)): `ahub setup` asks once which agents (`--agents <a,b>`, `none` for no agent), `ahub settings set start.agents <a,b>` changes one project, and `ahub up --no-agents` skips them for one run; a restart, recovery or upgrade relaunch starts none (#1046). An owner can re-file `hub_task_done` on an approved, unmerged task with a new head, which sends it back to review, and a reviewer can withdraw an approval with `changes_requested` (#1049). Signed external runner receipts for research attempts are opt-in: the person registers a runner under `research.runners` in untracked `.agenthub/config.local.json` (#1050). Restart Claude sessions so they load the 0.26.0 plugin.
32
37
 
33
38
  ## Upgrading to 0.25.0
34
39
 
@@ -167,9 +172,14 @@ In your project directory, one terminal each:
167
172
 
168
173
  ```bash
169
174
  ahub init # .agenthub/config.json, .agenthub/routing.toml, the marker block in AGENTS.md
170
- ahub up # starts the daemon and opens the operator console in a terminal
175
+ ahub up # starts the daemon, opens claude and codex in terminals (see below), then the operator console
171
176
  ahub console # use in another terminal if up used --no-console; Tab opens panels
172
177
  ahub kimi # Kimi in its own pull-only TUI; --mode headless for the hub-driven peer
178
+ ```
179
+
180
+ `ahub up` also starts a default set of agents, `claude` and `codex` unless you choose others, each in a terminal the hub opens (`terminal.open`, or Orca). It prints one line per agent, and an agent it cannot open is named with the command to run yourself (`ahub claude`, `ahub codex`). Choose the set once with `ahub setup --agents kimi,codex` (or answer the question in `ahub setup`), change one project with `ahub settings set start.agents <a,b>`, and skip it for a run with `ahub up --no-agents`. Without a terminal provider nothing is opened and the hub keeps running; start them by hand:
181
+
182
+ ```bash
173
183
  ahub codex # Codex TUI, attached through the hub
174
184
  ahub claude # Claude Code with the hub channel
175
185
  ```
package/docs/security.md CHANGED
@@ -7,7 +7,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
7
7
  - **Other agents' text is untrusted.** Every message that crosses from one peer to another is framed as untrusted input: a channel tag with `meta.source` for Claude started with `ahub claude`; otherwise a fixed header line (`frame()`, `[agent-hub message from ...]`) with an instruction that says it is untrusted, the plugin's server instructions for a tools-only Claude reading `hub_inbox` and a standing instruction for the others. A message body cannot forge the hub's own headers: such lines are quoted (`sanitize`). Replies inherit a hop count capped at 3, so agents cannot ping-pong forever; neither a digest nor a steer can reset it.
8
8
  - **The control link is loopback plus a secret.** The daemon and the Codex proxy bind 127.0.0.1 only. The control WebSocket requires a per-run token (`.agenthub/state/control-token`, mode 600), and both servers refuse any request that carries an `Origin` header: any web page can open a WebSocket to localhost, and browsers always send `Origin`. The bearer header a Codex TUI sends to the proxy carries only the terminal launch id (#586): it binds terminal ownership and is not a credential. External clients cannot claim the console user's id or a hub-managed peer's id.
9
9
  - **Permission prompts stay on unless an operator selects a permission mode.** `ask` leaves native decisions unchanged. `ask-when-needed` grants scoped Claude file tools or Pi/local writes and edits, and a headless Claude implementer's proven shell commands through the host wrapper (#845, below); Kimi and Codex use their own native policies. `never-ask` requires explicit runtime confirmation, a person's console `y` for a config default at hub start, or retained autonomy consent bound to that peer's exact never-ask value in the untracked `config.local.json` (#999, below). Without one of these the peer runs in `ask`. Kimi's and `local`'s permission requests are relayed to the console and cancelled after `approvals.timeout_s` (default 120 s) of silence; the macOS notification for a waiting request carries the peer and the tool name only. The one exception is the hub's own tools (`hub_send` and the task tools, matched by exact name): Kimi's requests for them are approved once without a prompt and logged by name, the same trust Codex gets through `approval_mode` in the hub's config. They invoke hub-owned operations rather than arbitrary file or shell tools, and every call passes the hub's own checks. A call the always-ask policy classes (below) is never approved this way, whatever its title. Identity comes from the exact permission title, or from the earlier tool-call title bound to the same call id and resolved against the configured MCP servers when the permission title contains argument JSON (Qwen). Payload text and unrelated display titles never establish identity. Configured ACP kinds (#451) never get this exception: a request whose title spells a hub tool exactly still goes to the person approval path, and only a mapped native mode changes what the agent itself asks. For ACP agents, a payload longer than the console shows is marked as cut and never offers a session-wide grant. Pi's write, edit, shell and git write requests are relayed the same way and can be answered `Always allow <tool> until Pi restarts`: later calls of that tool by the same Pi start skip the prompt but stay inside the path guard and sandbox. The grant covers every later call of that tool whatever its payload, is kept in memory only, cannot be given from the dashboard, and each call it allows is logged by tool name. A Pi or `local` request whose call was cancelled or whose turn ended is withdrawn at once: a later answer is refused and grants nothing, an `Always allow` included. One shell command runs without a hub approval: the `!command` a person types in the managed Pi TUI. It stays inside the same sandbox and denylist, and the hub takes it only from the verified owner process of a Pi in TUI mode, while Pi is idle, on a reservation that is used once; a model's own bash call never takes that path (issue #253). `--unattended` turns prompts off, says so loudly, and is never the default; a classed call from a hub-owned ACP peer, the local worker or Pi still waits for a person under it.
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` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules` (the `every`, `cron` and `tz` forms alike), `folder_triggers`, `corpus.paths`, `invocation`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
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` (still accepted, ignored since 0.16.0), `local.read_allow`, `local.bash_network`, `local.network_allow`, `local.doc_read`, `local.web_fetch`, `terminal.open`, `tasks.auto_pull`, `tasks.queue_backpressure`, `context.max_assignment_used`, `schedules` (the `every`, `cron` and `tz` forms alike), `folder_triggers`, `corpus.paths`, `invocation`, `start.agents`, `instances`, `action_policy`, `rooms`) apply only from a config file git confirms nobody committed (`instances`, `action_policy` and `rooms` are read only from an untracked `.agenthub/config.local.json`; in `config.json` they are ignored even when untracked): `.agenthub/config.json` or `.agenthub/config.local.json`, matched by file identity so no other spelling the file system accepts slips past, and `.agenthub` itself not a committed symlink or submodule. Without a repository, or when git fails, they keep their defaults; an empty value always means the default. "Untracked" is answered by the repository that contains the project: a checkout copied or extracted into an unrelated repository, or into an ignored directory of one, is trusted like your own files. So a cloned repository cannot choose a launch command, a completion check, a gateway to send a key file to, a memory endpoint, a permission mode, the command that opens a terminal, which agents `ahub up` opens terminals for, a folder the hub reads, work proposed on a timer, a host a model may fetch from, which of your agents' skills other peers may call, or a wider sandbox. A command in `checks` runs as you, outside the local worker's sandbox, like a git hook. So do the `setup` and `verify` commands of a benchmark suite (issue #251): `ahub bench run <suite.json>` is a person's command, refused from an agent session, and it resets the work tree for each attempt, so it runs only with `bench.enabled`, at the root of a clean repository that tracks nothing under `.agenthub/`. Nothing in `routing.toml` or in task text is ever run. `routing.toml` and the other shared fields still come from the checkout, and they matter: `routing.toml` picks the models the local worker and the hub's inference use at your gateway and can turn the PII constraint off, and roles and budget shape who does what. Review them in a repository you do not trust. The hub's own git calls outside the sandbox (snapshots, facts, config trust, project discovery, memory recall, the bench runner) all use the single hardened implementation in the exact-pinned first-party `@staix/agent-mem/platform` package, re-exported by `src/hub/git.ts`, which disables the programs a repository's configuration can name — the file-system monitor, hooks, external diff, the pager, and the credential and askpass helpers, with prompts failing closed — so a config that ever does get written cannot run code with the hub's rights. Of those, the monitor, hooks and external diff are reachable by commands the hub issues (the planted-program test exercises all three; a `diff --no-index` reads the repo's config, so that call also passes `--no-ext-diff` and `--no-textconv`); the pager is not (every call pipes stdout). Credential and askpass helpers are disabled for project repositories; memory sync alone may use credentials for its own memory repository, without prompts. The cost: `core.fsmonitor=false` also turns off a person's enabled built-in monitor for the hub's own calls, so the per-turn snapshot's `add -A` scans the whole tree (the snapshot Git calls of one burst share a 250 ms budget, each call at most 10 s; a boundary that runs out is recorded as unknown, never as a stale baseline) (#576). git has no global switch for filter drivers, and of the hub's commands only the snapshot's `add`/`restore` would run one (its driver command lives in the protected `.git/config`).
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. Budget pauses, resumes and quota resets carry the peer, window, times and the hub's own reason text, and a person's release authorization its commit, version and docs digest. The usage roll-up (#683) folds the usage, token and limit records into `hub.db` and joins issue, PR and release from the board when read; `ahub usage` and the console's Usage panel answer only the console and a configured planner, and a planner not during a private turn. Kimi's token records (#285) are counted from its own session wire log under its home: only the four token integers, the model label and the row time are copied, and the record id is an opaque hash, never a path, session id or text. The log stays on the machine; `ahub export` only prints it. Two opt-in stores copy numbers from it into your home directory, shared by every project on the machine (directories mode 700, files 600): research records (issue #247, `research.enabled`) in `~/.agenthub/research/` hold per approved task the ids, states, counts, tokens, times and PII flag the events hold, a person's label from a closed list, and a hash in place of the project id; benchmark runs (issue #251) in `~/.agenthub/bench/` hold suite name and hash, arm label, the attached peers' ids, models and permission modes, task ids, outcomes, exit codes, counts, tokens and times, never suite text or a command's output.
12
12
  - **Snapshots stay in your repository.** Per-turn snapshots (issue #33) are git objects in the project's own object store, written through a temporary index; nothing is referenced, pushed or copied elsewhere, and `git gc` prunes them. They hold what the work tree held, including untracked files that are not ignored, so keep secrets in ignored files. They carry the repository's own permissions, and nothing caps their disk use but `git gc`. A turn of a peer holding an open PII task is not snapshotted; a PII file left in the project is snapshotted by later turns like any other file. `ahub undo` restores only files whose current content is exactly what the turn left.
13
13
  - **The edit hook reads, never decides.** `ahub check-path --hook` (issue #32) reads hub.db and returns context for Claude and a line for you; it sets no permission decision, so your permission rules stay in charge. It names other owners' task ids, titles and states, which then reach Claude's model; PII tasks are left out.
@@ -848,16 +848,17 @@ role admission.
848
848
  contract's `budget.hardCap` is checked against that exact budget id at approval
849
849
  and again at every dispatch and owner change; another budget covering the task
850
850
  proves nothing. A hold never stops a running native turn.
851
- - **Provenance is never upgraded.** Experiment fields are `declared` unless the hub
852
- measured them itself from a finished check receipt for the same task and commit,
853
- or by hashing a deliverable through the #509 path policy; an agent-supplied hash
854
- is never host evidence. `ahub study approve-protocol` binds approval to one exact
851
+ - **Provenance stays separate.** Experiment declarations remain `declared`. Separate
852
+ `host-observed` values come from a finished check receipt for the same task and
853
+ commit, or from hashing a deliverable through the #509 path policy; an
854
+ agent-supplied hash is never host evidence. `ahub study approve-protocol` binds approval to one exact
855
855
  protocol hash and evaluator identity hash, and nothing copies it to a changed
856
856
  protocol. Environment descriptors are refused when they contain assignment
857
857
  forms, Bearer credentials or known secret shapes; that screen detects those forms
858
- only and does not prove a secret is absent. This release exposes no hub tool for
859
- recording experiments, and runner attestation (`research.runners`) is not
860
- implemented yet.
858
+ only and does not prove a secret is absent. Signed external receipts use the
859
+ separate `runner-attested` level described below; their fields never overwrite
860
+ declared or host-measured evidence. Public experiment recording tools and the
861
+ receipt CLI are the separate #602 slice.
861
862
  - **Privacy.** A campaign whose parent task is PII, or that names a private source,
862
863
  is private, and so is an attempt with a private task, input or output path.
863
864
  Public views keep ids, counts, states and provenance and stub every question,
@@ -869,6 +870,48 @@ role admission.
869
870
  `research_*` feature is refused while campaign, dispatch, re-offer, experiment or
870
871
  comparison rows exist, or while `research.runners` is configured.
871
872
 
873
+ ### Registered external runner receipts (#1018)
874
+
875
+ A runner registration is a trust root, not permission to execute a command. The
876
+ person registers an Ed25519 SPKI public key, allowed project ids and a revocation
877
+ flag under `research.runners` in untracked `.agenthub/config.local.json`. Shared,
878
+ tracked or untrusted sources refuse even an empty block. Whole-block structural
879
+ edits require the person's tier2 confirmation; the conductor cannot register a
880
+ key. The hub stores no private key, contacts no runner and schedules no compute.
881
+
882
+ Receipt admission verifies the domain-separated canonical body, signature,
883
+ current accepted registration and exact project/attempt/protocol binding. A
884
+ commit-based protocol also requires an explicitly supplied commit claim to match.
885
+ The current owner or the person may relay a receipt; signature authority never
886
+ bypasses current task, source, campaign, privacy or request authority. All checks
887
+ are repeated after asynchronous screening before any write. An unknown/revoked
888
+ key, foreign project, changed identity or tampered signature refuses before write.
889
+
890
+ Stored fields are `runner-attested`, including claimed timestamps, exit status,
891
+ image, network, resources and file hashes. Missing fields remain unknown. A signed
892
+ commit for a deliverable-based protocol stays an independent claim with unknown
893
+ code match. Sealing is attested only with distinct candidate/evaluator containers,
894
+ valid mount references and nonempty evaluator code/data mounts that are all
895
+ read-only and absent from candidates. Other receipts retain declared/unverified
896
+ sealing. This is a signed runner statement, not host observation or proof of
897
+ scientific correctness, isolation enforcement or evaluator secrecy.
898
+
899
+ An identical canonical receipt and signature with a known receipt id returns its
900
+ stored proof without re-verification or writes, including after revocation. A
901
+ changed body/signature at that id refuses; every new id needs a currently trusted
902
+ key. Readback retains as-of-signing provenance and separately shows whether the
903
+ signing key is active, revoked, replaced or unavailable now. Private records stub
904
+ the signed fields, mount/output names, signer identity and key details.
905
+
906
+ The first stored attestation, its event and the `hub.db` format-2 marker commit in
907
+ one transaction; unused databases keep the format-1 baseline. Recovery requires
908
+ `research_runners` for stored receipts or raw/accepted registration presence,
909
+ including empty blocks. Its release-equality integrity digest covers stored proofs only and is present
910
+ only when receipts exist. Accepted registrations remain configuration, governed by
911
+ the feature floor and the config-reload path; pending edits do not change the
912
+ board-state equality required for release. Earlier research guards remain intact. Fixtures
913
+ use test key pairs with no GPU, runner network request or live account.
914
+
872
915
  ## Cross-project task handoff (#604, #1030)
873
916
 
874
917
  A handoff crosses a project boundary, but not a security boundary: both projects
@@ -218,14 +218,14 @@ Protocol approval:
218
218
  - This block lives only in the untracked `.agenthub/config.local.json`. A tracked or shared file naming it throws at load, the `checks.remote` precedent.
219
219
  - It is added to `ALL_MACHINE_LOCAL` and is a structural tier-2 key for `ahub settings edit`. Its writes are person only.
220
220
  - `name` follows the id rule. At most 16 runners.
221
- - Revocation sets `revoked: true`. The key is kept, so old receipts still show who signed them, but a revoked key verifies nothing new.
221
+ - Revocation sets `revoked: true`. The key is kept, so old receipts still show who signed them, but a revoked key verifies nothing new. Readback retains the key identity that verified the stored receipt and reports its current status separately (active, revoked, replaced or unavailable); a later config change never rewrites the receipt's as-of-signing provenance.
222
222
  - The hub stores no private key.
223
223
 
224
224
  ### AttemptReceiptV1
225
225
 
226
226
  The runner signs `"agent-hub.runner-receipt/v1\0" + canonical JSON of the receipt body` with Ed25519. The hub verifies with `node:crypto`.
227
227
 
228
- The body is `{ receiptId, runner, projectId, attemptId, protocolHash, codeCommit, imageDigest, containers: [{ id, role: "candidate" | "evaluator" | "other" }], mounts: [{ container, path, readOnly }], network: "none" | "restricted" | "open", resourceLimits, startedAt, endedAt, exitStatus, outputs: [{ name, sha256 }], metricsFile?: { name, sha256 } }`.
228
+ The body is `{ receiptId, runner, projectId, attemptId, protocolHash, codeCommit, imageDigest, containers: [{ id, role: "candidate" | "evaluator" | "other" }], mounts: [{ container, path, readOnly }], network: "none" | "restricted" | "open", resourceLimits, startedAt, endedAt, exitStatus, outputs: [{ name, sha256 }], metricsFile?: { name, sha256 } }`. The identity fields `receiptId`, `runner`, `projectId`, `attemptId` and `protocolHash` are required; all observed fields are optional and an omitted observation remains `unknown`.
229
229
 
230
230
  Verification refuses before any write when any of these holds:
231
231
  - the runner is unknown or revoked;
@@ -236,7 +236,7 @@ Verification refuses before any write when any of these holds:
236
236
 
237
237
  Each mount also carries `purpose: "evaluator-code" | "evaluator-data" | "candidate-output" | "other"`.
238
238
 
239
- A verified receipt appends one `attestation` event, and its fields are `runner-attested`. `sealed` becomes verified only when every condition holds:
239
+ A verified receipt appends one host-generated `attestation` event, and its fields are `runner-attested`. The declared event parser refuses caller-supplied attestation events. Runner fields are projected per receipt, separately from declared and host-observed evidence; missing fields remain `unknown`. A matching `codeCommit` may attest the match only when the protocol names that exact commit. For a deliverable-based protocol, the signed commit remains an independent runner claim and the code-to-deliverable match stays `unknown`; it is never a host observation. `sealed` becomes verified only when every condition holds:
240
240
  - at least one container has role `candidate` and at least one has role `evaluator`, and their ids are disjoint and unique;
241
241
  - every mount's `container` names a listed container;
242
242
  - at least one mount has purpose `evaluator-code` or `evaluator-data` and is on an evaluator container;
@@ -244,7 +244,9 @@ A verified receipt appends one `attestation` event, and its fields are `runner-a
244
244
 
245
245
  Missing, empty or inconsistent isolation evidence leaves `sealed` declared or unverified, never verified.
246
246
 
247
- A receipt with a known `receiptId` and identical bytes is a no-op. The same `receiptId` with different bytes refuses.
247
+ A receipt with a known `receiptId` and identical canonical signed bytes (canonical receipt body and signature) is a no-op, including after its signing key is revoked, replaced or removed. Replay returns the stored proof without a new verification, event or write. The same `receiptId` with different canonical signed bytes refuses. Any new receipt id must pass verification against the current registration and key status; an unknown or revoked key therefore admits nothing new.
248
+
249
+ The stored receipt, its `attestation` event and the first incompatible database-format marker advance share one transaction. Readers support hub.db format 2 while opening a fresh or legacy database keeps baseline format 1; only the first successful receipt insertion advances it to format 2. A rejected receipt or failed transaction advances nothing. The `research_runners` recovery floor protects both stored receipts and a raw runner config key independently of the earlier research recovery floors. Release-equality integrity includes the stored receipt count and row digest only when receipts exist. Accepted registrations and raw key presence remain config/feature-floor inputs, not board-state equality inputs, so pending file edits and legacy snapshots without runner rows can release normally.
248
250
 
249
251
  ### Delivery
250
252
 
@@ -265,9 +267,10 @@ All new tables live in `hub.db`, created in the `Board` constructor with `CREATE
265
267
  | `research_protocols` | `hash` | campaign id and revision, body JSON, by, at |
266
268
  | `research_plans` | `hash` | campaign id, body JSON, by, at |
267
269
  | `research_attempts` | `attempt_id`, unique `(project, idempotency_key)` | protocol hash, task id, phase, retry_of, reproduces, by, at |
268
- | `research_attempt_events` | `(attempt_id, seq)`, unique `receipt_id` when present | kind, provenance, body JSON, at |
270
+ | `research_attempt_events` | `(attempt_id, seq)`, unique `(project, idempotency_key)` | kind, authority-derived provenance, body JSON (receipt id for an attestation), at |
271
+ | `research_runner_receipts` | `receipt_id`, unique `(attempt_id, event_seq)` | canonical receipt, signature, signing public key, body hash, by, at, private |
269
272
 
270
- `board.hasResearchState()` reports whether any row exists. Readback is paginated: at most 50 rows per call, with a cursor.
273
+ `board.hasResearchState()` reports whether any row exists. Campaign, attempt and event readback is paginated: at most 50 primary rows per page, with a cursor. The runner slice bounds signed receipts to 64 per attempt and projects them separately; a later expansion needs a reviewed receipt-page contract.
271
274
 
272
275
  ## Wire names
273
276
 
@@ -0,0 +1,54 @@
1
+ # 0.26.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.26.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.26.0, prepared from main f14cb83b (6 commits since v0.25.0, 3 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.25.0: #1052, #1051, #1050, #1046, #1049, #1045.
12
+
13
+ Each merged after a claude `hub_review` approval with exact-head CI green, no unresolved review thread and no pending change request:
14
+
15
+ - #1045 (task 607, staged upgrades): approved at 54298320, exact-head CI run 41284968505. The board bound the approval to 5b55eaa7, because hub 0.23.1 refuses in_review re-files; the verdict note names the delta to 54298320.
16
+ - #1049 (task 614, re-review an approved task at a new head): approved at c0768919, run 41288118624.
17
+ - #1046 (task 608, default agent set): approved at 855d1c4e, run 41290278469 (the aggregate jobs re-run on attempt 2 to refresh GitHub's rollup). The board bound it to 5bb4d8ca; the verdict note names the delta.
18
+ - #1050 (task 600, signed runner receipts): approved at 1333c1ec, run 41298012917, after two Important findings were fixed. The board bound it to 0be7de7d; the verdict note names the main merge in between.
19
+ - #1051 (task 613, ORCA_* scrub): approved at b9e544ab, run 41304275881, after two Important test-rule findings were fixed.
20
+ - #1052 (task 610, sticky project runtime and capture pruning): approved at de1cf56c, run 41306542595 attempt 2 (attempt 1 stopped at the CI admission step before any test), after three Important findings were fixed.
21
+
22
+ Independent OCR read-only reviews ran on #1045, #1050 and #1052.
23
+
24
+ ## Native acceptance receipts
25
+
26
+ Receipts added under `docs/verification/` since v0.25.0: none.
27
+
28
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.26.0`, person only, and is not recorded in this file. The tag run refuses without it.
29
+
30
+ ## Document review
31
+
32
+ Stale at preparation (`node scripts/check-docs.mjs`): README.md, docs/agent-notes/{README,adapters,benchmarks,budget,bus,daemon,tasks,tests}.md, docs/operations.md, docs/quickstart.md, docs/security.md and src/cli/help.ts. Most were stamped at aaccc30b; benchmarks.md at ce8da3ec. Three independent read-only reviews read each one against the diff of its covered sources since its stamp. They found:
33
+
34
+ - README.md: control protocol 17 (accepts 16), `ahub up` opening claude and codex, per-project `ahub upgrade` with the CLI and plugin updated separately, and the report link.
35
+ - docs/quickstart.md: the v0.26.0 tag, per-project upgrade, and a new "Upgrading to 0.26.0" section.
36
+ - src/cli/help.ts: `init` inheriting `start.agents`, `research.runners` under `settings edit`, and the doctor pruning rows. src/cli/main.ts gained `--project` in the upgrade usage line.
37
+ - docs/operations.md: the guide header (0.26.0, protocols 16 through 17), the "planned 0.26.0" table row, re-file and withdrawal of approved tasks (#1047), `research_runners` in the upgrade floors, the `start.agents` settings row, and `ALL_MACHINE_LOCAL`.
38
+ - docs/security.md: `start.agents` in the machine-local list.
39
+ - adapters.md: `start.upStart` as a fixed-builtin field. daemon.md: scoped receipts at schema 3, and scope paths for the new runtime, schema, protocol-window and default-agent modules. tasks.md: scope paths for research-runner.ts and task-approval.ts. tests.md: the preload named, and a workstation-only remote-run sentence removed.
40
+ - AGENTS.md "Area notes" and docs/verified.json: the same new paths.
41
+ - README.md (notes), benchmarks.md, budget.md and bus.md: accurate as they were.
42
+
43
+ All 13 were then restamped at 362859ec with `release-prepare.ts restamp --reviewed`.
44
+
45
+ ## Checks
46
+
47
+ On release/0.26.0 after the restamp: `node scripts/check-docs.mjs --require-fresh` printed `docs: OK (0 stale documents)`; `bun x tsc --noEmit` exited 0; `bun scripts/build.mjs --check` exited 0 (the bundle diff is the version string only); `node scripts/check-package.mjs` printed `package: OK (723 files, @staix/agent-hub@0.26.0)`. The release PR's exact-head CI run is the full test gate.
48
+
49
+ ## Known gaps, not blocking
50
+
51
+ - No local full test suite: heavy tests run off the workstation, and the release PR's exact-head CI is the gate. Each merged PR had its own exact-head CI green (see Reviews).
52
+ - No running-hub upgrade to 0.26.0, no live TUI launch and no live check of `ahub up` opening the default agents in a real terminal; those are covered only by tests with stubs.
53
+ - The scoped-upgrade guarantee (a project's recorded runtime survives restart) starts with 0.26.0 CLIs; an older published CLI ignores the record. This ceiling is documented in docs/operations.md.
54
+ - One single non-reproducible failure of test/terminal-launch-daemon.test.ts "#482 ..." was seen in a remote full run of #1051; it passed in reruns, in CI and on main, and is treated as a timing flake.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
5
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
14
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
21
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -35,7 +35,7 @@
35
35
  ]
36
36
  },
37
37
  "docs/quickstart.md": {
38
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
38
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
39
39
  "paths": [
40
40
  "package.json",
41
41
  "src/",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "docs/agent-notes/adapters.md": {
46
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
46
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
47
47
  "paths": [
48
48
  "src/adapters/",
49
49
  "src/pi/",
@@ -81,7 +81,7 @@
81
81
  ]
82
82
  },
83
83
  "docs/agent-notes/benchmarks.md": {
84
- "verifiedAgainst": "ce8da3ec141f7ef17e2e1cd6a8104ddc36304efe",
84
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
85
85
  "paths": [
86
86
  "scripts/benchmarks/",
87
87
  "test/benchmarks/",
@@ -93,7 +93,7 @@
93
93
  ]
94
94
  },
95
95
  "docs/agent-notes/budget.md": {
96
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
96
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
97
97
  "paths": [
98
98
  "src/hub/budget.ts",
99
99
  "src/cli/statusline-tee.ts",
@@ -117,7 +117,7 @@
117
117
  ]
118
118
  },
119
119
  "docs/agent-notes/bus.md": {
120
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
120
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
121
121
  "paths": [
122
122
  "src/hub/bus.ts",
123
123
  "src/hub/envelope.ts",
@@ -132,7 +132,7 @@
132
132
  ]
133
133
  },
134
134
  "docs/agent-notes/daemon.md": {
135
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
135
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
136
136
  "paths": [
137
137
  "src/hub/daemon.ts",
138
138
  "src/hub/control-client.ts",
@@ -210,7 +210,16 @@
210
210
  "src/hub/session-identity.ts",
211
211
  "src/hub/provenance.ts",
212
212
  "src/hub/channel-project.ts",
213
- "src/hub/project.ts"
213
+ "src/hub/project.ts",
214
+ "src/hub/project-runtime-state.ts",
215
+ "src/hub/project-runtime.ts",
216
+ "src/hub/runtime-capture-prune.ts",
217
+ "src/hub/runtime-prune-health.ts",
218
+ "src/hub/db-schema.ts",
219
+ "src/hub/protocol-window.ts",
220
+ "src/hub/start-agents.ts",
221
+ "src/cli/start-agents-run.ts",
222
+ "src/cli/runtime-version-warning.ts"
214
223
  ]
215
224
  },
216
225
  "docs/agent-notes/local-worker.md": {
@@ -234,7 +243,7 @@
234
243
  ]
235
244
  },
236
245
  "docs/agent-notes/tasks.md": {
237
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
246
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
238
247
  "paths": [
239
248
  "src/hub/tasks.ts",
240
249
  "src/hub/board.ts",
@@ -276,11 +285,13 @@
276
285
  "src/hub/folder-trigger.ts",
277
286
  "src/hub/handoff.ts",
278
287
  "src/cli/handoff-args.ts",
279
- "src/cli/handoff-output.ts"
288
+ "src/cli/handoff-output.ts",
289
+ "src/hub/task-approval.ts",
290
+ "src/hub/research-runner.ts"
280
291
  ]
281
292
  },
282
293
  "docs/agent-notes/tests.md": {
283
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
294
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
284
295
  "paths": [
285
296
  "test/",
286
297
  "scripts/check.sh",
@@ -304,11 +315,12 @@
304
315
  "scripts/progress-tee.sh",
305
316
  "scripts/release-prepare.ts",
306
317
  "scripts/release-tag.ts",
307
- "scripts/lib/release-plan.ts"
318
+ "scripts/lib/release-plan.ts",
319
+ "src/hub/orca-env.ts"
308
320
  ]
309
321
  },
310
322
  "docs/agent-notes/README.md": {
311
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
323
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
312
324
  "paths": [
313
325
  "src/cli/console-state.ts",
314
326
  "src/cli/attention.ts",
@@ -317,7 +329,7 @@
317
329
  ]
318
330
  },
319
331
  "src/cli/help.ts": {
320
- "verifiedAgainst": "aaccc30bd32653ba1925220334445e020be21732",
332
+ "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
321
333
  "paths": [
322
334
  "src/cli/main.ts",
323
335
  "src/cli/launch.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",