@noetic-tools/core 0.0.1 → 0.1.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 (202) hide show
  1. package/dist/adapters/in-memory-fs-adapter.js +1 -1
  2. package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
  3. package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
  4. package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
  5. package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
  6. package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
  7. package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
  8. package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
  9. package/dist/adapters/in-memory-subprocess/types.js +2 -0
  10. package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
  11. package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
  12. package/dist/adapters/openrouter.d.ts +11 -2
  13. package/dist/adapters/openrouter.js +66 -5
  14. package/dist/builders/control-flow-builders.js +8 -3
  15. package/dist/builders/every.js +4 -1
  16. package/dist/builders/layer-provides-builders.d.ts +1 -32
  17. package/dist/builders/layer-provides-builders.js +1 -33
  18. package/dist/builders/loop-builder.js +4 -1
  19. package/dist/builders/memory-builder.js +1 -1
  20. package/dist/builders/provide-builder.js +4 -1
  21. package/dist/builders/spawn-builder.d.ts +20 -7
  22. package/dist/builders/spawn-builder.js +16 -2
  23. package/dist/builders/step-builders.d.ts +49 -28
  24. package/dist/builders/step-builders.js +30 -11
  25. package/dist/builders/tool-builder.d.ts +5 -1
  26. package/dist/builders/tool-builder.js +2 -0
  27. package/dist/builders/workflow-hydrator.d.ts +38 -0
  28. package/dist/builders/workflow-hydrator.js +336 -0
  29. package/dist/conditions/conditions.js +1 -1
  30. package/dist/errors/noetic-error.js +2 -0
  31. package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
  32. package/dist/harness/agent-harness.js +576 -0
  33. package/dist/harness/deps/adapters.d.ts +4 -0
  34. package/dist/harness/deps/adapters.js +4 -0
  35. package/dist/harness/deps/interpreter.d.ts +5 -0
  36. package/dist/harness/deps/interpreter.js +4 -0
  37. package/dist/harness/deps/memory.d.ts +5 -0
  38. package/dist/harness/deps/memory.js +4 -0
  39. package/dist/harness/deps/runtime.d.ts +10 -0
  40. package/dist/harness/deps/runtime.js +7 -0
  41. package/dist/harness/deps/types.d.ts +17 -0
  42. package/dist/harness/deps/types.js +1 -0
  43. package/dist/harness/model-call.d.ts +50 -0
  44. package/dist/harness/model-call.js +694 -0
  45. package/dist/harness/model-schema.d.ts +19 -0
  46. package/dist/harness/model-schema.js +46 -0
  47. package/dist/harness/step-bootstrap.d.ts +25 -0
  48. package/dist/harness/step-bootstrap.js +170 -0
  49. package/dist/index.d.ts +48 -12
  50. package/dist/index.js +29 -7
  51. package/dist/interpreter/action-deps.d.ts +12 -0
  52. package/dist/interpreter/action-deps.js +10 -0
  53. package/dist/interpreter/action-types.d.ts +8 -0
  54. package/dist/interpreter/action-types.js +1 -0
  55. package/dist/interpreter/collect-tools.d.ts +1 -1
  56. package/dist/interpreter/collect-tools.js +6 -2
  57. package/dist/interpreter/execute-action.d.ts +22 -0
  58. package/dist/interpreter/execute-action.js +527 -0
  59. package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +7 -1
  60. package/dist/interpreter/execute-control.js +530 -0
  61. package/dist/interpreter/execute.d.ts +13 -0
  62. package/dist/interpreter/execute.js +151 -48
  63. package/dist/interpreter/message-helpers.d.ts +1 -9
  64. package/dist/interpreter/message-helpers.js +1 -42
  65. package/dist/interpreter/step-dispatcher.d.ts +61 -0
  66. package/dist/interpreter/step-dispatcher.js +67 -0
  67. package/dist/interpreter/typeguards.d.ts +3 -10
  68. package/dist/interpreter/typeguards.js +3 -20
  69. package/dist/{runtime → memory}/exec-context-factory.js +1 -1
  70. package/dist/memory/flow-schema.d.ts +52 -0
  71. package/dist/memory/flow-schema.js +102 -0
  72. package/dist/memory/function-call-utils.js +4 -2
  73. package/dist/memory/layer-api.d.ts +1 -1
  74. package/dist/memory/layer-api.js +2 -2
  75. package/dist/memory/layer-lifecycle.d.ts +1 -1
  76. package/dist/memory/layer-lifecycle.js +2 -2
  77. package/dist/memory/layer-provides.d.ts +32 -0
  78. package/dist/memory/layer-provides.js +33 -0
  79. package/dist/memory/layer-usage.d.ts +3 -2
  80. package/dist/memory/layer-usage.js +9 -3
  81. package/dist/memory/layers/durable-task-state.js +1 -1
  82. package/dist/memory/layers/file-reference.js +302 -246
  83. package/dist/memory/layers/history-window.js +1 -1
  84. package/dist/memory/layers/observational-memory.js +1 -2
  85. package/dist/memory/layers/plan.d.ts +1 -1
  86. package/dist/memory/layers/plan.js +13 -8
  87. package/dist/memory/layers/steering.js +1 -1
  88. package/dist/memory/layers/tool-memory-layer.d.ts +1 -1
  89. package/dist/memory/layers/working-memory.js +2 -2
  90. package/dist/patterns/dynamic-workflow.d.ts +55 -0
  91. package/dist/patterns/dynamic-workflow.js +194 -0
  92. package/dist/patterns/flow.d.ts +2 -52
  93. package/dist/patterns/flow.js +1 -103
  94. package/dist/patterns/interview.js +1 -1
  95. package/dist/patterns/plans.d.ts +3 -3
  96. package/dist/patterns/plans.js +3 -3
  97. package/dist/patterns/ralph-wiggum.d.ts +1 -1
  98. package/dist/patterns/react.d.ts +1 -1
  99. package/dist/portable/adapters.d.ts +6 -0
  100. package/dist/portable/adapters.js +6 -0
  101. package/dist/portable/ask-user.d.ts +4 -0
  102. package/dist/portable/ask-user.js +2 -0
  103. package/dist/portable/builders.d.ts +12 -0
  104. package/dist/portable/builders.js +12 -0
  105. package/dist/portable/memory-layers.d.ts +12 -0
  106. package/dist/portable/memory-layers.js +12 -0
  107. package/dist/portable/runtime.d.ts +4 -0
  108. package/dist/portable/runtime.js +4 -0
  109. package/dist/portable/until.d.ts +4 -0
  110. package/dist/portable/until.js +4 -0
  111. package/dist/portable.d.ts +8 -26
  112. package/dist/portable.js +8 -34
  113. package/dist/runtime/broadcaster-utils.d.ts +5 -1
  114. package/dist/runtime/broadcaster-utils.js +11 -6
  115. package/dist/runtime/channel-store.js +1 -1
  116. package/dist/runtime/context-deps.d.ts +4 -0
  117. package/dist/runtime/context-deps.js +2 -0
  118. package/dist/runtime/context-impl.d.ts +42 -12
  119. package/dist/runtime/context-impl.js +51 -4
  120. package/dist/runtime/context-type-deps.d.ts +14 -0
  121. package/dist/runtime/context-type-deps.js +1 -0
  122. package/dist/runtime/detached-handle.d.ts +50 -2
  123. package/dist/runtime/detached-handle.js +216 -12
  124. package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
  125. package/dist/runtime/durable/checkpoint-store.js +101 -0
  126. package/dist/runtime/durable/detached-signal.d.ts +37 -0
  127. package/dist/runtime/durable/detached-signal.js +52 -0
  128. package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
  129. package/dist/runtime/durable/harness-checkpoints.js +104 -0
  130. package/dist/runtime/durable/index.d.ts +10 -0
  131. package/dist/runtime/durable/index.js +5 -0
  132. package/dist/runtime/durable/runnable-loop.d.ts +100 -0
  133. package/dist/runtime/durable/runnable-loop.js +90 -0
  134. package/dist/runtime/durable/session-seed.d.ts +28 -0
  135. package/dist/runtime/durable/session-seed.js +27 -0
  136. package/dist/runtime/durable/stall-nudge.d.ts +82 -0
  137. package/dist/runtime/durable/stall-nudge.js +87 -0
  138. package/dist/runtime/in-memory-storage.js +1 -1
  139. package/dist/runtime/item-log-impl.d.ts +1 -1
  140. package/dist/runtime/step-registry.d.ts +42 -0
  141. package/dist/runtime/step-registry.js +77 -0
  142. package/dist/schemas/workflow.d.ts +139 -0
  143. package/dist/schemas/workflow.js +232 -0
  144. package/dist/types/ask-user-types.d.ts +57 -0
  145. package/dist/types/ask-user-types.js +74 -0
  146. package/dist/types/checkpoint.d.ts +98 -0
  147. package/dist/types/checkpoint.js +81 -0
  148. package/dist/types/common.d.ts +1 -50
  149. package/dist/types/context-parts/item-log.d.ts +6 -0
  150. package/dist/types/context-parts/item-log.js +1 -0
  151. package/dist/types/context-parts/layer-usage.d.ts +18 -0
  152. package/dist/types/context-parts/layer-usage.js +1 -0
  153. package/dist/types/context.d.ts +135 -26
  154. package/dist/types/error.d.ts +15 -0
  155. package/dist/types/harness-result.d.ts +1 -1
  156. package/dist/types/items.d.ts +18 -2
  157. package/dist/types/memory-context.d.ts +39 -0
  158. package/dist/types/memory-context.js +1 -0
  159. package/dist/types/memory.d.ts +35 -65
  160. package/dist/types/runtime.d.ts +2 -1
  161. package/dist/types/steering.d.ts +1 -1
  162. package/dist/types/step-registrar.d.ts +46 -0
  163. package/dist/types/step-registrar.js +56 -0
  164. package/dist/types/step.d.ts +30 -5
  165. package/dist/types/subprocess-adapter.d.ts +119 -3
  166. package/dist/types/subprocess-adapter.js +2 -0
  167. package/dist/types/tool.d.ts +64 -0
  168. package/dist/types/tool.js +1 -0
  169. package/dist/unstable.d.ts +2 -2
  170. package/dist/unstable.js +2 -2
  171. package/dist/until/predicates.d.ts +9 -0
  172. package/dist/until/predicates.js +14 -0
  173. package/dist/util/message-helpers.d.ts +17 -0
  174. package/dist/util/message-helpers.js +64 -0
  175. package/package.json +1 -1
  176. package/dist/adapters/local-fs-adapter.d.ts +0 -3
  177. package/dist/adapters/local-fs-adapter.js +0 -43
  178. package/dist/adapters/local-shell-adapter.d.ts +0 -51
  179. package/dist/adapters/local-shell-adapter.js +0 -167
  180. package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
  181. package/dist/adapters/local-subprocess-adapter.js +0 -187
  182. package/dist/interpreter/execute-branch.d.ts +0 -3
  183. package/dist/interpreter/execute-branch.js +0 -10
  184. package/dist/interpreter/execute-every.js +0 -143
  185. package/dist/interpreter/execute-fork.d.ts +0 -3
  186. package/dist/interpreter/execute-fork.js +0 -215
  187. package/dist/interpreter/execute-llm.d.ts +0 -4
  188. package/dist/interpreter/execute-llm.js +0 -217
  189. package/dist/interpreter/execute-loop.d.ts +0 -3
  190. package/dist/interpreter/execute-loop.js +0 -171
  191. package/dist/interpreter/execute-provide.d.ts +0 -10
  192. package/dist/interpreter/execute-provide.js +0 -64
  193. package/dist/interpreter/execute-run.d.ts +0 -3
  194. package/dist/interpreter/execute-run.js +0 -39
  195. package/dist/interpreter/execute-spawn.d.ts +0 -11
  196. package/dist/interpreter/execute-spawn.js +0 -113
  197. package/dist/interpreter/execute-tool.d.ts +0 -5
  198. package/dist/interpreter/execute-tool.js +0 -81
  199. package/dist/runtime/agent-harness.js +0 -1170
  200. /package/dist/{runtime → memory}/exec-context-factory.d.ts +0 -0
  201. /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
  202. /package/dist/{interpreter → util}/framework-cast.js +0 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Converts a validated `WorkflowDocument` into a live `Step` tree.
