@staix/agent-hub 0.22.0 → 0.23.1

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 (96) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -2
  3. package/docs/agent-notes/adapters.md +2 -1
  4. package/docs/agent-notes/budget.md +1 -1
  5. package/docs/agent-notes/bus.md +4 -1
  6. package/docs/agent-notes/daemon.md +3 -2
  7. package/docs/agent-notes/local-worker.md +1 -1
  8. package/docs/agent-notes/models.md +2 -1
  9. package/docs/agent-notes/tasks.md +1 -0
  10. package/docs/agent-notes/tests.md +1 -1
  11. package/docs/dogfood.md +7 -1
  12. package/docs/memory.md +1 -1
  13. package/docs/operations.md +154 -13
  14. package/docs/profiles.md +28 -3
  15. package/docs/quickstart.md +11 -3
  16. package/docs/security.md +11 -5
  17. package/docs/specs/2026-09-19-agent-hub-design.md +66 -4
  18. package/docs/verification/2026-10-30-0.23.0.md +70 -0
  19. package/docs/verification/2026-10-30-adapter-operational-settings.md +261 -0
  20. package/docs/verification/2026-10-30-model-operational-settings.md +211 -0
  21. package/docs/verification/2026-10-30-operational-settings-closure.md +1217 -0
  22. package/docs/verification/2026-10-30-ui-console-operational-settings.md +1845 -0
  23. package/docs/verification/2026-10-31-0.23.1.md +74 -0
  24. package/docs/verification/2026-10-31-claude-implementer-2.1.314.md +67 -0
  25. package/docs/verification/2026-10-31-codex-0166-reviewer-isolation.md +52 -0
  26. package/docs/verification/2026-10-31-codex-0166-reviewer-tool-call.md +49 -0
  27. package/docs/verification/2026-10-31-kimi-241-reviewer-compat.md +63 -0
  28. package/docs/verification/2026-10-31-qwen-0264-acp.md +57 -0
  29. package/docs/verification/2026-10-31-qwen-0264-dual-output.md +52 -0
  30. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  31. package/docs/verified.json +15 -15
  32. package/package.json +2 -2
  33. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  34. package/plugins/agent-hub/ROLE-NOTICE.md +1 -1
  35. package/plugins/agent-hub/server.js +749 -241
  36. package/src/adapters/acp.ts +48 -2
  37. package/src/adapters/claude-channel.ts +6 -4
  38. package/src/adapters/codex-appserver.ts +19 -0
  39. package/src/adapters/compat.ts +11 -11
  40. package/src/adapters/jcode.ts +1 -1
  41. package/src/adapters/local-worker.ts +3 -3
  42. package/src/adapters/native-capabilities.ts +50 -0
  43. package/src/adapters/registry-types.ts +3 -10
  44. package/src/cli/capabilities-output.ts +30 -0
  45. package/src/cli/console-state.ts +8 -5
  46. package/src/cli/console.ts +27 -4
  47. package/src/cli/help.ts +2 -1
  48. package/src/cli/identity.ts +1 -0
  49. package/src/cli/main.ts +8 -0
  50. package/src/hub/board.ts +8 -2
  51. package/src/hub/bus.ts +100 -8
  52. package/src/hub/capabilities.ts +215 -0
  53. package/src/hub/config-apply.ts +6 -3
  54. package/src/hub/config-trust.ts +1 -0
  55. package/src/hub/control-client.ts +41 -8
  56. package/src/hub/daemon.ts +376 -20
  57. package/src/hub/deferred-hub.ts +2 -2
  58. package/src/hub/delivery-hints.ts +2 -2
  59. package/src/hub/delivery-journal.ts +2 -4
  60. package/src/hub/envelope.ts +12 -2
  61. package/src/hub/events.ts +2 -0
  62. package/src/hub/hub-tools.ts +16 -1
  63. package/src/hub/inference.ts +10 -5
  64. package/src/hub/invocations.ts +316 -0
  65. package/src/hub/operational-settings.ts +102 -1
  66. package/src/hub/peers.ts +3 -0
  67. package/src/hub/restart.ts +2 -2
  68. package/src/hub/room-invocations.ts +145 -0
  69. package/src/hub/settings.ts +1 -1
  70. package/src/hub/shipped-packs.ts +5 -2
  71. package/src/hub/shipped-roles.ts +13 -0
  72. package/src/hub/tasks.ts +14 -0
  73. package/src/memory/capture.ts +2 -2
  74. package/src/models/relay.ts +20 -9
  75. package/src/rooms/application.ts +67 -2
  76. package/src/rooms/boundary.ts +43 -11
  77. package/src/rooms/service.ts +40 -2
  78. package/templates/checks/analysis.mjs +59 -0
  79. package/templates/checks/fixtures/analysis-expected.json +1 -0
  80. package/templates/checks/fixtures/analysis-input.json +1 -0
  81. package/templates/checks/fixtures/analysis.mjs +7 -0
  82. package/templates/checks/fixtures/ops.json +4 -0
  83. package/templates/checks/fixtures/writing-rules.json +4 -0
  84. package/templates/checks/fixtures/writing.json +5 -0
  85. package/templates/checks/ops.mjs +40 -0
  86. package/templates/checks/writing.mjs +40 -0
  87. package/templates/packs/analysis.pack.json +14 -0
  88. package/templates/packs/ops.pack.json +14 -0
  89. package/templates/packs/writing-pack.pack.json +14 -0
  90. package/templates/roles/NOTICE.md +1 -1
  91. package/templates/roles/ops/meeting-actions-owner.md +26 -0
  92. package/templates/roles/ops/project-manager.md +26 -0
  93. package/templates/roles/writing/editor.md +26 -0
  94. package/templates/roles/writing/style-checker.md +26 -0
  95. package/templates/roles/writing/terminology-checker.md +26 -0
  96. package/templates/roles/writing/translator.md +26 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.23.1
