@tea-agent/loop-agent 0.8.0 → 0.10.0-alpha.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/AGENTS.md +10 -0
- package/CHANGELOG.md +101 -1
- package/README.md +69 -5
- package/dist/application/dag/args.js +13 -16
- package/dist/application/dag/generate-task-dag.js +32 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +7 -11
- package/dist/cli/program.js +9 -21
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/dag-reconcile-run.js +118 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +60 -40
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +75 -9
- package/dist/governance/checks.js +6 -3
- package/dist/governance/manifest-types.js +33 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +353 -0
- package/dist/shared/reference-context.js +48 -22
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +30 -13
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +2 -3
- package/dist/worker/cli.js +243 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/delivery/final-verification.js +194 -0
- package/dist/worker/delivery/git-transaction.js +354 -0
- package/dist/worker/delivery/package.js +502 -0
- package/dist/worker/feature/decision-loader.js +68 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/next-action.js +74 -0
- package/dist/worker/feature/reducer.js +133 -0
- package/dist/worker/feature/review.js +502 -0
- package/dist/worker/feature/run.js +365 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +270 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/loop-agent-client.js +345 -101
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/read-model.js +282 -15
- package/dist/worker/observe/paths.js +17 -5
- package/dist/worker/observe/routes.js +78 -20
- package/dist/worker/observe/server.js +8 -6
- package/dist/worker/observe/static/app.js +1045 -177
- package/dist/worker/observe/static/index.html +70 -43
- package/dist/worker/observe/static/styles.css +553 -610
- package/dist/worker/pool/run-store.js +14 -2
- package/dist/worker/pool/validation.js +59 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/report/morning-report.js +41 -6
- package/dist/worker/run-task/run-task.js +23 -13
- package/dist/worker/runner/run-ready.js +89 -11
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/governance-profile.js +10 -0
- package/dist/workflows/dag/init-hybrid.js +601 -26
- package/dist/workflows/dag/lifecycle.js +146 -0
- package/dist/workflows/dag/node-execution.js +64 -7
- package/dist/workflows/dag/prompt.js +16 -0
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/runner.js +176 -119
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +45 -9
- package/dist/workflows/dag/validate.js +5 -8
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +35 -12
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +41 -15
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +39 -13
- package/docs/development-principles.md +1 -1
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +21 -0
- package/docs/feature-workflow.md +44 -4
- package/docs/init-surface.manifest.json +63 -1
- package/docs/loop-agent-harness.md +65 -3
- package/docs/progress/README.md +27 -0
- package/docs/reports/README.md +74 -5
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +4 -2
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
- package/docs/templates/backend-test-dag.json +213 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +8 -5
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/init-evolution-review.md +4 -2
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +17 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +6 -11
- package/package.json +22 -44
- package/scripts/check-product-line-docs.sh +10 -3
- package/scripts/check-task-pool-root.sh +1 -1
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +48 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/init-capability-evolution/SKILL.md +1 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +28 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +13 -15
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/verification-and-failure-handling.md +8 -3
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { hostname } from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
4
5
|
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
5
6
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareActiveRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
6
7
|
import { createDagNodeExecutor } from "./executor-registry.js";
|
|
7
|
-
import {
|
|
8
|
+
import { executeDagPiNode } from "../../executors/dag-pi-executor.js";
|
|
8
9
|
import { assertValidDagSpec } from "./validate.js";
|
|
9
10
|
import { normalizeDagPromptSources } from "./prompt-source.js";
|
|
10
11
|
import { relocateConvergenceArtifactPaths, relocateRunArtifactPaths, } from "./upstream-artifacts.js";
|
|
12
|
+
import { createSkillSnapshot, prepareSkillSnapshotForContinuation, writeSkillSnapshot, } from "./skill-snapshot.js";
|
|
11
13
|
import { buildNodePrompt, buildNodePromptWithResolvedSkillInstructions, executeDagNode, } from "./node-execution.js";
|
|
12
14
|
import { runConvergencePassController, shouldEnableDagConvergence, } from "./convergence/controller.js";
|
|
13
15
|
import { executeDagRanksOnce, isConditionSkippedReason, } from "./scheduler.js";
|
|
14
16
|
import { topoSortToRanks } from "./topo.js";
|
|
15
|
-
import { parseDagSpec, } from "./types.js";
|
|
17
|
+
import { parseDagSpec, resolveModelForTask, } from "./types.js";
|
|
16
18
|
import { executeDynamicCondition } from "./dynamic-runtime/condition.js";
|
|
17
19
|
import { executeDynamicLoopUntil } from "./dynamic-runtime/loop-until.js";
|
|
18
20
|
import { executeDynamicMapExpansion } from "./dynamic-runtime/map.js";
|
|
@@ -109,6 +111,9 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
109
111
|
status: "PENDING",
|
|
110
112
|
executor: task.executor,
|
|
111
113
|
complexity: task.complexity,
|
|
114
|
+
...(resolveModelForTask(task, spec.executorModels)
|
|
115
|
+
? { model: resolveModelForTask(task, spec.executorModels) }
|
|
116
|
+
: {}),
|
|
112
117
|
};
|
|
113
118
|
}
|
|
114
119
|
return {
|
|
@@ -117,6 +122,12 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
117
122
|
runId,
|
|
118
123
|
cwd: opts.cwd,
|
|
119
124
|
startedAt: new Date().toISOString(),
|
|
125
|
+
runner: {
|
|
126
|
+
pid: process.pid,
|
|
127
|
+
hostname: hostname(),
|
|
128
|
+
startedAt: new Date().toISOString(),
|
|
129
|
+
heartbeatAt: new Date().toISOString(),
|
|
130
|
+
},
|
|
120
131
|
status: opts.initOnly || opts.dryRun ? "pending" : "running",
|
|
121
132
|
ranks,
|
|
122
133
|
nodes,
|
|
@@ -152,6 +163,19 @@ export async function runDag(spec, opts) {
|
|
|
152
163
|
const runDir = activeRunDir;
|
|
153
164
|
await prepareActiveRunDir(runDir);
|
|
154
165
|
await writeRunSpec(runDir, spec);
|
|
166
|
+
// Resolve and snapshot all skill profiles before any node executes.
|
|
167
|
+
try {
|
|
168
|
+
const snapshot = await createSkillSnapshot({
|
|
169
|
+
runId: state.runId,
|
|
170
|
+
spec,
|
|
171
|
+
cwd: opts.cwd,
|
|
172
|
+
mode: "run-start",
|
|
173
|
+
});
|
|
174
|
+
state.skillSnapshotRef = await writeSkillSnapshot(runDir, snapshot);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new Error(`failed to create run-start skill snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
155
179
|
await writeRunState(runDir, state);
|
|
156
180
|
await notifyRunObserver(opts.observer, "onRunStart", state);
|
|
157
181
|
if (opts.dryRun || opts.initOnly) {
|
|
@@ -180,7 +204,6 @@ export async function runDag(spec, opts) {
|
|
|
180
204
|
});
|
|
181
205
|
}
|
|
182
206
|
finally {
|
|
183
|
-
await stopCursorWorkerIfRunning();
|
|
184
207
|
}
|
|
185
208
|
}
|
|
186
209
|
export async function resumeDagRun(opts) {
|
|
@@ -211,8 +234,34 @@ export async function resumeDagRun(opts) {
|
|
|
211
234
|
catch {
|
|
212
235
|
throw new Error(`dag run ${opts.runId} is missing human-approval.json for node ${decisionNodeId}`);
|
|
213
236
|
}
|
|
237
|
+
// Validate or backfill before mutating runner/status for this resume attempt.
|
|
238
|
+
try {
|
|
239
|
+
const prepared = await prepareSkillSnapshotForContinuation({
|
|
240
|
+
runDir,
|
|
241
|
+
runId: state.runId,
|
|
242
|
+
spec,
|
|
243
|
+
state,
|
|
244
|
+
cwd: state.cwd,
|
|
245
|
+
ref: state.skillSnapshotRef,
|
|
246
|
+
});
|
|
247
|
+
if (prepared.backfilled) {
|
|
248
|
+
console.warn(`[dag resume] warning: run ${state.runId} has no skill snapshot ref; creating a legacy-resume-backfill that freezes remaining nodes only`);
|
|
249
|
+
state.skillSnapshotRef = prepared.ref;
|
|
250
|
+
await writeRunState(runDir, state);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
throw new Error(`skill snapshot validation failed on resume: ${error instanceof Error ? error.message : String(error)}`);
|
|
255
|
+
}
|
|
214
256
|
const maxConcurrent = Math.max(1, opts.maxConcurrent ?? 4);
|
|
215
257
|
state.status = "running";
|
|
258
|
+
const resumedAt = new Date().toISOString();
|
|
259
|
+
state.runner = {
|
|
260
|
+
pid: process.pid,
|
|
261
|
+
hostname: hostname(),
|
|
262
|
+
startedAt: resumedAt,
|
|
263
|
+
heartbeatAt: resumedAt,
|
|
264
|
+
};
|
|
216
265
|
const activeRunDir = getDagRunDir(opts.cwd, "active", state.runId);
|
|
217
266
|
const completedRunDir = getDagRunDir(opts.cwd, "completed", state.runId);
|
|
218
267
|
const pausedRunDir = getDagRunDir(opts.cwd, "paused", state.runId);
|
|
@@ -232,7 +281,6 @@ export async function resumeDagRun(opts) {
|
|
|
232
281
|
});
|
|
233
282
|
}
|
|
234
283
|
finally {
|
|
235
|
-
await stopCursorWorkerIfRunning();
|
|
236
284
|
}
|
|
237
285
|
}
|
|
238
286
|
async function executeDagCheckpoint(input) {
|
|
@@ -243,124 +291,136 @@ async function executeDagCheckpoint(input) {
|
|
|
243
291
|
stateWriteQueue = stateWriteQueue.then(() => writeRunState(runDir, state, options));
|
|
244
292
|
await stateWriteQueue;
|
|
245
293
|
};
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
294
|
+
const heartbeatTimer = setInterval(() => {
|
|
295
|
+
if (!state.runner)
|
|
296
|
+
return;
|
|
297
|
+
state.runner.heartbeatAt = new Date().toISOString();
|
|
298
|
+
void persistState().catch(() => { });
|
|
299
|
+
}, 15_000);
|
|
300
|
+
heartbeatTimer.unref();
|
|
301
|
+
try {
|
|
302
|
+
const tasksById = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
303
|
+
const baseExecuteNode = input.executeNode ??
|
|
304
|
+
createDagNodeExecutor({
|
|
305
|
+
runDir,
|
|
306
|
+
runId: state.runId,
|
|
307
|
+
spec,
|
|
260
308
|
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
309
|
+
const executeDynamicNode = async (dynamicInput) => {
|
|
310
|
+
const { task } = dynamicInput;
|
|
311
|
+
if (task.dynamicExpansion) {
|
|
312
|
+
return executeDynamicMapExpansion({
|
|
313
|
+
...dynamicInput,
|
|
314
|
+
expansion: task.dynamicExpansion,
|
|
315
|
+
executeDynamicNode,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (task.dynamicCondition) {
|
|
319
|
+
return executeDynamicCondition({
|
|
320
|
+
task,
|
|
321
|
+
condition: task.dynamicCondition,
|
|
322
|
+
tasksById: dynamicInput.tasksById,
|
|
323
|
+
state: dynamicInput.state,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (task.dynamicLoopUntil) {
|
|
327
|
+
return executeDynamicLoopUntil({
|
|
328
|
+
...dynamicInput,
|
|
329
|
+
loop: task.dynamicLoopUntil,
|
|
330
|
+
executeDynamicNode,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return executeDynamicReduction({
|
|
264
334
|
task,
|
|
265
|
-
|
|
266
|
-
tasksById: dynamicInput.tasksById,
|
|
335
|
+
reduction: task.dynamicReduction,
|
|
267
336
|
state: dynamicInput.state,
|
|
337
|
+
runDir: dynamicInput.runDir,
|
|
268
338
|
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
loop: task.dynamicLoopUntil,
|
|
274
|
-
executeDynamicNode,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return executeDynamicReduction({
|
|
278
|
-
task,
|
|
279
|
-
reduction: task.dynamicReduction,
|
|
280
|
-
state: dynamicInput.state,
|
|
281
|
-
runDir: dynamicInput.runDir,
|
|
282
|
-
});
|
|
283
|
-
};
|
|
284
|
-
let pausedByNodeId;
|
|
285
|
-
while (true) {
|
|
286
|
-
pausedByNodeId = await executeDagRanksOnce({
|
|
287
|
-
state,
|
|
288
|
-
ranks,
|
|
289
|
-
tasksById,
|
|
290
|
-
maxConcurrent,
|
|
291
|
-
persistState,
|
|
292
|
-
createExecuteNodeForRank: (rankCursorNodeIds) => buildRankAwareExecuteNode({
|
|
293
|
-
baseExecuteNode,
|
|
294
|
-
customExecuteNode: input.executeNode,
|
|
295
|
-
rankCursorNodeIds,
|
|
296
|
-
tasksById,
|
|
297
|
-
meta: { runDir, runId: state.runId, spec },
|
|
298
|
-
}),
|
|
299
|
-
executeScheduledNode: (nodeId, executeNode, onPause) => executeDagNode({
|
|
300
|
-
nodeId,
|
|
301
|
-
tasksById,
|
|
339
|
+
};
|
|
340
|
+
let pausedByNodeId;
|
|
341
|
+
while (true) {
|
|
342
|
+
pausedByNodeId = await executeDagRanksOnce({
|
|
302
343
|
state,
|
|
344
|
+
ranks,
|
|
345
|
+
tasksById,
|
|
346
|
+
maxConcurrent,
|
|
347
|
+
persistState,
|
|
348
|
+
createExecuteNodeForRank: (rankWriterNodeIds) => buildRankAwareExecuteNode({
|
|
349
|
+
baseExecuteNode,
|
|
350
|
+
customExecuteNode: input.executeNode,
|
|
351
|
+
rankWriterNodeIds,
|
|
352
|
+
tasksById,
|
|
353
|
+
meta: { runDir, runId: state.runId, spec },
|
|
354
|
+
}),
|
|
355
|
+
executeScheduledNode: (nodeId, executeNode, onPause) => executeDagNode({
|
|
356
|
+
nodeId,
|
|
357
|
+
tasksById,
|
|
358
|
+
state,
|
|
359
|
+
spec,
|
|
360
|
+
cwd,
|
|
361
|
+
runDir,
|
|
362
|
+
executeNode,
|
|
363
|
+
executeDynamicNode,
|
|
364
|
+
observer: input.observer,
|
|
365
|
+
persistState,
|
|
366
|
+
onPause,
|
|
367
|
+
}),
|
|
368
|
+
});
|
|
369
|
+
if (pausedByNodeId) {
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
const convergenceDecision = await runConvergencePassController({
|
|
303
373
|
spec,
|
|
304
|
-
|
|
374
|
+
state,
|
|
375
|
+
tasksById,
|
|
305
376
|
runDir,
|
|
306
|
-
|
|
307
|
-
executeDynamicNode,
|
|
308
|
-
observer: input.observer,
|
|
377
|
+
cwd,
|
|
309
378
|
persistState,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
379
|
+
});
|
|
380
|
+
if (convergenceDecision.pausedByNodeId) {
|
|
381
|
+
pausedByNodeId = convergenceDecision.pausedByNodeId;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
if (convergenceDecision.retry) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
314
387
|
break;
|
|
315
388
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
389
|
+
state.finishedAt = new Date().toISOString();
|
|
390
|
+
const runDirBeforeTransfer = runDir;
|
|
391
|
+
if (pausedByNodeId) {
|
|
392
|
+
state.status = "paused";
|
|
393
|
+
await persistState();
|
|
394
|
+
await notifyRunObserver(input.observer, "onRunFinish", state);
|
|
395
|
+
runDir = await moveToPausedRunDir(runDir, pausedRunDir);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
finalizeTerminalRunStatus(state, spec.tasks.length);
|
|
399
|
+
await persistState();
|
|
400
|
+
await notifyRunObserver(input.observer, "onRunFinish", state);
|
|
401
|
+
runDir = await moveToCompletedRunDir(runDir, completedRunDir);
|
|
402
|
+
}
|
|
403
|
+
await relocateRunArtifactPaths({
|
|
320
404
|
runDir,
|
|
321
|
-
|
|
322
|
-
|
|
405
|
+
oldRunDir: runDirBeforeTransfer,
|
|
406
|
+
nodes: state.nodes,
|
|
323
407
|
});
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
if (convergenceDecision.retry) {
|
|
329
|
-
continue;
|
|
408
|
+
if (state.convergence) {
|
|
409
|
+
relocateConvergenceArtifactPaths(state.convergence, runDirBeforeTransfer, runDir);
|
|
330
410
|
}
|
|
331
|
-
|
|
411
|
+
await persistState({ allowCompletedFactsWrite: true });
|
|
412
|
+
return {
|
|
413
|
+
title: spec.title,
|
|
414
|
+
runId: state.runId,
|
|
415
|
+
status: state.status,
|
|
416
|
+
ranks,
|
|
417
|
+
nodes: state.nodes,
|
|
418
|
+
runDir,
|
|
419
|
+
};
|
|
332
420
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (pausedByNodeId) {
|
|
336
|
-
state.status = "paused";
|
|
337
|
-
await persistState();
|
|
338
|
-
await notifyRunObserver(input.observer, "onRunFinish", state);
|
|
339
|
-
runDir = await moveToPausedRunDir(runDir, pausedRunDir);
|
|
421
|
+
finally {
|
|
422
|
+
clearInterval(heartbeatTimer);
|
|
340
423
|
}
|
|
341
|
-
else {
|
|
342
|
-
finalizeTerminalRunStatus(state, spec.tasks.length);
|
|
343
|
-
await persistState();
|
|
344
|
-
await notifyRunObserver(input.observer, "onRunFinish", state);
|
|
345
|
-
runDir = await moveToCompletedRunDir(runDir, completedRunDir);
|
|
346
|
-
}
|
|
347
|
-
await relocateRunArtifactPaths({
|
|
348
|
-
runDir,
|
|
349
|
-
oldRunDir: runDirBeforeTransfer,
|
|
350
|
-
nodes: state.nodes,
|
|
351
|
-
});
|
|
352
|
-
if (state.convergence) {
|
|
353
|
-
relocateConvergenceArtifactPaths(state.convergence, runDirBeforeTransfer, runDir);
|
|
354
|
-
}
|
|
355
|
-
await persistState({ allowCompletedFactsWrite: true });
|
|
356
|
-
return {
|
|
357
|
-
title: spec.title,
|
|
358
|
-
runId: state.runId,
|
|
359
|
-
status: state.status,
|
|
360
|
-
ranks,
|
|
361
|
-
nodes: state.nodes,
|
|
362
|
-
runDir,
|
|
363
|
-
};
|
|
364
424
|
}
|
|
365
425
|
async function notifyRunObserver(observer, event, state) {
|
|
366
426
|
try {
|
|
@@ -384,16 +444,10 @@ function finalizeTerminalRunStatus(state, taskCount) {
|
|
|
384
444
|
state.status = "failed";
|
|
385
445
|
}
|
|
386
446
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (isCursorWorkerRunning()) {
|
|
390
|
-
await stopCursorWorker();
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
function concurrentSiblingWriteSetsForNode(rankCursorNodeIds, nodeId, tasksById) {
|
|
394
|
-
if (rankCursorNodeIds.length <= 1)
|
|
447
|
+
function concurrentSiblingWriteSetsForNode(rankWriterNodeIds, nodeId, tasksById) {
|
|
448
|
+
if (rankWriterNodeIds.length <= 1)
|
|
395
449
|
return [];
|
|
396
|
-
return
|
|
450
|
+
return rankWriterNodeIds
|
|
397
451
|
.filter((id) => id !== nodeId)
|
|
398
452
|
.map((id) => tasksById.get(id)?.writeSet ?? [])
|
|
399
453
|
.filter((writeSet) => writeSet.length > 0);
|
|
@@ -403,13 +457,16 @@ function buildRankAwareExecuteNode(input) {
|
|
|
403
457
|
return input.baseExecuteNode;
|
|
404
458
|
}
|
|
405
459
|
return async (nodeInput) => {
|
|
406
|
-
|
|
460
|
+
const isPiWriter = nodeInput.task.executor === "pi" &&
|
|
461
|
+
nodeInput.task.toolProfile === "write" &&
|
|
462
|
+
Boolean(nodeInput.model);
|
|
463
|
+
if (!isPiWriter) {
|
|
407
464
|
return input.baseExecuteNode(nodeInput);
|
|
408
465
|
}
|
|
409
466
|
const siblingWriteSets = nodeInput.task.writePolicy === "exclusive"
|
|
410
|
-
? concurrentSiblingWriteSetsForNode(input.
|
|
467
|
+
? concurrentSiblingWriteSetsForNode(input.rankWriterNodeIds, nodeInput.task.id, input.tasksById)
|
|
411
468
|
: [];
|
|
412
|
-
return
|
|
469
|
+
return executeDagPiNode({ ...nodeInput, model: nodeInput.model }, {
|
|
413
470
|
...input.meta,
|
|
414
471
|
concurrentSiblingWriteSets: siblingWriteSets.length > 0 ? siblingWriteSets : undefined,
|
|
415
472
|
writeGuardAttribution: siblingWriteSets.length > 0 ? "best-effort" : "per-node",
|
|
@@ -55,8 +55,13 @@ export async function executeDagRanksOnce(input) {
|
|
|
55
55
|
return isPauseOnHumanDecisionGate(task);
|
|
56
56
|
});
|
|
57
57
|
const regularRunnable = runnable.filter((id) => !pauseGateRunnable.includes(id));
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const rankWriterNodeIds = runnable.filter((id) => {
|
|
59
|
+
const task = input.tasksById.get(id);
|
|
60
|
+
return (task?.executor === "pi" &&
|
|
61
|
+
task.toolProfile === "write" &&
|
|
62
|
+
task.writePolicy === "exclusive");
|
|
63
|
+
});
|
|
64
|
+
const executeNode = input.createExecuteNodeForRank(rankWriterNodeIds);
|
|
60
65
|
for (const nodeId of pauseGateRunnable) {
|
|
61
66
|
await input.executeScheduledNode(nodeId, executeNode, (id, pausedAt, pauseReason) => {
|
|
62
67
|
pausedByNodeId = id;
|