3
+ *
4
+ * Each node kind maps to the corresponding builder (`step.llm`, `fork`,
5
+ * `loop`, etc.) so hydrated steps are indistinguishable from programmatic
6
+ * ones — they register in the step registry, carry retry policies, etc.
7
+ */
8
+ import type { WorkflowDocument, WorkflowNode } from '../schemas/workflow';
9
+ import type { ContextMemory, MemoryLayer } from '../types/memory';
10
+ import type { ExecuteStepFn, Step } from '../types/step';
11
+ import type { Tool } from '../types/tool';
12
+ /** @public Context required to hydrate a JSON workflow into live steps. */
13
+ export interface HydrationContext {
14
+ tools: ReadonlyMap<string, Tool>;
15
+ executeStep: ExecuteStepFn;
16
+ layers?: ReadonlyMap<string, MemoryLayer>;
17
+ }
18
+ /**
19
+ * Hydrates a single `WorkflowNode` into a live `Step`.
20
+ *
21
+ * @public
22
+ * @param node - Validated workflow node.
23
+ * @param ctx - Hydration context with tool registry and step executor.
24
+ * @returns A live `Step` ready for execution.
25
+ * @throws `NoeticConfigError` with code `UNKNOWN_NODE_KIND` if the node kind is unrecognised.
26
+ * @throws `NoeticConfigError` with code `UNKNOWN_TOOL_REFERENCE` if a tool name cannot be resolved.
27
+ * @throws `NoeticConfigError` with code `UNKNOWN_UNTIL_PREDICATE` if an until predicate kind is unrecognised.
28
+ */
29
+ export declare function hydrateNode(node: WorkflowNode, ctx: HydrationContext): Step<ContextMemory, string, string>;
30
+ /**
31
+ * Hydrates a complete `WorkflowDocument` into a live `Step` tree.
32
+ *
33
+ * @public
34
+ * @param doc - Validated workflow document (version 1).
35
+ * @param ctx - Hydration context with tool registry and step executor.
36
+ * @returns The root `Step` of the hydrated workflow.
37
+ */
38
+ export declare function hydrateWorkflow(doc: WorkflowDocument, ctx: HydrationContext): Step<ContextMemory, string, string>;
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Converts a validated `WorkflowDocument` into a live `Step` tree.
3
+ *
4
+ * Each node kind maps to the corresponding builder (`step.llm`, `fork`,
5
+ * `loop`, etc.) so hydrated steps are indistinguishable from programmatic
6
+ * ones — they register in the step registry, carry retry policies, etc.
7
+ */
8
+ import { NoeticConfigError } from '../errors/noetic-config-error';
9
+ import { all, any } from '../until/combinators';
10
+ import { until } from '../until/predicates';
11
+ import { frameworkCast } from '../util/framework-cast';
12
+ import { branch, fork } from './control-flow-builders';
13
+ import { every } from './every';
14
+ import { loop } from './loop-builder';
15
+ import { provide } from './provide-builder';
16
+ import { spawn } from './spawn-builder';
17
+ import { step } from './step-builders';
18
+ //#endregion
19
+ //#region Until Predicate Hydration
20
+ function hydrateUntilPredicate(pred) {
21
+ switch (pred.kind) {
22
+ case 'maxSteps':
23
+ return until.maxSteps(pred.n);
24
+ case 'maxCost':
25
+ return until.maxCost(pred.usd);
26
+ case 'maxDuration':
27
+ return until.maxDuration(pred.ms);
28
+ case 'noToolCalls':
29
+ return until.noToolCalls();
30
+ case 'outputContains':
31
+ return until.outputContains(pred.marker);
32
+ case 'outputEquals':
33
+ return until.outputEquals(pred.sentinel);
34
+ case 'converged':
35
+ return until.converged({
36
+ threshold: pred.threshold,
37
+ });
38
+ case 'any':
39
+ return any(...pred.predicates.map(hydrateUntilPredicate));
40
+ case 'all':
41
+ return all(...pred.predicates.map(hydrateUntilPredicate));
42
+ default:
43
+ throw new NoeticConfigError({
44
+ code: 'UNKNOWN_UNTIL_PREDICATE',
45
+ message: `Unknown until predicate kind: '${frameworkCast(pred).kind}'.`,
46
+ hint: 'Supported kinds: maxSteps, maxCost, maxDuration, noToolCalls, outputContains, outputEquals, converged, any, all.',
47
+ });
48
+ }
49
+ }
50
+ //#endregion
51
+ //#region Node Hydrators
52
+ function hydrateLlmNode(node, ctx) {
53
+ if (node.kind !== 'llm') {
54
+ return frameworkCast(undefined);
55
+ }
56
+ const resolvedTools = resolveTools(node.tools, ctx.tools);
57
+ return step.llm({
58
+ id: node.id,
59
+ model: node.model ?? 'openai/gpt-4o',
60
+ instructions: node.instructions,
61
+ tools: resolvedTools.length > 0 ? resolvedTools : undefined,
62
+ params: node.params,
63
+ });
64
+ }
65
+ function hydrateToolNode(node, ctx) {
66
+ if (node.kind !== 'tool') {
67
+ return frameworkCast(undefined);
68
+ }
69
+ const resolved = ctx.tools.get(node.toolName);
70
+ if (!resolved) {
71
+ throw new NoeticConfigError({
72
+ code: 'UNKNOWN_TOOL_REFERENCE',
73
+ message: `Tool '${node.toolName}' referenced in workflow node '${node.id}' is not registered.`,
74
+ hint: `Available tools: ${[
75
+ ...ctx.tools.keys(),
76
+ ].join(', ') || '(none)'}`,
77
+ });
78
+ }
79
+ return frameworkCast(step.run({
80
+ id: node.id,
81
+ execute: async (_input, execCtx) => {
82
+ const args = node.args ?? {};
83
+ const callId = `call-${node.id}-${Date.now()}`;
84
+ const callItem = {
85
+ id: callId,
86
+ type: 'function_call',
87
+ status: 'completed',
88
+ name: node.toolName,
89
+ callId,
90
+ arguments: JSON.stringify(args),
91
+ };
92
+ execCtx.itemLog.append(callItem);
93
+ const toolCtx = {
94
+ ctx: execCtx,
95
+ harness: execCtx.harness,
96
+ fs: execCtx.fs,
97
+ shell: execCtx.shell,
98
+ memory: {
99
+ get: (_layerId) => undefined,
100
+ set: (_layerId, _state) => { },
101
+ },
102
+ assembledView: execCtx.itemLog.items,
103
+ lastStepMeta: execCtx.lastStepMeta,
104
+ };
105
+ const result = await resolved.execute(args, toolCtx);
106
+ return stringifyResult(result);
107
+ },
108
+ }));
109
+ }
110
+ function hydrateBranchNode(node, ctx) {
111
+ if (node.kind !== 'branch') {
112
+ return frameworkCast(undefined);
113
+ }
114
+ const hydratedRoutes = node.routes.map((r) => ({
115
+ match: r.match,
116
+ target: hydrateNode(r.target, ctx),
117
+ }));
118
+ const defaultTarget = node.default ? hydrateNode(node.default, ctx) : null;
119
+ const allTargets = hydratedRoutes.map((r) => r.target);
120
+ if (defaultTarget) {
121
+ allTargets.push(defaultTarget);
122
+ }
123
+ return branch({
124
+ id: node.id,
125
+ route: (input) => {
126
+ const trimmed = input.trim().toLowerCase();
127
+ for (const r of hydratedRoutes) {
128
+ if (trimmed.includes(r.match.toLowerCase())) {
129
+ return r.target;
130
+ }
131
+ }
132
+ return defaultTarget;
133
+ },
134
+ _optimizable: frameworkCast(allTargets),
135
+ });
136
+ }
137
+ function hydrateForkNode(node, ctx) {
138
+ if (node.kind !== 'fork') {
139
+ return frameworkCast(undefined);
140
+ }
141
+ const hydratedPaths = node.paths.map((p) => hydrateNode(p, ctx));
142
+ if (node.mode === 'race') {
143
+ return fork({
144
+ id: node.id,
145
+ mode: 'race',
146
+ paths: () => hydratedPaths,
147
+ concurrency: node.concurrency,
148
+ _optimizable: frameworkCast(hydratedPaths),
149
+ });
150
+ }
151
+ const mergeFn = buildMerge(node.merge ?? 'last');
152
+ if (node.mode === 'settle') {
153
+ return fork({
154
+ id: node.id,
155
+ mode: 'settle',
156
+ paths: () => hydratedPaths,
157
+ merge: (results) => {
158
+ const values = results.filter((r) => r.status === 'fulfilled').map((r) => r.value ?? '');
159
+ return mergeFn(values);
160
+ },
161
+ concurrency: node.concurrency,
162
+ _optimizable: frameworkCast(hydratedPaths),
163
+ });
164
+ }
165
+ return fork({
166
+ id: node.id,
167
+ mode: 'all',
168
+ paths: () => hydratedPaths,
169
+ merge: mergeFn,
170
+ concurrency: node.concurrency,
171
+ _optimizable: frameworkCast(hydratedPaths),
172
+ });
173
+ }
174
+ function hydrateSpawnNode(node, ctx) {
175
+ if (node.kind !== 'spawn') {
176
+ return frameworkCast(undefined);
177
+ }
178
+ return spawn({
179
+ id: node.id,
180
+ child: hydrateNode(node.child, ctx),
181
+ timeout: node.timeout,
182
+ });
183
+ }
184
+ function hydrateProvideNode(node, ctx) {
185
+ if (node.kind !== 'provide') {
186
+ return frameworkCast(undefined);
187
+ }
188
+ const resolvedLayers = ctx.layers
189
+ ? node.layers.map((name) => {
190
+ const layer = ctx.layers?.get(name);
191
+ if (!layer) {
192
+ throw new NoeticConfigError({
193
+ code: 'UNKNOWN_LAYER_REFERENCE',
194
+ message: `Memory layer '${name}' referenced in provide node '${node.id}' is not registered.`,
195
+ hint: `Available layers: ${[
196
+ ...(ctx.layers?.keys() ?? []),
197
+ ].join(', ') || '(none)'}. Pass named layers via HydrationContext.layers.`,
198
+ });
199
+ }
200
+ return layer;
201
+ })
202
+ : [];
203
+ return provide({
204
+ id: node.id,
205
+ child: hydrateNode(node.child, ctx),
206
+ memory: resolvedLayers,
207
+ });
208
+ }
209
+ function hydrateLoopNode(node, ctx) {
210
+ if (node.kind !== 'loop') {
211
+ return frameworkCast(undefined);
212
+ }
213
+ return loop({
214
+ id: node.id,
215
+ steps: [
216
+ hydrateNode(node.body, ctx),
217
+ ],
218
+ until: hydrateUntilPredicate(node.until),
219
+ maxIterations: node.maxIterations,
220
+ });
221
+ }
222
+ function hydrateSequenceNode(node, ctx) {
223
+ if (node.kind !== 'sequence') {
224
+ return frameworkCast(undefined);
225
+ }
226
+ const children = node.steps.map((s) => hydrateNode(s, ctx));
227
+ return frameworkCast(step.run({
228
+ id: node.id,
229
+ execute: async (input, execCtx) => {
230
+ let current = input;
231
+ for (const child of children) {
232
+ const childInput = stringifyResult(current);
233
+ current = await ctx.executeStep(child, childInput, execCtx);
234
+ }
235
+ return stringifyResult(current);
236
+ },
237
+ }));
238
+ }
239
+ function hydrateEveryNode(node, ctx) {
240
+ if (node.kind !== 'every') {
241
+ return frameworkCast(undefined);
242
+ }
243
+ return every({
244
+ id: node.id,
245
+ step: hydrateNode(node.step, ctx),
246
+ ms: node.ms,
247
+ onError: node.onError,
248
+ });
249
+ }
250
+ //#endregion
251
+ //#region Handler Registry
252
+ const NODE_HYDRATORS = {
253
+ llm: hydrateLlmNode,
254
+ tool: hydrateToolNode,
255
+ branch: hydrateBranchNode,
256
+ fork: hydrateForkNode,
257
+ spawn: hydrateSpawnNode,
258
+ provide: hydrateProvideNode,
259
+ loop: hydrateLoopNode,
260
+ sequence: hydrateSequenceNode,
261
+ every: hydrateEveryNode,
262
+ };
263
+ //#endregion
264
+ //#region Helpers
265
+ function resolveTools(toolNames, registry) {
266
+ if (!toolNames || toolNames.length === 0) {
267
+ return [];
268
+ }
269
+ return toolNames.map((name) => {
270
+ const resolved = registry.get(name);
271
+ if (!resolved) {
272
+ throw new NoeticConfigError({
273
+ code: 'UNKNOWN_TOOL_REFERENCE',
274
+ message: `Tool '${name}' referenced in workflow is not registered.`,
275
+ hint: `Available tools: ${[
276
+ ...registry.keys(),
277
+ ].join(', ') || '(none)'}`,
278
+ });
279
+ }
280
+ return resolved;
281
+ });
282
+ }
283
+ function buildMerge(strategy) {
284
+ if (strategy === 'first') {
285
+ return (r) => r[0] ?? '';
286
+ }
287
+ if (strategy === 'concat') {
288
+ return (r) => r.join('\n');
289
+ }
290
+ return (r) => r[r.length - 1] ?? '';
291
+ }
292
+ function stringifyResult(value) {
293
+ if (typeof value === 'string') {
294
+ return value;
295
+ }
296
+ if (value === null || value === undefined) {
297
+ return '';
298
+ }
299
+ return JSON.stringify(value);
300
+ }
301
+ //#endregion
302
+ //#region Public API
303
+ /**
304
+ * Hydrates a single `WorkflowNode` into a live `Step`.
305
+ *
306
+ * @public
307
+ * @param node - Validated workflow node.
308
+ * @param ctx - Hydration context with tool registry and step executor.
309
+ * @returns A live `Step` ready for execution.
310
+ * @throws `NoeticConfigError` with code `UNKNOWN_NODE_KIND` if the node kind is unrecognised.
311
+ * @throws `NoeticConfigError` with code `UNKNOWN_TOOL_REFERENCE` if a tool name cannot be resolved.
312
+ * @throws `NoeticConfigError` with code `UNKNOWN_UNTIL_PREDICATE` if an until predicate kind is unrecognised.
313
+ */
314
+ export function hydrateNode(node, ctx) {
315
+ const hydrator = NODE_HYDRATORS[node.kind];
316
+ if (!hydrator) {
317
+ throw new NoeticConfigError({
318
+ code: 'UNKNOWN_NODE_KIND',
319
+ message: `Unknown workflow node kind: '${node.kind}'.`,
320
+ hint: `Supported kinds: ${Object.keys(NODE_HYDRATORS).join(', ')}.`,
321
+ });
322
+ }
323
+ return hydrator(node, ctx);
324
+ }
325
+ /**
326
+ * Hydrates a complete `WorkflowDocument` into a live `Step` tree.
327
+ *
328
+ * @public
329
+ * @param doc - Validated workflow document (version 1).
330
+ * @param ctx - Hydration context with tool registry and step executor.
331
+ * @returns The root `Step` of the hydrated workflow.
332
+ */
333
+ export function hydrateWorkflow(doc, ctx) {
334
+ return hydrateNode(doc.root, ctx);
335
+ }
336
+ //#endregion
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createMessage, extractAssistantText, trackUsage } from '../interpreter/message-helpers';
2
+ import { createMessage, extractAssistantText, trackUsage } from '../util/message-helpers';
3
3
  import { cosineSimilarity } from './cosine-similarity';
