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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ClaudeHarnessAdapter } from './claude.js';
|
|
2
|
+
import { CodexHarnessAdapter } from './codex.js';
|
|
3
|
+
import { PromptOnlyHarnessAdapter } from './prompt-only.js';
|
|
4
|
+
const promptOnly = new PromptOnlyHarnessAdapter();
|
|
5
|
+
const nativeAdapters = [new CodexHarnessAdapter(), new ClaudeHarnessAdapter()];
|
|
6
|
+
export function nativeHarnessEnabled(env = process.env) {
|
|
7
|
+
return env.BRAINCLAW_NATIVE_HARNESS === '1';
|
|
8
|
+
}
|
|
9
|
+
export function selectHarnessAdapter(agent, native = nativeHarnessEnabled()) {
|
|
10
|
+
if (!native)
|
|
11
|
+
return promptOnly;
|
|
12
|
+
return nativeAdapters.find((adapter) => adapter.matches(agent)) ?? promptOnly;
|
|
13
|
+
}
|
|
14
|
+
export function resolveHarnessBinding(agent, requestedModel, native, probeOptions) {
|
|
15
|
+
return selectHarnessAdapter(agent, native).resolve(agent, requestedModel, probeOptions);
|
|
16
|
+
}
|
|
17
|
+
export function prepareHarnessInvocation(input) {
|
|
18
|
+
const binding = input.binding ?? resolveHarnessBinding(input.capability_snapshot?.agent ?? '', input.capability_snapshot?.requested.model, input.native);
|
|
19
|
+
const adapter = selectHarnessAdapter(binding.agent, input.native ?? binding.adapter_id !== 'prompt-only');
|
|
20
|
+
if (adapter.id !== binding.adapter_id || adapter.version !== binding.adapter_version) {
|
|
21
|
+
throw new Error(`harness_binding_mismatch: frozen ${binding.adapter_id}@${binding.adapter_version}, selected ${adapter.id}@${adapter.version}`);
|
|
22
|
+
}
|
|
23
|
+
return adapter.prepare({ ...input, binding });
|
|
24
|
+
}
|
|
25
|
+
export function buildHarnessInvocation(agent, prompt, options = {}) {
|
|
26
|
+
let binding;
|
|
27
|
+
try {
|
|
28
|
+
binding = options.binding ?? resolveHarnessBinding(agent, options.model, options.native);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof Error && / is unavailable for /.test(error.message))
|
|
32
|
+
return undefined;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
return prepareHarnessInvocation({
|
|
36
|
+
binding,
|
|
37
|
+
prompt,
|
|
38
|
+
mode: options.mode ?? 'worker',
|
|
39
|
+
platform: options.platform,
|
|
40
|
+
native: options.native,
|
|
41
|
+
contract: options.contract,
|
|
42
|
+
capability_snapshot: options.capability_snapshot,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export function listHarnessAdapters() {
|
|
46
|
+
return [promptOnly, ...nativeAdapters];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { listHarnessAdapters } from './registry.js';
|
|
2
|
+
export function parseHarnessOutcome(adapterId, observation, adapterVersion) {
|
|
3
|
+
const adapter = listHarnessAdapters().find((candidate) => candidate.id === adapterId);
|
|
4
|
+
if (!adapter)
|
|
5
|
+
throw new Error(`unknown harness adapter: ${adapterId}`);
|
|
6
|
+
if (adapterVersion && adapter.version !== adapterVersion) {
|
|
7
|
+
throw new Error(`harness adapter version mismatch: installed ${adapter.id}@${adapter.version}, frozen ${adapterId}@${adapterVersion}`);
|
|
8
|
+
}
|
|
9
|
+
return adapter.parseOutcome(observation);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Convert an untrusted harness claim to the existing LaneResult ingress shape.
|
|
13
|
+
* This is deliberately normalization only: reconcileTurn still validates
|
|
14
|
+
* attempt identity and reducers/evidence/gates remain the authority boundary.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeHarnessClaimToLaneResult(claim, identity) {
|
|
17
|
+
const status = claim.status === 'completed'
|
|
18
|
+
? 'completed'
|
|
19
|
+
: claim.status === 'blocked' ? 'blocked' : 'failed';
|
|
20
|
+
return {
|
|
21
|
+
...identity,
|
|
22
|
+
status,
|
|
23
|
+
summary: claim.summary,
|
|
24
|
+
body: claim.body,
|
|
25
|
+
artifact_type: claim.artifact_type,
|
|
26
|
+
review_verdict: claim.review_verdict,
|
|
27
|
+
review_summary: claim.review_verdict ? claim.summary : undefined,
|
|
28
|
+
notes: claim.diagnostics.length > 0
|
|
29
|
+
? claim.diagnostics.map((item) => `${item.kind}:${item.code}: ${item.message}`).join('\n')
|
|
30
|
+
: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLoop } from './loops/store.js';
|
|
2
|
-
import { complete_turn, advance, evaluatePhaseAdvanceGate } from './loops/verbs.js';
|
|
2
|
+
import { complete_turn, completeTurnWithEvidence, advance, evaluatePhaseAdvanceGate } from './loops/verbs.js';
|
|
3
3
|
import { withLoopLock } from './loops/lock.js';
|
|
4
4
|
import { LOOP_ARTIFACT_BODY_MAX_BYTES } from './loops/types.js';
|
|
5
5
|
/** ideate-loop:lop_xxx[:slot] → the loop id (dispatch sets `ideate-loop:${loopId}:${slotId}`). */
|
|
@@ -137,7 +137,30 @@ export function closeIdeationLoopFromLaneResult(assignment, lane, actor, cwd) {
|
|
|
137
137
|
// artifact stops satisfying the current gate by construction — no
|
|
138
138
|
// separate refusal path, and the content is preserved rather than lost.
|
|
139
139
|
const dispatchPhase = slot.phase ?? loop.current_phase;
|
|
140
|
-
|
|
140
|
+
completeTurnWithEvidence({
|
|
141
|
+
id: loopId,
|
|
142
|
+
slot_id: slot.slot_id,
|
|
143
|
+
actor,
|
|
144
|
+
outcome: 'done',
|
|
145
|
+
artifact: { phase: dispatchPhase, type: 'critique', body: capCritique(critique) },
|
|
146
|
+
// This adapter is the trusted bridge from a harvested, assignment-bound
|
|
147
|
+
// LANE-RESULT to the slot that produced it. Without an explicit slot
|
|
148
|
+
// context, a coordinator-driven harvest is sealed as coordinator
|
|
149
|
+
// narration and the evidence gate correctly rejects it. Preserve the
|
|
150
|
+
// actual producer identity while keeping legacy lanes (which have no
|
|
151
|
+
// TurnReservation nonce) on the narrow complete_turn authority.
|
|
152
|
+
evidence_context: {
|
|
153
|
+
channel: 'complete_turn',
|
|
154
|
+
producer_kind: 'slot',
|
|
155
|
+
producer_id: slot.slot_id,
|
|
156
|
+
agent_id: slot.agent_id,
|
|
157
|
+
slot_id: slot.slot_id,
|
|
158
|
+
slot_role: slot.role,
|
|
159
|
+
assignment_id: slot.assignment_id ?? assignment.id,
|
|
160
|
+
claim_id: slot.claim_id,
|
|
161
|
+
turn_id: slot.current_turn_id,
|
|
162
|
+
},
|
|
163
|
+
}, cwd);
|
|
141
164
|
const advanced = tryAdvance(true);
|
|
142
165
|
return reportedArtifactType && reportedArtifactType !== expectedArtifactType
|
|
143
166
|
? { ...advanced, reason: `reconciled reported artifact type "${reportedArtifactType}" to expected "${expectedArtifactType}"; ${advanced.reason}` }
|
|
@@ -257,8 +257,9 @@ function renderUserWorkflow() {
|
|
|
257
257
|
'',
|
|
258
258
|
'Entities: `plan` (intended outcome) · `step` (unit inside a plan) · `sequence` (optional parallel lanes) · `claim` (advisory scope reservation) · `handoff` (stage snapshot) · `candidate` (proposed memory awaiting review) · `decision`/`constraint`/`trap`/`runtime_note` (context captured along the way).',
|
|
259
259
|
'',
|
|
260
|
-
'
|
|
261
|
-
'
|
|
260
|
+
'Loop Engine: five shipped protocols share one lifecycle — `review` validates a change, `ideation` pressure-tests a proposal, `implementation` drives a bound plan to green, `research` converges an open question, and `debug` drives a reproducible failure back to green.',
|
|
261
|
+
'Entry points: start review with `bclaw_coordinate(intent=review, open_loop=true, review_mode=symmetric|asymmetric, targetAgents=[reviewer])`; start ideation with `bclaw_coordinate(intent=ideate, targetAgents=[critic])`. For implementation, research, or debug, direct `bclaw_loop(intent=open, kind=<kind>, allow_orphan=true)` is supported only when the caller will explicitly drive or dispatch the loop.',
|
|
262
|
+
'Drive turns with `bclaw_loop(intent=turn|complete_turn|advance|close)` and follow each response\'s `next_actions`; on a worker phase, trusted `bclaw_loop(intent=turn, dispatch=true)` runs the common AttemptAuthority dispatch path. Parallelize a sequence\'s independent lanes with `bclaw_dispatch(intent=execute)`. Protocol details: `docs/concepts/loop-engine.md` and `docs/loops/`.',
|
|
262
263
|
].join('\n');
|
|
263
264
|
}
|
|
264
265
|
/**
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic production driver for one worker-backed Loop Engine turn.
|
|
3
|
+
*
|
|
4
|
+
* Review and ideation retain their ergonomic coordinate drivers, but every
|
|
5
|
+
* loop kind can enter the same AttemptAuthority lifecycle through this seam.
|
|
6
|
+
* The adapter creates transport projections and starts the worker; it never
|
|
7
|
+
* advances a phase or evaluates a gate.
|
|
8
|
+
*/
|
|
9
|
+
import { resolveModel } from './agent-capability.js';
|
|
10
|
+
import { listAgentIdentities } from './agent-registry.js';
|
|
11
|
+
import { transitionAgentRun } from './agentruns.js';
|
|
12
|
+
import { loadAssignment, patchAssignmentMessageId, transitionAssignment } from './assignments.js';
|
|
13
|
+
import { attachAssignmentMessageToClaim, createCoordinatorClaim, ensureClaimAssignmentBinding, } from './claims.js';
|
|
14
|
+
import { generateDispatchBrief } from './dispatcher.js';
|
|
15
|
+
import { search } from './search.js';
|
|
16
|
+
import { attemptExecution } from './execution.js';
|
|
17
|
+
import { resolveExecutionCandidate } from './execution-contract.js';
|
|
18
|
+
import { buildHarnessInvocation, resolveHarnessBinding } from './harness-adapters/index.js';
|
|
19
|
+
import { phasePolicy } from './loops/kind-policies.js';
|
|
20
|
+
import { buildIdeationBrief } from './loops/brief-assembly.js';
|
|
21
|
+
import { getLoop } from './loops/store.js';
|
|
22
|
+
import { prepareTurnExecution } from './loops/turn-execution.js';
|
|
23
|
+
import { sendMessage } from './messaging.js';
|
|
24
|
+
export async function dispatchLoopTurn(input) {
|
|
25
|
+
const loop = getLoop(input.loop_id, input.cwd);
|
|
26
|
+
if (!loop)
|
|
27
|
+
return { loop_id: input.loop_id, slot_id: input.slot_id, error: `unknown loop_id ${input.loop_id}` };
|
|
28
|
+
const slot = loop.slots.find((candidate) => candidate.slot_id === input.slot_id);
|
|
29
|
+
const result = {
|
|
30
|
+
loop_id: loop.id,
|
|
31
|
+
slot_id: input.slot_id,
|
|
32
|
+
kind: loop.kind,
|
|
33
|
+
phase: loop.current_phase,
|
|
34
|
+
agent: slot?.agent,
|
|
35
|
+
};
|
|
36
|
+
if (loop.status !== 'open')
|
|
37
|
+
return { ...result, error: `loop ${loop.id} is ${loop.status}, not open` };
|
|
38
|
+
if (!slot)
|
|
39
|
+
return { ...result, error: `slot ${input.slot_id} not found` };
|
|
40
|
+
const policy = phasePolicy(loop.kind, loop.current_phase);
|
|
41
|
+
if (!policy || policy.execution !== 'worker') {
|
|
42
|
+
return { ...result, error: `${loop.kind}.${loop.current_phase} is ${policy?.execution ?? 'unknown'}, not a worker phase` };
|
|
43
|
+
}
|
|
44
|
+
let agent = slot.agent;
|
|
45
|
+
let agentId = slot.agent_id;
|
|
46
|
+
if (!agent) {
|
|
47
|
+
const registered = listAgentIdentities(input.cwd).filter((identity) => identity.kind !== 'human');
|
|
48
|
+
const allowed = new Set(input.candidate_agents ?? registered.map((identity) => identity.agent_name));
|
|
49
|
+
const candidates = registered
|
|
50
|
+
.filter((identity) => allowed.has(identity.agent_name))
|
|
51
|
+
.map((identity) => ({ agent: identity.agent_name, agent_id: identity.agent_id }));
|
|
52
|
+
const role = loop.kind === 'review' || loop.kind === 'ideation'
|
|
53
|
+
? 'review'
|
|
54
|
+
: loop.kind === 'research' ? 'consult' : 'execute';
|
|
55
|
+
const selection = resolveExecutionCandidate(candidates, {
|
|
56
|
+
roles: [role], required_surfaces: ['cli_spawn'], execution_surfaces: [], required_tools: [],
|
|
57
|
+
});
|
|
58
|
+
if (selection.kind !== 'selected') {
|
|
59
|
+
return { ...result, error: `slot ${slot.slot_id} has no compatible worker candidate` };
|
|
60
|
+
}
|
|
61
|
+
agent = selection.selected.agent;
|
|
62
|
+
agentId = selection.selected.agent_id;
|
|
63
|
+
result.agent = agent;
|
|
64
|
+
}
|
|
65
|
+
const scope = slot.scope_hint ?? `loop:${loop.kind}:${loop.id}:slot:${slot.slot_id}`;
|
|
66
|
+
const sectionByCategory = {
|
|
67
|
+
traps: 'traps', decisions: 'decisions', constraints: 'constraints', handoffs: 'handoffs',
|
|
68
|
+
plans: 'plans', candidates: 'candidates',
|
|
69
|
+
};
|
|
70
|
+
const provider = {
|
|
71
|
+
fetch(category, query, topK) {
|
|
72
|
+
const section = sectionByCategory[category];
|
|
73
|
+
if (!section)
|
|
74
|
+
return [];
|
|
75
|
+
return search({ query, section, maxResults: topK, cwd: input.cwd, includePending: section === 'candidates' })
|
|
76
|
+
.map((item) => ({
|
|
77
|
+
id: item.id, category, text: item.text, score: item.score, relatedPaths: item.related_paths,
|
|
78
|
+
}));
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const phaseBrief = buildIdeationBrief({
|
|
82
|
+
thread: loop,
|
|
83
|
+
slotRole: slot.role,
|
|
84
|
+
memoryProvider: provider,
|
|
85
|
+
seedText: input.task,
|
|
86
|
+
scopeHints: slot.scope_hint ? slot.scope_hint.split(',').map((value) => value.trim()) : [],
|
|
87
|
+
});
|
|
88
|
+
const laneContext = slot.lane
|
|
89
|
+
? `Lane: ${slot.lane}\nPlans: ${(slot.plan_ids ?? []).join(', ') || '(none)'}\nSteps: ${(slot.step_ids ?? []).join(', ') || '(whole plan)'}`
|
|
90
|
+
: '';
|
|
91
|
+
const scopedTask = [phaseBrief.text, laneContext].filter(Boolean).join('\n\n');
|
|
92
|
+
const description = `${loop.kind} loop turn for ${loop.id} slot ${slot.slot_id} phase ${loop.current_phase}. ${input.task}`;
|
|
93
|
+
try {
|
|
94
|
+
const claim = createCoordinatorClaim({
|
|
95
|
+
agent,
|
|
96
|
+
scope,
|
|
97
|
+
description,
|
|
98
|
+
dispatcherAgent: input.dispatcher_agent,
|
|
99
|
+
sessionId: input.session_id,
|
|
100
|
+
cwd: input.cwd,
|
|
101
|
+
});
|
|
102
|
+
result.claim_id = claim.claimId;
|
|
103
|
+
result.worktree_path = claim.worktreePath;
|
|
104
|
+
if (claim.scopeConflict) {
|
|
105
|
+
result.error = `scope ${scope} is already claimed by ${claim.conflictAgent ?? 'another agent'}`;
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
const model = resolveModel(agent, { override: input.model });
|
|
109
|
+
const binding = resolveHarnessBinding(agent, model);
|
|
110
|
+
const prepared = prepareTurnExecution({
|
|
111
|
+
kind: loop.kind,
|
|
112
|
+
loop_id: loop.id,
|
|
113
|
+
slot_id: slot.slot_id,
|
|
114
|
+
phase: loop.current_phase,
|
|
115
|
+
agent,
|
|
116
|
+
agent_id: agentId,
|
|
117
|
+
claim_id: claim.claimId,
|
|
118
|
+
dispatcher_agent: input.dispatcher_agent,
|
|
119
|
+
dispatcher_agent_id: input.dispatcher_agent_id,
|
|
120
|
+
dispatcher_session_id: input.session_id,
|
|
121
|
+
scope,
|
|
122
|
+
description,
|
|
123
|
+
task: scopedTask,
|
|
124
|
+
cwd: input.cwd,
|
|
125
|
+
worktree_path: claim.worktreePath,
|
|
126
|
+
model,
|
|
127
|
+
harness_binding: binding,
|
|
128
|
+
assignment_tags: ['coordinate', loop.kind, 'loop', 'turn-owned'],
|
|
129
|
+
run_tags: ['turn-owned', loop.kind, 'loop'],
|
|
130
|
+
});
|
|
131
|
+
if (prepared.kind !== 'won') {
|
|
132
|
+
result.execution_status = 'inbox_only';
|
|
133
|
+
result.error = prepared.reason;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
result.assignment_id = prepared.assignment_id;
|
|
137
|
+
result.run_id = prepared.run_id;
|
|
138
|
+
result.turn_id = prepared.turn_id;
|
|
139
|
+
result.worktree_path = prepared.workspace_path;
|
|
140
|
+
const turnEcho = {
|
|
141
|
+
turn_id: prepared.turn_id,
|
|
142
|
+
run_id: prepared.run_id,
|
|
143
|
+
nonce: prepared.nonce,
|
|
144
|
+
...(prepared.execution_contract_ref ? {
|
|
145
|
+
contract_hash: prepared.execution_contract_ref.hash,
|
|
146
|
+
capability_snapshot_hash: prepared.execution_contract_ref.snapshot_hash,
|
|
147
|
+
} : {}),
|
|
148
|
+
...(prepared.attempt_epoch !== undefined ? { attempt_epoch: prepared.attempt_epoch } : {}),
|
|
149
|
+
...(prepared.workspace_digest ? { workspace_digest: prepared.workspace_digest } : {}),
|
|
150
|
+
};
|
|
151
|
+
const brief = generateDispatchBrief({
|
|
152
|
+
task: scopedTask,
|
|
153
|
+
agent,
|
|
154
|
+
claimId: claim.claimId,
|
|
155
|
+
scope,
|
|
156
|
+
worktreePath: prepared.workspace_path,
|
|
157
|
+
assignmentId: prepared.assignment_id,
|
|
158
|
+
executionContractRef: prepared.execution_contract_ref,
|
|
159
|
+
attemptFence: prepared.attempt_epoch !== undefined && prepared.workspace_digest ? {
|
|
160
|
+
turn_id: prepared.turn_id,
|
|
161
|
+
run_id: prepared.run_id,
|
|
162
|
+
nonce: prepared.nonce,
|
|
163
|
+
attempt_epoch: prepared.attempt_epoch,
|
|
164
|
+
workspace_digest: prepared.workspace_digest,
|
|
165
|
+
} : undefined,
|
|
166
|
+
cwd: input.cwd,
|
|
167
|
+
});
|
|
168
|
+
const message = sendMessage({
|
|
169
|
+
from: input.dispatcher_agent,
|
|
170
|
+
to: agent,
|
|
171
|
+
type: 'assign',
|
|
172
|
+
text: brief,
|
|
173
|
+
ref: loop.id,
|
|
174
|
+
scope,
|
|
175
|
+
requires_ack: true,
|
|
176
|
+
claim_id: claim.claimId,
|
|
177
|
+
assignment_id: prepared.assignment_id,
|
|
178
|
+
tags: ['coordinate', loop.kind, 'loop', 'turn-owned'],
|
|
179
|
+
author_id: input.dispatcher_agent_id,
|
|
180
|
+
session_id: input.session_id,
|
|
181
|
+
payload: {
|
|
182
|
+
intent: 'loop_turn', loop_id: loop.id, slot_id: slot.slot_id,
|
|
183
|
+
phase: loop.current_phase, scope, claim_id: claim.claimId,
|
|
184
|
+
assignment_id: prepared.assignment_id, worktree_path: prepared.workspace_path,
|
|
185
|
+
},
|
|
186
|
+
}, input.cwd);
|
|
187
|
+
result.message_id = message.id;
|
|
188
|
+
attachAssignmentMessageToClaim(claim.claimId, message.id, input.cwd);
|
|
189
|
+
ensureClaimAssignmentBinding(claim.claimId, prepared.assignment_id, input.cwd, {
|
|
190
|
+
worktreePath: prepared.workspace_path,
|
|
191
|
+
});
|
|
192
|
+
const assignment = loadAssignment(prepared.assignment_id, input.cwd);
|
|
193
|
+
if (assignment?.status === 'created' || assignment?.status === 'retrying') {
|
|
194
|
+
transitionAssignment(prepared.assignment_id, 'offered', { actor: input.dispatcher_agent }, input.cwd);
|
|
195
|
+
}
|
|
196
|
+
patchAssignmentMessageId(prepared.assignment_id, message.id, input.cwd);
|
|
197
|
+
const invoke = buildHarnessInvocation(agent, brief, {
|
|
198
|
+
mode: 'worker', model, binding,
|
|
199
|
+
contract: undefined,
|
|
200
|
+
capability_snapshot: prepared.capability_snapshot,
|
|
201
|
+
})?.invoke;
|
|
202
|
+
const execution = await attemptExecution(invoke, {
|
|
203
|
+
agent,
|
|
204
|
+
autoExecute: input.auto_execute ?? true,
|
|
205
|
+
worktreePath: prepared.workspace_path,
|
|
206
|
+
claimId: claim.claimId,
|
|
207
|
+
assignmentId: prepared.assignment_id,
|
|
208
|
+
dispatcherAgent: input.dispatcher_agent,
|
|
209
|
+
dispatcherAgentId: input.dispatcher_agent_id,
|
|
210
|
+
cwd: input.cwd,
|
|
211
|
+
requireWorktree: true,
|
|
212
|
+
turnEcho,
|
|
213
|
+
});
|
|
214
|
+
result.execution_status = execution.execution_status;
|
|
215
|
+
result.command = execution.command;
|
|
216
|
+
result.shell = execution.shell;
|
|
217
|
+
if (execution.error)
|
|
218
|
+
result.error = execution.error;
|
|
219
|
+
if (execution.execution_status === 'delivered_and_started') {
|
|
220
|
+
try {
|
|
221
|
+
transitionAgentRun(prepared.run_id, 'running', {
|
|
222
|
+
actor: input.dispatcher_agent,
|
|
223
|
+
status_reason: `turn-owned ${loop.kind} worker spawned`,
|
|
224
|
+
}, input.cwd);
|
|
225
|
+
}
|
|
226
|
+
catch { /* the reconciler converges transport state */ }
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
result.error = `loop turn dispatch failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=loop-turn-dispatch.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Immutable description of one artifact expected from a worker attempt. */
|
|
3
|
+
export const ExpectedArtifactSchema = z.object({
|
|
4
|
+
logical_name: z.string().min(1),
|
|
5
|
+
worker_path: z.string().min(1),
|
|
6
|
+
loop_artifact_type: z.string().min(1),
|
|
7
|
+
schema_id: z.string().optional(),
|
|
8
|
+
completion_policy: z.enum(['required', 'optional']).default('required'),
|
|
9
|
+
sha256: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=artifact-contract.js.map
|