@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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { readFile
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeLoopCloseout } from "../../infrastructure/harness/loop-store.js";
|
|
2
4
|
import { getLoopPaths } from "./paths.js";
|
|
3
5
|
import { readLoopRounds } from "./rounds.js";
|
|
4
6
|
import { readLoopSignals, pendingLoopSignals } from "./signals.js";
|
|
5
7
|
import { loadLoopState } from "./state.js";
|
|
6
8
|
import { loadTaskConfig } from "../../task/runtime.js";
|
|
7
|
-
import { repoRelativePath } from "../../shared/path-refs.js";
|
|
8
9
|
function unique(values) {
|
|
9
10
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
10
11
|
}
|
|
@@ -68,7 +69,7 @@ export async function generateLoopCloseout(repoRoot, taskId) {
|
|
|
68
69
|
: [`Loop status is ${state.status}; treat this closeout as partial.`]),
|
|
69
70
|
]);
|
|
70
71
|
const recordedAt = new Date().toISOString();
|
|
71
|
-
const relativeCloseoutPath =
|
|
72
|
+
const relativeCloseoutPath = path.relative(repoRoot, paths.closeoutPath);
|
|
72
73
|
const record = {
|
|
73
74
|
schemaVersion: 1,
|
|
74
75
|
taskId,
|
|
@@ -129,6 +130,6 @@ export async function generateLoopCloseout(repoRoot, taskId) {
|
|
|
129
130
|
"- It does not modify completed DAG or one-shot run facts.",
|
|
130
131
|
"",
|
|
131
132
|
].join("\n");
|
|
132
|
-
await
|
|
133
|
+
await writeLoopCloseout(repoRoot, taskId, markdown);
|
|
133
134
|
return record;
|
|
134
135
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getLoopPaths } from "./paths.js";
|
|
1
|
+
import { writeLoopContext } from "../../infrastructure/harness/loop-store.js";
|
|
3
2
|
import { readLoopRounds } from "./rounds.js";
|
|
4
3
|
import { refreshLoopState } from "./state.js";
|
|
5
4
|
import { appendLoopEvent } from "./events.js";
|
|
@@ -36,7 +35,7 @@ export async function rewriteLoopContext(repoRoot, taskId) {
|
|
|
36
35
|
`- Last decision: ${latest.decision}.`,
|
|
37
36
|
"",
|
|
38
37
|
].join("\n");
|
|
39
|
-
await
|
|
38
|
+
await writeLoopContext(repoRoot, taskId, content);
|
|
40
39
|
await refreshLoopState(repoRoot, taskId);
|
|
41
40
|
await appendLoopEvent(repoRoot, taskId, {
|
|
42
41
|
type: "context_rewrite",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { appendLoopEventLine } from "../../infrastructure/harness/loop-store.js";
|
|
2
3
|
import { getLoopPaths } from "./paths.js";
|
|
3
4
|
export async function appendLoopEvent(repoRoot, taskId, input) {
|
|
4
5
|
const now = new Date().toISOString();
|
|
@@ -12,7 +13,7 @@ export async function appendLoopEvent(repoRoot, taskId, input) {
|
|
|
12
13
|
refs: input.refs ?? [],
|
|
13
14
|
recordedAt: now,
|
|
14
15
|
};
|
|
15
|
-
await
|
|
16
|
+
await appendLoopEventLine(repoRoot, taskId, entry);
|
|
16
17
|
return entry;
|
|
17
18
|
}
|
|
18
19
|
export async function readLoopEvents(repoRoot, taskId) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { pendingLoopSignals } from "../signals.js";
|
|
2
|
+
export function decideNextLoopAutoAction(state, signals = [], options = {}) {
|
|
3
|
+
const pendingSignals = pendingLoopSignals(signals);
|
|
4
|
+
const urgent = pendingSignals.find((signal) => signal.urgent);
|
|
5
|
+
if (urgent) {
|
|
6
|
+
return {
|
|
7
|
+
action: "pause",
|
|
8
|
+
reason: `urgent ${urgent.type} signal: ${urgent.message}`,
|
|
9
|
+
decision: "pause",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const scopeChanged = pendingSignals.find((signal) => signal.type === "scope_changed");
|
|
13
|
+
if (scopeChanged) {
|
|
14
|
+
return {
|
|
15
|
+
action: "pause",
|
|
16
|
+
reason: `scope changed signal requires objective/context review: ${scopeChanged.message}`,
|
|
17
|
+
decision: "pause",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const reviewSignal = pendingSignals.find((signal) => signal.type === "review_feedback" || signal.type === "human_followup");
|
|
21
|
+
if (reviewSignal) {
|
|
22
|
+
return {
|
|
23
|
+
action: "pi-review",
|
|
24
|
+
reason: `${reviewSignal.type} signal requires read-only review: ${reviewSignal.message}`,
|
|
25
|
+
decision: "continue",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (state.round >= state.maxRounds) {
|
|
29
|
+
return {
|
|
30
|
+
action: "blocked",
|
|
31
|
+
reason: `loop maxRounds reached (${state.round}/${state.maxRounds})`,
|
|
32
|
+
decision: "blocked",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (state.failureStreak.category && state.failureStreak.count >= 2) {
|
|
36
|
+
return {
|
|
37
|
+
action: "blocked",
|
|
38
|
+
reason: `repeated failure category ${state.failureStreak.category} x${state.failureStreak.count}`,
|
|
39
|
+
decision: "blocked",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (state.freshness.sourceStale) {
|
|
43
|
+
return {
|
|
44
|
+
action: "pause",
|
|
45
|
+
reason: "task source changed; rebuild or review action prompt before continuing",
|
|
46
|
+
decision: "pause",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (state.lastAction === "shell-verify") {
|
|
50
|
+
return {
|
|
51
|
+
action: "pi-review",
|
|
52
|
+
reason: "shell verification already ran; request read-only review before next implementation decision",
|
|
53
|
+
decision: "continue",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (state.lastAction === "pi-review") {
|
|
57
|
+
if (options.latestPiReview?.recommendedAction === "implement_fix") {
|
|
58
|
+
const policy = options.loopAutoWritePolicy ?? "off";
|
|
59
|
+
const approvalSignal = pendingSignals.find((signal) => signal.type === "approval");
|
|
60
|
+
const allowedByPolicy = policy === "enabled" ||
|
|
61
|
+
(policy === "approval-required" &&
|
|
62
|
+
(Boolean(options.allowCursorFix) || Boolean(approvalSignal)));
|
|
63
|
+
if (options.cursorFixPolicyError) {
|
|
64
|
+
return {
|
|
65
|
+
action: "pause",
|
|
66
|
+
reason: `cursor-fix policy guard failed: ${options.cursorFixPolicyError}`,
|
|
67
|
+
decision: "pause",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (allowedByPolicy) {
|
|
71
|
+
return {
|
|
72
|
+
action: "cursor-fix",
|
|
73
|
+
reason: `pi-review recommended implement_fix; loopAutoWritePolicy=${policy}`,
|
|
74
|
+
decision: "continue",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
action: "dag",
|
|
80
|
+
reason: "read-only review produced next action; generate a governed DAG review packet",
|
|
81
|
+
decision: "continue",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const approvalSignal = pendingSignals.find((signal) => signal.type === "approval");
|
|
85
|
+
if (approvalSignal) {
|
|
86
|
+
return {
|
|
87
|
+
action: "dag",
|
|
88
|
+
reason: `approval signal received; generate next governed DAG packet: ${approvalSignal.message}`,
|
|
89
|
+
decision: "continue",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (state.lastAction === "cursor-fix" || state.lastAction === "dag") {
|
|
93
|
+
return {
|
|
94
|
+
action: "shell-verify",
|
|
95
|
+
reason: `${state.lastAction} round must be followed by deterministic shell verification`,
|
|
96
|
+
decision: "continue",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
action: "dag",
|
|
101
|
+
reason: "default to DAG governed review packet for the next bounded round",
|
|
102
|
+
decision: "continue",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { pathMatchesPattern } from "../../../shared/git-progress.js";
|
|
2
|
+
export function normalizePattern(pattern) {
|
|
3
|
+
return pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/\*\*$/, "");
|
|
4
|
+
}
|
|
5
|
+
export function patternsOverlap(allowed, forbidden) {
|
|
6
|
+
const a = normalizePattern(allowed);
|
|
7
|
+
const f = normalizePattern(forbidden);
|
|
8
|
+
return (a === f ||
|
|
9
|
+
a.startsWith(`${f}/`) ||
|
|
10
|
+
f.startsWith(`${a}/`) ||
|
|
11
|
+
pathMatchesPattern(a, forbidden) ||
|
|
12
|
+
pathMatchesPattern(f, allowed));
|
|
13
|
+
}
|
|
14
|
+
export function validateLoopCursorFixPolicy(input) {
|
|
15
|
+
if (input.allowedPaths.length === 0) {
|
|
16
|
+
throw new Error("loop cursor-fix requires non-empty task allowedPaths");
|
|
17
|
+
}
|
|
18
|
+
for (const allowed of input.allowedPaths) {
|
|
19
|
+
for (const forbidden of input.forbiddenPaths) {
|
|
20
|
+
if (patternsOverlap(allowed, forbidden)) {
|
|
21
|
+
throw new Error(`loop cursor-fix allowed/forbidden paths overlap: ${allowed} vs ${forbidden}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (input.complexity !== undefined &&
|
|
26
|
+
input.complexity !== "small" &&
|
|
27
|
+
!input.hasDagRound &&
|
|
28
|
+
!input.dagFallbackReason?.trim()) {
|
|
29
|
+
throw new Error("loop cursor-fix for medium/large tasks requires a prior loop dag round or task dagFallbackReason");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { writeLoopRounds } from "../../infrastructure/harness/loop-store.js";
|
|
2
3
|
import { getLoopPaths } from "./paths.js";
|
|
3
4
|
import { refreshLoopState } from "./state.js";
|
|
4
5
|
import { appendLoopEvent } from "./events.js";
|
|
@@ -33,14 +34,13 @@ export async function appendLoopRound(repoRoot, taskId, input) {
|
|
|
33
34
|
failureCategory: input.failureCategory ?? "",
|
|
34
35
|
completedCriteria: input.completedCriteria ?? [],
|
|
35
36
|
};
|
|
36
|
-
const paths = getLoopPaths(repoRoot, taskId);
|
|
37
37
|
await appendLoopEvent(repoRoot, taskId, {
|
|
38
38
|
type: "round_start",
|
|
39
39
|
round: entry.round,
|
|
40
40
|
message: `recording round ${entry.round} action ${entry.action}`,
|
|
41
41
|
refs: entry.refs,
|
|
42
42
|
});
|
|
43
|
-
await
|
|
43
|
+
await writeLoopRounds(repoRoot, taskId, [...rounds, entry]);
|
|
44
44
|
const previousState = await refreshLoopState(repoRoot, taskId, {
|
|
45
45
|
status: statusForDecision(entry.decision),
|
|
46
46
|
round: entry.round,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { writeLoopSignals as persistLoopSignals } from "../../infrastructure/harness/loop-store.js";
|
|
2
3
|
import { getLoopPaths } from "./paths.js";
|
|
3
4
|
import { LOOP_SIGNAL_TYPES, } from "./types.js";
|
|
4
5
|
export function parseLoopSignalType(value) {
|
|
@@ -31,7 +32,7 @@ export async function appendLoopSignal(repoRoot, taskId, input) {
|
|
|
31
32
|
if (!entry.message) {
|
|
32
33
|
throw new Error("loop signal message cannot be empty");
|
|
33
34
|
}
|
|
34
|
-
await
|
|
35
|
+
await persistLoopSignals(repoRoot, taskId, [...signals, entry]);
|
|
35
36
|
return entry;
|
|
36
37
|
}
|
|
37
38
|
export function pendingLoopSignals(signals) {
|
|
@@ -46,10 +47,6 @@ export async function drainLoopSignals(repoRoot, taskId, ids) {
|
|
|
46
47
|
const drained = signals.map((signal) => idSet.has(signal.id) && !signal.drainedAt
|
|
47
48
|
? { ...signal, drainedAt: now }
|
|
48
49
|
: signal);
|
|
49
|
-
await
|
|
50
|
+
await persistLoopSignals(repoRoot, taskId, drained);
|
|
50
51
|
return drained;
|
|
51
52
|
}
|
|
52
|
-
async function writeLoopSignals(repoRoot, taskId, signals) {
|
|
53
|
-
await writeFile(getLoopPaths(repoRoot, taskId).signalsPath, signals.map((signal) => JSON.stringify(signal)).join("\n") +
|
|
54
|
-
(signals.length > 0 ? "\n" : ""), "utf-8");
|
|
55
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureLoopDir, writeLoopContext, writeLoopObjective, writeLoopRounds, writeLoopSignals, writeLoopEvents, writeLoopState, } from "../../infrastructure/harness/loop-store.js";
|
|
2
4
|
import { getTaskDir } from "../../task/runtime.js";
|
|
3
|
-
import { repoRelativePath } from "../../shared/path-refs.js";
|
|
4
5
|
import { hashFileIfPresent, hashTaskSource } from "./hash.js";
|
|
5
6
|
import { getLoopPaths } from "./paths.js";
|
|
6
7
|
import { defaultContext, defaultObjective } from "./templates.js";
|
|
@@ -21,7 +22,7 @@ async function exists(filePath) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
function rel(repoRoot, filePath) {
|
|
24
|
-
return
|
|
25
|
+
return path.relative(repoRoot, filePath);
|
|
25
26
|
}
|
|
26
27
|
export async function buildLoopState(repoRoot, taskId, paths = getLoopPaths(repoRoot, taskId), previous) {
|
|
27
28
|
const now = new Date().toISOString();
|
|
@@ -59,8 +60,7 @@ export async function buildLoopState(repoRoot, taskId, paths = getLoopPaths(repo
|
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
export async function saveLoopState(repoRoot, taskId, state) {
|
|
62
|
-
|
|
63
|
-
await writeFile(paths.statePath, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
63
|
+
await writeLoopState(repoRoot, taskId, state);
|
|
64
64
|
}
|
|
65
65
|
export async function loadLoopState(repoRoot, taskId) {
|
|
66
66
|
const paths = getLoopPaths(repoRoot, taskId);
|
|
@@ -86,21 +86,21 @@ export async function initializeLoop(repoRoot, taskId, options = {}) {
|
|
|
86
86
|
if (stateExists && !options.recover) {
|
|
87
87
|
throw new Error(`loop already initialized for task: ${taskId}`);
|
|
88
88
|
}
|
|
89
|
-
await
|
|
89
|
+
await ensureLoopDir(repoRoot, taskId);
|
|
90
90
|
if (!(await exists(paths.objectivePath))) {
|
|
91
|
-
await
|
|
91
|
+
await writeLoopObjective(repoRoot, taskId, defaultObjective(taskId));
|
|
92
92
|
}
|
|
93
93
|
if (!(await exists(paths.contextPath))) {
|
|
94
|
-
await
|
|
94
|
+
await writeLoopContext(repoRoot, taskId, defaultContext());
|
|
95
95
|
}
|
|
96
96
|
if (!(await exists(paths.roundsPath))) {
|
|
97
|
-
await
|
|
97
|
+
await writeLoopRounds(repoRoot, taskId, []);
|
|
98
98
|
}
|
|
99
99
|
if (!(await exists(paths.signalsPath))) {
|
|
100
|
-
await
|
|
100
|
+
await writeLoopSignals(repoRoot, taskId, []);
|
|
101
101
|
}
|
|
102
102
|
if (!(await exists(paths.eventsPath))) {
|
|
103
|
-
await
|
|
103
|
+
await writeLoopEvents(repoRoot, taskId, []);
|
|
104
104
|
}
|
|
105
105
|
const previous = stateExists ? await loadLoopState(repoRoot, taskId) : undefined;
|
|
106
106
|
const state = await buildLoopState(repoRoot, taskId, paths, {
|
package/docs/README.md
CHANGED
|
@@ -6,8 +6,9 @@ Top-level `AGENTS.md` is the operating map. Durable knowledge belongs here: deci
|
|
|
6
6
|
|
|
7
7
|
## Core Documents
|
|
8
8
|
|
|
9
|
-
- `development-principles.md` — repository development principles
|
|
10
|
-
- `
|
|
9
|
+
- `development-principles.md` — repository development principles
|
|
10
|
+
- `architecture/runtime-boundaries.md` — runtime layer boundaries and dependency direction
|
|
11
|
+
- `feature-workflow.md` — bounded feature workflow
|
|
11
12
|
- `verification-matrix.md` — verification command selection
|
|
12
13
|
- `loop-agent-harness.md` — runtime and command surface overview
|
|
13
14
|
- `agent-dag-runner.md` — Agent DAG runner guide
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Runtime Boundaries
|
|
2
|
+
|
|
3
|
+
本文定义 loop-agent 各 runtime 层的 module interface、允许的依赖方向,以及治理检查 hook。目标是把「命令、文档、import 架构」从多处维护收敛为可机器校验的边界契约,而不在本阶段改变 runtime 行为。
|
|
4
|
+
|
|
5
|
+
## 分层概览
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Skill layer
|
|
9
|
+
└─ 入口策略、reference 路由、硬规则(skills/loop-agent/SKILL.md + references/)
|
|
10
|
+
|
|
11
|
+
CLI layer (src/cli/)
|
|
12
|
+
└─ argv 解析、adapter 解析、调用 application / command handler、格式化输出
|
|
13
|
+
|
|
14
|
+
Application layer (src/application/,逐步引入)
|
|
15
|
+
└─ 一次用户意图的 typed use-case interface,供 CLI、Loop、Workflow 复用
|
|
16
|
+
|
|
17
|
+
Workflow runtime (src/workflows/)
|
|
18
|
+
└─ DAG / Dynamic / Loop 核心执行规则;不应依赖 commands
|
|
19
|
+
|
|
20
|
+
Executors (src/executors/)
|
|
21
|
+
└─ Cursor / Pi / Shell 等外部工具适配;不应依赖 commands 或 CLI formatting
|
|
22
|
+
|
|
23
|
+
Infrastructure / Store (src/infrastructure/,逐步引入)
|
|
24
|
+
└─ .harness 文件系统副作用、run lifecycle、原子写入规则
|
|
25
|
+
|
|
26
|
+
Governance (scripts/check-*.sh, src/governance/)
|
|
27
|
+
└─ 防止 command registry、skill entry、import architecture 漂移
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 各层职责
|
|
31
|
+
|
|
32
|
+
### Skill layer
|
|
33
|
+
|
|
34
|
+
- **位置**:`skills/loop-agent/SKILL.md` 及 `skills/loop-agent/references/**`
|
|
35
|
+
- **职责**:定义 agent 何时启用 loop-agent、默认执行路径(Agent DAG)、硬规则与 reference 路由;不承载完整操作手册。
|
|
36
|
+
- **禁止**:在入口 skill 中重复维护 CLI command 列表或与 `src/cli/catalog.ts` 冲突的事实源。
|
|
37
|
+
|
|
38
|
+
### CLI layer
|
|
39
|
+
|
|
40
|
+
- **位置**:`src/cli.ts`、`src/cli/**`
|
|
41
|
+
- **职责**:解析全局 flag(如 `--repo-root`)、解析 top-level / subcommand、调用 command handler、输出 help / JSON / Markdown。
|
|
42
|
+
- **允许依赖**:`src/commands/**`(handler wiring)、`src/adapters/**`、`src/shared/**`、逐步引入的 `src/application/**`。
|
|
43
|
+
- **禁止**:在 router 中维护与 catalog 独立的 command 事实源(Phase 1 整改目标)。
|
|
44
|
+
|
|
45
|
+
### Application layer
|
|
46
|
+
|
|
47
|
+
- **位置**:`src/application/**`(按计划逐步引入)
|
|
48
|
+
- **职责**:封装一次用户意图的 typed result(如 validate DAG、run DAG、report),供 CLI 与 workflow runtime 共用。
|
|
49
|
+
- **允许依赖**:`src/workflows/**`、`src/infrastructure/**`、`src/task/**`、`src/records/**`。
|
|
50
|
+
- **禁止**:直接承担 CLI formatting 或 argv 解析。
|
|
51
|
+
|
|
52
|
+
### Workflow runtime
|
|
53
|
+
|
|
54
|
+
- **位置**:`src/workflows/dag/**`、`src/workflows/dynamic/**`、`src/workflows/loop/**`
|
|
55
|
+
- **职责**:DAG spec 校验与执行、dynamic workflow 编译与 expansion、Loop 状态机与 action 编排。
|
|
56
|
+
- **允许依赖**:`src/executors/**`、`src/task/**`、`src/records/**`、`src/shared/**`、application use-case(目标态)。
|
|
57
|
+
- **禁止**:`import` 来自 `src/commands/**`(见下方过渡例外)。
|
|
58
|
+
|
|
59
|
+
### Executors
|
|
60
|
+
|
|
61
|
+
- **位置**:`src/executors/**`
|
|
62
|
+
- **职责**:封装 Cursor SDK/CLI、Pi SDK/CLI、shell 执行与 write guard。
|
|
63
|
+
- **允许依赖**:`src/shared/**`、外部 SDK。
|
|
64
|
+
- **禁止**:依赖 `src/commands/**` 或 CLI 输出格式。
|
|
65
|
+
|
|
66
|
+
### Infrastructure / Store
|
|
67
|
+
|
|
68
|
+
- **位置**:`src/infrastructure/harness/**`(按计划逐步引入);过渡期部分逻辑仍在 `src/workflows/dag/lifecycle.ts`、`src/records/**`。
|
|
69
|
+
- **职责**:`.harness/tasks`、`.harness/dag-runs`、`.harness/runs`、loop state 的集中读写;completed run facts 只读约束。
|
|
70
|
+
- **禁止**:把 raw path mutation 扩散给 runner、loop action 或 command handler。
|
|
71
|
+
|
|
72
|
+
### Governance
|
|
73
|
+
|
|
74
|
+
- **位置**:`scripts/check-repo.sh` 及子脚本、`src/governance/**`、相关 Vitest。
|
|
75
|
+
- **职责**:在 CI / in-flight DAG 中检测文档链接、exec plan 状态、架构 import、command registry 漂移、skill entry 完整性。
|
|
76
|
+
|
|
77
|
+
## 允许的依赖方向
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
Skill (docs) ──advisory──> CLI catalog / command-reference(人类可读,机器 drift check)
|
|
81
|
+
|
|
82
|
+
CLI ──────────> Application (目标态)
|
|
83
|
+
CLI ──────────> Commands (handler 薄层)
|
|
84
|
+
Commands ──────────> Application / Workflows / Infrastructure
|
|
85
|
+
|
|
86
|
+
Workflow runtime ──────────> Executors / Task / Records / Application (目标态)
|
|
87
|
+
Workflow runtime ──X──> Commands (目标态禁止;见过渡例外)
|
|
88
|
+
|
|
89
|
+
Executors ──────────> Shared / 外部 SDK
|
|
90
|
+
Executors ──X──> Commands / CLI
|
|
91
|
+
|
|
92
|
+
Application ──────────> Workflows / Infrastructure / Task / Records
|
|
93
|
+
Application ──X──> CLI formatting
|
|
94
|
+
|
|
95
|
+
Infrastructure ──────────> Shared / node:fs
|
|
96
|
+
Runner / Loop ──(迁移中)──> 逐步改为仅经 Store / Application
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**规则摘要**
|
|
100
|
+
|
|
101
|
+
| From | May import | Must not import |
|
|
102
|
+
|------|------------|-----------------|
|
|
103
|
+
| `src/commands/**` | application, workflows, infrastructure, task, records, executors, shared | — |
|
|
104
|
+
| `src/workflows/**` | executors, task, records, shared, application(目标) | `src/commands/**` |
|
|
105
|
+
| `src/executors/**` | shared, 外部 SDK | `src/commands/**`, `src/cli/**` |
|
|
106
|
+
| `src/cli/**` | commands, adapters, shared, application(目标) | — |
|
|
107
|
+
| `skills/**` | (Markdown 引用,非 TS import) | 运行时 TS 模块 |
|
|
108
|
+
|
|
109
|
+
## 已知过渡耦合
|
|
110
|
+
|
|
111
|
+
当前没有允许的 `src/workflows/**` 或 `src/executors/**` → `src/commands/**` TypeScript import。
|
|
112
|
+
|
|
113
|
+
`scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors -> commands` import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
|
|
114
|
+
|
|
115
|
+
## Governance hooks
|
|
116
|
+
|
|
117
|
+
以下脚本由 `scripts/check-repo.sh` 调用(Phase 0 起):
|
|
118
|
+
|
|
119
|
+
| Script | 检查内容 | 失败条件 |
|
|
120
|
+
|--------|----------|----------|
|
|
121
|
+
| `scripts/check-architecture-boundaries.sh` | `workflows` / `executors` → `commands` import | 新的未 allowlist violation |
|
|
122
|
+
| `scripts/check-command-registry-drift.sh` | `command-reference.md` 中的 top-level command vs `src/cli/catalog.ts` | 文档引用未注册 command |
|
|
123
|
+
| `scripts/check-skill-entry.sh` | `SKILL.md` reference 文件存在、行数阈值 | reference 缺失(fail);行数 > 220(warn) |
|
|
124
|
+
|
|
125
|
+
相关 exec plan:`docs/exec-plans/active/2026-07-04-runtime-boundary-remediation.md`。
|
|
126
|
+
|
|
127
|
+
### 验证命令
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bash scripts/check-repo.sh
|
|
131
|
+
bash scripts/check-architecture-boundaries.sh
|
|
132
|
+
bash scripts/check-command-registry-drift.sh
|
|
133
|
+
bash scripts/check-skill-entry.sh
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Runtime 变更另需 `npm run typecheck` 及对应 targeted Vitest(见 exec plan 各 Phase 验证关口)。
|
|
137
|
+
|
|
138
|
+
## 演进里程碑
|
|
139
|
+
|
|
140
|
+
| Phase | 边界变化 |
|
|
141
|
+
|-------|----------|
|
|
142
|
+
| 0(当前) | 文档 + 机器 guard;已知 loop/actions.ts 耦合 advisory |
|
|
143
|
+
| 1 | CLI command definition 单源 |
|
|
144
|
+
| 2 | Skill entry 瘦身 + frontmatter references |
|
|
145
|
+
| 3 | DAG application use-case;Loop 开始脱离 command JSON |
|
|
146
|
+
| 6 | `workflows → commands` import check 已从 advisory 改为 **fail** |
|
|
147
|
+
| 7 | Harness Store / Repository 集中 `.harness` 副作用 |
|
|
@@ -3,3 +3,7 @@
|
|
|
3
3
|
Use this directory for execution plans that are currently in progress.
|
|
4
4
|
|
|
5
5
|
Source repositories may keep concrete active plan files next to this README. The npm package carries this README as a directory contract and does not copy loop-agent source-history active plans; target repositories generate their own active plans.
|
|
6
|
+
|
|
7
|
+
Current active plans:
|
|
8
|
+
|
|
9
|
+
- [2026-07-04-dag-role-skill-alignment.md](2026-07-04-dag-role-skill-alignment.md)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
# Completed Execution Plans
|
|
2
|
-
|
|
1
|
+
# Completed Execution Plans
|
|
2
|
+
|
|
3
3
|
Completed plans are moved here after their implementation and verification finish.
|
|
4
4
|
|
|
5
5
|
The npm package carries this README as a directory contract. Concrete completed plans are target-repository history and are not copied from loop-agent's source history.
|
|
6
|
+
|
|
7
|
+
- [`2026-07-02-loop-agent-subject-restructure.md`](2026-07-02-loop-agent-subject-restructure.md) — promoted the former `tools/code-agent` runtime to the repository root, renamed it to `loop-agent`, and removed the old memory plugin product lines.
|
|
8
|
+
- [`2026-07-04-remove-level1-fallback.md`](2026-07-04-remove-level1-fallback.md) — removed the historical sequential Level 1 fallback and converged the runtime, docs, and command surface on DAG execution.
|
|
9
|
+
- [`2026-07-04-runtime-boundary-remediation.md`](2026-07-04-runtime-boundary-remediation.md) — consolidated CLI/skill/runtime boundaries, extracted DAG/Loop runtime seams, and centralized harness store/guard policy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tea-agent/loop-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"loop-agent": "bin/loop-agent.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"dist/",
|
|
12
12
|
"docs/*.md",
|
|
13
|
+
"docs/architecture/runtime-boundaries.md",
|
|
13
14
|
"docs/decisions/README.md",
|
|
14
15
|
"docs/design/README.md",
|
|
15
16
|
"docs/exec-plans/README.md",
|
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ai-engineering-context
|
|
3
|
-
description:
|
|
3
|
+
description: 当 AI coding agent 启动 loop-agent 工作、准备 DAG 或 Loop context、委派 role-specific 节点,或决定 requirements、facts、evidence、handoff notes 应落何处时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# AI Engineering Context
|
|
7
7
|
|
|
8
|
-
Context
|
|
8
|
+
Context 是工程 artifact,不是 chat 残留。须显式保留 requirements、role 边界、write authority、evidence 与 handoff facts。
|
|
9
9
|
|
|
10
10
|
## When to Use
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
在启动 task、DAG、workflow 或 Loop round;准备 role prompt;委派给 Cursor/Pi/shell/static executor;或处理 stale plan、冲突 requirements、缺失 evidence 时使用。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
不要用于 private platform paths、personal memory、Google Drive 规则,或替代 task skills。
|
|
15
15
|
|
|
16
16
|
## Context Priority
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
按以下顺序优先采信 facts:最新 user instruction;task source/contract;DAG/Loop artifacts;docs/plans/ADRs;code/tests;chat history 仅作 hint。
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
若 sources 冲突,停止并点明冲突。
|
|
21
21
|
|
|
22
22
|
## Role Boundaries
|
|
23
23
|
|
|
24
|
-
- Planner
|
|
25
|
-
- Scout
|
|
26
|
-
- Implementer
|
|
27
|
-
- Reviewer
|
|
28
|
-
- Verifier
|
|
29
|
-
- Supervisor
|
|
30
|
-
- Closeout
|
|
24
|
+
- Planner:contract、DAG shape、write boundary、verification plan;不做 implementation edits。
|
|
25
|
+
- Scout:code/test/doc/artifact facts;不写 repo。
|
|
26
|
+
- Implementer:在 writeSet 内做 bounded changes;不扩大 scope 或宣称完成。
|
|
27
|
+
- Reviewer:bugs、regressions、missing tests、risk;除非被指派,否则不重写。
|
|
28
|
+
- Verifier:command evidence、reproduction、failure category;model judgment 不是 proof。
|
|
29
|
+
- Supervisor:gates、escalation、repair scope;不绕过 write-guard 或 human-gate。
|
|
30
|
+
- Closeout:evidence、risks、next steps;不隐藏 failures。
|
|
31
31
|
|
|
32
32
|
## Prompt Contract
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
每个 delegated node prompt 应包含:objective、task id、role、executor、allowed paths、forbidden paths、writeSet、upstream artifact refs、concise excerpts、output contract、expected evidence、non-goals、stop conditions。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
read-only node 只能在 node output 返回 findings。不得在 repo 中创建 scratch files。
|
|
37
37
|
|
|
38
38
|
## Persistence Rules
|
|
39
39
|
|
|
40
|
-
Requirements
|
|
40
|
+
Requirements 与 constraints 写入 task `source/`。Execution state 与 node artifacts 写入 `.harness/`。Durable plans、reports、decisions 写入 `docs/`。可复用 process guidance 写入 `skills/`。Chat 仅 transient。
|
|
41
41
|
|
|
42
42
|
## Failure Handling
|
|
43
43
|
|
|
44
|
-
- Missing context
|
|
45
|
-
- Ambiguous requirement
|
|
46
|
-
- Verification failure
|
|
47
|
-
- Missing skill instructions
|
|
48
|
-
- Missing fresh verification
|
|
44
|
+
- Missing context:运行 scout 或读取 durable source。
|
|
45
|
+
- Ambiguous requirement:implementation 前更新 contract。
|
|
46
|
+
- Verification failure:改 code 前先 diagnose cause。
|
|
47
|
+
- Missing skill instructions:视为 context defect;不要假设 hidden behavior。
|
|
48
|
+
- Missing fresh verification:不要 close out。
|