4
4
  //#endregion
5
5
  //#region Helpers
@@ -43,6 +43,8 @@ function formatMessage(error) {
43
43
  return `Budget exceeded: ${error.field} limit ${error.limit}, actual ${error.actual}`;
44
44
  case 'steering_denied':
45
45
  return `Steering denied${error.guidance ? `: ${error.guidance}` : ''}`;
46
+ case 'handle_evicted':
47
+ return `Subprocess handle '${error.handleId}' evicted (step '${error.stepId}'): adapter returned null for ${error.gracePeriodMs}ms`;
46
48
  default: {
47
49
  const _exhaustive = error;
48
50
  void _exhaustive;
@@ -1,20 +1,9 @@
1
- import type { ZodType } from 'zod';
2
- import type { LayerStateStore } from '../memory/layer-lifecycle';
3
- import type { Channel, ChannelHandle, ExternalChannel } from '../types/channel';
4
- import type { LLMResponse, LlmProviderConfig } from '../types/common';
5
- import type { Context, CwdState } from '../types/context';
6
- import type { DetachedHandle } from '../types/detached';
7
- import type { FsAdapter } from '../types/fs-adapter';
8
- import type { HarnessResponse, StreamEvent, StreamingItem } from '../types/harness-result';
9
- import type { ExecuteInput, Item, ItemSchemaExtensions } from '../types/items';
10
- import type { ContextMemory, MemoryLayer, StorageAdapter } from '../types/memory';
11
- import type { Span, TraceExporter } from '../types/observability';
12
- import type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, DeliveryMode, ExecuteOptions, HarnessStatus, RecallLayerOutput, SessionScope } from '../types/runtime';
13
- import type { ShellAdapter } from '../types/shell-adapter';
14
- import type { SteeringDecision } from '../types/steering';
15
- import type { Step } from '../types/step';
16
- import type { SubprocessAdapter } from '../types/subprocess-adapter';
17
- import type { EventBroadcaster } from './event-broadcaster';
1
+ import { ItemSchemaRegistry } from '../schemas/item';
2
+ import type { DetachedSpawnOverrides } from './deps/interpreter';
3
+ import type { LayerStateStore } from './deps/memory';
4
+ import type { CheckpointStore, EventBroadcaster } from './deps/runtime';
5
+ import type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, ContextMemory, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, MemoryLayer, RecallLayerOutput, SessionScope, ShellAdapter, Span, SteeringDecision, Step, StorageAdapter, StreamEvent, StreamingItem, SubprocessAdapter, Tool, TraceExporter, ZodType } from './deps/types';
6
+ export { createStreamIdleWatchdog } from './model-call';
18
7
  interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<string, unknown>> {
19
8
  name: string;
20
9
  initialStep?: Step<ContextMemory, string, string>;
@@ -22,6 +11,13 @@ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<stri
22
11
  memory?: MemoryLayer[];
23
12
  storage?: StorageAdapter;
24
13
  hooks?: AgentHooks;
14
+ /**
15
+ * Harness-wide tool pool. Merged (identity-deduplicated) with tools
16
+ * collected from `initialStep` to form every context's `unifiedTools`.
17
+ * Use this when the workflow step tree is static and tools are supplied
18
+ * per harness instance rather than baked into individual `step.llm` calls.
19
+ */
20
+ tools?: Tool[];
25
21
  params: TParams;
26
22
  paramsSchema?: ZodType<TParams>;
27
23
  /** Filesystem adapter. Defaults to local node:fs when not provided. */
@@ -30,6 +26,13 @@ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<stri
30
26
  shell?: ShellAdapter;
31
27
  /** Subprocess adapter. Defaults to an in-memory, same-process adapter. */
32
28
  subprocess?: SubprocessAdapter;
29
+ /**
30
+ * Checkpoint store used by `harness.checkpoint(ctx)` / `harness.restore()`.
31
+ * When absent, checkpoint/restore are no-ops — a zero-config harness keeps
32
+ * its current ephemeral semantics. Construct with `createCheckpointStore`
33
+ * to enable durable execution.
34
+ */
35
+ checkpointStore?: CheckpointStore;
33
36
  llm?: LlmProviderConfig;
34
37
  /** Harness-wide item schema extensions. */
35
38
  itemSchemas?: ItemSchemaExtensions;
@@ -54,19 +57,6 @@ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<stri
54
57
  /** @internal Test-only escape hatch to inject a mock callModel implementation. */
55
58
  _testCallModel?: (request: CallModelRequest) => Promise<LLMResponse>;
56
59
  }
