@polpo-ai/core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/approval-manager.d.ts +1 -0
  2. package/dist/approval-manager.d.ts.map +1 -1
  3. package/dist/approval-manager.js +7 -9
  4. package/dist/approval-manager.js.map +1 -1
  5. package/dist/index.d.ts +11 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +8 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/loop/contract.test.d.ts +2 -0
  10. package/dist/loop/contract.test.d.ts.map +1 -0
  11. package/dist/loop/contract.test.js +55 -0
  12. package/dist/loop/contract.test.js.map +1 -0
  13. package/dist/loop/expression.d.ts +29 -0
  14. package/dist/loop/expression.d.ts.map +1 -0
  15. package/dist/loop/expression.js +215 -0
  16. package/dist/loop/expression.js.map +1 -0
  17. package/dist/loop/expression.test.d.ts +2 -0
  18. package/dist/loop/expression.test.d.ts.map +1 -0
  19. package/dist/loop/expression.test.js +55 -0
  20. package/dist/loop/expression.test.js.map +1 -0
  21. package/dist/loop/hooks.d.ts +132 -0
  22. package/dist/loop/hooks.d.ts.map +1 -0
  23. package/dist/loop/hooks.js +85 -0
  24. package/dist/loop/hooks.js.map +1 -0
  25. package/dist/loop/pipeline.d.ts +38 -0
  26. package/dist/loop/pipeline.d.ts.map +1 -0
  27. package/dist/loop/pipeline.js +101 -0
  28. package/dist/loop/pipeline.js.map +1 -0
  29. package/dist/loop/pipeline.test.d.ts +2 -0
  30. package/dist/loop/pipeline.test.d.ts.map +1 -0
  31. package/dist/loop/pipeline.test.js +103 -0
  32. package/dist/loop/pipeline.test.js.map +1 -0
  33. package/dist/loop/runner.d.ts +44 -0
  34. package/dist/loop/runner.d.ts.map +1 -0
  35. package/dist/loop/runner.js +215 -0
  36. package/dist/loop/runner.js.map +1 -0
  37. package/dist/loop/runner.test.d.ts +2 -0
  38. package/dist/loop/runner.test.d.ts.map +1 -0
  39. package/dist/loop/runner.test.js +124 -0
  40. package/dist/loop/runner.test.js.map +1 -0
  41. package/dist/loop/selector.d.ts +12 -0
  42. package/dist/loop/selector.d.ts.map +1 -0
  43. package/dist/loop/selector.js +44 -0
  44. package/dist/loop/selector.js.map +1 -0
  45. package/dist/loop/selector.test.d.ts +2 -0
  46. package/dist/loop/selector.test.d.ts.map +1 -0
  47. package/dist/loop/selector.test.js +45 -0
  48. package/dist/loop/selector.test.js.map +1 -0
  49. package/dist/loop/types.d.ts +98 -0
  50. package/dist/loop/types.d.ts.map +1 -0
  51. package/dist/loop/types.js +15 -0
  52. package/dist/loop/types.js.map +1 -0
  53. package/dist/schemas.d.ts +55 -0
  54. package/dist/schemas.d.ts.map +1 -1
  55. package/dist/schemas.js +99 -0
  56. package/dist/schemas.js.map +1 -1
  57. package/dist/types.d.ts +7 -0
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js +0 -1
  60. package/dist/types.js.map +1 -1
  61. package/package.json +1 -1
