@staix/agent-hub 0.24.0 → 0.25.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 +13 -0
- package/README.md +27 -5
- package/docs/agent-notes/adapters.md +1 -0
- package/docs/agent-notes/bus.md +1 -0
- package/docs/agent-notes/daemon.md +15 -2
- package/docs/agent-notes/tasks.md +9 -2
- package/docs/agent-notes/tests.md +2 -1
- package/docs/dogfood.md +12 -2
- package/docs/memory.md +1 -1
- package/docs/operations.md +137 -4
- package/docs/profiles.md +2 -0
- package/docs/quickstart.md +10 -3
- package/docs/security.md +155 -3
- package/docs/specs/2026-09-19-agent-hub-design.md +25 -1
- package/docs/specs/2026-09-19-multi-project-design.md +12 -1
- package/docs/specs/2026-11-01-research-campaigns-design.md +351 -0
- package/docs/verification/2026-11-01-0.25.0.md +91 -0
- package/docs/verified.json +31 -15
- package/package.json +4 -3
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +5118 -4708
- package/src/adapters/claude-channel.ts +29 -6
- package/src/adapters/definitions/native-cli-doctor.ts +30 -0
- package/src/adapters/jcode.ts +3 -0
- package/src/adapters/registry.ts +1 -1
- package/src/cli/acon-register.ts +29 -0
- package/src/cli/handoff-args.ts +27 -0
- package/src/cli/handoff-output.ts +25 -0
- package/src/cli/help.ts +6 -3
- package/src/cli/identity.ts +7 -1
- package/src/cli/main.ts +76 -7
- package/src/cli/output.ts +8 -5
- package/src/cli/schedule-output.ts +8 -4
- package/src/cli/study-output.ts +71 -0
- package/src/cli/study.ts +58 -0
- package/src/cli/task-output.ts +1 -0
- package/src/hub/board.ts +871 -2
- package/src/hub/channel-project.ts +61 -0
- package/src/hub/control-client.ts +10 -1
- package/src/hub/daemon.ts +247 -11
- package/src/hub/envelope.ts +2 -0
- package/src/hub/events.ts +4 -1
- package/src/hub/execution-budget.ts +12 -0
- package/src/hub/handoff.ts +126 -0
- package/src/hub/hub-tools.ts +12 -2
- package/src/hub/manager.ts +64 -57
- package/src/hub/no-writes.ts +1 -1
- package/src/hub/path-policy.ts +12 -8
- package/src/hub/project.ts +14 -5
- package/src/hub/provenance.ts +30 -0
- package/src/hub/research-campaign.ts +265 -0
- package/src/hub/research-experiments.ts +445 -0
- package/src/hub/restart.ts +26 -0
- package/src/hub/schedules.ts +210 -9
- package/src/hub/session-identity.ts +49 -0
- package/src/hub/tasks.ts +894 -22
- package/src/hub/ui.ts +15 -6
- package/src/memory/capture.ts +2 -2
- package/src/ui/index.html +5 -3
- package/src/ui/office-space.ts +118 -0
- package/src/ui/plugin.ts +293 -0
- package/templates/AGENTS.block.md +1 -0
- package/templates/profiles/general.json +1 -1
- package/templates/profiles/research.json +4 -4
- package/templates/profiles/writing.json +1 -1
- package/templates/roles/research/analyst.md +1 -0
- package/templates/roles/research/fact-checker.md +1 -0
- package/templates/roles/research/research-lead.md +1 -0
- package/templates/roles/research/researcher.md +1 -0
- package/templates/roles/research/statistician.md +1 -0
|
@@ -91,5 +91,16 @@ and verified PR. Preserve pre-existing instruction-file changes.
|
|
|
91
91
|
|
|
92
92
|
## Out of scope
|
|
93
93
|
|
|
94
|
-
Cross-
|
|
94
|
+
Cross-machine task routing (broker, #446), fully isolated native memory, new runtime dependencies,
|
|
95
95
|
visual redesign, automatic merging and npm publication.
|
|
96
|
+
|
|
97
|
+
## Cross-project task handoff (#604, agent-hub#1030)
|
|
98
|
+
|
|
99
|
+
Same-machine cross-project routing is no longer out of scope: work that belongs to another project that runs agent-hub is registered on that project's board.
|
|
100
|
+
|
|
101
|
+
- **Target.** `project` names a project in the machine registry (its canonical root or its id). An unregistered target, the caller's own project and a target whose daemon is not running are refused, the last with the person's action (`ahub up` in that project). Nothing is queued.
|
|
102
|
+
- **Who and what.** The person (`ahub task handoff --project <root|id> [<class>] <title...> [--path <p>]... [--issue <n>] [--task <n>] [--detail <text>]`), or a configured planner or the conductor (`hub_task_handoff`). A feature or behavior change first needs a spec issue in the target repository, passed as `issue`; a mechanical chore goes straight to a task. The request is closed (unknown fields refused), bounded (8 KiB whole) and repository-relative for paths.
|
|
103
|
+
- **Transport.** The origin's daemon reads the target's control descriptor (same machine user) and sends `task_handoff` as a console-role control message with origin `{ projectId, peer, taskId? }`. The target's `Tasks` creates an ownerless proposal made by the hub, with the origin recorded in `task_handoff_origin`; its own routing and planner take it from there. The origin peer has no identity or authority in the target: it is a record, never a roster peer.
|
|
104
|
+
- **Follow-back.** On accepted, in review, approved, declined and cancelled the target sends `task_handoff_event`; the origin records it in `task_handoff_outbound` and delivers one `kind: task` envelope to the requesting peer (`agent-hub task 123 approved: PR #N at <sha>`: an event name, a PR number and a full head, never target text). An origin that was down asks the target for the current state of its open handoffs when it starts (`task_handoff_states`). `ahub board --handoffs` and `hub_task_handoffs` list them.
|
|
105
|
+
- **Privacy.** Refused during a private (PII) turn and for a `[pii]` task named with `task`; the target refuses text that matches a PII pattern or that its screen cannot clear. No memory, evidence or turn content crosses beyond the handoff fields.
|
|
106
|
+
- **Compatibility.** The additive daemon feature `task_handoff` (no protocol bump: a new message type is answered as unknown by an older hub, and an optional field is ignored). An older target is refused with a named "upgrade the target hub" error, and a recovery target without the label is refused while either handoff table holds a row.
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Research campaigns, experiment receipts and runner attestation
|
|
3
|
+
date: 2026-11-01
|
|
4
|
+
project: agent-hub
|
|
5
|
+
status: frozen for implementation (task #595); slices #596 to #602
|
|
6
|
+
issue: 930, 932, 1018
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Research campaigns, experiment receipts and runner attestation
|
|
10
|
+
|
|
11
|
+
This document freezes the implementation contract for #929 R1 (#930, research campaigns), #929 R3 (#932, experiment protocols and attempt receipts) and #1018 (registered external runner). The issues hold the decisions and acceptance criteria; this spec fixes the wire names, schemas, bounds, storage and the delivery slices, so implementation does not have to invent them. Where it narrows or departs from an issue, the departure is listed in "Departures from the issues". #931 (evidence links) is out of scope.
|
|
12
|
+
|
|
13
|
+
The person directed on 2026-11-01 that #932 be implemented together with #1018. #932 depends on #930, so all three are in scope.
|
|
14
|
+
|
|
15
|
+
## Common rules
|
|
16
|
+
|
|
17
|
+
### Authority and paths
|
|
18
|
+
|
|
19
|
+
- Every board write goes through `Tasks` (AGENTS.md "Board writes"). New Tasks methods throw `PreEffectToolRefusal` before any effect, follow the existing validate, then `withholds`, then re-check order, and write history only through `board.update` or `board.plan`.
|
|
20
|
+
- Research roles stay catalog guidance. A unit's `purpose` grants no authority; dispatch, ownership and review use the existing routing and role admission.
|
|
21
|
+
- Activation, revision approval, scope or ceiling enlargement, attestation and runner registration are person only. A planner may draft and propose a revision; it cannot approve one.
|
|
22
|
+
- No model call and no filesystem read inside a database transaction. Screening, source lookups and file hashing run first; the transaction then re-checks revisions and writes.
|
|
23
|
+
|
|
24
|
+
### Provenance levels
|
|
25
|
+
|
|
26
|
+
A field that can be evidenced carries `{ value, provenance, evidence? }`:
|
|
27
|
+
|
|
28
|
+
| Level | Meaning | Set by |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `declared` | An agent or a person wrote it | any record call; the default |
|
|
31
|
+
| `host-observed` | The hub measured it itself | a check-runner receipt the hub reads from `stateDir/checks/<run>.json`, or a deliverable hash from `hashDeliverables` |
|
|
32
|
+
| `runner-attested` | A registered external runner signed it | a verified #1018 receipt |
|
|
33
|
+
| `person-attested` | The person attested it | console-only `ahub study attest` |
|
|
34
|
+
|
|
35
|
+
- No path converts one level into another. A higher level is a new evidence row with its own source, never an update of a declared value.
|
|
36
|
+
- Projections show the level next to every evidenced field. `runner-attested` never renders as `host-observed`. A missing field is `unknown`, not `declared`.
|
|
37
|
+
- None of the levels states scientific correctness.
|
|
38
|
+
|
|
39
|
+
### Bounds
|
|
40
|
+
|
|
41
|
+
Every text field is UTF-8 and bounded. A record above its bounds refuses whole, with the field named. Shared limits: an id matches `^[a-z0-9][a-z0-9_-]{0,63}$`; a short text is at most 512 bytes and a long text at most 4 KiB; a list is at most its stated count; a whole stored body is at most 64 KiB.
|
|
42
|
+
|
|
43
|
+
### Privacy
|
|
44
|
+
|
|
45
|
+
- A campaign whose parent task is PII, or that names a private source (`SourceMeta.private`), is private. So is an attempt whose task or protocol inputs are private.
|
|
46
|
+
- Public views of private records keep ids, counts, states and provenance levels, and stub every question, scope, text, path, excerpt and metric name as `[pii]` (the `publicView` rule). Raw source text is never stored in a campaign, a handoff or an attempt; they reference source ids.
|
|
47
|
+
- Nothing from a private campaign or attempt goes to claude-mem or any cloud observer. Handoffs and attempt notes are screened through the existing public and private delivery paths.
|
|
48
|
+
|
|
49
|
+
### Recovery and features
|
|
50
|
+
|
|
51
|
+
- New daemon feature `research_records` in `DAEMON_FEATURES`. `incompatibleRecoveryTarget` refuses a target without it while `board.hasResearchState()` finds any campaign, protocol, attempt or runner-receipt row, or `research.runners` is set in config.
|
|
52
|
+
- The recovery `integrity()` digest gains the row counts and the latest revision of the research tables.
|
|
53
|
+
|
|
54
|
+
## T1 implementation boundary (#596)
|
|
55
|
+
|
|
56
|
+
T1 provides the bounded parser, Board storage and host Tasks methods, with no public study CLI or hub tool until #601. A write receipt carries only generated campaign ids, revision, status and unit counts; the full unit/task mapping is returned only by person-only approval. No raw contract or close reason enters parent Task history or outcome memory.
|
|
57
|
+
|
|
58
|
+
A stable unit id retains its task across approved revisions and restart, including a unit omitted from an intervening revision. T1 retains only an identical work-bearing unit definition, including its dependencies; changed work uses a fresh unit id. Existing intake or retained tasks keep their owner, reviewer, state, detail and dependencies. Incompatible dependencies or retaining a public task in a private campaign refuse whole rather than changing that task's authority. Approved omissions are recorded as counts in the parent history and remain inspectable through the immutable contracts. A replay creates nothing; it may only tighten the campaign's privacy metadata without changing body, approval time, mapping or Task history.
|
|
59
|
+
|
|
60
|
+
The #597 dispatch slice adds handoff-backed round advancement and budget-id-specific hard-cap admission. Until then, T1 refuses new follow-up rounds and hard-cap activation instead of silently running them without those checks. Drafts may still describe structurally valid limits. Study writes recheck recovery, shutdown and storage holds before commit; shutdown fences late screens and drains admissions within the existing bounded host-writer stop window.
|
|
61
|
+
|
|
62
|
+
The base `research_records` floor protects T1 stored state and raw runner-key compatibility. Later slices that introduce additional dispatch, wire or configuration requirements add their own recovery floors before exposing them; advertising base storage support alone is not proof of those later capabilities.
|
|
63
|
+
|
|
64
|
+
## Campaigns (#930)
|
|
65
|
+
|
|
66
|
+
### ResearchCampaignV1
|
|
67
|
+
|
|
68
|
+
| Field | Type and bound |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `id` | hub-issued `rc-<12 hex>`; stable across revisions |
|
|
71
|
+
| `parentTaskId` | existing task id in this project; at most one active campaign per parent |
|
|
72
|
+
| `revision` | integer from 1; each revision is immutable once stored |
|
|
73
|
+
| `status` | `draft`, `approved`, `superseded`, `closed` |
|
|
74
|
+
| `question` | long text |
|
|
75
|
+
| `scope` | long text |
|
|
76
|
+
| `exclusions` | at most 32 short texts |
|
|
77
|
+
| `sourceIds` | at most 64 `src:<16 hex>` ids, each known to `SourceRegistry` |
|
|
78
|
+
| `units` | 1 to 32 units |
|
|
79
|
+
| `deliverableSections` | at most 32 `{ id, title, ownerUnitId }`; each owner names one unit with purpose `synthesis` |
|
|
80
|
+
| `stopPolicy` | `{ maxUnits (1..32), maxFollowUpRounds (0..5), deadline (ISO time, at most 90 days after approval) }` |
|
|
81
|
+
| `budget` | optional `{ id (an existing ExecutionBudget id), hardCap: resources to enforce, possibly empty }`; reference ids retain the ledger's exact 1..200-character spelling rather than the new-id grammar; see "Budget support map" |
|
|
82
|
+
|
|
83
|
+
A unit: `{ id, round (integer, 0 for the initial round), question (long text), perspective (short), requiredOutput (short), sourceIds (subset of the campaign's, at most 16), dependsOn (unit ids, at most 16, acyclic, never a later round), purpose: "discovery" | "independent_check" | "analysis" | "synthesis", adoptTaskId?, taskId? }`. `adoptTaskId` names an approved intake child to bind. `taskId` is bound by the hub on activation, never supplied by a contract.
|
|
84
|
+
|
|
85
|
+
Rounds: a follow-up round is a set of units with `round = n + 1`, added only by a revision. Validation refuses a revision that adds round n+1 units while any round-n discovery unit has neither a complete handoff nor an `insufficientEvidence` reason, and refuses a round above `stopPolicy.maxFollowUpRounds`. A unit's round is stored in `research_units.round`, so the dispatch gate and restart read it rather than recompute it.
|
|
86
|
+
|
|
87
|
+
### Lifecycle
|
|
88
|
+
|
|
89
|
+
1. Draft: `Tasks.studyDraft(by, { parentTaskId, contract })` by the person (CLI `ahub study draft`) or a configured planner (`hub_study_draft`, checked with `planningAuthority`). It validates the whole contract and stores revision 1 as `draft`. A draft creates no task and dispatches nothing.
|
|
90
|
+
2. Revise: `Tasks.studyRevise(by, { id, revision, contract })` (`ahub study revise` or `hub_study_revise`, same authority as draft) stores revision n+1 as `draft`; `revision` must equal the latest, or it refuses as stale. A revision may enlarge scope, units or the stop policy only as a draft; only approval activates it.
|
|
91
|
+
3. Approve: `Tasks.studyApprove(by, { id, revision })`, person only (console role). It runs in three phases.
|
|
92
|
+
- Prepare (async, outside any transaction). Validate and screen the revision's units with the same `withholds` path `Tasks.propose` uses. Resolve every source, intake child, task and dependency ref, and build one child spec per unbound unit (title, detail with a `Study: <campaign id> unit <unit id>` line, class, refs, deps). No write happens here.
|
|
93
|
+
- Commit (one synchronous `board.activateCampaign(id, revision, prepared)` inside `board.atomic`, the `board.splitBacklog` precedent). It re-checks the revision is still the latest `draft` and that retained or adopted tasks still have their captured flow revisions. Previously bound stable units reuse their task ids. An intake child may be adopted only if unbound to another campaign/unit; its task is not rewritten. Only unbound person-authored units create new children with synchronous `Board.propose` as `backlog: true` cards, with deps from `dependsOn` mapped to task ids. It writes the unit-to-task mapping rows and the revision's `approved` status, and marks the previous approved revision `superseded`, all in the same transaction. If anything has changed since Prepare, it throws and nothing is written.
|
|
94
|
+
- Publish (after commit). The board's ordinary post-commit change notifications go out. The new children are offered through the existing planner pull path; the campaign does not assign owners itself.
|
|
95
|
+
Re-running approve on an approved revision returns the stored mapping and creates nothing. A crash between commit and publish loses nothing: the mapping and the backlog cards are committed, and the ordinary planner pull picks the cards up after restart. Restart never re-creates children.
|
|
96
|
+
4. Close: `Tasks.studyClose(by, { id, revision, reason })`, person or the campaign's planner. A closed campaign dispatches nothing more; its tasks keep their own states.
|
|
97
|
+
|
|
98
|
+
A person-approved revision may drop a dependency or a unit. The omission is recorded in that revision's history note. Task approval stays the only event that releases a dependent; a campaign never approves a task.
|
|
99
|
+
|
|
100
|
+
### Dispatch gate
|
|
101
|
+
|
|
102
|
+
Before the hub offers, assigns or pulls a task that is bound to a campaign unit, `Tasks` synchronously re-reads the campaign. It holds the task with a reason, and does not dispatch it, when:
|
|
103
|
+
- the bound revision is not `approved` (superseded, closed or revoked);
|
|
104
|
+
- the deadline has passed;
|
|
105
|
+
- the unit's stored `round` exceeds `maxFollowUpRounds`, or the approved revision's unit count exceeds `maxUnits` (both re-read from the tables, never from the request).
|
|
106
|
+
|
|
107
|
+
The check runs after any asynchronous screening, immediately before the dispatch write. Holding a task never kills a native turn already running.
|
|
108
|
+
|
|
109
|
+
### Coverage and stop projection
|
|
110
|
+
|
|
111
|
+
`studyProjection(campaign)` is pure and computes, per unit:
|
|
112
|
+
- its bound task's state, and whether its handoff is present and complete;
|
|
113
|
+
- the sources it covers, and any source assigned to more than one `discovery` unit (shown as a duplicate assignment);
|
|
114
|
+
- `independent_check` units, labeled as intentional independent review, never as duplicates.
|
|
115
|
+
|
|
116
|
+
Closing a discovery round needs every discovery unit to have a complete handoff, or a recorded `insufficientEvidence` reason on that unit. A model's statement that it is done is not coverage.
|
|
117
|
+
|
|
118
|
+
### Handoffs
|
|
119
|
+
|
|
120
|
+
`ResearchHandoffV1`: `{ campaignId, revision, unitId, taskId, evidence: [{ sourceId, locator (short) }] (at most 64), unresolved (at most 16 short), contradictions (at most 16 { sourceIds, note (short) }), limitations (at most 16 short), insufficientEvidence? (short) }`. It is recorded by the bound task's current owner with `hub_study_handoff` while that task is in progress. The latest handoff per unit is kept, and earlier ones stay in history. Validation reports every missing required field at once.
|
|
121
|
+
|
|
122
|
+
### Budget support map
|
|
123
|
+
|
|
124
|
+
A campaign with a `budget` shows, per unit and per resource (`model_calls`, `tool_calls`, `elapsed_ms`, `tokens`), one label:
|
|
125
|
+
- `enforceable` only when every condition is proven from current state:
|
|
126
|
+
- the budget exists and is enabled;
|
|
127
|
+
- the unit's task has an owner;
|
|
128
|
+
- that owner is in the budget's `peers` and is instrumented (today `pi` and `local`);
|
|
129
|
+
- the resource has a limit;
|
|
130
|
+
- the scope covers the task: a `kind: "run"` budget covers every task of its peers, and a `kind: "task"` budget covers only its `taskId`. `ExecutionBudget.applies([taskId], owner)` is the proof.
|
|
131
|
+
- `unknown` when the unit has no owner yet, or the budget cannot be read.
|
|
132
|
+
- `advisory` when the scope or peer does not cover the unit; usage is shown for attribution only.
|
|
133
|
+
- `unavailable` for `tokens` (the ledger denies them as `unknown_usage`) and for an uninstrumented owner.
|
|
134
|
+
|
|
135
|
+
Display is not enforcement. A contract that wants enforcement says so with `budget: { id, hardCap: [resources] }` in place of the bare `executionBudgetId`. `hardCap` lists the resources that must be enforced for every unit, as stored intent, and it may be empty for an advisory-only budget. With a non-empty `hardCap`:
|
|
136
|
+
- Activation refuses unless all of these hold:
|
|
137
|
+
- budget `id` exists and is enabled;
|
|
138
|
+
- every `hardCap` resource has a limit and is not `tokens`;
|
|
139
|
+
- the budget's `peers`, intersected with instrumented peers, is not empty.
|
|
140
|
+
That intersection is the campaign's eligible owner set, stored with the revision.
|
|
141
|
+
- The dispatch gate and every owner change for a campaign-bound task make the same id-specific check immediately before the write: the offer, the assignment, a planner pull, a sweep reassignment, `hub_task_assign` and an owner handoff. The chosen owner must be in the stored eligible set, and `ExecutionBudget.applies([taskId], owner)` must include this budget `id`, with every `hardCap` resource still limited. If any part fails or cannot be read, the task is held with the reason, or the assignment is refused, and nothing is offered to an uncovered owner. A budget that is disabled or removed after activation holds every not-yet-started unit the same way.
|
|
142
|
+
- A unit already running when the budget changes keeps running; the hold applies from its next dispatch or owner change.
|
|
143
|
+
|
|
144
|
+
V1 uses the existing ledger as is. A `kind: "run"` budget is peer-wide, so unrelated work by those peers spends the same budget, and the projection says so. Two campaigns that name one budget share it, and neither resets it. Activation refuses a requested hard cap on an `unavailable` cell, and refuses a campaign-exclusive hard cap entirely, until the ledger gains task membership in a separate reviewed change. Stop and budget limits block new dispatch only.
|
|
145
|
+
|
|
146
|
+
## Experiments (#932)
|
|
147
|
+
|
|
148
|
+
### ExperimentProtocolV1
|
|
149
|
+
|
|
150
|
+
Stored by content: `protocolHash = sha256(canonical JSON of the body)`.
|
|
151
|
+
|
|
152
|
+
| Field | Type and bound |
|
|
153
|
+
|---|---|
|
|
154
|
+
| `campaignId`, `campaignRevision` | an approved campaign revision in this project |
|
|
155
|
+
| `claimIds` | at most 32 ids (free ids until #931 defines claims) |
|
|
156
|
+
| `inputs` | at most 64 `{ sourceId, sha256 }`; the hash must equal the registry's |
|
|
157
|
+
| `code` | `{ commit: 40 hex }` or `{ deliverable: { id, sha256 } }` |
|
|
158
|
+
| `environment` | `{ descriptor (long text), hash? (64 hex) }`; variable names allowed, values refused |
|
|
159
|
+
| `method` | long text |
|
|
160
|
+
| `parameters` | at most 64 keys; values are scalars or lists of at most 64 scalars |
|
|
161
|
+
| `seeds` | at most 256 integers |
|
|
162
|
+
| `evaluator` | `{ id, version, hash? }` |
|
|
163
|
+
| `metrics` | at most 32 `{ name, unit, direction: "higher" | "lower" }` |
|
|
164
|
+
| `role` | `baseline` or `variant`, with `variantOf?` (a protocol hash) |
|
|
165
|
+
| `splits` | at most 16 short ids |
|
|
166
|
+
| `resourceLimits` | at most 16 `{ name, value, unit }` |
|
|
167
|
+
| `sealed` | `declared` boolean; verified only by a runner receipt (below) |
|
|
168
|
+
|
|
169
|
+
Every protocol also carries `lineage` (a short id, stable across revisions of one experiment) and `supersedes?` (the previous protocol hash in that lineage). A protocol is immutable, so a change is a new protocol with a new hash.
|
|
170
|
+
|
|
171
|
+
Protocol approval:
|
|
172
|
+
- `ahub study approve-protocol <hash>` is person only (console role). It writes one `research_protocol_approvals` row: `(protocol_hash, evaluator_hash, approved_by, at)`.
|
|
173
|
+
- An approval applies to exactly that protocol hash, and to that evaluator identity hash (sha256 of the canonical `evaluator` object).
|
|
174
|
+
- A changed protocol has a new hash and so no approval. Nothing copies an approval forward.
|
|
175
|
+
- Consumers:
|
|
176
|
+
- The comparison projection marks a pair `approved` only when both protocols have approvals.
|
|
177
|
+
- `hub_experiment_show` shows each attempt's protocol as `approved` or `unapproved`.
|
|
178
|
+
- Recording against an unapproved protocol is allowed, and shown as such.
|
|
179
|
+
|
|
180
|
+
### Comparison plan
|
|
181
|
+
|
|
182
|
+
`ComparisonPlanV1`, also stored by hash: `{ campaignId, fixed: { inputs, splits, evaluator, metrics }, varying: subset of ["code", "parameters", "seeds", "role"], rule: { kind: "tolerance", value } | { kind: "test", name (short), alpha } }`. Two attempts compare under a plan only when their protocols match every `fixed` field exactly and differ only in `varying` fields. Otherwise the projection names the first differing field and marks the pair incompatible; it still shows both. The hub never declares a best result.
|
|
183
|
+
|
|
184
|
+
### ExperimentAttemptV1
|
|
185
|
+
|
|
186
|
+
- `attemptId`: hub-issued `ra-<12 hex>`. `idempotencyKey`: caller-supplied, at most 64 bytes, unique per project. A second record call with the same key and the same body returns the existing attempt; the same key with a different body refuses.
|
|
187
|
+
- `protocolHash`, `taskId` (an existing task in this project), and optional `retryOf` or `reproducesAttemptId`. A retry is always a new attempt linked by `retryOf`; `reproducesAttemptId` is declared and never set by the hub.
|
|
188
|
+
- Every attempt has `phase: "development" | "final"`, set when it is created and never changed. A `final` attempt is the evaluation of record for its protocol.
|
|
189
|
+
- Each event carries `{ seq, kind, provenance, body, at }`, where `at` is the hub's time of recording. The kinds:
|
|
190
|
+
- `start`: `{ startedAt? }`, declared unless host evidence supplies it.
|
|
191
|
+
- `end`: `{ status: "succeeded" | "failed" | "cancelled", exitStatus?, reason? (short), run? (a check-runner run id) }`. At most one per attempt; a second `end` refuses.
|
|
192
|
+
- `metric`: `{ name, value }`. The name must exist in the protocol's `metrics`.
|
|
193
|
+
- `output`: deliverable claims. If the hub hashes them through `hashDeliverables`, they are `host-observed`; declared hashes stay `declared`. An inaccessible file is `unknown`.
|
|
194
|
+
- `note`: short text.
|
|
195
|
+
- `attestation`: a verified runner receipt or a person attestation.
|
|
196
|
+
- Status is a pure fold over the events:
|
|
197
|
+
- `declared` with no `start` and no `end`;
|
|
198
|
+
- `running` after `start` and before `end`;
|
|
199
|
+
- the `end` status once there is an `end`;
|
|
200
|
+
- `unknown` when there is no `end` and the attempt's task is approved, cancelled or closed.
|
|
201
|
+
- Post-hoc: a protocol is marked `post-hoc` when, at its recording time, its lineage already has a `final` attempt with an `end` event. That is, it was written after a final evaluation of an earlier protocol in the same lineage. The projection reads only `seq` and the recording order, so the fold is deterministic.
|
|
202
|
+
- Failed, cancelled and unknown attempts stay in every all-attempt count. Task approval and experiment success are separate fields.
|
|
203
|
+
|
|
204
|
+
### Host evidence
|
|
205
|
+
|
|
206
|
+
- A check-runner receipt (#793) supplies `host-observed` exit status (`exit`), `timedOut`, `cancelled`, duration (`duration_ms`) and receipt creation time (`createdAt`, from `created`) when an `end` event names its `run`. The hub reads `stateDir/checks/<run>.json` itself, and requires `status: "finished"`, the same task, and a `sha` equal to the protocol's `code.commit`. The receipt records no separate start or end time, so `startedAt` and `endedAt` stay `unknown` (or declared) for such an attempt. Extending the run receipt with timestamps is a separate change.
|
|
207
|
+
- Output hashes are host-observed only when the hub hashed the file.
|
|
208
|
+
- An agent-supplied hash is never host evidence.
|
|
209
|
+
|
|
210
|
+
## Runner attestation (#1018)
|
|
211
|
+
|
|
212
|
+
### Registration
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
"research": { "runners": { "<name>": { "publicKey": "<base64 Ed25519 SPKI DER>", "projects": ["<projectId>", ...], "revoked": false } } }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- This block lives only in the untracked `.agenthub/config.local.json`. A tracked or shared file naming it throws at load, the `checks.remote` precedent.
|
|
219
|
+
- It is added to `ALL_MACHINE_LOCAL` and is a structural tier-2 key for `ahub settings edit`. Its writes are person only.
|
|
220
|
+
- `name` follows the id rule. At most 16 runners.
|
|
221
|
+
- Revocation sets `revoked: true`. The key is kept, so old receipts still show who signed them, but a revoked key verifies nothing new.
|
|
222
|
+
- The hub stores no private key.
|
|
223
|
+
|
|
224
|
+
### AttemptReceiptV1
|
|
225
|
+
|
|
226
|
+
The runner signs `"agent-hub.runner-receipt/v1\0" + canonical JSON of the receipt body` with Ed25519. The hub verifies with `node:crypto`.
|
|
227
|
+
|
|
228
|
+
The body is `{ receiptId, runner, projectId, attemptId, protocolHash, codeCommit, imageDigest, containers: [{ id, role: "candidate" | "evaluator" | "other" }], mounts: [{ container, path, readOnly }], network: "none" | "restricted" | "open", resourceLimits, startedAt, endedAt, exitStatus, outputs: [{ name, sha256 }], metricsFile?: { name, sha256 } }`.
|
|
229
|
+
|
|
230
|
+
Verification refuses before any write when any of these holds:
|
|
231
|
+
- the runner is unknown or revoked;
|
|
232
|
+
- `projectId` is not in the runner's `projects`;
|
|
233
|
+
- the signature fails;
|
|
234
|
+
- the attempt is missing, in another project, or bound to a different `protocolHash`;
|
|
235
|
+
- the receipt is over its bounds.
|
|
236
|
+
|
|
237
|
+
Each mount also carries `purpose: "evaluator-code" | "evaluator-data" | "candidate-output" | "other"`.
|
|
238
|
+
|
|
239
|
+
A verified receipt appends one `attestation` event, and its fields are `runner-attested`. `sealed` becomes verified only when every condition holds:
|
|
240
|
+
- at least one container has role `candidate` and at least one has role `evaluator`, and their ids are disjoint and unique;
|
|
241
|
+
- every mount's `container` names a listed container;
|
|
242
|
+
- at least one mount has purpose `evaluator-code` or `evaluator-data` and is on an evaluator container;
|
|
243
|
+
- every `evaluator-code` and `evaluator-data` mount is `readOnly`, and none of them is on a candidate container.
|
|
244
|
+
|
|
245
|
+
Missing, empty or inconsistent isolation evidence leaves `sealed` declared or unverified, never verified.
|
|
246
|
+
|
|
247
|
+
A receipt with a known `receiptId` and identical bytes is a no-op. The same `receiptId` with different bytes refuses.
|
|
248
|
+
|
|
249
|
+
### Delivery
|
|
250
|
+
|
|
251
|
+
- Agents relay receipts with `hub_experiment_record { kind: "receipt", receipt, signature }`. The signature, not the caller, is the authority, so any task owner may relay.
|
|
252
|
+
- The person may also run `ahub study receipt <file>`.
|
|
253
|
+
- The hub never contacts the runner.
|
|
254
|
+
|
|
255
|
+
## Storage
|
|
256
|
+
|
|
257
|
+
All new tables live in `hub.db`, created in the `Board` constructor with `CREATE TABLE IF NOT EXISTS`. Writes use `board.atomic`.
|
|
258
|
+
|
|
259
|
+
| Table | Key | Content |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `research_campaigns` | `(id, revision)` | status, parent task id, body JSON, body sha256, approved_by, approved_at |
|
|
262
|
+
| `research_units` | `(campaign_id, revision, unit_id)` | round, task id, purpose, adopted (boolean) |
|
|
263
|
+
| `research_protocol_approvals` | `(protocol_hash)` | evaluator hash, approved_by, at |
|
|
264
|
+
| `research_handoffs` | `(campaign_id, unit_id, seq)` | task id, body JSON, by, at |
|
|
265
|
+
| `research_protocols` | `hash` | campaign id and revision, body JSON, by, at |
|
|
266
|
+
| `research_plans` | `hash` | campaign id, body JSON, by, at |
|
|
267
|
+
| `research_attempts` | `attempt_id`, unique `(project, idempotency_key)` | protocol hash, task id, phase, retry_of, reproduces, by, at |
|
|
268
|
+
| `research_attempt_events` | `(attempt_id, seq)`, unique `receipt_id` when present | kind, provenance, body JSON, at |
|
|
269
|
+
|
|
270
|
+
`board.hasResearchState()` reports whether any row exists. Readback is paginated: at most 50 rows per call, with a cursor.
|
|
271
|
+
|
|
272
|
+
## Wire names
|
|
273
|
+
|
|
274
|
+
| Surface | Name | Who |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| CLI | `ahub study draft <parent> <contract.json>`, `study revise <id> <revision> <contract.json>`, `study approve <id> <revision>`, `study close <id> <revision> --reason`, `study show [<id> | --after <id>] [--json]`, `study experiments [--campaign <id>] [--json]`, `study attest <attempt> <field> <value>`, `study receipt <file>` | approve, attest and receipt are person only; draft and revise are person or planner; show is anyone |
|
|
277
|
+
| Hub tools | `hub_study_draft` and `hub_study_revise` (configured planner only, through `planningAuthority`; they store drafts and never approve), `hub_study_show` (read-only; no id lists this project's campaigns as receipts, one page of at most 50 as `{ campaigns, next? }`, and `after` continues from the `next` of the previous page), `hub_study_handoff` (bound task owner), `hub_experiment_record` (`kind`: `protocol`, `plan`, `attempt`, `event` or `receipt`; task owner), `hub_experiment_show` (read-only) | none of them approves or attests |
|
|
278
|
+
| CLI (person only) | `ahub study approve-protocol <hash>` | person |
|
|
279
|
+
| Control | `study` (console role, CLI only) | person |
|
|
280
|
+
| Feature | `research_records` | recovery gate |
|
|
281
|
+
| Config | `research.runners` | untracked config.local.json only |
|
|
282
|
+
|
|
283
|
+
`ahub research`, the opt-in telemetry command, is unchanged.
|
|
284
|
+
|
|
285
|
+
## Delivery slices
|
|
286
|
+
|
|
287
|
+
Each slice amends this spec in its own PR if it must differ, and updates `docs/dogfood.md`. All of them touch `tasks.ts` or `board.ts`, so they run one at a time on those files.
|
|
288
|
+
|
|
289
|
+
| Task | Issue and ACs | Scope |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| #596 | #930 T1, AC1 and AC2 | the `research-campaign.ts` schema and validation (rounds included); the campaign and unit tables; `studyDraft`, `studyRevise`, the three-phase `studyApprove` with `board.activateCampaign`, and `studyClose`; intake adoption; the replay, crash-between-commit-and-publish and restart tests; the `research_records` feature gate |
|
|
292
|
+
| #597 | #930 T2, AC3 and AC4 | the dispatch gate on stored rounds; round advancement validation; the coverage and stop projection; the budget support map with its `applies` proof; the stored `hardCap` intent and eligible owner set, with refusal at activation and the id-specific check at every dispatch and owner change (offer, assign, pull, sweep reassign, handoff); the two-campaign shared-ledger fixture |
|
|
293
|
+
| #598 | #932 T1, AC1 and AC2 | the `research-experiments.ts` protocol (with lineage), attempt (with phase) and event schemas; the status fold; the provenance levels; the protocol, attempt and event tables; `hub_experiment_record` for protocol, attempt and event; host evidence from check receipts (exit, duration and receipt creation time only) and deliverable hashes |
|
|
294
|
+
| #599 | #932 T2, AC3 and AC4 | comparison plans and compatibility; the protocol approval table and `ahub study approve-protocol` with its consumers; idempotency and retry linkage; the post-hoc marking by lineage |
|
|
295
|
+
| #600 | #1018, AC1 to AC5 | the `research.runners` config; `research-runner.ts` receipt verification; receipt delivery; the sealing rule with its negative cases (no evaluator, empty mounts, a dangling container ref, an evaluator mount on a candidate); fixtures with a test key pair |
|
|
296
|
+
| #601 | #930 T3, AC5 and AC6 | `ahub study` draft, revise, approve, close and show; `hub_study_draft`, `hub_study_revise`, `hub_study_show` and `hub_study_handoff`; the privacy stubs; role guidance in `templates/`; the three-source, two-peer example |
|
|
297
|
+
| #602 | #932 T3, AC5 and AC6 | `ahub study experiments`, `attest` and `receipt`; `hub_experiment_show`; the synthetic CPU-only fixture; the rerun checklist in `docs/profiles.md` |
|
|
298
|
+
|
|
299
|
+
## Departures from the issues
|
|
300
|
+
|
|
301
|
+
- Runner registration is person only in V1. #1018 proposed "the person or conductor". A public key is a trust root, so the conductor path is deferred until a reviewed change gives it the same confirmation as other trust settings.
|
|
302
|
+
- Claim ids in a protocol are free ids until #931 defines claims; they are not validated against a claim store.
|
|
303
|
+
- `ahub study attest` covers `person-attested` fields. #932 names a "person attestation" without a command.
|
|
304
|
+
- The #601 list is paged at the Board query (`LIMIT`, at most 50 rows, id-ordered cursor), as the readback bound requires, so its reply is `{ campaigns, next? }` rather than a bare array; the feature is unreleased, so no caller depends on the array. `ahub study show --after <id>` continues a list. `--live-test` refuses every `study` form but `show`.
|
|
305
|
+
- `hub_study_handoff`, `hub_study_draft` and `hub_study_revise` are new tools. #930 lists only `hub_study_show`, but a handoff needs a write path that is not a task completion, and a planner needs a draft route that is not the person-only CLI.
|
|
306
|
+
- Protocol approval is the person-only `ahub study approve-protocol`; #932 asks for approvals bound to hashes without naming the operation.
|
|
307
|
+
- The budget support map adds an `unknown` label for a unit with no owner yet, since enforceability depends on the owner peer.
|
|
308
|
+
- Host evidence from a check run covers exit status, duration and receipt creation time only; the run receipt has no start or end timestamp.
|
|
309
|
+
|
|
310
|
+
## Out of scope
|
|
311
|
+
|
|
312
|
+
#931 evidence links and claim judgment; executing, scheduling or provisioning experiments; a campaign-exclusive budget ledger; holiday calendars; new search, crawling or source providers; any third-party dependency.
|
|
313
|
+
|
|
314
|
+
## T2 dispatch and coverage implementation boundary (#597)
|
|
315
|
+
|
|
316
|
+
- `research_dispatch` is an additive recovery floor. Any stored campaign already carries stop policy intent, including a campaign activated before this slice. A target without the feature refuses while any campaign, dispatch activation or handoff exists. The base `research_records` floor remains required too. Dispatch metadata and handoff content have their own integrity digest when present.
|
|
317
|
+
- The latest stored revision controls every offer, planner pull, owner change and acceptance. A newer draft holds older bound work until that draft is approved; a closed or omitted unit remains held. This gate never interrupts a native turn already running. A revision cannot reauthorize a changed unit definition without a new unit id.
|
|
318
|
+
- Approval stores the instrumented eligible owner set for the named budget. Immediately before dispatch or an owner change, current membership, task scope and each required limit must still be proven for that same budget id. Another budget covering the task proves nothing. The existing peer-wide ledger is shared, never reset by campaign activation. Exhausted requested cells hold dispatch. A model-call cap alongside a token ceiling is unavailable because the existing meter cannot prove token usage; activation refuses it.
|
|
319
|
+
- A complete handoff covers every source assigned to the unit; all required structural fields remain mandatory. `insufficientEvidence` is a separate way to settle a discovery unit. New round n+1 units require stored round-n discovery bindings and complete or insufficient-evidence handoffs for each. A proposed contract cannot supply its own completion evidence.
|
|
320
|
+
- A study-held owner offer is persisted and re-offered after approval (#603). When a hold makes the bus discard a queued owner offer, `Tasks.relevant` writes a `research_dropped_offers` row; approving a revision re-offers each retained, still-proposed unit task through `sendTask` and clears the row once the offer is published. `research_dropped_offers` is its own additive recovery label: a target that lacks it refuses while any row exists, and a row no longer raises only the `research_dispatch` floor. The row joins the dispatch integrity digest only when rows exist.
|
|
321
|
+
- `Tasks.studyHandoff` accepts only the current in-progress bound owner and screens outside the transaction; stored ownership, revision, sources and flow are rechecked at the write. `Tasks.studyShow` exposes the pure projection and removes source identifiers from private views. Public tool and CLI wiring remains in #601.
|
|
322
|
+
|
|
323
|
+
## Experiment T1 implementation boundary (#598)
|
|
324
|
+
|
|
325
|
+
- The record envelope for a protocol is `{ kind: "protocol", taskId, protocol }`; `recordedForTaskId` is immutable metadata outside the protocol's content hash. A writer must be that task's current owner while it is `in_progress`. Attempt and event writes use the attempt task's current owner under the same condition. No console override is introduced by this slice.
|
|
326
|
+
- Both the recording task and every attempt task must be unit tasks of the protocol's same current approved campaign revision, resolved by `Board.researchDispatchBinding`. Different units in that campaign may share a protocol. Removed units, a pending draft, foreign project references and a protocol from an earlier revision refuse before effects. New attempts and events against an older revision's protocol remain refused; the upgrade is an explicit membership+source fence. Historical comparison reads and person approvals are allowed as described in the T2 boundary.
|
|
327
|
+
- Protocol inputs are a subset of the campaign revision's source ids, not only the unit's source ids, and every registry hash must match exactly. A code deliverable reference must name a matching host record on the recording task. Privacy follows the task, protocol inputs and the current campaign; missing privacy evidence fails closed. All asynchronous screens, receipt reads and file hashes are followed by fresh authority, source and campaign checks; the transaction rechecks the task flow and campaign binding.
|
|
328
|
+
- Environment descriptors and their optional text-hash claims are always `declared`. The lexical screen rejects assignment forms, Bearer credentials and known secret shapes before writing; flagged text is refused, never stored redacted. It detects those forms only and never proves secret absence. The upgrade path is structured key/version fields or #1018 runner evidence. The hash labels the declared descriptor text; it is not a measured host environment hash.
|
|
329
|
+
- Output `{ id, sha256? }` is a declaration. Output `{ path, format, version? }` requests host hashing through the existing #509 reader and all its path, file-identity, deny and byte bounds. A missing, unreadable or refused path leaves the hash unknown, with no declared fallback. Output path privacy is classified independently of measurement using the #509 PII, deny and hub-state policies on both declared and resolved names. A protected path, or an unavailable classification, makes the event and whole attempt private even if hashing fails. The classification is retained across the asynchronous read. Experiment classification opts into fail-closed handling of an exhausted bounded alias-discovery budget; ordinary path-policy callers and the discovery ceiling stay unchanged. Neither record ingestion nor readback executes a command.
|
|
330
|
+
- Host measurements occupy separate immutable `research_attempt_observations` rows keyed by `(attempt_id, event_seq)` and linked to the declared end's run id or the output hash request's derived deliverable id. The declared event is never rewritten and no second end is created. Readback puts declared and measured values side by side with their own provenance. A check receipt must be finished and match the task and protocol commit; only exit status, timeout/cancellation, duration and its receipt creation timestamp (`createdAt`) are measured. Start/end timestamps remain unknown unless declared. The original check output and tail are not stored.
|
|
331
|
+
- Attempt idempotency keys are unique within the project. Event idempotency keys are likewise project-wide, bind the attempt and exact declared body, and return the original immutable event and measurements on replay. A different body refuses. Retries and reproduction links remain declared associations, never independent-reproduction claims. The all-attempt denominator includes failed, cancelled and unknown rows. Pages carry at most 50 records; the event cursor and completeness flag identify partial evidence pages.
|
|
332
|
+
- A private event makes the whole attempt private across every page. Public readback retains ids, counts, state and provenance while stubbing protocol text, metric names, paths and event bodies. Raw research text never enters task history, notices or memory.
|
|
333
|
+
- `research_experiments` is additive to `research_records` and `research_dispatch`. A target lacking it refuses while any protocol, attempt, event or observation exists. A separate content-sensitive experiment integrity digest is present only when experiment state exists. No new public tool or CLI surface, comparison/approval projection (#599), runner trust (#600) or execution engine is added here.
|
|
334
|
+
|
|
335
|
+
## Experiment T2 comparison identity and approval binding (#599)
|
|
336
|
+
|
|
337
|
+
- Both protocols in a pair must have the comparison plan's `campaignId`. Only `campaignRevision`, `lineage` and `supersedes` are identity/order metadata excluded from structural compatibility. They remain part of each immutable protocol body and its content hash.
|
|
338
|
+
- The only predeclarable varying groups are `code` (commit or deliverable), `parameters`, `seeds`, and `role` together with `variantOf`. A difference in an undeclared varying group is incompatible.
|
|
339
|
+
- `inputs`, `splits`, `evaluator` and `metrics` (name, unit and direction) must match the plan's fixed values on both sides. Every other experimental field stays exact: `environment` (descriptor and hash), `method`, `claimIds`, `resourceLimits` and `sealed`. These fields cannot be declared as varying.
|
|
340
|
+
- The projection retains both members, names the deterministic first differing field when incompatible, and never ranks them or evaluates the declared tolerance/statistical test. Each compared attempt shows its own exact protocol hash and `campaignRevision`, with an explicit note that exclusion from compatibility does not hide its identity.
|
|
341
|
+
- Any changed protocol hash requires a fresh person approval, even if the new protocol is structurally compatible. Approval binds to that exact full protocol hash and the canonical evaluator identity hash; it never carries through a changed revision, lineage, supersession or variant link.
|
|
342
|
+
|
|
343
|
+
## T2 comparison implementation boundary (#599)
|
|
344
|
+
|
|
345
|
+
- `Tasks.experimentRecord` accepts `{ kind: "plan", taskId, plan }` only from the current in-progress owner of a unit in the current approved campaign. Fixed input source ids and hashes must resolve in that campaign. An identical hash replays only for its original recording task; changing the recording task refuses rather than rebinding it. Plans remain immutable and paginated in Board storage.
|
|
346
|
+
- `Tasks.experimentShow` accepts `{ planHash, baselineAttemptId, variantAttemptId, eventAfter?, limit? }` for a pair, keeping each attempt and the all-attempt campaign denominator. The projection names incompatibility and exact protocol hashes/revisions, never ranks results or sets a best result. A private plan or attempt withholds the comparison body too.
|
|
347
|
+
- `ahub study approve-protocol <hash>` uses the console-only `study_approve_protocol` operation. The person may approve a current or historical immutable protocol. A changed hash is always unapproved until its own explicit approval; compatibility does not carry approval. Each attempt shows `approval`, declared retry/reproduction links, and `independentlyReproduced: false`.
|
|
348
|
+
- New protocol insertion captures `research_protocol_context` in the same Board transaction: either no earlier ended final attempt in its lineage, or that attempt id and terminal seq. This is a statement of recording order only. Replaying a protocol never restamps it after a later evaluation. Older protocols without the row remain `unknown`; timestamps never infer missing order evidence.
|
|
349
|
+
- `research_comparisons` is a separate additive recovery floor for any comparison plan, protocol approval or protocol-context row. Its conditional integrity digest covers all three tables. The older experiment and dispatch guards and integrity fields remain unchanged. No command or model runs during these writes.
|
|
350
|
+
|
|
351
|
+
New attempts and events may be recorded only against a protocol of the current approved campaign revision. Historical immutable protocols remain available for comparison reads and the person's explicit exact-hash approvals. New recording against older protocols is deferred until an explicit membership+source fence is implemented.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# 0.25.0 release verification
|
|
2
|
+
|
|
3
|
+
This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.25.0 and no live TUI launch.
|
|
4
|
+
|
|
5
|
+
## Candidate
|
|
6
|
+
|
|
7
|
+
Version 0.25.0, prepared from main b3303e3c (16 commits since v0.24.0, 9 of them `feat`; bump rule: minor). Main then gained #1037 and #1043, merged into the release branch at 70f3a56f, so the release carries 18 commits since v0.24.0. The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
|
|
8
|
+
|
|
9
|
+
## Reviews
|
|
10
|
+
|
|
11
|
+
Pull requests named by the commits since v0.24.0: #1042, #1041, #1031, #1012, #1033, #935, #1032, #1035, #1028, #1029, #1027, #1025, #1024, #1022, #1020, #1023, #1021, #1019.
|
|
12
|
+
|
|
13
|
+
#1012 and #935 are the issues behind #1033 and #1032, not pull requests.
|
|
14
|
+
|
|
15
|
+
Each of the 16 pull requests merged with its exact-head `check` workflow green on ubuntu-latest and macos-latest (all four shards each), and none has an unresolved review thread. The claude[bot] review on each is the "extra usage unavailable" notice, not a review. The release session runs on a different hub from the agent-hub project board and an agent shell cannot run `ahub task list`, so for the PRs below that were not reviewed in this session, the hub review record is not read here. Their reviews are stated as the PR bodies and GitHub reviews record them.
|
|
16
|
+
|
|
17
|
+
Reviewed in this release session:
|
|
18
|
+
|
|
19
|
+
- #1032 (task 12, #935): claude approved 71df8835 with `hub_review`, and Codex found no major issues at 71df8835. A follow-up commit 0076d986 makes inbox links carry the project and selects it in the panel (Codex: no major issues at 0076d986). The merged head 8f88eb48 is the rebase of 0076d986 onto main with a CHANGELOG conflict resolved by keeping both lines (check run 41252842704). That is an approval at an earlier head; the content delta is the conflict resolution only.
|
|
20
|
+
- #1033 (task 13, #1012 Part 2): claude approved e990bc7 with `hub_review`. Codex then left threads at 73fc8aa0, bb59e518, b0d5c3be and 9700c060, and Copilot at 74bcfffd: card attribution, broadcast arcs, cap integrity, arc expiry, a stalled project blocking the scene, and the dogfood row. Each was fixed with a test and its thread answered and resolved. Codex found no major issues at the merged head 89108375 (check run 41260946811). The `hub_review` approval is at an earlier head; the later commits are those review fixes, read by claude before merge.
|
|
21
|
+
- #1035 (agent-mem 0.17.0 pin): prepared by claude for the coordinated release the person approved on 2026-11-01. The Codex code review completed at the merged head eecd7631 with no findings; there is no separate hub review (check run 41249599153).
|
|
22
|
+
|
|
23
|
+
Merged by other sessions, as recorded on the PRs:
|
|
24
|
+
|
|
25
|
+
- #1037 (task 606, #1036, distinguishable sessions and sender provenance): Codex, Copilot and claude[bot] reviewed 872c757c; their threads were answered and resolved at 4dbea3f3. Merged head b65c4504 (check run 41273051923), after the release branch point.
|
|
26
|
+
- #1043 (task 604, #1030, cross-project task handoff): Codex, Copilot and claude[bot] reviewed ce62908b; their threads were answered and resolved at 2b28fded. Merged head c3cc372e (check run 41275467577), after the release branch point.
|
|
27
|
+
|
|
28
|
+
- #1019 (native version-named receipts): read-only OCR delegation, no finding; merged head bb04e335 (check run 41214600952).
|
|
29
|
+
- #1020 (research campaign spec, hub task 595): reviewer codex per its body; merged head 1e62fb5e (check run 41216881998).
|
|
30
|
+
- #1021 (owner review delivery refresh): independent read-only review and OCR delegation, no defect; merged head 93201d56 (check run 41216437395).
|
|
31
|
+
- #1022 (calendar schedules, task 592): Codex threads at b86fc7ed were resolved; merged head a1134856 (check run 41221438344).
|
|
32
|
+
- #1023 (security sentence on generation review): read-only manual review of a one-sentence documentation change; merged head 0c57e9aa (check run 41217077321).
|
|
33
|
+
- #1024 (versioned campaign persistence): independent review found two defects, both fixed with regression tests; merged head dbd487f6 (check run 41225669180).
|
|
34
|
+
- #1025 (doctor memory readiness, task 591): merged head 96303c2a (check run 41226139512).
|
|
35
|
+
- #1027 (campaign dispatch gate): Codex threads at c0caf0a8 were resolved; merged head 1cdfd888 (check run 41236820463).
|
|
36
|
+
- #1028 (experiment records): independent Bugs/Security/Compliance review, no remaining blocker; merged head 2ec771c9 (check run 41243986451).
|
|
37
|
+
- #1029 (study-dropped owner offers): Codex threads at cda2ac34 were resolved; merged head 15acb90c (check run 41242811388).
|
|
38
|
+
- #1031 (`ahub study`, hub_study tools): Codex threads at e47fea8b and Copilot at 115c6af6 were resolved; merged head 77b1992f (check run 41260046135).
|
|
39
|
+
- #1041 (exact protocol approvals): independent code review, no Important finding; Copilot reviewed the merged head a05342e6 (check run 41270130168).
|
|
40
|
+
- #1042 (agent-mem 0.18.0 pin, board task 609): independent review of all 11 non-generated paths, no Important finding; merged head 42aefad3 (check run 41271493781).
|
|
41
|
+
|
|
42
|
+
## Native acceptance receipts
|
|
43
|
+
|
|
44
|
+
Receipts added under `docs/verification/` since v0.24.0: none.
|
|
45
|
+
|
|
46
|
+
This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.25.0`, person only, and is not recorded in this file. The tag run refuses without it.
|
|
47
|
+
|
|
48
|
+
## Document review
|
|
49
|
+
|
|
50
|
+
`node scripts/check-docs.mjs --require-fresh` reported 13 stale documents after release preparation. Each was read against the source commits since its stamp (9af51bfd; ce8da3ec for local-worker.md), corrected where needed in 92282ae3, and restamped at 92282ae3 with `bun scripts/release-prepare.ts restamp <document>... --reviewed`.
|
|
51
|
+
|
|
52
|
+
- `src/cli/help.ts`, corrected: `ahub setup` names the acon plugin registration (#1032), and `ahub doctor` names the memory capture rows read from `amem setup <tool> --json` (#1025). The `study` entries already matched `src/cli/study.ts`, and the `schedule` usage is unchanged by #1022. `launch.ts` and the native entries are unchanged in the range. `test/help.test.ts`, `test/completion.test.ts` and `test/schedule-cli.test.ts` pass.
|
|
53
|
+
- `README.md`, corrected: a research campaigns bullet, `cron`/`tz` schedules in Operations, and the release report link now points to this file. The acon plugin and Space paragraphs from #1032/#1033 match `src/ui/plugin.ts`; the README states no agent-mem pin.
|
|
54
|
+
- `docs/quickstart.md`, corrected: a new "Upgrading to 0.25.0" note covers the acon plugin and Space, the agent-mem 0.18.0 pin with the rule that every amem writer on one memory home must run 0.16.0 or later, doctor's memory capture rows, research campaigns, cron schedules, the owner re-file at a new head, and running `ahub setup` again after an upgrade (acon marks a changed module stale). The install line names v0.25.0.
|
|
55
|
+
- `docs/operations.md`, corrected: new sections "acon console plugin" and "Research campaigns: `ahub study`"; `ahub study show` in the authority table; doctor's unknown outcome; the owner re-file during review; and the `calendar_schedules` recovery gate. The confirmation list, the seven views, the Space caps (32/200/64) and the 3 s budget were checked against `src/ui/plugin.ts`, `src/hub/ui.ts` and `src/ui/office-space.ts`.
|
|
56
|
+
- `docs/security.md`, corrected: new sections on the acon plugin (no credential of its own, registration hash, confirm fingerprint is a request binding and not authentication, deny-only Pi/local approvals, #264-allowlisted Space) and on research campaigns (console-only approval, rechecks at the write, provenance never upgraded, privacy stubs, recovery refusal). The schedules trust boundary covers `cron` and `tz`.
|
|
57
|
+
- `docs/agent-notes/daemon.md`, corrected: scope lists the acon and `ahub study` files; the recovery floor lists the five `research_*` features; the acon bullet covers registration and the Space timing rules; a doctor memory capture bullet.
|
|
58
|
+
- `docs/agent-notes/tasks.md`, corrected: scope lists `research-campaign.ts` and `research-experiments.ts`; new campaign storage and experiments bullets; `study_approve_protocol` among the person ops.
|
|
59
|
+
- `docs/agent-notes/tests.md`, corrected: a test that runs `ahub doctor` through the CLI also sets `CODEX_HOME` and puts a fake `amem` first on PATH (#1025).
|
|
60
|
+
- `docs/agent-notes/README.md`, `adapters.md`, `budget.md`, `bus.md`, `local-worker.md`: unchanged. Their sources changed only through files whose rules live in daemon.md and tasks.md (study forms in `identity.ts`, `STUDY_TOOLS` in `capture.ts`, research integrity fields in `restart.ts`).
|
|
61
|
+
|
|
62
|
+
After #1037 and #1043 were merged into the release branch (70f3a56f), the same 13 documents were stale again and were reviewed for those two PRs only, then restamped:
|
|
63
|
+
|
|
64
|
+
- `src/cli/help.ts`, corrected: `ahub task handoff` gains `[--color=auto|always|never]`, and its description says state changes show in `ahub board --handoffs` (only an agent requester gets task messages; `Tasks.handoffEvent` skips the person). #1037 changed no CLI entry.
|
|
65
|
+
- `README.md`, corrected: one sentence each for cross-project handoff and for distinguishable sessions as provenance, not authentication.
|
|
66
|
+
- `docs/operations.md`, corrected: the handoff section now says the person gets no follow-back message, that `ahub task handoff` is Console, that a target in recovery or shutdown refuses, and that the target's PII patterns apply.
|
|
67
|
+
- `docs/security.md`, corrected: a new "Cross-project task handoff" section (same-user console connection, who may hand off, what crosses each way, the origin applies no PII patterns of its own and refuses private turns and `[pii]` tasks, recovery label) and a paragraph on what session identity and sender provenance do and do not guarantee.
|
|
68
|
+
- `docs/quickstart.md`, corrected: the 0.25.0 note gains a sentence each for #1043 and #1037.
|
|
69
|
+
- `docs/agent-notes/daemon.md` (scope, channel plugin project, sender provenance, the handoff recovery fence), `tasks.md` (scope and handoff invariants), `adapters.md` (launch ids read by shape for labels only), `bus.md` (`Envelope.session`) and `tests.md` (fixtures that run the real plugin set `cwd` to the realPath'd hub root), corrected. `README.md`, `budget.md` and `local-worker.md` of agent-notes, unchanged.
|
|
70
|
+
|
|
71
|
+
The AGENTS.md path map and the daemon.md and tasks.md entries in `docs/verified.json` now include the new files `src/ui/plugin.ts`, `src/ui/office-space.ts`, `src/cli/acon-register.ts`, `src/cli/study.ts`, `src/cli/study-output.ts`, `src/hub/research-campaign.ts` and `src/hub/research-experiments.ts`, plus the earlier unmapped `src/hub/schedules.ts` and `src/hub/folder-trigger.ts`, and from #1037/#1043 `src/hub/session-identity.ts`, `src/hub/provenance.ts`, `src/hub/channel-project.ts`, `src/hub/project.ts`, `src/hub/handoff.ts`, `src/cli/handoff-args.ts` and `src/cli/handoff-output.ts`. The CHANGELOG 0.25.0 section gained the entries that #1021, #1022, #1025, #1037, #1043 and the research PRs had not added.
|
|
72
|
+
|
|
73
|
+
## Checks
|
|
74
|
+
|
|
75
|
+
On the Mac at 96b09c59 (the release branch after the second restamp, with #1037 and #1043 merged in), with the worktree's own frozen install (Bun 1.4.2; `@staix/agent-mem` 0.18.0):
|
|
76
|
+
- `node scripts/check-docs.mjs --require-fresh` printed `docs: OK (0 stale documents)`.
|
|
77
|
+
- `bun x tsc --noEmit` was clean.
|
|
78
|
+
- `bun scripts/build.mjs --check` exited 0.
|
|
79
|
+
- `node scripts/check-package.mjs` printed `package: OK (710 files, @staix/agent-hub@0.25.0)`.
|
|
80
|
+
- `bun test test/help.test.ts test/completion.test.ts test/schedule-cli.test.ts`: 55 pass, 0 fail (before the merge-in); `bun test test/help.test.ts test/completion.test.ts`: 24 pass, 0 fail (after).
|
|
81
|
+
|
|
82
|
+
The unsharded full `scripts/check.sh` at 0b198f46 (before the merge-in) in a fresh dgx02 container checkout (`remote_dev.py verify`) reached 6661 passing tests and no failure, then hang-watch stopped it at its 960 s total bound (`cause=total`, quiet for 1 s), so it did not finish and is not claimed as a pass. The release PR's exact-head CI, sharded on both OSes, is the full gate.
|
|
83
|
+
|
|
84
|
+
## Known gaps, not blocking
|
|
85
|
+
|
|
86
|
+
- Release preparation was done by claude, not jcode: board task 14 was assigned to jcode, who accepted it but had not started when the person asked to proceed. The board could not move the task (reassignment needs the conductor role); jcode was told to stand down.
|
|
87
|
+
- The hub review record of the PRs merged by other sessions is not read here (see Reviews).
|
|
88
|
+
- No installation, running-hub upgrade or live acon check of 0.25.0 is claimed. The person's Mac acon node is checked after the publish and the coordinated upgrade.
|
|
89
|
+
- `src/hub/execution-budget.ts` and `src/hub/path-policy.ts` remain without a named agent note in the AGENTS.md map; they predate this range.
|
|
90
|
+
- `cliCommandLabel` labels `ahub study approve-protocol` as a bare `study` in the audit; harmless (no text leaks), left as a follow-up.
|
|
91
|
+
- The experiment surfaces in the research campaigns spec (`hub_experiment_record`, `hub_experiment_show`, `ahub study attest|receipt`, `research.runners`) are not shipped; the docs and CHANGELOG say so.
|