@tea-agent/loop-agent 0.5.0 → 0.6.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.
Files changed (133) hide show
  1. package/AGENTS.md +142 -142
  2. package/CHANGELOG.md +116 -98
  3. package/README.md +195 -195
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/args.js +9 -1
  7. package/dist/application/dag/run-dag.js +16 -2
  8. package/dist/cli/command-definitions.js +22 -4
  9. package/dist/cli/help.js +3 -2
  10. package/dist/cli/program.js +7 -5
  11. package/dist/commands/import-prd.js +76 -0
  12. package/dist/commands/init.js +467 -457
  13. package/dist/commands/instructions.js +90 -58
  14. package/dist/commands/loop-benchmark.js +11 -11
  15. package/dist/commands/pi-reuse-benchmark.js +16 -16
  16. package/dist/executors/cursor-executor.js +1 -1
  17. package/dist/executors/dag-pi-executor.js +1 -0
  18. package/dist/executors/pi-sdk-executor.js +63 -1
  19. package/dist/shared/preview.js +39 -0
  20. package/dist/task/runtime.js +27 -27
  21. package/dist/task/source-references.js +221 -0
  22. package/dist/worker/cli.js +62 -1
  23. package/dist/worker/loop-agent/loop-agent-client.js +97 -5
  24. package/dist/worker/materialize/harness-task-materializer.js +162 -5
  25. package/dist/worker/observability/event-store.js +82 -0
  26. package/dist/worker/observability/events.js +79 -0
  27. package/dist/worker/observability/progress-composite.js +33 -0
  28. package/dist/worker/observability/read-model.js +1013 -0
  29. package/dist/worker/observability/snapshot-store.js +43 -0
  30. package/dist/worker/observability/types.js +1 -0
  31. package/dist/worker/observe/paths.js +64 -0
  32. package/dist/worker/observe/routes.js +423 -0
  33. package/dist/worker/observe/server.js +61 -0
  34. package/dist/worker/observe/static/app.js +1419 -0
  35. package/dist/worker/observe/static/index.html +63 -0
  36. package/dist/worker/observe/static/styles.css +613 -0
  37. package/dist/worker/pool/failure-routing.js +41 -6
  38. package/dist/worker/pool/run-store.js +50 -0
  39. package/dist/worker/progress-reporter.js +0 -18
  40. package/dist/worker/run-task/run-task.js +327 -92
  41. package/dist/worker/runner/run-ready.js +112 -4
  42. package/dist/workflows/dag/canvas-observer.js +275 -275
  43. package/dist/workflows/dag/event-observer.js +132 -0
  44. package/dist/workflows/dag/init-hybrid.js +146 -13
  45. package/dist/workflows/dag/observer-compose.js +52 -0
  46. package/docs/README.md +74 -72
  47. package/docs/agent-dag-recovery-playbook.md +184 -184
  48. package/docs/agent-dag-runner.md +42 -42
  49. package/docs/architecture/runtime-boundaries.md +162 -147
  50. package/docs/cursor-executor-usage.md +25 -25
  51. package/docs/decisions/README.md +3 -3
  52. package/docs/design/README.md +49 -36
  53. package/docs/development-principles.md +73 -73
  54. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  55. package/docs/exec-plans/README.md +6 -6
  56. package/docs/exec-plans/active/README.md +12 -7
  57. package/docs/exec-plans/completed/README.md +31 -19
  58. package/docs/feature-workflow.md +186 -186
  59. package/docs/harness-methodology-debugging.md +153 -153
  60. package/docs/harness-methodology-tdd.md +130 -130
  61. package/docs/harness-methodology-verification.md +27 -27
  62. package/docs/init-surface.manifest.json +205 -199
  63. package/docs/loop-agent-harness.md +55 -42
  64. package/docs/production-readiness.md +96 -96
  65. package/docs/progress/README.md +3 -3
  66. package/docs/reports/README.md +9 -5
  67. package/docs/skills/README.md +6 -6
  68. package/docs/skills/vetted-skill-registry.md +26 -26
  69. package/docs/templates/adr.md +60 -60
  70. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  71. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  72. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  73. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  74. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  75. package/docs/templates/agent-dag-report.schema.json +454 -454
  76. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  77. package/docs/templates/agent-dag.base.json +195 -195
  78. package/docs/templates/agent-dag.final-verification.json +190 -190
  79. package/docs/templates/agent-dag.schema.json +316 -316
  80. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  81. package/docs/templates/exec-plan.md +64 -64
  82. package/docs/templates/feature-spec.md +53 -53
  83. package/docs/templates/hybrid-dag.json +193 -193
  84. package/docs/templates/init-evolution-review.md +33 -33
  85. package/docs/templates/production-readiness-checklist.md +57 -57
  86. package/docs/templates/progress-log.md +17 -17
  87. package/docs/templates/project-start-checklist.md +9 -9
  88. package/docs/templates/qa-report.md +48 -48
  89. package/docs/templates/sprint-contract.md +29 -29
  90. package/docs/templates/worker-dogfood-evidence.md +52 -0
  91. package/docs/templates/worker-dogfood-setup.md +48 -0
  92. package/docs/verification-matrix.md +41 -41
  93. package/examples/decision-gate-agent-dag.json +123 -123
  94. package/examples/example-dag.json +51 -51
  95. package/examples/hybrid-loop-agent-dag.json +194 -194
  96. package/harness.json +70 -69
  97. package/package.json +66 -66
  98. package/skills/ai-engineering-context/SKILL.md +48 -48
  99. package/skills/code-review-core/SKILL.md +20 -20
  100. package/skills/codebase-scout/SKILL.md +19 -19
  101. package/skills/init-capability-evolution/SKILL.md +69 -69
  102. package/skills/loop-agent/SKILL.md +149 -147
  103. package/skills/loop-agent/references/README.md +67 -67
  104. package/skills/loop-agent/references/command-reference.md +412 -403
  105. package/skills/loop-agent/references/harness-policy.md +263 -259
  106. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  107. package/skills/loop-agent/references/learned/README.md +21 -21
  108. package/skills/loop-agent/references/long-running-loop.md +59 -59
  109. package/skills/loop-agent/references/model-routing.md +36 -36
  110. package/skills/loop-agent/references/multi-worktree.md +54 -54
  111. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  112. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  113. package/skills/loop-agent/references/pi-prompt.md +23 -23
  114. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  115. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  116. package/skills/loop-agent/references/task-workflow.md +89 -84
  117. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  118. package/skills/requesting-code-review/SKILL.md +101 -101
  119. package/skills/requesting-code-review/code-reviewer.md +168 -168
  120. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  121. package/skills/systematic-debugging/SKILL.md +296 -296
  122. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  123. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  124. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  125. package/skills/systematic-debugging/find-polluter.sh +63 -63
  126. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  127. package/skills/systematic-debugging/test-academic.md +14 -14
  128. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  129. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  130. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  131. package/skills/test-driven-development/SKILL.md +20 -20
  132. package/skills/verification-before-completion/SKILL.md +154 -154
  133. package/skills/webapp-testing/SKILL.md +19 -19