@@ -0,0 +1,215 @@
1
+ import { SafeExpressionEvaluator } from "./expression.js";
2
+ import { LoopHookRegistry, } from "./hooks.js";
3
+ const DEFAULT_MAX_TURNS = 20;
4
+ function normalizeLoop(loop) {
5
+ return {
6
+ ...loop,
7
+ name: loop.name ?? "default",
8
+ };
9
+ }
10
+ function shouldStopAfterTurn(loop, context, result, turn, maxTurns, evaluator) {
11
+ const reachedMaxTurns = turn + 1 >= maxTurns;
12
+ if (loop.stopWhen) {
13
+ const matched = evaluator.evaluate(loop.stopWhen.expression, context);
14
+ if (matched)
15
+ return { reason: "completed", shouldStop: true };
16
+ return reachedMaxTurns ? { reason: "max_turns", shouldStop: true } : { reason: "completed", shouldStop: false };
17
+ }
18
+ if ((result.toolCalls ?? []).length === 0)
19
+ return { reason: "completed", shouldStop: true };
20
+ if (reachedMaxTurns)
21
+ return { reason: "max_turns", shouldStop: true };
22
+ return { reason: "completed", shouldStop: false };
23
+ }
24
+ export class LoopRunner {
25
+ hooks;
26
+ evaluator = new SafeExpressionEvaluator();
27
+ constructor(hooks) {
28
+ this.hooks = hooks ?? new LoopHookRegistry();
29
+ }
30
+ async run(options) {
31
+ const hooks = options.hooks ?? this.hooks;
32
+ const loop = normalizeLoop(options.loop);
33
+ const context = options.context ?? {};
34
+ const maxTurns = options.maxTurns ?? loop.maxTurns ?? DEFAULT_MAX_TURNS;
35
+ let finalText = "";
36
+ let turns = 0;
37
+ const allToolResults = [];
38
+ const start = await hooks.runBefore("loop:start", {
39
+ agent: options.agent,
40
+ loop,
41
+ context,
42
+ input: options.input,
43
+ });
44
+ if (start.cancelled) {
45
+ return this.finish(hooks, {
46
+ agent: options.agent,
47
+ loop,
48
+ context,
49
+ status: "cancelled",
50
+ reason: "cancelled",
51
+ turns,
52
+ text: finalText,
53
+ toolResults: allToolResults,
54
+ });
55
+ }
56
+ for (let turn = 0; turn < maxTurns; turn++) {
57
+ turns = turn + 1;
58
+ const step = await hooks.runBefore("step:before", {
59
+ agent: options.agent,
60
+ loop,
61
+ turn,
62
+ context,
63
+ activeTools: loop.tools,
64
+ });
65
+ if (step.cancelled) {
66
+ return this.finish(hooks, {
67
+ agent: options.agent,
68
+ loop,
69
+ context,
70
+ status: "cancelled",
71
+ reason: "cancelled",
72
+ turns,
73
+ text: finalText,
74
+ toolResults: allToolResults,
75
+ });
76
+ }
77
+ const modelBefore = await hooks.runBefore("model:before", {
78
+ agent: options.agent,
79
+ loop,
80
+ turn,
81
+ context,
82
+ activeTools: step.data.activeTools,
83
+ toolChoice: step.data.toolChoice,
84
+ });
85
+ if (modelBefore.cancelled) {
86
+ return this.finish(hooks, {
87
+ agent: options.agent,
88
+ loop,
89
+ context,
90
+ status: "cancelled",
91
+ reason: "cancelled",
92
+ turns,
93
+ text: finalText,
94
+ toolResults: allToolResults,
95
+ });
96
+ }
97
+ const modelInput = {
98
+ agent: options.agent,
99
+ loop,
100
+ turn,
101
+ context,
102
+ activeTools: modelBefore.data.activeTools,
103
+ toolChoice: modelBefore.data.toolChoice,
104
+ };
105
+ const modelResult = await options.model(modelInput);
106
+ finalText += modelResult.text;
107
+ const toolResults = [];
108
+ for (const rawCall of modelResult.toolCalls ?? []) {
109
+ const before = await hooks.runBefore("tool:before", {
110
+ agent: options.agent,
111
+ loop,
112
+ turn,
113
+ context,
114
+ toolCall: { ...rawCall, args: { ...rawCall.args } },
115
+ });
116
+ const toolCall = before.data.toolCall;
117
+ const skipped = before.cancelled;
118
+ const result = skipped
119
+ ? before.data.result ?? `Error: Tool call "${toolCall.name}" denied${before.cancelReason ? `: ${before.cancelReason}` : ""}`
120
+ : await options.executeTool(toolCall, modelInput);
121
+ const toolResult = {
122
+ toolCall,
123
+ result,
124
+ isError: result.startsWith("Error:"),
125
+ skipped,
126
+ };
127
+ toolResults.push(toolResult);
128
+ allToolResults.push(toolResult);
129
+ await hooks.runAfter("tool:after", {
130
+ agent: options.agent,
131
+ loop,
132
+ turn,
133
+ context,
134
+ toolCall,
135
+ result,
136
+ isError: toolResult.isError,
137
+ skipped,
138
+ });
139
+ }
140
+ await hooks.runAfter("step:after", {
141
+ agent: options.agent,
142
+ loop,
143
+ turn,
144
+ context,
145
+ text: modelResult.text,
146
+ toolCalls: modelResult.toolCalls ?? [],
147
+ toolResults,
148
+ usage: modelResult.usage,
149
+ });
150
+ const stop = shouldStopAfterTurn(loop, context, modelResult, turn, maxTurns, this.evaluator);
151
+ const stopResult = await hooks.runBefore("loop:stop", {
152
+ agent: options.agent,
153
+ loop,
154
+ turn,
155
+ context,
156
+ reason: stop.reason,
157
+ shouldStop: stop.shouldStop,
158
+ });
159
+ if (stopResult.cancelled) {
160
+ return this.finish(hooks, {
161
+ agent: options.agent,
162
+ loop,
163
+ context,
164
+ status: "cancelled",
165
+ reason: "cancelled",
166
+ turns,
167
+ text: finalText,
168
+ toolResults: allToolResults,
169
+ });
170
+ }
171
+ if (stopResult.data.shouldStop) {
172
+ return this.finish(hooks, {
173
+ agent: options.agent,
174
+ loop,
175
+ context,
176
+ status: "completed",
177
+ reason: stopResult.data.reason,
178
+ turns,
179
+ text: finalText,
180
+ toolResults: allToolResults,
181
+ });
182
+ }
183
+ }
184
+ return this.finish(hooks, {
185
+ agent: options.agent,
186
+ loop,
187
+ context,
188
+ status: "completed",
189
+ reason: "max_turns",
190
+ turns,
191
+ text: finalText,
192
+ toolResults: allToolResults,
193
+ });
194
+ }
195
+ async finish(hooks, result) {
196
+ await hooks.runAfter("loop:end", {
197
+ agent: result.agent,
198
+ loop: result.loop,
199
+ context: result.context,
200
+ status: result.status,
201
+ reason: result.reason,
202
+ turns: result.turns,
203
+ text: result.text,
204
+ });
205
+ return {
206
+ status: result.status,
207
+ reason: result.reason,
208
+ turns: result.turns,
209
+ text: result.text,
210
+ context: result.context,
211
+ toolResults: result.toolResults,
212
+ };
213
+ }
214
+ }
215
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/loop/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.test.d.ts","sourceRoot":"","sources":["../../src/loop/runner.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { LoopHookRegistry } from "./hooks.js";
3
+ import { LoopRunner } from "./runner.js";
4
+ describe("LoopRunner", () => {
5
+ it("runs the implicit loop with ordered lifecycle hooks", async () => {
6
+ const events = [];
7
+ const hooks = new LoopHookRegistry();
8
+ hooks.register({ hook: "loop:start", phase: "before", handler: () => { events.push("loop:start"); } });
9
+ hooks.register({ hook: "step:before", phase: "before", handler: () => { events.push("step:before"); } });
10
+ hooks.register({ hook: "model:before", phase: "before", handler: () => { events.push("model:before"); } });
11
+ hooks.register({ hook: "step:after", phase: "after", handler: () => { events.push("step:after"); } });
12
+ hooks.register({ hook: "loop:stop", phase: "before", handler: () => { events.push("loop:stop"); } });
13
+ hooks.register({ hook: "loop:end", phase: "after", handler: () => { events.push("loop:end"); } });
14
+ const runner = new LoopRunner(hooks);
15
+ const result = await runner.run({
16
+ loop: { name: "default" },
17
+ model: async () => ({ text: "done" }),
18
+ executeTool: async () => "unused",
19
+ });
20
+ expect(result).toMatchObject({ status: "completed", reason: "completed", turns: 1, text: "done" });
21
+ expect(events).toEqual(["loop:start", "step:before", "model:before", "step:after", "loop:stop", "loop:end"]);
22
+ });
23
+ it("lets step hooks constrain active tools before the model call", async () => {
24
+ const hooks = new LoopHookRegistry();
25
+ hooks.register({
26
+ hook: "step:before",
27
+ phase: "before",
28
+ handler: (ctx) => {
29
+ ctx.data.activeTools = ["read"];
30
+ },
31
+ });
32
+ const seenTools = [];
33
+ const runner = new LoopRunner(hooks);
34
+ await runner.run({
35
+ loop: { name: "plan", tools: ["read", "write"] },
36
+ model: async (input) => {
37
+ seenTools.push(input.activeTools);
38
+ return { text: "planned" };
39
+ },
40
+ executeTool: async () => "unused",
41
+ });
42
+ expect(seenTools).toEqual([["read"]]);
43
+ });
44
+ it("allows tool:before hooks to deny a tool call without invoking the executor", async () => {
45
+ const hooks = new LoopHookRegistry();
46
+ hooks.register({
47
+ hook: "tool:before",
48
+ phase: "before",
49
+ handler: (ctx) => {
50
+ if (ctx.data.toolCall.name === "write")
51
+ ctx.cancel("read-only loop");
52
+ },
53
+ });
54
+ let executed = false;
55
+ const runner = new LoopRunner(hooks);
56
+ const result = await runner.run({
57
+ loop: { name: "plan", tools: ["read"] },
58
+ maxTurns: 1,
59
+ model: async () => ({
60
+ text: "I will write.",
61
+ toolCalls: [{ id: "call-1", name: "write", args: { path: "x.txt" } }],
62
+ }),
63
+ executeTool: async () => {
64
+ executed = true;
65
+ return "should not run";
66
+ },
67
+ });
68
+ expect(executed).toBe(false);
69
+ expect(result.toolResults).toHaveLength(1);
70
+ expect(result.toolResults[0]).toMatchObject({
71
+ skipped: true,
72
+ isError: true,
73
+ result: 'Error: Tool call "write" denied: read-only loop',
74
+ });
75
+ });
76
+ it("continues when loop:stop mutates shouldStop to false", async () => {
77
+ const hooks = new LoopHookRegistry();
78
+ hooks.register({
79
+ hook: "loop:stop",
80
+ phase: "before",
81
+ handler: (ctx) => {
82
+ if (ctx.data.turn === 0)
83
+ ctx.data.shouldStop = false;
84
+ },
85
+ });
86
+ let calls = 0;
87
+ const runner = new LoopRunner(hooks);
88
+ const result = await runner.run({
89
+ loop: { name: "review" },
90
+ maxTurns: 2,
91
+ model: async () => {
92
+ calls++;
93
+ return { text: String(calls) };
94
+ },
95
+ executeTool: async () => "unused",
96
+ });
97
+ expect(result.text).toBe("12");
98
+ expect(result.turns).toBe(2);
99
+ expect(result.reason).toBe("completed");
100
+ });
101
+ it("uses stopWhen as the deterministic stop condition when configured", async () => {
102
+ const hooks = new LoopHookRegistry();
103
+ hooks.register({
104
+ hook: "step:after",
105
+ phase: "after",
106
+ handler: (ctx) => {
107
+ if (ctx.data.turn === 1)
108
+ ctx.data.context.done = true;
109
+ },
110
+ });
111
+ const runner = new LoopRunner(hooks);
112
+ const result = await runner.run({
113
+ loop: { name: "build", stopWhen: { expression: "done == true" } },
114
+ maxTurns: 3,
115
+ model: async () => ({ text: "tick" }),
116
+ executeTool: async () => "unused",
117
+ });
118
+ expect(result.text).toBe("ticktick");
119
+ expect(result.turns).toBe(2);
120
+ expect(result.reason).toBe("completed");
121
+ expect(result.context.done).toBe(true);
122
+ });
123
+ });
124
+ //# sourceMappingURL=runner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../src/loop/runner.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { AgentConfig } from "../types.js";
2
+ import type { LoopConfig } from "./types.js";
3
+ export interface LoopSelection {
4
+ name: string;
5
+ loop: LoopConfig & {
6
+ name: string;
7
+ };
8
+ agent: AgentConfig;
9
+ }
10
+ export declare function resolveActiveLoopTools(agent: AgentConfig, loop?: LoopConfig): string[] | undefined;
11
+ export declare function resolveLoopSelection(agent: AgentConfig, requestedLoop?: string): LoopSelection;
12
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/loop/selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,SAAS,CAElG;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CAwB9F"}
@@ -0,0 +1,44 @@
1
+ export function resolveActiveLoopTools(agent, loop) {
2
+ return loop?.tools ?? agent.allowedTools;
3
+ }
4
+ export function resolveLoopSelection(agent, requestedLoop) {
5
+ if (!requestedLoop) {
6
+ const defaultLoop = agent.loops?.default;
7
+ if (!defaultLoop) {
8
+ return {
9
+ name: "default",
10
+ loop: { name: "default", tools: agent.allowedTools, model: agent.model, reasoning: agent.reasoning, maxTurns: agent.maxTurns },
11
+ agent,
12
+ };
13
+ }
14
+ return materializeLoopSelection(agent, "default", defaultLoop);
15
+ }
16
+ const loop = agent.loops?.[requestedLoop];
17
+ if (!loop) {
18
+ const available = Object.keys(agent.loops ?? {});
19
+ throw new Error(available.length > 0
20
+ ? `Unknown loop "${requestedLoop}". Available loops: ${available.join(", ")}`
21
+ : `Agent "${agent.name}" does not define configurable loops`);
22
+ }
23
+ return materializeLoopSelection(agent, requestedLoop, loop);
24
+ }
25
+ function materializeLoopSelection(agent, name, loop) {
26
+ const loopWithName = { ...loop, name: loop.name ?? name };
27
+ const loopPrompt = loop.systemPrompt?.trim();
28
+ const systemPrompt = loopPrompt
29
+ ? [agent.systemPrompt, `## Active loop: ${loopWithName.name}`, loopPrompt].filter(Boolean).join("\n\n")
30
+ : agent.systemPrompt;
31
+ return {
32
+ name: loopWithName.name,
33
+ loop: loopWithName,
34
+ agent: {
35
+ ...agent,
36
+ systemPrompt,
37
+ allowedTools: resolveActiveLoopTools(agent, loop),
38
+ model: loop.model ?? agent.model,
39
+ reasoning: loop.reasoning ?? agent.reasoning,
40
+ maxTurns: loop.maxTurns ?? agent.maxTurns,
41
+ },
42
+ };
43
+ }
44
+ //# sourceMappingURL=selector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/loop/selector.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,sBAAsB,CAAC,KAAkB,EAAE,IAAiB;IAC1E,OAAO,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,YAAY,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAkB,EAAE,aAAsB;IAC7E,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC;QACzC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO;gBACL,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;gBAC9H,KAAK;aACN,CAAC;QACJ,CAAC;QACD,OAAO,wBAAwB,CAAC,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,aAAa,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,SAAS,CAAC,MAAM,GAAG,CAAC;YAClB,CAAC,CAAC,iBAAiB,aAAa,uBAAuB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAC7E,CAAC,CAAC,UAAU,KAAK,CAAC,IAAI,sCAAsC,CAC/D,CAAC;IACJ,CAAC;IAED,OAAO,wBAAwB,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAkB,EAAE,IAAY,EAAE,IAAgB;IAClF,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,YAAY,GAAG,UAAU;QAC7B,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,EAAE,mBAAmB,YAAY,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;QACvG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;IAEvB,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE;YACL,GAAG,KAAK;YACR,YAAY;YACZ,YAAY,EAAE,sBAAsB,CAAC,KAAK,EAAE,IAAI,CAAC;YACjD,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK;YAChC,SAAS,EAAG,IAAI,CAAC,SAAwC,IAAI,KAAK,CAAC,SAAS;YAC5E,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ;SAC1C;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=selector.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.test.d.ts","sourceRoot":"","sources":["../../src/loop/selector.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveLoopSelection } from "./selector.js";
3
+ describe("resolveLoopSelection", () => {
4
+ it("keeps current agent behavior when no loop is requested", () => {
5
+ const agent = { name: "agent", model: "base", allowedTools: ["read"], systemPrompt: "base" };
6
+ const selected = resolveLoopSelection(agent);
7
+ expect(selected.name).toBe("default");
8
+ expect(selected.agent).toBe(agent);
9
+ });
10
+ it("applies requested loop overrides to the effective agent", () => {
11
+ const selected = resolveLoopSelection({
12
+ name: "agent",
13
+ model: "base",
14
+ reasoning: "low",
15
+ allowedTools: ["read", "write"],
16
+ systemPrompt: "base prompt",
17
+ loops: {
18
+ plan: {
19
+ systemPrompt: "plan prompt",
20
+ tools: ["read"],
21
+ model: "planner",
22
+ reasoning: "high",
23
+ maxTurns: 3,
24
+ },
25
+ },
26
+ }, "plan");
27
+ expect(selected.loop.name).toBe("plan");
28
+ expect(selected.agent).toMatchObject({
29
+ model: "planner",
30
+ reasoning: "high",
31
+ allowedTools: ["read"],
32
+ maxTurns: 3,
33
+ });
34
+ expect(selected.agent.systemPrompt).toContain("base prompt");
35
+ expect(selected.agent.systemPrompt).toContain("## Active loop: plan");
36
+ expect(selected.agent.systemPrompt).toContain("plan prompt");
37
+ });
38
+ it("throws a clear error for unknown requested loops", () => {
39
+ expect(() => resolveLoopSelection({
40
+ name: "agent",
41
+ loops: { plan: {} },
42
+ }, "build")).toThrow('Unknown loop "build". Available loops: plan');
43
+ });
44
+ });
45
+ //# sourceMappingURL=selector.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.test.js","sourceRoot":"","sources":["../../src/loop/selector.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;QAC7F,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAE7C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,QAAQ,GAAG,oBAAoB,CAAC;YACpC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,KAAK;YAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAC/B,YAAY,EAAE,aAAa;YAC3B,KAAK,EAAE;gBACL,IAAI,EAAE;oBACJ,YAAY,EAAE,aAAa;oBAC3B,KAAK,EAAE,CAAC,MAAM,CAAC;oBACf,KAAK,EAAE,SAAS;oBAChB,SAAS,EAAE,MAAM;oBACjB,QAAQ,EAAE,CAAC;iBACZ;aACF;SACF,EAAE,MAAM,CAAC,CAAC;QAEX,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC;YACnC,KAAK,EAAE,SAAS;YAChB,SAAS,EAAE,MAAM;YACjB,YAAY,EAAE,CAAC,MAAM,CAAC;YACtB,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC7D,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACtE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC;YAChC,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;SACpB,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Configurable agentic loops — config model (declarative surface).
3
+ *
4
+ * An agent has a collection of named loops (one active at a time) and an
5
+ * optional pipeline: a tree of heterogeneous steps with deterministic
6
+ * conditions over a shared, read-only context bag. Step kinds: loop (LLM),
7
+ * switch (deterministic branch), parallel (fork-join), human (structured human
8
+ * decision). See the loop-architecture design.
9
+ */
10
+ export interface Condition {
11
+ /** Expression over the context bag, evaluated by SafeExpressionEvaluator. */
12
+ expression: string;
13
+ }
14
+ export type ContextBag = Record<string, unknown>;
15
+ export interface LoopConfig {
16
+ /** Optional — the loops-map key is the canonical name. */
17
+ name?: string;
18
+ systemPrompt?: string;
19
+ /** Tool subset active in this loop (restriction = determinism/safety). */
20
+ tools?: string[];
21
+ model?: string;
22
+ reasoning?: string;
23
+ temperature?: number;
24
+ maxTurns?: number;
25
+ /** Deterministic stop condition over the context bag. */
26
+ stopWhen?: Condition;
27
+ /** Structured output contract — the loop's typed deliverable. */
28
+ output?: {
29
+ schema?: unknown;
30
+ };
31
+ }
32
+ export interface SwitchCase {
33
+ when: string;
34
+ steps: Step[];
35
+ }
36
+ export type Step = {
37
+ loop: string;
38
+ when?: string;
39
+ } | {
40
+ parallel: Step[];
41
+ join?: "all" | "any" | number;
42
+ when?: string;
43
+ } | {
44
+ switch: {
45
+ cases: SwitchCase[];
46
+ default?: {
47
+ steps: Step[];
48
+ };
49
+ };
50
+ when?: string;
51
+ } | {
52
+ human: string;
53
+ output?: {
54
+ schema?: unknown;
55
+ };
56
+ notify?: string[];
57
+ when?: string;
58
+ };
59
+ export interface Pipeline {
60
+ mode?: "sequential" | "parallel";
61
+ context?: "shared";
62
+ steps: Step[];
63
+ }
64
+ export interface AgentLoopConfig {
65
+ name?: string;
66
+ model?: string;
67
+ /** Out-of-scope runtime/environment ref (default polpo-runner; custom image later). */
68
+ runtime?: string;
69
+ loops: Record<string, LoopConfig>;
70
+ pipeline?: Pipeline;
71
+ }
72
+ export declare const isLoopStep: (s: Step) => s is {
73
+ loop: string;
74
+ when?: string;
75
+ };
76
+ export declare const isParallelStep: (s: Step) => s is {
77
+ parallel: Step[];
78
+ join?: "all" | "any" | number;
79
+ when?: string;
80
+ };
81
+ export declare const isSwitchStep: (s: Step) => s is {
82
+ switch: {
83
+ cases: SwitchCase[];
84
+ default?: {
85
+ steps: Step[];
86
+ };
87
+ };
88
+ when?: string;
89
+ };
90
+ export declare const isHumanStep: (s: Step) => s is {
91
+ human: string;
92
+ output?: {
93
+ schema?: unknown;
94
+ };
95
+ notify?: string[];
96
+ when?: string;
97
+ };
98
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/loop/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Configurable agentic loops — config model (declarative surface).
3
+ *
4
+ * An agent has a collection of named loops (one active at a time) and an
5
+ * optional pipeline: a tree of heterogeneous steps with deterministic
6
+ * conditions over a shared, read-only context bag. Step kinds: loop (LLM),
7
+ * switch (deterministic branch), parallel (fork-join), human (structured human
8
+ * decision). See the loop-architecture design.
9
+ */
10
+ // ─── Step type guards ────────────────────────────────────
11
+ export const isLoopStep = (s) => "loop" in s;
12
+ export const isParallelStep = (s) => "parallel" in s;
13
+ export const isSwitchStep = (s) => "switch" in s;
14
+ export const isHumanStep = (s) => "human" in s;
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/loop/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmDH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAO,EAAwC,EAAE,CAAC,MAAM,IAAI,CAAC,CAAC;AACzF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAO,EAA2E,EAAE,CACjH,UAAU,IAAI,CAAC,CAAC;AAClB,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,CAAO,EAC+E,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC;AACzG,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,CAAO,EACkF,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC"}