brainclaw 1.28.0 → 1.28.2
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/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli/register-coordination.js +12 -0
- package/dist/commands/code-map.js +2 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/harvest.js +32 -43
- package/dist/commands/loop.js +12 -0
- package/dist/commands/loops-handlers.js +284 -17
- package/dist/commands/mcp-catalog.js +6 -3
- package/dist/commands/mcp-write-claims.js +55 -8
- package/dist/commands/mcp-write-coordination.js +413 -137
- package/dist/commands/mcp.js +32 -4
- package/dist/core/actions.js +17 -3
- package/dist/core/agentrun-reconciler.js +138 -4
- package/dist/core/claims.js +4 -1
- package/dist/core/code-map/backend.js +8 -0
- package/dist/core/execution-adapters.js +15 -7
- package/dist/core/hygiene-policy.js +2 -1
- package/dist/core/loop-turn-dispatch.js +18 -1
- package/dist/core/loops/attempt-authority.js +22 -4
- package/dist/core/loops/attempt-generations.js +17 -4
- package/dist/core/loops/attempt-reservation.js +14 -1
- package/dist/core/loops/attempt-takeover.js +173 -76
- package/dist/core/loops/continuation.js +337 -0
- package/dist/core/loops/facade-schema.js +15 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/reconcile-turn.js +224 -26
- package/dist/core/loops/result-reducers.js +8 -8
- package/dist/core/loops/turn-execution.js +38 -19
- package/dist/core/loops/types.js +9 -0
- package/dist/core/loops/verbs.js +1 -1
- package/dist/core/reviewer-policy.js +39 -0
- package/dist/core/schema.js +16 -1
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/cli.md +4 -2
- package/docs/code-map.md +10 -0
- package/docs/concepts/loop-engine.md +30 -0
- package/docs/mcp-schema-changelog.md +6 -1
- package/package.json +1 -1
|
@@ -13,22 +13,23 @@ import { getLoop } from './store.js';
|
|
|
13
13
|
import { phasePolicy } from './kind-policies.js';
|
|
14
14
|
/** Idempotently materialize every child projection required before launch. */
|
|
15
15
|
export function ensureTurnExecutionProjections(reservation, input, cwd) {
|
|
16
|
-
|
|
16
|
+
const claimId = input.claim_id ?? reservation.claim_id;
|
|
17
|
+
if (claimId === '')
|
|
17
18
|
throw new Error('attempt reservation has no claim');
|
|
18
19
|
ensureAssignmentProjection({
|
|
19
20
|
id: input.assignment_id,
|
|
20
21
|
short_label: input.assignment_id,
|
|
21
|
-
claim_id:
|
|
22
|
+
claim_id: claimId,
|
|
22
23
|
agent: input.agent,
|
|
23
24
|
agent_id: input.agent_id,
|
|
24
25
|
dispatcher_agent: input.dispatcher_agent,
|
|
25
26
|
dispatcher_session_id: input.dispatcher_session_id,
|
|
26
27
|
scope: input.scope,
|
|
27
28
|
description: input.description,
|
|
28
|
-
// Assignment is
|
|
29
|
-
//
|
|
30
|
-
execution_contract_ref: reservation.execution_contract_ref,
|
|
31
|
-
capability_snapshot: reservation.capability_snapshot,
|
|
29
|
+
// Assignment is generation-scoped. Generation zero preserves the historic
|
|
30
|
+
// deterministic id; every takeover projects a fresh assignment and contract.
|
|
31
|
+
execution_contract_ref: input.execution_contract_ref ?? reservation.execution_contract_ref,
|
|
32
|
+
capability_snapshot: input.capability_snapshot ?? reservation.capability_snapshot,
|
|
32
33
|
tags: input.assignment_tags ?? ['coordinate', 'loop', 'turn-owned'],
|
|
33
34
|
}, cwd);
|
|
34
35
|
input.on_projection?.('assignment');
|
|
@@ -36,7 +37,7 @@ export function ensureTurnExecutionProjections(reservation, input, cwd) {
|
|
|
36
37
|
id: input.run_id,
|
|
37
38
|
short_label: input.run_id,
|
|
38
39
|
assignment_id: input.assignment_id,
|
|
39
|
-
claim_id:
|
|
40
|
+
claim_id: claimId,
|
|
40
41
|
attempt_index: input.attempt_index ?? 1,
|
|
41
42
|
agent: input.agent,
|
|
42
43
|
agent_id: input.agent_id,
|
|
@@ -50,7 +51,7 @@ export function ensureTurnExecutionProjections(reservation, input, cwd) {
|
|
|
50
51
|
tags: input.run_tags ?? ['turn-owned', 'loop'],
|
|
51
52
|
}, cwd);
|
|
52
53
|
input.on_projection?.('run');
|
|
53
|
-
ensureClaimAssignmentBinding(
|
|
54
|
+
ensureClaimAssignmentBinding(claimId, input.assignment_id, cwd, {
|
|
54
55
|
worktreePath: input.worktree_path,
|
|
55
56
|
});
|
|
56
57
|
input.on_projection?.('claim_binding');
|
|
@@ -61,7 +62,7 @@ export function ensureTurnExecutionProjections(reservation, input, cwd) {
|
|
|
61
62
|
input: input.task,
|
|
62
63
|
turn_id: input.turn_id,
|
|
63
64
|
assignment_id: input.assignment_id,
|
|
64
|
-
claim_id:
|
|
65
|
+
claim_id: claimId,
|
|
65
66
|
}, cwd);
|
|
66
67
|
input.on_projection?.('slot_binding');
|
|
67
68
|
input.on_projection?.('before_crossing');
|
|
@@ -71,7 +72,11 @@ function preconditionDenied(reason) {
|
|
|
71
72
|
}
|
|
72
73
|
function authorityDenied(input, turnId, reason) {
|
|
73
74
|
const reservation = getReservation(turnId, input.cwd);
|
|
74
|
-
const
|
|
75
|
+
const chain = resolveTurnGenerationChain(input.cwd, turnId);
|
|
76
|
+
const authorityClaimId = chain?.status === 'active'
|
|
77
|
+
? chain.latest_generation.executor?.claim_id ?? reservation?.claim_id
|
|
78
|
+
: reservation?.claim_id;
|
|
79
|
+
const ownsAuthority = authorityClaimId === input.claim_id;
|
|
75
80
|
const crossed = reservation?.launch?.status === 'crossed';
|
|
76
81
|
return {
|
|
77
82
|
kind: 'denied',
|
|
@@ -129,13 +134,19 @@ export function prepareTurnExecution(input) {
|
|
|
129
134
|
}, input.cwd);
|
|
130
135
|
const childIds = deriveChildIds(turnId);
|
|
131
136
|
const existingReservation = getReservation(turnId, input.cwd);
|
|
137
|
+
const v2 = resolveTurnGenerationChain(input.cwd, turnId);
|
|
138
|
+
const activeExecutor = v2?.status === 'active' && v2.latest_generation.attempt_epoch > 0
|
|
139
|
+
? v2.latest_generation.executor
|
|
140
|
+
: undefined;
|
|
132
141
|
if (slot.agent !== undefined && slot.agent !== input.agent) {
|
|
133
142
|
return preconditionDenied(`slot ${input.slot_id} belongs to agent '${slot.agent}', not '${input.agent}'`);
|
|
134
143
|
}
|
|
135
144
|
if (slot.agent_id !== undefined && slot.agent_id !== input.agent_id) {
|
|
136
145
|
return preconditionDenied(`slot ${input.slot_id} belongs to agent_id '${slot.agent_id}', not '${input.agent_id ?? 'none'}'`);
|
|
137
146
|
}
|
|
138
|
-
if (slot.claim_id !== undefined
|
|
147
|
+
if (slot.claim_id !== undefined
|
|
148
|
+
&& slot.claim_id !== input.claim_id
|
|
149
|
+
&& ['assigned', 'working', 'waiting_input'].includes(slot.status)) {
|
|
139
150
|
return preconditionDenied(`slot ${input.slot_id} is bound to claim ${slot.claim_id}, not ${input.claim_id}`);
|
|
140
151
|
}
|
|
141
152
|
if (slot.current_turn_id !== undefined
|
|
@@ -202,13 +213,15 @@ export function prepareTurnExecution(input) {
|
|
|
202
213
|
catch (error) {
|
|
203
214
|
return preconditionDenied(error instanceof Error ? error.message : String(error));
|
|
204
215
|
}
|
|
205
|
-
const frozenHarnessBinding =
|
|
216
|
+
const frozenHarnessBinding = activeExecutor?.capability_snapshot.resolved.harness
|
|
217
|
+
?? existingReservation?.capability_snapshot?.resolved.harness;
|
|
206
218
|
if (frozenHarnessBinding && JSON.stringify(frozenHarnessBinding) !== JSON.stringify(requestedHarnessBinding)) {
|
|
207
219
|
return preconditionDenied(`harness binding differs from immutable capability snapshot: frozen `
|
|
208
220
|
+ `${frozenHarnessBinding.adapter_id}@${frozenHarnessBinding.adapter_version}, requested `
|
|
209
221
|
+ `${requestedHarnessBinding.adapter_id}@${requestedHarnessBinding.adapter_version}`);
|
|
210
222
|
}
|
|
211
|
-
const capabilitySnapshot =
|
|
223
|
+
const capabilitySnapshot = activeExecutor?.capability_snapshot
|
|
224
|
+
?? existingReservation?.capability_snapshot
|
|
212
225
|
?? resolveCapabilitySnapshot(input.agent, capabilityRequirement, input.agent_id, requestedHarnessBinding);
|
|
213
226
|
if (!capabilitySnapshot.accepted) {
|
|
214
227
|
const reasons = capabilitySnapshot.reasons.map((reason) => reason.code).join(', ');
|
|
@@ -256,7 +269,6 @@ export function prepareTurnExecution(input) {
|
|
|
256
269
|
const contractRef = contract
|
|
257
270
|
? (existingReservation?.execution_contract_ref ?? executionContractRef(contract, capabilitySnapshot))
|
|
258
271
|
: undefined;
|
|
259
|
-
const v2 = resolveTurnGenerationChain(input.cwd, turnId);
|
|
260
272
|
const activeRollout = resolveActiveAttemptRollout(input.cwd);
|
|
261
273
|
const localHome = readLocalAuthorityHome(input.cwd);
|
|
262
274
|
if (activeRollout && (!localHome || !contractRef)) {
|
|
@@ -339,10 +351,16 @@ export function prepareTurnExecution(input) {
|
|
|
339
351
|
try {
|
|
340
352
|
const generation = v2.latest_generation;
|
|
341
353
|
const generationContract = executionContractForGeneration(reservation, generation);
|
|
354
|
+
const executor = generation.executor ?? {
|
|
355
|
+
agent: reservation.agent,
|
|
356
|
+
agent_id: reservation.agent_id,
|
|
357
|
+
claim_id: reservation.claim_id,
|
|
358
|
+
capability_snapshot: reservation.capability_snapshot,
|
|
359
|
+
};
|
|
342
360
|
let accepted;
|
|
343
361
|
try {
|
|
344
362
|
accepted = input.accepted_execution_contract
|
|
345
|
-
?? attestHarnessContractAcceptance(generationContract.ref,
|
|
363
|
+
?? attestHarnessContractAcceptance(generationContract.ref, executor.capability_snapshot, requestedHarnessBinding);
|
|
346
364
|
}
|
|
347
365
|
catch (error) {
|
|
348
366
|
return preconditionDenied(`worker contract acceptance unavailable before crossing: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -357,8 +375,9 @@ export function prepareTurnExecution(input) {
|
|
|
357
375
|
turn_id: turnId,
|
|
358
376
|
assignment_id: generation.assignment_id,
|
|
359
377
|
run_id: generation.run_id,
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
claim_id: executor.claim_id,
|
|
379
|
+
agent: executor.agent,
|
|
380
|
+
agent_id: executor.agent_id,
|
|
362
381
|
dispatcher_agent: input.dispatcher_agent,
|
|
363
382
|
dispatcher_agent_id: input.dispatcher_agent_id,
|
|
364
383
|
dispatcher_session_id: input.dispatcher_session_id,
|
|
@@ -370,7 +389,7 @@ export function prepareTurnExecution(input) {
|
|
|
370
389
|
run_tags: [...(input.run_tags ?? ['turn-owned', 'loop']), `attempt-generation:${generation.attempt_epoch}`],
|
|
371
390
|
attempt_index: generation.attempt_epoch + 1,
|
|
372
391
|
execution_contract_ref: generationContract.ref,
|
|
373
|
-
capability_snapshot:
|
|
392
|
+
capability_snapshot: executor.capability_snapshot,
|
|
374
393
|
on_projection: input.on_projection,
|
|
375
394
|
}, input.cwd);
|
|
376
395
|
const crossing = crossActiveAttemptGenerationV2(turnId, generation.attempt_epoch, localHome, input.dispatcher_agent_id ?? input.dispatcher_agent, input.dispatcher_agent_id ?? input.dispatcher_agent, input.cwd);
|
|
@@ -387,7 +406,7 @@ export function prepareTurnExecution(input) {
|
|
|
387
406
|
workspace_path: generation.workspace_path,
|
|
388
407
|
contract_status: 'contracted',
|
|
389
408
|
execution_contract_ref: generationContract.ref,
|
|
390
|
-
capability_snapshot:
|
|
409
|
+
capability_snapshot: executor.capability_snapshot,
|
|
391
410
|
};
|
|
392
411
|
}
|
|
393
412
|
catch (error) {
|
package/dist/core/loops/types.js
CHANGED
|
@@ -25,6 +25,12 @@ export const LoopLinksSchema = z.object({
|
|
|
25
25
|
sequence_ids: z.array(z.string().min(1)).optional(),
|
|
26
26
|
/** Upstream loop in an ideation → implementation → review pipeline. */
|
|
27
27
|
source_loop_id: z.string().regex(/^lop_[0-9a-z]+$/).optional(),
|
|
28
|
+
/** Exact upstream artifact that authorized this continuation. */
|
|
29
|
+
source_artifact_id: z.string().regex(/^art_[0-9a-z]+$/).optional(),
|
|
30
|
+
/** Sealed digest of source_artifact_id at continuation evaluation time. */
|
|
31
|
+
source_artifact_digest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
32
|
+
/** Durable, deterministic identity of the policy decision that created this loop. */
|
|
33
|
+
continuation_key: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
28
34
|
});
|
|
29
35
|
/**
|
|
30
36
|
* Memory categories a loop phase can request via `context_filter` (pln#492).
|
|
@@ -728,6 +734,9 @@ export const LoopEventSchema = z.discriminatedUnion('kind', [
|
|
|
728
734
|
slot_id: z.string().min(1),
|
|
729
735
|
turn_id: z.string().min(1),
|
|
730
736
|
assignment_id: z.string().min(1),
|
|
737
|
+
claim_id: z.string().min(1).optional(),
|
|
738
|
+
agent: z.string().min(1).optional(),
|
|
739
|
+
agent_id: z.string().min(1).optional(),
|
|
731
740
|
from_epoch: z.number().int().nonnegative(),
|
|
732
741
|
to_epoch: z.number().int().positive(),
|
|
733
742
|
from_run_id: z.string().min(1),
|
package/dist/core/loops/verbs.js
CHANGED
|
@@ -586,7 +586,7 @@ function authorizeCompleteTurnAttempt(input, slot, cwd) {
|
|
|
586
586
|
slot_role: slot.role,
|
|
587
587
|
turn_id: generation.turn_id,
|
|
588
588
|
assignment_id: generation.assignment_id,
|
|
589
|
-
claim_id:
|
|
589
|
+
claim_id: generation.executor?.claim_id ?? slot.claim_id ?? reservation.claim_id,
|
|
590
590
|
run_id: generation.run_id,
|
|
591
591
|
nonce: generation.launch_nonce,
|
|
592
592
|
attempt_epoch: generation.attempt_epoch,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { listAgentIdentities } from './agent-registry.js';
|
|
2
|
+
import { resolveExecutionCandidate } from './execution-contract.js';
|
|
3
|
+
export const REVIEWER_SELECTION_POLICY_VERSION = 'reviewer-selection-v1';
|
|
4
|
+
/**
|
|
5
|
+
* Select a concrete review worker from project-registered identities.
|
|
6
|
+
*
|
|
7
|
+
* The shared execution-contract resolver supplies capability checks and stable
|
|
8
|
+
* ordering. The policy additionally enforces reviewer independence by
|
|
9
|
+
* excluding every identity frozen onto an implementation slot.
|
|
10
|
+
*/
|
|
11
|
+
export function selectImplementationReviewer(source, cwd) {
|
|
12
|
+
if (source.kind !== 'implementation') {
|
|
13
|
+
throw new Error(`reviewer_selection_source_invalid: loop ${source.id} is ${source.kind}`);
|
|
14
|
+
}
|
|
15
|
+
const excludedImplementers = source.slots
|
|
16
|
+
.filter((slot) => Boolean(slot.agent))
|
|
17
|
+
.map((slot) => ({ agent: slot.agent, ...(slot.agent_id ? { agent_id: slot.agent_id } : {}) }));
|
|
18
|
+
const excludedNames = new Set(excludedImplementers.map((identity) => identity.agent.normalize('NFC')));
|
|
19
|
+
const excludedIds = new Set(excludedImplementers.flatMap((identity) => identity.agent_id ? [identity.agent_id.normalize('NFC')] : []));
|
|
20
|
+
const identities = listAgentIdentities(cwd)
|
|
21
|
+
.filter((identity) => identity.kind !== 'human')
|
|
22
|
+
.filter((identity) => !excludedNames.has(identity.agent_name.normalize('NFC')) && !excludedIds.has(identity.agent_id.normalize('NFC')))
|
|
23
|
+
.map((identity) => ({ agent: identity.agent_name, agent_id: identity.agent_id }));
|
|
24
|
+
const resolution = resolveExecutionCandidate(identities, { roles: ['review'], required_surfaces: ['cli_spawn'], execution_surfaces: [], required_tools: [] });
|
|
25
|
+
if (resolution.kind !== 'selected') {
|
|
26
|
+
const reasons = resolution.evaluated
|
|
27
|
+
.map((candidate) => `${candidate.agent}:${candidate.snapshot.reasons.map((reason) => reason.code).join('+') || 'excluded'}`)
|
|
28
|
+
.join(', ');
|
|
29
|
+
throw new Error(`continuation_reviewer_unavailable: no independent spawnable reviewer${reasons ? ` (${reasons})` : ''}`);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
policy_version: REVIEWER_SELECTION_POLICY_VERSION,
|
|
33
|
+
agent: resolution.selected.agent,
|
|
34
|
+
agent_id: resolution.selected.agent_id,
|
|
35
|
+
evaluated: resolution.evaluated,
|
|
36
|
+
excluded_implementers: excludedImplementers,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=reviewer-policy.js.map
|
package/dist/core/schema.js
CHANGED
|
@@ -923,11 +923,18 @@ export const ActionRequiredResponseSchema = z.object({
|
|
|
923
923
|
responded_by_id: z.string().optional(),
|
|
924
924
|
responded_at: z.string(),
|
|
925
925
|
});
|
|
926
|
+
export const ActionRequiredTargetSchema = z.discriminatedUnion('kind', [
|
|
927
|
+
z.object({ kind: z.literal('assignment'), assignment_id: z.string() }),
|
|
928
|
+
z.object({ kind: z.literal('continuation'), continuation_id: z.string().regex(/^ctn_[a-f0-9]{24}$/) }),
|
|
929
|
+
]);
|
|
926
930
|
export const ActionRequiredSchema = z.object({
|
|
927
931
|
schema_version: z.number().int().positive().optional(),
|
|
928
932
|
id: z.string(),
|
|
929
933
|
short_label: z.string().optional(),
|
|
930
|
-
|
|
934
|
+
/** Legacy top-level assignment link; retained for v1 records. */
|
|
935
|
+
assignment_id: z.string().optional(),
|
|
936
|
+
/** Discriminated approval target. New records always persist this field. */
|
|
937
|
+
target: ActionRequiredTargetSchema.optional(),
|
|
931
938
|
run_id: z.string().optional(),
|
|
932
939
|
claim_id: z.string().optional(),
|
|
933
940
|
message_id: z.string().optional(),
|
|
@@ -949,6 +956,14 @@ export const ActionRequiredSchema = z.object({
|
|
|
949
956
|
resolved_at: z.string().optional(),
|
|
950
957
|
response: ActionRequiredResponseSchema.optional(),
|
|
951
958
|
tags: TagsWithDefaultSchema,
|
|
959
|
+
}).superRefine((action, ctx) => {
|
|
960
|
+
const target = action.target ?? (action.assignment_id ? { kind: 'assignment', assignment_id: action.assignment_id } : undefined);
|
|
961
|
+
if (!target) {
|
|
962
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['target'], message: 'ActionRequired requires an assignment or continuation target' });
|
|
963
|
+
}
|
|
964
|
+
if (target?.kind === 'assignment' && action.assignment_id && target.assignment_id !== action.assignment_id) {
|
|
965
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['target'], message: 'assignment target must match assignment_id' });
|
|
966
|
+
}
|
|
952
967
|
});
|
|
953
968
|
// --- Runtime notes schemas ---
|
|
954
969
|
export const RuntimeNoteTypeSchema = z.enum(['observation', 'session_start', 'session_end']);
|
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.2 on 2026-08-25T18:22:09.950Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.28.
|
|
5
|
-
"generated_at": "2026-08-
|
|
4
|
+
"version": "1.28.2",
|
|
5
|
+
"generated_at": "2026-08-25T18:22:09.950Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 70,
|
|
8
8
|
"published_count": 68,
|
|
@@ -478,7 +478,7 @@ export const FACTS = {
|
|
|
478
478
|
},
|
|
479
479
|
"bench": {
|
|
480
480
|
"schema": "brainclaw.bench.v1",
|
|
481
|
-
"generated_at": "2026-08-
|
|
481
|
+
"generated_at": "2026-08-25T18:22:07.761Z",
|
|
482
482
|
"node_version": "v24.19.0",
|
|
483
483
|
"platform": "linux-x64",
|
|
484
484
|
"repeats": 3,
|
|
@@ -487,7 +487,7 @@ export const FACTS = {
|
|
|
487
487
|
"name": "cold_onboard",
|
|
488
488
|
"volume": "empty",
|
|
489
489
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
490
|
-
"duration_ms_median":
|
|
490
|
+
"duration_ms_median": 82,
|
|
491
491
|
"payload_chars_median": 1640,
|
|
492
492
|
"payload_tokens_est_median": 410
|
|
493
493
|
},
|
|
@@ -495,7 +495,7 @@ export const FACTS = {
|
|
|
495
495
|
"name": "warm_work",
|
|
496
496
|
"volume": "medium",
|
|
497
497
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
498
|
-
"duration_ms_median":
|
|
498
|
+
"duration_ms_median": 132,
|
|
499
499
|
"payload_chars_median": 2626,
|
|
500
500
|
"payload_tokens_est_median": 657
|
|
501
501
|
},
|
|
@@ -504,8 +504,8 @@ export const FACTS = {
|
|
|
504
504
|
"volume": "medium",
|
|
505
505
|
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
506
506
|
"duration_ms_median": 13,
|
|
507
|
-
"payload_chars_median":
|
|
508
|
-
"payload_tokens_est_median":
|
|
507
|
+
"payload_chars_median": 1629,
|
|
508
|
+
"payload_tokens_est_median": 407
|
|
509
509
|
}
|
|
510
510
|
]
|
|
511
511
|
}
|
package/dist/facts.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.28.
|
|
3
|
-
"generated_at": "2026-08-
|
|
2
|
+
"version": "1.28.2",
|
|
3
|
+
"generated_at": "2026-08-25T18:22:09.950Z",
|
|
4
4
|
"tools": {
|
|
5
5
|
"count": 70,
|
|
6
6
|
"published_count": 68,
|
|
@@ -476,7 +476,7 @@
|
|
|
476
476
|
},
|
|
477
477
|
"bench": {
|
|
478
478
|
"schema": "brainclaw.bench.v1",
|
|
479
|
-
"generated_at": "2026-08-
|
|
479
|
+
"generated_at": "2026-08-25T18:22:07.761Z",
|
|
480
480
|
"node_version": "v24.19.0",
|
|
481
481
|
"platform": "linux-x64",
|
|
482
482
|
"repeats": 3,
|
|
@@ -485,7 +485,7 @@
|
|
|
485
485
|
"name": "cold_onboard",
|
|
486
486
|
"volume": "empty",
|
|
487
487
|
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
488
|
-
"duration_ms_median":
|
|
488
|
+
"duration_ms_median": 82,
|
|
489
489
|
"payload_chars_median": 1640,
|
|
490
490
|
"payload_tokens_est_median": 410
|
|
491
491
|
},
|
|
@@ -493,7 +493,7 @@
|
|
|
493
493
|
"name": "warm_work",
|
|
494
494
|
"volume": "medium",
|
|
495
495
|
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
496
|
-
"duration_ms_median":
|
|
496
|
+
"duration_ms_median": 132,
|
|
497
497
|
"payload_chars_median": 2626,
|
|
498
498
|
"payload_tokens_est_median": 657
|
|
499
499
|
},
|
|
@@ -502,8 +502,8 @@
|
|
|
502
502
|
"volume": "medium",
|
|
503
503
|
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
504
504
|
"duration_ms_median": 13,
|
|
505
|
-
"payload_chars_median":
|
|
506
|
-
"payload_tokens_est_median":
|
|
505
|
+
"payload_chars_median": 1629,
|
|
506
|
+
"payload_tokens_est_median": 407
|
|
507
507
|
}
|
|
508
508
|
]
|
|
509
509
|
}
|
package/docs/cli.md
CHANGED
|
@@ -1016,9 +1016,11 @@ research, and debug; they are not a review-only command group.
|
|
|
1016
1016
|
| `takeover <loop_id>` | slot, turn, expected epoch, cause, liveness evidence, external-effect policy, next workspace and coordinator identity | Fence one physical generation and arm a successor without changing the logical Assignment. |
|
|
1017
1017
|
| `advance <loop_id>` | — | Advance through the protocol; optional `--to-phase`, `--force`, `--reason`. |
|
|
1018
1018
|
| `add-artifact <loop_id>` | `--phase --type --body` | Attach a typed artifact; optional producer and ref. |
|
|
1019
|
+
| `continue <loop_id>` | — | Evaluate an attested Ideation→Implementation or Implementation→Review action and persist/apply `AUTO`, `REQUIRE_APPROVAL`, or `DENY`; options: `--action-index`, `--autonomy-mode`, `--risk`. Review continuation selects an independent registered reviewer and fails closed if none is available. |
|
|
1019
1020
|
|
|
1020
1021
|
```bash
|
|
1021
1022
|
brainclaw loop advance lop_abc --json
|
|
1023
|
+
brainclaw loop continue lop_abc --autonomy-mode autonomous --risk normal --json
|
|
1022
1024
|
brainclaw loop takeover lop_abc \
|
|
1023
1025
|
--slot lsl_abc --turn-id tat_abc --expected-epoch 0 \
|
|
1024
1026
|
--cause "worker is no longer live" \
|
|
@@ -1028,7 +1030,7 @@ brainclaw loop takeover lop_abc \
|
|
|
1028
1030
|
```
|
|
1029
1031
|
|
|
1030
1032
|
The full public lifecycle (`open`, `get`, `list`, `pause`, `resume`, `close`,
|
|
1031
|
-
`bind`, `verify`, `request_input`, `provide_input`, and the verbs above) is the
|
|
1033
|
+
`bind`, `verify`, `continue`, `request_input`, `provide_input`, and the verbs above) is the
|
|
1032
1034
|
MCP `bclaw_loop(intent)` facade. Direct MCP `open` requires
|
|
1033
1035
|
`allow_orphan=true`; review and ideation normally start through
|
|
1034
1036
|
`bclaw_coordinate` so opening and dispatch stay one operation. See the
|
|
@@ -2032,7 +2034,7 @@ The default catalog is intentionally small and centred on the canonical grammar.
|
|
|
2032
2034
|
|---|---|
|
|
2033
2035
|
| `bclaw_coordinate(intent)` | Assign, consult, review, reroute, or summarize across agents. Pass `open_loop: true` on `intent="review"` to also dispatch the reviewer turn. |
|
|
2034
2036
|
| `bclaw_dispatch(intent)` | Parallelize execute across a sequence's lanes (analysis / execute / review). |
|
|
2035
|
-
| `bclaw_loop(intent)` | Open, inspect, or drive a multi-turn loop.
|
|
2037
|
+
| `bclaw_loop(intent)` | Open, inspect, or drive a multi-turn loop. `continue` persists and applies policy-governed cross-loop progression through the public mutation path. Implementation loops add engine-only `bind` and `verify`; any kind may use `request_input` / `provide_input`. Trusted `turn(dispatch=true)` remains the only worker launch path. A direct `open` must include `allow_orphan: true`. |
|
|
2036
2038
|
|
|
2037
2039
|
**Sequences**:
|
|
2038
2040
|
|
package/docs/code-map.md
CHANGED
|
@@ -47,6 +47,8 @@ brainclaw code-map status
|
|
|
47
47
|
```
|
|
48
48
|
Code Map status
|
|
49
49
|
Store: present
|
|
50
|
+
Root: /workspace/apps/api
|
|
51
|
+
Path: /workspace/apps/api/.brainclaw/code
|
|
50
52
|
Freshness: fresh
|
|
51
53
|
Files: 142
|
|
52
54
|
Nodes: 1873
|
|
@@ -212,6 +214,14 @@ Code Map to **that child** — the same per-project scoping that powers `bclaw_w
|
|
|
212
214
|
juggling. A submodule that is itself an application (under e.g. `apps/`) is indexed
|
|
213
215
|
like any other directory.
|
|
214
216
|
|
|
217
|
+
Both CLI and MCP status responses disclose the exact resolved project root and
|
|
218
|
+
Code Map store path. The MCP response additionally includes `active_source`, the
|
|
219
|
+
resolved project identity, the running server version, and the package version
|
|
220
|
+
visible on disk. In a monorepo, compare these fields before concluding that an
|
|
221
|
+
index is missing: a root store and a child store are intentionally distinct. If
|
|
222
|
+
the versions differ, restart the MCP server; if the roots differ, select the
|
|
223
|
+
intended project/session (or pass `cascade=true` at the workspace root).
|
|
224
|
+
|
|
215
225
|
### Cascading a multi-project workspace (`--cascade`)
|
|
216
226
|
|
|
217
227
|
In a `project_mode: multi-project` workspace, one refresh at the root can index
|
|
@@ -363,6 +363,7 @@ Additional shared and engine-owned actions complete the lifecycle:
|
|
|
363
363
|
- **request_input** / **provide_input** — bounded, evidence-backed operator clarification usable by any protocol.
|
|
364
364
|
- **bind** — implementation-loop engine action that validates the linked sequence and advances to `execute`; it never launches a worker.
|
|
365
365
|
- **verify** — implementation/debug engine action that runs the opener-configured command outside the loop lock, then records a verification-attested report.
|
|
366
|
+
- **continue** — orchestration action above the Loop engine. It evaluates an attested `next_action`, persists `AUTO | REQUIRE_APPROVAL | DENY`, and applies supported actions through the same public `open`/`bind` handlers.
|
|
366
367
|
|
|
367
368
|
Artifact authority is sealed at these verb boundaries. `produced_by` is
|
|
368
369
|
derived from the authenticated slot/engine/coordinator context. A narrative
|
|
@@ -396,6 +397,7 @@ type BclawLoopInput = BclawLoopCallerEnvelope & (
|
|
|
396
397
|
| { intent: 'close'; loop_id: LoopId; status: 'completed' | 'cancelled' | 'blocked'; reason?: string; expected_version?: number }
|
|
397
398
|
| { intent: 'verify'; loop_id: LoopId }
|
|
398
399
|
| { intent: 'bind'; loop_id: LoopId; dry_run?: boolean; lanes?: string[]; auto_execute?: boolean; model?: string; max_assignments?: number }
|
|
400
|
+
| { intent: 'continue'; loop_id: LoopId; action_index?: number; autonomy_mode?: 'autonomous' | 'require_approval' | 'deny'; risk?: 'normal' | 'protected' }
|
|
399
401
|
| { 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
402
|
| { 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 }
|
|
401
403
|
| { intent: 'get'; loop_id: LoopId; include_events?: boolean }
|
|
@@ -470,6 +472,29 @@ The shared lifecycle verbs are `turn`, `complete_turn`, `advance`,
|
|
|
470
472
|
use engine-only `bind` to validate their linked sequence and enter `execute`,
|
|
471
473
|
then `turn(dispatch:true)` for worker slots; `verify` runs their declared command.
|
|
472
474
|
|
|
475
|
+
### Persisted continuation authority
|
|
476
|
+
|
|
477
|
+
An accepted ideation synthesis and an attested implementation handoff no
|
|
478
|
+
longer expose ungoverned downstream mutations. Their `next_actions` point to
|
|
479
|
+
`bclaw_loop(intent="continue")`. The
|
|
480
|
+
continuation record binds the source loop, iteration, sealed artifact digest,
|
|
481
|
+
canonical action hash and policy version into a deterministic key. It is
|
|
482
|
+
written before the downstream mutation.
|
|
483
|
+
|
|
484
|
+
For Ideation→Implementation, `AUTO` invokes the ordinary public `open` handler
|
|
485
|
+
with that key in `linked.continuation_key`, then invokes engine-only `bind`.
|
|
486
|
+
For Implementation→Review, it deterministically selects a project-registered,
|
|
487
|
+
spawnable review-capable identity that did not occupy an implementation slot,
|
|
488
|
+
then invokes the ordinary public `bclaw_coordinate(intent="review",
|
|
489
|
+
open_loop=true)` path. If no independent reviewer exists, it fails closed.
|
|
490
|
+
A retry first scans existing loops for the key, so a crash after either public
|
|
491
|
+
mutation but before the response reuses the same loop. A live concurrent owner
|
|
492
|
+
is observed rather than stolen.
|
|
493
|
+
`REQUIRE_APPROVAL` creates an `ActionRequired` whose discriminated target is
|
|
494
|
+
the continuation; approval resumes the same record, while rejection or expiry
|
|
495
|
+
persists `DENY`. Unsupported actions, placeholders, missing evidence and
|
|
496
|
+
ambiguous downstreams fail closed.
|
|
497
|
+
|
|
473
498
|
### Clarification is a cross-cutting primitive
|
|
474
499
|
|
|
475
500
|
Clarification is deliberately not a sixth protocol. Any workflow can call
|
|
@@ -493,6 +518,11 @@ The Loop engine is a **control plane**; existing primitives remain the **data pl
|
|
|
493
518
|
|
|
494
519
|
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.
|
|
495
520
|
|
|
521
|
+
Inline `LoopArtifact.body` values are capped at **4096 UTF-8 bytes**, not 4096
|
|
522
|
+
characters. Larger task and result text remains available through its source
|
|
523
|
+
object or a `ref`; any inline projection is byte-truncated with an explicit
|
|
524
|
+
`…[truncated]` marker. This contract is identical for review and ideation.
|
|
525
|
+
|
|
496
526
|
## Per-protocol guides
|
|
497
527
|
|
|
498
528
|
Each of the five kinds has its own operator-facing guide with the same
|
|
@@ -408,7 +408,12 @@ will still succeed. A follow-up PR will strip the dead handler code.
|
|
|
408
408
|
changelog records the published MCP surface fingerprint. When a tool
|
|
409
409
|
name, tier, category, or input schema changes, the test fails until
|
|
410
410
|
this section is updated.
|
|
411
|
-
- MCP public surface fingerprint: `sha256:
|
|
411
|
+
- MCP public surface fingerprint: `sha256:be86e5571fcd0226`
|
|
412
|
+
(updated 2026-08-24 for persisted continuation authority: additive
|
|
413
|
+
`bclaw_loop(intent="continue")` inputs `action_index`, `autonomy_mode`, and
|
|
414
|
+
`risk`; the intent evaluates an attested Ideation→Implementation action,
|
|
415
|
+
persists AUTO/REQUIRE_APPROVAL/DENY, and reuses the public open/bind path.)
|
|
416
|
+
Previous: `sha256:681c47cba85b79c3`
|
|
412
417
|
(`LoopSlotInput` gains optional `lane`, `scope_hint`, `plan_ids`, and
|
|
413
418
|
`step_ids` fields so implementation-loop lane scope and provenance survive
|
|
414
419
|
through the public facade. Existing callers remain valid.)
|