brainclaw 1.28.1 → 1.28.3
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/commands/code-map.js +2 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/harvest.js +32 -43
- package/dist/commands/loops-handlers.js +66 -3
- package/dist/commands/mcp-catalog.js +2 -2
- package/dist/commands/mcp-write-coordination.js +426 -141
- package/dist/commands/mcp-write-entities.js +5 -2
- package/dist/commands/mcp.js +57 -11
- package/dist/core/agentrun-reconciler.js +138 -4
- package/dist/core/claims.js +4 -1
- package/dist/core/code-map/aggregate.js +20 -7
- package/dist/core/code-map/backend.js +25 -7
- package/dist/core/code-map/cascade-jobs.js +174 -0
- package/dist/core/code-map/cascade-worker.js +15 -0
- package/dist/core/code-map/cascade.js +63 -26
- package/dist/core/code-map/query.js +6 -3
- package/dist/core/entity-operations.js +18 -4
- package/dist/core/execution-adapters.js +23 -8
- 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/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 +3 -0
- package/dist/core/loops/verbs.js +1 -1
- package/dist/core/spawn-check.js +9 -1
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/cli.md +2 -2
- package/docs/code-map.md +30 -9
- package/docs/concepts/loop-engine.md +5 -0
- package/docs/integrations/mcp.md +2 -2
- package/docs/mcp-schema-changelog.md +30 -0
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import { CapabilitySnapshotSchema } from '../execution-contract.js';
|
|
5
6
|
import { memoryDir, writeFileAtomic } from '../io.js';
|
|
6
7
|
import { nowISO } from '../ids.js';
|
|
7
8
|
/**
|
|
@@ -31,9 +32,17 @@ export const GenerationFenceSchema = z.object({
|
|
|
31
32
|
workspace_digest: z.string().min(1),
|
|
32
33
|
authority_home: AuthorityHomeSchema,
|
|
33
34
|
}).strict();
|
|
35
|
+
export const GenerationExecutorSchema = z.object({
|
|
36
|
+
agent: z.string().min(1),
|
|
37
|
+
agent_id: z.string().min(1).optional(),
|
|
38
|
+
claim_id: z.string().min(1),
|
|
39
|
+
capability_snapshot: CapabilitySnapshotSchema,
|
|
40
|
+
}).strict();
|
|
34
41
|
export const AttemptGenerationSchema = GenerationFenceSchema.extend({
|
|
35
42
|
schema_version: z.literal(ATTEMPT_AUTHORITY_VERSION),
|
|
36
43
|
generation_kind: z.literal('attempt_generation'),
|
|
44
|
+
/** Executor ownership is generation-scoped so a reroute never inherits stale provenance. */
|
|
45
|
+
executor: GenerationExecutorSchema.optional(),
|
|
37
46
|
created_at: z.string().min(1),
|
|
38
47
|
}).strict();
|
|
39
48
|
export const LaunchDecisionCellSchema = z.object({
|
|
@@ -145,8 +154,9 @@ function stableId(prefix, material) {
|
|
|
145
154
|
return `${prefix}_${digest}`;
|
|
146
155
|
}
|
|
147
156
|
/** Compatible with the existing logical Assignment derivation. */
|
|
148
|
-
export function deriveGenerationAssignmentId(turnId) {
|
|
149
|
-
|
|
157
|
+
export function deriveGenerationAssignmentId(turnId, attemptEpoch = 0) {
|
|
158
|
+
const salt = attemptEpoch === 0 ? 'assignment' : `assignment:${attemptEpoch}`;
|
|
159
|
+
return stableId('asgn', `${turnId}:${salt}`);
|
|
150
160
|
}
|
|
151
161
|
/** Epoch zero preserves the existing run id; every later epoch is fresh. */
|
|
152
162
|
export function deriveGenerationRunId(turnId, attemptEpoch) {
|
|
@@ -161,7 +171,7 @@ export function prepareInitialGeneration(input) {
|
|
|
161
171
|
schema_version: ATTEMPT_AUTHORITY_VERSION,
|
|
162
172
|
generation_kind: 'attempt_generation',
|
|
163
173
|
turn_id: input.turn_id,
|
|
164
|
-
assignment_id: deriveGenerationAssignmentId(input.turn_id),
|
|
174
|
+
assignment_id: deriveGenerationAssignmentId(input.turn_id, 0),
|
|
165
175
|
attempt_epoch: 0,
|
|
166
176
|
run_id: deriveGenerationRunId(input.turn_id, 0),
|
|
167
177
|
launch_nonce: input.launch_nonce ?? crypto.randomUUID(),
|
|
@@ -170,6 +180,7 @@ export function prepareInitialGeneration(input) {
|
|
|
170
180
|
workspace_path: input.workspace_path,
|
|
171
181
|
workspace_digest: input.workspace_digest,
|
|
172
182
|
authority_home: AuthorityHomeSchema.parse(input.authority_home),
|
|
183
|
+
executor: input.executor,
|
|
173
184
|
created_at: input.created_at ?? nowISO(),
|
|
174
185
|
});
|
|
175
186
|
}
|
|
@@ -178,6 +189,7 @@ export function prepareNextGeneration(current, input) {
|
|
|
178
189
|
const attemptEpoch = parsed.attempt_epoch + 1;
|
|
179
190
|
return AttemptGenerationSchema.parse({
|
|
180
191
|
...parsed,
|
|
192
|
+
assignment_id: deriveGenerationAssignmentId(parsed.turn_id, attemptEpoch),
|
|
181
193
|
attempt_epoch: attemptEpoch,
|
|
182
194
|
run_id: deriveGenerationRunId(parsed.turn_id, attemptEpoch),
|
|
183
195
|
launch_nonce: input.launch_nonce ?? crypto.randomUUID(),
|
|
@@ -185,6 +197,7 @@ export function prepareNextGeneration(current, input) {
|
|
|
185
197
|
workspace_id: deriveGenerationWorkspaceId(parsed.turn_id, attemptEpoch),
|
|
186
198
|
workspace_path: input.workspace_path,
|
|
187
199
|
workspace_digest: input.workspace_digest,
|
|
200
|
+
executor: input.executor ?? parsed.executor,
|
|
188
201
|
created_at: input.created_at ?? nowISO(),
|
|
189
202
|
});
|
|
190
203
|
}
|
|
@@ -231,7 +244,7 @@ function assertValidSuccessor(current, next) {
|
|
|
231
244
|
}
|
|
232
245
|
const invalid = [
|
|
233
246
|
current.turn_id !== next.turn_id && 'turn_id',
|
|
234
|
-
|
|
247
|
+
next.assignment_id === current.assignment_id && 'assignment_id',
|
|
235
248
|
next.attempt_epoch !== current.attempt_epoch + 1 && 'attempt_epoch',
|
|
236
249
|
next.run_id === current.run_id && 'run_id',
|
|
237
250
|
next.launch_nonce === current.launch_nonce && 'launch_nonce',
|
|
@@ -446,7 +446,20 @@ export function findReservationByAssignmentId(assignmentId, cwd) {
|
|
|
446
446
|
// coexists with a real LANE-RESULT (dispatch commits before spawn). Filtering here makes
|
|
447
447
|
// the turn-owned discriminator explicit — a `prepared`/`aborted` reservation must never
|
|
448
448
|
// route a lane to reconcileTurn (it has no live launch generation to accept evidence for).
|
|
449
|
-
return listReservations({ decision: 'committed' }, cwd).find((
|
|
449
|
+
return listReservations({ decision: 'committed' }, cwd).find((reservation) => {
|
|
450
|
+
if (reservation.child_ids.assignment_id === assignmentId)
|
|
451
|
+
return true;
|
|
452
|
+
try {
|
|
453
|
+
const root = cwd ?? reservation.store_root;
|
|
454
|
+
const initial = readInitialGeneration(root, reservation.turn_id);
|
|
455
|
+
return initial
|
|
456
|
+
? listAttemptGenerations(root, initial).some((generation) => generation.assignment_id === assignmentId)
|
|
457
|
+
: false;
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
450
463
|
}
|
|
451
464
|
/** Run-scoping key for runtime evidence; undefined preserves legacy assignment-scoped paths. */
|
|
452
465
|
export function currentAttemptRunIdForAssignment(assignmentId, cwd) {
|
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { ensureAgentRunProjection, loadAgentRun, transitionAgentRun } from '../agentruns.js';
|
|
4
|
-
import { loadAssignment } from '../assignments.js';
|
|
4
|
+
import { convergeAssignmentToTerminal, loadAssignment, transitionAssignment } from '../assignments.js';
|
|
5
5
|
import { createRuntimeEvent } from '../events.js';
|
|
6
6
|
import { nowISO } from '../ids.js';
|
|
7
7
|
import { prepareAttemptTakeoverV2 } from './attempt-authority.js';
|
|
8
|
-
import { generationDigest } from './attempt-generations.js';
|
|
8
|
+
import { generationDigest, resolveTurnGenerationChain } from './attempt-generations.js';
|
|
9
9
|
import { commitViaIntent } from './commit-intent.js';
|
|
10
10
|
import { evidenceDigest } from './evidence.js';
|
|
11
11
|
import { withLoopLock } from './lock.js';
|
|
12
12
|
import { getReservation } from './attempt-reservation.js';
|
|
13
13
|
import { getLoop, listLoopEvents } from './store.js';
|
|
14
|
+
/**
|
|
15
|
+
* Signals that the immutable successor generation already won even though a
|
|
16
|
+
* replayable projection failed afterward. Callers must never roll claims back
|
|
17
|
+
* on this error; replaying the same takeover input repairs the projections.
|
|
18
|
+
*/
|
|
19
|
+
export class AttemptTakeoverCommittedError extends Error {
|
|
20
|
+
turn_id;
|
|
21
|
+
assignment_id;
|
|
22
|
+
run_id;
|
|
23
|
+
attempt_epoch;
|
|
24
|
+
constructor(takeover, cause) {
|
|
25
|
+
super(`takeover generation ${takeover.next_generation.attempt_epoch} committed; projection repair required: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
26
|
+
this.name = 'AttemptTakeoverCommittedError';
|
|
27
|
+
this.turn_id = takeover.next_generation.turn_id;
|
|
28
|
+
this.assignment_id = takeover.next_generation.assignment_id;
|
|
29
|
+
this.run_id = takeover.next_generation.run_id;
|
|
30
|
+
this.attempt_epoch = takeover.next_generation.attempt_epoch;
|
|
31
|
+
this.cause = cause;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
14
34
|
/**
|
|
15
35
|
* Operator/engine takeover transaction.
|
|
16
36
|
*
|
|
@@ -25,66 +45,137 @@ export function takeoverLoopAttempt(input) {
|
|
|
25
45
|
throw new Error(`takeover workspace must already exist as an isolated directory: ${input.next_workspace_path}`);
|
|
26
46
|
}
|
|
27
47
|
const authorityActor = input.actor_id ?? input.actor;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
let committed;
|
|
49
|
+
let transaction;
|
|
50
|
+
try {
|
|
51
|
+
transaction = withLoopLock({
|
|
52
|
+
cwd: input.cwd,
|
|
53
|
+
intent: 'attempt-takeover',
|
|
54
|
+
agentId: authorityActor,
|
|
55
|
+
scope: { kind: 'loop', loopId: input.loop_id },
|
|
56
|
+
work: () => {
|
|
57
|
+
const loop = getLoop(input.loop_id, input.cwd);
|
|
58
|
+
if (!loop || loop.status !== 'open')
|
|
59
|
+
throw new Error(`loop ${input.loop_id} is not open`);
|
|
60
|
+
if (loop.created_by !== authorityActor) {
|
|
61
|
+
throw new Error(`attempt takeover requires loop coordinator ${loop.created_by}; caller is ${authorityActor}`);
|
|
62
|
+
}
|
|
63
|
+
const slot = loop.slots.find((candidate) => candidate.slot_id === input.slot_id);
|
|
64
|
+
if (!slot)
|
|
65
|
+
throw new Error(`slot ${input.slot_id} not found in loop ${input.loop_id}`);
|
|
66
|
+
const reservation = getReservation(input.turn_id, input.cwd);
|
|
67
|
+
if (!reservation || reservation.loop_id !== loop.id || reservation.slot_id !== slot.slot_id) {
|
|
68
|
+
throw new Error(`turn ${input.turn_id} does not own ${loop.id}/${slot.slot_id}`);
|
|
69
|
+
}
|
|
70
|
+
const activeGeneration = resolveTurnGenerationChain(input.cwd, input.turn_id)?.latest_generation;
|
|
71
|
+
const activeAssignmentId = activeGeneration?.assignment_id ?? reservation.child_ids.assignment_id;
|
|
72
|
+
if (slot.current_turn_id !== input.turn_id || slot.assignment_id !== activeAssignmentId) {
|
|
73
|
+
throw new Error(`slot ${slot.slot_id} is no longer bound to turn ${input.turn_id}`);
|
|
74
|
+
}
|
|
75
|
+
const takeover = prepareAttemptTakeoverV2({ ...input, actor: authorityActor });
|
|
76
|
+
committed = takeover;
|
|
77
|
+
input.on_stage?.('authority_committed');
|
|
78
|
+
const duplicate = listLoopEvents(loop.id, input.cwd).some((event) => event.kind === 'attempt_generation_changed'
|
|
79
|
+
&& event.turn_id === input.turn_id
|
|
80
|
+
&& event.to_epoch === takeover.next_generation.attempt_epoch
|
|
81
|
+
&& event.to_run_id === takeover.next_generation.run_id);
|
|
82
|
+
if (duplicate)
|
|
83
|
+
return { loop, takeover, reservation };
|
|
84
|
+
const now = nowISO();
|
|
85
|
+
const mutationId = crypto.randomUUID();
|
|
86
|
+
const executor = takeover.next_generation.executor ?? {
|
|
87
|
+
agent: reservation.agent,
|
|
88
|
+
agent_id: reservation.agent_id,
|
|
89
|
+
claim_id: reservation.claim_id,
|
|
90
|
+
capability_snapshot: reservation.capability_snapshot,
|
|
91
|
+
};
|
|
92
|
+
const event = {
|
|
93
|
+
event_id: crypto.randomUUID(),
|
|
94
|
+
loop_id: loop.id,
|
|
95
|
+
seq: loop.version + 1,
|
|
96
|
+
at: now,
|
|
97
|
+
by: authorityActor,
|
|
98
|
+
mutation_id: mutationId,
|
|
99
|
+
kind: 'attempt_generation_changed',
|
|
100
|
+
slot_id: slot.slot_id,
|
|
101
|
+
turn_id: input.turn_id,
|
|
102
|
+
assignment_id: takeover.next_generation.assignment_id,
|
|
103
|
+
claim_id: executor.claim_id,
|
|
104
|
+
agent: executor.agent,
|
|
105
|
+
agent_id: executor.agent_id,
|
|
106
|
+
from_epoch: takeover.previous_generation.attempt_epoch,
|
|
107
|
+
to_epoch: takeover.next_generation.attempt_epoch,
|
|
108
|
+
from_run_id: takeover.previous_generation.run_id,
|
|
109
|
+
to_run_id: takeover.next_generation.run_id,
|
|
110
|
+
close_digest: evidenceDigest(takeover.close_cell),
|
|
111
|
+
cause: input.cause,
|
|
112
|
+
};
|
|
113
|
+
const next = {
|
|
114
|
+
...loop,
|
|
115
|
+
version: loop.version + 1,
|
|
116
|
+
mutation_id: mutationId,
|
|
117
|
+
slots: loop.slots.map((candidate) => candidate.slot_id === slot.slot_id
|
|
118
|
+
? {
|
|
119
|
+
...candidate,
|
|
120
|
+
agent: executor.agent,
|
|
121
|
+
agent_id: executor.agent_id,
|
|
122
|
+
assignment_id: takeover.next_generation.assignment_id,
|
|
123
|
+
claim_id: executor.claim_id,
|
|
124
|
+
current_turn_id: input.turn_id,
|
|
125
|
+
status: 'assigned',
|
|
126
|
+
}
|
|
127
|
+
: candidate),
|
|
128
|
+
updated_at: now,
|
|
129
|
+
};
|
|
130
|
+
commitViaIntent({ loop_id: loop.id, base_version: loop.version, events: [event], thread_snapshot: next }, input.cwd);
|
|
131
|
+
input.on_stage?.('loop_committed');
|
|
132
|
+
return { loop: next, takeover, reservation };
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (committed)
|
|
138
|
+
throw new AttemptTakeoverCommittedError(committed, error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
const { takeover, reservation } = transaction;
|
|
142
|
+
const executor = takeover.next_generation.executor ?? {
|
|
143
|
+
agent: reservation.agent,
|
|
144
|
+
agent_id: reservation.agent_id,
|
|
145
|
+
claim_id: reservation.claim_id,
|
|
146
|
+
capability_snapshot: reservation.capability_snapshot,
|
|
147
|
+
};
|
|
148
|
+
if (takeover.previous_generation.assignment_id !== takeover.next_generation.assignment_id) {
|
|
149
|
+
try {
|
|
150
|
+
const previousAssignmentId = takeover.previous_generation.assignment_id;
|
|
151
|
+
const previousAssignment = loadAssignment(previousAssignmentId, input.cwd);
|
|
152
|
+
const predecessorTerminal = input.predecessor_assignment_terminal ?? 'cancelled';
|
|
153
|
+
const statusReason = `fenced by ${input.mode ?? 'takeover'} to epoch ${takeover.next_generation.attempt_epoch}`;
|
|
154
|
+
// System convergence intentionally covers only file-worker states
|
|
155
|
+
// (offered/accepted/started). A takeover can win before launch while the
|
|
156
|
+
// predecessor is still created, or during retry setup; both states have
|
|
157
|
+
// a legal direct cancellation edge and must be terminal before the stable
|
|
158
|
+
// claim is rebound to the successor generation.
|
|
159
|
+
if (predecessorTerminal === 'rerouted' && previousAssignment && previousAssignment.status !== 'rerouted') {
|
|
160
|
+
transitionAssignment(previousAssignmentId, 'rerouted', {
|
|
161
|
+
actor: input.actor,
|
|
162
|
+
syncAgentRun: false,
|
|
163
|
+
status_reason: statusReason,
|
|
164
|
+
}, input.cwd);
|
|
39
165
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
166
|
+
else if (previousAssignment?.status === 'created' || previousAssignment?.status === 'retrying') {
|
|
167
|
+
transitionAssignment(previousAssignmentId, 'cancelled', {
|
|
168
|
+
actor: input.actor,
|
|
169
|
+
syncAgentRun: false,
|
|
170
|
+
status_reason: statusReason,
|
|
171
|
+
}, input.cwd);
|
|
46
172
|
}
|
|
47
|
-
|
|
48
|
-
|
|
173
|
+
else {
|
|
174
|
+
convergeAssignmentToTerminal(previousAssignmentId, 'cancelled', statusReason, input.cwd);
|
|
49
175
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
&& event.to_epoch === takeover.next_generation.attempt_epoch
|
|
54
|
-
&& event.to_run_id === takeover.next_generation.run_id);
|
|
55
|
-
if (duplicate)
|
|
56
|
-
return { loop, takeover, reservation };
|
|
57
|
-
const now = nowISO();
|
|
58
|
-
const mutationId = crypto.randomUUID();
|
|
59
|
-
const event = {
|
|
60
|
-
event_id: crypto.randomUUID(),
|
|
61
|
-
loop_id: loop.id,
|
|
62
|
-
seq: loop.version + 1,
|
|
63
|
-
at: now,
|
|
64
|
-
by: authorityActor,
|
|
65
|
-
mutation_id: mutationId,
|
|
66
|
-
kind: 'attempt_generation_changed',
|
|
67
|
-
slot_id: slot.slot_id,
|
|
68
|
-
turn_id: input.turn_id,
|
|
69
|
-
assignment_id: takeover.next_generation.assignment_id,
|
|
70
|
-
from_epoch: takeover.previous_generation.attempt_epoch,
|
|
71
|
-
to_epoch: takeover.next_generation.attempt_epoch,
|
|
72
|
-
from_run_id: takeover.previous_generation.run_id,
|
|
73
|
-
to_run_id: takeover.next_generation.run_id,
|
|
74
|
-
close_digest: evidenceDigest(takeover.close_cell),
|
|
75
|
-
cause: input.cause,
|
|
76
|
-
};
|
|
77
|
-
const next = {
|
|
78
|
-
...loop,
|
|
79
|
-
version: loop.version + 1,
|
|
80
|
-
mutation_id: mutationId,
|
|
81
|
-
updated_at: now,
|
|
82
|
-
};
|
|
83
|
-
commitViaIntent({ loop_id: loop.id, base_version: loop.version, events: [event], thread_snapshot: next }, input.cwd);
|
|
84
|
-
return { loop: next, takeover, reservation };
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
const { takeover, reservation } = transaction;
|
|
176
|
+
}
|
|
177
|
+
catch { /* immutable close cell already fences the old assignment */ }
|
|
178
|
+
}
|
|
88
179
|
const assignment = loadAssignment(takeover.next_generation.assignment_id, input.cwd);
|
|
89
180
|
const previousRun = loadAgentRun(takeover.previous_generation.run_id, input.cwd);
|
|
90
181
|
if (previousRun && !['completed', 'failed', 'cancelled', 'timed_out', 'interrupted'].includes(previousRun.status)) {
|
|
@@ -97,23 +188,29 @@ export function takeoverLoopAttempt(input) {
|
|
|
97
188
|
}
|
|
98
189
|
catch { /* immutable close cell already fences the old run */ }
|
|
99
190
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
191
|
+
try {
|
|
192
|
+
ensureAgentRunProjection({
|
|
193
|
+
id: takeover.next_generation.run_id,
|
|
194
|
+
short_label: takeover.next_generation.run_id,
|
|
195
|
+
assignment_id: takeover.next_generation.assignment_id,
|
|
196
|
+
claim_id: executor.claim_id,
|
|
197
|
+
attempt_index: takeover.next_generation.attempt_epoch + 1,
|
|
198
|
+
agent: executor.agent,
|
|
199
|
+
agent_id: executor.agent_id,
|
|
200
|
+
transport: 'cli_spawn',
|
|
201
|
+
status: 'created',
|
|
202
|
+
scope: assignment?.scope ?? reservation.execution_contract?.workspace_policy.scope ?? reservation.cwd,
|
|
203
|
+
description: assignment?.description ?? `Attempt generation ${takeover.next_generation.attempt_epoch} for ${input.turn_id}`,
|
|
204
|
+
worktree_path: takeover.next_generation.workspace_path,
|
|
205
|
+
execution_contract_ref: takeover.execution_contract_ref,
|
|
206
|
+
capability_snapshot: executor.capability_snapshot,
|
|
207
|
+
tags: ['turn-owned', 'loop', 'attempt-takeover', `attempt-generation:${takeover.next_generation.attempt_epoch}`],
|
|
208
|
+
}, input.cwd);
|
|
209
|
+
input.on_stage?.('run_projected');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw new AttemptTakeoverCommittedError(takeover, error);
|
|
213
|
+
}
|
|
117
214
|
try {
|
|
118
215
|
createRuntimeEvent({
|
|
119
216
|
agent: input.actor,
|