@noetic-tools/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
  4. package/dist/adapters/in-memory-fs-adapter.js +245 -0
  5. package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
  6. package/dist/adapters/in-memory-shell-adapter.js +20 -0
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
  8. package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
  9. package/dist/adapters/local-fs-adapter.d.ts +3 -0
  10. package/dist/adapters/local-fs-adapter.js +43 -0
  11. package/dist/adapters/local-shell-adapter.d.ts +51 -0
  12. package/dist/adapters/local-shell-adapter.js +167 -0
  13. package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
  14. package/dist/adapters/local-subprocess-adapter.js +187 -0
  15. package/dist/adapters/openrouter.d.ts +57 -0
  16. package/dist/adapters/openrouter.js +248 -0
  17. package/dist/builders/channel-builder.d.ts +36 -0
  18. package/dist/builders/channel-builder.js +15 -0
  19. package/dist/builders/control-flow-builders.d.ts +79 -0
  20. package/dist/builders/control-flow-builders.js +53 -0
  21. package/dist/builders/every.d.ts +45 -0
  22. package/dist/builders/every.js +60 -0
  23. package/dist/builders/layer-provides-builders.d.ts +32 -0
  24. package/dist/builders/layer-provides-builders.js +33 -0
  25. package/dist/builders/loop-builder.d.ts +23 -0
  26. package/dist/builders/loop-builder.js +46 -0
  27. package/dist/builders/memory-builder.d.ts +11 -0
  28. package/dist/builders/memory-builder.js +15 -0
  29. package/dist/builders/provide-builder.d.ts +20 -0
  30. package/dist/builders/provide-builder.js +34 -0
  31. package/dist/builders/spawn-builder.d.ts +20 -0
  32. package/dist/builders/spawn-builder.js +33 -0
  33. package/dist/builders/step-builders.d.ts +62 -0
  34. package/dist/builders/step-builders.js +99 -0
  35. package/dist/builders/tool-builder.d.ts +52 -0
  36. package/dist/builders/tool-builder.js +59 -0
  37. package/dist/conditions/conditions.d.ts +114 -0
  38. package/dist/conditions/conditions.js +271 -0
  39. package/dist/conditions/cosine-similarity.d.ts +11 -0
  40. package/dist/conditions/cosine-similarity.js +28 -0
  41. package/dist/conditions/index.d.ts +3 -0
  42. package/dist/conditions/index.js +2 -0
  43. package/dist/errors/noetic-config-error.d.ts +41 -0
  44. package/dist/errors/noetic-config-error.js +44 -0
  45. package/dist/errors/noetic-error.d.ts +18 -0
  46. package/dist/errors/noetic-error.js +52 -0
  47. package/dist/index.d.ts +152 -0
  48. package/dist/index.js +112 -0
  49. package/dist/internal-test.d.ts +11 -0
  50. package/dist/internal-test.js +11 -0
  51. package/dist/interpreter/clone-guard.d.ts +4 -0
  52. package/dist/interpreter/clone-guard.js +18 -0
  53. package/dist/interpreter/collect-tools.d.ts +12 -0
  54. package/dist/interpreter/collect-tools.js +70 -0
  55. package/dist/interpreter/execute-branch.d.ts +3 -0
  56. package/dist/interpreter/execute-branch.js +10 -0
  57. package/dist/interpreter/execute-every.d.ts +15 -0
  58. package/dist/interpreter/execute-every.js +143 -0
  59. package/dist/interpreter/execute-fork.d.ts +3 -0
  60. package/dist/interpreter/execute-fork.js +215 -0
  61. package/dist/interpreter/execute-llm.d.ts +4 -0
  62. package/dist/interpreter/execute-llm.js +217 -0
  63. package/dist/interpreter/execute-loop.d.ts +3 -0
  64. package/dist/interpreter/execute-loop.js +171 -0
  65. package/dist/interpreter/execute-provide.d.ts +10 -0
  66. package/dist/interpreter/execute-provide.js +64 -0
  67. package/dist/interpreter/execute-run.d.ts +3 -0
  68. package/dist/interpreter/execute-run.js +39 -0
  69. package/dist/interpreter/execute-spawn.d.ts +11 -0
  70. package/dist/interpreter/execute-spawn.js +113 -0
  71. package/dist/interpreter/execute-tool.d.ts +5 -0
  72. package/dist/interpreter/execute-tool.js +81 -0
  73. package/dist/interpreter/execute.d.ts +14 -0
  74. package/dist/interpreter/execute.js +119 -0
  75. package/dist/interpreter/framework-cast.d.ts +10 -0
  76. package/dist/interpreter/framework-cast.js +16 -0
  77. package/dist/interpreter/message-helpers.d.ts +9 -0
  78. package/dist/interpreter/message-helpers.js +42 -0
  79. package/dist/interpreter/typeguards.d.ts +25 -0
  80. package/dist/interpreter/typeguards.js +50 -0
  81. package/dist/memory/budget.d.ts +23 -0
  82. package/dist/memory/budget.js +117 -0
  83. package/dist/memory/function-call-utils.d.ts +6 -0
  84. package/dist/memory/function-call-utils.js +25 -0
  85. package/dist/memory/layer-api.d.ts +14 -0
  86. package/dist/memory/layer-api.js +91 -0
  87. package/dist/memory/layer-lifecycle.d.ts +160 -0
  88. package/dist/memory/layer-lifecycle.js +552 -0
  89. package/dist/memory/layer-usage.d.ts +15 -0
  90. package/dist/memory/layer-usage.js +48 -0
  91. package/dist/memory/layers/durable-task-state.d.ts +79 -0
  92. package/dist/memory/layers/durable-task-state.js +118 -0
  93. package/dist/memory/layers/file-reference.d.ts +55 -0
  94. package/dist/memory/layers/file-reference.js +538 -0
  95. package/dist/memory/layers/history-window.d.ts +29 -0
  96. package/dist/memory/layers/history-window.js +114 -0
  97. package/dist/memory/layers/observational-memory.d.ts +53 -0
  98. package/dist/memory/layers/observational-memory.js +110 -0
  99. package/dist/memory/layers/plan.d.ts +55 -0
  100. package/dist/memory/layers/plan.js +369 -0
  101. package/dist/memory/layers/static-content.d.ts +27 -0
  102. package/dist/memory/layers/static-content.js +35 -0
  103. package/dist/memory/layers/steering.d.ts +51 -0
  104. package/dist/memory/layers/steering.js +258 -0
  105. package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
  106. package/dist/memory/layers/tool-memory-layer.js +43 -0
  107. package/dist/memory/layers/working-memory.d.ts +44 -0
  108. package/dist/memory/layers/working-memory.js +92 -0
  109. package/dist/memory/projector.d.ts +10 -0
  110. package/dist/memory/projector.js +19 -0
  111. package/dist/memory/scope.d.ts +3 -0
  112. package/dist/memory/scope.js +31 -0
  113. package/dist/memory/strip-unresolved.d.ts +13 -0
  114. package/dist/memory/strip-unresolved.js +35 -0
  115. package/dist/observability/genai-attributes.d.ts +11 -0
  116. package/dist/observability/genai-attributes.js +12 -0
  117. package/dist/observability/span-impl.d.ts +20 -0
  118. package/dist/observability/span-impl.js +33 -0
  119. package/dist/observability/trace-exporter.d.ts +15 -0
  120. package/dist/observability/trace-exporter.js +26 -0
  121. package/dist/patterns/flow.d.ts +52 -0
  122. package/dist/patterns/flow.js +103 -0
  123. package/dist/patterns/interview.d.ts +53 -0
  124. package/dist/patterns/interview.js +113 -0
  125. package/dist/patterns/plans.d.ts +45 -0
  126. package/dist/patterns/plans.js +125 -0
  127. package/dist/patterns/ralph-wiggum.d.ts +20 -0
  128. package/dist/patterns/ralph-wiggum.js +37 -0
  129. package/dist/patterns/react.d.ts +18 -0
  130. package/dist/patterns/react.js +39 -0
  131. package/dist/portable.d.ts +26 -0
  132. package/dist/portable.js +34 -0
  133. package/dist/runtime/agent-harness.d.ts +206 -0
  134. package/dist/runtime/agent-harness.js +1170 -0
  135. package/dist/runtime/broadcaster-utils.d.ts +30 -0
  136. package/dist/runtime/broadcaster-utils.js +53 -0
  137. package/dist/runtime/channel-store.d.ts +18 -0
  138. package/dist/runtime/channel-store.js +187 -0
  139. package/dist/runtime/context-impl.d.ts +80 -0
  140. package/dist/runtime/context-impl.js +140 -0
  141. package/dist/runtime/cwd-helpers.d.ts +42 -0
  142. package/dist/runtime/cwd-helpers.js +68 -0
  143. package/dist/runtime/detached-handle.d.ts +14 -0
  144. package/dist/runtime/detached-handle.js +56 -0
  145. package/dist/runtime/event-broadcaster.d.ts +61 -0
  146. package/dist/runtime/event-broadcaster.js +187 -0
  147. package/dist/runtime/exec-context-factory.d.ts +25 -0
  148. package/dist/runtime/exec-context-factory.js +30 -0
  149. package/dist/runtime/in-memory-storage.d.ts +9 -0
  150. package/dist/runtime/in-memory-storage.js +33 -0
  151. package/dist/runtime/item-log-impl.d.ts +11 -0
  152. package/dist/runtime/item-log-impl.js +25 -0
  153. package/dist/runtime/message-queue.d.ts +30 -0
  154. package/dist/runtime/message-queue.js +46 -0
  155. package/dist/runtime/session-runner.d.ts +77 -0
  156. package/dist/runtime/session-runner.js +253 -0
  157. package/dist/runtime/session-streams.d.ts +11 -0
  158. package/dist/runtime/session-streams.js +204 -0
  159. package/dist/runtime/tool-memory.d.ts +6 -0
  160. package/dist/runtime/tool-memory.js +22 -0
  161. package/dist/schemas/item.d.ts +35 -0
  162. package/dist/schemas/item.js +155 -0
  163. package/dist/types/channel.d.ts +29 -0
  164. package/dist/types/channel.js +1 -0
  165. package/dist/types/common.d.ts +111 -0
  166. package/dist/types/common.js +1 -0
  167. package/dist/types/context.d.ts +93 -0
  168. package/dist/types/context.js +1 -0
  169. package/dist/types/detached.d.ts +17 -0
  170. package/dist/types/detached.js +7 -0
  171. package/dist/types/embed.d.ts +5 -0
  172. package/dist/types/embed.js +1 -0
  173. package/dist/types/error.d.ts +51 -0
  174. package/dist/types/error.js +1 -0
  175. package/dist/types/fs-adapter.d.ts +51 -0
  176. package/dist/types/fs-adapter.js +3 -0
  177. package/dist/types/harness-result.d.ts +43 -0
  178. package/dist/types/harness-result.js +2 -0
  179. package/dist/types/items.d.ts +115 -0
  180. package/dist/types/items.js +2 -0
  181. package/dist/types/memory.d.ts +336 -0
  182. package/dist/types/memory.js +13 -0
  183. package/dist/types/mutable-context.d.ts +13 -0
  184. package/dist/types/mutable-context.js +1 -0
  185. package/dist/types/observability.d.ts +30 -0
  186. package/dist/types/observability.js +1 -0
  187. package/dist/types/runtime.d.ts +255 -0
  188. package/dist/types/runtime.js +1 -0
  189. package/dist/types/shell-adapter.d.ts +34 -0
  190. package/dist/types/shell-adapter.js +10 -0
  191. package/dist/types/steering.d.ts +89 -0
  192. package/dist/types/steering.js +14 -0
  193. package/dist/types/step.d.ts +206 -0
  194. package/dist/types/step.js +1 -0
  195. package/dist/types/subprocess-adapter.d.ts +41 -0
  196. package/dist/types/subprocess-adapter.js +1 -0
  197. package/dist/types/tool-context.d.ts +34 -0
  198. package/dist/types/tool-context.js +1 -0
  199. package/dist/unstable.d.ts +28 -0
  200. package/dist/unstable.js +24 -0
  201. package/dist/until/combinators.d.ts +17 -0
  202. package/dist/until/combinators.js +49 -0
  203. package/dist/until/never.d.ts +9 -0
  204. package/dist/until/never.js +12 -0
  205. package/dist/until/predicates.d.ts +43 -0
  206. package/dist/until/predicates.js +126 -0
  207. package/package.json +81 -0
