@tea-agent/loop-agent 0.5.0 → 0.7.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 (136) hide show
  1. package/AGENTS.md +142 -142
  2. package/CHANGELOG.md +132 -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/config-types.js +3 -0
  21. package/dist/task/runtime.js +27 -27
  22. package/dist/task/source-references.js +221 -0
  23. package/dist/worker/cli.js +62 -1
  24. package/dist/worker/loop-agent/loop-agent-client.js +97 -5
  25. package/dist/worker/materialize/harness-task-materializer.js +166 -5
  26. package/dist/worker/observability/event-store.js +82 -0
  27. package/dist/worker/observability/events.js +79 -0
  28. package/dist/worker/observability/progress-composite.js +33 -0
  29. package/dist/worker/observability/read-model.js +1013 -0
  30. package/dist/worker/observability/snapshot-store.js +43 -0
  31. package/dist/worker/observability/types.js +1 -0
  32. package/dist/worker/observe/paths.js +64 -0
  33. package/dist/worker/observe/routes.js +423 -0
  34. package/dist/worker/observe/server.js +61 -0
  35. package/dist/worker/observe/static/app.js +1419 -0
  36. package/dist/worker/observe/static/index.html +63 -0
  37. package/dist/worker/observe/static/styles.css +613 -0
  38. package/dist/worker/pool/failure-routing.js +41 -6
  39. package/dist/worker/pool/run-store.js +50 -0
  40. package/dist/worker/progress-reporter.js +0 -18
  41. package/dist/worker/run-task/run-task.js +327 -92
  42. package/dist/worker/runner/run-ready.js +112 -4
  43. package/dist/worker/task-spec/schema.js +2 -1
  44. package/dist/workflows/dag/canvas-observer.js +275 -275
  45. package/dist/workflows/dag/event-observer.js +132 -0
  46. package/dist/workflows/dag/init-hybrid.js +182 -21
  47. package/dist/workflows/dag/observer-compose.js +52 -0
  48. package/docs/README.md +75 -72
  49. package/docs/agent-dag-recovery-playbook.md +184 -184
  50. package/docs/agent-dag-runner.md +42 -42
  51. package/docs/architecture/runtime-boundaries.md +162 -147
  52. package/docs/cursor-executor-usage.md +25 -25
  53. package/docs/decisions/README.md +3 -3
  54. package/docs/design/README.md +49 -36
  55. package/docs/development-principles.md +73 -73
  56. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
  57. package/docs/exec-plans/README.md +6 -6
  58. package/docs/exec-plans/active/README.md +12 -7
  59. package/docs/exec-plans/completed/README.md +32 -19
  60. package/docs/feature-workflow.md +186 -186
  61. package/docs/harness-methodology-debugging.md +153 -153
  62. package/docs/harness-methodology-tdd.md +130 -130
  63. package/docs/harness-methodology-verification.md +27 -27
  64. package/docs/init-surface.manifest.json +208 -199
  65. package/docs/loop-agent-harness.md +55 -42
  66. package/docs/production-readiness.md +96 -96
  67. package/docs/progress/README.md +3 -3
  68. package/docs/reports/README.md +9 -5
  69. package/docs/skills/README.md +6 -6
  70. package/docs/skills/vetted-skill-registry.md +26 -26
  71. package/docs/templates/adr.md +60 -60
  72. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  73. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  74. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  75. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  76. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  77. package/docs/templates/agent-dag-report.schema.json +454 -454
  78. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  79. package/docs/templates/agent-dag.base.json +195 -195
  80. package/docs/templates/agent-dag.final-verification.json +190 -190
  81. package/docs/templates/agent-dag.schema.json +316 -316
  82. package/docs/templates/agent-dag.supervised-implementation.json +500 -500
  83. package/docs/templates/exec-plan.md +64 -64
  84. package/docs/templates/feature-spec.md +53 -53
  85. package/docs/templates/hybrid-dag.json +193 -193
  86. package/docs/templates/init-evolution-review.md +33 -33
  87. package/docs/templates/interactive-ui-round2-experiment.md +66 -0
  88. package/docs/templates/production-readiness-checklist.md +57 -57
  89. package/docs/templates/progress-log.md +17 -17
  90. package/docs/templates/project-start-checklist.md +9 -9
  91. package/docs/templates/qa-report.md +48 -48
  92. package/docs/templates/sprint-contract.md +29 -29
  93. package/docs/templates/worker-dogfood-evidence.md +52 -0
  94. package/docs/templates/worker-dogfood-setup.md +48 -0
  95. package/docs/verification-matrix.md +41 -41
  96. package/examples/decision-gate-agent-dag.json +123 -123
  97. package/examples/example-dag.json +51 -51
  98. package/examples/hybrid-loop-agent-dag.json +194 -194
  99. package/harness.json +70 -69
  100. package/package.json +66 -66
  101. package/skills/ai-engineering-context/SKILL.md +48 -48
  102. package/skills/code-review-core/SKILL.md +20 -20
  103. package/skills/codebase-scout/SKILL.md +19 -19
  104. package/skills/init-capability-evolution/SKILL.md +69 -69
  105. package/skills/loop-agent/SKILL.md +149 -147
  106. package/skills/loop-agent/references/README.md +67 -67
  107. package/skills/loop-agent/references/command-reference.md +412 -403
  108. package/skills/loop-agent/references/harness-policy.md +263 -259
  109. package/skills/loop-agent/references/hybrid-dag.md +216 -216
  110. package/skills/loop-agent/references/learned/README.md +21 -21
  111. package/skills/loop-agent/references/long-running-loop.md +59 -59
  112. package/skills/loop-agent/references/model-routing.md +36 -36
  113. package/skills/loop-agent/references/multi-worktree.md +54 -54
  114. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  115. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  116. package/skills/loop-agent/references/pi-prompt.md +23 -23
  117. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
  118. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  119. package/skills/loop-agent/references/task-workflow.md +89 -84
  120. package/skills/loop-agent/references/verification-and-failure-handling.md +128 -128
  121. package/skills/requesting-code-review/SKILL.md +101 -101
  122. package/skills/requesting-code-review/code-reviewer.md +168 -168
  123. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  124. package/skills/systematic-debugging/SKILL.md +296 -296
  125. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  126. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  127. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  128. package/skills/systematic-debugging/find-polluter.sh +63 -63
  129. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  130. package/skills/systematic-debugging/test-academic.md +14 -14
  131. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  132. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  133. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  134. package/skills/test-driven-development/SKILL.md +20 -20
  135. package/skills/verification-before-completion/SKILL.md +154 -154
  136. 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")));
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { verifyModeSchema, verifyPresetSchema, verifyQuotaSchema, } from "../../task/config-types.js";
2
+ import { taskCapabilitySchema, verifyModeSchema, verifyPresetSchema, verifyQuotaSchema, } from "../../task/config-types.js";
3
3
  export const taskSpecTypeSchema = z.enum([
4
4
  "architecture",
5
5
  "backend-feature",
@@ -42,6 +42,7 @@ export const taskSpecSchema = z
42
42
  type: taskSpecTypeSchema,
43
43
  priority: z.enum(["P0", "P1", "P2", "P3"]).optional().default("P2"),
44
44
  risk_level: taskSpecRiskLevelSchema,
45
+ capabilities: z.array(taskCapabilitySchema).optional(),
45
46
  depends_on: z.array(z.string().min(1)).optional().default([]),
46
47
  source_docs: z
47
48
  .object({