brainclaw 1.26.2 → 1.27.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/README.md +13 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-coordination.js +65 -1
- package/dist/commands/attempt-authority.js +80 -0
- package/dist/commands/harvest.js +140 -61
- package/dist/commands/loop.js +34 -0
- package/dist/commands/loops-handlers.js +87 -14
- package/dist/commands/mcp-catalog.js +42 -18
- package/dist/commands/mcp-schemas.generated.js +44 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +146 -76
- package/dist/core/agent-capability.js +1 -1
- package/dist/core/agentrun-reconciler.js +148 -22
- package/dist/core/agentruns.js +254 -29
- package/dist/core/assignment-request-schema.js +7 -0
- package/dist/core/assignment-sweeper.js +5 -3
- package/dist/core/assignments.js +131 -33
- package/dist/core/claim-request-schema.js +7 -0
- package/dist/core/claims.js +53 -2
- package/dist/core/dispatch-status.js +16 -6
- package/dist/core/dispatcher.js +51 -51
- package/dist/core/entity-operations.js +20 -0
- package/dist/core/events.js +4 -0
- package/dist/core/execution-adapters.js +160 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/harness-adapters/base.js +150 -0
- package/dist/core/harness-adapters/claude.js +39 -0
- package/dist/core/harness-adapters/codex.js +57 -0
- package/dist/core/harness-adapters/harvest.js +109 -0
- package/dist/core/harness-adapters/index.js +8 -0
- package/dist/core/harness-adapters/prompt-only.js +13 -0
- package/dist/core/harness-adapters/registry.js +48 -0
- package/dist/core/harness-adapters/result.js +33 -0
- package/dist/core/harness-adapters/types.js +2 -0
- package/dist/core/ideation-loop-close.js +25 -2
- package/dist/core/instruction-templates.js +3 -2
- package/dist/core/loop-turn-dispatch.js +207 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +476 -0
- package/dist/core/loops/attempt-generations.js +509 -0
- package/dist/core/loops/attempt-reservation.js +197 -35
- package/dist/core/loops/attempt-rollout.js +404 -0
- package/dist/core/loops/attempt-takeover.js +155 -0
- package/dist/core/loops/bootstrap-acquire.js +7 -3
- package/dist/core/loops/evidence.js +187 -0
- package/dist/core/loops/facade-schema.js +41 -10
- package/dist/core/loops/gate-policy.js +485 -0
- package/dist/core/loops/impl-bind.js +37 -79
- package/dist/core/loops/index.js +9 -0
- package/dist/core/loops/iteration-engine.js +31 -19
- package/dist/core/loops/kind-policies.js +90 -0
- package/dist/core/loops/lock.js +71 -13
- package/dist/core/loops/reconcile-turn.js +235 -18
- package/dist/core/loops/result-reducers.js +99 -10
- package/dist/core/loops/store.js +30 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +113 -2
- package/dist/core/loops/verbs.js +332 -99
- package/dist/core/loops/verify-command.js +31 -8
- package/dist/core/loops/workspace-digest.js +54 -0
- package/dist/core/review-loop-close.js +25 -3
- package/dist/core/review-loop-turn-dispatch.js +210 -161
- package/dist/core/runtime-signals.js +62 -25
- package/dist/core/schema.js +35 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +7 -6
- package/dist/facts.json +6 -5
- package/docs/cli.md +49 -1
- package/docs/concepts/attempt-authority.md +407 -0
- package/docs/concepts/evidence-attestations.md +135 -0
- package/docs/concepts/execution-contract.md +166 -0
- package/docs/concepts/harness-adapters.md +166 -0
- package/docs/concepts/ideation-loop.md +5 -4
- package/docs/concepts/loop-engine.md +302 -113
- package/docs/index.md +4 -1
- package/docs/integrations/codex.md +3 -3
- package/docs/integrations/mcp.md +59 -5
- package/docs/loops/debug.md +144 -0
- package/docs/loops/ideation.md +158 -0
- package/docs/loops/implementation.md +154 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +14 -5
- package/package.json +1 -1
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Attempt authority
|
|
2
|
+
|
|
3
|
+
`AttemptAuthority` is the kind-neutral execution authority used by every
|
|
4
|
+
worker-backed phase of the Loop Engine. It answers three questions that must
|
|
5
|
+
never be inferred from process IDs, mutable projections, or telemetry:
|
|
6
|
+
|
|
7
|
+
1. was the logical turn committed;
|
|
8
|
+
2. which physical generation may spawn;
|
|
9
|
+
3. which generation may settle the turn.
|
|
10
|
+
|
|
11
|
+
It is not a sixth loop protocol and it adds no event journal. Review, ideation,
|
|
12
|
+
implementation, research, and debug all use the same substrate. Loop events
|
|
13
|
+
remain the causal history; runtime events remain telemetry; Assignment and
|
|
14
|
+
AgentRun remain queryable projections.
|
|
15
|
+
|
|
16
|
+
## Identity model
|
|
17
|
+
|
|
18
|
+
One logical turn may now have several physical runs.
|
|
19
|
+
|
|
20
|
+
| Identity | Lifetime | Rule |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `turn_id` | logical turn | stable across takeover and retry |
|
|
23
|
+
| `assignment_id` | logical work assignment | stable across takeover and retry |
|
|
24
|
+
| `attempt_epoch` | physical generation | starts at `0`, increases by one |
|
|
25
|
+
| `run_id` | physical execution | fresh for every epoch |
|
|
26
|
+
| `launch_nonce` | launch fence | fresh and unpredictable for every epoch |
|
|
27
|
+
| `contract_hash` | execution contract | recomputed for the generation's run and workspace |
|
|
28
|
+
| `workspace_digest` | isolated workspace | binds the real workspace path to the turn and epoch |
|
|
29
|
+
|
|
30
|
+
For backward compatibility, the first worker phase to occupy a slot in a
|
|
31
|
+
protocol iteration uses the historical deterministic identity derived from
|
|
32
|
+
`(loop_id, slot_id, iteration)`. When the same slot is reused by a different
|
|
33
|
+
worker phase in that iteration, Brainclaw derives a versioned phase-qualified
|
|
34
|
+
identity from `(loop_id, slot_id, phase, iteration)`. The resolver adopts an
|
|
35
|
+
existing compatible legacy or phase-qualified reservation before minting
|
|
36
|
+
anything, so same-phase retries remain exactly-once and in-flight upgrades keep
|
|
37
|
+
their durable identity. A different logical phase is a different turn; a
|
|
38
|
+
takeover remains a new physical generation of the same turn.
|
|
39
|
+
|
|
40
|
+
Evidence for an AttemptAuthority v2 generation is accepted only when the full
|
|
41
|
+
tuple matches:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
(assignment_id, turn_id, attempt_epoch, run_id, launch_nonce,
|
|
45
|
+
contract_hash, workspace_digest)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Matching only `turn_id`, `run_id`, and `nonce` remains supported for legacy
|
|
49
|
+
reservations. Once a turn has a v2 generation chain, the complete tuple is
|
|
50
|
+
mandatory. An old worker can therefore finish late, but its output cannot
|
|
51
|
+
mutate the loop or the current AgentRun.
|
|
52
|
+
|
|
53
|
+
## Durable decisions
|
|
54
|
+
|
|
55
|
+
The original `TurnReservation` still owns logical commit and the immutable
|
|
56
|
+
generation-zero contract. AttemptAuthority v2 adds small immutable decision
|
|
57
|
+
cells under `.brainclaw/memory/loops/attempt-generations/<turn_id>/`; rollout
|
|
58
|
+
guards and signed ACKs live separately under
|
|
59
|
+
`.brainclaw/memory/loops/attempt-authority-v2/rollout/`:
|
|
60
|
+
|
|
61
|
+
- one initial-generation cell per turn;
|
|
62
|
+
- `launch(epoch)`, decided once as `crossed` or `revoked`;
|
|
63
|
+
- `close(epoch)`, decided once as `settled`, `takeover`, `retry`, or
|
|
64
|
+
`cancelled`;
|
|
65
|
+
- an optional `head.json`, which is only a rebuildable cache.
|
|
66
|
+
|
|
67
|
+
Settlement and takeover contend on the same `close(epoch)` cell. Exactly one
|
|
68
|
+
wins. A takeover embeds the complete successor generation in that cell, so a
|
|
69
|
+
crash after the decision but before projections is repaired by replaying the
|
|
70
|
+
same successor; it never mints another run.
|
|
71
|
+
|
|
72
|
+
The publish protocol is intentionally conservative on Windows and POSIX:
|
|
73
|
+
|
|
74
|
+
1. create a temporary file on the same volume;
|
|
75
|
+
2. write, flush, `fsync`, and close it;
|
|
76
|
+
3. publish the final path with a hard-link create-if-absent;
|
|
77
|
+
4. read and adopt the incumbent on `EEXIST`.
|
|
78
|
+
|
|
79
|
+
There is no rename fallback. If the filesystem cannot provide this no-clobber
|
|
80
|
+
primitive, v2 mutations fail closed. Orphan temporary files are
|
|
81
|
+
non-authoritative and safe to remove later.
|
|
82
|
+
|
|
83
|
+
## Surfaces and their roles
|
|
84
|
+
|
|
85
|
+
Four event/state surfaces exist. Their responsibilities are deliberately
|
|
86
|
+
non-overlapping; only the first one answers whether a process may launch or a
|
|
87
|
+
result may settle.
|
|
88
|
+
|
|
89
|
+
| Surface | Role | Owner | Authority rule |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `TurnReservation` plus immutable `initial`, `launch(epoch)` and `close(epoch)` cells | **Authoritative** execution decisions | `AttemptAuthority` | The first no-clobber decision wins. Mutable projections never override it. |
|
|
92
|
+
| Loop `LoopEvent` journal | **Causal** protocol history | Loop Engine | Replays phases, artifacts, gates and generation-change causes; it does not grant spawn authority. |
|
|
93
|
+
| `RuntimeEvent` stream | **Telemetry** | execution/runtime layer | Reports processes, heartbeats, adapters and diagnostics; it is evidence for an operator, never a launch/settlement decision. |
|
|
94
|
+
| Legacy project `events.jsonl` | **Compatibility-only** audit stream | legacy consumers | Retained for compatibility. New AttemptAuthority or registry logic must not depend on it; journal v2 carries registry projections. |
|
|
95
|
+
|
|
96
|
+
Assignment, AgentRun, Claim, slot and `head.json` records are queryable
|
|
97
|
+
projections rather than a fifth event surface. The separation invariant is:
|
|
98
|
+
`AttemptAuthority` decides execution, `LoopEvent` explains protocol causality,
|
|
99
|
+
`RuntimeEvent` observes execution, and `events.jsonl` serves old readers.
|
|
100
|
+
|
|
101
|
+
## What can run in parallel
|
|
102
|
+
|
|
103
|
+
Brainclaw does not concurrently rewrite shared JSON files. That proved fragile
|
|
104
|
+
in particular on Windows. Instead it narrows serialization to the decision
|
|
105
|
+
that actually needs consensus:
|
|
106
|
+
|
|
107
|
+
- capability resolution, liveness collection, contract construction, isolated
|
|
108
|
+
workspace preparation, and writer signatures can run in parallel;
|
|
109
|
+
- each rollout writer publishes its own immutable ACK file independently;
|
|
110
|
+
- contenders race on one immutable `launch(epoch)` or `close(epoch)` cell;
|
|
111
|
+
- Assignment, AgentRun, loop event, runtime event, and head updates are
|
|
112
|
+
sequential, idempotent projections that may be replayed after a crash.
|
|
113
|
+
|
|
114
|
+
This gives parallel preparation without permitting concurrent mutation of the
|
|
115
|
+
same file. The global mutation pipeline remains serialized for ordinary store
|
|
116
|
+
entities; AttemptAuthority decisions do not hold that lock while agents work.
|
|
117
|
+
|
|
118
|
+
## Ordered dispatch
|
|
119
|
+
|
|
120
|
+
P0A characterised the legacy order as
|
|
121
|
+
`reserve → commit → durable projections → legacy launch CAS → spawn →
|
|
122
|
+
reconcile`. That order remains the compatibility prefix: a crash before the
|
|
123
|
+
launch CAS is repairable, and a replay observing an already-crossed legacy
|
|
124
|
+
decision never spawns again.
|
|
125
|
+
|
|
126
|
+
The shipped v2 order extends that prefix rather than bypassing it. During the
|
|
127
|
+
Release-B cutover, generation zero is anchored only after the legacy launch
|
|
128
|
+
decision has crossed. A crash between either boundary is repaired
|
|
129
|
+
idempotently: the initial cell embeds the same immutable generation, and the
|
|
130
|
+
v2 `launch(0)` cell remains the final spawn fence. Successor generations skip
|
|
131
|
+
identity reminting for the logical work and use the v2 path below.
|
|
132
|
+
|
|
133
|
+
The common worker path is
|
|
134
|
+
[`prepareTurnExecution`](../../src/core/loops/turn-execution.ts):
|
|
135
|
+
|
|
136
|
+
1. validate the loop phase, slot, claim, capabilities, and workspace policy;
|
|
137
|
+
2. reserve and commit the stable logical turn;
|
|
138
|
+
3. freeze its ExecutionContract and capability snapshot;
|
|
139
|
+
4. create or validate all durable projections (Assignment, AgentRun, claim, and slot) before crossing;
|
|
140
|
+
5. cross `launch(0)` immediately before spawn;
|
|
141
|
+
6. when Release B is active, anchor generation zero in the v2 chain;
|
|
142
|
+
7. accept completion only after rechecking the full fence under the loop lock;
|
|
143
|
+
8. race settlement on `close(epoch)` before applying loop projections.
|
|
144
|
+
|
|
145
|
+
For v2, worker MCP lifecycle reports stop at `accepted`, `started`, and
|
|
146
|
+
`progress`. A worker cannot set the stable Assignment terminal or release its
|
|
147
|
+
Claim, even with the current fence. It writes full-fence `LANE-RESULT.json`;
|
|
148
|
+
settlement seals that result first, then Brainclaw projects Assignment,
|
|
149
|
+
AgentRun, Claim, artifacts, and loop state.
|
|
150
|
+
|
|
151
|
+
After takeover, re-entering the same common path keeps the logical Assignment,
|
|
152
|
+
projects the successor AgentRun and contract, then races `launch(next_epoch)`.
|
|
153
|
+
Only the caller whose publish returns `won: true` may spawn. Replays adopt the
|
|
154
|
+
crossed cell and do not spawn.
|
|
155
|
+
|
|
156
|
+
The per-kind phase graph, artifacts, gates, iteration, and stop condition stay
|
|
157
|
+
in the [Loop Engine](./loop-engine.md). AttemptAuthority does not decide what a
|
|
158
|
+
review verdict means, when an ideation synthesis is sufficient, or whether an
|
|
159
|
+
implementation/debug verification is green.
|
|
160
|
+
|
|
161
|
+
## Functional API
|
|
162
|
+
|
|
163
|
+
The kind-neutral facade is
|
|
164
|
+
[`src/core/loops/attempt-authority.ts`](../../src/core/loops/attempt-authority.ts).
|
|
165
|
+
Its public operations map directly to the decisions above:
|
|
166
|
+
|
|
167
|
+
- `prepareAttempt` and `projectAndCross` implement the legacy
|
|
168
|
+
reserve/commit/projection/cross compatibility prefix. `projectAndCross`
|
|
169
|
+
authorises a spawn only when it returns `kind: 'won'`.
|
|
170
|
+
- `inspectAttempt`, `matchEvidence`, `revokeAttempt`, and `abortAttempt`
|
|
171
|
+
expose read-strict inspection and the irreversible legacy decision axes.
|
|
172
|
+
- `bootstrapAttemptAuthorityV2` anchors generation zero without minting a
|
|
173
|
+
second logical Assignment.
|
|
174
|
+
- `prepareAttemptTakeoverV2` closes the current epoch with a complete successor
|
|
175
|
+
embedded in the immutable cell; `takeoverLoopAttempt` applies the loop-level
|
|
176
|
+
causal and replayable projections.
|
|
177
|
+
- `crossActiveAttemptGenerationV2` arbitrates the successor's launch cell.
|
|
178
|
+
Only its `won: true` result carries spawn authority.
|
|
179
|
+
- `settleActiveAttemptGenerationV2` seals result evidence, competes on
|
|
180
|
+
`close(epoch)`, and exposes the incumbent verdict to losing/replaying calls.
|
|
181
|
+
- `resolveTurnGenerationChain` and `rebuildAttemptGenerationHead` read the
|
|
182
|
+
immutable chain and repair the non-authoritative head.
|
|
183
|
+
|
|
184
|
+
The low-level cell functions live in
|
|
185
|
+
[`attempt-generations.ts`](../../src/core/loops/attempt-generations.ts); signed
|
|
186
|
+
membership and authority-home checks live in
|
|
187
|
+
[`attempt-rollout.ts`](../../src/core/loops/attempt-rollout.ts). The Loop Engine
|
|
188
|
+
still owns artifacts, phase transitions, gates and convergence. Harness and
|
|
189
|
+
execution adapters translate/execute a contracted turn but never call these
|
|
190
|
+
functions to approve their own output.
|
|
191
|
+
|
|
192
|
+
## Takeover and retry
|
|
193
|
+
|
|
194
|
+
A coordinator may fence a crossed generation and arm a successor with:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
brainclaw loop takeover <loop_id> \
|
|
198
|
+
--slot <slot_id> \
|
|
199
|
+
--turn-id <turn_id> \
|
|
200
|
+
--expected-epoch <n> \
|
|
201
|
+
--cause "heartbeat and process evidence are stale" \
|
|
202
|
+
--liveness-evidence "no heartbeat for 30m; wrapper exited" \
|
|
203
|
+
--external-effect-policy idempotent \
|
|
204
|
+
--next-workspace-path <existing-isolated-directory> \
|
|
205
|
+
--agent <loop-coordinator>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The equivalent MCP call is `bclaw_loop(intent="takeover", ...)`. It records an
|
|
209
|
+
`attempt_generation_changed` LoopEvent, projects an `attempt_takeover`
|
|
210
|
+
RuntimeEvent, interrupts the old AgentRun best-effort, and creates the new
|
|
211
|
+
AgentRun. Its response is deliberately `spawn_authority: false`: the caller
|
|
212
|
+
must dispatch the same logical turn through the common path, which crosses the
|
|
213
|
+
new launch cell immediately before process creation.
|
|
214
|
+
|
|
215
|
+
Takeover requires:
|
|
216
|
+
|
|
217
|
+
- the loop coordinator identity;
|
|
218
|
+
- the expected active epoch;
|
|
219
|
+
- a non-empty cause and liveness evidence;
|
|
220
|
+
- an existing linked Git worktree of the same repository, with a distinct
|
|
221
|
+
gitdir and top-level path that does not alias the prior workspace (including
|
|
222
|
+
through a Windows junction);
|
|
223
|
+
- effects declared `none`, `idempotent`, or protected by an external fence.
|
|
224
|
+
|
|
225
|
+
An operation with non-idempotent external effects and no external fencing must
|
|
226
|
+
not be taken over automatically. Human recovery must first establish the
|
|
227
|
+
external system's outcome or fence it there.
|
|
228
|
+
|
|
229
|
+
## Two-release activation
|
|
230
|
+
|
|
231
|
+
AttemptAuthority v2 is a writer compatibility boundary. Enabling a new writer
|
|
232
|
+
beside an old binary would let the old binary ignore generation cells, so the
|
|
233
|
+
rollout has two releases.
|
|
234
|
+
|
|
235
|
+
Release A:
|
|
236
|
+
|
|
237
|
+
1. deploy v2-aware readers and guarded writers everywhere;
|
|
238
|
+
2. stop or drain pre-Release-A processes;
|
|
239
|
+
3. prepare one immutable membership guard;
|
|
240
|
+
4. let every active writer sign and publish its own ACK in parallel;
|
|
241
|
+
5. activate the guard only after every active writer ACKs the same digest.
|
|
242
|
+
|
|
243
|
+
Release B permits the first v2 generation write. CLI support:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
brainclaw attempt-authority status --json
|
|
247
|
+
brainclaw attempt-authority prepare --writers <agent_id...>
|
|
248
|
+
brainclaw attempt-authority ack --membership-epoch 1 --agent-id <agent_id>
|
|
249
|
+
brainclaw attempt-authority activate --membership-epoch 1
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Membership epochs form an immutable chain. A genuinely offline writer can be
|
|
253
|
+
revoked only by a later membership epoch chained to the active activation. A
|
|
254
|
+
pre-Release-A binary cannot sign or honor this guard; it must be stopped or
|
|
255
|
+
removed by deployment/service control before activation.
|
|
256
|
+
|
|
257
|
+
## Migration and rollout runbook
|
|
258
|
+
|
|
259
|
+
Treat the first v2 generation cell as the irreversible cutover boundary.
|
|
260
|
+
Release A can be rolled back while no v2 cell exists; Release B cannot be
|
|
261
|
+
downgraded in place.
|
|
262
|
+
|
|
263
|
+
1. **Drain and inventory.** Stop new loop dispatch, let active generations
|
|
264
|
+
settle, list every process/service/host capable of writing this store, and
|
|
265
|
+
stop pre-Release-A binaries. An offline writer is not implicitly safe: it
|
|
266
|
+
must be removed from service control or excluded by a new membership epoch.
|
|
267
|
+
2. **Create and verify a private backup.** With writers quiescent, run:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
node scripts/store-snapshot.mjs create --store .brainclaw
|
|
271
|
+
node scripts/store-snapshot.mjs verify --snapshot <snapshot-directory>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Keep the snapshot outside the repository. Record its manifest hash and the
|
|
275
|
+
output of `brainclaw attempt-authority status --json`. The detailed storage
|
|
276
|
+
procedure is [store-snapshot.md](../playbooks/store-snapshot.md).
|
|
277
|
+
3. **Release A guard.** Run `prepare`, let every active writer run `ack` in
|
|
278
|
+
parallel, then run `activate`. Re-run `status --json` and verify the
|
|
279
|
+
membership epoch, authority home and ACK digest before enabling Release B.
|
|
280
|
+
`prepare`/`ack` also exercise the hard-link create-if-absent primitive; a
|
|
281
|
+
filesystem that cannot provide it stops the rollout here.
|
|
282
|
+
4. **Canary Release B.** Enable v2 on the authority home only. Run one
|
|
283
|
+
no-external-effect worker attempt, then one explicit retry/takeover in a
|
|
284
|
+
linked Git worktree. Verify that the Assignment stays stable, epochs and
|
|
285
|
+
AgentRuns change, one `close(epoch)` winner exists, stale output is rejected,
|
|
286
|
+
and `head.json` can be rebuilt from the chain. Observe LoopEvents and
|
|
287
|
+
RuntimeEvents separately; neither may contradict the decision cells.
|
|
288
|
+
5. **Expand.** Resume ordinary dispatch only after the canary and targeted
|
|
289
|
+
tests are green. Add or revoke writers through a new, digest-chained
|
|
290
|
+
membership epoch; never edit an activated guard or ACK in place.
|
|
291
|
+
|
|
292
|
+
**Abort before cutover.** If no v2 initial/launch/close cell was ever written,
|
|
293
|
+
stop writers, restore the verified pre-cutover snapshot into an empty directory
|
|
294
|
+
with `store-snapshot.mjs restore`, verify it, and re-point the workspace under
|
|
295
|
+
the previous release. Do not restore over a live store.
|
|
296
|
+
|
|
297
|
+
**Recovery after cutover.** Once any v2 cell exists, disabling the feature flag
|
|
298
|
+
or installing an old writer is a forbidden downgrade. First stop all writers,
|
|
299
|
+
export the current v2 store with `store-snapshot.mjs create`, verify the export,
|
|
300
|
+
and restore it only into an empty directory using a v2-capable binary. Preserve
|
|
301
|
+
the immutable generation/rollout cells and use the same local authority-home
|
|
302
|
+
identity; a restore on another device is a passive replica until explicitly
|
|
303
|
+
re-authorised. A pre-v2 backup may be inspected or used to recover unrelated
|
|
304
|
+
data, but it must not replace a store whose v2 history has started.
|
|
305
|
+
|
|
306
|
+
## Authority home and federation
|
|
307
|
+
|
|
308
|
+
Every v2 fence carries an `authority_home`:
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
{ store_instance_id, device_id }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The identity is random and stored outside the project store in the user's
|
|
315
|
+
Brainclaw registry (`~/.brainclaw/store-instances/` by default). It is not
|
|
316
|
+
derived from a path or hostname. Copying a `.brainclaw` directory therefore
|
|
317
|
+
does not copy write authority. `BRAINCLAW_AUTHORITY_IDENTITY_ROOT` may relocate
|
|
318
|
+
that local registry for managed or test environments.
|
|
319
|
+
|
|
320
|
+
Only the activated authority home may write v2 cells. Federated replicas are
|
|
321
|
+
passive readers of those cells; they do not arbitrate takeover independently.
|
|
322
|
+
A copied or foreign device fails with `authority_home_mismatch`.
|
|
323
|
+
|
|
324
|
+
## Recovery
|
|
325
|
+
|
|
326
|
+
- No initial-generation cell: use the legacy reservation path. A Release-B
|
|
327
|
+
writer anchors generation zero only after the legacy launch has crossed.
|
|
328
|
+
- `launch(epoch)` absent: projections may be repaired, then callers may contend
|
|
329
|
+
on launch.
|
|
330
|
+
- `launch(epoch) = crossed`, `close(epoch)` absent: the run may still produce
|
|
331
|
+
acceptable full-fence evidence; do not respawn it.
|
|
332
|
+
- `close(epoch) = takeover|retry`: the embedded successor is authoritative.
|
|
333
|
+
Replay missing projections and continue from it.
|
|
334
|
+
- `close(epoch) = settled`: replay any missing terminal projections; never
|
|
335
|
+
takeover that generation.
|
|
336
|
+
- Corrupt cells, broken chain links, authority mismatch, incompatible writer,
|
|
337
|
+
or unsupported hard links: fail closed and require operator repair.
|
|
338
|
+
|
|
339
|
+
`head.json`, AgentRun status, RuntimeEvents, and process liveness never override
|
|
340
|
+
the immutable chain. The head can always be rebuilt from generation and close
|
|
341
|
+
cells.
|
|
342
|
+
|
|
343
|
+
## Invariants (I1–I18)
|
|
344
|
+
|
|
345
|
+
- **I1 — Stable logical identity.** `turn_id` and `assignment_id` do not
|
|
346
|
+
change across retry or takeover.
|
|
347
|
+
- **I2 — Fresh physical identity.** Every epoch has a fresh `run_id`, nonce,
|
|
348
|
+
workspace identity/digest and generation contract hash.
|
|
349
|
+
- **I3 — Single execution authority.** Only reservation and immutable
|
|
350
|
+
generation decision cells decide launch or settlement.
|
|
351
|
+
- **I4 — Irreversible logical commit.** A committed reservation never becomes
|
|
352
|
+
aborted; an aborted reservation never becomes committed.
|
|
353
|
+
- **I5 — One launch verdict per epoch.** `crossed` and `revoked` are exclusive,
|
|
354
|
+
and only the caller that creates `crossed` may spawn.
|
|
355
|
+
- **I6 — One close verdict per epoch.** `settled`, `takeover`, `retry`, and
|
|
356
|
+
`cancelled` are mutually exclusive.
|
|
357
|
+
- **I7 — One receivable generation.** A closed generation is never active;
|
|
358
|
+
late output stays audit-only.
|
|
359
|
+
- **I8 — Full-fence evidence.** V2 acceptance matches assignment, turn, epoch,
|
|
360
|
+
run, nonce, contract hash and workspace digest.
|
|
361
|
+
- **I9 — Evidence before projections.** Settlement seals immutable result
|
|
362
|
+
evidence before applying terminal Assignment, AgentRun, Claim, artifact or
|
|
363
|
+
loop projections.
|
|
364
|
+
- **I10 — Projections are replayable.** Assignment, AgentRun, Claim, slot,
|
|
365
|
+
events and head can be created-or-validated again after a crash without
|
|
366
|
+
changing authority.
|
|
367
|
+
- **I11 — Shared mutable JSON is serialized.** Parallel work prepares inputs or
|
|
368
|
+
publishes disjoint immutable files; it never concurrently rewrites one JSON
|
|
369
|
+
projection.
|
|
370
|
+
- **I12 — No-clobber means hard link.** Final cells use same-volume temp,
|
|
371
|
+
fsync/close and hard-link create-if-absent. There is no rename fallback.
|
|
372
|
+
- **I13 — Authority home is local.** Only the activated
|
|
373
|
+
`(store_instance_id, device_id)` may mutate v2 cells.
|
|
374
|
+
- **I14 — Federation is passive.** Replicas may validate/replay the chain but
|
|
375
|
+
never promote themselves during a partition.
|
|
376
|
+
- **I15 — Writer rollout is explicit.** Release B requires one activated,
|
|
377
|
+
signed membership epoch whose active writers all ACK the same digest and
|
|
378
|
+
both `minimum_writer_version` and `minimum_reader_version`.
|
|
379
|
+
- **I16 — External effects are fenced.** Automatic takeover is forbidden for
|
|
380
|
+
non-idempotent external effects without an external fence.
|
|
381
|
+
- **I17 — Recovery is decision-driven.** Digests and immutable cells determine
|
|
382
|
+
the next action; clocks, PIDs, heartbeats and marker files are supporting
|
|
383
|
+
liveness evidence only.
|
|
384
|
+
- **I18 — Event roles remain separate.** AttemptAuthority is authoritative,
|
|
385
|
+
LoopEvent causal, RuntimeEvent telemetry, and `events.jsonl`
|
|
386
|
+
compatibility-only. No fifth journal is introduced.
|
|
387
|
+
|
|
388
|
+
## Code map
|
|
389
|
+
|
|
390
|
+
- [`attempt-reservation.ts`](../../src/core/loops/attempt-reservation.ts) —
|
|
391
|
+
logical reservation and legacy launch authority.
|
|
392
|
+
- [`attempt-generations.ts`](../../src/core/loops/attempt-generations.ts) —
|
|
393
|
+
immutable generation, launch, close, and head primitives.
|
|
394
|
+
- [`attempt-rollout.ts`](../../src/core/loops/attempt-rollout.ts) — signed
|
|
395
|
+
writer membership and local authority identity.
|
|
396
|
+
- [`attempt-authority.ts`](../../src/core/loops/attempt-authority.ts) — common
|
|
397
|
+
facade, bootstrap, takeover, crossing, and settlement.
|
|
398
|
+
- [`attempt-takeover.ts`](../../src/core/loops/attempt-takeover.ts) — loop-level
|
|
399
|
+
takeover transaction and replayable projections.
|
|
400
|
+
- [`reconcile-turn.ts`](../../src/core/loops/reconcile-turn.ts) — full-fence
|
|
401
|
+
validation, close-cell settlement, and business convergence.
|
|
402
|
+
- [`turn-execution.ts`](../../src/core/loops/turn-execution.ts) — common worker
|
|
403
|
+
dispatch path for all five LoopKinds.
|
|
404
|
+
|
|
405
|
+
See also [Execution contract](./execution-contract.md),
|
|
406
|
+
[Harness adapters](./harness-adapters.md), and the
|
|
407
|
+
[Loop Engine](./loop-engine.md).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Evidence envelopes, attestations, and protocol gates
|
|
2
|
+
|
|
3
|
+
The Loop Engine does not treat an artifact's text as proof that a transition
|
|
4
|
+
is safe. Every new loop binds `gate-policy-v1`; every artifact committed by
|
|
5
|
+
the engine receives a server-sealed `EvidenceEnvelope` before it enters the
|
|
6
|
+
thread.
|
|
7
|
+
|
|
8
|
+
This contract applies equally to the five shipped loop kinds: review,
|
|
9
|
+
ideation, implementation, research, and debug. It refines their gates; it
|
|
10
|
+
does not introduce a separate workflow or event store.
|
|
11
|
+
|
|
12
|
+
## EvidenceEnvelope v1
|
|
13
|
+
|
|
14
|
+
An envelope binds:
|
|
15
|
+
|
|
16
|
+
- the artifact digest, including id, phase, type, body/ref, producer,
|
|
17
|
+
production time, critique links, and iteration;
|
|
18
|
+
- the exact subject: loop, artifact, phase, iteration and, when available,
|
|
19
|
+
slot, turn, assignment, claim, run, launch-nonce digest/epoch, execution-contract
|
|
20
|
+
hash, command digest, and workspace digest;
|
|
21
|
+
- a server-derived producer and ingress channel;
|
|
22
|
+
- an observation time and explicit validity window;
|
|
23
|
+
- independent attestations;
|
|
24
|
+
- a canonical SHA-256 integrity seal.
|
|
25
|
+
|
|
26
|
+
Ingress callers never submit an envelope. `complete_turn`, turn reconciliation,
|
|
27
|
+
the engine verify runner, operator-input handlers, bootstrap hooks, and
|
|
28
|
+
`add_artifact` all seal at their server-controlled commit boundary. Likewise,
|
|
29
|
+
`produced_by` is derived by the server; the public `add_artifact` input no
|
|
30
|
+
longer accepts it as authority. A direct `add_artifact` commit is intentionally
|
|
31
|
+
audit-only: its observation carries `artifact:write`, not `gate:artifact`.
|
|
32
|
+
The internal evidence context and sealing helper are omitted from the public
|
|
33
|
+
Loop Engine barrel; public completion and artifact functions explicitly strip
|
|
34
|
+
any runtime object that attempts to smuggle such a context.
|
|
35
|
+
|
|
36
|
+
The SHA-256 seal detects accidental or local-store tampering. It is not a
|
|
37
|
+
remote cryptographic identity signature: Brainclaw's local store remains in
|
|
38
|
+
the trusted computing base.
|
|
39
|
+
|
|
40
|
+
## Attestations are independent
|
|
41
|
+
|
|
42
|
+
There is deliberately no global confidence score and no ordering such as
|
|
43
|
+
“verification is stronger than approval”. A policy asks for the exact right
|
|
44
|
+
and attestation it needs:
|
|
45
|
+
|
|
46
|
+
| Attestation | Meaning | Typical right |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `claim` | the result is bound to the recorded claim/attempt subject | `subject:claim` |
|
|
49
|
+
| `observation` | the engine observed and committed an artifact | `gate:artifact` |
|
|
50
|
+
| `verification` | Brainclaw ran the opener-configured command | `gate:command_green` |
|
|
51
|
+
| `approval` | an authorized reviewer slot returned an accepted verdict | `gate:reviewer_green` |
|
|
52
|
+
|
|
53
|
+
A worker or adapter can report a passing `verify_report`, but it cannot grant
|
|
54
|
+
itself `verification`. A generic artifact insertion can store a deliverable or
|
|
55
|
+
an accepted verdict for audit, but cannot open either a generic artifact gate
|
|
56
|
+
or a reviewer gate.
|
|
57
|
+
|
|
58
|
+
Gate authority is an explicit tuple, not a producer-kind shortcut: policy
|
|
59
|
+
matches the ingress channel, producer kind, attestation kind, issuer, right,
|
|
60
|
+
and required subject fields. For example, `reviewer_green` accepts an approval
|
|
61
|
+
issued by `brainclaw:review-slot` through `complete_turn` or a fully bound
|
|
62
|
+
`reconcile_turn`; `command_green` accepts only `brainclaw:verify-command`.
|
|
63
|
+
|
|
64
|
+
## Gate evaluation
|
|
65
|
+
|
|
66
|
+
One evaluator is used by terminal stop conditions, phase-advance gates, and
|
|
67
|
+
iteration exits. A decision records:
|
|
68
|
+
|
|
69
|
+
- `passed`, plus distinct `strict_passed` and `legacy_passed` dimensions;
|
|
70
|
+
- policy version and rollout mode;
|
|
71
|
+
- a digest of the evaluated condition;
|
|
72
|
+
- accepted evidence ids;
|
|
73
|
+
- rejected artifact ids with machine-readable reasons.
|
|
74
|
+
|
|
75
|
+
The decision is attached to causal `phase_advanced`,
|
|
76
|
+
`phase_advance_blocked`, and automatic `closed` LoopEvents. RuntimeEvents
|
|
77
|
+
remain best-effort telemetry and never authorize a transition.
|
|
78
|
+
|
|
79
|
+
The evaluator rejects missing evidence on strict threads, invalid seals,
|
|
80
|
+
artifact/subject mismatches, cross-loop or cross-iteration replay, evidence
|
|
81
|
+
predating the loop, future timestamps, unauthorized channel/producer/issuer
|
|
82
|
+
combinations, missing execution bindings or rights, and duplicate payloads in
|
|
83
|
+
threshold gates. If an envelope is present but invalid, legacy behavior is
|
|
84
|
+
never used as a fallback.
|
|
85
|
+
|
|
86
|
+
The engine snapshots the workspace bytes immediately before and after
|
|
87
|
+
`verify_command`, then again at the evidence commit boundary. A concurrent
|
|
88
|
+
mutation changes the digest and forces the report red. The command argv and
|
|
89
|
+
its digest plus the stable workspace digest are copied into the report and its
|
|
90
|
+
sealed subject. Gate policy requires those bindings to match the configured
|
|
91
|
+
command and recomputes the current workspace digest whenever the gate is read;
|
|
92
|
+
a post-verification mutation therefore invalidates an earlier green report.
|
|
93
|
+
Reconciled worker evidence likewise binds the run, launch generation, and
|
|
94
|
+
execution contract. Pre-P1 reservations retain a deterministic hash of their
|
|
95
|
+
immutable legacy reservation fields so an in-flight historical attempt can
|
|
96
|
+
converge without pretending it carried a v1 execution contract.
|
|
97
|
+
|
|
98
|
+
Reviewer approval is scoped to the current loop iteration and to the slot's
|
|
99
|
+
current turn, assignment, and claim projections. A creator/admin recovery may
|
|
100
|
+
settle a slot, but it is recorded as coordinator evidence and cannot mint the
|
|
101
|
+
reviewer's approval.
|
|
102
|
+
|
|
103
|
+
Negative convergence is fail-closed too: an invalid critique cannot be used
|
|
104
|
+
to manufacture “no new critique”. After at least one full ideation cycle, a
|
|
105
|
+
settled critique round with no eligible new critiques is evaluated before the
|
|
106
|
+
quantitative critique gate. “Settled” is causal: the last trusted critic
|
|
107
|
+
successful completion emits an engine-owned `critique_window_closed` artifact
|
|
108
|
+
for that iteration. An open, assigned, running, failed, or cancelled critic
|
|
109
|
+
turn therefore cannot create saturation merely by staying silent.
|
|
110
|
+
|
|
111
|
+
## Rollout and legacy threads
|
|
112
|
+
|
|
113
|
+
`LoopThread.evidence_policy` makes compatibility explicit:
|
|
114
|
+
|
|
115
|
+
- absent: pre-policy thread; unsealed legacy artifacts retain legacy gate
|
|
116
|
+
semantics (including duplicate threshold counting), while any present
|
|
117
|
+
envelope is still validated;
|
|
118
|
+
- `{version: "gate-policy-v1", mode: "shadow"}`: writers seal evidence and
|
|
119
|
+
decisions report both the independently composed strict and legacy results,
|
|
120
|
+
while the legacy outcome controls the transition;
|
|
121
|
+
- `{version: "gate-policy-v1", mode: "strict"}`: only policy-eligible evidence
|
|
122
|
+
influences gates.
|
|
123
|
+
|
|
124
|
+
New loops default to `strict`. Set `BRAINCLAW_EVIDENCE_ENVELOPES=shadow` for a
|
|
125
|
+
measured rollout, or `off` before opening a loop to create an explicit
|
|
126
|
+
pre-policy thread. The policy is frozen on the thread: changing the process
|
|
127
|
+
flag later does not silently downgrade an already-strict loop.
|
|
128
|
+
|
|
129
|
+
## Persistence model
|
|
130
|
+
|
|
131
|
+
Evidence lives on `LoopArtifact`; gate decisions live in the existing
|
|
132
|
+
append-only LoopEvent journal. Brainclaw does not add an evidence database or
|
|
133
|
+
a second event journal. The attempt reservation, execution contract,
|
|
134
|
+
artifact, evidence envelope, and causal event remain separately inspectable
|
|
135
|
+
parts of one execution history.
|