@staix/agent-hub 0.22.0 → 0.23.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.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -2
- package/docs/agent-notes/adapters.md +2 -1
- package/docs/agent-notes/budget.md +1 -1
- package/docs/agent-notes/bus.md +4 -1
- package/docs/agent-notes/daemon.md +3 -2
- package/docs/agent-notes/local-worker.md +1 -1
- package/docs/agent-notes/models.md +2 -1
- package/docs/agent-notes/tasks.md +1 -0
- package/docs/agent-notes/tests.md +1 -1
- package/docs/dogfood.md +7 -1
- package/docs/operations.md +147 -6
- package/docs/profiles.md +28 -3
- package/docs/quickstart.md +5 -1
- package/docs/security.md +7 -1
- package/docs/specs/2026-09-19-agent-hub-design.md +66 -4
- package/docs/verification/2026-10-30-0.23.0.md +70 -0
- package/docs/verification/2026-10-30-adapter-operational-settings.md +261 -0
- package/docs/verification/2026-10-30-model-operational-settings.md +211 -0
- package/docs/verification/2026-10-30-operational-settings-closure.md +1217 -0
- package/docs/verification/2026-10-30-ui-console-operational-settings.md +1845 -0
- package/docs/verified.json +15 -15
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/ROLE-NOTICE.md +1 -1
- package/plugins/agent-hub/server.js +559 -115
- package/src/adapters/acp.ts +48 -2
- package/src/adapters/claude-channel.ts +6 -4
- package/src/adapters/codex-appserver.ts +19 -0
- package/src/adapters/jcode.ts +1 -1
- package/src/adapters/local-worker.ts +3 -3
- package/src/adapters/native-capabilities.ts +50 -0
- package/src/adapters/registry-types.ts +3 -10
- package/src/cli/capabilities-output.ts +30 -0
- package/src/cli/console-state.ts +8 -5
- package/src/cli/console.ts +27 -4
- package/src/cli/help.ts +2 -1
- package/src/cli/identity.ts +1 -0
- package/src/cli/main.ts +8 -0
- package/src/hub/board.ts +8 -2
- package/src/hub/bus.ts +100 -8
- package/src/hub/capabilities.ts +215 -0
- package/src/hub/config-apply.ts +6 -3
- package/src/hub/config-trust.ts +1 -0
- package/src/hub/control-client.ts +41 -8
- package/src/hub/daemon.ts +376 -20
- package/src/hub/deferred-hub.ts +2 -2
- package/src/hub/delivery-hints.ts +2 -2
- package/src/hub/delivery-journal.ts +2 -4
- package/src/hub/envelope.ts +12 -2
- package/src/hub/events.ts +2 -0
- package/src/hub/hub-tools.ts +16 -1
- package/src/hub/inference.ts +10 -5
- package/src/hub/invocations.ts +316 -0
- package/src/hub/operational-settings.ts +102 -1
- package/src/hub/peers.ts +3 -0
- package/src/hub/restart.ts +2 -2
- package/src/hub/room-invocations.ts +145 -0
- package/src/hub/settings.ts +1 -1
- package/src/hub/shipped-packs.ts +5 -2
- package/src/hub/shipped-roles.ts +13 -0
- package/src/hub/tasks.ts +14 -0
- package/src/memory/capture.ts +2 -2
- package/src/models/relay.ts +20 -9
- package/src/rooms/application.ts +67 -2
- package/src/rooms/boundary.ts +43 -11
- package/src/rooms/service.ts +40 -2
- package/templates/checks/analysis.mjs +59 -0
- package/templates/checks/fixtures/analysis-expected.json +1 -0
- package/templates/checks/fixtures/analysis-input.json +1 -0
- package/templates/checks/fixtures/analysis.mjs +7 -0
- package/templates/checks/fixtures/ops.json +4 -0
- package/templates/checks/fixtures/writing-rules.json +4 -0
- package/templates/checks/fixtures/writing.json +5 -0
- package/templates/checks/ops.mjs +40 -0
- package/templates/checks/writing.mjs +40 -0
- package/templates/packs/analysis.pack.json +14 -0
- package/templates/packs/ops.pack.json +14 -0
- package/templates/packs/writing-pack.pack.json +14 -0
- package/templates/roles/NOTICE.md +1 -1
- package/templates/roles/ops/meeting-actions-owner.md +26 -0
- package/templates/roles/ops/project-manager.md +26 -0
- package/templates/roles/writing/editor.md +26 -0
- package/templates/roles/writing/style-checker.md +26 -0
- package/templates/roles/writing/terminology-checker.md +26 -0
- package/templates/roles/writing/translator.md +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.0
|
|
8
|
+
|
|
9
|
+
- 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).
|
|
10
|
+
- 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).
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- 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).
|
|
16
|
+
- 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).
|
|
17
|
+
- 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`.
|
|
18
|
+
|
|
7
19
|
## 0.22.0
|
|
8
20
|
|
|
9
21
|
- 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.
|
|
8
|
+
Status: 0.23.0, 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.
|
|
97
|
+
[release verification report](docs/verification/2026-10-30-0.23.0.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).
|
package/docs/agent-notes/bus.md
CHANGED
|
@@ -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
|
-
-
|
|
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, #
|
|
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/operations.md
CHANGED
|
@@ -581,6 +581,32 @@ authority. All newly admitted reads use the ordinary tools connection and public
|
|
|
581
581
|
views, never console identity. The person's full readback stays unchanged.
|
|
582
582
|
Conductor operations are listed in [the operational command table](#conductor-operations-812-t2-933-900).
|
|
583
583
|
|
|
584
|
+
### Capability discovery (#445 C1)
|
|
585
|
+
|
|
586
|
+
`ahub capabilities [--json]` is Console only: an agent shell is refused, and so is the configured conductor. It lists, for each attached peer, the skills and commands the hub can read from that peer's own session, whether you share each one, its digest, and the class a call would count as. It lists and never calls anything; agents call what you share with `hub_invoke` (see below).
|
|
587
|
+
|
|
588
|
+
What the hub can read is deliberately narrow. A listing is read only from a version whose listing was probed (the [C0 receipt](verification/2026-10-30-445-capability-probes.md)): Kimi 2.3.2, Qwen 0.26.1 and Codex 0.166.0. Any other version lists nothing until a new receipt covers it, and so does a peer that is offline or whose list did not arrive. Codex's pinned 0.154.0 is not probed, so a pinned install lists nothing for now. Only skills are listed: Qwen by its `_meta.source` (`bundled-skill`, `skill-dir-command`), Kimi by the `skill:` prefix of its name, Codex by an enabled entry of `skills/list`. A builtin such as Kimi's `compact` or Qwen's `clear` changes session or host state and is never listed. A session the hub cannot read natively publishes its own list with `hub_capabilities_publish`; that list is bound to the connection that published it, forced to `invoke: prompt`, and gone when the connection ends.
|
|
589
|
+
|
|
590
|
+
Sharing is a positive allowlist in `.agenthub/config.local.json` only (`invocation` is machine-local: a committed copy is ignored and logged, and it is never editable from settings):
|
|
591
|
+
|
|
592
|
+
```json
|
|
593
|
+
{ "invocation": { "share": { "kimi": { "command:skill:review": { "digest": "sha256:<64 hex>", "class": "read" } } } } }
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
The key is the peer id (an instance `codex-2` never inherits `codex`'s entries). The digest is the one `ahub capabilities` prints, and covers the kind, name, input schema, declared class, source and invoke form, not the description: a change to any of those unlists the entry until you pin the new digest, and rewording the description does not. `class` is `read`, `write`, `external` or `destructive`, and the effective class is the higher of what the source declared and what you wrote. No native listing declares a class, so an entry you share with no `class` counts as `destructive`, which a later invocation refuses.
|
|
597
|
+
|
|
598
|
+
Agents see only what you shared. `hub_capabilities { peer? }` returns the shared entries (id, flat input schema, effective class, digest, source, invoke form and the target's permission mode) and nothing else; it grants no right to call anything. Descriptions are written by the peer that offers them: they are sanitized, cut at 300 characters and shown as untrusted. Neither `hub_capabilities` nor `hub_capabilities_publish` runs in a PII turn or for a peer holding a PII task, and neither call reaches memory capture.
|
|
599
|
+
|
|
600
|
+
### Invoking a shared capability (#445 C2/C3)
|
|
601
|
+
|
|
602
|
+
`hub_invoke { peer, capability, args?, task? }` asks another peer to run one capability you shared from it (see `hub_capabilities`). A `read` may run without approval. A `write` or `external` requires an open non-PII task owned or reviewed by the caller and your allow-once approval on every call; unattended refuses before prompting. One with no class counts as `destructive` and is never invoked. The approval prompt shows the screened, sanitized arguments (200 characters per value, marked when cut), capability/class/task bindings and argument digest. Argument previews are person-only; audit metadata keeps the digest. Client requests cover the supported approval window; cancelling the tool or closing its control connection withdraws pending admission and approval. A late allow on a withdrawn request is refused. An already admitted native turn is not cancelled. After admission and any required approval, the call returns `{ invocation, state: "queued" }` and the result arrives later as one message from the hub, addressed to the caller only, so a long target turn never holds the caller's turn.
|
|
603
|
+
|
|
604
|
+
What the target receives is one hub-written request that names the caller and the capability and says the arguments are not instructions. It answers as an ordinary turn under its own permissions and sandbox, and may start its reply with `[REFUSED]` and a reason to decline. The result is the target's text, sanitized (a forged hub header is quoted), cut to 8 KiB and given a hub-written first line that marks it as untrusted agent text. Nothing else is promised: a result is another model's words. The answer reaches only that one result: it is taken over before the bus builds anything from it, so no other peer, digest, log line, event, journal row or memory entry ever carries it. The full answer, including a refusal, passes the private-turn, PII-pattern, secret and private-path checks and any enabled model screen using the full answer rather than only its shortened reason, before forwarding any reason; unknown or a screen failure does not clear it, and privacy is rechecked after that await. An unsafe `[REFUSED]` answer keeps outcome `refused` and returns only `<target> declined; the reason was withheld`. An ordinary unsafe answer keeps outcome `withheld` and the existing fixed withheld text. No unsafe callee text is kept; a clean refusal retains its screened reason.
|
|
605
|
+
|
|
606
|
+
Every admission refusal happens before any envelope exists and names its reason: the PII context, nesting (a peer answering an invocation cannot start one), the target being offline, paused, held, off the roster or pull-only, the capability not shared or changed since you pinned it, a destructive class, a missing effect task or person approval, a task that is not the caller's open work or is a PII task, an argument that does not fit the capability's schema, one that the PII or secret screen does not clear, or too many open invocations (4 per caller, 8 per target). A target that is paused (by you, the conductor or the budget) is refused too; a pause that starts while the request waits leaves it waiting until its deadline. Each call counts against the caller's ordinary message rate limits to that peer. An entry you did not share and one that does not exist read the same to an agent.
|
|
607
|
+
|
|
608
|
+
`invocation.timeout_s` in `.agenthub/config.local.json` (10 to 86400 seconds, default 600) bounds the wait. A request still queued at the deadline is withdrawn and nothing runs (`expired`). One the target received with no result by then is a `timeout` for read or `needs_review` for write/external: it may still run, and a late answer changes nothing. Read may automatically replay once on a proven `failed_safe` receipt; write/external never automatically replay. An uncertain effect holds its journal delivery for your inspection. Recipient retry does not replay invocation requests. There is no caller cancel; `ahub queue` resolves a delivery by hand. Task-scoped calls keep queued and terminal ids/digests/class/approval/outcome history through Tasks, without changing task state. The `invocations` table in `hub.db` and the `invocation` event hold ids, the capability, its class, the outcome and the result's size, never an argument or a result. A hub older than this feature refuses `hub_invoke` with a message, and a restart or upgrade refuses a target without the feature while an invocation is open.
|
|
609
|
+
|
|
584
610
|
## Conducting a team from Claude Code or Codex
|
|
585
611
|
|
|
586
612
|
Start the daemon with `ahub up --no-console`, set exactly one conductor in the
|
|
@@ -760,6 +786,22 @@ Private envelopes and PII tasks stay redacted. Local-worker and Pi tool details
|
|
|
760
786
|
allow decisions stay in `ahub tail` / `ahub permit`; the page can deny them.
|
|
761
787
|
No dashboard port is opened until requested. See [the session security model](security.md#local-dashboard-sessions-issue-6).
|
|
762
788
|
|
|
789
|
+
### Cross-hub read invocations (#446 X6)
|
|
790
|
+
|
|
791
|
+
Both persons consent by pinning. On the target hub, use the existing `ahub capabilities` to obtain the local capability id and digest and pin its effective class to `read`. Transfer that digest out of band. On the source hub, add an exact remote entry in its machine-local file:
|
|
792
|
+
|
|
793
|
+
```json
|
|
794
|
+
{ "invocation": { "share": { "kimi@beta": { "command:review": { "digest": "sha256:<64 hex>", "class": "read" } } } } }
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
The target independently retains its own local `kimi` entry for the same capability/digest/read class. Room peer exposure alone grants no invocation. There is no inheritance between labels/peers, and no remote discovery RPC. `hub_capabilities` lists local observed manifests; callers obtain remote capability ids/schema information manually.
|
|
798
|
+
|
|
799
|
+
`hub_invoke` can name the exact `peer@label`. The source validates bounded flat scalar arguments; the target applies its actual schema, current share/digest/effective-read class and privacy/availability gates before building a native request. Source task scope remains local and is not interpreted on the target. Task/approval refs and write/external/destructive requests never cross.
|
|
800
|
+
|
|
801
|
+
A signed room admission receipt is not a capability result. Native execution outcome and result transport state are recorded separately. Result correlation binds room/member/epoch/request/peer/digest, and duplicate or mismatched frames cannot trigger another native invocation. Nested invocation turns refuse depth 2, including before native acceptance is reported. Unknown handoff, receipt, result deadline or restart publication is `needs_review` under the existing journal hold and never silently replays. Inspect the retained invocation and room queue before explicit person disposition.
|
|
802
|
+
|
|
803
|
+
Remote-origin host envelopes retain the existing remote-turn memory/capture/history/telemetry exclusions even though the local native request is hub-authored. Live native account and two-Mac X7 receipts remain separate from the two-daemon broker/crypto fixtures.
|
|
804
|
+
|
|
763
805
|
### Dashboard design tokens (#469 R1)
|
|
764
806
|
|
|
765
807
|
The page's style block starts with one token block that owns every colour, the 4px spacing scale (4, 8, 12, 16, 24, 32), the
|
|
@@ -1974,6 +2016,21 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
|
|
|
1974
2016
|
| Routing | `recovery.control_timeout_s` (30-300 seconds, default 30) | `config.local.json` | next use; the next control request of the recovery coordinator to a hub reads it |
|
|
1975
2017
|
| Routing | `recovery.shutdown_wait_s` (30-300 seconds, default 30) | `config.local.json` | next use; the next wait for a committed source to stop reads it once, at its start |
|
|
1976
2018
|
| Routing | `recovery.terminal_idle_wait_s` (30-600 seconds, default 120) | `config.local.json` | next use; the next wait for a terminal to go idle before it is closed reads it |
|
|
2019
|
+
| Start | `adapter_timing.acp_tool_ceiling_min` (1-30 minutes, default 30) | `config.local.json` | next use; a new ACP launch captures it; effective ceiling is at least the watchdog |
|
|
2020
|
+
| Start | `adapter_timing.acp_activity_batch_ms` (10-50 ms, default 50) | `config.local.json` | next use; a new ACP launch captures its activity batch delay; pending flushes are unchanged |
|
|
2021
|
+
| Start | `adapter_timing.jcode_steer_timeout_s` (5-60 seconds, default 5) | `config.local.json` | next use; a new Jcode launch captures one total native steer deadline |
|
|
2022
|
+
| Routing | `model_relay.cooldown_failures` (1-100, default 3) | `config.local.json` | restart; consecutive transport/startup failures before cooling an alias |
|
|
2023
|
+
| Routing | `model_relay.cooldown_s` (1-300 seconds, default 30) | `config.local.json` | restart; initial cooldown and recent-failure window |
|
|
2024
|
+
| Routing | `model_relay.cooldown_cap_s` (300-3600 seconds, default 300) | `config.local.json` | restart; cooldown cap, never below the initial cooldown |
|
|
2025
|
+
| Routing | `model_relay.terminal_history_limit` (1-1000 records, default 1000) | `config.local.json` | restart; only tightens retained closed-request history |
|
|
2026
|
+
| Routing | `inference.backoff_min` (1-60 minutes, default 5) | `config.local.json` | restart; failure backoff for optional inference, interactive calls exempt |
|
|
2027
|
+
| Routing | `inference.digest_min_items` (1-100000 items, default 5) | `config.local.json` | restart; existing condensation threshold |
|
|
2028
|
+
| Routing | `inference.digest_min_chars` (1-10000000 characters, default 8000) | `config.local.json` | restart; existing condensation threshold |
|
|
2029
|
+
| Start | `console.poll_ms` (2000-60000 ms, default 4000) | `config.local.json` | next use; next console launch captures its refresh interval |
|
|
2030
|
+
| Start | `console.notice_ms` (10000-60000 ms, default 10000) | `config.local.json` | next use; notice visibility may only lengthen |
|
|
2031
|
+
| Start | `console.history_limit` (1-100 commands, default 100) | `config.local.json` | next use; only tightens transient command history |
|
|
2032
|
+
| Start | `console.pending_stream_limit` (1-1000 events, default 1000) | `config.local.json` | next use; only tightens offscreen stream buffering, with omitted counts |
|
|
2033
|
+
| Start | `console.attention_limit` (1-1000 messages, default 1000) | `config.local.json` | next use; only tightens transient message attention entries |
|
|
1977
2034
|
|
|
1978
2035
|
### Operational numeric settings (#489 H1)
|
|
1979
2036
|
|
|
@@ -2066,6 +2123,88 @@ not a setting: the source's preparation lease is a fixed ten minutes, so no othe
|
|
|
2066
2123
|
install timeout has no project to own it, and the 610 s Orca command timeout follows terminal recovery's own ten-minute
|
|
2067
2124
|
ceiling. See the [H4 ledger](verification/2026-10-30-recovery-wait-settings.md) for every item in the group.
|
|
2068
2125
|
|
|
2126
|
+
#### Adapters and handshakes (#489 H5)
|
|
2127
|
+
|
|
2128
|
+
Three `adapter_timing.*` rows use the same H1 metadata, closed schema, loader and
|
|
2129
|
+
settings writer. `next use` means a new native peer launch: the host captures
|
|
2130
|
+
primitive option values for Kimi/generic ACP and Jcode, including Jcode's inherited
|
|
2131
|
+
ACP options. An accepted edit leaves active tool ceilings, pending activity
|
|
2132
|
+
flushes and native steer deadlines unchanged until a new peer starts.
|
|
2133
|
+
|
|
2134
|
+
The open-tool ceiling defaults to the legacy 30 minutes, can only tighten, and
|
|
2135
|
+
remains `max(watchdogMs, configured ceiling)` with remaining-only rearming. The
|
|
2136
|
+
activity delay defaults to the legacy 50 ms and never exceeds that buffer window;
|
|
2137
|
+
its 4096-character threshold and privacy handling stay fixed. Jcode's default
|
|
2138
|
+
5-second steer deadline can only rise, and one absolute timer covers all native
|
|
2139
|
+
phases; pre-send refusals and post-send uncertainty retain their existing fences.
|
|
2140
|
+
No new conductor authority or native permission mapping is added.
|
|
2141
|
+
|
|
2142
|
+
The [H5 ledger](verification/2026-10-30-adapter-operational-settings.md) accounts for
|
|
2143
|
+
all 232 unique H0 identities: 3 converted and 229 excluded with reasons. ACP
|
|
2144
|
+
startup/mode/cancel and Jcode readiness retain their 30-second constants: raising
|
|
2145
|
+
startup deadlines does not fit the existing 65/70-second callers, and shortening
|
|
2146
|
+
would fail slow healthy launches. The shared MCP server's reconnect cap stays
|
|
2147
|
+
fixed at 30 seconds; no daemon-to-plugin projection, raw-config reader, RPC or
|
|
2148
|
+
environment knob is introduced. Cleanup/identity proofs, wire/storage/safety caps,
|
|
2149
|
+
existing settings and test seams remain at their current owners.
|
|
2150
|
+
|
|
2151
|
+
#### Model relay and inference (#489 H6)
|
|
2152
|
+
|
|
2153
|
+
The seven rows above use the same H1 descriptors, validation, settings writer and
|
|
2154
|
+
undo path. All require a daemon restart: an inference consumer captures its
|
|
2155
|
+
configuration at daemon start, and each relay captures its policy at creation.
|
|
2156
|
+
A relay remains cached across a native peer stop/start, so restarting that peer
|
|
2157
|
+
alone does not adopt these values. Pending edits never retime current cooldowns,
|
|
2158
|
+
backoff deadlines or requests, or trim the running relay's journal.
|
|
2159
|
+
|
|
2160
|
+
The initial cooldown and its recent-failure window share one setting. Cooldowns
|
|
2161
|
+
still start only after consecutive transport/startup failures, double by the
|
|
2162
|
+
same fixed factor and end on expiry or an HTTP answer. The cap's minimum equals
|
|
2163
|
+
the initial cooldown's maximum, so it can never fall below that delay. The
|
|
2164
|
+
history setting can only tighten the legacy 1000 closed records; it does not
|
|
2165
|
+
change active-request evidence or suppress terminal observers.
|
|
2166
|
+
|
|
2167
|
+
`inference.backoff_min` preserves fail-open delivery and interactive-call
|
|
2168
|
+
exemption. The two digest thresholds were already configurable; their H1 rows
|
|
2169
|
+
retain those keys, defaults and existing schema bounds. No new model selection,
|
|
2170
|
+
PII screening, conductor authority or cross-process configuration path is added.
|
|
2171
|
+
The 180-second request deadline, 15-second moved-attempt first-byte deadline and
|
|
2172
|
+
8-second inference timeout stay fixed under their current caller budgets.
|
|
2173
|
+
|
|
2174
|
+
The [H6 ledger](verification/2026-10-30-model-operational-settings.md) accounts for
|
|
2175
|
+
all 169 H0 identities: seven registered and 162 left out with individual reasons.
|
|
2176
|
+
PII policy, route scoring, protocol contracts, existing model/routing settings
|
|
2177
|
+
and test seams remain with their current owners.
|
|
2178
|
+
|
|
2179
|
+
#### UI and console (#489 H7)
|
|
2180
|
+
|
|
2181
|
+
Five `console.*` rows use H1 and apply at the next console launch. The console
|
|
2182
|
+
reads accepted `settings_get` row values once through its existing authenticated
|
|
2183
|
+
control connection, under the existing 3-second read bound. It uses fixed H1
|
|
2184
|
+
bounds, ignores pending/file values, and retains legacy defaults for an older,
|
|
2185
|
+
refusing or unreachable hub. A running console keeps its captured preferences.
|
|
2186
|
+
The existing `options.pollMs` test seam still wins, with the fixed 2000 ms floor;
|
|
2187
|
+
refresh serialization and its pending-refresh coalescing remain unchanged.
|
|
2188
|
+
|
|
2189
|
+
Notice visibility may only lengthen. The other retention settings only tighten
|
|
2190
|
+
local presentation memory: command history, offscreen stream events and message
|
|
2191
|
+
attention references. They never trim bus queues, delivery records or approvals.
|
|
2192
|
+
Close/signal and permission handlers remain active while the startup read waits.
|
|
2193
|
+
If pushes arrived before capture, only cold-start presentation buffers are
|
|
2194
|
+
trimmed, preserving accurate omitted counts and every approval/control message.
|
|
2195
|
+
|
|
2196
|
+
The 1-second redraw/approval countdown interval, `appendEvent` cap test seam,
|
|
2197
|
+
Usage history query bound, browser toast/poll/office timings, authentication
|
|
2198
|
+
lifetimes and action budgets remain fixed. The multi-project manager owns the
|
|
2199
|
+
browser page; this change adds no per-project configuration projection or RPC.
|
|
2200
|
+
|
|
2201
|
+
The [H7 ledger](verification/2026-10-30-ui-console-operational-settings.md) covers
|
|
2202
|
+
all 1007 H0 UI/console fallback identities, with 81 in these UI/console paths and
|
|
2203
|
+
926 outside them. The additional `task-progress.ts` initializer is classified
|
|
2204
|
+
under tasks/routing by the inventory's precedence and is recorded separately.
|
|
2205
|
+
Five rows cover six H0 identities (the two command-history paths share one key).
|
|
2206
|
+
The deferred support, CLI and memory items stay constant at their current owners and bounds ([closure record](verification/2026-10-30-operational-settings-closure.md), #989); #489 is complete.
|
|
2207
|
+
|
|
2069
2208
|
- Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`,
|
|
2070
2209
|
the machine-local files (`ahub init` ignores both in git). The shared
|
|
2071
2210
|
`config.json` and the hand-written `routing.toml` are never rewritten, and a
|
|
@@ -3476,10 +3615,12 @@ after them is not retried on MLX, and a failed one marks `dgx/fast` failing (a c
|
|
|
3476
3615
|
disconnects does not). Each backend is tried once per request, so with moves on, a moved attempt
|
|
3477
3616
|
cut at 15 s followed by an MLX slot that stays busy past 120 s fails the request (502), where
|
|
3478
3617
|
without moves the request would have waited for MLX and then had `dgx/fast` with its full
|
|
3479
|
-
deadline; this is part of what the opt-in accepts until #199 AC5 measures it. After
|
|
3480
|
-
consecutive transport or startup failures
|
|
3481
|
-
|
|
3482
|
-
|
|
3618
|
+
deadline; this is part of what the opt-in accepts until #199 AC5 measures it. After
|
|
3619
|
+
`model_relay.cooldown_failures` (default three) consecutive transport or startup failures
|
|
3620
|
+
outside a cooldown (a failure more than twice the cap, 10 min by default, after the last counted
|
|
3621
|
+
one starts the count over), a relay alias cools down for `model_relay.cooldown_s` (default
|
|
3622
|
+
30 s), doubling up to `model_relay.cooldown_cap_s` (default 5 min); all three apply at the next
|
|
3623
|
+
daemon restart. A busy MLX slot and timeouts cut short by an execution budget never count; any HTTP
|
|
3483
3624
|
answer, a success or an error status, proves the transport works and ends the streak and the
|
|
3484
3625
|
cooldown. `ahub status` shows a `cooling` detail with a relative time and failure count and `events.jsonl`
|
|
3485
3626
|
records `cooldown` events.
|
|
@@ -4078,13 +4219,13 @@ A bundled adapter module is authorized by the person with `adapter_modules` (at
|
|
|
4078
4219
|
|
|
4079
4220
|
# Catalog session guidance (#295)
|
|
4080
4221
|
|
|
4081
|
-
- Read available guidance with `ahub role list [--group dev|research]` and `ahub role show <id>`. Each definition identifies its layer and content hash. Doctor reports invalid files/selections without printing their contents.
|
|
4222
|
+
- Read available guidance with `ahub role list [--group dev|research|writing|ops]` and `ahub role show <id>`. Each definition identifies its layer and content hash. Doctor reports invalid files/selections without printing their contents.
|
|
4082
4223
|
- Select up to two ids per peer in `catalog_roles` in the shared/local config, with at least one flow base already held by that peer. Empty local lists clear shared selections. This initial slice applies selections at a new daemon/MCP/adapter start; live assignment/settings and task roles follow separately.
|
|
4083
4224
|
- Defaults preserve the existing flow. Catalog guidance adds no authority or routing preference. Native ingestion/retention and measured benefit remain unverified. Schema, bounds and source notice: [roles](roles.md).
|
|
4084
4225
|
|
|
4085
4226
|
# Project profiles (#259)
|
|
4086
4227
|
|
|
4087
|
-
- Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it. `--profile <pack>` applies a domain pack (`.agenthub/roles/<name>.pack.json` or a shipped one) as review-checklist and example-check overrides; `--dry-run` previews ids only, and a pack is data, never a command ([domain packs](profiles.md#domain-packs-513a), #513a).
|
|
4228
|
+
- Initialize new projects with `ahub init --profile research|writing|general`; default dev retains its existing templates and contracts. Profile aliases map to the existing base flow and are displayed beside it. `--profile <pack>` applies a domain pack (`.agenthub/roles/<name>.pack.json` or a shipped one: `writing-pack`, `analysis`, `ops`) as review-checklist and example-check overrides; `--dry-run` previews ids only, and a pack is data, never a command ([domain packs](profiles.md#domain-packs-513a), #513a).
|
|
4088
4229
|
- Non-dev plans declare paths, section anchors, source ids and claims. Configure completion checks explicitly in machine-local config; selecting a profile never starts a check or study. Schema, examples and pending converter/policy slices: [profiles](profiles.md).
|
|
4089
4230
|
|
|
4090
4231
|
# Private and read-only data paths (#259)
|
package/docs/profiles.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
# Current scope and remaining slices
|
|
32
32
|
|
|
33
|
-
- Peer guidance and profile-specific task schemas use existing MCP/native/local paths. Research defaults use the #295 dev/research catalog, with bases already held. Writing
|
|
33
|
+
- Peer guidance and profile-specific task schemas use existing MCP/native/local paths. Research defaults use the #295 dev/research catalog, with bases already held. Writing and ops catalog groups are available through the domain packs below; profile defaults do not automatically assign them to peers.
|
|
34
34
|
- Board/public projections, route explain, terminal/dashboard cards and #247 events/records carry the profile alias and base. Native model ingestion and context retention remain unverified.
|
|
35
35
|
- Bounded rich-document facts/review/undo projections and declared pii.paths/data.readonly policy are supported. Opt-in shadow git supports non-git folders; project profile overrides are supported; bounded glob policy and deeper lifecycle integration are tracked in #259. Native ingestion and research measurement remain unverified.
|
|
36
36
|
|
|
@@ -61,14 +61,39 @@
|
|
|
61
61
|
|
|
62
62
|
# Domain packs (#513a)
|
|
63
63
|
|
|
64
|
-
A pack bundles a profile with the roles it expects, a review rubric and example checks in one file, so a project can adopt a way of working with one command. The
|
|
64
|
+
A pack bundles a profile with the roles it expects, a review rubric and example checks in one file, so a project can adopt a way of working with one command. The shipped writing, analysis and ops packs use this format (#513b).
|
|
65
65
|
|
|
66
|
-
- A pack comes from two layers: the shipped one (`templates/packs/`, bundled;
|
|
66
|
+
- A pack comes from two layers: the shipped one (`templates/packs/`, bundled; `example`, `writing-pack`, `analysis` and `ops`), and the project one, `.agenthub/roles/<name>.pack.json`, which replaces a shipped pack of the same name. The project layer is in the directory the role catalog already reads (the catalog takes only `.md`, so the two never collide). At most 16 packs of 8192 bytes each, plain regular files that are not links, in a directory that is not a link. A pack that is oversize, malformed or names a role that is not in the catalog (shipped or project) is refused with the reason, never loaded half usable.
|
|
67
67
|
- The file holds exactly `name` (equal to the file name, and never a profile's name), `profile` (one of dev, research, writing, general), `summary` (160 characters), `roles` (at most 8 distinct role ids), `rubric` (1 to 12 criteria, each `id`, `text` of at most 300 characters and `required`) and `checks` (at most 8, each `id` and a `note` of at most 500 characters). Any other field is refused. Text is plain ASCII punctuation with no control characters.
|
|
68
68
|
- Rubric ids are #507's criterion ids (a letter, then letters, digits or `-`, at most 48 characters), the same shape a task's recorded criteria use, so a reviewer names an unmet item with the id at the start of the line. The rubric only changes the wording of the review checklist; it never adds or removes a task's recorded criteria.
|
|
69
69
|
- A pack is data, never authority. A check is display text exactly like `example_checks`: nothing selects, runs or trusts a command from a pack, and there is no field that could. Roles are named, not created: the pack asks for catalog roles by id and the project assigns them with `catalog_roles` as before.
|
|
70
70
|
- `ahub init --profile <profile|pack>` takes a shipped profile or a project pack. `--dry-run` (add `--json` for rows) previews the config row with the pack's id, base profile, rubric criterion ids, check ids and expected role ids, and never their text. The apply writes the pack's rubric as `profile_overrides.review_checklist` (each line `<id>: <text>`, optional criteria marked, then the `{check}` line) and its checks as `profile_overrides.example_checks`, into a new `.agenthub/config.json` or the existing one, keeping every other key. Applying the same pack again changes nothing. A project whose `profile` differs from the pack's, or whose `profile_overrides` already hold different data (compared with sorted keys, so key order never matters), is refused with the reason rather than overwritten. Init does not copy role files and a check is a note, not a command: the spec amendment "domain packs (#513a)" records why.
|
|
71
71
|
|
|
72
|
+
# Shipped domain packs and checks (#513b)
|
|
73
|
+
|
|
74
|
+
| Pack | Base profile | Catalog roles | Deterministic check |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `writing-pack` | writing | editor, translator, style-checker, terminology-checker | writing-metadata, writing-terms, writing-style |
|
|
77
|
+
| `analysis` | research | analyst | analysis-reproduce |
|
|
78
|
+
| `ops` | general | project-manager, meeting-actions-owner | ops-records |
|
|
79
|
+
|
|
80
|
+
Use `ahub init --profile writing-pack --dry-run` (or `analysis`/`ops`) to preview, then omit `--dry-run` to apply. `writing` remains the existing plain profile; the pack uses `writing-pack` because pack names cannot shadow profiles. Expected roles are already in the shipped catalog; assign them deliberately through the existing role flow. Neither init nor pack loading copies or executes a checker, adds trusted `checks` commands, or grants permissions.
|
|
81
|
+
|
|
82
|
+
From an agent-hub source checkout, these commands run the shipped synthetic examples:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
bun templates/checks/writing.mjs templates/checks/fixtures/writing.json templates/checks/fixtures/writing-rules.json
|
|
86
|
+
bun templates/checks/analysis.mjs templates/checks/fixtures/analysis.mjs templates/checks/fixtures/analysis-input.json templates/checks/fixtures/analysis-expected.json
|
|
87
|
+
bun templates/checks/ops.mjs templates/checks/fixtures/ops.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The npm package includes these files under `templates/checks/`; when installed, use their absolute installed-package paths or copy the reviewed examples into the project. Only an explicitly selected, trusted analysis program is executed. A project that wants an automatic task check must separately review and configure a command through the existing `checks` policy. A pack note remains advisory.
|
|
91
|
+
|
|
92
|
+
- Writing takes a JSON document with nonempty `title`, `language` and `text`, and JSON rules with literal `requiredTerms` and `bannedPhrases` arrays. Required terms must occur in text; banned phrases are absent from title/text. The mechanical style check refuses TODO/TBD placeholders and trailing whitespace. These checks do not assess translation fidelity, source support, readability or audience fit.
|
|
93
|
+
- Analysis takes a program, JSON input and an exact expected stdout file. It invokes the selected program twice with the input path as its first argument, without a shell, compares stdout bytes including the final newline, and refuses changes to any declared file after either run. Each run has a 5-second/1-MiB stdout bound; its stderr is withheld. Equality does not prove method suitability, input provenance or reproducibility of undeclared environment/dependencies.
|
|
94
|
+
- Ops takes an array of exact `{id, owner, due, status}` records. IDs must be unique and nonempty, owners nonempty, due dates real `YYYY-MM-DD` values, and status one of `open`, `in_progress`, `blocked`, `done`. It does not compare to the current clock. An empty array passes structure only; it does not establish that a meeting had no actions or that all actions were captured.
|
|
95
|
+
- All input files are bounded to 1 MiB. JSON inputs must be valid UTF-8/JSON; symbolic links and missing files refuse. Analysis expected output is compared as raw bytes rather than parsed JSON. Results contain stable criterion ids and a final `writing: OK`, `analysis: OK` or `ops: OK` (or FAIL), never artifact text. Exit codes are 0 for pass, 1 for refusal and 2 for incorrect arguments. Human rubric items remain separate from these mechanical results. Fixture success is not live workflow or publication approval.
|
|
96
|
+
|
|
72
97
|
# Profile scorecard (#799)
|
|
73
98
|
|
|
74
99
|
The existing ahub report --dogfood command adds a profile section when the selected profile is non-dev, or bound non-dev tasks have evidence in the requested window. Dev-only JSON and readable output retain their prior bytes. Existing flow, quality, cost and other fields keep their meanings; the profile section does not require merged PRs.
|