@tea-agent/loop-agent 0.1.0 → 0.2.1

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.
Files changed (143) hide show
  1. package/AGENTS.md +62 -45
  2. package/CHANGELOG.md +60 -28
  3. package/README.md +160 -124
  4. package/bin/loop-agent.js +21 -21
  5. package/dist/adapters/index.js +3 -2
  6. package/dist/adapters/loop-agent.js +44 -2
  7. package/dist/application/dag/args.js +420 -0
  8. package/dist/application/dag/generate-task-dag.js +280 -0
  9. package/dist/application/dag/report-dag.js +14 -0
  10. package/dist/application/dag/run-dag.js +106 -0
  11. package/dist/application/dag/validate-dag.js +102 -0
  12. package/dist/application/loop/run-action.js +23 -0
  13. package/dist/cli/catalog.js +2 -237
  14. package/dist/cli/command-definitions.js +571 -0
  15. package/dist/cli/index.js +2 -0
  16. package/dist/cli/program.js +65 -1
  17. package/dist/cli/router.js +13 -0
  18. package/dist/cli-governance/active-residue-check.js +38 -0
  19. package/dist/commands/dag-report.js +6 -107
  20. package/dist/commands/dag-run-task.js +8 -466
  21. package/dist/commands/dag-validate.js +7 -179
  22. package/dist/commands/examples.js +90 -0
  23. package/dist/commands/init.js +1518 -0
  24. package/dist/commands/loop.js +57 -31
  25. package/dist/commands/pi-prompt.js +2 -9
  26. package/dist/commands/run-dag.js +7 -180
  27. package/dist/executors/cursor-executor-artifacts.js +3 -4
  28. package/dist/executors/cursor-worker-client.js +13 -3
  29. package/dist/executors/dag-cursor-executor.js +2 -3
  30. package/dist/executors/dag-pi-executor.js +3 -4
  31. package/dist/executors/dag-static-executor.js +2 -5
  32. package/dist/executors/pi-defaults.js +9 -0
  33. package/dist/executors/shell-executor.js +12 -20
  34. package/dist/governance/manifest-types.js +1 -0
  35. package/dist/infrastructure/harness/active-residue-policy.js +73 -0
  36. package/dist/infrastructure/harness/artifact-store.js +72 -0
  37. package/dist/infrastructure/harness/atomic-write.js +49 -0
  38. package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
  39. package/dist/infrastructure/harness/loop-action-store.js +23 -0
  40. package/dist/infrastructure/harness/loop-store.js +41 -0
  41. package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
  42. package/dist/infrastructure/harness/task-store.js +77 -0
  43. package/dist/records/one-shot-runs.js +26 -61
  44. package/dist/records/promotion.js +3 -4
  45. package/dist/shared/artifacts-core.js +5 -5
  46. package/dist/shared/logger.js +9 -15
  47. package/dist/task/delegate.js +4 -4
  48. package/dist/task/runtime.js +5 -7
  49. package/dist/task/state.js +6 -20
  50. package/dist/workflows/dag/convergence/controller.js +277 -0
  51. package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
  52. package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
  53. package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
  54. package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
  55. package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
  56. package/dist/workflows/dag/failure-routing.js +82 -0
  57. package/dist/workflows/dag/lifecycle.js +101 -8
  58. package/dist/workflows/dag/node-execution.js +262 -0
  59. package/dist/workflows/dag/report.js +73 -1
  60. package/dist/workflows/dag/run-store.js +36 -0
  61. package/dist/workflows/dag/runner.js +82 -1341
  62. package/dist/workflows/dag/scheduler.js +84 -0
  63. package/dist/workflows/dag/upstream-artifacts.js +20 -18
  64. package/dist/workflows/loop/actions/cursor-fix.js +191 -0
  65. package/dist/workflows/loop/actions/dag-action.js +130 -0
  66. package/dist/workflows/loop/actions/pi-review.js +267 -0
  67. package/dist/workflows/loop/actions/shared.js +157 -0
  68. package/dist/workflows/loop/actions/shell-verify.js +82 -0
  69. package/dist/workflows/loop/actions/types.js +1 -0
  70. package/dist/workflows/loop/actions/workflow-action.js +255 -0
  71. package/dist/workflows/loop/actions.js +55 -1212
  72. package/dist/workflows/loop/closeout.js +5 -4
  73. package/dist/workflows/loop/context.js +2 -3
  74. package/dist/workflows/loop/events.js +3 -2
  75. package/dist/workflows/loop/policy/auto-policy.js +104 -0
  76. package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
  77. package/dist/workflows/loop/rounds.js +3 -3
  78. package/dist/workflows/loop/signals.js +4 -7
  79. package/dist/workflows/loop/state.js +11 -11
  80. package/docs/README.md +47 -44
  81. package/docs/agent-dag-recovery-playbook.md +32 -6
  82. package/docs/agent-dag-runner.md +17 -17
  83. package/docs/architecture/runtime-boundaries.md +147 -0
  84. package/docs/cursor-executor-usage.md +5 -5
  85. package/docs/decisions/README.md +2 -2
  86. package/docs/design/README.md +24 -24
  87. package/docs/development-principles.md +50 -50
  88. package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
  89. package/docs/exec-plans/README.md +4 -4
  90. package/docs/exec-plans/active/README.md +10 -5
  91. package/docs/exec-plans/completed/README.md +9 -5
  92. package/docs/feature-workflow.md +111 -109
  93. package/docs/harness-methodology-verification.md +18 -18
  94. package/docs/loop-agent-harness.md +36 -36
  95. package/docs/production-readiness.md +96 -0
  96. package/docs/progress/README.md +2 -2
  97. package/docs/reports/README.md +4 -2
  98. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
  99. package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
  100. package/docs/templates/agent-dag-report.schema.json +33 -2
  101. package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
  102. package/docs/templates/agent-dag.base.json +195 -195
  103. package/docs/templates/agent-dag.final-verification.json +190 -190
  104. package/docs/templates/agent-dag.schema.json +17 -17
  105. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  106. package/docs/templates/hybrid-dag.json +193 -193
  107. package/docs/templates/production-readiness-checklist.md +57 -0
  108. package/docs/templates/progress-log.md +7 -7
  109. package/docs/templates/project-start-checklist.md +8 -8
  110. package/docs/templates/qa-report.md +17 -11
  111. package/docs/templates/sprint-contract.md +19 -19
  112. package/docs/verification-matrix.md +37 -26
  113. package/examples/example-dag.json +51 -51
  114. package/examples/hybrid-loop-agent-dag.json +194 -194
  115. package/harness.json +5 -5
  116. package/package.json +62 -61
  117. package/skills/ai-engineering-context/SKILL.md +21 -21
  118. package/skills/loop-agent/SKILL.md +56 -171
  119. package/skills/loop-agent/references/README.md +6 -2
  120. package/skills/loop-agent/references/command-reference.md +107 -65
  121. package/skills/loop-agent/references/harness-policy.md +115 -115
  122. package/skills/loop-agent/references/hybrid-dag.md +30 -30
  123. package/skills/loop-agent/references/learned/README.md +13 -13
  124. package/skills/loop-agent/references/long-running-loop.md +59 -0
  125. package/skills/loop-agent/references/model-routing.md +1 -1
  126. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  127. package/skills/loop-agent/references/pi-prompt.md +9 -9
  128. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
  129. package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
  130. package/skills/loop-agent/references/task-workflow.md +19 -19
  131. package/skills/loop-agent/references/verification-and-failure-handling.md +54 -0
  132. package/skills/requesting-code-review/SKILL.md +40 -40
  133. package/skills/requesting-code-review/code-reviewer.md +4 -4
  134. package/skills/systematic-debugging/CREATION-LOG.md +43 -43
  135. package/skills/systematic-debugging/SKILL.md +113 -113
  136. package/skills/systematic-debugging/condition-based-waiting.md +20 -20
  137. package/skills/systematic-debugging/defense-in-depth.md +27 -27
  138. package/skills/systematic-debugging/root-cause-tracing.md +38 -38
  139. package/skills/systematic-debugging/test-academic.md +6 -6
  140. package/skills/systematic-debugging/test-pressure-1.md +6 -6
  141. package/skills/systematic-debugging/test-pressure-2.md +2 -2
  142. package/skills/systematic-debugging/test-pressure-3.md +6 -6
  143. 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
+ }