loopforge 1.0.0 → 1.3.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/dist/autonomous.d.ts +84 -0
- package/dist/autonomous.d.ts.map +1 -0
- package/dist/autonomous.js +250 -0
- package/dist/autonomous.js.map +1 -0
- package/dist/backends/ai.d.ts +37 -0
- package/dist/backends/ai.d.ts.map +1 -0
- package/dist/backends/ai.js +98 -0
- package/dist/backends/ai.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +68 -6
- package/dist/cli.js.map +1 -1
- package/dist/engine.d.ts +15 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +102 -1
- package/dist/engine.js.map +1 -1
- package/dist/generate-schema.js +56 -4
- package/dist/generate-schema.js.map +1 -1
- package/dist/index.d.ts +23 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/loop-compiler.d.ts +4 -0
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +43 -0
- package/dist/loop-compiler.js.map +1 -1
- package/dist/mcp/server.d.ts +14 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +93 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +57 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +177 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/tools.d.ts +108 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +191 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +14 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/policy.d.ts +8 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +7 -0
- package/dist/policy.js.map +1 -1
- package/dist/protocol.d.ts +89 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +20 -1
- package/dist/protocol.js.map +1 -1
- package/dist/runtime.d.ts +57 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +414 -0
- package/dist/runtime.js.map +1 -0
- package/dist/tests/_helpers.d.ts +25 -0
- package/dist/tests/_helpers.d.ts.map +1 -0
- package/dist/tests/_helpers.js +47 -0
- package/dist/tests/_helpers.js.map +1 -0
- package/dist/tests/autonomous.test.d.ts +7 -0
- package/dist/tests/autonomous.test.d.ts.map +1 -0
- package/dist/tests/autonomous.test.js +294 -0
- package/dist/tests/autonomous.test.js.map +1 -0
- package/dist/tests/generate-schema.test.js +2 -2
- package/dist/tests/generate-schema.test.js.map +1 -1
- package/dist/tests/mcp.test.d.ts +7 -0
- package/dist/tests/mcp.test.d.ts.map +1 -0
- package/dist/tests/mcp.test.js +212 -0
- package/dist/tests/mcp.test.js.map +1 -0
- package/dist/tests/replay.test.js +69 -41
- package/dist/tests/replay.test.js.map +1 -1
- package/dist/tests/runtime.test.d.ts +7 -0
- package/dist/tests/runtime.test.d.ts.map +1 -0
- package/dist/tests/runtime.test.js +323 -0
- package/dist/tests/runtime.test.js.map +1 -0
- package/loop_policy.json +7 -0
- package/package.json +9 -3
- package/skills/perception/SKILL.md +186 -0
- package/skills/prompt-techniques/SKILL.md +52 -0
- package/skills/prompt-techniques/references/chain-of-thought.md +207 -0
- package/skills/prompt-techniques/references/few-shot.md +174 -0
- package/skills/prompt-techniques/references/least-to-most.md +182 -0
- package/skills/prompt-techniques/references/step-back.md +160 -0
- package/skills/prompt-techniques/references/tree-of-thought.md +191 -0
- package/skills/prompt-techniques/references/zero-shot.md +97 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** LoopForge — Autonomous loop driver (v1.1).
|
|
2
|
+
*
|
|
3
|
+
* Closes the feedback loop: compile → agent executes → extract self-eval →
|
|
4
|
+
* auto-feedback → compile next → ... until task complete or circuit breaker.
|
|
5
|
+
*
|
|
6
|
+
* Zero API dependencies — LoopForge does NOT call AI APIs. The caller
|
|
7
|
+
* provides an execute callback or feeds agent output round by round.
|
|
8
|
+
*/
|
|
9
|
+
import { type LoopHealth, type SelfEvaluation } from "./protocol.js";
|
|
10
|
+
import { LoopForgeEngine } from "./engine.js";
|
|
11
|
+
export interface AutonomousConfig {
|
|
12
|
+
/** The task description — becomes the loop objective. */
|
|
13
|
+
task: string;
|
|
14
|
+
/** Kebab-case loop identifier. */
|
|
15
|
+
loopId: string;
|
|
16
|
+
/** Optional explicit goal ID. Derived from task if omitted. */
|
|
17
|
+
goalId?: string;
|
|
18
|
+
/** Maximum rounds before forced stop. Default 20. */
|
|
19
|
+
maxRounds?: number;
|
|
20
|
+
/** Health check interval (rounds). Default 3. */
|
|
21
|
+
healthCheckInterval?: number;
|
|
22
|
+
/** Optional path to a plan/spec file for constraint extraction. */
|
|
23
|
+
planSource?: string;
|
|
24
|
+
/** Constraints extracted from the plan (provided by caller). */
|
|
25
|
+
constraintsFromPlan?: string[];
|
|
26
|
+
/** Domain hint for technique routing. */
|
|
27
|
+
domain?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RoundOutput {
|
|
30
|
+
round: number;
|
|
31
|
+
/** The compiled prompt — ready for agent execution. */
|
|
32
|
+
prompt: string;
|
|
33
|
+
/** L0 | L1 | L2 */
|
|
34
|
+
recompileLevel: string;
|
|
35
|
+
/** Selected prompting technique. */
|
|
36
|
+
techniqueUsed: string;
|
|
37
|
+
/** Cross-round health snapshot (null on round 1 if no prior data). */
|
|
38
|
+
health: LoopHealth | null;
|
|
39
|
+
/** Quality score from the PREVIOUS round (null on round 1). */
|
|
40
|
+
lastQualityScore: number | null;
|
|
41
|
+
}
|
|
42
|
+
export type StopReason = "task_complete" | "circuit_breaker" | "max_rounds" | "stalled" | "extraction_failed";
|
|
43
|
+
export interface AutonomousResult {
|
|
44
|
+
/** Total rounds completed. */
|
|
45
|
+
roundsCompleted: number;
|
|
46
|
+
/** Why the loop stopped. */
|
|
47
|
+
stopReason: StopReason;
|
|
48
|
+
/** Health snapshot at stop. */
|
|
49
|
+
healthAtStop: LoopHealth | null;
|
|
50
|
+
/** Quality trajectory [round1_score, round2_score, ...]. */
|
|
51
|
+
qualityTrajectory: number[];
|
|
52
|
+
/** Whether the loop achieved its objective. */
|
|
53
|
+
success: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface RunOneRoundResult {
|
|
56
|
+
/** If stopNow is true, the loop should terminate. */
|
|
57
|
+
stopNow: boolean;
|
|
58
|
+
stopReason: StopReason | null;
|
|
59
|
+
/** The compiled prompt (null if stopping). */
|
|
60
|
+
roundOutput: RoundOutput | null;
|
|
61
|
+
/** Extracted self-evaluation (null on round 1 or if extraction failed). */
|
|
62
|
+
selfEval: SelfEvaluation | null;
|
|
63
|
+
/** Whether structured extraction succeeded. */
|
|
64
|
+
extractionSucceeded: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** Run one round of the autonomous loop.
|
|
67
|
+
*
|
|
68
|
+
* Round 1 (agentOutput = null): compile the first prompt (L2 full build).
|
|
69
|
+
* Round N (agentOutput = string): extract self-eval, record auto-feedback,
|
|
70
|
+
* compile next prompt (L0/L1/L2 decided by the 4-gate router).
|
|
71
|
+
*
|
|
72
|
+
* Returns stopNow = true when the loop should terminate. */
|
|
73
|
+
export declare function runOneRound(engine: LoopForgeEngine, config: AutonomousConfig, currentRound: number, agentOutput: string | null): RunOneRoundResult;
|
|
74
|
+
/** Execute callback: takes a compiled prompt, returns the agent's output. */
|
|
75
|
+
export type AgentExecutor = (prompt: string, round: number) => Promise<string>;
|
|
76
|
+
/** Run a fully autonomous loop from start to finish.
|
|
77
|
+
*
|
|
78
|
+
* The `execute` callback is called with each compiled prompt.
|
|
79
|
+
* LoopForge does NOT call any AI API — the caller provides execution.
|
|
80
|
+
*
|
|
81
|
+
* Stops when: task complete, circuit breaker triggered, max rounds
|
|
82
|
+
* reached, or self-eval extraction fails. */
|
|
83
|
+
export declare function runAutonomousLoop(engine: LoopForgeEngine, config: AutonomousConfig, execute: AgentExecutor): Promise<AutonomousResult>;
|
|
84
|
+
//# sourceMappingURL=autonomous.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autonomous.d.ts","sourceRoot":"","sources":["../src/autonomous.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,UAAU,EACf,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EAIhB,MAAM,aAAa,CAAC;AAMrB,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,iBAAiB,GACjB,YAAY,GACZ,SAAS,GACT,mBAAmB,CAAC;AAExB,MAAM,WAAW,gBAAgB;IAC/B,8BAA8B;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,4BAA4B;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,+BAA+B;IAC/B,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,4DAA4D;IAC5D,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,8CAA8C;IAC9C,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,+CAA+C;IAC/C,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;6DAM6D;AAC7D,wBAAgB,WAAW,CACzB,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,gBAAgB,EACxB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,iBAAiB,CAwLnB;AAMD,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE/E;;;;;;8CAM8C;AAC9C,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,gBAAgB,CAAC,CAiE3B"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/** LoopForge — Autonomous loop driver (v1.1).
|
|
2
|
+
*
|
|
3
|
+
* Closes the feedback loop: compile → agent executes → extract self-eval →
|
|
4
|
+
* auto-feedback → compile next → ... until task complete or circuit breaker.
|
|
5
|
+
*
|
|
6
|
+
* Zero API dependencies — LoopForge does NOT call AI APIs. The caller
|
|
7
|
+
* provides an execute callback or feeds agent output round by round.
|
|
8
|
+
*/
|
|
9
|
+
import { makeLoopCompileRequest, makeLoopRoundResult, makeVaultConfig, } from "./protocol.js";
|
|
10
|
+
import { extractSelfEvaluation, heuristicSelfEvaluation, } from "./engine.js";
|
|
11
|
+
/** Run one round of the autonomous loop.
|
|
12
|
+
*
|
|
13
|
+
* Round 1 (agentOutput = null): compile the first prompt (L2 full build).
|
|
14
|
+
* Round N (agentOutput = string): extract self-eval, record auto-feedback,
|
|
15
|
+
* compile next prompt (L0/L1/L2 decided by the 4-gate router).
|
|
16
|
+
*
|
|
17
|
+
* Returns stopNow = true when the loop should terminate. */
|
|
18
|
+
export function runOneRound(engine, config, currentRound, agentOutput) {
|
|
19
|
+
const maxRounds = config.maxRounds ?? 20;
|
|
20
|
+
const healthInterval = config.healthCheckInterval ?? 3;
|
|
21
|
+
// ── Check max rounds before doing anything ──────────────────────────
|
|
22
|
+
if (currentRound > maxRounds) {
|
|
23
|
+
return {
|
|
24
|
+
stopNow: true,
|
|
25
|
+
stopReason: "max_rounds",
|
|
26
|
+
roundOutput: null,
|
|
27
|
+
selfEval: null,
|
|
28
|
+
extractionSucceeded: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ── Round N (N > 1): Extract self-eval and record feedback ──────────
|
|
32
|
+
let selfEval = null;
|
|
33
|
+
let extractionSucceeded = false;
|
|
34
|
+
if (agentOutput !== null && currentRound > 1) {
|
|
35
|
+
// Try structured extraction first
|
|
36
|
+
selfEval = extractSelfEvaluation(agentOutput);
|
|
37
|
+
extractionSucceeded = selfEval !== null;
|
|
38
|
+
// Fallback to heuristic if structured extraction failed
|
|
39
|
+
const usedHeuristic = selfEval === null;
|
|
40
|
+
if (selfEval === null) {
|
|
41
|
+
selfEval = heuristicSelfEvaluation(agentOutput);
|
|
42
|
+
}
|
|
43
|
+
// Record auto-feedback (even heuristic — best effort for vault audit trail)
|
|
44
|
+
if (selfEval !== null) {
|
|
45
|
+
engine.autoFeedback(selfEval, config.loopId, currentRound - 1, // feedback is for the PREVIOUS round
|
|
46
|
+
config.task);
|
|
47
|
+
}
|
|
48
|
+
// Stop if structured extraction failed — heuristic is too low-confidence
|
|
49
|
+
// to continue the loop autonomously
|
|
50
|
+
if (usedHeuristic) {
|
|
51
|
+
return {
|
|
52
|
+
stopNow: true,
|
|
53
|
+
stopReason: "extraction_failed",
|
|
54
|
+
roundOutput: null,
|
|
55
|
+
selfEval,
|
|
56
|
+
extractionSucceeded: false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Check stop conditions from agent's self-eval (structured only)
|
|
60
|
+
if (selfEval !== null && !selfEval.should_continue) {
|
|
61
|
+
return {
|
|
62
|
+
stopNow: true,
|
|
63
|
+
stopReason: "task_complete",
|
|
64
|
+
roundOutput: null,
|
|
65
|
+
selfEval,
|
|
66
|
+
extractionSucceeded,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Check circuit breaker after recording feedback
|
|
70
|
+
if (engine.shouldBreak()) {
|
|
71
|
+
return {
|
|
72
|
+
stopNow: true,
|
|
73
|
+
stopReason: "circuit_breaker",
|
|
74
|
+
roundOutput: null,
|
|
75
|
+
selfEval,
|
|
76
|
+
extractionSucceeded,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Check if extraction failed entirely
|
|
80
|
+
if (selfEval === null) {
|
|
81
|
+
return {
|
|
82
|
+
stopNow: true,
|
|
83
|
+
stopReason: "extraction_failed",
|
|
84
|
+
roundOutput: null,
|
|
85
|
+
selfEval: null,
|
|
86
|
+
extractionSucceeded: false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ── Compile the next prompt ────────────────────────────────────────
|
|
91
|
+
const lcr = makeLoopCompileRequest({
|
|
92
|
+
loop_id: config.loopId,
|
|
93
|
+
round: currentRound,
|
|
94
|
+
goal_id: config.goalId ?? "",
|
|
95
|
+
task: config.task,
|
|
96
|
+
domain: config.domain ?? "",
|
|
97
|
+
plan_source: config.planSource ?? null,
|
|
98
|
+
constraints_from_plan: config.constraintsFromPlan ?? [],
|
|
99
|
+
health_check_interval: healthInterval,
|
|
100
|
+
vault_config: makeVaultConfig(),
|
|
101
|
+
});
|
|
102
|
+
// Attach last round result if we have self-eval data
|
|
103
|
+
if (selfEval !== null && currentRound > 1) {
|
|
104
|
+
lcr.last_round_result = makeLoopRoundResult({
|
|
105
|
+
round: currentRound - 1,
|
|
106
|
+
success: selfEval.success,
|
|
107
|
+
output_summary: selfEval.output_summary,
|
|
108
|
+
constraint_violations: selfEval.constraint_violations,
|
|
109
|
+
manual_fixes_needed: "",
|
|
110
|
+
quality_score: 0, // Will be backfilled from vault
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const result = engine.invokeLoopCompile({
|
|
114
|
+
task: config.task,
|
|
115
|
+
mode: "loop_compile",
|
|
116
|
+
vault_config: makeVaultConfig(),
|
|
117
|
+
feedback: null,
|
|
118
|
+
skill_name: null,
|
|
119
|
+
task_id: null,
|
|
120
|
+
loop_id: config.loopId,
|
|
121
|
+
round: currentRound,
|
|
122
|
+
goal_id: config.goalId ?? "",
|
|
123
|
+
domain: config.domain ?? "",
|
|
124
|
+
plan_source: config.planSource ?? null,
|
|
125
|
+
constraints_from_plan: config.constraintsFromPlan ?? [],
|
|
126
|
+
health_check_interval: healthInterval,
|
|
127
|
+
last_round_result: lcr.last_round_result ?? undefined,
|
|
128
|
+
});
|
|
129
|
+
if (result.status === "error" || !result.response?.prompt) {
|
|
130
|
+
return {
|
|
131
|
+
stopNow: true,
|
|
132
|
+
stopReason: "stalled",
|
|
133
|
+
roundOutput: null,
|
|
134
|
+
selfEval,
|
|
135
|
+
extractionSucceeded,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Extract health from the response (it's embedded in the prompt text as markdown)
|
|
139
|
+
// The health data is in the LoopCompileResponse, but we need to get it from the engine
|
|
140
|
+
const context = engine.hydrateLoopContext(config.loopId);
|
|
141
|
+
let health = null;
|
|
142
|
+
if (context) {
|
|
143
|
+
const results = context.results ?? [];
|
|
144
|
+
const latest = results[results.length - 1];
|
|
145
|
+
if (latest) {
|
|
146
|
+
const lineage = (latest.loop_lineage ?? {});
|
|
147
|
+
// Health is computed per-round, not stored directly in lineage
|
|
148
|
+
// We'll report null for simplicity — the prompt text contains health info
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Determine last quality score
|
|
152
|
+
let lastQuality = null;
|
|
153
|
+
if (selfEval !== null) {
|
|
154
|
+
const fb = selfEval;
|
|
155
|
+
// Recompute using the same formula as scoreQuality
|
|
156
|
+
if (fb.success && fb.constraint_violations.length === 0)
|
|
157
|
+
lastQuality = 5;
|
|
158
|
+
else if (fb.success && fb.constraint_violations.length === 0)
|
|
159
|
+
lastQuality = 4;
|
|
160
|
+
else if (fb.success)
|
|
161
|
+
lastQuality = 3;
|
|
162
|
+
else if (fb.constraint_violations.length > 0)
|
|
163
|
+
lastQuality = 2;
|
|
164
|
+
else
|
|
165
|
+
lastQuality = 1;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
stopNow: false,
|
|
169
|
+
stopReason: null,
|
|
170
|
+
roundOutput: {
|
|
171
|
+
round: currentRound,
|
|
172
|
+
prompt: result.response.prompt,
|
|
173
|
+
recompileLevel: result.response.analysis?.rationale?.includes("l0")
|
|
174
|
+
? "l0"
|
|
175
|
+
: result.response.analysis?.rationale?.includes("l1")
|
|
176
|
+
? "l1"
|
|
177
|
+
: "l2",
|
|
178
|
+
techniqueUsed: result.response.analysis?.technique ?? "unknown",
|
|
179
|
+
health,
|
|
180
|
+
lastQualityScore: lastQuality,
|
|
181
|
+
},
|
|
182
|
+
selfEval,
|
|
183
|
+
extractionSucceeded,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** Run a fully autonomous loop from start to finish.
|
|
187
|
+
*
|
|
188
|
+
* The `execute` callback is called with each compiled prompt.
|
|
189
|
+
* LoopForge does NOT call any AI API — the caller provides execution.
|
|
190
|
+
*
|
|
191
|
+
* Stops when: task complete, circuit breaker triggered, max rounds
|
|
192
|
+
* reached, or self-eval extraction fails. */
|
|
193
|
+
export async function runAutonomousLoop(engine, config, execute) {
|
|
194
|
+
const maxRounds = config.maxRounds ?? 20;
|
|
195
|
+
const qualityTrajectory = [];
|
|
196
|
+
let agentOutput = null;
|
|
197
|
+
let finalStopReason = "max_rounds";
|
|
198
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
199
|
+
const roundResult = runOneRound(engine, config, round, agentOutput);
|
|
200
|
+
if (roundResult.stopNow) {
|
|
201
|
+
finalStopReason = roundResult.stopReason ?? "stalled";
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (!roundResult.roundOutput) {
|
|
205
|
+
finalStopReason = "stalled";
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
// Track quality
|
|
209
|
+
if (roundResult.roundOutput.lastQualityScore !== null) {
|
|
210
|
+
qualityTrajectory.push(roundResult.roundOutput.lastQualityScore);
|
|
211
|
+
}
|
|
212
|
+
// Execute the prompt
|
|
213
|
+
try {
|
|
214
|
+
agentOutput = await execute(roundResult.roundOutput.prompt, round);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
finalStopReason = "stalled";
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
// If this was the last round and we didn't stop earlier
|
|
221
|
+
if (round === maxRounds) {
|
|
222
|
+
finalStopReason = "max_rounds";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Get final health snapshot
|
|
226
|
+
let healthAtStop = null;
|
|
227
|
+
try {
|
|
228
|
+
const context = engine.hydrateLoopContext(config.loopId);
|
|
229
|
+
if (context) {
|
|
230
|
+
const results = context.results ?? [];
|
|
231
|
+
if (results.length > 0) {
|
|
232
|
+
const latest = results[results.length - 1];
|
|
233
|
+
const lineage = (latest.loop_lineage ?? {});
|
|
234
|
+
// Health is in the lineage if it was stored
|
|
235
|
+
healthAtStop = lineage.loop_health ?? null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// Best effort
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
roundsCompleted: qualityTrajectory.length,
|
|
244
|
+
stopReason: finalStopReason,
|
|
245
|
+
healthAtStop,
|
|
246
|
+
qualityTrajectory,
|
|
247
|
+
success: finalStopReason === "task_complete",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=autonomous.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autonomous.js","sourceRoot":"","sources":["../src/autonomous.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** LoopForge — Anthropic API backend (v1.1).
|
|
2
|
+
*
|
|
3
|
+
* Calls Claude API via Node.js built-in fetch. Zero additional dependencies.
|
|
4
|
+
* Uses ANTHROPIC_API_KEY from environment. Falls back gracefully on failure.
|
|
5
|
+
*/
|
|
6
|
+
export interface AiConfig {
|
|
7
|
+
/** Anthropic API key. Default: process.env.ANTHROPIC_API_KEY */
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/** Model ID. Default: claude-sonnet-4-6 */
|
|
10
|
+
model?: string;
|
|
11
|
+
/** Max output tokens. Default: 8192 */
|
|
12
|
+
maxTokens?: number;
|
|
13
|
+
/** Base URL override (for proxies / alternative endpoints). */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
/** Timeout in ms. Default: 300_000 (5 min). */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface AiResult {
|
|
19
|
+
/** The model's text response, or null on failure. */
|
|
20
|
+
text: string | null;
|
|
21
|
+
/** True if the API call succeeded. */
|
|
22
|
+
ok: boolean;
|
|
23
|
+
/** Error message if !ok. */
|
|
24
|
+
error: string | null;
|
|
25
|
+
/** Model used. */
|
|
26
|
+
model: string;
|
|
27
|
+
}
|
|
28
|
+
/** Call Claude with a single user message. Returns the text response. */
|
|
29
|
+
export declare function callClaude(prompt: string, config?: AiConfig): Promise<AiResult>;
|
|
30
|
+
import type { AgentExecutor } from "../autonomous.js";
|
|
31
|
+
/** Create an AgentExecutor backed by Claude API.
|
|
32
|
+
* Usage:
|
|
33
|
+
* const executor = createClaudeExecutor({ apiKey: "sk-ant-..." });
|
|
34
|
+
* const result = await runAutonomousLoop(engine, config, executor);
|
|
35
|
+
*/
|
|
36
|
+
export declare function createClaudeExecutor(aiConfig?: AiConfig): AgentExecutor;
|
|
37
|
+
//# sourceMappingURL=ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/backends/ai.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,sCAAsC;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,4BAA4B;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,yEAAyE;AACzE,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,QAAa,GACpB,OAAO,CAAC,QAAQ,CAAC,CA+EnB;AAMD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,QAAa,GAAG,aAAa,CAQ3E"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** LoopForge — Anthropic API backend (v1.1).
|
|
2
|
+
*
|
|
3
|
+
* Calls Claude API via Node.js built-in fetch. Zero additional dependencies.
|
|
4
|
+
* Uses ANTHROPIC_API_KEY from environment. Falls back gracefully on failure.
|
|
5
|
+
*/
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
// API call
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
+
const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
|
|
10
|
+
/** Call Claude with a single user message. Returns the text response. */
|
|
11
|
+
export async function callClaude(prompt, config = {}) {
|
|
12
|
+
const apiKey = config.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
13
|
+
const model = config.model ?? process.env.LOOPFORGE_MODEL ?? "claude-sonnet-4-6";
|
|
14
|
+
const maxTokens = config.maxTokens ?? 8192;
|
|
15
|
+
const baseUrl = config.baseUrl ?? process.env.ANTHROPIC_BASE_URL ?? DEFAULT_BASE_URL;
|
|
16
|
+
const timeoutMs = config.timeoutMs ?? 300_000;
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
return {
|
|
19
|
+
text: null,
|
|
20
|
+
ok: false,
|
|
21
|
+
error: "ANTHROPIC_API_KEY not set. Export it or pass apiKey in config.",
|
|
22
|
+
model,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
|
+
try {
|
|
28
|
+
const resp = await fetch(`${baseUrl}/messages`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"x-api-key": apiKey,
|
|
32
|
+
"anthropic-version": "2023-06-01",
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
model,
|
|
37
|
+
max_tokens: maxTokens,
|
|
38
|
+
messages: [{ role: "user", content: prompt }],
|
|
39
|
+
}),
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
});
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
if (!resp.ok) {
|
|
44
|
+
const body = await resp.text().catch(() => "");
|
|
45
|
+
return {
|
|
46
|
+
text: null,
|
|
47
|
+
ok: false,
|
|
48
|
+
error: `API returned ${resp.status}: ${body.slice(0, 300)}`,
|
|
49
|
+
model,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const data = (await resp.json());
|
|
53
|
+
const content = data.content;
|
|
54
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
text: null, ok: false,
|
|
57
|
+
error: `Unexpected response shape: ${JSON.stringify(data).slice(0, 200)}`,
|
|
58
|
+
model,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const first = content[0];
|
|
62
|
+
const text = String(first.text ?? "");
|
|
63
|
+
if (!text) {
|
|
64
|
+
return {
|
|
65
|
+
text: null, ok: false,
|
|
66
|
+
error: "Empty response text from model.",
|
|
67
|
+
model,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { text, ok: true, error: null, model };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
75
|
+
return { text: null, ok: false, error: `Request timed out after ${timeoutMs}ms`, model };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
text: null, ok: false,
|
|
79
|
+
error: `Fetch error: ${err instanceof Error ? err.message : String(err)}`,
|
|
80
|
+
model,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Create an AgentExecutor backed by Claude API.
|
|
85
|
+
* Usage:
|
|
86
|
+
* const executor = createClaudeExecutor({ apiKey: "sk-ant-..." });
|
|
87
|
+
* const result = await runAutonomousLoop(engine, config, executor);
|
|
88
|
+
*/
|
|
89
|
+
export function createClaudeExecutor(aiConfig = {}) {
|
|
90
|
+
return async (prompt, _round) => {
|
|
91
|
+
const result = await callClaude(prompt, aiConfig);
|
|
92
|
+
if (!result.ok || result.text === null) {
|
|
93
|
+
throw new Error(`Claude API error (round ${_round}): ${result.error}`);
|
|
94
|
+
}
|
|
95
|
+
return result.text;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/backends/ai.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** LoopForge CLI — command-line interface for the loop compiler.
|
|
3
3
|
*
|
|
4
|
-
* v1.
|
|
4
|
+
* v1.2 — npm install loopforge
|
|
5
5
|
*/
|
|
6
6
|
import { createEngine } from "./engine.js";
|
|
7
7
|
import { FSBackend } from "./backends/fs.js";
|
|
8
8
|
import { ReplayBackend } from "./replay.js";
|
|
9
|
-
import { makeExecutionFeedback, } from "./protocol.js";
|
|
9
|
+
import { Mode, makeExecutionFeedback, } from "./protocol.js";
|
|
10
|
+
import { LoopRuntime } from "./runtime.js";
|
|
10
11
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
11
12
|
// Constants
|
|
12
13
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
-
const VERSION = "1.
|
|
14
|
+
const VERSION = "1.2.0";
|
|
14
15
|
const VAULT_CONFIG = {
|
|
15
16
|
project_vault: ".promptcraft/prompt_vault.json",
|
|
16
17
|
global_vault: "~/.promptcraft/global_vault.json",
|
|
@@ -23,6 +24,7 @@ Usage:
|
|
|
23
24
|
loopforge init Initialise .promptcraft vault
|
|
24
25
|
loopforge compile [json] Compile a loop prompt
|
|
25
26
|
loopforge feedback [json] Record execution feedback
|
|
27
|
+
loopforge run [json] Autonomous loop (event-driven runtime)
|
|
26
28
|
loopforge replay <loop-id> Show loop timeline
|
|
27
29
|
loopforge diff <loop-id> <a> <b> Diff two rounds
|
|
28
30
|
loopforge review <loop-id> <rN> Audit stored prompt
|
|
@@ -38,6 +40,7 @@ const CMD_HELP = {
|
|
|
38
40
|
init: "loopforge init — create .promptcraft/prompt_vault.json if it doesn't exist.",
|
|
39
41
|
compile: "loopforge compile '<json>' — run loop_compile (L0/L1/L2).\n Required: task, loop_id, goal_id. Optional: round, domain, constraints_from_plan, new_since_last_round, force_level.",
|
|
40
42
|
feedback: "loopforge feedback '<json>' — record execution result.\n Flat: {loop_id, round, success, score, output}\n Nested: {loop_id, round, feedback: {success, score, output}}",
|
|
43
|
+
run: "loopforge run '<json>' — autonomous loop (v1.2).\n Required: task, loop_id. Optional: max_rounds (default 20), plan_source, domain.\n Outputs compiled prompt → reads agent output from stdin → continues until task complete or circuit breaker.",
|
|
41
44
|
replay: "loopforge replay <loop-id> — show timeline: round, recompile level, quality, technique, task.",
|
|
42
45
|
diff: "loopforge diff <loop-id> <round-a> <round-b> — field-level comparison between two rounds.",
|
|
43
46
|
review: "loopforge review <loop-id> <round-num> — structural audit of a stored prompt.",
|
|
@@ -128,7 +131,7 @@ async function cmdCompile(engine, jsonArg) {
|
|
|
128
131
|
const data = parseJson(raw);
|
|
129
132
|
const result = engine.invokeLoopCompile({
|
|
130
133
|
task: data.task ?? "",
|
|
131
|
-
mode:
|
|
134
|
+
mode: Mode.LOOP_COMPILE,
|
|
132
135
|
vault_config: { ...VAULT_CONFIG },
|
|
133
136
|
feedback: null,
|
|
134
137
|
skill_name: null,
|
|
@@ -159,7 +162,7 @@ async function cmdFeedback(engine, jsonArg) {
|
|
|
159
162
|
}
|
|
160
163
|
const result = engine.invokeFeedback({
|
|
161
164
|
task: data.task ?? "",
|
|
162
|
-
mode:
|
|
165
|
+
mode: Mode.FEEDBACK,
|
|
163
166
|
vault_config: { ...VAULT_CONFIG },
|
|
164
167
|
feedback: makeExecutionFeedback({
|
|
165
168
|
output: fbPayload.output ?? "",
|
|
@@ -269,7 +272,7 @@ function cmdReview(loopId, roundStr) {
|
|
|
269
272
|
}
|
|
270
273
|
const result = engine.handleReview({
|
|
271
274
|
task: target.task ?? "review",
|
|
272
|
-
mode:
|
|
275
|
+
mode: Mode.REVIEW,
|
|
273
276
|
vault_config: { ...VAULT_CONFIG },
|
|
274
277
|
feedback: null,
|
|
275
278
|
skill_name: null,
|
|
@@ -277,6 +280,62 @@ function cmdReview(loopId, roundStr) {
|
|
|
277
280
|
}, { results: [target], global_entries: [] });
|
|
278
281
|
console.log(result.response?.prompt ?? "Review produced no output.");
|
|
279
282
|
}
|
|
283
|
+
async function cmdRun(_engine, jsonArg) {
|
|
284
|
+
if (jsonArg === "--help" || jsonArg === "-h") {
|
|
285
|
+
showHelp("run");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
let raw;
|
|
289
|
+
if (jsonArg) {
|
|
290
|
+
raw = jsonArg;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
raw = await readStdin();
|
|
294
|
+
}
|
|
295
|
+
if (!raw.trim())
|
|
296
|
+
die("run requires JSON input (argument or stdin)");
|
|
297
|
+
const data = parseJson(raw);
|
|
298
|
+
const task = data.task ?? "";
|
|
299
|
+
const loopId = data.loop_id ?? "";
|
|
300
|
+
if (!task)
|
|
301
|
+
die("run requires 'task' field");
|
|
302
|
+
if (!loopId)
|
|
303
|
+
die("run requires 'loop_id' field");
|
|
304
|
+
const maxRounds = data.max_rounds ?? 20;
|
|
305
|
+
const runtime = new LoopRuntime({
|
|
306
|
+
task,
|
|
307
|
+
loopId,
|
|
308
|
+
maxRounds,
|
|
309
|
+
interactive: true,
|
|
310
|
+
goalId: data.goal_id ?? undefined,
|
|
311
|
+
planSource: data.plan_source ?? undefined,
|
|
312
|
+
constraintsFromPlan: data.constraints_from_plan ?? [],
|
|
313
|
+
domain: data.domain ?? "",
|
|
314
|
+
onRoundStart: (info) => {
|
|
315
|
+
console.log(`\n=== Round ${info.round} | Level: ${info.level.toUpperCase()} | ` +
|
|
316
|
+
`Technique: ${info.technique} ===\n`);
|
|
317
|
+
console.log(info.prompt);
|
|
318
|
+
console.log(`\n=== End Round ${info.round} Prompt ===\n` +
|
|
319
|
+
`[Paste agent output, then press Ctrl+D (Unix) or Ctrl+Z (Windows) to continue]\n`);
|
|
320
|
+
},
|
|
321
|
+
execute: async (_prompt, _ctx) => {
|
|
322
|
+
const output = await readStdin();
|
|
323
|
+
if (!output.trim()) {
|
|
324
|
+
throw new Error("No agent output provided.");
|
|
325
|
+
}
|
|
326
|
+
return output;
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
const result = await runtime.start();
|
|
330
|
+
console.log(`\n--- Loop stopped: ${result.stopReason} ---`);
|
|
331
|
+
console.log(`Rounds: ${result.roundsCompleted} | ` +
|
|
332
|
+
`Quality: [${result.qualityTrajectory.join(", ")}]`);
|
|
333
|
+
if (result.stopReason === "stalled" && result.qualityTrajectory.length > 0) {
|
|
334
|
+
const lastRound = result.roundsCompleted;
|
|
335
|
+
console.log(`WARNING: Could not extract self-evaluation from agent output in round ${lastRound}. ` +
|
|
336
|
+
"Ensure the agent outputs a ---loopforge-eval block.");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
280
339
|
function cmdStatus() {
|
|
281
340
|
const backend = new FSBackend();
|
|
282
341
|
const vault = backend.readVault();
|
|
@@ -334,6 +393,9 @@ async function main() {
|
|
|
334
393
|
case "feedback":
|
|
335
394
|
await cmdFeedback(engine, args[1]);
|
|
336
395
|
break;
|
|
396
|
+
case "run":
|
|
397
|
+
await cmdRun(engine, args[1]);
|
|
398
|
+
break;
|
|
337
399
|
case "replay":
|
|
338
400
|
cmdReplay(args[1]);
|
|
339
401
|
break;
|