brainclaw 1.28.5 → 1.28.6
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.
|
Binary file
|
|
@@ -34,9 +34,28 @@ function deriveBindings(loop, sequenceId, cwd) {
|
|
|
34
34
|
if (loop.slots.length !== lanes.length) {
|
|
35
35
|
throw new Error(`impl-bind lane/slot mismatch: sequence ${sequenceId} has ${lanes.length} lane(s) (${lanes.join(', ')}) but loop has ${loop.slots.length} slot(s); open one worker slot per lane`);
|
|
36
36
|
}
|
|
37
|
+
// A positional fallback is unsafe here: callers commonly attach a
|
|
38
|
+
// perspective to a slot, and silently pairing that perspective with a
|
|
39
|
+
// different alphabetically-sorted lane can invert the lane's scope policy.
|
|
40
|
+
// Single-lane sequences retain the historical default-slot convenience;
|
|
41
|
+
// multi-lane binds must carry an explicit, unique lane on every slot.
|
|
42
|
+
if (lanes.length > 1) {
|
|
43
|
+
const slotLanes = loop.slots.map((slot) => slot.lane?.trim() || '');
|
|
44
|
+
if (slotLanes.some((lane) => !lane)) {
|
|
45
|
+
throw new Error(`impl-bind requires explicit slot.lane for every lane when binding multiple lanes (${lanes.join(', ')}); positional slot order is rejected to prevent lane permutation`);
|
|
46
|
+
}
|
|
47
|
+
const uniqueSlotLanes = new Set(slotLanes);
|
|
48
|
+
if (uniqueSlotLanes.size !== slotLanes.length || uniqueSlotLanes.size !== lanes.length) {
|
|
49
|
+
throw new Error(`impl-bind requires one unique slot.lane per sequence lane; slots=${slotLanes.join(', ')}, lanes=${lanes.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
const unknown = slotLanes.filter((lane) => !grouped.has(lane));
|
|
52
|
+
if (unknown.length > 0) {
|
|
53
|
+
throw new Error(`impl-bind slot lane(s) not present in sequence ${sequenceId}: ${unknown.join(', ')}; expected ${lanes.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
37
56
|
const bindings = {};
|
|
38
57
|
loop.slots.forEach((slot, index) => {
|
|
39
|
-
const lane = lanes[index];
|
|
58
|
+
const lane = lanes.length > 1 ? slot.lane.trim() : (slot.lane?.trim() || lanes[index]);
|
|
40
59
|
const items = grouped.get(lane);
|
|
41
60
|
const scopes = [...new Set(items.map((item) => item.scope_hint?.trim()).filter((value) => Boolean(value)))];
|
|
42
61
|
bindings[slot.slot_id] = {
|
package/dist/core/loops/store.js
CHANGED
|
@@ -62,6 +62,18 @@ function resolveProtocol(kind, mode) {
|
|
|
62
62
|
}
|
|
63
63
|
return iteration ? { iteration } : undefined;
|
|
64
64
|
}
|
|
65
|
+
function collectStopConditionPhases(condition) {
|
|
66
|
+
switch (condition.kind) {
|
|
67
|
+
case 'phase_reached':
|
|
68
|
+
case 'artifact_produced':
|
|
69
|
+
return [condition.phase];
|
|
70
|
+
case 'any':
|
|
71
|
+
case 'all':
|
|
72
|
+
return condition.conditions.flatMap(collectStopConditionPhases);
|
|
73
|
+
default:
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
65
77
|
function buildSlot(partial) {
|
|
66
78
|
return {
|
|
67
79
|
slot_id: partial.slot_id ?? generateSlotId(),
|
|
@@ -141,6 +153,12 @@ export function openLoop(input, cwd) {
|
|
|
141
153
|
if (phaseNames.size !== phases.length) {
|
|
142
154
|
throw new Error('openLoop: phase names must be unique');
|
|
143
155
|
}
|
|
156
|
+
const stopCondition = input.stop_condition ?? protocolDefaults.stop_condition;
|
|
157
|
+
const referencedPhases = collectStopConditionPhases(stopCondition);
|
|
158
|
+
const invalidPhase = referencedPhases.find((phase) => !phaseNames.has(phase));
|
|
159
|
+
if (invalidPhase) {
|
|
160
|
+
throw new Error(`openLoop: stop_condition references unknown phase "${invalidPhase}"; expected one of ${[...phaseNames].join(', ')}`);
|
|
161
|
+
}
|
|
144
162
|
const now = nowISO();
|
|
145
163
|
const id = generateLoopId();
|
|
146
164
|
const mutation_id = generateMutationId();
|
|
@@ -174,7 +192,7 @@ export function openLoop(input, cwd) {
|
|
|
174
192
|
// questions; the request_input handler (step 2) appends/removes ids.
|
|
175
193
|
open_questions: [],
|
|
176
194
|
linked: input.linked,
|
|
177
|
-
stop_condition:
|
|
195
|
+
stop_condition: stopCondition,
|
|
178
196
|
evidence_policy: evidencePolicyForNewLoop(),
|
|
179
197
|
created_at: now,
|
|
180
198
|
updated_at: now,
|
package/dist/core/loops/types.js
CHANGED
|
@@ -33,7 +33,7 @@ export const LoopLinksSchema = z.object({
|
|
|
33
33
|
source_artifact_digest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
34
34
|
/** Durable, deterministic identity of the policy decision that created this loop. */
|
|
35
35
|
continuation_key: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
36
|
-
});
|
|
36
|
+
}).strict();
|
|
37
37
|
/**
|
|
38
38
|
* Memory categories a loop phase can request via `context_filter` (pln#492).
|
|
39
39
|
*
|
package/dist/facts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
|
|
2
|
-
// Source: brainclaw v1.28.
|
|
2
|
+
// Source: brainclaw v1.28.6 on 2026-08-28T22:03:41.224Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.28.
|
|
5
|
-
"generated_at": "2026-08-
|
|
4
|
+
"version": "1.28.6",
|
|
5
|
+
"generated_at": "2026-08-28T22:03:41.224Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 71,
|
|
8
8
|
"published_count": 69,
|
|
@@ -479,7 +479,7 @@ export const FACTS = {
|
|
|
479
479
|
},
|
|
480
480
|
"bench": {
|
|
481
481
|
"schema": "brainclaw.bench.v1",
|
|
482
|
-
"generated_at": "2026-08-
|
|
482
|
+
"generated_at": "2026-08-28T22:03:39.012Z",
|
|
483
483
|
"node_version": "v24.19.0",
|
|
484
484
|
"platform": "linux-x64",
|
|
485
485
|
"repeats": 3,
|
|
@@ -488,7 +488,7 @@ export const FACTS = {
|
|
|
488
488
|
"name": "cold_onboard",
|
|
489
489
|
"volume": "empty",
|
|
490
490
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
491
|
-
"duration_ms_median":
|
|
491
|
+
"duration_ms_median": 77,
|
|
492
492
|
"payload_chars_median": 1640,
|
|
493
493
|
"payload_tokens_est_median": 410
|
|
494
494
|
},
|
|
@@ -496,15 +496,15 @@ export const FACTS = {
|
|
|
496
496
|
"name": "warm_work",
|
|
497
497
|
"volume": "medium",
|
|
498
498
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
499
|
-
"duration_ms_median":
|
|
500
|
-
"payload_chars_median":
|
|
501
|
-
"payload_tokens_est_median":
|
|
499
|
+
"duration_ms_median": 129,
|
|
500
|
+
"payload_chars_median": 2626,
|
|
501
|
+
"payload_tokens_est_median": 657
|
|
502
502
|
},
|
|
503
503
|
{
|
|
504
504
|
"name": "first_edit",
|
|
505
505
|
"volume": "medium",
|
|
506
506
|
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
507
|
-
"duration_ms_median":
|
|
507
|
+
"duration_ms_median": 12,
|
|
508
508
|
"payload_chars_median": 1680,
|
|
509
509
|
"payload_tokens_est_median": 420
|
|
510
510
|
}
|
package/dist/facts.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.28.
|
|
3
|
-
"generated_at": "2026-08-
|
|
2
|
+
"version": "1.28.6",
|
|
3
|
+
"generated_at": "2026-08-28T22:03:41.224Z",
|
|
4
4
|
"tools": {
|
|
5
5
|
"count": 71,
|
|
6
6
|
"published_count": 69,
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
},
|
|
478
478
|
"bench": {
|
|
479
479
|
"schema": "brainclaw.bench.v1",
|
|
480
|
-
"generated_at": "2026-08-
|
|
480
|
+
"generated_at": "2026-08-28T22:03:39.012Z",
|
|
481
481
|
"node_version": "v24.19.0",
|
|
482
482
|
"platform": "linux-x64",
|
|
483
483
|
"repeats": 3,
|
|
@@ -486,7 +486,7 @@
|
|
|
486
486
|
"name": "cold_onboard",
|
|
487
487
|
"volume": "empty",
|
|
488
488
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
489
|
-
"duration_ms_median":
|
|
489
|
+
"duration_ms_median": 77,
|
|
490
490
|
"payload_chars_median": 1640,
|
|
491
491
|
"payload_tokens_est_median": 410
|
|
492
492
|
},
|
|
@@ -494,15 +494,15 @@
|
|
|
494
494
|
"name": "warm_work",
|
|
495
495
|
"volume": "medium",
|
|
496
496
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
497
|
-
"duration_ms_median":
|
|
498
|
-
"payload_chars_median":
|
|
499
|
-
"payload_tokens_est_median":
|
|
497
|
+
"duration_ms_median": 129,
|
|
498
|
+
"payload_chars_median": 2626,
|
|
499
|
+
"payload_tokens_est_median": 657
|
|
500
500
|
},
|
|
501
501
|
{
|
|
502
502
|
"name": "first_edit",
|
|
503
503
|
"volume": "medium",
|
|
504
504
|
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
505
|
-
"duration_ms_median":
|
|
505
|
+
"duration_ms_median": 12,
|
|
506
506
|
"payload_chars_median": 1680,
|
|
507
507
|
"payload_tokens_est_median": 420
|
|
508
508
|
}
|
|
@@ -20,9 +20,11 @@ a worker. `execute ↔ verify` iterates until the verify command is green or
|
|
|
20
20
|
the cycle cap is hit.
|
|
21
21
|
|
|
22
22
|
Binding validates the complete graph: every sequence item must reference a
|
|
23
|
-
linked, existing plan (and an existing step when `stepId` is present).
|
|
24
|
-
sequence
|
|
25
|
-
|
|
23
|
+
linked, existing plan (and an existing step when `stepId` is present). A
|
|
24
|
+
multi-lane sequence requires exactly one explicitly named worker slot per lane;
|
|
25
|
+
positional slots are rejected because sorting lanes could otherwise attach a
|
|
26
|
+
perspective or scope policy to the wrong lane. Each slot then carries its lane,
|
|
27
|
+
plan/step ids, and `scope_hint`.
|
|
26
28
|
|
|
27
29
|
## Default protocol
|
|
28
30
|
|
|
@@ -46,9 +48,13 @@ participating slot must produce its expected artifact before advance fires.
|
|
|
46
48
|
## Entry points
|
|
47
49
|
|
|
48
50
|
- **Direct open (typical).**
|
|
49
|
-
`bclaw_loop(intent='open', kind='implementation', slots=[…], linked={plan_ids:[…], sequence_ids:[…]}, allow_orphan=true)`
|
|
50
|
-
followed by `bind`. `allow_orphan=true`
|
|
51
|
-
drive worker turns.
|
|
51
|
+
`bclaw_loop(intent='open', kind='implementation', slots=[{role:'implementer', lane:'api'}, …], linked={plan_ids:[…], sequence_ids:[…]}, allow_orphan=true)`
|
|
52
|
+
followed by a dry-run `bind`, then the real `bind`. `allow_orphan=true`
|
|
53
|
+
acknowledges that the caller will drive worker turns. For multiple lanes,
|
|
54
|
+
every slot must name its lane; the bind response must still be checked for
|
|
55
|
+
lane/perspective alignment. The `linked` key is exactly
|
|
56
|
+
`{sequence_ids:[…], plan_ids:[…]}`; unknown keys are rejected by the open
|
|
57
|
+
envelope, and a dry-run is the safe way to validate the complete contract.
|
|
52
58
|
- **Via bind.** `bclaw_loop(intent='bind', loop_id=…)` validates the linked
|
|
53
59
|
sequence and advances `bind → execute`. Historical launch options
|
|
54
60
|
(`lanes`, `auto_execute`, `model`, `max_assignments`) remain accepted
|
|
@@ -59,6 +65,11 @@ participating slot must produce its expected artifact before advance fires.
|
|
|
59
65
|
slots may be dispatched concurrently. Without `dispatch=true`, `turn` is
|
|
60
66
|
state-only and never starts a process.
|
|
61
67
|
|
|
68
|
+
An implementation loop has no `update` intent. If opening or binding fails,
|
|
69
|
+
cancel the invalid open loop and reopen it with corrected links/slots. The
|
|
70
|
+
default phases are `bind`, `execute`, `verify`, and `handoff_ready`; a custom
|
|
71
|
+
`stop_condition` must reference only phases present in the loop.
|
|
72
|
+
|
|
62
73
|
## Advance gates
|
|
63
74
|
|
|
64
75
|
`verify` carries an `advance_gate`:
|