@staix/agent-hub 0.30.0 → 0.31.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 +39 -0
- package/README.md +13 -7
- package/docs/agent-notes/README.md +1 -0
- package/docs/agent-notes/adapters.md +5 -2
- package/docs/agent-notes/budget.md +1 -0
- package/docs/agent-notes/bus.md +9 -4
- package/docs/agent-notes/daemon.md +15 -8
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +7 -3
- package/docs/agent-notes/tests.md +6 -4
- package/docs/design/adapters.md +52 -0
- package/docs/design/agent-hub-architecture.md +46 -0
- package/docs/design/bus.md +139 -1
- package/docs/design/task-board.md +82 -0
- package/docs/dogfood.md +22 -4
- package/docs/events.md +3 -0
- package/docs/memory.md +17 -1
- package/docs/operations.md +307 -78
- package/docs/profiles.md +35 -0
- package/docs/quickstart.md +18 -5
- package/docs/security.md +154 -60
- package/docs/specs/2026-09-19-agent-hub-design.md +84 -14
- package/docs/specs/2026-09-19-multi-project-design.md +7 -2
- package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
- package/docs/verification/2026-11-05-0.31.0.md +182 -0
- package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
- package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +257 -0
- package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
- package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
- package/docs/verification/claude-shell-wrapper-native.py +1 -1
- package/docs/verified.json +14 -14
- package/package.json +3 -4
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +4393 -3609
- package/src/adapters/acp.ts +5 -4
- package/src/adapters/channel-peer.ts +72 -3
- package/src/adapters/claude-channel-evidence.ts +52 -0
- package/src/adapters/claude-channel.ts +61 -18
- package/src/adapters/claude-implementer.ts +29 -7
- package/src/adapters/claude-print-child.ts +12 -2
- package/src/adapters/codex-appserver.ts +13 -12
- package/src/adapters/compat.ts +3 -3
- package/src/adapters/definitions/builtin-metadata.ts +13 -13
- package/src/adapters/definitions/qwen.ts +1 -0
- package/src/adapters/local-worker.ts +61 -18
- package/src/adapters/pi.ts +48 -6
- package/src/adapters/registry-types.ts +12 -0
- package/src/adapters/registry.ts +2 -0
- package/src/adapters/reviewer-print.ts +50 -11
- package/src/broker/server.ts +44 -13
- package/src/broker/store.ts +38 -8
- package/src/cli/answer-args.ts +50 -0
- package/src/cli/attention.ts +17 -2
- package/src/cli/backlog-return-args.ts +37 -0
- package/src/cli/console-question.ts +110 -0
- package/src/cli/console-state.ts +77 -16
- package/src/cli/console.ts +54 -1
- package/src/cli/handoff-args.ts +15 -5
- package/src/cli/handoff-output.ts +15 -2
- package/src/cli/help.ts +8 -5
- package/src/cli/identity.ts +13 -3
- package/src/cli/main.ts +128 -16
- package/src/cli/memory.ts +107 -3
- package/src/cli/output.ts +29 -6
- package/src/cli/pet.ts +4 -4
- package/src/cli/results-args.ts +30 -0
- package/src/cli/runtime-version-warning.ts +50 -0
- package/src/cli/status-lines.ts +41 -1
- package/src/cli/tail-render.ts +1 -0
- package/src/cli/upgrade-interactive.ts +14 -2
- package/src/hub/action-class.ts +9 -1
- package/src/hub/board.ts +369 -18
- package/src/hub/bus.ts +493 -53
- package/src/hub/completion-detail.ts +50 -0
- package/src/hub/completions-store.ts +168 -0
- package/src/hub/completions.ts +120 -0
- package/src/hub/config-apply.ts +7 -5
- package/src/hub/config-reload.ts +10 -1
- package/src/hub/control-client.ts +40 -4
- package/src/hub/daemon.ts +1569 -134
- package/src/hub/delivery-failure.ts +14 -0
- package/src/hub/delivery-hints.ts +14 -4
- package/src/hub/delivery-journal.ts +751 -42
- package/src/hub/delivery-superseded.ts +4 -1
- package/src/hub/envelope.ts +3 -3
- package/src/hub/events.ts +6 -0
- package/src/hub/handoff.ts +145 -6
- package/src/hub/hub-tools.ts +56 -7
- package/src/hub/manager.ts +59 -3
- package/src/hub/memory-policy.ts +44 -1
- package/src/hub/memory-sync-policy.ts +109 -0
- package/src/hub/memory-sync-state.ts +145 -0
- package/src/hub/no-writes.ts +1 -1
- package/src/hub/operational-settings.ts +11 -2
- package/src/hub/operational-view.ts +12 -1
- package/src/hub/peers.ts +25 -2
- package/src/hub/planning.ts +6 -1
- package/src/hub/question-desk.ts +201 -0
- package/src/hub/question-detach.ts +6 -0
- package/src/hub/questions.ts +260 -0
- package/src/hub/research-evidence.ts +106 -0
- package/src/hub/restart.ts +14 -0
- package/src/hub/review-anchor.ts +181 -0
- package/src/hub/review-brief.ts +13 -0
- package/src/hub/review-comments.ts +115 -0
- package/src/hub/review-findings.ts +7 -2
- package/src/hub/review-material.ts +76 -0
- package/src/hub/routing.ts +3 -1
- package/src/hub/task-sweep.ts +2 -0
- package/src/hub/tasks.ts +577 -63
- package/src/memory/capture.ts +5 -2
- package/src/pet/config.ts +25 -13
- package/src/rooms/link.ts +209 -21
- package/src/rooms/remote-peer.ts +186 -19
- package/src/rooms/service.ts +338 -22
- package/src/rooms/store.ts +437 -65
- package/src/rooms/wire.ts +24 -6
- package/src/ui/index.html +138 -7
- package/src/ui/needs.ts +10 -4
- package/src/ui/plugin-questions.ts +56 -0
- package/src/ui/plugin-results.ts +81 -0
- package/src/ui/plugin.ts +207 -27
- package/src/pet/window.html +0 -114
- package/src/pet/window.jxa.js +0 -122
- package/src/pet/window.ts +0 -154
package/docs/profiles.md
CHANGED
|
@@ -25,6 +25,41 @@
|
|
|
25
25
|
- Experiment attempts recorded on a study unit are read with `ahub study experiments --campaign <id>` (or `--attempt`, or `--plan` with `--baseline` and `--variant`) and `hub_experiment_show`. Each value shows its source: `declared` is the agent's own word, and only `host-observed`, `runner-attested` or `person-attested` rows are evidence, none of which proves the science. A rerun by a person attaches through an existing check, a hashed output file or a registered runner's signed receipt, as the checklist in `docs/operations.md` ("Experiment readback") says; without one the experiment stays declared and its sealing unverified. The synthetic CPU-only fixture is `test/research-experiments-fixture.test.ts`; no account, GPU or native model has run an experiment here.
|
|
26
26
|
- The deterministic three-source, two-peer example (`test/study-daemon.test.ts`, "three-source, two-peer example"): one contract names sources S0, S1 and S2 and two discovery units, `alpha` (S0 and S1, owned by codex) and `beta` (S2, an independent check, owned by kimi). After `ahub study approve` both units read `none` and `0/2 covered`, `0/1 covered`. A handoff for S0 only moves `alpha` to `incomplete` with `1/2 covered`. A handoff for both S0 and S1, and one for S2, make both `complete` and the discovery round complete. Another peer's handoff for `alpha` is refused. This runs with fake peers: it shows the hub's rules, not that a native model follows the guidance.
|
|
27
27
|
|
|
28
|
+
# Claim evidence links (#931 T1)
|
|
29
|
+
|
|
30
|
+
An active owner of an in-progress research-profile task can record
|
|
31
|
+
`hub_evidence_record`. The task need not belong to a campaign. Researcher role
|
|
32
|
+
guidance grants no extra authority, and the tool is absent from dev-profile and
|
|
33
|
+
read-only publication. T1 refuses private-turn, PII-task and private-source writes.
|
|
34
|
+
|
|
35
|
+
Read the task with `hub_task_show` or `ahub task show --json`, then copy its
|
|
36
|
+
text-free `claims[].claimId` and `claims[].claimRevision` into the record. The
|
|
37
|
+
view also gives each slot. Refresh that view after claims change; clients never
|
|
38
|
+
need to reproduce the internal plan encoding or hash. PII and dev tasks expose
|
|
39
|
+
no such bindings.
|
|
40
|
+
|
|
41
|
+
Claim references use #901's canonical identity: `claim:<task id>:<created>:<slot>`
|
|
42
|
+
with a 1-based slot, and `claimRevision` equal to the plan's existing ordered-list
|
|
43
|
+
hash (`claimListId`). Any edit or reordering of that list stales every bound link;
|
|
44
|
+
restoring the exact list keeps #901's existing semantics. No second claim or
|
|
45
|
+
verdict store is introduced. A per-claim revision is a later upgrade if the
|
|
46
|
+
conservative list-level binding invalidates too much work.
|
|
47
|
+
|
|
48
|
+
The flat record carries task/claim/source IDs, raw and projection SHA-256 hashes,
|
|
49
|
+
projection version, `locator: {start, end}` and `relation` (`supports`,
|
|
50
|
+
`contradicts` or `context`). The host stamps `recordedBy`. Initial revision is 1;
|
|
51
|
+
an identical replay writes nothing, while a changed link names its ID and next
|
|
52
|
+
revision. Revisions remain append-only. The public receipt contains only link ID,
|
|
53
|
+
revision and task ID; arguments and results are excluded from memory capture.
|
|
54
|
+
|
|
55
|
+
T1 checks registered source identity/raw hash and stores a proposed zero-based,
|
|
56
|
+
half-open scalar-value range of at most 4096. Projection hashes/version and
|
|
57
|
+
relation labels remain declared. It stores no claim text, excerpt, origin/path or
|
|
58
|
+
invented page coordinates. Source/Unicode locator resolution and privacy
|
|
59
|
+
revalidation are T2; `hub_evidence_read`, study evidence/check surfaces and
|
|
60
|
+
synthesis-readiness binding remain later slices. Neither a matching citation key
|
|
61
|
+
nor an evidence relation creates a person verdict or proves entailment.
|
|
62
|
+
|
|
28
63
|
# General project example
|
|
29
64
|
|
|
30
65
|
- Initialize with `ahub init --profile general`.
|
package/docs/quickstart.md
CHANGED
|
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
|
|
|
17
17
|
Or use the matching GitHub release:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.31.0
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -29,7 +29,16 @@ For managed launchers, two-agent task/review work, approvals, graceful shutdown,
|
|
|
29
29
|
and upgrade or crash recovery, see the [operations guide](operations.md). To
|
|
30
30
|
move a running project to a newer release later, run `ahub upgrade` in a
|
|
31
31
|
terminal in that project: it reviews the plan with you, applies it and follows
|
|
32
|
-
it; other projects keep their version.
|
|
32
|
+
it; other projects keep their version. It then names the `bun add -g` and
|
|
33
|
+
`ahub setup` step when your global CLI or plugin is older than the new daemon.
|
|
34
|
+
|
|
35
|
+
## Upgrading to 0.31.0
|
|
36
|
+
|
|
37
|
+
The control protocol stays at 16 through 17 and hub.db keeps schema version 2, but this release adds the nullable `deliveries.failure_cause` diagnostic column. N-1 recovery ignores the extra snapshot key, and older rows without it remain unknown. The exact `@staix/agent-mem` pin moves from 0.20.1 to 0.21.0; upgrading does not activate placement or migrate a memory home. `ahub memory declined` now lists complete bounded physical IDs and safe rule categories when inspection succeeds. Automatic v3 sync remains unsupported until agent-mem#281 provides activation and fresh host attestation. The [N-1 statement](../CHANGELOG.md) distinguishes the hub 0.30.0 fixture from agent-mem's own 0.20.3 compatibility proof.
|
|
38
|
+
|
|
39
|
+
Two defaults are new. An accepted channel delivery that is never confirmed moves to needs-review after 10 minutes (`bus.accepted_settle_ms`). A Claude session that only inherits `AGENTHUB_CHANNEL=1`, without the native channel launch, attaches tools-only and reads its messages with `hub_inbox`; `ahub status` names the mode. A 0.30.0 hub sending a handoff here is refused when the spec issue already has an open task, until it is upgraded and passes `--duplicate-ok`.
|
|
40
|
+
|
|
41
|
+
`ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.31.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
|
|
33
42
|
|
|
34
43
|
## Upgrading to 0.30.0
|
|
35
44
|
|
|
@@ -37,6 +46,8 @@ The hub now exact-pins `@staix/agent-mem` 0.20.1 (#1136). A setup receipt writte
|
|
|
37
46
|
|
|
38
47
|
Nothing else changes until you use something new. `ahub worktrees` lists the project's live and settled worktrees (settled: its PR merged or closed with no newer work, or quiet for `worktrees.quiet_days`, default 7), and `ahub worktrees tidy` previews which settled ones are safe to remove, with the reasons for each refused row; only you remove them, with `ahub worktrees tidy --confirm <fingerprint>` in your own terminal, which runs `git worktree remove` and `git branch -d` and never forces (#1057). The dashboard's Agents panel gets a Worktrees section with Park, Pin and "Refresh PR states", and an inbox alert fires above `worktrees.alert_over` (default 10). Pi and the local worker name their own session with `hub_set_title`; `ahub title <peer> <text...>` or the console's `t` editor replaces the name (#1157). A plan's `sources` must now name registered `src:<16 hex>` ids (`ahub source list`), or the proposal or acceptance is refused (#1151). Run agy with `AGY_CLI_DISABLE_AUTO_UPDATE=1`: without it agy replaced its own binary during a launch (#1140).
|
|
39
48
|
|
|
49
|
+
`ahub upgrade` does not update the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.30.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon.
|
|
50
|
+
|
|
40
51
|
## Upgrading to 0.29.0
|
|
41
52
|
|
|
42
53
|
Upgrade every `amem` that shares your memory home together with the hub: the hub now exact-pins `@staix/agent-mem` 0.20.0, and an `amem` 0.19.0 hook or CLI cannot drain 0.20.0's new hook spool records and can retire them after seven days (#1131). Formats, shards, candidates and indexes are unchanged.
|
|
@@ -177,7 +188,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
|
|
|
177
188
|
|
|
178
189
|
Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
|
|
179
190
|
|
|
180
|
-
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.
|
|
191
|
+
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.21.0. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
|
|
181
192
|
|
|
182
193
|
From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
|
|
183
194
|
|
|
@@ -242,6 +253,8 @@ or set `OMNIROUTE_API_KEY`. The `.agenthub/routing.toml` that `ahub init` writes
|
|
|
242
253
|
| `ahub status` | peers, queues, pauses; `ahub budget` for quota windows |
|
|
243
254
|
| `ahub usage [--by peer\|task\|issue\|pr\|release\|day\|model]` | tokens per group, each number with its source; `ahub usage limits` for pauses and resets |
|
|
244
255
|
| `ahub worktrees` | live and settled worktrees; `ahub worktrees tidy` previews what is safe to remove, and only you confirm it with `--confirm <fingerprint>` |
|
|
256
|
+
| `ahub answer` | the questions agents asked you with `hub_ask`; `ahub answer <set> <question>=<option>` answers one set, or press `A` in `ahub console` |
|
|
257
|
+
| `ahub results [--unread]` | what the runs you started left behind, unread ones marked `*`; `--read <id>` or `--read-all` marks them read, listing never does |
|
|
245
258
|
| `ahub agents` | which installed agent CLI versions the hub accepts; `ahub agents trust <id> <version>` accepts an untested one on this machine |
|
|
246
259
|
| `ahub memory profile-check` | a person's read-only check of running memory bindings in the current registry |
|
|
247
260
|
| `ahub ask <question>` | what the board, shared memory and the log say, with the ids the answer rests on |
|
|
@@ -253,7 +266,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
|
|
|
253
266
|
|
|
254
267
|
## Shared memory
|
|
255
268
|
|
|
256
|
-
The hub exact-pins the first-party `@staix/agent-mem` 0.
|
|
269
|
+
The hub exact-pins the first-party `@staix/agent-mem` 0.21.0 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
|
|
257
270
|
|
|
258
271
|
Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
|
|
259
272
|
|
|
@@ -265,7 +278,7 @@ benchmark runs every second. No build step, web server command or browser
|
|
|
265
278
|
extension is needed.
|
|
266
279
|
|
|
267
280
|
Use the page to pause/resume a peer, send a console message, propose/assign tasks,
|
|
268
|
-
|
|
281
|
+
allow/deny agent permission requests and mark results read. Local-worker and Pi requests show no tool
|
|
269
282
|
contents: inspect them with `ahub tail`, then use `ahub permit <id> <option>` to
|
|
270
283
|
allow. They can be denied on the page. PII task details remain available only via
|
|
271
284
|
`ahub task show <id>` in the terminal. The page also starts a peer in its start
|
package/docs/security.md
CHANGED
|
@@ -26,7 +26,7 @@ agent-hub connects agents that can each run commands. This page says what the hu
|
|
|
26
26
|
|
|
27
27
|
## Native memory authority and profile selection
|
|
28
28
|
|
|
29
|
-
The portable core is the exact-pinned first-party `@staix/agent-mem` 0.
|
|
29
|
+
The portable core is the exact-pinned first-party `@staix/agent-mem` 0.21.0 package; it does not replace the hub's caller identity, PII policy or task-write authority. Deny, hardened git, real paths and process identity are single-sourced in its Node-safe platform entry. Current project privacy is applied by the hub before promotion and again before model-facing reads. Standalone person reads are unscreened, and the standalone `--policy-free` acknowledgement grants no hub or peer authority.
|
|
30
30
|
|
|
31
31
|
`hub_memory_search`, `hub_memory_show` and `hub_memory_graph` take the project from the daemon, require `memory_read`, refuse private turns and validate UTF-8 input before logs or storage work. Complete records and graph metadata are screened before counts and truncation. Search is at most 20 lines/4 KiB (default 8); show accepts at most 8 ids and returns at most 8 KiB; graph is at most 120 nodes/240 edges/6 KiB (default 60 nodes), with no edge lacking an endpoint. Unknown, ambiguous and withheld ids share one neutral refusal. Arguments and results are excluded from hub-managed capture; this does not govern a vendor's independent transcript or memory hooks.
|
|
32
32
|
|
|
@@ -165,6 +165,20 @@ enrollment and X4 privacy/framing/role boundaries are in their own sections belo
|
|
|
165
165
|
traffic metadata, availability, malicious owners and same-user compromise remain
|
|
166
166
|
outside the encryption boundary.
|
|
167
167
|
|
|
168
|
+
Room presence uses a signed `ephemeral: true` header (#1204 T1). This reveals
|
|
169
|
+
presence traffic class to the blind broker, not peer state or body text. Only the
|
|
170
|
+
latest sender observation is retained for connected, ready recipients; offline
|
|
171
|
+
recipients and broker restart do not retain presence. Durable queue limits and
|
|
172
|
+
receiver replay/admission checks are unchanged. A `queue_full` storage refusal
|
|
173
|
+
names the verified frame digest; local bounded backoff retries that same sealed
|
|
174
|
+
frame and grants no completion authority.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
The ephemeral advertisement is a transport capability, not authentication or
|
|
178
|
+
completion authority. Old brokers receive the legacy frame shape. The broker withholds ephemeral frames from older member
|
|
179
|
+
hubs until they upgrade; chat
|
|
180
|
+
frames remain compatible. See [room wire compatibility](design/bus.md#room-wire-compatibility-1204-t1).
|
|
181
|
+
|
|
168
182
|
## What is not defended
|
|
169
183
|
|
|
170
184
|
- **A malicious local process running as you.** It can read the control token, `hub.db`, the project and your home directory without the hub's help. The token keeps web pages and other users out, not your own processes.
|
|
@@ -362,7 +376,7 @@ as that peer in tools mode. Messages and task changes retain that actor;
|
|
|
362
376
|
capability. Conflicting or malformed markers fail closed. Refusals name only the marker variable names actually present, never their values. There is no `--as-user`.
|
|
363
377
|
By default, console operations are refused before connecting, including permission answers
|
|
364
378
|
and permission modes, agent version trust, budget overrides, role assignment, `--as-user`, recovery
|
|
365
|
-
operations, the interactive terminals (`watch`,
|
|
379
|
+
operations, the interactive terminals (`watch`, a launcher's person-only flags), and `ask`
|
|
366
380
|
(#933 keeps the person's class to trust, money, attribution and interactive terminals).
|
|
367
381
|
For live verification explicitly authorized by the user, an agent may invoke
|
|
368
382
|
`ahub --live-test [--project <path|id>] <command> [args...]`. The flag delegates
|
|
@@ -389,8 +403,8 @@ messages, proposals and reviews. Peer starts, pause and resume may delegate
|
|
|
389
403
|
operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
|
|
390
404
|
person-authored planning/release/memory/goal writes, SDLC record, spec and plan-departure acceptance, release authorization, design-spec stamps (`ahub specs stamp`), agent version trust, benchmark run,
|
|
391
405
|
`ahub recovery reattach`, a `--fresh-session` dispose that would replace a repaired
|
|
392
|
-
session's conversation (or whose scope cannot be read),
|
|
393
|
-
|
|
406
|
+
session's conversation (or whose scope cannot be read), `ahub answer` with an
|
|
407
|
+
operand and the `ahub results` receipt forms are refused; presentation flags such as `--color` or `--verbose` before
|
|
394
408
|
the recovery action do not hide it from this check. No argument text is copied into this audit.
|
|
395
409
|
Delegated dashboard restrictions are bound to its server-issued ticket/session:
|
|
396
410
|
operational settings/start/stop controls remain, but message, proposal and
|
|
@@ -465,9 +479,10 @@ start (Jcode). Memory changes and the other `goal` changes stay the person's
|
|
|
465
479
|
while amem records their actor as the user. `task unassign` is a conductor form;
|
|
466
480
|
it keeps the conductor as the board actor. `task cancel` remains separately
|
|
467
481
|
available through `hub_task_cancel` only on the caller's own eligible task
|
|
468
|
-
(#933),
|
|
469
|
-
|
|
470
|
-
`
|
|
482
|
+
(#933), and for the conductor only on an unaccepted task another project handed
|
|
483
|
+
off (#1175), not as a general conductor operation; `task role`, `task label` and
|
|
484
|
+
`ahub deliverables` retain their own-task checks. `models start|stop` remains
|
|
485
|
+
Console. `restart --yes`,
|
|
471
486
|
`upgrade --yes --to <exact-version>` and `recovery resume|abort` have the
|
|
472
487
|
separate continuity proof described below. Each conductor operation is admitted
|
|
473
488
|
after a recheck of role and project membership; effectful operations and history
|
|
@@ -557,7 +572,7 @@ Please report vulnerabilities privately through GitHub's "Report a vulnerability
|
|
|
557
572
|
- The exchange creates a separate random session, valid for one hour without renewal, in an `HttpOnly; SameSite=Strict; Path=/` cookie named for this listener's port. Sessions and tickets live only in memory and die with the daemon. The page itself keeps one more cookie, `agent-hub-theme` (issue #246): script-set, `SameSite=Strict`, kept for a year for the whole `127.0.0.1` host, and read back only as one of `system`, `light` or `dark`. A second script-set cookie, `agent-hub-office` (issue #264), keeps the Office view choice under the same attributes and is read back only as `off`, `flat` or `isometric`. The #391 view split adds two more script-set cookies under the same attributes: `agent-hub-view`, read back only as one of the seven view names, and `agent-hub-column-focus`, read back only as one of the five board columns or empty. HTTP is loopback-only; the cookie is not a substitute for the origin checks.
|
|
558
573
|
- Every request must have the exact listener Host. Every data or action request, including the ticket exchange and snapshot polling, must be POST with the exact listener Origin and JSON content type. Foreign and missing origins, missing or expired sessions, oversized bodies and unknown actions are rejected. There is no CORS support. The only unauthenticated GET is the fixed, data-free HTML shell. No files or paths are served dynamically.
|
|
559
574
|
- Responses are non-cacheable, cannot be framed, suppress referrers, and use a content security policy restricting scripts/styles to the shipped inline content and connections to this origin. Browser text is rendered with textContent, never interpreted as HTML.
|
|
560
|
-
- The browser receives a bounded redacted event stream, peer states and queue counts, public task views (with a task's screened changed-symbol observation, #684: ids, paths, names and kinds, never source or line spans, and its Code group, #684 phase 2c: those symbols marked against the plan's names, each name re-screened by the public metadata screen; a private task has no group and its plan names never enter the snapshot), budget windows, each peer's DOING line (#878: a native session title or current plan item, at most 120 characters, kept in daemon memory only and screened when observed and again when shown; a failed screen, a private turn or the peer's open PII task shows `[private]`), pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text, the Usage view (#683 U4), computed only while the Agents view is open: peer ids, UTC days and token counts with their source, budget pause, resume and reset events without their reason text, and per-task rows of the task id, a PII flag and token counts, never a title, and the office view's optional fields (issue #264): each peer's open turn (start time, attributed task id, attribution kind), a `checking` flag on tasks whose check is running, and warning `links` of overlapping, conflicting or cohort tasks (task and peer ids only, never paths, symbols or reasons; a PII task never joins a link, and a private or PII-attributed turn exposes no task id). Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted.
|
|
575
|
+
- The browser receives a bounded redacted event stream, peer states and queue counts, public task views (with a task's screened changed-symbol observation, #684: ids, paths, names and kinds, never source or line spans, and its Code group, #684 phase 2c: those symbols marked against the plan's names, each name re-screened by the public metadata screen; a private task has no group and its plan names never enter the snapshot), budget windows, each peer's DOING line (#878: a native session title or current plan item, at most 120 characters, kept in daemon memory only and screened when observed and again when shown; a failed screen, a private turn or the peer's open PII task shows `[private]`), pending approvals, the settings rows (closed values and ids, the file each comes from) and each startable peer's start plan (issue #269), benchmark run summaries (issue #251): the newest runs in `~/.agenthub/bench/`, whichever project on this machine ran them, by suite name, arm label, state, the task id in progress, counts, tokens and times, never suite text, the Usage view (#683 U4), computed only while the Agents view is open: peer ids, UTC days and token counts with their source, budget pause, resume and reset events without their reason text, and per-task rows of the task id, a PII flag and token counts, never a title, and the office view's optional fields (issue #264): each peer's open turn (start time, attributed task id, attribution kind), a `checking` flag on tasks whose check is running, and warning `links` of overlapping, conflicting or cohort tasks (task and peer ids only, never paths, symbols or reasons; a PII task never joins a link, and a private or PII-attributed turn exposes no task id). Private envelope bodies and PII task text never cross this endpoint. Local-worker and Pi approval titles can contain PII, so their details remain terminal-only; the dashboard identifies the request and directs the operator to `ahub tail` before allowing it. Budget checkpoint summaries are also omitted. An ordinary (not live-test) session's snapshot also carries the open questions with their full text, a private one included, for the person's answer form (#1053), and unread results with the detail built when shown (#1054). An explicit review refresh of one task adds its person review comment threads and placement, with PII and private-path comments as counts only (#1056).
|
|
561
576
|
- The closed action list includes permission response, peer pause/resume, console message, task proposal/assignment and the settings actions below (set, undo, preview, reload, and the structural edit preview and apply of a settings session). Project-board actions add revision-fenced planning, Backlog pull/return/split/undo and release planning/state changes through Tasks; they do not tag, publish or write GitHub state. These use the existing daemon/task paths and budget pause rules. `start_peer` (issue #269) starts a peer from a closed list in the mode a person set, with a command the hub assembles: the request carries a peer id and nothing else. There is no generic control proxy, task-detail read, shell, file access, free-form configuration edit, budget override, launch of a caller-chosen command or daemon shutdown API.
|
|
562
577
|
- The listener stops with the daemon. Expired sessions must be reopened with `ahub ui`; the page does not silently obtain new credentials.
|
|
563
578
|
|
|
@@ -598,30 +613,38 @@ its existing basename/worktree aliases and provider accounts can remain shared.
|
|
|
598
613
|
|
|
599
614
|
`@staix/agent-hub/ui-plugin` runs inside the agent-console (`acon`) process as the
|
|
600
615
|
same user. It holds no credential of its own: it reaches project hubs only through
|
|
601
|
-
the multi-project manager
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
616
|
+
the multi-project manager, which connects as the console with each project's
|
|
617
|
+
control token. Beyond what `ahub ui --all` has, it uses dedicated manager ports the
|
|
618
|
+
dashboard page cannot reach: `answer` (#1053), `receipt` (#1054) and the review port
|
|
619
|
+
(`review_comment_put|delete|send`, `review_comments`, `review_blob`, #1056). Every
|
|
620
|
+
write through them requires acon's verified `ActionMeta`, as described in those
|
|
621
|
+
sections. Every other action must be on the manager's closed list and every view one
|
|
622
|
+
of the seven view names. There is no generic control proxy, task-detail read, shell
|
|
623
|
+
or caller-chosen command, and the only file read is the bounded review material of a
|
|
624
|
+
public task (`review_blob`).
|
|
606
625
|
|
|
607
626
|
- **Registration.** `ahub setup` writes `console/plugins/ahub.json` under the hub
|
|
608
627
|
home (directory 0700, file 0600, atomic replace) with the module path and its
|
|
609
628
|
sha256; acon refuses to load a module whose hash no longer matches.
|
|
610
629
|
- **Confirmation.** Outward or destructive actions (`stop`, `permit`, `send`,
|
|
611
630
|
`setting`, `setting_undo`, `setting_reload`, `config_edit_apply`, `start_peer`,
|
|
612
|
-
`backlog_split`
|
|
631
|
+
`backlog_split`, `worktree_override_set`, `worktrees_refresh_prs`,
|
|
632
|
+
`review_comment_send`, `review_comment_delete` and the inbox's Allow once and Deny) first answer `needsConfirm`
|
|
613
633
|
with a preview and a fingerprint, a sha256 of the request's own fields; the call
|
|
614
634
|
runs only when it comes back with `confirmed` and that fingerprint. The
|
|
615
635
|
fingerprint binds a confirmation to one exact request, so a changed request needs
|
|
616
636
|
a new confirmation; it is not a secret, and the confirm step is acon's host UI
|
|
617
637
|
contract, not authentication. A process that can load the module already has the
|
|
618
638
|
console's reach, and the hostile-agent boundary above applies unchanged.
|
|
619
|
-
- **Inbox.** Items are the pending approvals
|
|
620
|
-
is the person,
|
|
639
|
+
- **Inbox.** Items are the pending approvals, the in-review tasks whose reviewer
|
|
640
|
+
is the person, open questions (#1053) and, for a console that gives receipts
|
|
641
|
+
person evidence, unread results (#1054), read from the same dashboard snapshot: a local-worker or
|
|
621
642
|
Pi approval shows only the fixed private stub and offers Deny, and Allow once for
|
|
622
643
|
it is refused with "answer this approval with ahub permit". Allow once and Deny
|
|
623
644
|
resolve to the hub's own `permit` with an option the pending request offers,
|
|
624
|
-
looked up again at the time of the call; nothing else is reachable from an
|
|
645
|
+
looked up again at the time of the call; nothing else is reachable from an approval
|
|
646
|
+
item. A question's Answer and a result's mark-read go only through the person-evidence
|
|
647
|
+
paths in "Questions a peer asks the person" and "Results a person's runs leave behind".
|
|
625
648
|
Task titles are the public view's, so a PII task shows the `[pii]` stub.
|
|
626
649
|
- **Space.** The Space scene is built only from the office model the dashboard
|
|
627
650
|
already allowlists (#264): peer ids, conditions and counts, task ids, classes,
|
|
@@ -644,6 +667,15 @@ file edits, and a configuration reload refuses roster changes. External ids outs
|
|
|
644
667
|
admission rules. A roster key left by an undeclared instance loads as inert and grants
|
|
645
668
|
nothing; the only change it accepts is its removal from `config.local.json`.
|
|
646
669
|
|
|
670
|
+
Handing a peer to a standby session (#1174) is the person's: `peer_takeover` is
|
|
671
|
+
console-role only, and `ahub peer takeover` is refused to agent shells and
|
|
672
|
+
`--live-test`. A standby connection runs no peer operation until it owns the peer,
|
|
673
|
+
and the selected standby is rechecked for membership, launch and isolation,
|
|
674
|
+
lifecycle and claim generation after its preface. Standby ids and reported PIDs are
|
|
675
|
+
display metadata, never native-session or permission evidence. Takeover detaches
|
|
676
|
+
the old hub connection, not its native process; that session's later hub calls are
|
|
677
|
+
refused, and takeover never marks a delivery completed.
|
|
678
|
+
|
|
647
679
|
Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
|
|
648
680
|
merges/removes only the hub MCP entry; the daemon never edits that file and
|
|
649
681
|
refuses headless starts while a stale entry exists. An attached Kimi TUI blocks
|
|
@@ -759,45 +791,47 @@ historical claude-mem database used only by the explicit read-only import.
|
|
|
759
791
|
Sync starts only after a person configures a remote. Public GitHub remotes are
|
|
760
792
|
refused; unknown visibility proceeds with a warning for the configured remote.
|
|
761
793
|
Short-term segments, machine identity and the derived index are excluded from
|
|
762
|
-
sync.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
794
|
+
sync. Sync and direct long-term writes apply the configured agent-mem sync policy
|
|
795
|
+
narrowed by the hub's deny paths, PII patterns and project-private paths; a promotion
|
|
796
|
+
that fails it leaves the note short-term (#1173). Status exposes safe sync state and
|
|
797
|
+
counts, including verified withheld/exported counts from a successful SDK placement
|
|
798
|
+
result. Failure logs retain only the closed cause and known declined count.
|
|
799
|
+
`ahub memory declined` is person-only (refused to agent shells and `--live-test`)
|
|
800
|
+
and uses bounded physical-row inspection to show IDs and rule categories, never
|
|
801
|
+
note text or literal patterns; incomplete inspection is not a zero count.
|
|
802
|
+
Default-host v3 sync remains unsupported because the hub supplies no fresh
|
|
803
|
+
placement attestation. No activation proof is fabricated; the supported activation
|
|
804
|
+
and attestation path remains agent-mem#281. Suggested graph edges are off by default
|
|
805
|
+
and are not evidence until a person confirms them. Operational details and limits: [hub memory](memory.md).
|
|
806
|
+
|
|
807
|
+
## Floating pet retirement (#1012)
|
|
808
|
+
|
|
809
|
+
The standalone JXA host, WKWebView, window bridge and its packaged assets are
|
|
810
|
+
removed. `ahub pet --window [--close]` only prints the `acon ui` Mini pointer and
|
|
811
|
+
exits successfully; it has no launcher, process-control or network effect and
|
|
812
|
+
is available to agent shells and `--live-test`. Legacy `pet.window` is ignored
|
|
813
|
+
as compatibility input and grants no configuration or conductor authority.
|
|
814
|
+
|
|
815
|
+
Terminal pet reads retain their existing behavior. The retired `pet --palette`
|
|
816
|
+
command validates its argument and prints the same Mini pointer without writing
|
|
817
|
+
`.agenthub/config.local.json`. Existing `pet.palette` values remain validated
|
|
818
|
+
compatibility input and have no rendering effect.
|
|
819
|
+
|
|
820
|
+
The **Console message** view already exists in the agent-hub acon plugin
|
|
821
|
+
(#935). Its existing `send` action retains message audience, PII and actor
|
|
822
|
+
checks; the retired window adds no alternative composer path.
|
|
789
823
|
|
|
790
824
|
## Planner, cancellation and delivery authority
|
|
791
825
|
|
|
792
826
|
The `planner` setting is person-controlled: one peer id or a list of up to eight (#525-2). A valid pin determines the planner holders without adding them to the roster; otherwise configured rostered planner roles apply, then the first rostered peer in the closed planner catalog. The automatic choice is not written to disk. Each new non-PII task and each planner-created release records one effective planner from the same pure walk `ahub route explain` shows; only that planner or the person changes its planning. A bound planner keeps that authority while busy, paused or offline; one removed from the holders or the roster falls back to the current holders, and older rows with no binding, PII tasks included, keep holder-wide authority. Only the person moves a task's planner (`ahub task assign <id> <peer> --role planner`). Another holder may record a plan review without gaining the task's other planning powers; an agent cannot review its own plan while another rostered holder is not offline, and otherwise the receipt records `selfReview: true` with `planAuthor`. Planner guidance does not grant conductor authority or bypass roster, PII, WIP or owner capability checks.
|
|
793
827
|
|
|
794
|
-
`cancelled` is closed, never approval. The person can cancel non-approved work with a reason and optional supersession reference; a planner can cancel only its own proposed/backlog work with no historical acceptance, and only as that task's effective planner. Current task revision, privacy and recovery/shutdown holds are checked before writing and again after awaited screening. Cancellation preserves history, releases reservations and overlap membership, writes no outcome memory, and does not stop native turns or delete branches/worktrees. Owner/reviewer stop notices remain relevant; earlier task requests become moot. Dependents remain blocked and receive ids-only advice, including backlog dependents.
|
|
828
|
+
`cancelled` is closed, never approval. The person can cancel non-approved work with a reason and optional supersession reference; a planner can cancel only its own proposed/backlog work with no historical acceptance, and only as that task's effective planner. The configured, rostered conductor can cancel only a task another project handed this one (#1175), while it is proposed or backlog, nobody has accepted it, it has no execution evidence and it is not PII. Current task revision, privacy and recovery/shutdown holds are checked before writing and again after awaited screening. Cancellation preserves history, releases reservations and overlap membership, writes no outcome memory, and does not stop native turns or delete branches/worktrees. Owner/reviewer stop notices remain relevant; earlier task requests become moot. Dependents remain blocked and receive ids-only advice, including backlog dependents.
|
|
795
829
|
|
|
796
830
|
Current daemons advertise `task_cancel` in hello and status metadata. A current CLI refuses cancellation against a daemon without that feature. Controlled upgrade/restart refuses an unsupported target while cancelled task ids exist; source prepare and commit recheck compatibility, and the rows are never rewritten as approved. In the same way it refuses a target without `task_planners` once a planner list or an instance planner is configured or any task or release planner binding or plan-review authorship is stored, even after that configuration is removed. A manually started older binary is outside this compatibility guarantee: it can display cancelled rows as open or fail to move them.
|
|
797
831
|
|
|
798
832
|
Stall causes are persisted and deduplicated by cause and replanning revision. Planner advice joins its next delivery as bounded ids-only context; it does not manufacture an ordinary task turn. Pull-only offer attention clears only after acceptance, decline, supersession or durable positive evidence for that exact offer and recipient. Operator, hub or owner completion of an uncertain delivery is not evidence that the offer was read.
|
|
799
833
|
|
|
800
|
-
`hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never pause removal. `hub_delivery_resolve` (#1112) lets a local agent mark only its own `needs_review` row `completed` or `discard`, at the revision `hub_inbox` showed, with a public, screened reason recorded under its actor. It never replays the originals or changes task state. An unknown id, another peer's row and a row
|
|
834
|
+
`hub_delivery_retry` grants an eligible recipient one retry of its own `needs_review` work, never pause removal. `hub_delivery_resolve` (#1112) lets a local agent mark only its own `needs_review` row `completed` or `discard`, at the revision `hub_inbox` showed, with a public, screened reason recorded under its actor. An attached Claude push-channel session may also settle its own `accepted` row that way (#1172); any other peer's `accepted` row is refused. It never replays the originals or changes task state. An unknown id, another peer's row and a row in neither state share one refusal, so the answer never confirms another peer's row. A channel `accepted` row that gets neither native completion nor that disposition within `bus.accepted_settle_ms` (default ten minutes) becomes `needs_review` with no replay. With `conductor.feed` on, the configured conductor gets one ids-only notice for an important, task or review timeout. The person, the hub, a remote peer, a paused or held peer, and recovery, shutdown or storage holds are refused. A row that carries an invocation stays person-only, because its outcome is uncertain. An explicit `hub_inbox` read may return newly queued messages behind such a hold, never the held originals, and only when no recovery, pause, lifecycle or storage fence applies. Durable per-original evidence survives journal pruning; unknown retry history or a used retry requires the person. Hub presence prefaces are excluded from attempted-work identity, so a shared context preface does not consume an unrelated message's retry. Delivery-only local and ACP peers (including Jcode) cannot invoke recipient retry; tool exposure hints do not replace the daemon's actual-adapter guard. Their held work requires person resolution. Before transport handoff, storage failure preserves the queue and preface; after handoff an uncertain effect is never automatically replayed.
|
|
801
835
|
|
|
802
836
|
A declared instance whose only role is reviewer gets review requests, task messages addressed to it and the person's messages; agent chat, digests and presence are discarded and journaled, and it gets no memory recall (task 223). The headless Claude reviewer runs one new `claude -p` per review with `--setting-sources ""`, `--strict-mcp-config`, `--no-session-persistence`, tools limited to Read, Grep, Glob, `hub_review` and `hub_task_show`, and `dontAsk`. Its prompt holds hub facts and a bounded diff, never the builder's summary, and its `hub_task_show` drops the builder's notes. Changes touching `local.deny` or `pii.paths` are withheld and passed as `Read` deny rules, which Claude enforces, not the hub. Private tasks are never given to it. Its `hub_review` counts only while the child started for that task still runs; a stop or exit revokes it first, and surviving processes hold the delivery as `needs_review` and keep the reviewer busy.
|
|
803
837
|
|
|
@@ -805,7 +839,7 @@ A terminal Claude implementer or verifier instance (#525-5) owns write-class tas
|
|
|
805
839
|
|
|
806
840
|
A planner-only instance (#525-6: `planner` without `implementer` or `verifier`) plans on the project checkout, so a declared `worktree` is refused. A Codex planner's `thread/start` and `thread/resume` are requested read-only, and a thread is adopted only when Codex's reply positively confirms a read-only sandbox; an unconfirmed or malformed reply refuses that RPC and closes the link before the hub adopts the thread or delivers work, and the current roles are rechecked before each injection. This is an initial ceiling, not confinement of the person's attached TUI, which can change its native sandbox later. A declared planner-only Claude channel instance gets no automatic grant from its facts hook for any tool but Read, LS, Glob and Grep, whatever the permission mode.
|
|
807
841
|
|
|
808
|
-
The headless Claude implementer (#525-4) runs one owned `claude -p` child per turn in its declared worktree, with `--setting-sources ""`, `--strict-mcp-config`, native permission mode `default`, the hub's managed PreToolUse hook and `--permission-prompt-tool mcp__agent-hub__hub_permission_request`; never `--bare`, `acceptEdits`, `dontAsk` or `bypassPermissions`, and `never-ask` is refused. Each task keeps its own persisted session; unscoped chat reuses one only while exactly one task is in progress, and a `hub_inbox` read refuses a batch attributed to another task. The approval path fails closed: `hub_permission_request` is accepted only from the current child's launch and generation, goes to the console's existing approval queue and `approvals.timeout_s`, and offers allow-once and deny only, with no allow-always or session grant. A timeout, a prompt failure, a disconnect, a hub that is down or refusing, or a stale child binding answers deny; a failed hook decides nothing, so the call still waits for a person. The approval title is the tool name and a JSON rendering of the arguments passed through `sanitize()` and the public-text screen (any PII, secret or private-path match withholds the whole value as `[withheld]`, before any cut), never task text, cut at 600 characters with a `[cut, N chars]` marker; the original arguments stay with the child and are not logged. The MCP wire cannot tell a native permission dispatch from a call the model makes itself; a person allow still refers only to its original arguments. Automatic Bash authorization uses the separate single-use host wrapper contract below, never a caller-supplied tool id as native provenance. A stop holds an uncertain delivery until the owned process group is confirmed gone. The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) is model-free on Claude Code 2.1.309, [repeated on 2.1.310](verification/2026-10-26-claude-implementer-2.1.310.md), [on 2.1.314](verification/2026-10-31-claude-implementer-2.1.314.md)
|
|
842
|
+
The headless Claude implementer (#525-4) runs one owned `claude -p` child per turn in its declared worktree, with `--setting-sources ""`, `--strict-mcp-config`, native permission mode `default`, the hub's managed PreToolUse hook and `--permission-prompt-tool mcp__agent-hub__hub_permission_request`; never `--bare`, `acceptEdits`, `dontAsk` or `bypassPermissions`, and `never-ask` is refused. Each task keeps its own persisted session; unscoped chat reuses one only while exactly one task is in progress, and a `hub_inbox` read refuses a batch attributed to another task. The approval path fails closed: `hub_permission_request` is accepted only from the current child's launch and generation, goes to the console's existing approval queue and `approvals.timeout_s`, and offers allow-once and deny only, with no allow-always or session grant. A timeout, a prompt failure, a disconnect, a hub that is down or refusing, or a stale child binding answers deny; a failed hook decides nothing, so the call still waits for a person. The approval title is the tool name and a JSON rendering of the arguments passed through `sanitize()` and the public-text screen (any PII, secret or private-path match withholds the whole value as `[withheld]`, before any cut), never task text, cut at 600 characters with a `[cut, N chars]` marker; the original arguments stay with the child and are not logged. The MCP wire cannot tell a native permission dispatch from a call the model makes itself; a person allow still refers only to its original arguments. Automatic Bash authorization uses the separate single-use host wrapper contract below, never a caller-supplied tool id as native provenance. A stop holds an uncertain delivery until the owned process group is confirmed gone. The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) is model-free on Claude Code 2.1.309, [repeated on 2.1.310](verification/2026-10-26-claude-implementer-2.1.310.md), [on 2.1.314](verification/2026-10-31-claude-implementer-2.1.314.md), [on 2.1.316](verification/2026-10-31-claude-implementer-2.1.316.md) and [on 2.1.318](verification/2026-11-05-claude-implementer-2.1.318.md); it is not sandbox proof, and a real account, model and person interaction remains a live leg.
|
|
809
843
|
|
|
810
844
|
The #845 host shell wrapper is the only automatic Bash path for a headless implementer. In `ask-when-needed`, a complete public input must pass the existing #816 read-only proof or the closed task-command proof, with an active owned task and registered worktree. Enabled action-policy classes still ask, and `ask`, PII handling and the `never-ask` refusal are unchanged. The native request and authenticated host response both name wrapper protocol 1, so an older plugin cannot drop the rewrite and execute an original command automatically.
|
|
811
845
|
|
|
@@ -881,6 +915,14 @@ role admission.
|
|
|
881
915
|
separate `runner-attested` level described below; their fields never overwrite
|
|
882
916
|
declared or host-measured evidence. Public experiment recording tools and the
|
|
883
917
|
receipt CLI are the separate #602 slice.
|
|
918
|
+
- **Claim evidence links (#931 T1).** `hub_evidence_record` is published only to
|
|
919
|
+
research-profile peers. It admits only the current in-progress owner of a stored
|
|
920
|
+
research task, outside a private turn, on a non-PII task and a registered public
|
|
921
|
+
source whose raw hash matches. The checks repeat after screening and inside the
|
|
922
|
+
append transaction. Its relation, projection hash and locator are declared
|
|
923
|
+
provenance, never an entailment judgment or host verification; person verdicts
|
|
924
|
+
stay the only judgment record. Receipts carry the link id, revision and task id
|
|
925
|
+
only, and arguments and results are excluded from capture.
|
|
884
926
|
- **Privacy.** A campaign whose parent task is PII, or that names a private source,
|
|
885
927
|
is private, and so is an attempt with a private task, input or output path.
|
|
886
928
|
Public views keep ids, counts, states and provenance and stub every question,
|
|
@@ -940,7 +982,7 @@ A handoff crosses a project boundary, but not a security boundary: both projects
|
|
|
940
982
|
belong to the same machine user. The origin daemon reads the target's control
|
|
941
983
|
token file and connects to the target's daemon as the `console` role through
|
|
942
984
|
`ControlClient`, like every control message; the hello checks the target's project
|
|
943
|
-
root and instance. It sends that connection only the
|
|
985
|
+
root and instance. It sends that connection only the four handoff messages, and
|
|
944
986
|
it has no reach a same-user process lacks (see "What is not defended").
|
|
945
987
|
|
|
946
988
|
- **Who may hand off.** `ahub task handoff` is Console in `classifyPeerCommand`;
|
|
@@ -950,21 +992,28 @@ it has no reach a same-user process lacks (see "What is not defended").
|
|
|
950
992
|
the target's own `status.json` must advertise `task_handoff` and name no other project. A target
|
|
951
993
|
that is not running is refused with the action; nothing is queued.
|
|
952
994
|
- **No authority in the target.** The target answers `task_handoff`,
|
|
953
|
-
`task_handoff_event` and `task_handoff_states` to a console
|
|
995
|
+
`task_handoff_event`, `task_handoff_by_key` and `task_handoff_states` to a console
|
|
996
|
+
connection only, and
|
|
954
997
|
refuses a new handoff while it recovers or shuts down. The task is an ownerless proposal
|
|
955
998
|
its own hub makes and routes. The origin project and peer are a record in
|
|
956
999
|
`task_handoff_origin` and a line at the end of the detail, never an actor, a
|
|
957
1000
|
roster peer or a role: the origin cannot accept, review, approve, assign or
|
|
958
1001
|
cancel there.
|
|
959
1002
|
- **What crosses.** To the target: a title (300 characters), a class, a detail
|
|
960
|
-
(4,000), up to 16 repository-relative paths, a spec issue number,
|
|
961
|
-
|
|
1003
|
+
(4,000), up to 16 repository-relative paths, a spec issue number, an optional
|
|
1004
|
+
`duplicate_ok` reason (300, kept in the target's detail), a handoff key (a UUID,
|
|
1005
|
+
sent only to a target that advertises `task_handoff_key`), and the origin project
|
|
1006
|
+
id, peer id and task id, at most 8 KiB in all. The request is parsed
|
|
962
1007
|
closed on both sides: an unknown field, an accessor or a control character is
|
|
963
1008
|
refused, never dropped, and the target sanitizes before it stores. No memory,
|
|
964
1009
|
evidence, turn content or task history crosses. Back to the origin: an event
|
|
965
|
-
name, a PR number and a full 40-hex head, never the target's
|
|
966
|
-
other text;
|
|
967
|
-
|
|
1010
|
+
name, a sequence number, a PR number and a full 40-hex head, never the target's
|
|
1011
|
+
title, notes or other text; a duplicate-issue refusal names only the target's own
|
|
1012
|
+
open non-PII task ids. The catch-up (at start, every five minutes and when a
|
|
1013
|
+
target's descriptor changes) and the key lookup answer only for tasks whose
|
|
1014
|
+
recorded origin is the asking project, with the same fields. A key the target
|
|
1015
|
+
already holds answers with the task it made and never files a second one; the
|
|
1016
|
+
same key with a different request is refused. The origin peer gets that as a
|
|
968
1017
|
hub-written `kind: task` message.
|
|
969
1018
|
- **Privacy.** The origin refuses during a private turn and for a `[pii]` task
|
|
970
1019
|
named as the source, before anything leaves. The text itself is checked by the
|
|
@@ -1078,9 +1127,20 @@ targets before any local effect; implicit broadcast never crosses the boundary.
|
|
|
1078
1127
|
|
|
1079
1128
|
Signed completion proves remote durable queue admission only and is displayed as
|
|
1080
1129
|
completed (admitted). Crypto inbox, application claim and Bus publication use
|
|
1081
|
-
separate durable stages; a cross-DB failure is uncertainty, never an
|
|
1082
|
-
or native-execution claim.
|
|
1083
|
-
|
|
1130
|
+
separate durable stages; a receiver cross-DB failure is uncertainty, never an
|
|
1131
|
+
exactly-once or native-execution claim. A late authenticated ordinary-chat receipt
|
|
1132
|
+
may settle its matching unresolved sender row under current room/member identity,
|
|
1133
|
+
without overriding a person disposition. Invocation uncertainty remains person-held
|
|
1134
|
+
and is never inferred from transport proof. Person accept/discard is local disposed
|
|
1135
|
+
metadata and never a signed application receipt.
|
|
1136
|
+
|
|
1137
|
+
Sender retries retain the application id, canonical content and original queue TTL.
|
|
1138
|
+
A distinct root delivery requires the same retained content hash and recipient,
|
|
1139
|
+
with no disposition or permanent hold; it inherits the original creation/expiry.
|
|
1140
|
+
Legacy unknown content hashes refuse cross-root reuse. Receipt ciphertext commits
|
|
1141
|
+
to the local durable outbox before chat transport ACK and replays until broker
|
|
1142
|
+
storage confirmation. Deferred admission may ACK its durable crypto inbox entry
|
|
1143
|
+
and commit its receipt when admission resumes. Neither path claims native execution.
|
|
1084
1144
|
|
|
1085
1145
|
Schema3 reserves a separate durable lane for at most eight canonical receipt
|
|
1086
1146
|
payloads per room, at most 1,024 bytes each and 512 KiB extra globally. A receipt
|
|
@@ -1100,12 +1160,15 @@ Capacity recovery requests fresh presence once per transition; runtime and
|
|
|
1100
1160
|
other storage errors stay unACKed.
|
|
1101
1161
|
Fresh, fully authenticated chat at the ordinary cap is likewise durably refused
|
|
1102
1162
|
and transport-ACKed without admission, floor advance or signed receipt. Its
|
|
1103
|
-
sender
|
|
1104
|
-
|
|
1163
|
+
sender retries at the ACK deadline under the same immutable application id, without
|
|
1164
|
+
renewing its queue TTL. Queue expiry or a permanent refusal ends automatic sending.
|
|
1165
|
+
The refused chat cannot hold matching receipt traffic behind it in the broker FIFO.
|
|
1105
1166
|
|
|
1106
1167
|
Permanent signed sequence floors survive all application retention/eviction.
|
|
1107
1168
|
Application-id and trace guards retain 30 days/10,000 rows per room, settled first;
|
|
1108
|
-
outside that retention they make no historical claim.
|
|
1169
|
+
outside that retention they make no historical claim. Automatic resend can reach
|
|
1170
|
+
this settled-eviction ceiling and repeat admission under a fresh ciphertext sequence:
|
|
1171
|
+
exactly once within dedupe ceiling. MAX_HOP 3 remains absolute.
|
|
1109
1172
|
All-unsettled capacity refuses with ids/counts, and person disposition releases
|
|
1110
1173
|
settled capacity. This state protects against other users, not hostile code
|
|
1111
1174
|
running as the same user with access to the private room keys. Native/account,
|
|
@@ -1202,3 +1265,34 @@ show it only to the console or configured conductor; other readers see
|
|
|
1202
1265
|
source note identifies incoming-message text and never attributes it as the
|
|
1203
1266
|
recipient peer's own speech. `status.json`, events, logs and memory never
|
|
1204
1267
|
receive a derived copy. Removed-instance and Local bindings are not retained.
|
|
1268
|
+
|
|
1269
|
+
## Questions a peer asks the person (#1053)
|
|
1270
|
+
|
|
1271
|
+
`hub_ask` lets any peer put a structured question (one to four questions, two to eight options each) to the person. A question is not a permission: it is its own record in `hub.db` (`questions`), has its own control messages, and never expires as a denial.
|
|
1272
|
+
|
|
1273
|
+
- **Who answers.** Only the console role. The control message `answer { questionSet, answers, surface? }` is refused, with a reply and no change to the row, for every `tools` or `peer` connection, the asking peer's own included. Nothing a peer writes counts as an answer: a `hub_send` body, a tool result, a file or an envelope that quotes one (the exact JSON `hub_ask` returns included) is text, and `test/questions-daemon.test.ts` pins that none of them changes a row. The answer envelope the peer receives is built by the hub (`kind: "answer"`, `from: "user"`), so its header cannot be forged: every body goes through `sanitize()`, and `kind`, never the sender, is what code keys on.
|
|
1274
|
+
- **One terminal transition.** `open` becomes `answered`, `superseded`, `cancelled` or `expired` by one compare-and-set (`UPDATE ... WHERE state = 'open'`). Of any number of concurrent answers, stops and expiries exactly one wins, and every other caller is told the row's real state. A second answer is refused and publishes nothing.
|
|
1275
|
+
- **Validation.** An answer is checked against the stored row before anything is written: every question answered once, only offered option ids, one option unless `multi`, free text only where `allowOther` and at most 500 characters. The refusal names the question by position and never echoes the offered or submitted text. Input is closed and bounded the same way (`parseAsk`).
|
|
1276
|
+
- **Answering from acon (#1053 T5).** The plugin accepts an `answer` action only when the console advertises `action.meta` and passes its own `meta` (`via: "console"`, a verified `identity` and `node`), which the page cannot set. Without it, or with page-supplied `identity`, `surface`, `questionSet`, `projectId` or `instanceId` fields, the action is refused before any request reaches the hub (`test/questions-plugin.test.ts`). The project and set come from the inbox item id the plugin built, never from the page. The plugin reaches the hub through a dedicated manager port (`projectConsole(...).answer`), not through the dashboard action whitelist, so the dashboard page of `ahub ui --all` and the manager's `ui_action` whitelist have no `answer` action, and a page can never answer for the person. The hub re-validates every answer against the stored row. The verified `identity` the plugin read from the console's own `meta` rides with the answer (`answer { ..., surface: "acon", person }`); the hub records it, one bounded line, on the answered row and on the `question` event, and reads `person` only for the `acon` surface, so a terminal or console answer cannot claim another person. It is never question or answer text. The acon types of the form and of the submitted answer (`ActionMeta`, `InboxVerb`, `VerbForm`, `InboxAnswers`) are type-only imports from the exact-pinned `@staix/agent-console/contract` entry, never hand-copied. `ahub answer` with any operand is refused in `liveTestRefusal` (a delegated `--live-test` run is promoted to console identity, so without it an agent shell could answer its own `hub_ask` as the person); only the bare listing is allowed there.
|
|
1277
|
+
- **Residual.** The same as `permit`: a vendor agent that can read the control token can act as the console (see "What is not defended"). This feature does not narrow that and does not widen it.
|
|
1278
|
+
- **Privacy.** Question and answer text live in `hub.db` and in the envelope addressed to the asking peer, nowhere else: never `hub.log` (an `answer` or question-notice envelope logs a fixed stub), never `events.jsonl` (the `question` event carries the set id, peer, event name, counts, latency, surface and a closed reason), never the console tail or the dashboard feed (`redactBusEvent` stubs the body), never memory capture (`QUESTION_TOOLS`) or the progress observer. A question asked in a PII turn, or about a PII task, is stored `private` and its late envelope is private too. The inbox title of a private question is a stub (`<peer> asks a question`); its text is only in the answer form, the person's own answer surface, and the status attention model, the stream line and the CLI audit carry ids only. `test/questions-console-daemon.test.ts` pins these with a PII task, the status reply, the dashboard snapshot, `hub.log`, `events.jsonl` and memory capture.
|
|
1279
|
+
- **Not defended.** A peer that is cancelled, superseded or restarted loses a pending question's wait, never its row, until the person or `questions.ttl_s` settles it. An answer a person gives is shown to the asking peer in full; do not ask the person for a secret.
|
|
1280
|
+
|
|
1281
|
+
## Results a person's runs leave behind (#1054)
|
|
1282
|
+
|
|
1283
|
+
A result is the record that a run the person started has settled. Reading one and marking it read are separate acts, and only the person makes the second.
|
|
1284
|
+
|
|
1285
|
+
- **A receipt is the person's, on every surface.** `result_seen`, `results` (`read-all`, `unread-last`) and the dashboard's `result_seen`, `results_read_all` and `results_unread_last` write or delete a receipt, and each is refused unless the caller is the console role or a dashboard session the console opened: a peer's connection, a `hub_send` body, a tool result or an agent shell (`ahub results` is the person's command, and its receipt forms `--read`, `--read-all` and `--unread-last` are in `liveTestRefusal`, so a delegated `--live-test` run of them is refused too; the bare listing stays allowed there) is text and never reaches the store. A snapshot, a listing and the Results section's refresh never write one; a poll is not a person looking (`test/results-dashboard-1054.test.ts`).
|
|
1286
|
+
- **From acon.** The plugin accepts `result_seen`, `results_read_all` and `results_unread_last` only when the console advertises `action.meta` and passes its own `meta` (`via: "console"`, a verified `identity` and `node`), which the page cannot set. Without it, or with page-supplied `identity`, `surface`, `person` or a completion key, the action is refused before any request reaches the hub (`test/results-plugin-1054.test.ts`). The project and the result come from the inbox item id the plugin built or, from the panel, from the project it is showing; acon's `seenAt` and `explicit` are accepted and dropped, because the hub stamps its own time. The write goes through a dedicated port (`projectConsole(...).receipt`) as a console-role control message on the `acon` surface, never through the dashboard's forwarding list, and a plugin that is not given the person's meta cannot widen that.
|
|
1287
|
+
- **A live-test dashboard** gets no results and is refused every receipt, in two places that each hold alone: the page server's live-test allowlist and the daemon's own guard (`test/results-dashboard-1054.test.ts`).
|
|
1288
|
+
- **Residual.** The same as `permit`: a vendor agent that can read the control token can act as the console (see "What is not defended"), and so can write a receipt. This feature does not narrow that and does not widen it. A receipt is not an authority: it clears an unread need and nothing else.
|
|
1289
|
+
- **Privacy.** No prompt or reply text is stored: a completion row keeps envelope ids only. The one-line detail shown beside a result (the head of the starting prompt) is built when the person looks, from the envelope the bus still holds, after `sanitize` (so it cannot forge a hub header) and the hub's secret, PII and private-path screens; it is thrown away after it is shown and is in no `hub.db` row, `hub.log` line, `events.jsonl` event, status field or memory capture (canary tests: `test/results-dashboard-1054.test.ts`, `test/completions-daemon.test.ts`). A private run shows `a private request` without its body being read, a head the screens refuse shows a withheld stub, and a request the bus no longer holds shows nothing. The title is built from the peer and the outcome, never from request text, and the `completion_seen` event carries the id and the surface.
|
|
1290
|
+
- **Not defended.** The detail is the person's own request shown back to them on their own surfaces, so a request that holds something the screens do not recognise is shown to the person who typed it. Marking a result read does not mean the person read it: the hub takes the console's word, and acon's seeing rule (agent-console#27) is what makes that word mean the result was in view.
|
|
1291
|
+
|
|
1292
|
+
## Anchored review comments (#1056)
|
|
1293
|
+
|
|
1294
|
+
Person comment writes (`review_comment_put`, `review_comment_delete`, `review_comment_send`) are console-role control messages, and their actor is always the person: the conductor, a planner and every peer are refused. From acon they require the console's verified `ActionMeta` (`via: "console"`, `identity`, `node`). Page-supplied `author`, `meta`, `personMeta`, `at`, `state` or `sentAt` fields are refused before any request, and delete and send also need acon's confirmation fingerprint. The standalone `ahub ui` page is read-only for comments.
|
|
1295
|
+
|
|
1296
|
+
- **Material.** Placement material comes only from guarded immutable Git snapshots at the task's recorded head (declared refs, plan paths, or same-head verified PR paths), bounded per operation to eight files and eight seconds; a sent package is at most 12,000 bytes. `review_blob` refuses a PII task, and periodic snapshots make no comment-material Git or GitHub read. PII tasks and private paths keep comments on the board and count-only in every public view.
|
|
1297
|
+
- **Delivery.** A sent package carries the host-only `refs.reviewPackage` marker: the intended owner gets the body, while logs, stream projections, incoming labels, memory capture and observers get an ids-only stub; review markers a peer supplies are discarded. Open sent comments, anchored findings and unsettled review deliveries keep the original recipient's responses out of shared memory and observers until resolution, dismissal, task close or explicit disposition (#1192, #1205). The marker grants no task, console or PII authority.
|
|
1298
|
+
- **Retry and recovery.** A send is never retried automatically, and a queued receipt proves publication, not that the owner read it. A restart, upgrade or recovery onto a daemon lacking `review_comments` is refused while anchored comments are stored.
|