8
+
9
+ - Pin `@staix/agent-mem` 0.14.0 (was 0.13.0). The hub uses the library API and does not activate amem 0.14.0's opt-in v2 public prune view (`amem migrate --public-prune-view`), so hub recall, capture, briefs and existing homes are unchanged. 0.13.0 and earlier readers refuse an activated v2 home: before a person activates it, upgrade every hub sharing the memory home to 0.23.1 or later (which bundles amem 0.14.0) and every global `amem` CLI to 0.14.0.
10
+ - Claude Code 2.1.314 is a receipt-verified headless implementer version (#888, #996): the model-free permission-transport and host shell-wrapper producers pass on it with 2.1.310 as the control (`docs/verification/2026-10-31-claude-implementer-2.1.314.md`). 2.1.311-2.1.313 stay unverified.
11
+ - Codex 0.166.0 stays an unverified reviewer version (#888, #997): it passed the model-free isolation procedure (`docs/verification/2026-10-31-codex-0166-reviewer-isolation.md`), but its one authorized model review missed the required synthetic marker in the persisted verdict (`docs/verification/2026-10-31-codex-0166-reviewer-tool-call.md`), so the proposed verified bump was reverted. The compat receipt pointer names the 0.166.0 isolation receipt; verified stays 0.146.0, 0.163.0 and 0.164.0.
12
+ - Kimi Code 2.4.1 is a receipt-verified fresh reviewer version and Qwen Code 0.26.4 a receipt-verified ACP version (#888, #1002), both model-free (`docs/verification/2026-10-31-kimi-241-reviewer-compat.md`, `docs/verification/2026-10-31-qwen-0264-acp.md`). The Qwen dual-output TUI stays verified on 0.26.1 only; the separate 0.26.4 dual-output attempt did not qualify (`docs/verification/2026-10-31-qwen-0264-dual-output.md`).
13
+
14
+ ## 0.23.0
15
+
16
+ - Write and external invocations (#445 C3, task 180, #991): a shared `write` or `external` capability runs only for a caller's open non-PII task (`hub_invoke` `task`) and only after a person allows that one call; unattended hubs refuse before prompting, and `destructive` still never runs. The approval shows caller, target, capability, class, digest, task and the screened, sanitized arguments (200 characters per value, marked when cut); the previews stay on person permission surfaces and never reach audit, events or logs. Every admission gate is rechecked after the approval. The task history records an invocation's queued and terminal metadata without changing the task's state. Only a `read` may replay, at most once after a safe failure; an uncertain write or external outcome is held as `needs_review`, never retried. `hub_invoke` waits up to the approval window instead of the 30 s tool timeout, and a closed or cancelled call is withdrawn before it can be granted. Not live-verified (C4).
17
+ - Read invocations across hubs (#446 X6, task 187, #992): `hub_invoke` can name an exact remote `peer@label`. Both persons must pin the same capability, digest and class `read` (`invocation.share` on the source under the `peer@label` key, and the target's own local entry); there is no remote discovery, and write, external and destructive never cross. Only bounded flat scalar arguments travel; the target re-checks its schema and every #445 gate. Task and approval refs stay local. A remote call cannot start another one, duplicates run once, and an uncertain handoff or result is `needs_review`, never replayed. Remote results keep the remote-turn exclusions from memory, history and telemetry. Not live-verified across two machines (X7).
18
+ - Writing, analysis and ops domain packs (#513b, task 322, #993): `writing-pack` (base `writing`; editor, translator, style and terminology roles), `analysis` (base `research`, analyst) and `ops` (base `general`; project-manager and meeting-actions-owner roles), each with a criterion-id rubric and one advisory check. The checks are standalone scripts in `templates/checks/` that a person runs by hand; packs and `ahub init` never execute them. The role catalog now ships 17 roles.
19
+ - Adapter timing settings (#489 H5, task 446, #982): `adapter_timing.acp_tool_ceiling_min` (1-30, default 30), `adapter_timing.acp_activity_batch_ms` (10-50, default 50) and `adapter_timing.jcode_steer_timeout_s` (5-60, default 5), applied at the next start of a Kimi, generic ACP or Jcode peer. Running peers keep their values.
20
+ - Model relay and inference settings (#489 H6, task 447, #984): `model_relay.cooldown_failures`, `model_relay.cooldown_s`, `model_relay.cooldown_cap_s`, `model_relay.terminal_history_limit`, `inference.backoff_min`, `inference.digest_min_items` and `inference.digest_min_chars`. All seven need a daemon restart; unset behaves as before.
21
+ - Console presentation settings (#489 H7, #987): `console.poll_ms` (2000-60000, default 4000), `console.notice_ms` (raise only, default 10000), `console.history_limit`, `console.pending_stream_limit` and `console.attention_limit` (each 1 to its old default), read once at the next console launch. The three limits only trim the console's own display state, never queues, deliveries or approvals.
22
+ - An ACP peer whose stdout carried another session's command list in the same chunk as its own no longer adopts the foreign list and lists nothing (#567, #988).
23
+ - Read invocations (#445 C2, task 179): `hub_invoke { peer, capability, args?, task? }` asks another peer to run one capability the person shared from it and returns an invocation id at once; the result arrives later as one hub-headed, sanitized message of at most 8 KiB, addressed to the caller only. A `read` capability runs without approval (write and external need a task and a person's approval, C3 above; destructive never runs). The target gets one hub-written request and answers under its own permissions; `[REFUSED]` declines. The answer is taken over inside the bus before anything is built from it, so it reaches no one but the caller's one result; an answer the PII or secret checks do not clear, or a callee whose turn became private, is `withheld` with fixed text and no body kept. Every refusal names its reason before any envelope exists: PII, nesting, target offline, paused, held, off roster or pull-only, not shared or changed, class, task, arguments, screen, and per-caller or per-target caps. `invocation.timeout_s` (default 600) expires a queued request and times out a delivered one. A row in `hub.db` and an `invocation` event hold ids only. New envelope kinds `invocation` and `invocation_result` are delivered alone and never condensed; `invocation` is a daemon feature and recovery refuses a target without it while one is open. Not live-verified (C4).
24
+ - Capability discovery (#445 C1, task 178): `ahub capabilities [--json]` (person only) lists what each attached peer offers, and agents read what you share with `hub_capabilities { peer? }`. Kimi 2.3.2 and Qwen 0.26.1 give their ACP `available_commands_update` skills (builtins never), Codex 0.166.0 gives its enabled `skills/list` entries; any other version, an offline peer and an unanswered list give nothing. Sharing is `invocation.share` in `.agenthub/config.local.json` only, pinned by a digest of kind, name, schema, class, source and invoke form, with an optional person `class`; an entry with no class anywhere counts as `destructive`. `hub_capabilities_publish` lets a session the hub cannot read publish its own list (forced to `invoke: prompt`, bound to its connection). Neither tool runs in a PII turn or reaches memory. This slice only lists; calling a shared capability is C2 and C3 above. The `exec` side-effect class is gone: arbitrary execution is `destructive`.
25
+
7
26
  ## 0.22.0
8
27
 
9
28
  - Five bus and delivery limits are settings (#489 H3, task 444, #975): `bus.max_attempts` (1-100, default 3), `bus.digest_max` (1-10, default 10), `bus.pull_budget_bytes` (1-65536, default 65536), `bus.notes_kept` (1-10, default 10) and `bus.terminal_history_limit` (1-2048, default 2048), live from `config.local.json` at their next use. Unset behaves as before; only `bus.max_attempts` may rise above what shipped, and a reload never trims queues, resets attempts or prunes history.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode a
5
5
  Antigravity if you want them) share a message bus, a task board and a memory, each
6
6
  connected through its own native interface.
7
7
 
8
- Status: 0.22.0, control protocol 16. Bun + TypeScript, one loopback daemon per
8
+ Status: 0.23.1, control protocol 16. Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
11
  ## Quick start
@@ -94,7 +94,7 @@ uses the home must run a version that understands the new layout.
94
94
  [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
95
95
 
96
96
  Live checks that still need a real account or a person are listed in the
97
- [release verification report](docs/verification/2026-10-30-0.22.0.md) and the
97
+ [release verification report](docs/verification/2026-10-31-0.23.1.md) and the
98
98
  [smoke checklist](docs/smoke.md).
99
99
 
100
100
  ## Documentation
@@ -44,7 +44,8 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
44
44
  - A peer must set `busy` synchronously inside `deliver()`, otherwise the bus drains the next envelope into a running turn (Kimi answers `turn.agent_busy`). Outside a turn the adapter accepts an occupancy diagnostic (a `context-used` `usage_update` naming its current session) and nothing else: no token total, no chunk, nothing unnamed or for an offline peer (#285).
45
45
  - A watchdog-cancelled turn still reports later. Anything a turn does on completion must check it is still the current turn (`turn` generation in `acp.ts`, `activeTurns` in `codex-appserver.ts`). A rejected prompt other than a recognised quota refusal, or an abnormal prompt end, reports through the adapter's `onTurnFailure` (same shape in `acp.ts` and `pi.ts`), even when ACP streamed partial text; a stale cancelled turn never reports it; a rejected watchdog-cancelled ACP prompt during its journal-less wait is discarded with one log line, not requeued as on the earlier adapter.
46
46
  - After an ACP watchdog cancels a prompt on a bus without a journal, keep the peer busy with its inactivity watchdog cleared until that prompt settles; suppress its turn-scoped updates and permissions while still accepting session-bound occupancy diagnostics. Bound the wait by the existing 30 s ACP handshake deadline, go offline, never idle, with queued work retained if it expires, and revoke the wait on stop or transport loss; a durable delivery keeps its existing offline/needs_review path.
47
- - ACP tool ceiling (#542): a tool call announced (`tool_call`) and not yet `completed` or `failed` in the current turn keeps a silent busy turn alive past the watchdog, up to `AcpOptions.toolCeilingMs` (never below the watchdog) counted from the last protocol message. Re-arm with `touch(<remaining ceiling, at most the watchdog>)`, never a full extra watchdog, and clear the open calls at each `deliver()`. Past the ceiling, cancel as an ordinary watchdog; its log line names the open tools only on a turn that is not private (latch `isPrivateTurn`, fail closed when it throws), each title as one sanitized, quoted, cut line.
47
+ - ACP tool ceiling (#542): a tool call announced (`tool_call`) and not yet `completed` or `failed` in the current turn keeps a silent busy turn alive past the watchdog, up to `AcpOptions.toolCeilingMs` (never below the watchdog) counted from the last protocol message. Re-arm with `touch(<remaining ceiling, at most the watchdog>)`, never a full extra watchdog, and clear the open calls at each `deliver()`. Past the ceiling, cancel as an ordinary watchdog; its log line names the open tools only on a turn that is not private (latch `isPrivateTurn`, fail closed when it throws), each title as one sanitized, quoted, cut line. The daemon passes `toolCeilingMs`, `activityBatchMs` and Jcode's `steerTimeoutMs` from `adapterOperationalReaders` (`adapter_timing.*`, #982) only when it constructs an adapter; an adapter never reads config itself, and an accepted edit never retimes a running launch.
48
+ - Native capability listings (#445 C1, #985): keep vendor identities and listing shapes in `src/adapters/native-capabilities.ts`, never in `src/hub/capabilities.ts`. `capabilityManifest` lists only on a `NATIVE_PINS` version; any other version returns an empty manifest, never a guess, and a transport failure rejects rather than showing a stale list. Codex `skills/list` uses a negative hub id, and only `enabled: true` skills are listed. ACP keeps `available_commands_update` lists that arrive during `binding` keyed by session (`earlyCommands`, at most `EARLY_COMMANDS_MAX`), and the bind adopts only the bound session's list (#567).
48
49
  - Retire a cancelled Codex native turn until its exact completion or session teardown. Late start acceptance, answer frames and completion must not revive its audience or relabel uncertainty as `failed_safe`. If cancellation has no provable native id, keep the peer offline until verified reattachment.
49
50
  - Codex: the adapter never sends its own `initialize`. It is a proxy; hub requests use negative ids and their responses must not reach the TUI.
50
51
  - A durable Codex steer stays pending while its native turn runs. Its RPC may answer after `turn/completed`: bound unanswered replies from that turn's end, requeue a definitive late refusal, and hold a late acceptance as uncertain without attaching it to a later turn's audience (#296).
@@ -13,7 +13,7 @@ Scope: quota and native context readings, checkpoints, pause, handoff and resume
13
13
  - Context checkpoint completion requires its request id and the current attached peer/native session. Invalidate its transport generation synchronously at a new peer claim, before asynchronous recall or attachment. Never pause, reassign or replace a session on context pressure; `ContextWindows.admission` (#802) only copies the reading for new-offer routing and never changes the crossing latch. Refuse private-turn, PII-pattern and all associated open PII-task summaries (owner or reviewer, including in_review, with the current routing policy; use `isClosedTaskState` to exclude approved and cancelled work) before local persistence or cloud memory; never broadcast checkpoint text.
14
14
  - Invalid/stale context readings expose unknown and do not rearm a crossing. Latch a crossing only after its event is accepted; reconsider paused/recovery-held crossings after release with fresh current-session readings. Tee context fields contain finite numeric values or null only, bound to daemon instance, launcher and native session. Context and session go to the per-peer file `claudeStateFile(base, peer)` (the builtin keeps the unsuffixed name); `claude-usage.json` stays one shared file per account.
15
15
 
16
- - A provider hard refusal ends a reading override and supersedes a soft evaluation still awaiting its checkpoint; never let the obsolete checkpoint overwrite that pause. An ACP provider quota refusal reports a hard budget reading and confirms the live pause before the adapter requeues work, so the pause fences retries and the existing relay handles assignments. Use the observed window length for an unknown reset; repeated quota refusals while paused do not consume poison-envelope retries.
16
+ - A provider hard refusal ends a reading override and supersedes a soft evaluation still awaiting its checkpoint; never let the obsolete checkpoint overwrite that pause. An ACP provider quota refusal reports a hard budget reading and confirms the live pause before the adapter requeues work, so the pause fences retries and the existing relay handles assignments. Use the observed window length for an unknown reset; repeated quota refusals while paused do not consume poison-envelope retries, except for an `invocation` request: it gets at most one automatic retry, only when `canRetryInvocation` says so, paused or not (rule: `bus.md`).
17
17
  - Kimi's ACP `{used, size}` occupancy is a context reading (`acpContext`, source `acp_usage_update`) bound to the adapter's own session id: never a `tokens` event or a budget window, and an invalid shape reports unknown, not the previous value. A gate crossing sends a checkpoint request only to definitions with `budget.checkpoint` (Claude and Codex); an ACP peer gets the event and the notice (#285).
18
18
  - Jcode (#801): its ACP `usage_update` `{used, size}` is a context reading (`jcodeContextValue`, source `jcode_usage_update`, session-bound); its prompt result `usage` is per-turn consumption recorded as one `jcode_acp` `usage` event (`acpTurnUsage`, `onTurnUsage`) and never reaches `addTokens`, a token window or a budget window. Never read Jcode's `totalTokens`, never infer a quota window from either record, and keep a malformed, wrong-session or stale value unknown. Never read `jcode usage --json`. A later Jcode version repeats `docs/verification/2026-10-28-jcode-0.99.1-usage.md` before its readings are claimed.
19
19
  - A quota resume notice replaces the peer's unread resume notices: carry their moves into the new one (`refs.budgetResumeMoves`, or the whole text of a notice that has none), publish it, and only then withdraw the ones it replaces, so a failed publish keeps them (#306).
@@ -10,7 +10,10 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
10
10
  - `WsPeer.steer` exists only for a push channel session: set at attach, cleared on detach, roster detach and a pull-only attach, and it refuses anything but important work in the current busy turn (#493).
11
11
  - `replyParent()` decides what a reply answers (highest hop, never the `hub` preface). Use it for deliveries and steers alike, or the hop cap can be reset.
12
12
  - Report a send's outcome from `Bus.publishResult`, never from `publish()`'s bare target list: a hop-capped send answers `ok: true, dropped: "hop"` with the hop-cap notice, and the channel tool and `ahub say` print that notice, never "no peers attached" (#546). A native peer's `onMessage` returns that `PublishResult` on a hop drop, its send tool (Pi, the local worker) reports it as not delivered, and no off-roster retention note follows a hop drop (#550).
13
- - What a peer is handed (`out`) and what it stands for (`originals`) differ once a delivery is condensed: the bus keeps both, registers `out` so `reply_to` resolves, puts the originals back on a `failed_safe` receipt and holds any other failure (a thrown `deliver`, a later `onFailed`, a `needs_review` receipt) as `needs_review` with its originals, for `ahub queue resolve`, and counts no attempt when the peer merely got busy while the delivery was prepared. A delivery with an `important` envelope or a remote sender is never condensed; a remote recipient (`remoteId`, rooms #941) gets one envelope per delivery with its queue deadline, never a preface, batching delay, condensation or steer.
13
+ - What a peer is handed (`out`) and what it stands for (`originals`) differ once a delivery is condensed: the bus keeps both, registers `out` so `reply_to` resolves, puts the originals back on a `failed_safe` receipt and holds any other failure (a thrown `deliver`, a later `onFailed`, a `needs_review` receipt) as `needs_review` with its originals, for `ahub queue resolve`, and counts no attempt when the peer merely got busy while the delivery was prepared. A delivery with an `important` envelope, an invocation kind or a remote sender is never condensed; a remote recipient (`remoteId`, rooms #941) gets one envelope per delivery with its queue deadline, never a preface, batching delay, condensation or steer.
14
+ - Invocations (#445 C2, #986): an `invocation` or `invocation_result` (`isInvocationKind`) goes alone in `take` and `takeBudget`, is never condensed, and is never steered into: both steer sites and `steerQueued` ask `invocationTurnOpen` first. In `onMessage`, a peer's answer goes to `onAnswer` before `parseMarker` and before any publish, when it names the request (`inReplyTo` in the handed delivery) or names nothing while the invocation turn is open. A taken answer, or a throwing hook, publishes, stores and emits nothing.
15
+ - An invocation is never replayed through ordinary retry (#991). `retryDelivery` refuses a row that holds one. An uncertain or thrown delivery holds the peer (`recoveryHeldPeers`) and calls `onInvocationUncertain` instead of `failed()`. `failed()` allows at most one automatic retry, and only when `canRetryInvocation` returns true; unknown or a throw refuses.
16
+ - Add an envelope kind only to `KINDS` (`envelope.ts`); the journal, restart reader, deferred-hub check and held hints read that one list. Test room provenance with `remoteEnvelope(env)`, never `from.includes("@")`: a relayed room invocation arrives `from: hub` with `refs.invocationRemote` (#992).
14
17
  - A condensed digest is sent by `digest`, not `hub`: `replyParent()` skips only the hub's `presence` item, and a reply to a digest must keep the highest hop of what it replaced. On a `failed_safe` delivery or a `retry` resolution the bus puts back the originals, never the digest.
15
18
  - Limits admit what is sent: the envelope `newEnvelope` builds (a reply inherits its parent's sender, `digest` resolves to the originals, `capPriority` applies), never the raw `to` and priority. Admit first and build later, and implicit replies all count as broadcasts.
16
19
  - A reply is addressed, never broadcast. An adapter that answers a delivery passes `to: replyAudience(envs)`; anything else with an `inReplyTo` inherits that envelope's sender. Leaving `to` out or empty fans the message out to every peer. The control `send` takes a nonempty string `to` as that one peer and refuses any other non-array shape or bad item before publishing (#963); never turn a malformed `to` into a broadcast.
@@ -21,6 +21,7 @@
21
21
  Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/config-tier.ts`, `src/hub/config-edit.ts`, `src/hub/config-edit-host.ts`, `src/hub/claude-launch-proof.ts`, `src/hub/recovery-continuity.ts`, `src/cli/recovery-presentation-options.ts`, `src/cli/setup.ts`, `src/cli/upgrade*.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts`, `src/cli/facts-hook.ts`, `src/hub/usage.ts`, `src/hub/memory-profile.ts`, the memory code modules (`src/hub/memory-symbols.ts`, `src/hub/memory-code-overlay.ts`, `src/hub/memory-code-head.ts`, `src/hub/memory-stale-anchors.ts`, `src/cli/memory.ts`), the task brief host (`src/memory/brief.ts`, `src/hub/memory-plan-guard.ts`), `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, `src/hub/task-code-group.ts` (dashboard Code group), the agent drift sweep (`src/hub/agent-drift.ts`, `src/cli/agents.ts`) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
22
22
 
23
23
  - The plugin bundle is installed apart from the daemon. Breaking a control WS message contract bumps `PROTOCOL` in `control-client.ts`; a new request answered as unknown or an optional field preserves it, as does additive optional public task metadata in `ui_snapshot` that older clients ignore (such as `deps` in #246).
24
+ - `hub_invoke` over the control WS (#445, #991): the daemon binds each admission to its socket and `rid` with an `AbortController` (`invocationRequests`, at most 64 per connection; a duplicate `rid` refuses). `request_cancel` and socket close abort it, and every check after an await confirms the socket is open and still maps that `rid` to the same controller before any effect. Clients go through `ControlClient.request`, whose `controlRequestTimeout` raises a `hub_invoke` wait to `INVOCATION_CONTROL_TIMEOUT_MS`; never call it with a shorter bound.
24
25
  - `peer_stop` is the console's or an admitted conductor's (`admitOperational(sock, "stop")`) and stops, through its adapter, only an owner whose adapter reports `hubOwned === true` and whose `form` is not `tui` (today headless ACP, Pi, local, print-reviewer and Claude implementer); a present `undefined` form refuses. Refuse native-terminal peers and recovery-held mutations; serialize with starts and permission changes, and refuse a recovery `prepare` while a stop runs. An admitted stop holds delivery through `BusOptions.deliveryHeld` before any teardown await (never a console pause that resume can undo, or a global recovery hold that blocks unrelated peers) and withdraws the peer's pending approvals before the adapter stops. A requested stop holds an in-flight delivery as `needs_review` with an ids-only stop reason, suppresses restart/escalation, and never resolves that delivery automatically.
25
26
  - Console review guidance (#711): `r` on a task in review opens `reviewChoice` (`src/cli/console-review.ts`, modelled on `modeChoice`) from the row or the open task detail, never a bare prefill; `a` and `c` prefill `review N approved|changes_requested ` with a guide line, a `changes_requested` line with no note is refused in `reduceConsole` before any effect, and the result of a review command is shown as a notice in panels mode (`reviewResult`). The console only prefills and runs `ahub review` as the person: never grant it extra authority, and keep a private task's title out of the choice line (`[pii]`). The CLI cause line (`agentShellCause`, `src/cli/identity.ts`) is printed for the `tools` identity and a person-only hub refusal only, never for the console identity, and never replaces the refusal.
26
27
  - Console changed-symbols block (#684 2b): `symbolFields` (`src/cli/console-symbols.ts`) adds `symbols` after `plan` for a task_show-shaped detail only, from the public `codeSymbols`; never from a raw `Task`, never for a private task, never from a stored receipt (one with `repositoryKey` reads as unknown: only the screened public receipt is shown), never as a second copy of the match rule (`planSymbolMatches` in `review-brief.ts`). Unknown is `unknown (no code facts)`, never an empty list.
@@ -33,7 +34,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
33
34
  - SDLC band timer (#481 S7): the daemon's band interval (`bandsEveryMs`, default 5 min) is the only caller of `tasks.proposeFromBands`. It skips while stopping or in recovery, when `tasks.bandsState()` is not ready or `bandsHeld` holds, and when `board.statusRevision()` with the band signature is unchanged since the last pass that ran; it records that key after the write. A failure is one sanitized `hub.log` line. Writer rules: `tasks.md`.
34
35
  - `ahub board` (#724): `renderBoard` prints one line per task only with a terminal width and without `--full`; `--full`, `ahub task show` and output without a width keep the full table and SDLC advice. `STAGE_CODES` (`src/cli/output.ts`) keys on blocker-text prefixes from `src/hub/sdlc.ts`: rewording a blocker there updates its code in the same PR. TITLE is cut, never wrapped; STAGE is never dropped (it falls back to the meter and the code); a PII row keeps its `(ahub task show N)` hint while 8 title columns remain.
35
36
  - CLI output default (#814): a command prints for a person unless `--json` is given. The recovery and upgrade commands follow it too (`src/cli/upgrade-output.ts` composes `planLines`, `renderPlan`, `renderOperation`, `renderBlocked` and `choiceInstruction`, so the non-interactive text never says what the screens would not): a pipe and `--dry-run` print the plan with blockers, reconnect-only and fresh-start peers in stdout, `--json` keeps the document and the stderr lines (#206), and `recoveryDisplay` takes a pipe's width from `COLUMNS`. Hub tool text (`task show`, the backlog and release commands) is parsed on the CLI side by `showToolText` and rendered by `src/cli/task-output.ts`; `--json` prints the text byte for byte, unparseable text prints as it came, and a private task is rendered as the stub the hub sent, never asked for more. A command that takes free text reads `--json`, `--full` and `--color=` through `splitPresentation` (`edges`): only a leading or trailing run is a flag, and a value flag's value never is, so a reason keeps its words. `test/cli-json-guard.test.ts` also scans every file in `src/cli/` for a print of a hub tool's text (`console.log(await taskOp(<op>))`, or `console.log(x)` where `x` is bound from one) and passes only when the print is inside a `--json` branch, the op is in the named `SENTENCE_OPS` (ops whose daemon case returns prose, each checked against the daemon), or it is named debt with a reason (`TEXT_DEBT` for JSON ops a later task converts, `CONDUCTOR_DEBT` for the conductor-role commands, `REFUSAL_FALLBACK` for the refusal sentence printed after `--json` and the table were handled); a new JSON-returning op printed bare fails it, and an entry that matches no print fails too. Convert a command by rendering it through `showToolText`, never by adding it to a list. A renderer is a pure function in `src/cli/` (`sdlc-output.ts` for the `sdlc` read commands) and the call site keeps its own visible `if (options.json) console.log(JSON.stringify(...))`, so `test/cli-json-guard.test.ts` can see the branch. Never print JSON by default: add the command's `--json` branch, or name the site in the guard's `DATA_FORMAT` (JSON by purpose: `export`, `sdlc export`, `bench export`, the PreToolUse hook reply) with its reason. The guard accepts a `--json` branch only when the condition PROVES the flag (`selectsJson`): `X.json` on an option holder (`options`, `display`, `present`), `args|flags.includes("--json")`, a known alias, `&&` when either side does, `||` only when both do, `=== true`. Anything else (`!x`, `=== false`, `|| other`, `?? x`, a comparison, an unknown name) is not a guard, so a new shape fails until it is written one of these ways. A renderer table never exceeds the terminal: `grid` (`sdlc-output.ts`) wraps cells, then falls back to one labelled block per row, and keeps every identifier whole. `NOT_YET_CONVERTED` is debt the later #814 tasks delete; an entry that matches no site fails the test, so the list only shrinks. `sdlc` drops `--json`, `--full` and `--color=` from its operands only for its read subcommands (`sdlcOperands`): the others keep their argv exactly. A hub tool's JSON text that does not parse prints as it came, never as an empty report.
36
- - Settings (#269): the dashboard's `setting`, `setting_undo`, `setting_preview` and `setting_reload` actions and the console-role `settings_get|set|undo|preview|reload` requests go through `settingsSet`, `settingsUndo`, `settingsPreview` (`settingsUndoPreview` for `undo: true`) and `reloadConfig` (`src/hub/daemon.ts`) and the registry in `src/hub/settings.ts`. A new editable setting is a registry entry, never a new write path; a numeric operational tunable (#489) is a fixed row in `OPERATIONAL_NUMBERS` (`src/hub/operational-settings.ts`) plus its explicit `config-apply.ts` leaf (`operationalNumberCheck`), its `DEFAULT_CONFIG` default and a reader asked at each use, never registered from a file, request or environment variable; a key that chooses what the hub runs or where secrets or task text go joins `NEVER_EDITABLE` instead. Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml` through `store()`: the loader's own validation first, temp file and rename, never a file git tracks. Authority comes from the dashboard session (`DashboardSession`, `src/hub/ui.ts`) or the console role (for `settings_set` and `settings_preview` of an `operationalSetting` or `operationalRosterSetting` key, also an admitted conductor), never from the request body; `ui_action` from the manager is an ordinary session. Never return a setting's value in an event or notice except through `recordSetting`.
37
+ - Settings (#269): the dashboard's `setting`, `setting_undo`, `setting_preview` and `setting_reload` actions and the console-role `settings_get|set|undo|preview|reload` requests go through `settingsSet`, `settingsUndo`, `settingsPreview` (`settingsUndoPreview` for `undo: true`) and `reloadConfig` (`src/hub/daemon.ts`) and the registry in `src/hub/settings.ts`. A new editable setting is a registry entry, never a new write path; a numeric operational tunable (#489) is a fixed row in `OPERATIONAL_NUMBERS` (`src/hub/operational-settings.ts`) plus its explicit `config-apply.ts` leaf (`operationalNumberCheck`), its `DEFAULT_CONFIG` default and a reader asked at each use, or, for a row with `applies: "next use"` or `"restart"`, captured once by the consumer it configures (a new adapter for `adapter_timing.*`, a new model relay for `model_relay.*`, `Inference` at daemon start for `inference.*`, a console launch for `console.*` through one `settings_get` after its first draw and tail subscription, 3 s bound, defaults on failure); never retime or trim a running consumer's state with it, never registered from a file, request or environment variable; a key that chooses what the hub runs or where secrets or task text go joins `NEVER_EDITABLE` instead. Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml` through `store()`: the loader's own validation first, temp file and rename, never a file git tracks. Authority comes from the dashboard session (`DashboardSession`, `src/hub/ui.ts`) or the console role (for `settings_set` and `settings_preview` of an `operationalSetting` or `operationalRosterSetting` key, also an admitted conductor), never from the request body; `ui_action` from the manager is an ordinary session. Never return a setting's value in an event or notice except through `recordSetting`.
37
38
  - Structural config edits (#689): `src/hub/config-tier.ts` is the one tier rule (editor, dashboard refusal and `structuralLeaf` in `authorizeConfigLeaf`), `src/hub/config-edit.ts` plans and writes (loaders first, revision checked twice, bounded backups, atomic), `src/hub/config-edit-host.ts` owns authority, the typed tier 3 confirmation, the `config_edit` event and the scoped publish. The daemon only supplies ports (`createConfigEditor` in `daemon.ts`); never validate, write or classify in `daemon.ts`. A new editable structural key is an entry in `config-tier.ts` and nothing else; a key that chooses what the hub runs or where secrets go is tier 3 and is never offered to the dashboard (the hub refuses it, the page also hides it). The audit event and every notice carry key paths and value hashes, never a value. `structural` reaches `authorizeConfigLeaf` only through `structuralScopes` for the one scoped reload. `sdlc.toml` is read once at start, so it joins `pendingRestart` through `allPendingRestart()`, never through `reloadConfig`. A new ahub settings subcommand that writes is classified `console` by default; keep it that way.
38
39
  - Live reload (#439): `reloadConfig` is the only publication path. Settings writes and undo, `ahub settings reload`, the dashboard reload and the debounced watcher of the four `CONFIG_SOURCE_FILES` (`src/hub/config-reload.ts`) all reach it; `prepareConfiguration` validates the complete candidate before anything is published, and recovery or shutdown holds it. A write or undo passes its own key as `scope` and publishes only that path, and `acknowledgeConfiguration` records only the accepted paths, so other edits stay pending. Every changed key, inside the registry or not, is decided by `authorizeConfigLeaf` (`src/hub/config-apply.ts`): a refused key keeps its accepted value and is reported by name while the others apply, `roster.*` applies only through its own scoped roster write, and a change of the settled planner or conductor holders (`roleHolders` over `resolveSettledRoles`) is guarded even when a role list shrinks. Outside a named write, only the console's `settings_reload` (authority `terminal`) adopts guarded file edits, by sending back the `confirmation` token bound to the candidate `fingerprint` and `configRevision`; changed bytes or accepted state refuse it, and a watcher, dashboard or settings session never confirms. An undo that names `key` and `undoDigest` writes only while `pendingUndoIdentity` still matches both. Keep config-source snapshots plain and cloneable; classify dynamic ids with validated running/source identity scopes, never suffix discovery or a builtin fallback. Compare restart-bound declarations with the running config on every reload, never a pending desired value accepted by an earlier reload; derived defaults stay bound to that running declaration. A `restart` key (`configApply`) keeps its running value and is listed in `pendingRestart`; reload notices and events carry key names and reasons, never values. The console settings editor (the Settings panel, or the Peers panel's `w` WIP shortcut, which opens only an existing `tasks.wip_limits.<peer>` row for a rostered peer listed in status `taskWip`) stays bound to the panel that opened it (`SettingsEdit.panel`) and arms a write only from a preview reply that matches that panel and its open edit's token, key and value; pasted text never confirms. Console Tasks backlog actions (`p`, `a`, `e`, `R`, `c`) only prefill person CLI commands (`backlog pull-ready|plan|return`, `task cancel`) with the row's immutable id and its planning or flow revision; `validateLockedRow` rechecks the row's state and revision at Enter and again at `y`, and no console path writes the board directly (#532).
39
40
  - Planner setting (#755): `planner` is one id or a list of one to eight distinct eligible ids, validated only by the `planner` row of `projectSettings` (eligible declared instances join its `values`) and `checkSettingValue`, in `loadConductorPolicy` and `validateRawConfig` alike. Every `resolveSettledRoles` call (`settlePlanner`, `roleHolders`) passes the same `plannerEligible` predicate built from that row, so reload guards compare holders the way the running map settles them.
@@ -54,7 +55,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
54
55
  - Adding a native peer requires testing the command emitted by the actual recovery driver, not only its inspection or launch helpers. Never let a generic non-Codex branch treat a new peer as Claude.
55
56
  - Cross-version recovery must use the authenticated source protocol for prepare/commit/abort and the target protocol after startup. Prove the transition against a real prior release before claiming compatibility.
56
57
  - Preserve `task_cancel` target-feature preflight before source holds, capture or commit. Reuse stored target features only after verifying the same retained root and digest; legacy receipts still capable of commit must obtain feature proof. Do not apply pre-commit compatibility checks to sources already past commit. Every prepare, including the lease renewal before a terminal close and after a resume, sends the operation's stored `targetFeatures` (#478).
57
- - A recovery target-feature floor (`task_cancel`, `peer_instances`, `role_stages` while a declared Claude-kind instance holds `implementer`, `task_planners` while a list/instance planner is configured or `Board.hasPlannerState()` finds a task/release binding or plan-review authorship marker, `release_trains` while a configured train is active or frozen, `reviewer_pool` while a fresh reviewer or `review.pool` is configured, `configured_acp` while the project declares `adapters`, `external_adapter_modules` while external module definitions are loaded, `external_adapter_instances` with `external_adapter_modules` and `peer_instances` while an external module-kind instance is declared, `conductor_recovery` while `Board.hasConductorManualState()` finds open conductor manual-assignment intent) lives in `incompatibleRecoveryTarget`, which every existing check site calls, and its name in `DAEMON_FEATURES`; never check a floor at one site only.
58
+ - A recovery target-feature floor (`task_cancel`, `invocation` while the invocation ledger has an open (`queued` or `delivered`) row, `peer_instances`, `role_stages` while a declared Claude-kind instance holds `implementer`, `task_planners` while a list/instance planner is configured or `Board.hasPlannerState()` finds a task/release binding or plan-review authorship marker, `release_trains` while a configured train is active or frozen, `reviewer_pool` while a fresh reviewer or `review.pool` is configured, `configured_acp` while the project declares `adapters`, `external_adapter_modules` while external module definitions are loaded, `external_adapter_instances` with `external_adapter_modules` and `peer_instances` while an external module-kind instance is declared, `conductor_recovery` while `Board.hasConductorManualState()` finds open conductor manual-assignment intent) lives in `incompatibleRecoveryTarget`, which every existing check site calls, and its name in `DAEMON_FEATURES`; never check a floor at one site only.
58
59
  - Defer hub notices only for an authenticated recovery `prepare` or `commit` whose target features include `recovery_deferred_hub`, or for a restored daemon. Otherwise `setRecoveryHold(true)` moves the deferred notices into queues before commit records expected integrity, and a bare `fenceRecovery` defers nothing. Abort and lease expiry release the bus hold before clearing the operation; if that release fails, keep the operation and let expiry retry on its 1 s timer (#478).
59
60
  - Resume a prepared `commitSent` source that is unavailable or database-busy inside the existing bounded shutdown poll; never re-send commit, re-prepare or close terminals again on that path. Require confirmed `stopped` before target startup, refuse a replacement instance, and retain the prepared receipt on timeout.
60
61
  - Keep restoration receipts explicitly tagged as pending, waiting with origin/binding, done with binding, or failed. Project waiting origins as `waiting:pending` and `waiting:failed`; preserve the chosen binding and origin across interruption instead of inferring completion from an object.
@@ -16,7 +16,7 @@ Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt
16
16
  - git arguments never pass through `guardPath`; `gitArgsProblem` refuses absolute paths, `..`, `--no-index` and denylisted `rev:path` forms.
17
17
  - A worker turn builds its messages in a local array and joins the history only as a whole. Never push to `history` mid-turn: one tool call without its result poisons every later request.
18
18
  - After a tool with side effects ran, a failed turn is reported, never redelivered.
19
- - A turn with any remote envelope (`from` containing `@`, rooms #941) never joins `history` and never reaches `capture.observe`, like a PII turn, and every failure it reports (answer, delivery reason, log line) says only `remote turn failed`. Keep all three when you touch the turn's settlement or the tool loop.
19
+ - A turn with any remote envelope (`remoteEnvelope(env)`: `from` containing `@`, or a hub `invocation`/`invocation_result` carrying `refs.invocationRemote`; rooms #941, #992; never test `from` alone) never joins `history` and never reaches `capture.observe`, like a PII turn, and every failure it reports (answer, delivery reason, log line) says only `remote turn failed`. Keep all three when you touch the turn's settlement or the tool loop.
20
20
  - `ctx.permit` answers a `ToolApproval` (`true`, `false`, `"expired"`, `"aborted"`, #253): only `true` permits an effect, so never test it for truthiness. A tool returns `approvalError`'s text for the rest: a denial, the expiry (which tells the model not to retry) or the withdrawal. A turn that two unanswered approvals ended (`ApprovalWaitStop`; counting rule: `adapters.md`) completes its missing tool results, settles its delivery `completed` with the reason and is never handed back for replay, also when no tool had a side effect.
21
21
  - `Facts` (`src/hub/facts.ts`, #428) names a large file whose ctime is under `RACY_MS` by its git blob id: in `load()` and `observe()` only through `racyBlob()` (per-file `RACY_HASH_MAX_BYTES`, per-store `RACY_BURST_BYTES` per event-loop turn, memo cleared at `boundary()`), past which it keeps the stat-only stamp and makes no claim; `atHead()` keeps its own `HASHED_BYTES` bound, `tree()` reads large files by stat only, and `largeBlob()` caches only settled stamps.
22
22
  - Hub tool calls (`hub_send`, `TASK_TOOL_NAMES`, `CONDUCTOR_TOOL_NAMES`) are never passed to `capture.observe`; `Tasks` alone decides what hub tool text reaches memory (#230). Every live `capture.observe` passes a host `sourceId` (`<session>.<turn>.<step>.<n>`, #752); a new host caller supplies one too. Capture stores `paths` only for `read`/`write`/`edit` and only project-relative ones: command argv is never path metadata.
@@ -11,5 +11,6 @@ Scope: the model relay and its journal, the hub's own inference and its slots, O
11
11
  - Shared inference slots must recover after a hub process dies, without evicting a live owner. Cancellation has to reach slot acquisition from the relay caller, not just exist in the helper signature.
12
12
  - Stay/switch (#197): `hub/auto` and local stage routes apply `stay_switch` only through `stayOrSwitch` (`src/models/route/stage.ts`), never at a call site; `shadow` must not change a routing decision (the recorded-decision golden test in `test/route-relay.test.ts`). No route summarizes, trims or rewrites the forwarded conversation: a tier that cannot fit is filtered out. A PII route's planner never sees the conversation size (no prefill bound) and its event omits `prefillTokens`.
13
13
  - A change to the model-routing keys of `templates/routing.toml` (`hub_routes`, `local`, `fixed_model`, `stay_switch`, the `[pi]` windows) carries a model-routing receipt (#637 part 2): `bun scripts/benchmarks/model-routing-gate.ts --receipt`, plus `--live` by hand when an alias or a judge moves. The corpus drives the real `HubRouteRuntime`; see "Model-routing promotion rule" in `docs/agent-notes/benchmarks.md`.
14
- - Load and cooldowns (#199) only reorder a `hub/auto` request's candidates (the route's own backend, then its fallback, as on main), in one ordering step in the relay's fetch handler; a fixed alias is never reordered. Never remove a candidate. A candidate goes first only while it is neither cooling nor `failing` (any failure, an error status or a failed stream included, for 30 s or until a success; separate from the cooldown streak). Load and pin moves are opt-in (`efficientWaitMs` absent: none) until #199 AC5 measures them. Their attempt is bounded only up to its response headers (`MOVE_FIRST_BYTE_MS`, after `omni.base()`); a cut there marks `failing` without counting toward a cooldown, and a stream that fails after the headers cannot fall back, so do not claim a move never fails a request MLX would have served. None happens while the request runs under an execution budget (`underBudget`, a read that admits nothing; a throw counts as a budget). The selector only names `prefer`; it never picks a moved backend itself. They never consider PII because the relay serves only Pi, which refuses PII work before any model call (`selectModel` in `daemon.ts`); a relay caller that can carry PII must not get a reorder toward DGX without a positive `onCampus()`. Count a cooldown failure only for a transport or startup failure outside a cooldown that was neither cancelled, refused admission, a busy MLX slot (`MlxBusyError`: load, never failure), nor a deadline the relay cut itself (`journalEntry.cut`: `"budget"` for the execution budget's, `"move"` for a moved attempt's first byte, which only marks `failing`); any HTTP answer, whatever its status, ends the streak and the cooldown (`answered`).
14
+ - Load and cooldowns (#199) only reorder a `hub/auto` request's candidates (the route's own backend, then its fallback, as on main), in one ordering step in the relay's fetch handler; a fixed alias is never reordered. Never remove a candidate. A candidate goes first only while it is neither cooling nor `failing` (any failure, an error status or a failed stream included, for `model_relay.cooldown_s` (default 30 s) or until a success; separate from the cooldown streak). Load and pin moves are opt-in (`efficientWaitMs` absent: none) until #199 AC5 measures them. Their attempt is bounded only up to its response headers (`MOVE_FIRST_BYTE_MS`, after `omni.base()`); a cut there marks `failing` without counting toward a cooldown, and a stream that fails after the headers cannot fall back, so do not claim a move never fails a request MLX would have served. None happens while the request runs under an execution budget (`underBudget`, a read that admits nothing; a throw counts as a budget). The selector only names `prefer`; it never picks a moved backend itself. They never consider PII because the relay serves only Pi, which refuses PII work before any model call (`selectModel` in `daemon.ts`); a relay caller that can carry PII must not get a reorder toward DGX without a positive `onCampus()`. Count a cooldown failure only for a transport or startup failure outside a cooldown that was neither cancelled, refused admission, a busy MLX slot (`MlxBusyError`: load, never failure), nor a deadline the relay cut itself (`journalEntry.cut`: `"budget"` for the execution budget's, `"move"` for a moved attempt's first byte, which only marks `failing`); any HTTP answer, whatever its status, ends the streak and the cooldown (`answered`).
15
+ - Relay cooldown policy and journal size (`model_relay.*`, read through `modelOperationalReaders`, #984) are passed only when a relay is constructed, and `inference.backoff_min` only when `Inference` is constructed. Both are restart-bound: a reload never rewrites a running relay's cooldowns or journal, or an existing backoff deadline.
15
16
  - MLX owner and generation identity read `ps lstart` with LC_ALL=C/TZ=UTC pinned (#177): unpinned, a record written under one environment reads as a foreign process under another, and the fail-closed paths would refuse the hub's own server.
@@ -90,6 +90,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
90
90
  - Planner walk and reviewer independence (#525-3): `plannerWalk` and `implementers` in `src/hub/routing.ts` are pure; the planner is chosen only inside `assign()` (`planners`, `namedPlanner`, `keptPlanner` in, `Assignment.planner`/`plannerReason` out), and `Tasks` never picks one outside it. `Tasks.weights()` is the one place `planners` is built (rostered holders of `d.roles`, sorted), so every `assign()` path and `explain` agree. Pass a bound task as `keptPlanner` to that same walk: its explanation names the binding as `kept`; drafts and legacy null bindings still rotate, and explanation never writes a binding or history. Rotation is `task.id mod n` over every holder and is never renumbered by availability; a named planner is exact and never replaced. I-2 (`implemented this task`) lives in `blocked()` for the reviewer role as its last capability/independence check before queue admission, so the legacy, named and pool paths share it and it only ever removes an otherwise eligible candidate; when I-2 removed every otherwise eligible reviewer, the person reviews with `reviewBlocked`, never an implementer.
91
91
  - Owned implementer child (#747): `propose`, `acceptReviewed`, `done` (through `complete()`), `recordSdlc`, `review` (in `valid()`), `correct` and `remember` take `requestCheck(by)` (`TasksDeps.requestAuthority`) at entry and call it after every await, before the next write; a new await on those paths adds a call, and its refusal stays pre-effect. The child's tool surface is the fixed `IMPLEMENTER_HUB_TOOL_NAMES` (`src/hub/hub-tools.ts`); never widen it from project roles.
92
92
  - Shell branch binding (#845, `Tasks.bindShellBranch`): the daemon's host shell wrapper path is its only caller, and it is the only writer of the hub's `shell branch bound` entry (the branch and a sha256 of the worktree root, setting `refs.branch` in the same write). It binds only for the current owner of an `in_progress` or `changes_requested` non-PII task in this project's repository, outside a private turn, whose verified execution root (`TasksDeps.executionRoot`) is not the project root and is the binding's worktree. It refuses a branch that differs from an earlier binding or from `refs.branch`, or that another open task in the same repository holds, and rechecks `current()`, the private turn and the flow revision right before the write. Every failure returns `false` (no grant), never a throw. It records host-observed evidence, never a branch a tool call claims, and grants no execution by itself. The entry is in `QUIET_EVENTS`: it neither moves nor settles the task, so it never makes a running check's result late, supersedes an assignment or counts as a move for review admission (#880).
93
+ - Invocation audit (#991, `Tasks.recordInvocation`): the daemon's invocation admission and `finishInvocation` are its only callers; no hub tool reaches it. The `invocation` history entry carries host-owned metadata only (`InvocationHistory`: ids, caller, target, capability, digest, class, approval, `argsSha256`, outcome), never arguments or results, because `publicView` copies it unscreened. At `queued` it rechecks task scope (non-PII task and turn, open and not backlog, caller is owner or reviewer), and a failed audit refuses the invocation before it runs; never catch that and proceed. The entry is in `QUIET_EVENTS`: it neither moves nor settles the task.
93
94
  - Deliverables (#509a, `src/hub/deliverables.ts`, `src/hub/deliverable-files.ts`): the hub hashes each file itself under the owner's verified execution root (`TasksDeps.executionRoot`, the daemon's `ownerWriteRoot`; an unproven root refuses); never accept a caller's hash, size or unknown field. Vet and hash before any board write and refuse pre-effect; a `done (checking)` carries its records to `complete()`, never a later read. An approval rereads the delivered files: a reviewer's verdict is refused before any write unless every file is `unchanged`, and only the person approves moved files, which are rehashed and named in the note. A file the hub cannot read or vouch for is `unknown`, never `unchanged`. Peer-facing records go through `publicDeliverables`: for a PII task or a `pii.paths` file, id, hash and size only.
94
95
 
95
96
  - Task/release planners (#525-2): keep nullable `planner` columns additive, with no backfill. Bind new proposals and null-planner pulls only from `assign()`; install the fixed synchronous `Board.proposalPlanner` host port so proposal, split-child and generated-card bindings enter the original insert transaction before notification. Preserve explicit null callers and existing rows. Preserve existing bindings on owner moves. `Tasks.taskPlanner` keeps a bound rostered settled holder even while offline/paused; a missing, removed or off-roster holder falls back to legacy holders. Every task/release planner effect and its post-await validation uses that fresh scope; project-wide reads remain holder-wide. Naming a planner is person-only, refused for closed or PII work, admits only the holder `assign()` returns for `namedPlanner`, and records only `planner changed`, never an owner delivery event. Split children intentionally run their own pure rotation and may bind a different planner from their parent; do not inherit the parent binding. A removed/off-roster binding is preserved as legacy metadata on pull without recording a false `kept` reason.
@@ -18,7 +18,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
18
18
  - A bounded polling wait must assert its condition after the last attempt, or document why giving up is intentional. Append-only event histories wait for a correlated closing event, not removal of the opening event. Before changing a slow wait, identify the unreached state and repair its cause (#296).
19
19
  - A test of turn or notice behavior that is not about the snapshot budget yields an I/O turn (`await new Promise(r => setImmediate(r))`) between captures: captures in one synchronous burst share the 250 ms budget and may correctly record unknown evidence. A daemon test whose assertion needs a turn's snapshot or end evidence passes `snapshotClock: () => 0` (`DaemonOptions.snapshotClock`, #661) so a slow runner cannot spend that budget; never lengthen the budget instead (#550). A wait on concurrent-edit evidence correlates the expected turn ids and paths (#617).
20
20
  - Holding the snapshot budget clock (`snapshotClock`, #661) holds the deadline and, since #554, the git child's wall-clock timeout (the 10 s ceiling instead of the remaining budget). The rule lives in the pure `childTimeoutMs` (`src/hub/snapshots.ts`: production keeps min(10000, remaining), only a budget carrying `now` gets the ceiling). Reproduce a slow git with a deliberately slow `git` first on PATH (`slowGit` in `test/snapshots.test.ts`), never with host-wide load, and never run a high-parallel loop while another gate shares the dgx02 host.
21
- - The macOS CI leg is four shards (#548): `check (macos-latest) shard i/4` runs `AHUB_CHECK_SHARD=i/4 scripts/check.sh` (`bun test --shard --timings=scripts/test-timings.json`), and the job named `check (macos-latest)` is only the aggregator over them; `scripts/ci-gates.mjs`, branch protection and the reuse proof read that name, so never rename it or turn it back into a matrix job without reading them. `gateJob()` in `scripts/ci-gates.mjs` counts the shard names (`check (macos-latest) shard i/N`) as gate jobs, so a run with Linux and the seeded shards done and only macOS shards running reads as a launched full gate and not as unknown scope (`test/ci-reuse.test.ts` pins every mid-run window). A new test file needs no timing entry (it gets the median), but a shard that grows past about 15 minutes means the timings are stale: regenerate them with `scripts/make-test-timings.ts`. A shard starts the suite cold, so `scripts/check.sh` execs the system git once (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`) before the tests, because tests that give it `PATH=/usr/bin:/bin` and a 1000 ms timeout (`read-only-shell-proof`, `task-shell-default`) are killed by a cold first exec; a failing or absent warm-up never fails the shard. `check.sh` unsets `AHUB_CHECK_SHARD` after building the test arguments, so the runner and every scratch `check.sh` a test spawns (`test/check-lock.test.ts` copies `process.env`) see an unsharded environment; keep that unset. A test must not depend on running in the same process as another file (shards split files across machines), and anything that has to happen once per gate belongs behind the shard-1 condition in `scripts/check.sh`, which `test/macos-shard.test.ts` pins.
21
+ - Both OS CI legs use four shards (#548): `check (ubuntu-latest) shard i/4` and `check (macos-latest) shard i/4` runs `AHUB_CHECK_SHARD=i/4 scripts/check.sh` (`bun test --shard --timings=scripts/test-timings.json`), and each stable job name `check (ubuntu-latest)` / `check (macos-latest)` is the aggregator over its OS shards; `scripts/ci-gates.mjs`, branch protection and the reuse proof read that name, so never rename it or turn it back into a matrix job without reading them. `gateJob()` in `scripts/ci-gates.mjs` counts the shard names (`check (ubuntu-latest) shard i/N` and `check (macos-latest) shard i/N`) as gate jobs, so a run with either OS and the seeded shards done and only the other OS shards running reads as a launched full gate and not as unknown scope (`test/ci-reuse.test.ts` pins every mid-run window). A new test file needs no timing entry (it gets the median), but a shard that grows past about 15 minutes means the timings are stale: regenerate them with `scripts/make-test-timings.ts`. A shard starts the suite cold, so `scripts/check.sh` execs the system git once (`AHUB_CHECK_WARM_GIT`, default `/usr/bin/git`) before the tests, because tests that give it `PATH=/usr/bin:/bin` and a 1000 ms timeout (`read-only-shell-proof`, `task-shell-default`) are killed by a cold first exec; a failing or absent warm-up never fails the shard. `check.sh` unsets `AHUB_CHECK_SHARD` after building the test arguments, so the runner and every scratch `check.sh` a test spawns (`test/check-lock.test.ts` copies `process.env`) see an unsharded environment; keep that unset. A test must not depend on running in the same process as another file (shards split files across machines), and anything that has to happen once per gate belongs behind the shard-1 condition in `scripts/check.sh`, which `test/macos-shard.test.ts` pins.
22
22
  - A test that proves a daemon answers while work is still running asserts ordering (the answer arrived before the producer finished), never an elapsed wall-clock bound: the daemon promises no millisecond figure for `status`, so a bound measures the runner, not the product (#531); a request timeout in such a test is a hang guard only.
23
23
  - A tight wall-clock bound in a test must be sized for the slowest CI runner, or replaced by a barrier (#559). Hosted macOS shards run each test about 1.5 times slower than the old single leg (and git-heavy tests about 7 times), so three unchanged tests tipped over once each after #954: `test/path-policy.test.ts` (the #541 hardlink loops took 10.6 s idle for three layouts in one test and 27 s on a shard, past the 20 s limit: the loops are now five parts of 1000 iterations per layout, 5000 per layout kept, about 0.8 s per part), `test/code-facts.test.ts` (a 400 ms deadline raced the fake CLI writing its child marker, which takes about 0.3 s idle and 1.4 s at 30-way load: the test now holds the deadline timers by the configured value, waits for the marker and fires the analysis's own deadline callback, a barrier with the real deadline code; because the held deadlines never fire by themselves, its `finally` must `close()` the reader and await the analysis before the fixture is removed, and a second test pins that teardown) and `test/smoke-pty.test.ts` (the macOS PTY bridge hung inside `Bun.spawn` with a `terminal` when its child wrote and exited before the call returned: Bun up to 1.4.3, oven-sh/bun#45300, fixed in 1.4.4; 26 of 96 bare-Bun runs at 12-way parallel, none of 180 on 1.4.4. A longer timeout cannot help a process that never starts and a retry only lowers the odds, so the bridge starts its child behind a gate it opens after `Bun.spawn` returns and the retry helper is gone (#560); the bridge also stops its child when its caller dies, and a test names a hung bridge with `runPtyWith` instead of relying on load). When a test needs a bound, measure the slow tail under 30-way local load, size the bound from it with the number in a comment, and prefer a readiness signal (a marker file the fake writes before it hangs, with the deadline fired after it) over a shorter deadline or a longer margin: a margin still depends on the runner's speed, a barrier does not.
24
24
  - A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
package/docs/dogfood.md CHANGED
@@ -109,6 +109,9 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
109
109
  | Recovery and upgrade wait settings (#489 H4, task 445) | available after release; this PR does not change this hub's running configuration: the five `recovery.*` settings default to the waits they replace (managed attach 600 s, unmanaged reconnect 90 s, control request 30 s, shutdown 30 s, terminal idle 120 s) and nothing is set in this repository's config | every default equals its old constant (pinned with independent literals), every person-visible wait refuses a value below what shipped, a bad or unreadable file is the default and never an error inside a recovery, the coordinator reads both layers and the file at each wait, and inherit and undo go through the local file; the waits' use inside the real recovery driver (a configured value changing an actual attachment, shutdown or control wait) is covered only as far as the tests in this PR reach, and no real upgrade was run with a configured value |
110
110
  | Tasks and routing operational settings (#489 H2, task 441) | available after release; this PR does not change this hub's running configuration: the six `tasks.*` settings default to the constants they replace (demotion threshold 1.5, half-life, contradiction window, re-screen cap 10, split minimum 5 observations, split failure share 0.3), and nothing is set in this repository's `config.local.json` | each setting's default equals its old constant (pinned independently of the descriptor), each reader is called by the code that used the constant, `ahub settings set`, inherit and undo for `tasks.demote_at`, and a reload that reaches the next demotion, re-screen and split read; 29 hand-made mutants, none surviving; inherit and undo are not yet looped over all six keys, and no live `ahub settings set tasks.*` against a running hub |
111
111
  | Bus and delivery operational settings (#489 H3, task 444) | available after release; no running hub configuration is changed here; five defaults retain the previous retry/digest/pull/note/history constants | default pins, bounded H1 schema/UI/apply parity, next-operation runtime traces and real-daemon write/inherit/undo fixtures for all five keys; live native console/dashboard editing and production delivery performance remain unverified |
112
+ | Adapter timing settings (#489 H5, task 446) | available after release; no running hub configuration is changed here; defaults remain ACP tool ceiling 30 min, activity batch 50 ms and Jcode steer 5 s | independent legacy pins, bounds/schema/UI/apply checks, fake-based watchdog/batching/absolute-deadline behavior and real-daemon next-launch snapshots/inherit/undo; native/account timing and live console/dashboard editing remain unverified |
113
+ | Model relay and inference settings (#489 H6, task 447) | available after release; no running hub configuration changed here; legacy defaults retained and all seven rows require daemon restart | default pins, schema/UI/apply parity, fake-clock cooldown/backoff and local fake-server journal coverage; configured native/model calls and live console/dashboard editing remain unverified |
114
+ | UI and console settings (#489 H7, task 448) | available after release; no running console or hub configuration changed here; defaults retained | independent default pins, bounds and config/inherit/undo, startup snapshot/fallback, poll seam, notice/history, cold-start counted trimming and permission/close handling; native interactive console editing remains unverified |
112
115
  | Context gate | off: `context.gate = 0` | context-pressure events |
113
116
  | Context headroom admission (#802) | off by default: `context.max_assignment_used = 0`; not enabled here by this PR | `ahub route explain` known/unknown context decisions and offered owners; pure/session-freshness, real Tasks/manual override and machine-local config regressions; native live admission remains unverified |
114
117
  | Always-ask action classes (#636) | on after release for managed Claude forms, hub-owned ACP in ask mode, local/Pi tools and received Codex command approvals; Codex native bypass stays not enforceable | classed permission requests (a `gh api graphql` query is a read, a mutation, `gh run` or `gh workflow` write and every command with a shell expansion in a classed position (`git push $B`, `rm -rf "$DIR"`) is asked about: #756, #762; `test/action-class.test.ts` holds the table), full local/Pi command visibility and denied effects for incomplete native views; actionPolicyInfo current-child enforceability, remaining class budgets and weakening attention; Codex native no-request and Kimi non-ask ceilings are explicit |
@@ -127,11 +130,14 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
127
130
  | Feature | State here | Measured by |
128
131
  | --- | --- | --- |
129
132
  | Screened room chat and person inbox (#446 X4/X5/X5b) | default off here; requires person enrollment/exposure; console panel 8 and dashboard Agents remain metadata-only; person CLI alone can show one inbox body; real two-machine/account leg remains unverified | signed completed (admitted) versus immutable completed (inbox); once-only inbound count, body-free metadata/list JSON, framed/control-stripped person show, done/discard/expiry purge and fixed stored-byte cap/age refusals, post-ack retries; 1,100-row complete-list ceiling; no broadcast/roles/board/body telemetry, key/storage creation from status or mixed-version success claim; existing restart/binding/disposition regressions |
133
+ | Capability discovery (#445 C1) | off here until a person shares an entry: nothing is listed without `invocation.share` in `.agenthub/config.local.json`; Kimi 2.3.2, Qwen 0.26.1 and Codex 0.166.0 list, every other version lists nothing; the live listing under a real login and a Codex 0.166.0 install remain unverified (the C0 probes used fixture homes); invoking is C2 | `ahub capabilities --json` per-peer entry counts and share states; `hub_capabilities` calls (the `capabilities_list` and tool lines in the hub log) against the count of shared entries; a peer on an unprobed version showing zero entries is the pin working, not a fault |
134
+ | Capability invocations (#445 C2/C3) | off here until a person shares a capability; read needs no approval, write/external require task scope and allow-once approval for each call; unattended refuses effects. Stand-in round trips and approval/audit/hold tests are the verification target; live Kimi/Qwen/Codex receipts remain C4 | `invocation` events and rows by outcome (completed, refused, failed, expired, timeout, needs_review), approval ids and task audit entries, result bytes, time from queue to settle; a read that times out against a live peer is the signal to look at that peer, not at the hub |
135
+ | Cross-hub read invocations (#446 X6) | off until both persons expose peers and independently pin the exact capability/digest/read class; discovery is manual; two-daemon broker/crypto fixtures are the verification target, live X7 receipts remain unverified | source/target invocation ids, request admission versus capability result and result transport outcomes; needs_review counts and no duplicate native dispatch |
130
136
  | Dogfood scorecard (#687) | on after release; read-only weekly receipt | `ahub report --dogfood --since 7d --json`; bounded evidence and explicit unknowns |
131
137
  | Claim verdicts and fact_check provenance (#901) | source/fixture coverage; the live research pilot (a person entering verdicts with `ahub task claim`) remains unverified | `ahub report --dogfood` research rows: `claims.reachedPersonVerdict`, per-slot `personVerdict`/`modelLabel`/`agreement`, `factCheck.selection`/`passed`/`failed`/`missingKeysPerRun`; null until a verdict or a keyed check is recorded |
132
138
  | Dirty-submodule notices (#798) | source/fixture coverage; the live writing/general pilot (a workspace with 26 submodules) remains unverified | `ahub turns` shows `<path> (changed, contents unobserved)` for a turn that only edited a submodule; the D1 notice at `hub_task_propose` and `hub_task_accept`; `ahub undo` names what it cannot restore; a project without `.gitmodules` makes the same git calls per turn (counting-shim test) |
133
139
  | Profile scorecard (#799) | source/fixture coverage; live writing/general/research pilot remains unverified | ahub report --dogfood profile rows; pure and reader dev byte goldens, private-text exclusion, research declarations/unknown verdicts and per-source unknown tests; anonymous weekly receipt templates |
134
- | Domain packs (#318, #513a) | off here: this repository has no pack in `.agenthub/roles`, so `ahub init --profile <pack>` is not used on it; live adoption in another project is unverified | the `ahub init --profile <pack>` preview and apply, the pack validator and the shipped-pack catalogue, covered by test/packs.test.ts (40 mutants killed); adoption would show as a `.pack.json` under `.agenthub/roles` and a `profile` in `.agenthub/config.json` of the adopting project, read from `ahub init --profile` runs. Runnable checks stay display-only (#513b needs its own reviewed runner) |
140
+ | Domain packs (#318/#322, #513) | off here: this repository has no pack in `.agenthub/roles`, so `ahub init --profile <pack>` is not used on it; live adoption in another project is unverified | the `ahub init --profile <pack>` preview and apply, the pack validator and the shipped-pack catalogue, covered by test/packs.test.ts (40 mutants killed); adoption would show as a `.pack.json` under `.agenthub/roles` and a `profile` in `.agenthub/config.json` of the adopting project, read from `ahub init --profile` runs. The writing-pack, analysis and ops packs load with shipped roles; test/domain-packs.test.ts executes all three standalone checks on synthetic passing/refusing fixtures. Their notes remain advisory and no check is auto-selected; live domain-workflow use remains unverified |
135
141
  | Agent-shell read-only commands (#812 T1) | source/fixture coverage; active after release | the kept Allowed forms succeed from an agent shell and the demoted forms are refused with the console line (identity tests plus the live table in the PR body) |
136
142
  | Public agent CLI reads (#812 T1b) | source/fixture coverage; active after release | ordinary tools and agent-shell read success, private/secret projection tests, mutating-neighbour and planner-only refusals |
137
143
  | Conductor operational CLI (#812 T2, #899, #933) | source/fixture coverage; active after release; `ahub doctor --orphans --kill` from the conductor's shell prints the stopped pids for a person and the hub's reply with `--json`; #933 adds board import, intake approve, schedule run/pause/resume, research backfill, bench run, source add, goal link and `tasks.escalate_after`, and lets any agent cancel, label or re-role its own task and list the deliverables it may see | peer-attributed effect admissions, authority/domain-error separation, delayed-handshake and incomplete-output CLI tests, cached privacy projections and revocation checks; `test/conductor-cli.test.ts` holds the orphan cleanup's readable default and `--json` byte equality; `test/console-class-933.test.ts` holds one admission row per #933 form, the operational refusal for another agent, own-task cancel and role, and the screened deliverables list; metric: `agent_cli` rows by label in `events.jsonl` and the person-only refusals that remain |
package/docs/memory.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Hub memory integration
2
2
 
3
- Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.13.0`, initial pin `0.1.1`). The package owns portable storage, tiers, graph, goals, sync, import and raw commands. Agent-hub owns caller authority, current project privacy policy, task lifecycle integration and presentation. Claude-mem is an optional, explicit import source only; runtime recall and capture make no worker HTTP requests.
3
+ Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.14.0`, initial pin `0.1.1`). The package owns portable storage, tiers, graph, goals, sync, import and raw commands. Agent-hub owns caller authority, current project privacy policy, task lifecycle integration and presentation. Claude-mem is an optional, explicit import source only; runtime recall and capture make no worker HTTP requests.
4
4
 
5
5
  ## Portable core and data directory
6
6