@@ -2,10 +2,10 @@ import { createHash } from "node:crypto";
2
2
  import { readFile, writeFile, mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import YAML from "yaml";
5
- import { deriveFailureRoute } from "../pool/failure-routing.js";
5
+ import { deriveFailureRoute, deriveFailureRouteFromError, } from "../pool/failure-routing.js";
6
6
  import { findRunByWorkerRunId, getTaskPoolRoot, readAllTaskPoolStates, recordTaskPoolRun, writeTaskPoolState, } from "../pool/run-store.js";
7
7
  import { runTaskSpec, } from "../run-task/run-task.js";
8
- import { formatDuration, noopProgressReporter } from "../progress-reporter.js";
8
+ import { formatDuration, noopProgressReporter, } from "../progress-reporter.js";
9
9
  import { computeReadyQueue } from "../task-graph/ready-queue.js";
10
10
  import { taskGraphSpecSchema } from "../task-graph/task-graph-schema.js";
11
11
  import { taskSpecSchema } from "../task-spec/schema.js";
@@ -23,18 +23,43 @@ export async function runReadyTasks(options) {
23
23
  const total = limitedTaskIds.length;
24
24
  let index = 0;
25
25
  const batchStartedAt = Date.now();
26
+ emit(progress, {
27
+ type: "readyQueue.computed",
28
+ source: "worker",
29
+ label: `ready queue: ${total} task${total === 1 ? "" : "s"} ready`,
30
+ batchRunId,
31
+ });
26
32
  progress.batch(`batch ${batchRunId}: feature=${graph.feature_id}, ${total} task${total === 1 ? "" : "s"} ready → starting`);
33
+ emit(progress, {
34
+ type: "batch.started",
35
+ source: "worker",
36
+ label: `batch ${batchRunId}: feature=${graph.feature_id}, ${total} task${total === 1 ? "" : "s"} ready`,
37
+ batchRunId,
38
+ status: "running",
39
+ });
27
40
  for (const taskId of limitedTaskIds) {
28
41
  index += 1;
29
42
  const node = graph.nodes.find((candidate) => candidate.id === taskId);
30
43
  const taskSpecPath = path.join(options.featureDir, "tasks", node?.task ?? `${taskId}.yaml`);
31
44
  const taskSpec = await loadTaskSpec(taskSpecPath);
32
- const workerRunId = options.workerRunIdForTask?.(taskId, taskSpec) ??
33
- buildStableWorkerRunId(taskId, taskSpec, now);
45
+ const retryOfWorkerRunId = states[taskId]?.retryOfWorkerRunId;
46
+ const workerRunId = retryOfWorkerRunId
47
+ ? buildRetryWorkerRunId(taskId, retryOfWorkerRunId, now)
48
+ : options.workerRunIdForTask?.(taskId, taskSpec) ??
49
+ buildStableWorkerRunId(taskId, taskSpec, now);
34
50
  if (workerRunId) {
35
51
  const existing = await findRunByWorkerRunId(options.repoRoot, workerRunId);
36
52
  if (existing) {
37
53
  progress.task(`task ${index}/${total} ${taskId}: reuse existing run ${workerRunId}`);
54
+ emit(progress, {
55
+ type: "task.reused",
56
+ source: "worker",
57
+ label: `task ${taskId}: reuse existing run ${workerRunId}`,
58
+ batchRunId,
59
+ taskId,
60
+ workerRunId,
61
+ status: "reused",
62
+ });
38
63
  tasks.push({
39
64
  taskId,
40
65
  workerRunId,
@@ -46,6 +71,15 @@ export async function runReadyTasks(options) {
46
71
  }
47
72
  const taskStartedAt = Date.now();
48
73
  progress.task(`task ${index}/${total} ${taskId} "${taskSpec.title}" (${workerRunId})`);
74
+ emit(progress, {
75
+ type: "task.started",
76
+ source: "worker",
77
+ label: `task ${index}/${total} ${taskId}`,
78
+ batchRunId,
79
+ taskId,
80
+ workerRunId,
81
+ status: "running",
82
+ });
49
83
  progress.step(`preflight → materialize → dag-run-task → validate → run-dag → report`);
50
84
  let result;
51
85
  try {
@@ -54,6 +88,16 @@ export async function runReadyTasks(options) {
54
88
  status: "Running",
55
89
  updatedAt: new Date().toISOString(),
56
90
  workerRunId,
91
+ ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
92
+ });
93
+ emit(progress, {
94
+ type: "state.updated",
95
+ source: "worker",
96
+ label: `task pool state: ${taskId} → Running`,
97
+ batchRunId,
98
+ taskId,
99
+ workerRunId,
100
+ status: "running",
57
101
  });
58
102
  result = await runner({
59
103
  repoRoot: options.repoRoot,
@@ -72,7 +116,18 @@ export async function runReadyTasks(options) {
72
116
  }
73
117
  catch (error) {
74
118
  const message = errorMessage(error);
119
+ const failure = deriveFailureRouteFromError(message, taskId);
75
120
  progress.note(`task ${taskId}: ERROR ${message}`);
121
+ emit(progress, {
122
+ type: "task.finished",
123
+ source: "worker",
124
+ label: `task ${taskId} run-error`,
125
+ batchRunId,
126
+ taskId,
127
+ workerRunId,
128
+ status: "failed",
129
+ message,
130
+ });
76
131
  try {
77
132
  await recordTaskPoolRun({
78
133
  repoRoot: options.repoRoot,
@@ -85,6 +140,8 @@ export async function runReadyTasks(options) {
85
140
  status: "run-error",
86
141
  recordedAt: new Date().toISOString(),
87
142
  error: message,
143
+ failure,
144
+ ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
88
145
  },
89
146
  });
90
147
  }
@@ -106,6 +163,29 @@ export async function runReadyTasks(options) {
106
163
  continue;
107
164
  }
108
165
  const failure = deriveFailureRoute(result);
166
+ emit(progress, {
167
+ type: "task.finished",
168
+ source: "worker",
169
+ label: `task ${taskId} ${result.status}`,
170
+ batchRunId,
171
+ taskId,
172
+ workerRunId: result.workerRunId,
173
+ status: result.status,
174
+ durationMs: Date.now() - taskStartedAt,
175
+ });
176
+ if (failure) {
177
+ emit(progress, {
178
+ type: "failure.routed",
179
+ source: "worker",
180
+ label: `failure routed: ${failure.category}`,
181
+ batchRunId,
182
+ taskId,
183
+ workerRunId: result.workerRunId,
184
+ failureCategory: failure.category,
185
+ recommendedFollowUp: failure.recommendedFollowUpKind,
186
+ status: "failed",
187
+ });
188
+ }
109
189
  if (result.status === "succeeded") {
110
190
  progress.step(`✓ ${taskId} succeeded in ${formatDuration(Date.now() - taskStartedAt)} (promoted + closed out)`);
111
191
  }
@@ -124,6 +204,7 @@ export async function runReadyTasks(options) {
124
204
  dagPath: result.dagPath,
125
205
  recordedAt: new Date().toISOString(),
126
206
  ...(failure ? { failure } : {}),
207
+ ...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
127
208
  ...(result.failureArtifacts ? { failureArtifacts: result.failureArtifacts } : {}),
128
209
  };
129
210
  try {
@@ -160,8 +241,22 @@ export async function runReadyTasks(options) {
160
241
  await mkdir(path.dirname(batchRunPath), { recursive: true });
161
242
  await writeFile(batchRunPath, `${JSON.stringify(output, null, 2)}\n`, "utf-8");
162
243
  progress.batch(`batch ${batchRunId} ${output.status}: ${summary.succeeded} succeeded, ${summary.failed} failed, ${summary.reused} reused in ${formatDuration(Date.now() - batchStartedAt)} → ${batchRunPath}`);
244
+ emit(progress, {
245
+ type: "batch.finished",
246
+ source: "worker",
247
+ label: `batch ${batchRunId} ${output.status}`,
248
+ batchRunId,
249
+ status: output.status === "completed" ? "succeeded" : "failed",
250
+ durationMs: Date.now() - batchStartedAt,
251
+ });
163
252
  return output;
164
253
  }
254
+ function emit(progress, input) {
255
+ const structured = progress;
256
+ if (typeof structured.event !== "function")
257
+ return;
258
+ void structured.event(input).catch(() => { });
259
+ }
165
260
  export function buildBatchRunId(now) {
166
261
  return `batch-${now.toISOString().replace(/[-:.]/g, "").slice(0, 15)}`;
167
262
  }
@@ -177,6 +272,19 @@ export function buildStableWorkerRunId(taskId, taskSpec, now) {
177
272
  .slice(0, 10);
178
273
  return `wr-${date}-${slug}-${hash}`;
179
274
  }
275
+ /** A retry is a distinct execution attempt and must not collide with failed evidence. */
276
+ export function buildRetryWorkerRunId(taskId, previousWorkerRunId, now) {
277
+ const date = now.toISOString().slice(0, 10).replace(/-/g, "");
278
+ const slug = taskId
279
+ .toLowerCase()
280
+ .replace(/[^a-z0-9]+/g, "-")
281
+ .replace(/^-+|-+$/g, "");
282
+ const hash = createHash("sha256")
283
+ .update(`${previousWorkerRunId}:${now.toISOString()}`)
284
+ .digest("hex")
285
+ .slice(0, 10);
286
+ return `wr-${date}-${slug}-retry-${hash}`;
287
+ }
180
288
  async function loadTaskGraph(featureDir) {
181
289
  const graphPath = path.join(featureDir, "tasks", "task-graph.yaml");
182
290
  return taskGraphSpecSchema.parse(YAML.parse(await readFile(graphPath, "utf-8")));