@tea-agent/loop-agent 0.1.0 → 0.2.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/CHANGELOG.md +17 -1
- package/README.md +127 -92
- package/dist/adapters/index.js +3 -2
- package/dist/adapters/loop-agent.js +44 -2
- package/dist/application/dag/args.js +420 -0
- package/dist/application/dag/generate-task-dag.js +280 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +93 -0
- package/dist/application/dag/validate-dag.js +101 -0
- package/dist/application/loop/run-action.js +23 -0
- package/dist/cli/catalog.js +2 -237
- package/dist/cli/command-definitions.js +571 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/program.js +65 -1
- package/dist/cli/router.js +13 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/commands/dag-report.js +6 -107
- package/dist/commands/dag-run-task.js +8 -466
- package/dist/commands/dag-validate.js +7 -179
- package/dist/commands/examples.js +90 -0
- package/dist/commands/init.js +1495 -0
- package/dist/commands/loop.js +57 -31
- package/dist/commands/pi-prompt.js +2 -9
- package/dist/commands/run-dag.js +7 -180
- package/dist/executors/cursor-executor-artifacts.js +3 -4
- package/dist/executors/cursor-worker-client.js +13 -3
- package/dist/executors/dag-cursor-executor.js +2 -3
- package/dist/executors/dag-pi-executor.js +3 -4
- package/dist/executors/dag-static-executor.js +2 -5
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/shell-executor.js +12 -20
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +49 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +23 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +77 -0
- package/dist/records/one-shot-runs.js +26 -61
- package/dist/records/promotion.js +3 -4
- package/dist/shared/artifacts-core.js +5 -5
- package/dist/shared/logger.js +9 -15
- package/dist/task/delegate.js +4 -4
- package/dist/task/runtime.js +5 -7
- package/dist/task/state.js +6 -20
- package/dist/workflows/dag/convergence/controller.js +277 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
- package/dist/workflows/dag/lifecycle.js +6 -5
- package/dist/workflows/dag/node-execution.js +262 -0
- package/dist/workflows/dag/run-store.js +36 -0
- package/dist/workflows/dag/runner.js +82 -1341
- package/dist/workflows/dag/scheduler.js +84 -0
- package/dist/workflows/dag/upstream-artifacts.js +20 -18
- package/dist/workflows/loop/actions/cursor-fix.js +191 -0
- package/dist/workflows/loop/actions/dag-action.js +130 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +55 -1212
- package/dist/workflows/loop/closeout.js +5 -4
- package/dist/workflows/loop/context.js +2 -3
- package/dist/workflows/loop/events.js +3 -2
- package/dist/workflows/loop/policy/auto-policy.js +104 -0
- package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
- package/dist/workflows/loop/rounds.js +3 -3
- package/dist/workflows/loop/signals.js +4 -7
- package/dist/workflows/loop/state.js +11 -11
- package/docs/README.md +3 -2
- package/docs/architecture/runtime-boundaries.md +147 -0
- package/docs/exec-plans/active/README.md +4 -0
- package/docs/exec-plans/completed/README.md +6 -2
- package/package.json +2 -1
- package/skills/ai-engineering-context/SKILL.md +21 -21
- package/skills/loop-agent/SKILL.md +73 -188
- package/skills/loop-agent/references/README.md +6 -2
- package/skills/loop-agent/references/harness-policy.md +113 -113
- package/skills/loop-agent/references/learned/README.md +13 -13
- package/skills/loop-agent/references/long-running-loop.md +59 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
- package/skills/loop-agent/references/verification-and-failure-handling.md +18 -0
- package/skills/requesting-code-review/SKILL.md +40 -40
- package/skills/requesting-code-review/code-reviewer.md +4 -4
- package/skills/systematic-debugging/CREATION-LOG.md +43 -43
- package/skills/systematic-debugging/SKILL.md +113 -113
- package/skills/systematic-debugging/condition-based-waiting.md +20 -20
- package/skills/systematic-debugging/defense-in-depth.md +27 -27
- package/skills/systematic-debugging/root-cause-tracing.md +38 -38
- package/skills/systematic-debugging/test-academic.md +6 -6
- package/skills/systematic-debugging/test-pressure-1.md +6 -6
- package/skills/systematic-debugging/test-pressure-2.md +2 -2
- package/skills/systematic-debugging/test-pressure-3.md +6 -6
- package/skills/verification-before-completion/SKILL.md +37 -37
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compileWorkflowToDag, defaultCompileManifestPath, } from "../../dynamic/compile.js";
|
|
4
|
+
import { buildWorkflowSpecForProfile } from "../../dynamic/profiles.js";
|
|
5
|
+
import { parseWorkflowSpec } from "../../dynamic/validate.js";
|
|
6
|
+
import { resumeDagRun, runDag } from "../../dag/runner.js";
|
|
7
|
+
import { assertValidDagSpec } from "../../dag/validate.js";
|
|
8
|
+
import { assertDagRunTransferTargetAvailable, getDagRunDir, locateDagRun, readHumanEscalation, requirePausedDagRun, transferDagRunDir, writeDagRunState, writeHumanApprovalArtifact, } from "../../dag/lifecycle.js";
|
|
9
|
+
import { getTaskPaths } from "../../../task/runtime.js";
|
|
10
|
+
import { appendLoopRound } from "../rounds.js";
|
|
11
|
+
import { rewriteLoopContext } from "../context.js";
|
|
12
|
+
import { readLoopRounds } from "../rounds.js";
|
|
13
|
+
import { drainLoopSignals, pendingLoopSignals } from "../signals.js";
|
|
14
|
+
import { dagRunIdFromApprovalMessage, dagRunIdFromRef, relativeRef, summarizeWorkflowReport, workflowFailureCategory, } from "./shared.js";
|
|
15
|
+
export async function runLoopWorkflowAction(repoRoot, taskId, options = {}) {
|
|
16
|
+
const mode = options.mode ?? "review";
|
|
17
|
+
const cwd = options.cwd ? path.resolve(repoRoot, options.cwd) : repoRoot;
|
|
18
|
+
const rounds = await readLoopRounds(repoRoot, taskId);
|
|
19
|
+
const round = rounds.length + 1;
|
|
20
|
+
const taskPaths = getTaskPaths(repoRoot, taskId);
|
|
21
|
+
const workflowRoot = path.join(taskPaths.taskDir, "workflows");
|
|
22
|
+
const plannedDir = path.join(workflowRoot, "planned");
|
|
23
|
+
const compiledDir = path.join(workflowRoot, "compiled");
|
|
24
|
+
const reportsDir = path.join(workflowRoot, "reports");
|
|
25
|
+
await mkdir(plannedDir, { recursive: true });
|
|
26
|
+
await mkdir(compiledDir, { recursive: true });
|
|
27
|
+
await mkdir(reportsDir, { recursive: true });
|
|
28
|
+
const profile = options.profile ?? "pr-review";
|
|
29
|
+
const workflowPath = options.workflowPath
|
|
30
|
+
? path.resolve(repoRoot, options.workflowPath)
|
|
31
|
+
: path.join(plannedDir, `${profile}.workflow.json`);
|
|
32
|
+
const workflow = options.workflowPath
|
|
33
|
+
? parseWorkflowSpec(JSON.parse(await readFile(workflowPath, "utf-8")))
|
|
34
|
+
: buildWorkflowSpecForProfile({
|
|
35
|
+
profile,
|
|
36
|
+
taskId,
|
|
37
|
+
base: options.base,
|
|
38
|
+
head: options.head,
|
|
39
|
+
changedFiles: options.changedFiles,
|
|
40
|
+
});
|
|
41
|
+
if (!options.workflowPath) {
|
|
42
|
+
await writeFile(workflowPath, `${JSON.stringify(workflow, null, 2)}\n`, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
const compiledDagPath = path.join(compiledDir, `${workflow.meta.name}.dag.json`);
|
|
45
|
+
const compileManifestPath = defaultCompileManifestPath(compiledDagPath);
|
|
46
|
+
const compiled = compileWorkflowToDag(workflow, { sourcePath: workflowPath });
|
|
47
|
+
assertValidDagSpec(compiled.dag, { strictGovernance: true });
|
|
48
|
+
await writeFile(compiledDagPath, `${JSON.stringify(compiled.dag, null, 2)}\n`, "utf-8");
|
|
49
|
+
await writeFile(compileManifestPath, `${JSON.stringify(compiled.manifest, null, 2)}\n`, "utf-8");
|
|
50
|
+
let runId = "";
|
|
51
|
+
let runStatus = "not-run";
|
|
52
|
+
let runRef = "";
|
|
53
|
+
let failureCategory = "";
|
|
54
|
+
let decision = "continue";
|
|
55
|
+
let result = "workflow planned, compiled, and strict-governance validated";
|
|
56
|
+
let verification = "workflow compiled DAG validated with strict governance";
|
|
57
|
+
let expectedNextVerification = "inspect workflow DAG/report refs before executing or closing out";
|
|
58
|
+
const reportPath = path.join(reportsDir, `${workflow.meta.name}.workflow-report.md`);
|
|
59
|
+
if (mode === "execute") {
|
|
60
|
+
const summary = await runDag(compiled.dag, {
|
|
61
|
+
cwd,
|
|
62
|
+
runId: options.runId,
|
|
63
|
+
maxConcurrent: options.maxConcurrent,
|
|
64
|
+
});
|
|
65
|
+
runId = summary.runId;
|
|
66
|
+
runStatus = summary.status;
|
|
67
|
+
runRef = relativeRef(repoRoot, summary.runDir);
|
|
68
|
+
failureCategory = workflowFailureCategory(summary.nodes);
|
|
69
|
+
decision = runStatus === "paused" ? "pause" : "continue";
|
|
70
|
+
expectedNextVerification =
|
|
71
|
+
decision === "pause"
|
|
72
|
+
? "approve or reject paused workflow DAG run, then resume or replan"
|
|
73
|
+
: "review workflow report and run any required shell verification";
|
|
74
|
+
const report = summarizeWorkflowReport(summary.nodes[workflow.final.from]?.stdout);
|
|
75
|
+
result = report.result;
|
|
76
|
+
verification = `workflow run status=${runStatus}; ${report.verification}`;
|
|
77
|
+
await writeFile(reportPath, report.markdown, "utf-8");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await writeFile(reportPath, "# Workflow Report\n\nWorkflow was compiled in review mode and not executed.\n", "utf-8");
|
|
81
|
+
}
|
|
82
|
+
const record = {
|
|
83
|
+
schemaVersion: 1,
|
|
84
|
+
taskId,
|
|
85
|
+
round,
|
|
86
|
+
action: "workflow",
|
|
87
|
+
mode,
|
|
88
|
+
profile: workflow.meta.name,
|
|
89
|
+
ok: mode === "review" || runStatus === "finished",
|
|
90
|
+
workflowRef: relativeRef(repoRoot, workflowPath),
|
|
91
|
+
compiledDagRef: relativeRef(repoRoot, compiledDagPath),
|
|
92
|
+
compileManifestRef: relativeRef(repoRoot, compileManifestPath),
|
|
93
|
+
runId,
|
|
94
|
+
runStatus,
|
|
95
|
+
runRef,
|
|
96
|
+
reportRef: relativeRef(repoRoot, reportPath),
|
|
97
|
+
result,
|
|
98
|
+
verification,
|
|
99
|
+
failureCategory,
|
|
100
|
+
expectedNextVerification,
|
|
101
|
+
recordedAt: new Date().toISOString(),
|
|
102
|
+
};
|
|
103
|
+
await appendLoopRound(repoRoot, taskId, {
|
|
104
|
+
action: "workflow",
|
|
105
|
+
refs: [
|
|
106
|
+
record.workflowRef,
|
|
107
|
+
record.compiledDagRef,
|
|
108
|
+
record.compileManifestRef,
|
|
109
|
+
record.runRef,
|
|
110
|
+
record.reportRef,
|
|
111
|
+
].filter(Boolean),
|
|
112
|
+
result,
|
|
113
|
+
verification,
|
|
114
|
+
lesson: mode === "review"
|
|
115
|
+
? "Workflow spec is planned and compiled; inspect workflow refs before execution."
|
|
116
|
+
: "Workflow run facts recorded by ref; keep detailed node outputs in DAG artifacts.",
|
|
117
|
+
next: expectedNextVerification,
|
|
118
|
+
decision,
|
|
119
|
+
failureCategory,
|
|
120
|
+
completedCriteria: ["workflow round action executed"],
|
|
121
|
+
});
|
|
122
|
+
await rewriteLoopContext(repoRoot, taskId);
|
|
123
|
+
return record;
|
|
124
|
+
}
|
|
125
|
+
async function findPausedWorkflowGateRunId(input) {
|
|
126
|
+
const candidates = new Set();
|
|
127
|
+
const fromMessage = dagRunIdFromApprovalMessage(input.signal.message);
|
|
128
|
+
if (fromMessage)
|
|
129
|
+
candidates.add(fromMessage);
|
|
130
|
+
for (const ref of input.signal.refs) {
|
|
131
|
+
const fromRef = dagRunIdFromRef(ref);
|
|
132
|
+
if (fromRef)
|
|
133
|
+
candidates.add(fromRef);
|
|
134
|
+
}
|
|
135
|
+
const rounds = await readLoopRounds(input.repoRoot, input.taskId);
|
|
136
|
+
for (const round of [...rounds].reverse()) {
|
|
137
|
+
if (round.action !== "workflow")
|
|
138
|
+
continue;
|
|
139
|
+
for (const ref of round.refs) {
|
|
140
|
+
const fromRef = dagRunIdFromRef(ref);
|
|
141
|
+
if (fromRef)
|
|
142
|
+
candidates.add(fromRef);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
const located = await locateDagRun(input.repoRoot, candidate);
|
|
147
|
+
if (located?.lifecycle === "paused")
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
async function approvePausedDagRunFromSignal(input) {
|
|
153
|
+
const { runDir, state } = await requirePausedDagRun(input.repoRoot, input.runId);
|
|
154
|
+
const nodeId = state.pausedByNodeId;
|
|
155
|
+
const node = state.nodes[nodeId];
|
|
156
|
+
if (!node) {
|
|
157
|
+
throw new Error(`paused node ${nodeId} not found in run state`);
|
|
158
|
+
}
|
|
159
|
+
const escalation = await readHumanEscalation(runDir, nodeId);
|
|
160
|
+
const options = escalation.humanEscalation.options.map((entry) => entry.id);
|
|
161
|
+
const selectedOption = escalation.humanEscalation.recommendedOption &&
|
|
162
|
+
options.includes(escalation.humanEscalation.recommendedOption)
|
|
163
|
+
? escalation.humanEscalation.recommendedOption
|
|
164
|
+
: options[0];
|
|
165
|
+
if (!selectedOption) {
|
|
166
|
+
throw new Error(`missing human escalation options for node ${nodeId}`);
|
|
167
|
+
}
|
|
168
|
+
const activeRunDir = getDagRunDir(input.repoRoot, "active", state.runId);
|
|
169
|
+
await assertDagRunTransferTargetAvailable(activeRunDir);
|
|
170
|
+
const approvedAt = new Date().toISOString();
|
|
171
|
+
const artifact = {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
runId: state.runId,
|
|
174
|
+
nodeId,
|
|
175
|
+
decision: "approved",
|
|
176
|
+
selectedOption,
|
|
177
|
+
notes: `Approved by loop approval signal ${input.signal.id}: ${input.signal.message}`,
|
|
178
|
+
approvedAt,
|
|
179
|
+
escalationArtifactPath: node.escalationArtifactPath,
|
|
180
|
+
};
|
|
181
|
+
const approvalPath = await writeHumanApprovalArtifact({
|
|
182
|
+
runDir,
|
|
183
|
+
nodeId,
|
|
184
|
+
artifact,
|
|
185
|
+
});
|
|
186
|
+
node.pauseReason = undefined;
|
|
187
|
+
node.humanApprovalArtifactPath = approvalPath;
|
|
188
|
+
state.status = "running";
|
|
189
|
+
state.humanDecisionNodeId = nodeId;
|
|
190
|
+
delete state.pausedAt;
|
|
191
|
+
delete state.pausedByNodeId;
|
|
192
|
+
delete state.pauseReason;
|
|
193
|
+
await writeDagRunState(runDir, state);
|
|
194
|
+
await transferDagRunDir(runDir, activeRunDir);
|
|
195
|
+
return { selectedOption };
|
|
196
|
+
}
|
|
197
|
+
export async function runLoopWorkflowGateSignalAction(repoRoot, taskId, signals = []) {
|
|
198
|
+
const approvalSignal = pendingLoopSignals(signals).find((signal) => signal.type === "approval");
|
|
199
|
+
if (!approvalSignal)
|
|
200
|
+
return undefined;
|
|
201
|
+
const runId = await findPausedWorkflowGateRunId({
|
|
202
|
+
repoRoot,
|
|
203
|
+
taskId,
|
|
204
|
+
signal: approvalSignal,
|
|
205
|
+
});
|
|
206
|
+
if (!runId)
|
|
207
|
+
return undefined;
|
|
208
|
+
const rounds = await readLoopRounds(repoRoot, taskId);
|
|
209
|
+
const round = rounds.length + 1;
|
|
210
|
+
const approval = await approvePausedDagRunFromSignal({
|
|
211
|
+
repoRoot,
|
|
212
|
+
runId,
|
|
213
|
+
signal: approvalSignal,
|
|
214
|
+
});
|
|
215
|
+
const summary = await resumeDagRun({ cwd: repoRoot, runId });
|
|
216
|
+
const runRef = relativeRef(repoRoot, summary.runDir);
|
|
217
|
+
const failureCategory = workflowFailureCategory(summary.nodes);
|
|
218
|
+
const decision = summary.status === "paused" ? "pause" : "continue";
|
|
219
|
+
const expectedNextVerification = decision === "pause"
|
|
220
|
+
? "approve or reject paused workflow DAG run, then resume or replan"
|
|
221
|
+
: "review resumed workflow report and run any required shell verification";
|
|
222
|
+
const result = `approval signal satisfied workflow human gate for DAG run ${runId}`;
|
|
223
|
+
const verification = `workflow gate approved with option=${approval.selectedOption}; resumed status=${summary.status}`;
|
|
224
|
+
const record = {
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
taskId,
|
|
227
|
+
round,
|
|
228
|
+
action: "workflow-gate-signal",
|
|
229
|
+
ok: summary.status === "finished",
|
|
230
|
+
signalId: approvalSignal.id,
|
|
231
|
+
runId,
|
|
232
|
+
selectedOption: approval.selectedOption,
|
|
233
|
+
runStatus: summary.status,
|
|
234
|
+
runRef,
|
|
235
|
+
result,
|
|
236
|
+
verification,
|
|
237
|
+
failureCategory,
|
|
238
|
+
expectedNextVerification,
|
|
239
|
+
recordedAt: new Date().toISOString(),
|
|
240
|
+
};
|
|
241
|
+
await appendLoopRound(repoRoot, taskId, {
|
|
242
|
+
action: "workflow-gate-signal",
|
|
243
|
+
refs: [...approvalSignal.refs, runRef].filter(Boolean),
|
|
244
|
+
result,
|
|
245
|
+
verification,
|
|
246
|
+
lesson: "Loop approval signal was converted into the existing DAG approval/resume lifecycle.",
|
|
247
|
+
next: expectedNextVerification,
|
|
248
|
+
decision,
|
|
249
|
+
failureCategory,
|
|
250
|
+
completedCriteria: ["workflow human gate approval signal consumed"],
|
|
251
|
+
});
|
|
252
|
+
await drainLoopSignals(repoRoot, taskId, [approvalSignal.id]);
|
|
253
|
+
await rewriteLoopContext(repoRoot, taskId);
|
|
254
|
+
return record;
|
|
255
|
+
}
|