brainclaw 1.26.2 → 1.28.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 +143 -15
- package/dist/commands/mcp-catalog.js +52 -18
- package/dist/commands/mcp-schemas.generated.js +64 -0
- package/dist/commands/mcp-write-claims.js +128 -1
- package/dist/commands/mcp-write-coordination.js +149 -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 +189 -14
- package/dist/core/execution-contract.js +345 -0
- package/dist/core/execution.js +130 -16
- package/dist/core/facade-schema.js +3 -0
- 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 +235 -0
- package/dist/core/loops/artifact-contract.js +11 -0
- package/dist/core/loops/attempt-authority.js +496 -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/brief-assembly.js +21 -4
- package/dist/core/loops/evidence.js +188 -0
- package/dist/core/loops/facade-schema.js +75 -11
- package/dist/core/loops/gate-policy.js +533 -0
- package/dist/core/loops/impl-bind.js +91 -81
- 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 +237 -18
- package/dist/core/loops/result-reducers.js +113 -10
- package/dist/core/loops/store.js +34 -3
- package/dist/core/loops/turn-execution.js +480 -0
- package/dist/core/loops/types.js +127 -3
- package/dist/core/loops/verbs.js +335 -99
- package/dist/core/loops/verify-command.js +105 -20
- 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 +40 -0
- package/dist/core/spawn-check.js +3 -2
- package/dist/core/upgrades/backup.js +27 -4
- package/dist/facts.js +9 -8
- package/dist/facts.json +8 -7
- 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 +174 -0
- package/docs/loops/research.md +136 -0
- package/docs/loops/review.md +200 -0
- package/docs/mcp-schema-changelog.md +18 -5
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Loop engine
|
|
2
2
|
|
|
3
|
-
brainclaw coordinates many agents against shared state.
|
|
4
|
-
|
|
5
|
-
—
|
|
6
|
-
|
|
3
|
+
brainclaw coordinates many agents against shared state. The Loop engine
|
|
4
|
+
turns repetitive multi-turn workflows — review, ideation, implementation,
|
|
5
|
+
research, and debug — into **first-class, persistable, automatable objects**
|
|
6
|
+
sharing one common runtime.
|
|
7
7
|
|
|
8
8
|
Status: **shipped**. `bclaw_loop` exposes the persistent engine and its five
|
|
9
9
|
built-in protocols; `bclaw_coordinate` and `bclaw_dispatch` add ergonomic
|
|
@@ -11,6 +11,30 @@ shortcuts for ideation and review. This document retains the RFC-level
|
|
|
11
11
|
concurrency contract and implementation history where it explains an
|
|
12
12
|
invariant, but its operational sections describe the surface available today.
|
|
13
13
|
|
|
14
|
+
**How this doc is organised.** It starts with the shared engine — authority,
|
|
15
|
+
phases, artifacts, lifecycle verbs, gates, recovery, observability — and
|
|
16
|
+
then points at five per-protocol guides. Each protocol has its own page in
|
|
17
|
+
[`docs/loops/`](../loops/); this page is not a review guide. The five
|
|
18
|
+
protocols are equal citizens of the engine, and this is not a review feature
|
|
19
|
+
with a few extensions.
|
|
20
|
+
|
|
21
|
+
- [Review](../loops/review.md) — validate a change against a reviewer.
|
|
22
|
+
- [Ideation](../loops/ideation.md) — pressure-test a proposal against
|
|
23
|
+
project memory.
|
|
24
|
+
- [Implementation](../loops/implementation.md) — drive a plan+sequence to a
|
|
25
|
+
green verify.
|
|
26
|
+
- [Research](../loops/research.md) — converge open-ended investigation to
|
|
27
|
+
a synthesis.
|
|
28
|
+
- [Debug](../loops/debug.md) — drive a broken system back to green.
|
|
29
|
+
|
|
30
|
+
Identity, dispatch decisions and spawn authority live in a separate façade
|
|
31
|
+
over the reservation core — see
|
|
32
|
+
[Attempt authority](./attempt-authority.md). The exact worker, workspace,
|
|
33
|
+
artifact and evidence expectations for each physical turn are frozen by the
|
|
34
|
+
[Execution contract and capability snapshot](./execution-contract.md).
|
|
35
|
+
Artifacts then cross the server-controlled commit boundary described in
|
|
36
|
+
[Evidence envelopes, attestations, and protocol gates](./evidence-attestations.md).
|
|
37
|
+
|
|
14
38
|
## Why
|
|
15
39
|
|
|
16
40
|
Without a loop, recurring work is easy to reduce to manual ping-pong:
|
|
@@ -38,6 +62,72 @@ A Loop is a **persistent thread of structured work** with:
|
|
|
38
62
|
A Loop stores *references* to existing objects — it never duplicates them.
|
|
39
63
|
Claims, handoffs, and candidates remain the source of truth for their own data.
|
|
40
64
|
|
|
65
|
+
## Attempt authority
|
|
66
|
+
|
|
67
|
+
Every dispatched turn crosses several boundaries — the loop mints identity,
|
|
68
|
+
an assignment must be persisted, a run must be launched, evidence must
|
|
69
|
+
eventually be accepted. `AttemptAuthority` owns those execution decisions while
|
|
70
|
+
the Loop Engine owns phases, artifacts, gates, and convergence. It is common to
|
|
71
|
+
all five kinds and adds no event journal.
|
|
72
|
+
|
|
73
|
+
`prepareTurnExecution` applies the same projections-before-crossing path to
|
|
74
|
+
every worker phase and refuses `engine` or `manual` phases before reservation.
|
|
75
|
+
The first worker phase for a `(loop_id, slot_id, iteration)` keeps the legacy
|
|
76
|
+
deterministic `turn_id`. If that same reusable slot enters another worker phase
|
|
77
|
+
without an iteration bump, the resolver derives a versioned identity from
|
|
78
|
+
`(loop_id, slot_id, phase, iteration)` instead. Replays of the same phase still
|
|
79
|
+
adopt one cell, while compatible legacy reservations remain adoptable during
|
|
80
|
+
crash recovery and upgrades. This rule is kind-neutral: it prevents one phase
|
|
81
|
+
from inheriting another phase's already-consumed launch authority in review,
|
|
82
|
+
ideation, research, debug, or any future multi-phase protocol.
|
|
83
|
+
The first physical generation follows `reserve → commit → durable projections
|
|
84
|
+
→ launch(0)`. A fenced takeover keeps the same `turn_id` and `assignment_id`,
|
|
85
|
+
but creates a new epoch, run, nonce, contract, and isolated workspace. Re-entry
|
|
86
|
+
through the same common path projects that successor and races
|
|
87
|
+
`launch(next_epoch)` immediately before spawn.
|
|
88
|
+
|
|
89
|
+
Completion is accepted only on the full generation fence. Settlement and
|
|
90
|
+
takeover contend on one immutable `close(epoch)` decision, so an old worker
|
|
91
|
+
cannot settle after a successor wins. Mutable AgentRun and head records are
|
|
92
|
+
replayable projections, not authority. See [Attempt authority](./attempt-authority.md)
|
|
93
|
+
for the Windows-safe publish protocol, two-release activation, and recovery.
|
|
94
|
+
|
|
95
|
+
Before reservation, that common adapter resolves the selected agent against a
|
|
96
|
+
typed capability requirement and hashes an immutable ExecutionContract. The
|
|
97
|
+
full contract lives on TurnReservation; Assignment and AgentRun carry the same
|
|
98
|
+
hash/reference and capability snapshot before crossing. This is one shared
|
|
99
|
+
dispatch substrate for all five protocols, not protocol-specific review
|
|
100
|
+
metadata. See
|
|
101
|
+
[Execution contract and capability snapshot](./execution-contract.md).
|
|
102
|
+
|
|
103
|
+
The contracted attempt then passes through a
|
|
104
|
+
[Harness adapter](./harness-adapters.md). That adapter binds a concrete agent
|
|
105
|
+
harness and normalizes its output, while the existing `ExecutionAdapter` owns
|
|
106
|
+
the process transport. Neither layer owns phases, artifacts, evidence, gates,
|
|
107
|
+
or convergence: those remain here in the shared engine. The same boundary is
|
|
108
|
+
used for every worker phase in the five-kind table below.
|
|
109
|
+
|
|
110
|
+
Phase-specific execution metadata lives in `LOOP_KIND_POLICIES`; phase graphs,
|
|
111
|
+
gates, iteration and stop conditions remain exclusively in `DEFAULT_PROTOCOLS`.
|
|
112
|
+
The current execution split is deliberately visible here for all five protocols:
|
|
113
|
+
|
|
114
|
+
| Kind | Worker phases | Engine phases | Manual phases | Integration required before convergence |
|
|
115
|
+
|---|---|---|---|---|
|
|
116
|
+
| `review` | `findings`, `author_response`, `followup_review` | `verdict` | `change_summary` | `author_response` |
|
|
117
|
+
| `ideation` | `critique`, `revision`, `synthesis` | — | `proposal` | — |
|
|
118
|
+
| `implementation` | `execute` | `bind`, `verify` | `handoff_ready` | `execute` |
|
|
119
|
+
| `research` | `investigate`, `synthesize` | `conclude` | — | — |
|
|
120
|
+
| `debug` | `reproduce`, `hypothesize`, `isolate`, `fix` | — | `handoff` | `fix` |
|
|
121
|
+
|
|
122
|
+
Every worker result must carry the phase's explicit `artifact_type`. A summary
|
|
123
|
+
is useful observability, but it is never proof that opens a gate. Report harvest
|
|
124
|
+
may reconcile read-only phases; mutating phases keep their claim until
|
|
125
|
+
`harvest --integrate` has integrated the worktree.
|
|
126
|
+
The concurrency and recovery contract lives in the dedicated document —
|
|
127
|
+
see [Attempt authority](./attempt-authority.md) for the full model,
|
|
128
|
+
identity matrix, ordered dispatch and invariants I1–I18. This page assumes
|
|
129
|
+
that contract without restating it.
|
|
130
|
+
|
|
41
131
|
## Data model
|
|
42
132
|
|
|
43
133
|
```ts
|
|
@@ -64,6 +154,10 @@ interface LoopThread {
|
|
|
64
154
|
artifacts: LoopArtifact[];
|
|
65
155
|
linked?: LoopLinks; // top-level context only (plan/sequence). Other refs live on artifacts/slots.
|
|
66
156
|
stop_condition?: StopCondition;
|
|
157
|
+
evidence_policy?: { // absent only on explicit/pre-policy threads
|
|
158
|
+
version: 'gate-policy-v1';
|
|
159
|
+
mode: 'shadow' | 'strict';
|
|
160
|
+
};
|
|
67
161
|
|
|
68
162
|
created_at: string; // ISO
|
|
69
163
|
updated_at: string;
|
|
@@ -75,12 +169,19 @@ type LoopStatus = 'open' | 'paused' | 'completed' | 'blocked' | 'cancelled';
|
|
|
75
169
|
type ReviewMode = 'asymmetric' | 'symmetric';
|
|
76
170
|
|
|
77
171
|
interface LoopProtocolConfig {
|
|
78
|
-
review_mode?: ReviewMode;
|
|
172
|
+
review_mode?: ReviewMode;
|
|
173
|
+
iteration?: { cycle: string[]; max_iterations: number; exit_when: 'critic_signal' | 'no_new_critique_artifacts' | 'command_green' };
|
|
174
|
+
verify?: { command: string[]; timeout_ms?: number };
|
|
175
|
+
preset?: string;
|
|
176
|
+
max_operator_questions?: number;
|
|
177
|
+
max_pause_duration?: string; // ISO-8601 duration
|
|
79
178
|
}
|
|
80
179
|
|
|
81
180
|
interface LoopPhase {
|
|
82
181
|
name: string;
|
|
83
182
|
advance_when?: 'all' | 'any'; // default 'all' — every slot turn in this phase must be `done` before advance
|
|
183
|
+
context_filter?: LoopContextCategory[];
|
|
184
|
+
advance_gate?: StopCondition;
|
|
84
185
|
}
|
|
85
186
|
|
|
86
187
|
interface LoopSlot {
|
|
@@ -91,7 +192,8 @@ interface LoopSlot {
|
|
|
91
192
|
assignment_id?: string; // set when a turn is dispatched
|
|
92
193
|
claim_id?: string; // for execution loops, the claim held by this slot
|
|
93
194
|
phase?: string; // which phase this slot currently participates in (supports parallel slots per phase)
|
|
94
|
-
status: 'open' | 'assigned' | 'working' | 'done';
|
|
195
|
+
status: 'open' | 'assigned' | 'working' | 'waiting_input' | 'done' | 'failed' | 'cancelled';
|
|
196
|
+
current_turn_id?: string; // immutable attempt currently owning this reusable slot
|
|
95
197
|
}
|
|
96
198
|
|
|
97
199
|
interface LoopArtifact {
|
|
@@ -100,8 +202,9 @@ interface LoopArtifact {
|
|
|
100
202
|
type: string; // "finding" | "synthesis" | "verdict" | "plan_draft" | ...
|
|
101
203
|
ref?: LoopRef; // preferred: link to an existing primitive
|
|
102
204
|
body?: string; // inline content ≤ 4 KB; else force `ref`
|
|
103
|
-
produced_by?:
|
|
205
|
+
produced_by?: string; // derived server-side from slot/engine/coordinator context
|
|
104
206
|
produced_at: string;
|
|
207
|
+
evidence?: EvidenceEnvelope; // server-sealed; never caller-authored
|
|
105
208
|
}
|
|
106
209
|
|
|
107
210
|
type LoopRef =
|
|
@@ -123,7 +226,10 @@ type AtomicStopCondition =
|
|
|
123
226
|
| { kind: 'phase_reached'; phase: string }
|
|
124
227
|
| { kind: 'reviewer_green' } // an `accepted` verdict artifact in any phase
|
|
125
228
|
| { kind: 'max_iterations'; n: number } // hard cap; on hit, close with status=blocked
|
|
229
|
+
| { kind: 'min_iterations'; n: number }
|
|
126
230
|
| { kind: 'artifact_produced'; phase: string; type: string }
|
|
231
|
+
| { kind: 'min_artifacts_by_type'; type: string; n: number; scope: 'phase' | 'loop' }
|
|
232
|
+
| { kind: 'no_open_questions' }
|
|
127
233
|
| { kind: 'manual' }; // only closes on explicit close
|
|
128
234
|
|
|
129
235
|
type StopCondition =
|
|
@@ -132,6 +238,9 @@ type StopCondition =
|
|
|
132
238
|
| { kind: 'all'; conditions: StopCondition[] }; // AND — every clause must match
|
|
133
239
|
|
|
134
240
|
// LoopEvent is a discriminated union with typed per-kind payloads (no loose `payload` map).
|
|
241
|
+
// The excerpt below shows the base lifecycle. The shipped union also includes
|
|
242
|
+
// turn_reserved, phase_advance_blocked, max_iterations_reached, input/file-apply,
|
|
243
|
+
// and slot-status events. Gate-driving transitions carry GateDecision.
|
|
135
244
|
interface LoopEventBase {
|
|
136
245
|
event_id: string; // ULID
|
|
137
246
|
loop_id: LoopId;
|
|
@@ -146,6 +255,7 @@ type LoopEvent =
|
|
|
146
255
|
| (LoopEventBase & { kind: 'phase_advanced'; from_phase: string; to_phase: string; iteration: number; reason?: string })
|
|
147
256
|
| (LoopEventBase & { kind: 'turn_assigned'; slot_id: SlotId; phase: string; assignment_id?: string; input?: string; retry_of?: string /* prior event_id */ })
|
|
148
257
|
| (LoopEventBase & { kind: 'turn_completed'; slot_id: SlotId; phase: string; artifact_id?: string; outcome: 'done' | 'failed' | 'cancelled'; failure_reason?: string })
|
|
258
|
+
| (LoopEventBase & { kind: 'attempt_generation_changed'; slot_id: SlotId; turn_id: string; assignment_id: string; from_epoch: number; to_epoch: number; from_run_id: string; to_run_id: string; close_digest: string; cause: string })
|
|
149
259
|
| (LoopEventBase & { kind: 'artifact_added'; artifact_id: string; phase: string; type: string; produced_by?: SlotId })
|
|
150
260
|
| (LoopEventBase & { kind: 'linked'; target: LoopRef })
|
|
151
261
|
| (LoopEventBase & { kind: 'paused'; reason?: string })
|
|
@@ -223,18 +333,43 @@ file lists which artifact types are ref-based and which use inline JSON bodies.
|
|
|
223
333
|
|
|
224
334
|
## Lifecycle verbs
|
|
225
335
|
|
|
226
|
-
The engine exposes
|
|
336
|
+
The engine exposes one shared lifecycle for every protocol. Mutating verbs
|
|
337
|
+
persist state plus causal events and return the updated `LoopThread`.
|
|
338
|
+
**All verbs are strictly synchronous-on-state and asynchronous-on-work**: any
|
|
339
|
+
downstream dispatch continues outside the commit window so the per-loop lock
|
|
340
|
+
is released quickly.
|
|
227
341
|
|
|
228
342
|
- **open** — create a new loop. Inserts `opened` event; `current_phase` set to `phases[0].name`.
|
|
229
|
-
- **turn** — record that a phase's work is assigned to a slot.
|
|
343
|
+
- **turn** — record that a phase's work is assigned to a slot. With
|
|
344
|
+
`dispatch` absent/false it performs state mutation only and may receive an
|
|
345
|
+
existing `assignment_id`. Trusted `dispatch: true` routes a worker phase
|
|
346
|
+
through the common AttemptAuthority preparation/projection/crossing path and
|
|
347
|
+
launches outside the loop lock; engine/manual phases are refused before
|
|
348
|
+
reservation. `slot.status` flips to `'assigned'`, and the worker reports back
|
|
349
|
+
later through harvest/reconciliation or `complete_turn`. Inserts
|
|
350
|
+
`turn_assigned`.
|
|
230
351
|
- **advance** — evaluate `stop_condition`; if satisfied, `close` with `status=completed`. Otherwise, transition `current_phase` to the next phase (or a specified one). Inserts `phase_advanced`. If `advance` revisits an earlier phase (e.g. a fixup round re-enters `findings`), `iteration_count` increments.
|
|
231
352
|
- **close** — terminal: set `status` to `completed | cancelled | blocked` and `closed_at`. Inserts `closed`.
|
|
232
353
|
|
|
233
|
-
|
|
354
|
+
Additional shared and engine-owned actions complete the lifecycle:
|
|
234
355
|
|
|
235
356
|
- **pause** / **resume** — suspend a loop without closing (e.g. waiting on an external input).
|
|
236
357
|
- **add_artifact** — attach an artifact to a phase without moving on.
|
|
237
358
|
- **complete_turn** — close out a previously-assigned turn: flips `slot.status` to `'done'` (or `'failed' | 'cancelled'`), optionally attaches an artifact carrying the outcome. Emitted by the slot agent itself when its dispatched work returns. Separate from `turn` precisely because the dispatch is async. Authorization is strict: the caller's `agentId` must equal that slot's `agent_id`, unless the caller is the loop's `created_by`, which is the only admin override.
|
|
359
|
+
- **takeover** — coordinator-only cross-kind recovery action. It closes the
|
|
360
|
+
active physical generation, arms a successor in a distinct isolated
|
|
361
|
+
workspace, and records `attempt_generation_changed`. It does not spawn; the
|
|
362
|
+
normal turn dispatch must still win the successor's launch cell.
|
|
363
|
+
- **request_input** / **provide_input** — bounded, evidence-backed operator clarification usable by any protocol.
|
|
364
|
+
- **bind** — implementation-loop engine action that validates the linked sequence and advances to `execute`; it never launches a worker.
|
|
365
|
+
- **verify** — implementation/debug engine action that runs the opener-configured command outside the loop lock, then records a verification-attested report.
|
|
366
|
+
|
|
367
|
+
Artifact authority is sealed at these verb boundaries. `produced_by` is
|
|
368
|
+
derived from the authenticated slot/engine/coordinator context. A narrative
|
|
369
|
+
`accepted` verdict or `{passed:true}` report is stored but cannot open a gate
|
|
370
|
+
unless its envelope carries the policy-specific approval or verification
|
|
371
|
+
attestation. Gate decisions and rejection reasons are persisted on causal
|
|
372
|
+
LoopEvents; RuntimeEvents remain telemetry only.
|
|
238
373
|
|
|
239
374
|
## MCP facade: `bclaw_loop(intent)`
|
|
240
375
|
|
|
@@ -250,16 +385,21 @@ interface BclawLoopCallerEnvelope {
|
|
|
250
385
|
|
|
251
386
|
// Per-intent payloads. Every mutating intent supports `expected_version` + `client_request_id`.
|
|
252
387
|
type BclawLoopInput = BclawLoopCallerEnvelope & (
|
|
253
|
-
| { intent: 'open'; kind: LoopKind; title: string; goal?: string; phases?: LoopPhase[]; slots?: Partial<LoopSlot>[]; linked?: LoopLinks; stop_condition?: StopCondition; mode?: ReviewMode /* review only;
|
|
254
|
-
| { intent: 'turn'; loop_id: LoopId; slot_id?: SlotId; role?: string; input?: string; dispatch?: boolean; expected_version?: number }
|
|
255
|
-
| { intent: 'complete_turn'; loop_id: LoopId; slot_id: SlotId; artifact?:
|
|
388
|
+
| { intent: 'open'; kind: LoopKind; title: string; goal?: string; phases?: LoopPhase[]; slots?: Partial<LoopSlot>[]; linked?: LoopLinks; stop_condition?: StopCondition; mode?: ReviewMode /* review only */; verify?: { command: string[]; timeout_ms?: number }; allow_orphan?: boolean }
|
|
389
|
+
| { intent: 'turn'; loop_id: LoopId; slot_id?: SlotId; role?: string; input?: string; assignment_id?: string; claim_id?: string; dispatch?: boolean; auto_execute?: boolean; model?: string; target_agents?: string[]; expected_version?: number }
|
|
390
|
+
| { intent: 'complete_turn'; loop_id: LoopId; slot_id: SlotId; assignment_id?: string; turn_id?: string; run_id?: string; nonce?: string; attempt_epoch?: number; execution_contract_hash?: string; workspace_digest?: string; artifact?: Pick<LoopArtifact, 'phase' | 'type' | 'body' | 'ref' | 'addresses_critique'>; outcome?: 'done' | 'failed' | 'cancelled'; failure_reason?: string; expected_version?: number }
|
|
391
|
+
| { intent: 'takeover'; loop_id: LoopId; slot_id: SlotId; turn_id: string; expected_epoch: number; cause: string; liveness_evidence: string; external_effect_policy: 'none' | 'idempotent' | 'externally_fenced'; next_workspace_path: string; takeover_mode?: 'takeover' | 'retry' }
|
|
256
392
|
| { intent: 'advance'; loop_id: LoopId; to_phase?: string; reason?: string; force?: boolean; expected_version?: number }
|
|
257
|
-
| { intent: 'add_artifact'; loop_id: LoopId; artifact:
|
|
393
|
+
| { intent: 'add_artifact'; loop_id: LoopId; artifact: Pick<LoopArtifact, 'phase' | 'type' | 'body' | 'ref' | 'addresses_critique'>; expected_version?: number }
|
|
258
394
|
| { intent: 'pause'; loop_id: LoopId; reason?: string; expected_version?: number }
|
|
259
395
|
| { intent: 'resume'; loop_id: LoopId; expected_version?: number }
|
|
260
396
|
| { intent: 'close'; loop_id: LoopId; status: 'completed' | 'cancelled' | 'blocked'; reason?: string; expected_version?: number }
|
|
397
|
+
| { intent: 'verify'; loop_id: LoopId }
|
|
398
|
+
| { intent: 'bind'; loop_id: LoopId; dry_run?: boolean; lanes?: string[]; auto_execute?: boolean; model?: string; max_assignments?: number }
|
|
399
|
+
| { intent: 'request_input'; loop_id: LoopId; slot_id: SlotId; phase: string; question_text: string; evidence: string[]; suggested_default?: string; options?: OperatorQuestionOption[]; pause_scope: 'slot' | 'loop'; on_timeout: 'use_default' | 'cancel_loop' | 'continue_incomplete'; timeout_at?: string; expected_version?: number }
|
|
400
|
+
| { intent: 'provide_input'; loop_id: LoopId; replies_to: string; resolved_via: 'answer' | 'choose' | 'skip' | 'timeout_default'; answer_text?: string; chosen_option_id?: string; by?: 'operator' | 'system'; expected_version?: number }
|
|
261
401
|
| { intent: 'get'; loop_id: LoopId; include_events?: boolean }
|
|
262
|
-
| { intent: 'list'; kind?: LoopKind; status?: LoopStatus;
|
|
402
|
+
| { intent: 'list'; kind?: LoopKind; status?: LoopStatus; limit?: number; offset?: number }
|
|
263
403
|
);
|
|
264
404
|
|
|
265
405
|
// Standard facade envelope, matching bclaw_work / bclaw_coordinate output shape.
|
|
@@ -285,6 +425,18 @@ type NextExpectedHint =
|
|
|
285
425
|
| { action: 'close'; intent: 'bclaw_loop.close'; reason: string };
|
|
286
426
|
```
|
|
287
427
|
|
|
428
|
+
The `complete_turn` fence fields remain optional in the transport schema for
|
|
429
|
+
legacy turns. When the slot is backed by AttemptAuthority v2, the runtime
|
|
430
|
+
requires the complete tuple — assignment, turn, run, nonce, epoch, execution
|
|
431
|
+
contract hash and workspace digest — and rejects stale or partial evidence
|
|
432
|
+
before mutating the slot or LoopEvent journal.
|
|
433
|
+
|
|
434
|
+
For `turn(dispatch:true)`, a slot with a frozen agent keeps that identity. An
|
|
435
|
+
unbound slot can instead receive `target_agents`; Brainclaw resolves the
|
|
436
|
+
capability requirement deterministically, independent of array order. A
|
|
437
|
+
pre-cross rejection can therefore exclude that candidate and replay selection
|
|
438
|
+
without minting a second attempt.
|
|
439
|
+
|
|
288
440
|
**Why a single facade, not `bclaw_loop_open`/`_advance`/`_close` tools.** Consistency beats granularity for agent-facing DX. The two existing facades are intent-based; adding a third in the same style keeps the surface small and predictable. Agents that need low-level control can still go to the underlying store (local file reads, not MCP).
|
|
289
441
|
|
|
290
442
|
**Slot-bound auth.** `complete_turn` is a slot-owned mutation, so the server must resolve the target slot inside the lock and verify `caller.agentId === slot.agent_id`. If not, reject with `unauthorized_slot_write`. The single admin fallback is `caller.agentId === loop.created_by`, which allows the loop owner to recover a wedged slot or cancel it explicitly. Any future slot-specific intent added to this facade inherits the same rule.
|
|
@@ -315,8 +467,8 @@ one workflow among the five.
|
|
|
315
467
|
will drive or dispatch the resulting loop rather than creating an inert thread.
|
|
316
468
|
The shared lifecycle verbs are `turn`, `complete_turn`, `advance`,
|
|
317
469
|
`add_artifact`, `pause`, `resume`, and `close`. Implementation loops additionally
|
|
318
|
-
use `bind` to
|
|
319
|
-
command.
|
|
470
|
+
use engine-only `bind` to validate their linked sequence and enter `execute`,
|
|
471
|
+
then `turn(dispatch:true)` for worker slots; `verify` runs their declared command.
|
|
320
472
|
|
|
321
473
|
### Clarification is a cross-cutting primitive
|
|
322
474
|
|
|
@@ -341,78 +493,50 @@ The Loop engine is a **control plane**; existing primitives remain the **data pl
|
|
|
341
493
|
|
|
342
494
|
A Loop never copies these objects — it links them. Deleting the linked primitive does not break the loop; the reference just becomes dangling, surfaced in diagnostics.
|
|
343
495
|
|
|
344
|
-
##
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
The operator never copy-pastes. They see status in the board (`bclaw_context(kind="board")`) and can `bclaw_loop(intent="get", loop_id=…)` for detail.
|
|
376
|
-
|
|
377
|
-
## Automation: extending `bclaw_coordinate(intent='ideate')`
|
|
378
|
-
|
|
379
|
-
Shipped in v1.5.0 (pln#492). The full design — phases, context_filter,
|
|
380
|
-
iteration block, advance_gate, brief assembly, system events,
|
|
381
|
-
single-agent vs multi-agent UX — lives in [ideation-loop.md](./ideation-loop.md).
|
|
382
|
-
Summary for the loop-engine perspective:
|
|
383
|
-
|
|
384
|
-
- `bclaw_coordinate(intent='ideate', task=…, [targetAgents=[…]])` opens
|
|
385
|
-
an ideation_loop with the caller as `champion` slot and the targets
|
|
386
|
-
(when provided) as `critic` slots. The task is stored verbatim as
|
|
387
|
-
the `proposal` artifact (sliced to the 4 KB body cap).
|
|
388
|
-
- Single-agent mode (no `targetAgents`): the loop opens at the
|
|
389
|
-
proposal phase and stops there. The champion drives the cycle
|
|
390
|
-
manually via `bclaw_loop(intent='turn'|'advance')`. Useful when the
|
|
391
|
-
loop's structure (memory filter, gate, iteration accounting) is
|
|
392
|
-
what's wanted, not the multi-slot orchestration.
|
|
393
|
-
- Multi-agent mode (explicit `targetAgents`): the driver advances
|
|
394
|
-
proposal → critique and dispatches a turn per critic with a brief
|
|
395
|
-
assembled by `buildIdeationBrief` — context-filtered (critic sees
|
|
396
|
-
only `traps + feedback + runtime_notes + critique_history`),
|
|
397
|
-
BM25-ranked via `search()`, capped at 48 KB.
|
|
398
|
-
|
|
399
|
-
The ideation_loop introduces three loop-engine extensions consumed by
|
|
400
|
-
this driver:
|
|
401
|
-
|
|
402
|
-
- `LoopPhase.context_filter?: LoopContextCategory[]` — closed enum
|
|
496
|
+
## Per-protocol guides
|
|
497
|
+
|
|
498
|
+
Each of the five kinds has its own operator-facing guide with the same
|
|
499
|
+
template — purpose, default protocol, entry points, advance gates, stop
|
|
500
|
+
condition, artifacts, routing, recovery, "when NOT to use", reference
|
|
501
|
+
implementation. Consult them for anything protocol-specific.
|
|
502
|
+
|
|
503
|
+
- [Review](../loops/review.md) — the most automated coordinator shortcut,
|
|
504
|
+
autonomous fix cycle on `request_changes`, symmetric review-and-fix mode.
|
|
505
|
+
- [Ideation](../loops/ideation.md) — memory-confrontation with a
|
|
506
|
+
per-phase context filter and a `critique↔revision` iteration block; see
|
|
507
|
+
also the full RFC in [ideation-loop.md](./ideation-loop.md).
|
|
508
|
+
- [Implementation](../loops/implementation.md) — `bind → execute↔verify →
|
|
509
|
+
handoff_ready`, deterministic `command_green` exit.
|
|
510
|
+
- [Research](../loops/research.md) — `investigate↔synthesize → conclude`,
|
|
511
|
+
no `blocked` outcome, `critic_signal` exit.
|
|
512
|
+
- [Debug](../loops/debug.md) — `reproduce → hypothesize↔isolate↔fix →
|
|
513
|
+
handoff`, mirrors implementation's `command_green` gate on the repro.
|
|
514
|
+
|
|
515
|
+
`bclaw_coordinate` exposes two of these as ergonomic shortcuts today:
|
|
516
|
+
`intent='review'` (with `open_loop: true`) and `intent='ideate'`. Both
|
|
517
|
+
were extended strictly backward-compatibly — every prior call still
|
|
518
|
+
behaves as before. The current coordinate vocabulary is
|
|
519
|
+
`assign | consult | review | reroute | summarize | ideate`.
|
|
520
|
+
|
|
521
|
+
## Common engine extensions used by protocols
|
|
522
|
+
|
|
523
|
+
Three engine extensions are shared across protocols; per-protocol guides
|
|
524
|
+
reference them rather than re-defining them.
|
|
525
|
+
|
|
526
|
+
- **`LoopPhase.context_filter?: LoopContextCategory[]`** — closed enum
|
|
403
527
|
with `'*'` wildcard. Drives per-phase memory selection at brief
|
|
404
|
-
assembly time.
|
|
405
|
-
-
|
|
406
|
-
vocabulary as a phase-exit guard. When unmet, the driver
|
|
407
|
-
`phase_advance_blocked` system event
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
(`
|
|
414
|
-
|
|
415
|
-
|
|
528
|
+
assembly time (ideation, implementation, research, debug all use it).
|
|
529
|
+
- **`LoopPhase.advance_gate?: StopCondition`** — re-uses the
|
|
530
|
+
`StopCondition` vocabulary as a phase-exit guard. When unmet, the driver
|
|
531
|
+
emits a `phase_advance_blocked` system event with a structured
|
|
532
|
+
`gate_reason` and throws an actionable error. Every protocol except
|
|
533
|
+
`review` ships at least one default gate.
|
|
534
|
+
- **`LoopProtocolConfig.iteration?: { cycle, max_iterations, exit_when }`**
|
|
535
|
+
— wraps an inner cycle. The FSM (`decideNextPhase` in
|
|
536
|
+
`iteration-engine.ts`) handles cycle progress and the `exit_when`
|
|
537
|
+
predicates (`no_new_critique_artifacts`, `critic_signal`,
|
|
538
|
+
`command_green`), and emits `max_iterations_reached` when the cap
|
|
539
|
+
fires.
|
|
416
540
|
|
|
417
541
|
Both new event kinds — `phase_advance_blocked` and
|
|
418
542
|
`max_iterations_reached` — live in the same event journal as
|
|
@@ -420,6 +544,38 @@ Both new event kinds — `phase_advance_blocked` and
|
|
|
420
544
|
artifacts (which would force every consumer to filter `is_system`
|
|
421
545
|
before processing content).
|
|
422
546
|
|
|
547
|
+
## Recovery and observability
|
|
548
|
+
|
|
549
|
+
Recovery of a dispatched turn is decision-driven, not marker-driven — a
|
|
550
|
+
recoverer reads the reservation record and acts on its
|
|
551
|
+
`(decision, launch.status, lease_deadline)` triple. The full set of
|
|
552
|
+
transitions and their handling lives in
|
|
553
|
+
[attempt-authority.md#recovery](./attempt-authority.md#recovery). Loop-level
|
|
554
|
+
recovery on top of that:
|
|
555
|
+
|
|
556
|
+
- **Projection repair before crossing.** The common dispatch choke point
|
|
557
|
+
replays create-or-validate operations for Assignment, AgentRun, claim and
|
|
558
|
+
slot while the grant is still armed. It crosses only after all four exist;
|
|
559
|
+
an already-crossed replay never acquires spawn authority again.
|
|
560
|
+
- **Terminal loop early-return.** Every mutating convergence
|
|
561
|
+
(`reconcileTurn`, `reconcileFailedTurn`) idempotent no-ops on a closed
|
|
562
|
+
loop and still releases the coordinator claim.
|
|
563
|
+
- **Journal crash recovery.** `max(event.seq) > thread.version` triggers
|
|
564
|
+
a synchronous journal replay before any new mutation proceeds (see the
|
|
565
|
+
commit protocol below).
|
|
566
|
+
- **Superseded-turn guard.** A newer turn taking over a slot binds
|
|
567
|
+
`slot.current_turn_id`; a late reconcile of the old turn no-ops.
|
|
568
|
+
- **Contradictions.** A turn-keyed completed+failed pair on the same
|
|
569
|
+
attempt withholds convergence and journals a `run_blocked` runtime
|
|
570
|
+
event with `status_reason: turn_evidence_contradiction`.
|
|
571
|
+
|
|
572
|
+
Observability is split across four surfaces — see
|
|
573
|
+
[attempt-authority.md#surfaces-and-their-roles](./attempt-authority.md#surfaces-and-their-roles).
|
|
574
|
+
In short: the `TurnReservation` record + launch decision cell is
|
|
575
|
+
**authoritative**; `LoopEvent` is **causal**; `RuntimeEvent` is
|
|
576
|
+
**telemetry**; the legacy `events.jsonl` stream is **compatibility-only**.
|
|
577
|
+
No consumer looks past its role. There is no fifth journal.
|
|
578
|
+
|
|
423
579
|
## Persistence
|
|
424
580
|
|
|
425
581
|
```
|
|
@@ -427,6 +583,7 @@ before processing content).
|
|
|
427
583
|
threads/<id>.json # main state
|
|
428
584
|
events/<id>.jsonl # append-only journal (seq/version authoritative)
|
|
429
585
|
locks/<id>.lock # per-loop exclusive lock (all intents on an existing loop, and opt-out `open`)
|
|
586
|
+
locks/<id>.lock.takeovers/<sha256(mutation_id)>.lock # immutable election claim for reaping one dead lock generation
|
|
430
587
|
locks/open/<agent_id>/<client_request_id>.lock # idempotent-`open` lock keyed on idempotency scope
|
|
431
588
|
idempotency/<id>/<client_request_id>.json # 24h cache of completed mutation responses (one loop)
|
|
432
589
|
idempotency-open/<agent_id>/<client_request_id>.json # 24h cache for `open` intent (no loop_id yet)
|
|
@@ -452,24 +609,40 @@ before processing content).
|
|
|
452
609
|
}
|
|
453
610
|
```
|
|
454
611
|
|
|
455
|
-
**
|
|
612
|
+
**Lease/deadline fields are diagnostic today.** `lease_until` is initialized to
|
|
613
|
+
`acquired_at + 60 s`; `hard_deadline` is initialized from the intent's expected
|
|
614
|
+
maximum duration and never moves. The current synchronous implementation does
|
|
615
|
+
**not** run a lease-renewal heartbeat and does not reap a lock merely because
|
|
616
|
+
either timestamp elapsed. This is deliberate: on Windows, a live process may be
|
|
617
|
+
suspended longer than a deadline and later resume. Until every committing write
|
|
618
|
+
has its own fence check, elapsed time alone is not proof that takeover is safe.
|
|
456
619
|
|
|
457
620
|
Default `max_mutation_duration` per intent:
|
|
458
621
|
|
|
459
622
|
| Intent | `max_mutation_duration` | Rationale |
|
|
460
623
|
|---|---|---|
|
|
461
|
-
| `open`, `turn`, `advance`, `pause`, `resume`, `close` | 30 s |
|
|
624
|
+
| `open`, `turn`, `advance`, `pause`, `resume`, `close` | 30 s | Short state transitions. A trusted `turn(dispatch:true)` prepares/launches through AttemptAuthority outside the loop commit window, so worker duration never extends this lock. |
|
|
462
625
|
| `add_artifact`, `complete_turn` | 60 s | May write small external ref files. |
|
|
463
626
|
|
|
464
|
-
|
|
627
|
+
These values describe the expected mutation window and support diagnostics.
|
|
628
|
+
They are not automatic takeover thresholds. Callers never interact with them.
|
|
465
629
|
|
|
466
|
-
**Why
|
|
630
|
+
**Why turn dispatch never holds the loop lock while a worker runs.** A single
|
|
631
|
+
slow agent must never block every other loop mutation. Coordination, sequence
|
|
632
|
+
dispatch, and trusted `turn(dispatch:true)` use AttemptAuthority crossing, but
|
|
633
|
+
process preparation/launch happens
|
|
634
|
+
outside the short loop-state commit. The worker reports back later via
|
|
635
|
+
harvest/reconciliation or `complete_turn`, which takes its own short lock.
|
|
467
636
|
|
|
468
637
|
**Commit protocol (lock-file CAS with intra-lock idempotency):**
|
|
469
638
|
|
|
470
639
|
Before step 1, for the opt-out `open` path only (no `client_request_id`), the handler **pre-mints** the `loop_id` (ULID). Every other intent already has a `loop_id`; the idempotent `open` path postpones minting to step 3 so the idempotency cache can guard it.
|
|
471
640
|
|
|
472
|
-
1. **Acquire lock.**
|
|
641
|
+
1. **Acquire lock.** Write the complete owner blob to a unique sibling temp file,
|
|
642
|
+
then hard-link that file to the lock path. Hard-link creation is the shared
|
|
643
|
+
create-if-absent primitive on POSIX and Windows; only one contender can win.
|
|
644
|
+
Remove the temp file after linking. On `EEXIST`, retry with jittered backoff
|
|
645
|
+
(10 ms base, capped at 500 ms total). After timeout, fail with `lock_timeout`.
|
|
473
646
|
2. **Idempotency short-circuit (inside lock).** If the caller supplied `client_request_id`:
|
|
474
647
|
- For mutations on an existing loop: look up `idempotency/<id>/<client_request_id>.json`.
|
|
475
648
|
- For `open`: look up `idempotency-open/<agent_id>/<client_request_id>.json`.
|
|
@@ -480,23 +653,45 @@ Before step 1, for the opt-out `open` path only (no `client_request_id`), the ha
|
|
|
480
653
|
- After replay/auth, if the caller supplied `expected_version` and `thread.version !== expected_version`: append a `LoopConflictRecord` to `conflicts/<id>.jsonl` (observability only, no `seq`, no `version` bump), release the lock, and return `{ status: 'error', code: 'version_conflict', actual_version }`.
|
|
481
654
|
- For idempotent `open` (locked on the idempotency scope): mint a fresh random `loop_id` (ULID) here. This is the only id-mint point for the idempotent path.
|
|
482
655
|
- For opt-out `open`: `loop_id` was already minted before step 1; nothing to do here.
|
|
483
|
-
4. **
|
|
484
|
-
|
|
656
|
+
4. **Entry fence, then commit.** Immediately before the synchronous verb, re-read
|
|
657
|
+
the lock and verify its `mutation_id`. On mismatch, abort with `lock_lost`.
|
|
658
|
+
The verb appends its event and materializes `thread.json`; journal-first
|
|
659
|
+
recovery catches a crash between those writes. There is currently no second
|
|
660
|
+
fence between event and thread writes, which is why automatic reaping is
|
|
661
|
+
restricted to owners proven dead on the local host.
|
|
662
|
+
5. **Atomic-rename thread.** Write the next thread state with `version =
|
|
663
|
+
prev_version + 1` (or `1` for `open`) and the mutation id associated with the
|
|
664
|
+
verb, then atomic-rename it over `threads/<loop_id>.json`.
|
|
485
665
|
6. **Persist idempotency record.** If `client_request_id` was supplied, write `{ response, request_hash, stored_at }` to the relevant idempotency path. (For `open`, the stored response includes the minted `loop_id` so retries get the same id back.)
|
|
486
|
-
7. **Release lock.**
|
|
666
|
+
7. **Release lock.** Re-read the lock and remove it only when its `mutation_id`
|
|
667
|
+
still belongs to this handler.
|
|
487
668
|
|
|
488
|
-
**Fencing token —
|
|
669
|
+
**Fencing token — current guarantee.** Every handler writes its own `mutation_id`
|
|
670
|
+
into the lock blob and checks it at verb entry. Release also compares that token,
|
|
671
|
+
so an old owner cannot remove a different generation. Because a live local owner
|
|
672
|
+
is never reaped, it cannot resume after takeover inside the synchronous verb.
|
|
673
|
+
Enabling deadline-based, remote-host, or asynchronous takeover in a future slice
|
|
674
|
+
requires propagating the fence check to every journal, projection, thread and
|
|
675
|
+
idempotency commit first.
|
|
489
676
|
|
|
490
677
|
The `event.seq` and `thread.version` advance in lockstep — a successful commit produces exactly one new event with `seq = new_version`. Conflict records in `conflicts/<id>.jsonl` are out-of-band and never affect `seq` or `version`. The shared `mutation_id` on both committed files pins which event materialized which thread revision. Because step 3 always replays `events/<id>.jsonl` before a new CAS decision, a stale materialized thread cannot cause the next writer to append a journal event "ahead" of `thread.json`; the journal remains authoritative, and each new mutation must first catch the thread up to it.
|
|
491
678
|
|
|
492
|
-
**Stale-lock recovery (
|
|
679
|
+
**Stale-lock recovery (proof-based and generation-fenced):**
|
|
493
680
|
|
|
494
|
-
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
-
|
|
681
|
+
- If `host_id === current_host_id` and no process with `pid` exists (checked via
|
|
682
|
+
`kill -0` / `OpenProcess`), the owner is proven dead and its generation is
|
|
683
|
+
eligible for automatic recovery.
|
|
684
|
+
- A contender first creates
|
|
685
|
+
`<lock>.takeovers/<sha256(observed_mutation_id)>.lock` with the same hard-link
|
|
686
|
+
create-if-absent primitive. Only that elected reaper may re-read and unlink the
|
|
687
|
+
observed generation. This prevents the Windows ABA race where a late reaper
|
|
688
|
+
deletes a freshly acquired generation.
|
|
689
|
+
- A live local PID, a different host, or elapsed lease/deadline fields fail
|
|
690
|
+
closed. The caller times out; an operator can inspect the blob before explicit
|
|
691
|
+
recovery.
|
|
498
692
|
|
|
499
|
-
|
|
693
|
+
This preserves short per-loop serialization without a global Loop Engine lock.
|
|
694
|
+
Independent loops and immutable AttemptAuthority cells remain parallel.
|
|
500
695
|
|
|
501
696
|
**Journal crash recovery:**
|
|
502
697
|
|
|
@@ -555,6 +750,9 @@ table above rather than treating review as the default abstraction.
|
|
|
555
750
|
|
|
556
751
|
## Related
|
|
557
752
|
|
|
753
|
+
- [attempt-authority.md](./attempt-authority.md) — identity, dispatch decisions and spawn authority for every turn
|
|
754
|
+
- [P0B projection-boundary tests](../../tests/unit/loops-p0b-projections-before-crossing.test.ts) — crash/replay coverage around the common pre-crossing boundary
|
|
755
|
+
- [Per-protocol guides](../loops/) — review / ideation / implementation / research / debug
|
|
558
756
|
- [plans-and-claims.md](plans-and-claims.md)
|
|
559
757
|
- [coordination.md](coordination.md)
|
|
560
758
|
- [dispatch-lifecycle.md](dispatch-lifecycle.md) — entity FSMs (loop / assignment / agent_run / claim), brief-ack semantics, log-file diagnostic playbook
|
|
@@ -562,13 +760,4 @@ table above rather than treating review as the default abstraction.
|
|
|
562
760
|
- pln#394 `feat/loop-engine-mvp`
|
|
563
761
|
- pln#395 `feat/review-loop-protocol`
|
|
564
762
|
- pln#392 `doc/mcp-versioning-and-surface-governance` (prerequisite)
|
|
565
|
-
|
|
566
|
-
## Review-specific reliability notes
|
|
567
|
-
|
|
568
|
-
Review loops retain an extra exactly-once fix-cycle implementation because they
|
|
569
|
-
can automatically redispatch after `request_changes`. A reviewer writes
|
|
570
|
-
`review_verdict` and `review_summary` to `LANE-RESULT.json`; harvest maps this
|
|
571
|
-
to the loop, auto-closes on approval, and boundedly redispatches fix work when
|
|
572
|
-
appropriate. This is review-specific automation, not a limit on the other
|
|
573
|
-
workflow kinds. Set `BRAINCLAW_TURN_OWNED_REVIEW=0` (also `false`/`off`/`no`)
|
|
574
|
-
only to fall back to the legacy review finalizer.
|
|
763
|
+
- pln#676 / dec#171 — attempt-authority rollout
|