57
- interface StreamIdleWatchdog {
58
- /** Bump the deadline because a stream event just arrived. */
59
- reset: () => void;
60
- /** Clear the pending timer. Safe to call multiple times. */
61
- stop: () => void;
62
- }
63
- /** @internal Create a watchdog that aborts `controller` when no
64
- * {@link StreamIdleWatchdog.reset reset} is called within `timeoutMs`. When
65
- * `timeoutMs <= 0`, returns an inert no-op so callers can always call `.reset()`
66
- * / `.stop()` without a branch. Starts armed: the caller is responsible for
67
- * `.stop()` in a `finally`. `onTimeout` runs before the abort so observers can
68
- * emit a framework event with the original cause. Exported only for unit tests. */
69
- export declare function createStreamIdleWatchdog(timeoutMs: number, controller: AbortController, onTimeout?: () => void): StreamIdleWatchdog;
70
60
  /**
71
61
  * Default agent harness for executing agent steps with built-in channel, memory, and trace support.
72
62
  * Provides channel store, memory layer lifecycle, and trace export with no external dependencies.
@@ -82,8 +72,19 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
82
72
  readonly fs: FsAdapter;
83
73
  readonly shell: ShellAdapter;
84
74
  readonly subprocess: SubprocessAdapter;
75
+ /**
76
+ * Optional durable-execution store. When present, `checkpoint(ctx)` writes
77
+ * a `CheckpointSnapshot`; when absent, it's a no-op.
78
+ * @internal
79
+ */
80
+ readonly checkpointStore?: CheckpointStore;
85
81
  private readonly initialStep?;
