@tea-agent/loop-agent 0.2.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.
- package/AGENTS.md +62 -45
- package/CHANGELOG.md +59 -43
- package/README.md +180 -179
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/generate-task-dag.js +3 -3
- package/dist/application/dag/run-dag.js +14 -1
- package/dist/application/dag/validate-dag.js +1 -0
- package/dist/commands/init.js +482 -459
- package/dist/workflows/dag/failure-routing.js +82 -0
- package/dist/workflows/dag/lifecycle.js +95 -3
- package/dist/workflows/dag/report.js +73 -1
- package/docs/README.md +47 -45
- package/docs/agent-dag-recovery-playbook.md +32 -6
- package/docs/agent-dag-runner.md +17 -17
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/cursor-executor-usage.md +5 -5
- package/docs/decisions/README.md +2 -2
- package/docs/design/README.md +24 -24
- package/docs/development-principles.md +50 -50
- package/docs/dynamic-workflow-dag-engine-roadmap.md +6 -6
- package/docs/exec-plans/README.md +4 -4
- package/docs/exec-plans/active/README.md +10 -9
- package/docs/exec-plans/completed/README.md +8 -8
- package/docs/feature-workflow.md +111 -109
- package/docs/harness-methodology-verification.md +18 -18
- package/docs/loop-agent-harness.md +36 -36
- package/docs/production-readiness.md +96 -0
- package/docs/progress/README.md +2 -2
- package/docs/reports/README.md +4 -2
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +1 -1
- package/docs/templates/agent-dag-process-supervisor.prompt.md +2 -2
- package/docs/templates/agent-dag-report.schema.json +33 -2
- package/docs/templates/agent-dag-review-verdict.prompt.md +1 -1
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +17 -17
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +7 -7
- package/docs/templates/project-start-checklist.md +8 -8
- package/docs/templates/qa-report.md +17 -11
- package/docs/templates/sprint-contract.md +19 -19
- package/docs/verification-matrix.md +37 -26
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +5 -5
- package/package.json +62 -62
- package/skills/loop-agent/SKILL.md +35 -35
- package/skills/loop-agent/references/command-reference.md +107 -65
- package/skills/loop-agent/references/harness-policy.md +30 -30
- package/skills/loop-agent/references/hybrid-dag.md +30 -30
- package/skills/loop-agent/references/model-routing.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
- package/skills/loop-agent/references/pi-prompt.md +9 -9
- package/skills/loop-agent/references/post-implementation-and-patterns.md +7 -7
- package/skills/loop-agent/references/task-workflow.md +19 -19
- package/skills/loop-agent/references/verification-and-failure-handling.md +36 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const dagProductLineFailureCategoryValues = [
|
|
2
|
+
"SpecUnclear",
|
|
3
|
+
"ContractMismatch",
|
|
4
|
+
"ProductBug",
|
|
5
|
+
"TestBug",
|
|
6
|
+
"EnvFailure",
|
|
7
|
+
"FlakyTest",
|
|
8
|
+
"RiskyChange",
|
|
9
|
+
"DependencyFailure",
|
|
10
|
+
"NeedsHuman",
|
|
11
|
+
"Unknown",
|
|
12
|
+
];
|
|
13
|
+
const FOLLOW_UP_BY_PRODUCT_LINE = {
|
|
14
|
+
SpecUnclear: "spec-clarification",
|
|
15
|
+
ContractMismatch: "architecture-contract-fix",
|
|
16
|
+
ProductBug: "dev-fix",
|
|
17
|
+
TestBug: "qa-fix-test",
|
|
18
|
+
EnvFailure: "env-fix or retry verify",
|
|
19
|
+
FlakyTest: "flaky-test-analysis",
|
|
20
|
+
RiskyChange: "human-review or architecture-review",
|
|
21
|
+
DependencyFailure: "unblock dependency",
|
|
22
|
+
NeedsHuman: "human-review",
|
|
23
|
+
Unknown: "human triage",
|
|
24
|
+
};
|
|
25
|
+
function routeToProductLine(input) {
|
|
26
|
+
const normalized = input.normalizedFailureCategory;
|
|
27
|
+
if (!normalized || normalized === "success")
|
|
28
|
+
return undefined;
|
|
29
|
+
const raw = input.rawFailureCategory?.toLowerCase() ?? "";
|
|
30
|
+
const nodeId = input.nodeId?.toLowerCase() ?? "";
|
|
31
|
+
switch (normalized) {
|
|
32
|
+
case "write-guard":
|
|
33
|
+
return "RiskyChange";
|
|
34
|
+
case "auth":
|
|
35
|
+
case "executor":
|
|
36
|
+
case "timeout":
|
|
37
|
+
return "EnvFailure";
|
|
38
|
+
case "human-required":
|
|
39
|
+
case "human-rejected":
|
|
40
|
+
case "decision-envelope":
|
|
41
|
+
return "NeedsHuman";
|
|
42
|
+
case "skipped":
|
|
43
|
+
return "DependencyFailure";
|
|
44
|
+
case "shell-command":
|
|
45
|
+
if (raw.includes("flaky"))
|
|
46
|
+
return "FlakyTest";
|
|
47
|
+
if (nodeId.includes("test") || raw.includes("test-bug")) {
|
|
48
|
+
return "TestBug";
|
|
49
|
+
}
|
|
50
|
+
return "ProductBug";
|
|
51
|
+
case "static-error":
|
|
52
|
+
return "SpecUnclear";
|
|
53
|
+
case "validation":
|
|
54
|
+
if (raw.includes("path") ||
|
|
55
|
+
raw.includes("write") ||
|
|
56
|
+
raw.includes("forbidden")) {
|
|
57
|
+
return "RiskyChange";
|
|
58
|
+
}
|
|
59
|
+
if (raw.includes("test-bug") || nodeId.includes("test")) {
|
|
60
|
+
return "TestBug";
|
|
61
|
+
}
|
|
62
|
+
if (raw.includes("test-failure") || raw.includes("verify-failure")) {
|
|
63
|
+
return "ProductBug";
|
|
64
|
+
}
|
|
65
|
+
return "SpecUnclear";
|
|
66
|
+
case "unknown":
|
|
67
|
+
return "Unknown";
|
|
68
|
+
default: {
|
|
69
|
+
const exhaustive = normalized;
|
|
70
|
+
return exhaustive;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function routeDagFailure(input) {
|
|
75
|
+
const productLineFailureCategory = routeToProductLine(input);
|
|
76
|
+
if (!productLineFailureCategory)
|
|
77
|
+
return {};
|
|
78
|
+
return {
|
|
79
|
+
productLineFailureCategory,
|
|
80
|
+
recommendedFollowUp: FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -2,6 +2,8 @@ import { access, mkdir, readFile, readdir, rename, } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { writeJsonAtomic, } from "../../infrastructure/harness/atomic-write.js";
|
|
4
4
|
import { parseDagSpec } from "./types.js";
|
|
5
|
+
import { normalizeDagFailureCategory, } from "./failure-category.js";
|
|
6
|
+
import { routeDagFailure } from "./failure-routing.js";
|
|
5
7
|
const DAG_LIFECYCLE_SCAN_ORDER = [
|
|
6
8
|
"paused",
|
|
7
9
|
"active",
|
|
@@ -470,16 +472,106 @@ export async function runDagStatus(repoRoot, rawArgs) {
|
|
|
470
472
|
console.log(JSON.stringify(report, null, 2));
|
|
471
473
|
}
|
|
472
474
|
export function parseDagDoctorArgs(args) {
|
|
473
|
-
|
|
475
|
+
let runId;
|
|
476
|
+
let markdown = false;
|
|
477
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
478
|
+
const arg = args[i];
|
|
479
|
+
if (arg === "--run-id") {
|
|
480
|
+
runId = args[++i];
|
|
481
|
+
if (!runId || runId.startsWith("-")) {
|
|
482
|
+
throw new Error("dag doctor --run-id requires a value");
|
|
483
|
+
}
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (arg.startsWith("--run-id=")) {
|
|
487
|
+
runId = arg.slice("--run-id=".length);
|
|
488
|
+
if (!runId)
|
|
489
|
+
throw new Error("dag doctor --run-id requires a value");
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (arg === "--markdown") {
|
|
493
|
+
markdown = true;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
474
496
|
if (arg.startsWith("-")) {
|
|
475
497
|
throw new Error(`unknown dag doctor flag: ${arg}`);
|
|
476
498
|
}
|
|
477
499
|
throw new Error(`unexpected positional argument: ${arg}`);
|
|
478
500
|
}
|
|
479
|
-
return {};
|
|
501
|
+
return { runId, markdown };
|
|
502
|
+
}
|
|
503
|
+
function findDoctorFailureNode(state) {
|
|
504
|
+
if (state.pausedByNodeId) {
|
|
505
|
+
const node = state.nodes[state.pausedByNodeId];
|
|
506
|
+
return {
|
|
507
|
+
nodeId: state.pausedByNodeId,
|
|
508
|
+
status: node?.status,
|
|
509
|
+
rawFailureCategory: node?.failureCategory,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
|
|
513
|
+
const skippedEntry = Object.entries(state.nodes).find(([, node]) => node.status === "SKIPPED");
|
|
514
|
+
const selected = errorEntry ?? skippedEntry;
|
|
515
|
+
if (!selected) {
|
|
516
|
+
return {
|
|
517
|
+
rawFailureCategory: state.failureCategory,
|
|
518
|
+
status: state.status,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
nodeId: selected[0],
|
|
523
|
+
status: selected[1].status,
|
|
524
|
+
rawFailureCategory: selected[1].failureCategory,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
async function formatDagDoctorMarkdown(repoRoot, runId) {
|
|
528
|
+
const located = await locateDagRun(repoRoot, runId);
|
|
529
|
+
if (!located) {
|
|
530
|
+
throw new Error(`dag run not found: ${runId}`);
|
|
531
|
+
}
|
|
532
|
+
const state = await readDagRunState(located.runDir);
|
|
533
|
+
const summary = await buildDagStatusReport(repoRoot, runId);
|
|
534
|
+
const failure = findDoctorFailureNode(state);
|
|
535
|
+
const rawFailureCategory = failure.rawFailureCategory ??
|
|
536
|
+
(state.status === "paused" ? "human-required" : state.failureCategory);
|
|
537
|
+
const failureStatus = state.status === "paused" ? "paused" : failure.status;
|
|
538
|
+
const normalizedCategory = normalizeDagFailureCategory(rawFailureCategory, failureStatus ?? state.status);
|
|
539
|
+
const routing = routeDagFailure({
|
|
540
|
+
rawFailureCategory,
|
|
541
|
+
normalizedFailureCategory: normalizedCategory,
|
|
542
|
+
nodeId: failure.nodeId,
|
|
543
|
+
});
|
|
544
|
+
const evidence = failure.nodeId
|
|
545
|
+
? path.join(located.runDir, failure.nodeId, "result.summary.md")
|
|
546
|
+
: path.join(located.runDir, "state.json");
|
|
547
|
+
const nextCommand = summary.nextRecommendedAction ||
|
|
548
|
+
routing.recommendedFollowUp ||
|
|
549
|
+
"Inspect run facts and choose a recovery path.";
|
|
550
|
+
return [
|
|
551
|
+
"## Diagnosis",
|
|
552
|
+
"",
|
|
553
|
+
`- run id: ${runId}`,
|
|
554
|
+
`- lifecycle: ${located.lifecycle}`,
|
|
555
|
+
`- failed node: ${failure.nodeId ?? "-"}`,
|
|
556
|
+
`- raw failure: ${rawFailureCategory ?? "-"}`,
|
|
557
|
+
`- normalized category: ${normalizedCategory}`,
|
|
558
|
+
`- product-line category: ${routing.productLineFailureCategory ?? "-"}`,
|
|
559
|
+
`- recommended follow-up: ${routing.recommendedFollowUp ?? "-"}`,
|
|
560
|
+
`- evidence: ${evidence}`,
|
|
561
|
+
`- next command: ${nextCommand}`,
|
|
562
|
+
"",
|
|
563
|
+
].join("\n");
|
|
480
564
|
}
|
|
481
565
|
export async function runDagDoctor(repoRoot, rawArgs) {
|
|
482
|
-
parseDagDoctorArgs(rawArgs);
|
|
566
|
+
const parsed = parseDagDoctorArgs(rawArgs);
|
|
567
|
+
if (parsed.runId) {
|
|
568
|
+
if (parsed.markdown) {
|
|
569
|
+
console.log(await formatDagDoctorMarkdown(repoRoot, parsed.runId));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
console.log(JSON.stringify(await buildDagStatusReport(repoRoot, parsed.runId), null, 2));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
483
575
|
const report = await buildDagDoctorReport(repoRoot);
|
|
484
576
|
console.log(JSON.stringify(report, null, 2));
|
|
485
577
|
}
|
|
@@ -7,6 +7,7 @@ import { repairArtifactSchema } from "./repair-artifact.js";
|
|
|
7
7
|
import { dagRunDirExists, getDagRunDir, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
|
|
8
8
|
export const DAG_CLOSEOUT_DRAFT_DISCLAIMER = "> **Advisory only.** Derived from completed run facts. Canonical source remains `dag report --json` and `.harness/dag-runs/completed/<run-id>/`. Do not treat this draft as authoritative.";
|
|
9
9
|
import { dagNormalizedFailureCategorySchema, normalizeDagFailureCategory, } from "./failure-category.js";
|
|
10
|
+
import { dagProductLineFailureCategoryValues, routeDagFailure, } from "./failure-routing.js";
|
|
10
11
|
import { DAG_RECOVERY_ACTIONS, planDagRecovery, } from "./recovery-recommendation.js";
|
|
11
12
|
import { dagNodeExecutorSchema, dagNodeStatusSchema, LEGACY_TOP_LEVEL_MODELS_ERROR, parseDagSpec, resolveModelForTask, } from "./types.js";
|
|
12
13
|
export const DAG_REPORT_SCHEMA_VERSION = 1;
|
|
@@ -21,6 +22,7 @@ const dagRunStatusSchema = z.enum([
|
|
|
21
22
|
"paused",
|
|
22
23
|
]);
|
|
23
24
|
const dagRecoveryActionSchema = z.enum(DAG_RECOVERY_ACTIONS);
|
|
25
|
+
const dagProductLineFailureCategorySchema = z.enum(dagProductLineFailureCategoryValues);
|
|
24
26
|
const dagArtifactRefSchema = z
|
|
25
27
|
.object({
|
|
26
28
|
path: z.string(),
|
|
@@ -44,6 +46,8 @@ const dagReportPrimaryFailureSchema = z
|
|
|
44
46
|
nodeStatus: dagNodeStatusSchema.optional(),
|
|
45
47
|
failureCategory: z.string().optional(),
|
|
46
48
|
normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
|
|
49
|
+
productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
|
|
50
|
+
recommendedFollowUp: z.string().optional(),
|
|
47
51
|
})
|
|
48
52
|
.strict();
|
|
49
53
|
const dagReportDownstreamSkippedNodeSchema = z
|
|
@@ -51,6 +55,8 @@ const dagReportDownstreamSkippedNodeSchema = z
|
|
|
51
55
|
nodeId: z.string(),
|
|
52
56
|
failureCategory: z.string().optional(),
|
|
53
57
|
normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
|
|
58
|
+
productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
|
|
59
|
+
recommendedFollowUp: z.string().optional(),
|
|
54
60
|
})
|
|
55
61
|
.strict();
|
|
56
62
|
const dagConvergencePassArtifactRefSchema = z
|
|
@@ -130,6 +136,8 @@ const dagNodeReportRowSchema = z
|
|
|
130
136
|
tokensUsed: z.number().nonnegative().optional(),
|
|
131
137
|
failureCategory: z.string().optional(),
|
|
132
138
|
normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
|
|
139
|
+
productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
|
|
140
|
+
recommendedFollowUp: z.string().optional(),
|
|
133
141
|
recoveryRecommendation: dagRecoveryRecommendationSchema.optional(),
|
|
134
142
|
backend: z.enum(["sdk", "cli"]).optional(),
|
|
135
143
|
sdkAttempted: z.boolean().optional(),
|
|
@@ -150,6 +158,8 @@ const dagRunReportEntrySchema = z
|
|
|
150
158
|
finishedAt: z.string().optional(),
|
|
151
159
|
failureCategory: z.string().optional(),
|
|
152
160
|
normalizedFailureCategory: dagNormalizedFailureCategorySchema.optional(),
|
|
161
|
+
productLineFailureCategory: dagProductLineFailureCategorySchema.optional(),
|
|
162
|
+
recommendedFollowUp: z.string().optional(),
|
|
153
163
|
recoveryRecommendation: dagRecoveryRecommendationSchema.optional(),
|
|
154
164
|
primaryFailure: dagReportPrimaryFailureSchema,
|
|
155
165
|
primaryRecovery: dagRecoveryRecommendationSchema,
|
|
@@ -250,12 +260,18 @@ function buildPrimaryFailure(run, primaryNode) {
|
|
|
250
260
|
nodeStatus: primaryNode.status,
|
|
251
261
|
failureCategory: primaryNode.failureCategory,
|
|
252
262
|
normalizedFailureCategory: primaryNode.normalizedFailureCategory,
|
|
263
|
+
productLineFailureCategory: primaryNode.productLineFailureCategory,
|
|
264
|
+
recommendedFollowUp: primaryNode.recommendedFollowUp,
|
|
253
265
|
};
|
|
254
266
|
}
|
|
255
267
|
return {
|
|
256
268
|
scope: "run",
|
|
257
269
|
failureCategory: run.failureCategory,
|
|
258
270
|
normalizedFailureCategory: run.normalizedFailureCategory,
|
|
271
|
+
...routeDagFailure({
|
|
272
|
+
rawFailureCategory: run.failureCategory,
|
|
273
|
+
normalizedFailureCategory: run.normalizedFailureCategory,
|
|
274
|
+
}),
|
|
259
275
|
};
|
|
260
276
|
}
|
|
261
277
|
function resolvePrimaryRecovery(run, primaryNode) {
|
|
@@ -302,6 +318,8 @@ function collectDownstreamSkippedNodes(nodes, spec, primaryNode) {
|
|
|
302
318
|
nodeId: node.nodeId,
|
|
303
319
|
failureCategory: node.failureCategory,
|
|
304
320
|
normalizedFailureCategory: node.normalizedFailureCategory,
|
|
321
|
+
productLineFailureCategory: node.productLineFailureCategory,
|
|
322
|
+
recommendedFollowUp: node.recommendedFollowUp,
|
|
305
323
|
}));
|
|
306
324
|
}
|
|
307
325
|
function isFailedReportRun(run) {
|
|
@@ -342,6 +360,12 @@ export async function buildDagRunReportEntry(input) {
|
|
|
342
360
|
continue;
|
|
343
361
|
const task = tasks.get(nodeId);
|
|
344
362
|
const normalizedFailureCategory = normalizeDagFailureCategory(node.failureCategory, node.status);
|
|
363
|
+
const failureRouting = routeDagFailure({
|
|
364
|
+
rawFailureCategory: node.failureCategory,
|
|
365
|
+
normalizedFailureCategory,
|
|
366
|
+
nodeId,
|
|
367
|
+
executor: node.executor,
|
|
368
|
+
});
|
|
345
369
|
nodes.push({
|
|
346
370
|
nodeId,
|
|
347
371
|
rank,
|
|
@@ -355,6 +379,7 @@ export async function buildDagRunReportEntry(input) {
|
|
|
355
379
|
tokensUsed: node.tokensUsed,
|
|
356
380
|
failureCategory: node.failureCategory,
|
|
357
381
|
normalizedFailureCategory,
|
|
382
|
+
...failureRouting,
|
|
358
383
|
recoveryRecommendation: planDagRecovery({
|
|
359
384
|
status: node.status,
|
|
360
385
|
normalizedFailureCategory,
|
|
@@ -406,6 +431,11 @@ export async function buildDagRunReportEntry(input) {
|
|
|
406
431
|
lifecycle: input.lifecycle,
|
|
407
432
|
runStatus: input.state.status,
|
|
408
433
|
});
|
|
434
|
+
const runFailureRouting = routeDagFailure({
|
|
435
|
+
rawFailureCategory: runRecoverySource.failureCategory,
|
|
436
|
+
normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
|
|
437
|
+
nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
|
|
438
|
+
});
|
|
409
439
|
const partialEntry = {
|
|
410
440
|
runId: input.state.runId,
|
|
411
441
|
title: input.state.title,
|
|
@@ -416,6 +446,7 @@ export async function buildDagRunReportEntry(input) {
|
|
|
416
446
|
finishedAt: input.state.finishedAt,
|
|
417
447
|
failureCategory: input.state.failureCategory,
|
|
418
448
|
normalizedFailureCategory,
|
|
449
|
+
...runFailureRouting,
|
|
419
450
|
recoveryRecommendation,
|
|
420
451
|
pausedByNodeId: input.state.pausedByNodeId,
|
|
421
452
|
pauseReason: input.state.pauseReason,
|
|
@@ -638,11 +669,15 @@ function formatPrimaryFailureSection(run) {
|
|
|
638
669
|
return [
|
|
639
670
|
`- **Node**: ${failure.nodeId} (${failure.nodeStatus ?? "unknown"})`,
|
|
640
671
|
`- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
|
|
672
|
+
`- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
|
|
673
|
+
`- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
|
|
641
674
|
];
|
|
642
675
|
}
|
|
643
676
|
return [
|
|
644
677
|
`- **Scope**: run`,
|
|
645
678
|
`- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
|
|
679
|
+
`- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
|
|
680
|
+
`- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
|
|
646
681
|
];
|
|
647
682
|
}
|
|
648
683
|
function formatPrimaryRecoverySection(run) {
|
|
@@ -784,7 +819,7 @@ export function formatDagReportHandoffMarkdown(report) {
|
|
|
784
819
|
? `- **Failure (raw/normalized)**: ${node.failureCategory} / ${node.normalizedFailureCategory ?? "-"}`
|
|
785
820
|
: "", `- **Executor**: ${node.executor}${node.backend ? ` (backend: ${node.backend})` : ""}`, `- **Model**: ${node.model ?? "—"}`, `- **Duration**: ${formatDuration(node.durationMs)}`, `- **Tokens**: ${formatTokens(node.tokensUsed)}`, `- **Started**: ${formatTimestamp(node.startedAt)}`, `- **Finished**: ${formatTimestamp(node.finishedAt)}`, "");
|
|
786
821
|
}
|
|
787
|
-
lines.push("## Failures", ...formatFailureSection(run), "", "## Recovery Plan", ...formatRecoveryPlanSection(run), "", "## Artifacts", ...formatArtifactsSection(run), "", "## Suggested Next Action", `- ${suggestedNextAction(run)}`, "");
|
|
822
|
+
lines.push("## Failures", ...formatFailureSection(run), "", "## Recovery Plan", ...formatRecoveryPlanSection(run), "", "## Operator Next Steps", ...formatRecommendedOperatorAction(run), "", "## Artifacts", ...formatArtifactsSection(run), "", "## Suggested Next Action", `- ${suggestedNextAction(run)}`, "");
|
|
788
823
|
return lines
|
|
789
824
|
.filter((line) => line !== undefined)
|
|
790
825
|
.join("\n");
|
|
@@ -869,6 +904,8 @@ function formatCloseoutPrimaryFailureSection(run) {
|
|
|
869
904
|
`- **Scope**: node`,
|
|
870
905
|
`- **Node**: ${failure.nodeId} (${failure.nodeStatus ?? "unknown"})`,
|
|
871
906
|
`- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
|
|
907
|
+
`- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
|
|
908
|
+
`- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
|
|
872
909
|
];
|
|
873
910
|
}
|
|
874
911
|
if (run.primaryRecovery.action === "none" &&
|
|
@@ -879,6 +916,8 @@ function formatCloseoutPrimaryFailureSection(run) {
|
|
|
879
916
|
return [
|
|
880
917
|
`- **Scope**: ${failure.scope}`,
|
|
881
918
|
`- **Category (raw/normalized)**: ${failure.failureCategory ?? "-"} / ${failure.normalizedFailureCategory ?? "-"}`,
|
|
919
|
+
`- **Product-line category**: ${failure.productLineFailureCategory ?? "-"}`,
|
|
920
|
+
`- **Recommended follow-up**: ${failure.recommendedFollowUp ?? "-"}`,
|
|
882
921
|
];
|
|
883
922
|
}
|
|
884
923
|
function formatCloseoutRecoverySection(run) {
|
|
@@ -939,6 +978,39 @@ function formatRemainingRisksSection(run) {
|
|
|
939
978
|
return risks;
|
|
940
979
|
}
|
|
941
980
|
export function formatDagCloseoutDraftMarkdown(run) {
|
|
981
|
+
if (isFailedReportRun(run)) {
|
|
982
|
+
const lines = [
|
|
983
|
+
`# Failure Handoff: ${run.runId}`,
|
|
984
|
+
"",
|
|
985
|
+
DAG_CLOSEOUT_DRAFT_DISCLAIMER,
|
|
986
|
+
"",
|
|
987
|
+
"## What failed",
|
|
988
|
+
...formatCloseoutPrimaryFailureSection(run),
|
|
989
|
+
"",
|
|
990
|
+
"## Evidence",
|
|
991
|
+
...formatCloseoutVerificationEvidence(run),
|
|
992
|
+
"",
|
|
993
|
+
"## Classification",
|
|
994
|
+
`- raw_failure_category: ${run.primaryFailure.failureCategory ?? "-"}`,
|
|
995
|
+
`- dag_normalized_failure_category: ${run.primaryFailure.normalizedFailureCategory ?? "-"}`,
|
|
996
|
+
`- product_line_failure_category: ${run.primaryFailure.productLineFailureCategory ?? "-"}`,
|
|
997
|
+
`- recommended_follow_up: ${run.primaryFailure.recommendedFollowUp ?? "-"}`,
|
|
998
|
+
"",
|
|
999
|
+
"## Recommended follow-up",
|
|
1000
|
+
...formatCloseoutRecoverySection(run),
|
|
1001
|
+
"",
|
|
1002
|
+
"## Safe retry conditions",
|
|
1003
|
+
`- Retry only after completing \`${run.primaryFailure.recommendedFollowUp ?? run.primaryRecovery.action}\` and preserving the original DAG run facts.`,
|
|
1004
|
+
"- Do not rewrite `.harness/dag-runs/completed/**`; create a new run or task artifact for follow-up evidence.",
|
|
1005
|
+
"",
|
|
1006
|
+
"## Human decision needed",
|
|
1007
|
+
run.primaryRecovery.humanRequired
|
|
1008
|
+
? "- Yes. Human review is required before retry or promotion."
|
|
1009
|
+
: "- No required human gate was derived, but review the failure evidence before retry.",
|
|
1010
|
+
"",
|
|
1011
|
+
];
|
|
1012
|
+
return lines.join("\n");
|
|
1013
|
+
}
|
|
942
1014
|
const lines = [
|
|
943
1015
|
`# DAG Closeout Draft: ${run.runId}`,
|
|
944
1016
|
"",
|
package/docs/README.md
CHANGED
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 文档索引
|
|
2
2
|
|
|
3
|
-
`docs/`
|
|
3
|
+
`docs/` 是 loop-agent 的治理根目录,包含工作流规则、方法论、验证规则、执行计划、报告、进度日志、决策记录和可复用模板。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
顶层 `AGENTS.md` 是操作地图。长期知识应落在此处:决策、契约、计划、验证证据、调试笔记和可复用流程规则应记录在 `docs/` 下,而不是只留在聊天里。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 核心文档
|
|
8
8
|
|
|
9
|
-
- `development-principles.md` —
|
|
10
|
-
- `architecture/runtime-boundaries.md` — runtime
|
|
11
|
-
- `feature-workflow.md` —
|
|
12
|
-
- `verification-matrix.md` —
|
|
13
|
-
- `
|
|
14
|
-
- `agent-
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
9
|
+
- `development-principles.md` — 仓库开发原则
|
|
10
|
+
- `architecture/runtime-boundaries.md` — runtime 层边界与依赖方向
|
|
11
|
+
- `feature-workflow.md` — 有边界的功能工作流
|
|
12
|
+
- `verification-matrix.md` — 验证命令选择
|
|
13
|
+
- `production-readiness.md` — Production Readiness v0.1 范围、证据与 DAG hardening 标准
|
|
14
|
+
- `loop-agent-harness.md` — runtime 与 command surface 概览
|
|
15
|
+
- `agent-dag-runner.md` — Agent DAG runner 指南
|
|
16
|
+
- `cursor-executor-usage.md` — Cursor executor 用法
|
|
17
|
+
- `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine 路线图与适配分析
|
|
17
18
|
|
|
18
|
-
##
|
|
19
|
+
## 方法论
|
|
19
20
|
|
|
20
|
-
- `harness-methodology-tdd.md` —
|
|
21
|
-
- `harness-methodology-verification.md` —
|
|
22
|
-
- `harness-methodology-debugging.md` —
|
|
21
|
+
- `harness-methodology-tdd.md` — 行为变更与 bug 修复的 TDD 纪律
|
|
22
|
+
- `harness-methodology-verification.md` — 完成声明前的验证纪律
|
|
23
|
+
- `harness-methodology-debugging.md` — 修复前的系统化调试工作流
|
|
23
24
|
|
|
24
|
-
##
|
|
25
|
+
## 产物目录
|
|
25
26
|
|
|
26
|
-
- `design/README.md` —
|
|
27
|
-
- `exec-plans/active/README.md` —
|
|
28
|
-
- `exec-plans/completed/README.md` —
|
|
29
|
-
- `progress/README.md` —
|
|
30
|
-
- `reports/README.md` —
|
|
31
|
-
- `decisions/README.md` —
|
|
32
|
-
- `templates/` —
|
|
27
|
+
- `design/README.md` — 设计草稿与实现契约
|
|
28
|
+
- `exec-plans/active/README.md` — 进行中的执行计划
|
|
29
|
+
- `exec-plans/completed/README.md` — 已完成的执行计划
|
|
30
|
+
- `progress/README.md` — 进度交接日志
|
|
31
|
+
- `reports/README.md` — 验证与审计报告
|
|
32
|
+
- `decisions/README.md` — 架构决策
|
|
33
|
+
- `templates/` — 可复用的规划、报告与 DAG 模板
|
|
33
34
|
|
|
34
|
-
##
|
|
35
|
+
## 仓库 Skills
|
|
35
36
|
|
|
36
|
-
- `../skills/loop-agent/` — loop-agent
|
|
37
|
-
-
|
|
37
|
+
- `../skills/loop-agent/` — loop-agent 自身的 skill 指令与参考资料
|
|
38
|
+
- 每个额外 skill 在仓库根 `../skills/` 下使用独立子目录;这些本地副本由 DAG 模板引用,维护不依赖外部 agent skill 目录
|
|
38
39
|
|
|
39
|
-
##
|
|
40
|
+
## 模板
|
|
40
41
|
|
|
41
|
-
- `templates/project-start-checklist.md` —
|
|
42
|
-
- `templates/feature-spec.md` —
|
|
43
|
-
- `templates/sprint-contract.md` —
|
|
44
|
-
- `templates/exec-plan.md` —
|
|
45
|
-
- `templates/progress-log.md` —
|
|
46
|
-
- `templates/qa-report.md` —
|
|
47
|
-
- `templates/
|
|
42
|
+
- `templates/project-start-checklist.md` — 开工前检查清单
|
|
43
|
+
- `templates/feature-spec.md` — 有边界的功能规格
|
|
44
|
+
- `templates/sprint-contract.md` — 实现契约与验收标准
|
|
45
|
+
- `templates/exec-plan.md` — 非平凡工作的执行计划
|
|
46
|
+
- `templates/progress-log.md` — 进度与交接日志
|
|
47
|
+
- `templates/qa-report.md` — 验证与 QA 证据
|
|
48
|
+
- `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
|
|
49
|
+
- `templates/adr.md` — 架构决策记录(ADR)
|
|
48
50
|
|
|
49
|
-
##
|
|
51
|
+
## 维护
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
bash scripts/check-repo.sh
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
文档变更后运行:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bash scripts/check-repo.sh
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Windows 上通过 Git Bash 或已配置的兼容 Bash 运行脚本。实际文件操作使用平台原生路径;`/` 仅用于 repo 引用、JSON/Markdown 证据引用和 glob 约定。
|
|
60
|
+
|
|
61
|
+
完整本地门禁:
|
|
60
62
|
|
|
61
63
|
```bash
|
|
62
64
|
bash scripts/ci.sh
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Agent DAG Recovery Playbook
|
|
1
|
+
# Agent DAG Recovery Playbook(恢复手册)
|
|
2
2
|
|
|
3
3
|
> **关联**:[`agent-dag-runner.md`](agent-dag-runner.md)(CLI 与 run 语义)· [`templates/agent-dag-decision-gate.prompt.md`](templates/agent-dag-decision-gate.prompt.md)(Decision Gate 消费 recovery 证据)
|
|
4
4
|
|
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
Agent DAG **recovery planning 是只读、派生、advisory** 的。`dag report` 与 `buildDagDecisionGateEvidence()` 从 `.harness/dag-runs/` 的 canonical facts 聚合 `normalizedFailureCategory` → `recoveryRecommendation`,供人工或 Decision Gate prompt 消费。
|
|
8
8
|
|
|
9
|
+
Production Readiness v0.1 在 normalized DAG category 之上增加 product-line routing。Report 与 doctor 输出应保留 raw DAG fact 并派生,不重写已完成 facts:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
raw_failure_category
|
|
13
|
+
dag_normalized_failure_category
|
|
14
|
+
product_line_failure_category
|
|
15
|
+
recommended_follow_up
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Product-line taxonomy 定义见 `design/state-and-failure-taxonomy.md`。
|
|
19
|
+
|
|
9
20
|
**非目标(本 playbook 不覆盖、runner 不实现):**
|
|
10
21
|
|
|
11
22
|
- 自动 retry / resume 节点执行
|
|
@@ -47,7 +58,7 @@ npm run dev -- dag decision inspect --run-id <run-id> [--node-id <node-id>]
|
|
|
47
58
|
npm run dev -- dag decision validate --run-id <run-id> [--node-id <node-id>]
|
|
48
59
|
```
|
|
49
60
|
|
|
50
|
-
### Paused run
|
|
61
|
+
### Paused run operator 路径
|
|
51
62
|
|
|
52
63
|
1. `dag report --paused-latest --json` 或 `dag doctor` — 定位最新 paused run 与 `primaryRecovery`
|
|
53
64
|
2. `dag status --run-id <id>` — 读 `approvalFlow`、`escalationArtifactPath`、`pendingNodes`
|
|
@@ -79,7 +90,22 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
79
90
|
| `inspect-upstream` | 先查上游失败 | SKIPPED 下游节点 |
|
|
80
91
|
| `unknown` | 未映射类别(不应出现在正常派生路径) | 内部兜底 |
|
|
81
92
|
|
|
82
|
-
##
|
|
93
|
+
## Product-Line Routing v0.1
|
|
94
|
+
|
|
95
|
+
| Product-line category | Default follow-up |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `SpecUnclear` | `spec-clarification` |
|
|
98
|
+
| `ContractMismatch` | `architecture-contract-fix` |
|
|
99
|
+
| `ProductBug` | `dev-fix` |
|
|
100
|
+
| `TestBug` | `qa-fix-test` |
|
|
101
|
+
| `EnvFailure` | `env-fix` 或 retry verify |
|
|
102
|
+
| `FlakyTest` | `flaky-test-analysis` |
|
|
103
|
+
| `RiskyChange` | `human-review` / `architecture-review` |
|
|
104
|
+
| `DependencyFailure` | unblock dependency |
|
|
105
|
+
| `NeedsHuman` | `human-review` |
|
|
106
|
+
| `Unknown` | human triage |
|
|
107
|
+
|
|
108
|
+
## 类别 → 动作 → operator 指引
|
|
83
109
|
|
|
84
110
|
| Normalized category | Recovery action | Operator guidance | Anti-patterns |
|
|
85
111
|
|---------------------|-----------------|-------------------|---------------|
|
|
@@ -107,9 +133,9 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
107
133
|
1. **Primary Failure** — `primaryFailure`(node 或 run scope)
|
|
108
134
|
2. **Recovery Action** — `primaryRecovery`(action、summary、reason、flags、commandHint)
|
|
109
135
|
3. **Blocked Downstream / Skipped Nodes** — `downstreamSkippedNodes`
|
|
110
|
-
4. **Recommended Operator Action** —
|
|
136
|
+
4. **Recommended Operator Action** — 面向 operator 的步骤摘要
|
|
111
137
|
|
|
112
|
-
保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
|
|
138
|
+
保存 handoff 时重定向到平台临时目录或 `docs/reports/`,不要写入 `.harness/dag-runs/`。
|
|
113
139
|
|
|
114
140
|
## Decision Gate 消费约定
|
|
115
141
|
|
|
@@ -123,7 +149,7 @@ Decision Gate prompt 侧:`buildDagDecisionGateEvidence()`(`./src/core/dag-de
|
|
|
123
149
|
|
|
124
150
|
`dag doctor` 与 `dag status` 通过 `detectDagRunHealthIssues()` 检测 lifecycle 不一致,**不** mutate run facts。
|
|
125
151
|
|
|
126
|
-
| Code | 典型场景 |
|
|
152
|
+
| Code | 典型场景 | operator 指引 |
|
|
127
153
|
|------|----------|------------|
|
|
128
154
|
| `terminal-in-active` | run 已完成但 `active/<run-id>/` 残留 | 对照 `completed/` canonical facts;手动 archive 或删除 stale 目录 |
|
|
129
155
|
| `paused-in-active` | pause 后目录未迁至 `paused/` | `dag doctor` 诊断;修复 facts 后再 approve/resume |
|
package/docs/agent-dag-runner.md
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
# Agent DAG Runner
|
|
2
2
|
|
|
3
|
-
Agent DAG
|
|
3
|
+
Agent DAG 是 loop-agent 的声明式编排 runtime。DAG 将工作拆为节点、按序执行 eligible ranks、记录 artifacts,并用 gate 做 review 与验证。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 基本用法
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
|
|
9
|
-
loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
|
|
10
|
-
loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
`<temp-dir>`
|
|
8
|
+
loop-agent dag run-task <task-id> --profile auto --strict-models --output <temp-dir>/<task-id>-dag.json
|
|
9
|
+
loop-agent dag validate --dag <temp-dir>/<task-id>-dag.json --strict-models --strict-governance
|
|
10
|
+
loop-agent run-dag --dag <temp-dir>/<task-id>-dag.json --cwd .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`<temp-dir>` 为平台原生临时目录。Windows 上 `--output`、`--dag`、`--cwd` 的实际值用原生路径。
|
|
14
14
|
|
|
15
15
|
## Executors
|
|
16
16
|
|
|
17
|
-
- `static
|
|
18
|
-
- `shell
|
|
19
|
-
- `pi
|
|
20
|
-
- `cursor
|
|
17
|
+
- `static`:确定性生成的 artifacts 或 notes
|
|
18
|
+
- `shell`:验证与文件系统检查
|
|
19
|
+
- `pi`:规划、review、诊断;节点设 `toolProfile: "write"` 时有界写入
|
|
20
|
+
- `cursor`:显式启用时的可选有界写后端
|
|
21
21
|
|
|
22
22
|
## Skills
|
|
23
23
|
|
|
24
|
-
DAG
|
|
24
|
+
DAG spec 可声明 `defaults.skills`、`skillsByRole` 与节点级 `skills`。Runner 从 `skills/<skill-name>/SKILL.md` 解析本地指令,并在各节点 `skills.json` artifact 中记录解析元数据。
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`loop-agent` skill 位于 `skills/loop-agent/SKILL.md`。遗留根路径 `skill/SKILL.md` 仅为旧 worktree 保留兼容 fallback。
|
|
27
27
|
|
|
28
28
|
## Artifacts
|
|
29
29
|
|
|
30
|
-
DAG artifacts
|
|
30
|
+
DAG artifacts 位于:
|
|
31
31
|
|
|
32
32
|
```text
|
|
33
33
|
.harness/dag-runs/<state>/<run-id>/artifacts/<node-id>/
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
根目录 `artifacts/` 不是有效的默认 DAG artifact 位置。
|
|
37
37
|
|
|
38
38
|
## Shell Gates
|
|
39
39
|
|
|
40
|
-
- `shell.verdictGate`
|
|
40
|
+
- `shell.verdictGate` 从注入的当前 run 目录读取 `$HARNESS_DAG_RUN_DIR/<fromNodeId>.json`;不应自行发现 active run paths。
|
|
@@ -112,7 +112,7 @@ Runner / Loop ──(迁移中)──> 逐步改为仅经 Store / Appli
|
|
|
112
112
|
|
|
113
113
|
`scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors -> commands` import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
|
|
114
114
|
|
|
115
|
-
## Governance
|
|
115
|
+
## Governance 钩子
|
|
116
116
|
|
|
117
117
|
以下脚本由 `scripts/check-repo.sh` 调用(Phase 0 起):
|
|
118
118
|
|