@@ -0,0 +1,217 @@
1
+ import { ZodError } from 'zod';
2
+ import { NoeticErrorImpl } from '../errors/noetic-error';
3
+ import { resolveLayerTools } from '../memory/layer-api';
4
+ import { commitLayerUsage, computeLayerUsage } from '../memory/layer-usage';
5
+ import { assembleView } from '../memory/projector';
6
+ import { SteeringAction } from '../types/steering';
7
+ import { frameworkCast } from './framework-cast';
8
+ import { createMessage, extractAssistantText, trackUsage } from './message-helpers';
9
+ import { isFunctionCall, isMutableContext } from './typeguards';
10
+ const MAX_STEERING_RETRIES = 3;
11
+ const emptyRecall = [];
12
+ /**
13
+ * Resolves which tools to send and what restrictions to apply.
14
+ *
15
+ * When a unified tool set exists on the context (collected before execution),
16
+ * every LLM call sends the full set and step.tools narrows via allowedToolNames.
17
+ *
18
+ * Semantics:
19
+ * step.tools = undefined → unrestricted (all tools, no allowedToolNames)
20
+ * step.tools = [] → no tools at all
21
+ * step.tools = [a, b] → full set sent, restrict to a and b
22
+ *
23
+ * Fallback: when no unified set exists (e.g. harness.run() called directly),
24
+ * merge step tools with layer tools as before.
25
+ */
26
+ function resolveToolsAndRestrictions(step, layers, ctx) {
27
+ // step.tools = [] → explicit opt-out
28
+ if (step.tools && step.tools.length === 0) {
29
+ return {
30
+ tools: undefined,
31
+ allowedToolNames: undefined,
32
+ };
33
+ }
34
+ const unified = ctx.unifiedTools;
35
+ if (unified && unified.length > 0) {
36
+ const allowedToolNames = step.tools ? step.tools.map((t) => t.name) : undefined;
37
+ return {
38
+ tools: [
39
+ ...unified,
40
+ ],
41
+ allowedToolNames,
42
+ };
43
+ }
44
+ // Fallback: no unified set (direct harness.run() path)
45
+ const layerTools = layers && layers.length > 0 ? resolveLayerTools(layers, ctx.harness, ctx) : [];
46
+ if (layerTools.length === 0 && !step.tools) {
47
+ return {
48
+ tools: undefined,
49
+ allowedToolNames: undefined,
50
+ };
51
+ }
52
+ const merged = [
53
+ ...(step.tools ?? []),
54
+ ...layerTools,
55
+ ];
56
+ return {
57
+ tools: merged.length > 0 ? merged : undefined,
58
+ allowedToolNames: undefined,
59
+ };
60
+ }
61
+ async function runInputPipeline({ ctx, layers, input }) {
62
+ const userItem = createMessage(input, 'user');
63
+ const { items: finalItems } = await ctx.harness.runAppendPipeline(layers, [
64
+ userItem,
65
+ ], ctx);
66
+ for (const item of finalItems) {
67
+ ctx.itemLog.append(item);
68
+ }
69
+ }
70
+ //#endregion
71
+ export async function executeLLM(step, input, ctx, layers) {
72
+ const baseCtx = frameworkCast(ctx);
73
+ const hasLayers = layers !== undefined && layers.length > 0;
74
+ // Append user input — through layer pipeline if layers exist, otherwise direct.
75
+ if (typeof input === 'string' && input.length > 0) {
76
+ if (hasLayers) {
77
+ await runInputPipeline({
78
+ ctx: baseCtx,
79
+ layers,
80
+ input,
81
+ });
82
+ }
83
+ else {
84
+ baseCtx.itemLog.append(createMessage(input, 'user'));
85
+ }
86
+ }
87
+ const { tools: resolvedTools, allowedToolNames } = resolveToolsAndRestrictions(step, layers, baseCtx);
88
+ // Recall once per LLM step: every layer with a recall hook contributes its
89
+ // current view. Results drive both the assembled context window and the
90
+ // per-layer usage breakdown (ctx.lastLayerUsage). Recall fires before the
91
+ // steering retry loop because retries replay the same context.
92
+ const recallQuery = typeof input === 'string' ? input : '';
93
+ const recallResults = hasLayers
94
+ ? await baseCtx.harness.recallLayers(layers, recallQuery, baseCtx)
95
+ : emptyRecall;
96
+ const layerOutputItems = recallResults.flatMap((r) => r.items);
97
+ let retries = 0;
98
+ while (retries <= MAX_STEERING_RETRIES) {
99
+ const rawHistoryItems = baseCtx.itemLog.items;
100
+ const projectedHistoryItems = hasLayers
101
+ ? await baseCtx.harness.projectHistory(layers, rawHistoryItems, baseCtx)
102
+ : rawHistoryItems;
103
+ const assembledItems = layerOutputItems.length > 0
104
+ ? assembleView({
105
+ systemPromptItems: [],
106
+ layerOutputItems,
107
+ historyItems: [
108
+ ...projectedHistoryItems,
109
+ ],
110
+ })
111
+ : projectedHistoryItems === rawHistoryItems
112
+ ? rawHistoryItems
113
+ : [
114
+ ...projectedHistoryItems,
115
+ ];
116
+ const request = resolvedTools
117
+ ? {
118
+ model: step.model,
119
+ items: assembledItems,
120
+ instructions: step.instructions,
121
+ tools: resolvedTools,
122
+ params: step.params,
123
+ outputSchema: step.output,
124
+ emit: step.emit,
125
+ ctx: baseCtx,
126
+ layers,
127
+ allowedToolNames,
128
+ }
129
+ : {
130
+ model: step.model,
131
+ items: assembledItems,
132
+ instructions: step.instructions,
133
+ params: step.params,
134
+ outputSchema: step.output,
135
+ emit: step.emit,
136
+ };
137
+ const response = await baseCtx.harness.callModel(request);
138
+ if (hasLayers) {
139
+ const decision = await baseCtx.harness.afterModelCall(layers, response, baseCtx);
140
+ if (decision.action === SteeringAction.Deny) {
141
+ throw new NoeticErrorImpl({
142
+ kind: 'steering_denied',
143
+ guidance: decision.guidance,
144
+ });
145
+ }
146
+ if (decision.action === SteeringAction.Guide && retries < MAX_STEERING_RETRIES) {
147
+ baseCtx.itemLog.append(createMessage(decision.guidance ?? 'Please adjust your response.', 'developer'));
148
+ retries++;
149
+ continue;
150
+ }
151
+ }
152
+ const toolCalls = [];
153
+ for (const item of response.items) {
154
+ baseCtx.itemLog.append(item);
155
+ if (isFunctionCall(item)) {
156
+ toolCalls.push(item);
157
+ }
158
+ }
159
+ const meta = {
160
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
161
+ usage: response.usage,
162
+ cost: response.cost,
163
+ responseItems: response.items,
164
+ };
165
+ if (isMutableContext(baseCtx)) {
166
+ baseCtx.lastStepMeta = meta;
167
+ }
168
+ trackUsage(baseCtx, response);
169
+ commitLayerUsage(baseCtx, computeLayerUsage({
170
+ ctx: baseCtx,
171
+ modelId: step.model,
172
+ instructions: step.instructions,
173
+ tools: resolvedTools,
174
+ recallResults,
175
+ }));
176
+ if (hasLayers) {
177
+ await baseCtx.harness.storeLayers(layers, response, baseCtx);
178
+ }
179
+ const lastText = extractAssistantText(response.items);
180
+ if (step.output) {
181
+ try {
182
+ const parsed = JSON.parse(lastText);
183
+ return step.output.parse(parsed);
184
+ }
185
+ catch (e) {
186
+ if (e instanceof SyntaxError || e instanceof ZodError) {
187
+ throw new NoeticErrorImpl({
188
+ kind: 'llm_parse_error',
189
+ stepId: step.id,
190
+ raw: lastText,
191
+ schema: step.output,
192
+ zodError: e instanceof ZodError
193
+ ? e
194
+ : new ZodError([
195
+ {
196
+ code: 'custom',
197
+ message: `Invalid JSON: ${e.message}`,
198
+ path: [],
199
+ },
200
+ ]),
201
+ });
202
+ }
203
+ throw e;
204
+ }
205
+ }
206
+ return frameworkCast(lastText);
207
+ }
208
+ // Safety net: the loop above always returns or throws within the body.
209
+ // This throw is unreachable but protects against future refactors that
210
+ // might break the loop invariant.
211
+ throw new NoeticErrorImpl({
212
+ kind: 'step_failed',
213
+ stepId: step.id,
214
+ cause: new Error('Steering retries exhausted'),
215
+ retriesExhausted: true,
216
+ });
217
+ }
@@ -0,0 +1,3 @@
1
+ import type { Context } from '../types/context';
2
+ import type { ExecuteStepFn, StepLoop } from '../types/step';
3
+ export declare function executeLoop<TMemory, I, O>(step: StepLoop<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn): Promise<O>;
@@ -0,0 +1,171 @@
1
+ import { isNoeticError, NoeticErrorImpl } from '../errors/noetic-error';
2
+ import { frameworkCast } from './framework-cast';
3
+ import { createMessage } from './message-helpers';
4
+ import { isMutableContext } from './typeguards';
5
+ //#endregion
6
+ //#region Helper Functions
7
+ function hasTextField(value) {
8
+ return typeof value === 'object' && value !== null && 'text' in value;
9
+ }
10
+ async function recvInboxWithTimeout(ctx, step) {
11
+ if (!step.inbox) {
12
+ return null;
13
+ }
14
+ if ((step.parkTimeout ?? 0) <= 0) {
15
+ return ctx.tryRecv(step.inbox);
16
+ }
17
+ try {
18
+ return await ctx.recv(step.inbox, {
19
+ timeout: step.parkTimeout,
20
+ });
21
+ }
22
+ catch {
23
+ // Expected: channel_timeout error when parkTimeout expires with no message.
24
+ return null;
25
+ }
26
+ }
27
+ function prepareNextInput(step, lastOutput, verdict, ctx) {
28
+ if (step.prepareNext) {
29
+ return step.prepareNext(lastOutput, verdict, ctx);
30
+ }
31
+ // Requires I === O when prepareNext is omitted — the loop feeds output
32
+ // back as input. Callers must ensure I and O are compatible types.
33
+ return frameworkCast(lastOutput);
34
+ }
35
+ //#endregion
36
+ //#region Public API
37
+ export async function executeLoop(step, input, ctx, executeStep) {
38
+ const baseCtx = frameworkCast(ctx);
39
+ let currentInput = input;
40
+ let lastOutput;
41
+ let lastText = '';
42
+ const history = [];
43
+ const startTime = Date.now();
44
+ let stepCount = 0;
45
+ const maxIterations = step.maxIterations ?? 1e3;
46
+ const maxHistory = step.maxHistorySize ?? 100;
47
+ let totalIterations = 0;
48
+ // Validate maxIterations
49
+ if (!Number.isFinite(maxIterations) || maxIterations < 1) {
50
+ throw new NoeticErrorImpl({
51
+ kind: 'step_failed',
52
+ stepId: step.id,
53
+ cause: new Error(`Invalid maxIterations: ${step.maxIterations}`),
54
+ retriesExhausted: false,
55
+ });
56
+ }
57
+ while (true) {
58
+ // Abort check at top of each iteration
59
+ if (ctx.aborted) {
60
+ throw new NoeticErrorImpl({
61
+ kind: 'cancelled',
62
+ reason: ctx.abortReason ?? 'context aborted',
63
+ });
64
+ }
65
+ // Enforce hard iteration ceiling (includes retries)
66
+ totalIterations++;
67
+ if (totalIterations > maxIterations) {
68
+ throw new NoeticErrorImpl({
69
+ kind: 'step_failed',
70
+ stepId: step.id,
71
+ cause: new Error(`Loop exceeded maximum iterations (${maxIterations})`),
72
+ retriesExhausted: false,
73
+ });
74
+ }
75
+ // Execute body steps sequentially
76
+ let output;
77
+ try {
78
+ let stepOutput = currentInput;
79
+ for (const bodyStep of step.steps) {
80
+ stepOutput = await executeStep(bodyStep, frameworkCast(stepOutput), ctx);
81
+ }
82
+ output = frameworkCast(stepOutput);
83
+ stepCount++;
84
+ }
85
+ catch (e) {
86
+ if (!step.onError || !isNoeticError(e)) {
87
+ throw e;
88
+ }
89
+ const action = step.onError(e.noeticError, ctx);
90
+ if (action === 'retry') {
91
+ continue;
92
+ }
93
+ if (action !== 'skip') {
94
+ throw e;
95
+ }
96
+ // Skip does not increment stepCount — it is not a successful execution.
97
+ if (lastOutput === undefined) {
98
+ continue;
99
+ }
100
+ output = lastOutput;
101
+ }
102
+ lastOutput = output;
103
+ history.push(output);
104
+ // Trim history if it exceeds maxHistorySize
105
+ if (history.length > maxHistory) {
106
+ history.splice(0, history.length - maxHistory);
107
+ }
108
+ // Extract text from output for snapshot
109
+ if (typeof output === 'string') {
110
+ lastText = output;
111
+ }
112
+ else if (hasTextField(output)) {
113
+ lastText = String(output.text);
114
+ }
115
+ else {
116
+ lastText = output === undefined ? '' : JSON.stringify(output);
117
+ }
118
+ // Build snapshot
119
+ const snapshot = {
120
+ stepCount,
121
+ tokens: {
122
+ ...ctx.tokens,
123
+ },
124
+ elapsed: Date.now() - startTime,
125
+ cost: ctx.cost,
126
+ lastOutput: output,
127
+ lastText,
128
+ history: [
129
+ ...history,
130
+ ],
131
+ depth: ctx.depth,
132
+ lastStepMeta: isMutableContext(baseCtx) ? baseCtx.lastStepMeta : null,
133
+ };
134
+ // Evaluate until predicate
135
+ let verdict;
136
+ try {
137
+ verdict = await step.until(snapshot);
138
+ }
139
+ catch (predicateError) {
140
+ // Per spec: if until predicate throws, treat as stop
141
+ verdict = {
142
+ stop: true,
143
+ reason: `Predicate error: ${predicateError instanceof Error ? predicateError.message : String(predicateError)}`,
144
+ };
145
+ }
146
+ if (verdict.stop) {
147
+ if (lastOutput === undefined) {
148
+ throw new NoeticErrorImpl({
149
+ kind: 'step_failed',
150
+ stepId: step.id,
151
+ cause: new Error('Loop completed with no successful output'),
152
+ retriesExhausted: false,
153
+ });
154
+ }
155
+ // Check inbox before truly stopping
156
+ if (step.inbox) {
157
+ const inboxMessage = await recvInboxWithTimeout(baseCtx, step);
158
+ if (inboxMessage !== null) {
159
+ baseCtx.itemLog.append(createMessage(inboxMessage, 'developer'));
160
+ // Continue the loop — don't stop
161
+ currentInput = prepareNextInput(step, lastOutput, verdict, ctx);
162
+ continue;
163
+ }
164
+ }
165
+ return lastOutput;
166
+ }
167
+ // Prepare input for next iteration
168
+ currentInput = prepareNextInput(step, output, verdict, ctx);
169
+ }
170
+ }
171
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import type { Context } from '../types/context';
2
+ import type { ExecuteStepFn, StepProvide } from '../types/step';
3
+ /**
4
+ * Executes a provide step by attaching memory layers to the current context
5
+ * without creating an isolated child context.
6
+ *
7
+ * Unlike spawn, provide does not create a new itemLog or clone state.
8
+ * Events flow through to the parent in real-time.
9
+ */
10
+ export declare function executeProvide<TMemory, I, O>(step: StepProvide<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn): Promise<O>;
@@ -0,0 +1,64 @@
1
+ import { resolveLayerTools } from '../memory/layer-api';
2
+ import { deduplicateTools } from './collect-tools';
3
+ import { frameworkCast } from './framework-cast';
4
+ //#region Helper Functions
5
+ function isMemoryConfig(value) {
6
+ return typeof value === 'object' && value !== null && 'layers' in value;
7
+ }
8
+ function resolveLayers(step) {
9
+ if (isMemoryConfig(step.memory)) {
10
+ return [
11
+ ...step.memory.layers,
12
+ ];
13
+ }
14
+ return step.memory;
15
+ }
16
+ function mergeLayers(existing, provided) {
17
+ if (!existing || existing.length === 0) {
18
+ return provided;
19
+ }
20
+ // Provided layers override existing layers with the same id (like nested React context)
21
+ const providedIds = new Set(provided.map((l) => l.id));
22
+ const kept = existing.filter((l) => !providedIds.has(l.id));
23
+ return [
24
+ ...kept,
25
+ ...provided,
26
+ ];
27
+ }
28
+ //#endregion
29
+ //#region Public API
30
+ /**
31
+ * Executes a provide step by attaching memory layers to the current context
32
+ * without creating an isolated child context.
33
+ *
34
+ * Unlike spawn, provide does not create a new itemLog or clone state.
35
+ * Events flow through to the parent in real-time.
36
+ */
37
+ export async function executeProvide(step, input, ctx, executeStep) {
38
+ const baseCtx = frameworkCast(ctx);
39
+ const previousLayers = baseCtx.layers;
40
+ const previousUnifiedTools = baseCtx.unifiedTools;
41
+ const newLayers = resolveLayers(step);
42
+ const mergedLayers = mergeLayers(previousLayers, newLayers);
43
+ // Attach layers to the current context (no isolation)
44
+ baseCtx.layers = mergedLayers;
45
+ // Merge new layer-provided tools into the unified tool set
46
+ if (baseCtx.unifiedTools && newLayers.length > 0) {
47
+ const layerTools = resolveLayerTools(newLayers, baseCtx.harness, baseCtx);
48
+ if (layerTools.length > 0) {
49
+ baseCtx.unifiedTools = deduplicateTools([
50
+ ...baseCtx.unifiedTools,
51
+ ...layerTools,
52
+ ]);
53
+ }
54
+ }
55
+ try {
56
+ return await executeStep(step.child, input, ctx);
57
+ }
58
+ finally {
59
+ // Restore previous layers and unified tools so siblings are not affected
60
+ baseCtx.layers = previousLayers;
61
+ baseCtx.unifiedTools = previousUnifiedTools;
62
+ }
63
+ }
64
+ //#endregion
@@ -0,0 +1,3 @@
1
+ import type { Context } from '../types/context';
2
+ import type { StepRun } from '../types/step';
3
+ export declare function executeRun<TMemory, I, O>(step: StepRun<TMemory, I, O>, input: I, ctx: Context<TMemory>): Promise<O>;
@@ -0,0 +1,39 @@
1
+ import { NoeticErrorImpl } from '../errors/noetic-error';
2
+ export async function executeRun(step, input, ctx) {
3
+ const retry = step.retry;
4
+ const maxAttempts = retry?.maxAttempts ?? 1;
5
+ let lastError = new Error('No attempts executed');
6
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
7
+ try {
8
+ return await step.execute(input, ctx);
9
+ }
10
+ catch (e) {
11
+ lastError = e instanceof Error ? e : new Error(String(e));
12
+ if (attempt < maxAttempts - 1 && retry) {
13
+ const delay = computeDelay(retry, attempt);
14
+ await new Promise((r) => setTimeout(r, delay));
15
+ }
16
+ }
17
+ }
18
+ throw new NoeticErrorImpl({
19
+ kind: 'step_failed',
20
+ stepId: step.id,
21
+ cause: lastError,
22
+ retriesExhausted: maxAttempts > 1,
23
+ });
24
+ }
25
+ function computeDelay(retry, attempt) {
26
+ let delay;
27
+ switch (retry.backoff) {
28
+ case 'fixed':
29
+ delay = retry.initialDelay;
30
+ break;
31
+ case 'linear':
32
+ delay = retry.initialDelay * (attempt + 1);
33
+ break;
34
+ case 'exponential':
35
+ delay = retry.initialDelay * 2 ** attempt;
36
+ break;
37
+ }
38
+ return Math.min(delay, retry.maxDelay ?? 30_000);
39
+ }
@@ -0,0 +1,11 @@
1
+ import type { LayerStateStore } from '../memory/layer-lifecycle';
2
+ import type { ItemSchemaRegistry } from '../schemas/item';
3
+ import type { Context } from '../types/context';
4
+ import type { MemoryLayer } from '../types/memory';
5
+ import type { ExecuteStepFn, StepSpawn } from '../types/step';
6
+ export interface ExecuteSpawnOpts {
7
+ layerStore?: LayerStateStore;
8
+ parentLayers?: MemoryLayer[];
9
+ itemSchemas?: ItemSchemaRegistry;
10
+ }
11
+ export declare function executeSpawn<TMemory, I, O>(step: StepSpawn<TMemory, I, O>, input: I, ctx: Context<TMemory>, executeStep: ExecuteStepFn, opts?: ExecuteSpawnOpts): Promise<O>;
@@ -0,0 +1,113 @@
1
+ import { resolveLayerTools } from '../memory/layer-api';
2
+ import { returnLayers, spawnLayers } from '../memory/layer-lifecycle';
3
+ import { ContextImpl } from '../runtime/context-impl';
4
+ import { snapshotCwdState } from '../runtime/cwd-helpers';
5
+ import { contextToExecCtx } from '../runtime/exec-context-factory';
6
+ import { defaultItemSchemaRegistry } from '../schemas/item';
7
+ import { cloneWithGuard } from './clone-guard';
8
+ import { collectAllTools, deduplicateTools } from './collect-tools';
9
+ import { frameworkCast } from './framework-cast';
10
+ import { getContextChannelStore } from './typeguards';
11
+ //#endregion
12
+ //#region Helper Functions
13
+ function isMemoryConfig(value) {
14
+ return typeof value === 'object' && value !== null && 'layers' in value;
15
+ }
16
+ function resolveLayersForSpawn(step, parentLayers) {
17
+ if (!step.memory) {
18
+ return parentLayers ?? [];
19
+ }
20
+ if (isMemoryConfig(step.memory)) {
21
+ return [
22
+ ...step.memory.layers,
23
+ ];
24
+ }
25
+ return step.memory;
26
+ }
27
+ async function collectSpawnItems({ layers, parentExecutionCtx, childExecutionCtx, layerStore, itemSchemas = defaultItemSchemaRegistry, }) {
28
+ const spawnResults = await spawnLayers({
29
+ layers,
30
+ parentCtx: parentExecutionCtx,
31
+ childCtx: childExecutionCtx,
32
+ store: layerStore,
33
+ itemSchemas,
34
+ });
35
+ return spawnResults.flatMap((r) => r.items);
36
+ }
37
+ //#endregion
38
+ //#region Public API
39
+ export async function executeSpawn(step, input, ctx, executeStep, opts) {
40
+ const baseCtx = frameworkCast(ctx);
41
+ const layers = resolveLayersForSpawn(step, opts?.parentLayers);
42
+ const childId = crypto.randomUUID();
43
+ const childExecutionCtx = contextToExecCtx(baseCtx, {
44
+ executionId: childId,
45
+ depth: baseCtx.depth + 1,
46
+ stepNumber: 0,
47
+ tokenUsage: {
48
+ input: 0,
49
+ output: 0,
50
+ },
51
+ cost: 0,
52
+ readLayerStateId: childId,
53
+ });
54
+ const layerStore = opts?.layerStore;
55
+ const hasLayers = layers.length > 0 && layerStore !== undefined;
56
+ // Collect items from memory layers via onSpawn hooks
57
+ let childItems = [];
58
+ let parentExecutionCtx;
59
+ if (hasLayers) {
60
+ parentExecutionCtx = contextToExecCtx(baseCtx);
61
+ childItems = await collectSpawnItems({
62
+ layers,
63
+ parentExecutionCtx,
64
+ childExecutionCtx,
65
+ layerStore,
66
+ itemSchemas: opts?.itemSchemas,
67
+ });
68
+ }
69
+ // Build unified tool set for child from its step tree + layers
70
+ const childStepTools = collectAllTools(step.child);
71
+ const childLayerTools = layers.length > 0 ? resolveLayerTools(layers, baseCtx.harness, baseCtx) : [];
72
+ const childUnifiedTools = deduplicateTools([
73
+ ...childStepTools,
74
+ ...childLayerTools,
75
+ ]);
76
+ // Create child context — empty by default, layers provide items via onSpawn.
77
+ const childCtx = new ContextImpl({
78
+ harness: baseCtx.harness,
79
+ parent: baseCtx,
80
+ items: childItems,
81
+ state: cloneWithGuard(baseCtx.state, `Spawn '${step.id}'`),
82
+ threadId: baseCtx.threadId,
83
+ resourceId: baseCtx.resourceId,
84
+ channelStore: getContextChannelStore(baseCtx),
85
+ layers: layers.length > 0 ? layers : undefined,
86
+ unifiedTools: childUnifiedTools.length > 0 ? childUnifiedTools : undefined,
87
+ cwdState: snapshotCwdState(baseCtx),
88
+ });
89
+ try {
90
+ // Execute the child step
91
+ const childOutput = await executeStep(step.child, input, frameworkCast(childCtx));
92
+ // Pipeline result through layer onReturn hooks
93
+ if (!hasLayers || !parentExecutionCtx) {
94
+ return childOutput;
95
+ }
96
+ const pipelinedResult = await returnLayers({
97
+ layers,
98
+ parentCtx: parentExecutionCtx,
99
+ childCtx: childExecutionCtx,
100
+ childLog: childCtx.itemLog,
101
+ result: childOutput,
102
+ store: layerStore,
103
+ });
104
+ return pipelinedResult;
105
+ }
106
+ finally {
107
+ // Clean up child execution state to prevent memory leaks — runs on success and error
108
+ if (hasLayers) {
109
+ layerStore.cleanup(childExecutionCtx.executionId);
110
+ }
111
+ }
112
+ }
113
+ //#endregion
@@ -0,0 +1,5 @@
1
+ import type { Context } from '../types/context';
2
+ import type { MemoryLayer } from '../types/memory';
3
+ import type { AgentHarnessContract } from '../types/runtime';
4
+ import type { StepTool } from '../types/step';
5
+ export declare function executeTool<TMemory, I, O>(step: StepTool<TMemory, I, O>, input: I, ctx: Context<TMemory>, harness: AgentHarnessContract, layers?: MemoryLayer[]): Promise<O>;