86
- private readonly _memory?;
82
+ /** Harness-wide tool pool merged into every context's `unifiedTools`. */
83
+ private readonly harnessTools;
84
+ /** @internal Memory layers configured for this harness. Exposed non-private
85
+ * so free functions in `runtime/durable/` (checkpoint/restore) can read it
86
+ * without friend-class gymnastics. Do not access from outside core. */
87
+ readonly _memory?: MemoryLayer[];
87
88
  private readonly client?;
88
89
  private readonly channelStore;
89
90
  private readonly callModelOverride?;
@@ -98,7 +99,10 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
98
99
  * TUI — observe each other's `cd`s.
99
100
  */
100
101
  readonly rootCwdState: CwdState;
101
- private readonly itemSchemas;
102
+ /** @internal Item schema registry. Exposed non-private so free functions
103
+ * in `runtime/durable/` (restore) can parse persisted items. Do not
104
+ * access from outside core. */
105
+ readonly itemSchemas: ItemSchemaRegistry;
102
106
  constructor(opts: AgentHarnessOpts<TParams>);
103
107
  /**
104
108
  * Update the harness-wide root cwd. The TUI calls this in response to a
@@ -122,20 +126,11 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
122
126
  private getOrCreateSession;
123
127
  private requireSession;
124
128
  callModel(request: CallModelRequest): Promise<LLMResponse>;
125
- /** @internal Drain only messages tagged `between-rounds` from the queue. */
126
- private drainBetweenRoundsMessages;
127
129
  run<I, O>(s: Step<ContextMemory, I, O>, input: I, ctx: Context): Promise<O>;
