@staix/agent-hub 0.12.20 → 0.12.21
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 +22 -0
- package/README.md +6 -5
- package/docs/agent-notes/adapters.md +9 -2
- package/docs/agent-notes/bus.md +5 -2
- package/docs/agent-notes/daemon.md +8 -2
- package/docs/agent-notes/local-worker.md +3 -1
- package/docs/agent-notes/tasks.md +3 -1
- package/docs/agent-notes/tests.md +3 -2
- package/docs/bench.md +143 -0
- package/docs/events.md +2 -1
- package/docs/operations.md +271 -46
- package/docs/quickstart.md +12 -10
- package/docs/research.md +92 -0
- package/docs/security.md +45 -10
- package/docs/smoke.md +79 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +194 -20
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +68 -10
- package/docs/verified.json +28 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +3 -2
- package/src/adapters/acp.ts +67 -6
- package/src/adapters/codex-appserver.ts +67 -2
- package/src/adapters/local-worker.ts +4 -3
- package/src/adapters/pi.ts +58 -4
- package/src/cli/bench.ts +287 -0
- package/src/cli/console-state.ts +226 -28
- package/src/cli/console.ts +16 -4
- package/src/cli/facts-hook.ts +50 -8
- package/src/cli/help.ts +10 -0
- package/src/cli/identity.ts +10 -4
- package/src/cli/launch.ts +96 -17
- package/src/cli/main.ts +159 -18
- package/src/cli/preview.ts +13 -3
- package/src/cli/status-lines.ts +2 -1
- package/src/cli/terminal-recovery.ts +83 -31
- package/src/cli/upgrade-runtime.ts +67 -18
- package/src/cli/upgrade.ts +81 -9
- package/src/hub/bench.ts +211 -0
- package/src/hub/bus.ts +19 -4
- package/src/hub/conductor.ts +22 -21
- package/src/hub/config-trust.ts +1 -0
- package/src/hub/control-client.ts +4 -1
- package/src/hub/daemon.ts +346 -40
- package/src/hub/delivery-journal.ts +6 -6
- package/src/hub/events.ts +2 -1
- package/src/hub/lifecycle.ts +8 -13
- package/src/hub/manager.ts +16 -14
- package/src/hub/permission-mode.ts +44 -0
- package/src/hub/recovery-store.ts +21 -9
- package/src/hub/registry.ts +36 -22
- package/src/hub/research.ts +255 -0
- package/src/hub/tasks.ts +70 -43
- package/src/hub/tool-refusal.ts +7 -0
- package/src/hub/ui.ts +4 -1
- package/src/local/tools.ts +14 -10
- package/src/pi/process-signature.ts +23 -0
- package/src/pi/tool-receipts.ts +7 -1
- package/src/ui/index.html +83 -6
- package/src/ui/task-progress.ts +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ 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.12.21
|
|
8
|
+
|
|
9
|
+
- Console and dashboard show shared whole-board task progress, waiting dependencies and four-stage tracks; the dashboard adds a host-scoped System/Light/Dark choice with pre-paint initialization and hashed inline CSP (#246).
|
|
10
|
+
- `ahub bench` follow-ups: a task id such as `constructor` no longer breaks reports, an attempt whose measure wait ran out or was interrupted in its last poll records no measures, the fingerprint lists only attached peers, a run returns to a branch that shares its name with a tag, and a suite ref deleted during the run is reported (#265).
|
|
11
|
+
- `ahub bench`: run a suite of tasks against the attached peers in a project kept for benchmarks, record pass, fail, timeout and the #247 measures per attempt in `~/.agenthub/bench/`, and compare arms with bootstrap intervals (`bench run|list|status|report|compare|export`); the dashboard gains a Benchmarks section (#251).
|
|
12
|
+
- Kimi can start or resume with an operator-selected `permission_modes.kimi` default, setting ACP `yolo` for `ask-when-needed` or `auto` for `never-ask` before its first prompt; unavailable or refused modes fail startup with a reason. Non-Kimi ACP sessions retain their native mode as unmanaged. Kimi 2.1.1 benign live checks accepted both ids and completed file edits and harmless shells with no ACP approval requests; risky-action behavior remains unverified (#240).
|
|
13
|
+
- Add human-only `ahub permission` and a console Peers `m` chooser for running Claude, Codex, Kimi, Pi and local, with explicit confirmation for `never-ask`, status/event visibility and project defaults capped at ask-when-needed until a source-labelled console confirmation enables never-ask at each hub start. Native sandboxes remain unchanged; `ask` restores native approval handling (#242).
|
|
14
|
+
- Return proven pre-effect Pi task/conductor refusals as their exact errors and completed receipts; preserve uncertain effects, storage failures and unproven legacy pending receipts (#254).
|
|
15
|
+
- Log each Pi exit cause and last tool name without arguments, show the next action, and resume an unexpectedly idle headless Pi on its verified session at most once in 60 seconds under `pi.auto_start`, with no fresh fallback (#255).
|
|
16
|
+
- Opt-in research records (`"research": { "enabled": true }`): one record per approved task in `~/.agenthub/research/`, across projects, with outcome, review rounds, rework, failed checks, tokens and time per peer and model, built only from `events.jsonl` (never task or message text). `ahub research` reports success, first-pass, rework and check-failure rates and tokens and wall time per task; `ahub research export` writes JSONL or CSV; `ahub research backfill` builds records from existing events; `ahub task label` adds a person's later verdict (#247).
|
|
17
|
+
- Preserve empty committed queue entries for offline peers during journal-backed recovery, so preservation verification does not require them to reconnect. Durable queues and delivery receipts remain authoritative; stale non-empty snapshot queues are never replayed (#234).
|
|
18
|
+
- Preserve every pause and queue array when a delivery resolution is refused or its journal write fails; roll back only the recipient queue and preface (#229).
|
|
19
|
+
- Retry a dependent task released while no peer can take it or its ready/assignment write fails; consume its per-run offer only after assignment (#231).
|
|
20
|
+
- Test port probes bind 127.0.0.1 to match the Codex servers, and failed daemon starts show their error in assertion diffs (#232).
|
|
21
|
+
- Reuse Git fixture templates and restore sealed-study mutations between subtests to reduce export test process spawns without changing its 20 s timeout (#233).
|
|
22
|
+
- Judge a recorded hub manifest, registry claim, recovery runner and manager owner by its process signature as well as its pid, through one helper, so a pid that a reboot handed to another process (one we may not even signal) no longer wedges recovery, `ahub up` or `ahub reset`. `status.json` (file only), the registry claim, the runner record and the manager owner now carry the signature, daemons stop writing `hub.pid`, and recovery reads the registry's live claim instead of the plan's frozen copy. Records written by 0.12.20 and older keep the bare-pid answer; the operations guide gives the manual clear for such a claim (#226).
|
|
23
|
+
- A stop-and-archive recorded on an operation whose coordinator predates #215 marks its receipt `schema: 2`, which every older runner and abort refuse before they take the lock, so an older global `ahub recovery resume` can no longer advance an operation being abandoned; checked against the real v0.12.19 source (#227).
|
|
24
|
+
- A `terminal-recovery.json` row that fails validation is unknown for the launcher it may describe, never "no launcher", and the next `ahub codex`, `ahub claude` or `ahub pi` writes it back unchanged; launchers recorded by 0.12.12 or older are also read by their unpinned signature (#228).
|
|
25
|
+
- `ahub recovery resume` restarts a target hub that stopped after it started (a crash, an OOM kill, a reboot) from the operation's snapshot, once per resume: it records the restart (status shows `restarts`), closes the terminals the dead hub's peers were restored into, relaunches those peers against the new hub (a session the operator had accepted by its own id), and treats a launcher of the dead hub or a session attached without an id as live, never as nothing attached. Without the snapshot, or for an operation of an older coordinator, stop-and-archive stays the way out (#225).
|
|
26
|
+
- Keep local-worker hub tool arguments and results out of claude-mem capture; ordinary turns see PII task stubs, and only PII turns may read full local board rows (#230).
|
|
27
|
+
- Console stream lines color peer names, the `task` keyword, numeric values and issue/task references separately, keeping ordinary words and message bodies at the default foreground and preserving plain output and terminal geometry (#239).
|
|
28
|
+
|
|
7
29
|
## 0.12.20
|
|
8
30
|
|
|
9
31
|
- `ahub help` groups the commands into sections, starts every description at one column and wraps at word boundaries to the terminal width (80 to 100 columns), colored with the console palette only on a terminal without `TERM=dumb` or `NO_COLOR`. `--help`, `-h` and `help <command>` work, and `ahub <command> -h` (or `--help`) given as the only argument prints that command's help instead of running it, except for claude and codex, which pass it to the agent; an unknown command prints a one-line hint instead of the full list. The console shares the word-aware wrapping, which also covers the wrapping item of #213: the help overlay, approval titles, details and stream lines break at word boundaries. A continuation line starts four columns deeper than its source line (at most half the width), so wrapped peer text in the console stream never starts at column 0 or 2, where headers and event lines start; a wrapped line that starts like a hub header keeps its `> ` marker, now counted in its width (#212).
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Native multi-agent hub for one developer's machine: Claude Code, Codex, Kimi Cod
|
|
|
4
4
|
hub-owned local-LLM worker collaborate as peers in independent project directories, with
|
|
5
5
|
task-aware model routing (Switchyard) in front of a self-hosted gateway (OmniRoute).
|
|
6
6
|
|
|
7
|
-
Status: 0.12.
|
|
7
|
+
Status: 0.12.21, control protocol 16. Durable delivery records distinguish queued
|
|
8
8
|
work from uncertain execution. The [smoke checklist](docs/smoke.md) records
|
|
9
9
|
verified paths and remaining prerequisites.
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@ cd <your project> && ahub init && ahub up
|
|
|
28
28
|
Or install the same version from GitHub:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.12.
|
|
31
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.12.21 && ahub setup
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
The installed commands remain `ahub` and `agent-hub`.
|
|
@@ -138,13 +138,14 @@ Bun + TypeScript. Single daemon per project directory, loopback only.
|
|
|
138
138
|
## Local dashboard
|
|
139
139
|
|
|
140
140
|
With a running daemon, `ahub ui` opens a local dashboard for messages, peer states
|
|
141
|
-
and queues, the task board, budget windows
|
|
142
|
-
`ahub ui --no-open` to print a single-use link when the
|
|
141
|
+
and queues, the task board, budget windows, pending approvals and this machine's
|
|
142
|
+
benchmark runs. Use `ahub ui --no-open` to print a single-use link when the
|
|
143
|
+
browser cannot be opened.
|
|
143
144
|
Open the link within 60 seconds; the browser session lasts one hour.
|
|
144
145
|
|
|
145
146
|
The dashboard can send console messages, propose and assign tasks, pause or resume
|
|
146
147
|
peers, and answer approvals. Budget pauses still require the terminal override.
|
|
147
|
-
Private envelopes and PII tasks stay redacted. Local-worker tool details and
|
|
148
|
+
Private envelopes and PII tasks stay redacted. Local-worker and Pi tool details and
|
|
148
149
|
allow decisions stay in `ahub tail` / `ahub permit`; the page can deny them.
|
|
149
150
|
No dashboard port is opened until requested. See [the session security model](docs/security.md#local-dashboard-sessions-issue-6).
|
|
150
151
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
# Native adapters and spawned agents
|
|
2
2
|
|
|
3
|
-
Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` and `activity` events), peer turn state, approvals, and the agent processes the hub spawns and stops. Read before editing `src/adapters/`, `src/pi/`, `src/hub/peers.ts`, `src/hub/child-process.ts`, `src/hub/lifecycle.ts` (shutdown deadline), `src/memory/capture.ts` (session end) or the process-tree stop in `scripts/benchmarks/teardown.ts`, or before adding a native peer.
|
|
3
|
+
Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` and `activity` events), peer turn state, approvals, and the agent processes the hub spawns and stops. Read before editing `src/adapters/`, `src/pi/`, `src/hub/peers.ts`, `src/hub/permission-mode.ts`, `src/hub/tool-refusal.ts`, `src/hub/child-process.ts`, `src/hub/lifecycle.ts` (shutdown deadline), `src/memory/capture.ts` (session end) or the process-tree stop in `scripts/benchmarks/teardown.ts`, or before adding a native peer.
|
|
4
4
|
|
|
5
5
|
- 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.
|
|
6
6
|
- Codex reports each thread it adopts, started or resumed (`onThread`, the `native_thread` event), before any turn on it starts: the upgrade planner (`codexZeroTurn` in `src/cli/upgrade-runtime.ts`, #215) gives each codex `turn_start` to the thread adopted last before it and plans a thread it saw start with no turn (and no rollout) as a fresh start.
|
|
7
7
|
- Codex 0.154.0 `agentMessage` items carry `text` and `phase` (not `content[]`); only the last non-`commentary` message of a turn is shared.
|
|
8
8
|
- Approval titles are agent-written text shown to a person: the daemon escapes control characters, and write/edit/bash show what will be written or run.
|
|
9
9
|
- An ACP permission request may carry no `rawInput` (Kimi 2.0.1): the arguments were on the earlier `tool_call` update, which `acp.ts` remembers by `toolCallId`. Kimi 2.1.1 sends no `rawInput` before the answer at all: the argument JSON streams as `content` text on `tool_call_update`, and only complete JSON counts. A payload that still cannot be resolved, or is too long to show whole (marked `[cut, N chars]`), withholds its `allow_always` option - never render a bare tool name as if it described the call.
|
|
10
|
+
- Tag a Pi hub-tool refusal only at a proven pre-effect guard with `PreEffectToolRefusal`; return its exact error and settle its receipt. Never classify a generic/post-effect/storage failure or a legacy pending receipt from error text or current board state.
|
|
11
|
+
- Report each Pi owner exit once, with code/signal (unknown for an unobserved TUI exit), turn/tool activity and only the last tool name. Auto-restart only a current, started, unexpectedly idle headless owner on its verified persisted session, once per 60 s; preserve shutdown/recovery/startup/active-tool fences and never fall back fresh.
|
|
10
12
|
- Pi's `always` grant (#209) is keyed by `executeTool`'s tool name, never parsed from the approval title, and lives only in that Pi start's closure. Never persist it, share it with the local worker, or let the dashboard give it.
|
|
11
|
-
- Auto-approval covers the hub's own tools by exact `mcp__agent-hub__<name>` title and only ever picks `allow_once`. Identity comes from the permission request title or, when an agent titles the request with the argument JSON (Qwen), from the announced `tool_call` title bound to the call id and resolved against the session's configured MCP servers, never from payload text, a prefix or an unrelated display title. Never widen it to a prefix
|
|
13
|
+
- Auto-approval covers the hub's own tools by exact `mcp__agent-hub__<name>` title and only ever picks `allow_once`. Identity comes from the permission request title or, when an agent titles the request with the argument JSON (Qwen), from the announced `tool_call` title bound to the call id and resolved against the session's configured MCP servers, never from payload text, a prefix or an unrelated display title. Never widen it to a prefix or replace it with an agent-selected session mode. Set Kimi `yolo`/`auto` only for its verified native identity and an operator mode; config never-ask waits for a console y, and other ACP agents stay unmanaged on fresh/resumed starts without setting/resetting modes or failing over their reported mode; refuse runtime changes until their map exists; await the advertised mode change after session new/load and before a prompt, and fail startup on refusal. Use the shared `KIMI_MODE_IDS` mapping in `src/hub/permission-mode.ts`.
|
|
14
|
+
- Permission modes (#242) are a person's and live in the daemon's memory only: `permission` and `permission_default` are console-role control messages, `ahub permission` is `console` in `classifyPeerCommand`, and no hub tool, conductor tool or dashboard action sets one. Never write a runtime choice to a restart snapshot.
|
|
15
|
+
- A peer whose mode lives in the native agent (Kimi, Codex) joins the bus only through `reconcileNativeStart` (`src/hub/daemon.ts`): after `start()` the daemon's current mode is applied, then `bus.add` (it can drain at once), then the state `start()` reached is replayed. Every mode change runs inside `permissionChange`, one per peer at a time.
|
|
16
|
+
- Codex takes a mode as an `approvalPolicy` override on `turn/start`, and the override persists on the thread: pass the hub's and the TUI's `turn/start` alike through `applyPermissionPolicy`, and return to `ask` only by sending the captured native policy once. With none captured, refuse while the TUI is attached; never just stop sending the override. A detached proxy's `clearPermissionMode` is never refused and logs that the thread keeps its last policy.
|
|
17
|
+
- An unanswered `session/set_mode` leaves the native mode unknown, not unchanged: `AcpPeer` invalidates the turn, goes offline, stops its process group and refuses further changes until restarted. Never report the old or the requested mode after a timeout.
|
|
18
|
+
- A mode grant in `piPermit` or the local worker's `permit` (`src/hub/daemon.ts`) answers as the prompt path does: allowed only while that owner is still the attached one and the call was not aborted. `ask-when-needed` covers `PI_EDIT_TOOLS` on a `grantablePath` only (path rule: `local-worker.md`).
|
|
12
19
|
- Every agent the hub spawns (the Codex app-server, ACP agents such as Kimi, Pi) runs in its own process group and is stopped as one (`stopOwnedProcess(proc, { group: true })`, `trackGroup` after spawn): `codex` is a node launcher, and mid-turn its native app-server does not exit on SIGTERM within the grace period, so a SIGKILL to the launcher alone left the app-server running under init and holding the daemon alive (#113, #115). A new adapter that spawns an agent does the same.
|
|
13
20
|
- To stop a process tree, freeze it (SIGSTOP) before reading what it contains, then SIGKILL, and call it done only when the table shows none of it (with no table at all, the stop can only ask the group itself, which cannot see groups below it). A process a read after the freeze shows for the first time is frozen and read again before anything is killed. A snapshot taken while the tree runs misses what it starts next: three review rounds of #113 found such a gap (groups of its own, an unreadable first read, a child started during the grace period).
|
|
14
21
|
- Work that must survive `ahub kill` (claude-mem `session-end`) is awaited in `stop()`; fire-and-forget dies with the process.
|
package/docs/agent-notes/bus.md
CHANGED
|
@@ -7,10 +7,13 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
|
|
|
7
7
|
- A failed digest is retried one envelope at a time, so a poison envelope cannot take its neighbours down with it.
|
|
8
8
|
- An `important` envelope being steered is not in the queue while the steer is in flight; queue it first and an idle transition delivers it twice.
|
|
9
9
|
- `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.
|
|
10
|
-
- 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 any failure (a thrown `deliver
|
|
11
|
-
- A condensed digest is sent by `digest`, not `hub`: `replyParent()` skips
|
|
10
|
+
- 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 is never condensed.
|
|
11
|
+
- 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.
|
|
12
12
|
- 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.
|
|
13
13
|
- 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` empty fans the message out to every peer.
|
|
14
14
|
- `digest` is not a peer: addressing a reply at the envelopes the peer was handed sends it nowhere once a delivery was condensed. The bus resolves `digest` back through `lastDelivery.originals`; anything else that reads a reply's `to` has to do the same.
|
|
15
15
|
- A pull-only peer (a Claude session without channel pushes, #205) is never drained; `Bus.pull` hands it what one push would take. Drain and pull share `held()` (recovery, an uncertain delivery, any pause) and `remember()` (so `reply_to` resolves), and a drain rechecks `deliverable()` after condensing because such a session can take the peer meanwhile. A pull is journaled `completed` in the same transaction as the bus snapshot, never `accepted`; a failed one puts back only that peer's queue and preface and never reloads the snapshot.
|
|
16
|
+
- `resolveDelivery` mutates queues in place; a refused or failed resolution puts back only that peer's queue and preface and keeps every pause. Never reload a snapshot on a live bus.
|
|
17
|
+
- `restore` takes queued work from the journal, never from the committed snapshot once the journal has state (an empty journal, revision 0, is seeded from the commit): from the commit only an empty queue key is carried over, for an offline peer whose key no delivery ever persisted (#234). Never resurrect a non-empty queue from it.
|
|
18
|
+
- The journal's owner check (`rejectLiveOwner` in `delivery-journal.ts`) judges the manifest pid with `processLiveness` and its `pidSignature`, never a bare probe (rule: `daemon.md`).
|
|
16
19
|
- A hub-native peer (Pi, the local worker) does not get to call its own message `important`: `capPriority` caps it unless the delivery it answers held an `important` envelope addressed to it. Check the delivery, not `replyParent`, which ties on hop and takes the later item. Do not bypass it by setting `priority` in the adapter.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Scope: the control WS and its protocol, state files, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/hub/daemon.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/cli/setup.ts`, `src/cli/upgrade*.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts` or `src/cli/facts-hook.ts`, or before adding a native peer.
|
|
4
4
|
|
|
5
|
-
- The plugin bundle is installed apart from the daemon.
|
|
5
|
+
- 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).
|
|
6
6
|
- `ahub setup` reads Claude Code's state from `--json` listings and takes one step at a time, re-reading after each; never match paths or names by substring.
|
|
7
7
|
- A copy of the git index keeps the original's mtime (`snapshot()`): git's racy-entry check compares entries with the index file's time in whole seconds, and a fresh copy makes a same-size edit look clean.
|
|
8
8
|
- State files that clients read (`status.json`, `control-token`) are written after the port is bound, and `status.json` via temp file + rename.
|
|
@@ -11,5 +11,11 @@ Scope: the control WS and its protocol, state files, the Claude channel's reconn
|
|
|
11
11
|
- 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.
|
|
12
12
|
- 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.
|
|
13
13
|
- A zero-turn Claude session has no transcript, so it can never be resumed: every identity gate that compares session ids (restore, coordinator verify, daemon readiness) must tolerate a fresh session while the transcript is absent and stay strict the moment one exists. The commit snapshot records `sessionPersisted` for the restored daemon, and snapshots too old to have it are re-derived from disk. Never treat "session id changed" as proof of a lost conversation without checking the transcript first.
|
|
14
|
-
- The terminal-recovery launcher signature is the shared pinned `processSignature` (`src/pi/process-signature.ts`): a record written by one CLI invocation must compare equal when a later invocation re-reads the live launcher under another TZ/LC_ALL (#177). In-process `process.env` mutation does not reach Bun.spawnSync's default child environment, so cross-environment regressions are tested with real subprocesses.
|
|
14
|
+
- The terminal-recovery launcher signature is the shared pinned `processSignature` (`src/pi/process-signature.ts`): a record written by one CLI invocation must compare equal when a later invocation re-reads the live launcher under another TZ/LC_ALL (#177). In-process `process.env` mutation does not reach Bun.spawnSync's default child environment, so cross-environment regressions are tested with real subprocesses. A row written by 0.12.12 or older carries the unpinned signature, which `launcherState` also compares (#228). A launch-record row that fails validation is unknown for the peer it may belong to and is written back unchanged, never filtered out (#228).
|
|
15
|
+
- Judge every recorded owner pid (hub manifest, registry claim, recovery runner, manager owner, launcher record) with `processLiveness` in `src/pi/process-signature.ts`, never a bare `process.kill(pid, 0)`: after a reboot a recorded pid can belong to another process, even one we may not signal, and the bare probe wedged recovery and `ahub up` (#226). A writer records `processSignature(process.pid)` beside its pid; a registry claim stores it as `<instance id> <signature>` because an older daemon's claim rewrites the pid but not that column. Records without a signature keep the bare-pid answer; unknown is never gone.
|
|
15
16
|
- A restored daemon accepts a changed native session id (Kimi and local aside: they restart as fresh workers) in two ways only: the #64 rule for a Claude session that never persisted a transcript, and the operation-fenced `recovery-waivers.json`, which the coordinator writes wherever it accepts a session other than the planned one: a reconnect-only Claude (#206), an operator-chosen fresh session, the plan's `freshStart` of a Codex thread the hub saw start with no turn, or a zero-turn Claude it accepted (#215). Never waive on any other evidence. Re-preparation after an expired lease keeps every effect receipt, and `restored:<peer>` = `failed` never counts as restored. Judge a restoration launcher by its `terminal-recovery.json` record and process signature (and a `terminal_handle_stale` answer), never by the terminal's exit; an unreadable record is unknown, never "none".
|
|
17
|
+
- A target that stopped after it started is restarted only through `recordRestart` (`src/cli/upgrade.ts`), recorded before the new one starts and at most once per project per resume: each done `restored:<peer>` becomes `retired:<peer>`, whose terminal is closed (`closedRetired:<peer>`) or found absent before that peer is relaunched, and is never revalidated against the new instance. Launcher evidence is read for every instance in `restarts`, and a session attached without an id is unknown, never gone (#225).
|
|
18
|
+
- An operation receipt stays `schema: 1`. Only `abandoned()` writes 2, on an operation with a recorded `disposition` whose coordinator predates #215: in the same write as the disposition, or, for a disposition 0.12.20 recorded at 1, by the next command that holds the runner claim. `validateReceipt` accepts 2 only with a disposition (#227). Never write 2 on an operation that can still be resumed.
|
|
19
|
+
- Three checks read another release's source text for an exact line: `coordinatorCurrent` (`export async function disposeRecovery` in `src/cli/upgrade.ts`), `targetReadsWaivers` (`export function readRecoveryWaivers` in `src/hub/restart.ts`) and `coordinatorRestarts` (`export function recordRestart` in `src/cli/upgrade.ts`). Renaming, moving or un-exporting one makes this release read as an older one: keep those lines until a capability list replaces the checks.
|
|
20
|
+
- Recovery judges the registry claim as it is now (`inspectRecovery` re-reads `registeredProjects()`), never the plan's frozen copy; a registry that cannot be read is not inspected, never stopped (#226).
|
|
21
|
+
- The managed Claude hook (`src/cli/facts-hook.ts`) may only add `permissionDecision: "allow"` on `PreToolUse`, and for `ask-when-needed` only what `projectFileTool` accepts: a hub that is down, slow or refuses decides nothing. The daemon puts `permission` in a `facts` reply only for the hook of this instance's recorded `ahub claude` launch (the same `launchId`, not `--unattended`), and a permission-only hook (`hookPurpose`) never drives busy state (#242).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Local worker and sandbox
|
|
2
2
|
|
|
3
|
-
Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt sandbox. Read before editing `src/adapters/local-worker.ts`, `src/local/`, `src/memory/capture.ts` or `src/hub/facts.ts` (both read the denylist).
|
|
3
|
+
Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt sandbox. Read before editing `src/adapters/local-worker.ts`, `src/local/`, `src/memory/capture.ts` or `src/hub/facts.ts` (both read the denylist), or the grant filter in `src/hub/permission-mode.ts`.
|
|
4
4
|
|
|
5
5
|
- Nothing the local worker executes may run outside `sandboxedExec`; a new tool that spawns a process goes through it, and a tool that touches a path goes through `guardPath`.
|
|
6
6
|
- The sandbox denies home reads by default (toolchain dirs, the project and its real git dir excepted) and all network, loopback included: claude-mem and the Codex app-server listen on loopback without auth. Tests that bind a local port therefore fail when the worker runs them, also with `local.bash_network: true` (the egress proxy keeps loopback closed, and the proxy variables send a test's plain-HTTP `fetch` to the proxy, which refuses it); that is the intended trade-off, and `"direct"` is the switch until 0.13.0 removes it.
|
|
@@ -10,3 +10,5 @@ Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt
|
|
|
10
10
|
- git arguments never pass through `guardPath`; `gitArgsProblem` refuses absolute paths, `..`, `--no-index` and denylisted `rev:path` forms.
|
|
11
11
|
- 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.
|
|
12
12
|
- After a tool with side effects ran, a failed turn is reported, never redelivered.
|
|
13
|
+
- 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).
|
|
14
|
+
- A tool that asks for approval passes its name to `ctx.permit`, and `write`/`edit` also the `guardPath` result, never the model's path string: `ask-when-needed` grants on that real path through `grantablePath` (`src/hub/permission-mode.ts`). `bash` and mutating `git` pass no path. After the awaited approval `write`/`edit` run `guardPath` again and refuse a target that changed (#242).
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Task board and hub tools
|
|
2
2
|
|
|
3
|
-
Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts` or `src/hub/hub-tools.ts`.
|
|
3
|
+
Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts` or `src/hub/hub-tools.ts`.
|
|
4
4
|
|
|
5
5
|
- Tool callers are models: MCP `inputSchema` is not enforced on the way in. Normalize at the boundary (`cleanRefs`) before anything reaches the board, and never throw after a board write.
|
|
6
6
|
- `assign()` never defaults to the task's current owner, or a decline can only come back to the decliner. A reservation (#207) is the same risk: the reserved owner goes first only through `blocked()`, and a decline or an escalation for reason `rejections` or `manual` records the owner it left in the history entry's `from`, which `excluded()` reads, so no later reroute hands the task back through its reservation.
|
|
7
7
|
- The state machine allows `in_progress -> approved` only for classes without a reviewer; `review()` checks `in_review` itself.
|
|
8
8
|
- Conductor authority requires the explicit unique role before capability checks. The only exceptions run before that check in `Conductor.execute` and stay task-scoped: a task's current owner and reviewer read it with `hub_task_show` (#208), and its proposer redirects it with `hub_task_assign` while nobody ever accepted it and no person moved it (#207). Removing them strands reviewers that missed a review request. Assignments preserve the acting peer. A peer releases only its own conductor hold; operator release and manual, quota and recovery holds remain separate paths.
|
|
9
9
|
- The PII screen (#198) runs in `propose` before anything else reads the task (no board row, envelope, triage or brief before its verdict), and `unknown` is PII. A call that screens free text (`done`, `review`, `remember`) screens only after its own checks pass, and `done` and `review` run them again after the awaited screen, because the task can change meanwhile. A history entry marked `withheld` stays withheld in public views, completed notices and review requests, whatever `nameable()` says; `ahub ask` shows it only on campus, like a note that matches a PII pattern.
|
|
10
|
+
- `taskOp` (`src/hub/daemon.ts`) lists full PII rows only when `onPrem` holds: the in-process local worker inside a PII turn (#230). Every other caller, the local worker's ordinary turns included, gets stubs.
|
|
11
|
+
- A guard in `tasks.ts` or `conductor.ts` passes `preEffect` (throws `PreEffectToolRefusal`, `src/hub/tool-refusal.ts`) only before the operation's first board or hold write; a throw after a write, a readback failure or a wrapped error stays a plain `Error` (#254). The Pi side of the rule is in `adapters.md`.
|
|
10
12
|
- Supervision reasons use structured enums, never history notes, check output or approval titles. Round signatures survive restart; later summaries count only newly joined tasks.
|
|
@@ -4,6 +4,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
4
4
|
|
|
5
5
|
- Tests that are not about batching build the bus with `batchMs: 0`; with the default 15 s window a lone status envelope looks like a lost message.
|
|
6
6
|
- In Bun 1.3.14 a test timeout that fires while `Bun.spawnSync` runs can start the next test inside spawnSync's own event loop, where another spawnSync then spins at full CPU for good (trigger not pinned down; see README). Bun 1.4.2 still runs the next test inside the outer spawnSync's event loop. `scripts/check.sh` runs tests with a 20 s timeout and under `scripts/hang-watch.sh`; a test that spawns and reads the process table gets a timeout of its own.
|
|
7
|
-
-
|
|
8
|
-
- Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers
|
|
7
|
+
- The Switchyard sidecar, sandboxed commands and the MLX server get a fixed environment (`PATH`, `HOME` and their own keys), so test knobs for their fakes travel in a wrapper script or argv, not in `process.env`; native peer children inherit the hub's environment minus the agent markers (`peerChildEnv`).
|
|
8
|
+
- Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers and without an `ahub claude` session's hub variables (`AGENTHUB_PROJECT_DIR`, `AGENTHUB_STATE_DIR`, `AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`); a new variable a native session exports and a fixture child reads joins that `env -u` list in `scripts/check.sh`. Never weaken production marker detection to make them pass.
|
|
9
|
+
- A free-port probe binds `hostname: "127.0.0.1"`, matching the loopback server that takes the port (#232).
|
|
9
10
|
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. Keep the six `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs this in the required Linux `seeded guards` job after the ordinary check, separately from `scripts/check.sh`.
|
package/docs/bench.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Benchmarks
|
|
2
|
+
|
|
3
|
+
`ahub bench` runs a fixed set of tasks against the agents attached to a hub and compares configurations (#251). A run
|
|
4
|
+
records each attempt's outcome and the #247 measures of the task (tokens, time, review rounds, rework), never the
|
|
5
|
+
suite's text, the verify command's output or the agents' words. CooperBench (`docs/cooperbench.md`) stays the
|
|
6
|
+
rigorous native study of this repository; `ahub bench` is for any project.
|
|
7
|
+
|
|
8
|
+
## A bench project
|
|
9
|
+
|
|
10
|
+
Each attempt resets the work tree, so a run refuses every project but one kept for benchmarks: a git repository whose
|
|
11
|
+
`.agenthub/config.json` has
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "bench": { "enabled": true } }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
and that passes these checks before anything changes:
|
|
18
|
+
|
|
19
|
+
- the project is the root of its repository (a reset acts on the whole repository);
|
|
20
|
+
- nothing under `.agenthub/` is tracked, in the tree or in any ref the suite names (a checkout would replace the hub's own
|
|
21
|
+
files): `git rm -r --cached .agenthub`, ignore it, commit;
|
|
22
|
+
- every ref the suite names is a commit (it is pinned to that commit for the whole run, so a branch an agent moves
|
|
23
|
+
later changes nothing);
|
|
24
|
+
- the tree is clean (`.agenthub/` aside).
|
|
25
|
+
|
|
26
|
+
The reset also deletes ignored files (`git clean -x`): keep the suite file, hidden verify scripts and anything else the
|
|
27
|
+
run needs outside the tree, and let `setup` recreate what an attempt needs (dependencies, local settings).
|
|
28
|
+
|
|
29
|
+
Between attempts the runner checks out the task's pinned commit (`git checkout --force --detach <commit>`), runs
|
|
30
|
+
`git clean -ffdx -e /.agenthub` and confirms the tree is clean; a step that fails says why. A run that finishes returns
|
|
31
|
+
to the branch (or commit) it started on; a run that stops leaves the tree as its last attempt left it. Either way the
|
|
32
|
+
runner says so if a branch or tag the suite names moved or was deleted during the run (a ref given as a hash, relative
|
|
33
|
+
to another such as `main~1`, or by a name that is both a branch and a tag is not tracked).
|
|
34
|
+
|
|
35
|
+
## Suite file
|
|
36
|
+
|
|
37
|
+
JSON, kept outside the bench project's tree (a relative path is read from the directory you run `ahub` in). Unknown
|
|
38
|
+
fields, a missing `verify` and duplicate ids are refused with the task and the field named.
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"name": "parser-fixes",
|
|
43
|
+
"tasks": [
|
|
44
|
+
{ "id": "csv-quote", "title": "Fix quoted commas in src/csv.ts", "detail": "parse('\"a,b\",c') must give two fields",
|
|
45
|
+
"class": "implement", "owner": "codex", "ref": "3f2c1aa", "setup": "bun install --frozen-lockfile",
|
|
46
|
+
"verify": "bun test test/csv.test.ts", "timeout_s": 1800 }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Field | Meaning |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `name` | the suite's label, kept in the records |
|
|
54
|
+
| `id` | the task's label, kept in the records |
|
|
55
|
+
| `title`, `detail` | what the hub proposes (never recorded) |
|
|
56
|
+
| `class` | the task class; `implement` when absent, since a hub without a model cannot triage |
|
|
57
|
+
| `owner` | the peer to propose it to; absent lets routing choose |
|
|
58
|
+
| `ref` | the commit the attempt starts from |
|
|
59
|
+
| `setup` | a shell command before the task is proposed (bounded, 5 min) |
|
|
60
|
+
| `verify` | a shell command run in the tree the agents left; exit 0 is a pass (bounded, 10 min) |
|
|
61
|
+
| `timeout_s` | 10 to 86400 seconds from the start of the attempt to approval |
|
|
62
|
+
|
|
63
|
+
`setup` and `verify` run in their own process group: at the bound, on Ctrl-C and when the command exits, everything in
|
|
64
|
+
that group is stopped, so nothing keeps writing into the next attempt's tree (a process that leaves the group, as a
|
|
65
|
+
daemon does, is not followed).
|
|
66
|
+
|
|
67
|
+
`name`, `id` and the arm label are stored; keep them free of anything private.
|
|
68
|
+
|
|
69
|
+
## Running
|
|
70
|
+
|
|
71
|
+
Start the peers of the configuration under test (`ahub claude`, `ahub codex`, `ahub pi`, their models, permission
|
|
72
|
+
modes and `routing.toml`), then:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ahub bench run ../suites/suite.json --arm baseline --repeat 5
|
|
76
|
+
ahub bench run ../suites/suite.json --arm ripwire-on --repeat 5 --tasks b,a,c # --tasks also sets the order
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Only a person can run a suite (agent shells are refused). Each attempt resets the tree to `ref`, runs `setup`, proposes
|
|
80
|
+
the task as the console does, waits until it is approved or `timeout_s` passes, then runs `verify`. Outcomes:
|
|
81
|
+
|
|
82
|
+
- `pass`: approved and `verify` exited 0;
|
|
83
|
+
- `fail`: approved and `verify` exited non-zero or ran out of time (`verify timeout`);
|
|
84
|
+
- `timeout`: not approved in time. The run stops there, because an agent may still be working in the tree the next
|
|
85
|
+
attempt would reset; settle the open task on the board before the next run;
|
|
86
|
+
- `error`: the reset, `setup` or the proposal failed, the hub stopped, or a person interrupted the run (the reason is
|
|
87
|
+
one of a closed list).
|
|
88
|
+
|
|
89
|
+
Ctrl-C stops the run at its next check, also inside `setup` or `verify`: the attempt in progress is recorded as
|
|
90
|
+
`error` (`interrupted`), a task still open on the board is named, and the run reads `stopped`. An interrupt after
|
|
91
|
+
`verify` has finished keeps that attempt's outcome and drops its measures. A run whose runner died without its end
|
|
92
|
+
record reads `interrupted`.
|
|
93
|
+
|
|
94
|
+
## Store
|
|
95
|
+
|
|
96
|
+
`~/.agenthub/bench/<run id>.jsonl` (directory 0700, files 0600), one file per run, shared by every bench project on the
|
|
97
|
+
machine. Schema `agent-hub.bench/v1`:
|
|
98
|
+
|
|
99
|
+
- `run` header: suite name and SHA-256 of the suite file, arm, fingerprint (the peers attached at the start, with the requested model and
|
|
100
|
+
permission mode where the hub's status reports them: today the model for Pi only; the SHA-256 of `routing.toml`; the
|
|
101
|
+
hub version), task order, repeats, the runner's process.
|
|
102
|
+
- `attempt`: task id, repeat, outcome, error reason, verify exit code, hub task id, start, end, duration, and, for an
|
|
103
|
+
approved task, its #247 measures (tokens, wall and active time, review rounds, changes requested, failed checks, first
|
|
104
|
+
pass, turns, files changed, models). They are read once the turns started since the task's proposal have ended (the
|
|
105
|
+
runner waits only while their peers are still busy or paused, at most 10 minutes), so the turn that approved the task is
|
|
106
|
+
counted; that wait is not part of the attempt's duration. A timeout or an error has none, nor has an attempt whose
|
|
107
|
+
wait was interrupted, ran out with the turn still open, or could not read the hub's status while it was open.
|
|
108
|
+
- `end`: end time, and `stopped` (`timeout`, `interrupted`, `error`) when the run did not finish.
|
|
109
|
+
|
|
110
|
+
## Reading results
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
ahub bench list # runs on this machine
|
|
114
|
+
ahub bench status # the run in progress
|
|
115
|
+
ahub bench report <run> [--json] # per task and overall
|
|
116
|
+
ahub bench compare baseline ripwire-on [--suite parser-fixes] [--json]
|
|
117
|
+
ahub bench export --format csv
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Measures: pass rate and median wall time (over attempts that did not end in `error`), first-pass rate, median tokens
|
|
121
|
+
and mean rework (over approved attempts). `compare` takes run ids or arm labels (an arm means all its runs that are
|
|
122
|
+
over, a run a timeout stopped included, since each attempt carries its own outcome), sets the first as the baseline and
|
|
123
|
+
gives each other arm's difference with a 95% bootstrap interval (1000 resamples, fixed seed, so the same records give
|
|
124
|
+
the same interval). Below 5 attempts counted for a measure in either arm, its difference is marked inconclusive. Runs of
|
|
125
|
+
different suites, or of different versions of one suite file, are not compared unless you pass `--mixed`; name runs by
|
|
126
|
+
id to compare within one version. Agents may
|
|
127
|
+
read all of this. CSV columns: run, suite, arm, task, repeat, outcome, error, verifyExit, hubTask, startedAt, endedAt,
|
|
128
|
+
ms, tokens, wallMs, activeMs, reviewRounds, changesRequested, checkFailed, firstPass, turns, filesChanged, models.
|
|
129
|
+
|
|
130
|
+
The dashboard's Benchmarks section shows the latest runs with their progress (the attempt in progress, elapsed time and
|
|
131
|
+
attempts left while one runs) and, per suite with two or more arms, a bar per arm and measure with its number as text.
|
|
132
|
+
|
|
133
|
+
## A fair comparison
|
|
134
|
+
|
|
135
|
+
- Change one thing per arm (a model, a permission mode, an add-on, a routing rule) and keep the suite, the refs and the
|
|
136
|
+
peers otherwise the same; the fingerprint records the routing file, the hub version and what the status reports of
|
|
137
|
+
each peer, so note the rest (models, permission modes, add-ons) in the arm label.
|
|
138
|
+
- Repeat: agents are not deterministic. Five attempts per task and arm is the floor for a comparison; more narrows the
|
|
139
|
+
interval.
|
|
140
|
+
- Randomize the order per run with `--tasks` (for example `--tasks "$(jq -r '.tasks[].id' ../suites/suite.json | shuf | paste -sd, -)"`)
|
|
141
|
+
and interleave the arms' runs, so quota, time of day and provider load do not line up with one arm.
|
|
142
|
+
- Keep `verify` independent of the agents' own tests where you can: a hidden test the agents never see measures the
|
|
143
|
+
outcome, not their opinion of it.
|
package/docs/events.md
CHANGED
|
@@ -35,8 +35,9 @@ marked `private: true`, and PII tasks `pii: true`.
|
|
|
35
35
|
| `turn_end` | `peer`, `turn`, `ms`, `tokens` (when the adapter reported any during the turn), `files` and `snapshotMs` (when snapshots are on: how many files the turn changed, and the time both snapshots took); optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`) frozen at turn start, `pii: true` for an attributed PII task |
|
|
36
36
|
| `tokens` | `peer`, `n` (tokens added since the previous report), optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`), `pii: true` for an attributed PII task |
|
|
37
37
|
| `usage` | `peer`, `source`, opaque `id`, optional `measuredAt` (provider/source time), requested/served model and provider labels, and any provider-reported input/output/cache/total counters; optional numeric `task`, `attribution` (`delivery`, `single_open`, `unattributed`), `pii: true` for an attributed PII task. Missing counters stay unknown. |
|
|
38
|
-
| `task` | `id`, `event` (the board history event, e.g. `proposed`, `assigned`, `done`, `check failed`, `blocked`, `ready`), `by`, `state`, `owner`, `reviewer`, `class`, `pii` |
|
|
38
|
+
| `task` | `id`, `event` (the board history event, e.g. `proposed`, `assigned`, `done`, `check failed`, `blocked`, `ready`), `by`, `state`, `owner`, `reviewer`, `class`, `pii`, and `reason` when the board recorded a move reason (`manual`, `budget`, `offline`, `idle`, `rejections`, `declined`, `inference_failed`, `delivery_failed`) |
|
|
39
39
|
| `overlap` | `task`, `owner`, `others` (`task`, `owner`, `paths`, and `symbols` when plans name the same symbol; a name that matches a PII pattern is left out, so either list can be empty), the structured twin of the console notice |
|
|
40
|
+
| `permission_mode` | `peer`, `from`, `to` (`ask`, `ask-when-needed`, `never-ask`): a person set a peer's permission mode (`from` equals `to` when it was already in force) or confirmed a never-ask default (issue #242) |
|
|
40
41
|
| `pii_screen` | `task` (absent for a budget hand-off or a note not about a task), `item` (`task`, `summary`, `review note`, `handoff`, `note`), `label` (`pii`, `clear`), `source` (`regex`: a pattern matched and no call was made; `screen`; `unknown`: no verdict, handled as PII), `category` (`name`, `student_id`, `phone`, `address`, `grade`, `health`, `other`), `miss` (why there was no verdict: `too long`, `off campus`, `timeout`, `unreadable`, `failed`), `ms` (absent for `regex`): one PII screen verdict while `signals.pii_screen = "local"` (issue #198); never the text |
|
|
41
42
|
| `quota` | `peer`, `windows` (`id`, `used`, `resetsAt`), `hard`, `measuredAt` (when the reading was taken, if not when it arrived: Claude's numbers come through a file) |
|
|
42
43
|
| `conflict` | `peer`, `task` (its task in progress, when it had one), `other` (the other owner's open task), `owner`, `paths` (names that match a PII pattern left out, so it can be empty), `concurrent` (another peer worked during the turn) |
|