128
130
  /** Run all layer `init` hooks before the first step executes. Per spec 11,
129
131
  * init MUST complete before any recall fires so layer state is populated. */
130
132
  private initAndRun;
131
- detachedSpawn<I, O>(s: Step<ContextMemory, I, O>, input: I, parentCtx: Context, overrides?: {
132
- /** Override the child's thread id. Default inherits from `parentCtx.threadId`. */
133
- threadId?: string;
134
- /** Override the child's resource id. Default inherits from `parentCtx.resourceId`. */
135
- resourceId?: string;
136
- /** Override the child's initial cwd. Used by worktree isolation to root the child at the worktree path. */
137
- cwdInit?: string;
138
- }): DetachedHandle<O>;
133
+ detachedSpawn<I, O>(s: Step<ContextMemory, I, O>, input: I, parentCtx: Context, overrides?: DetachedSpawnOverrides): DetachedHandle<O>;
139
134
  createContext(opts?: {
140
135
  parent?: Context;
141
136
  items?: Item[];
@@ -174,8 +169,34 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
174
169
  previewRequestItems(scope?: SessionScope): Promise<ReadonlyArray<Item>>;
175
170
  storeLayers(layers: MemoryLayer[], response: LLMResponse, ctx: Context): Promise<void>;
176
171
  disposeLayers(layers: MemoryLayer[], ctx: Context): Promise<void>;
177
- checkpoint(_ctx: Context): Promise<void>;
178
- restore(_executionId: string): Promise<Context | null>;
172
+ /**
173
+ * Snapshot the execution state at a checkpoint boundary.
174
+ *
175
+ * Fires at four well-defined points on the happy path:
176
+ * 1. End of every `execute()` that mutated the item log — so a crash
177
+ * between turns lands on a snapshot that includes the user/assistant
178
+ * items that actually flowed.
179
+ * 2. After `detachedSpawn()` settles (success or failure) — the parent's
180
+ * record of running/completed children stays consistent with the
181
+ * adapter's handle manifest.
182
+ * 3. After an ask-user enqueue — a restart can replay the pending modal
183
+ * to the TUI.
184
+ * 4. After `runAppendPipeline` — layer state can mutate as items land,
185
+ * so the snapshot must follow the mutation.
186
+ *
187
+ * Delegates to `captureCheckpoint` / `restoreFromCheckpoint` in
188
+ * `runtime/durable/harness-checkpoints` so the ~140 lines of snapshot
189
+ * logic live beside the other durability machinery.
190
+ */
191
+ checkpoint(ctx: Context): Promise<void>;
192
+ /**
193
+ * Rebuild a `Context` from a previously-persisted snapshot. Returns `null`
194
+ * if no snapshot is recorded for `executionId`. Surface a typed
195
+ * `NoeticConfigError(CHECKPOINT_SCHEMA_MISMATCH)` when the snapshot's
196
+ * schema version is unrecognised — the caller is expected to discard the
197
+ * checkpoint via `CheckpointStore.clear()` and start a fresh execution.
198
+ */
199
+ restore(executionId: string): Promise<Context | null>;
179
200
  cancel(_ctx: Context, _reason?: string): Promise<void>;
180
201
  createSpan(name: string, parent: Span | null): Span;
181
202
  getLayerState<T>(executionId: string, layerId: string): T | undefined;
@@ -203,4 +224,3 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
203
224
  tokenCount: number;
204
225
  }[]>;
205
226
  }
206
- export {};