@noetic-tools/core 5.6.0 → 5.8.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 (44) hide show
  1. package/dist/builders/channel-builder.d.ts +18 -0
  2. package/dist/builders/control-flow-builders.d.ts +17 -30
  3. package/dist/builders/decide-builders.d.ts +5 -5
  4. package/dist/builders/decide-builders.js +4 -0
  5. package/dist/builders/every.d.ts +2 -2
  6. package/dist/builders/every.js +2 -0
  7. package/dist/builders/loop-builder.d.ts +1 -1
  8. package/dist/builders/loop-builder.js +1 -1
  9. package/dist/builders/provide-builder.d.ts +2 -2
  10. package/dist/builders/provide-builder.js +2 -0
  11. package/dist/builders/spawn-builder.d.ts +2 -2
  12. package/dist/builders/spawn-builder.js +2 -0
  13. package/dist/builders/step-builders.d.ts +8 -8
  14. package/dist/builders/workflow-hydrator.js +6 -1
  15. package/dist/harness/agent-harness.d.ts +20 -5
  16. package/dist/harness/agent-harness.js +49 -14
  17. package/dist/harness/deps/runtime.d.ts +3 -2
  18. package/dist/harness/deps/runtime.js +2 -2
  19. package/dist/harness/model-call.js +10 -0
  20. package/dist/index.d.ts +9 -7
  21. package/dist/index.js +4 -4
  22. package/dist/interpreter/child-span.d.ts +17 -0
  23. package/dist/interpreter/child-span.js +51 -0
  24. package/dist/interpreter/execute-action.js +21 -33
  25. package/dist/interpreter/execute-control.js +64 -8
  26. package/dist/interpreter/execute.d.ts +10 -0
  27. package/dist/interpreter/execute.js +41 -12
  28. package/dist/observability/genai-attributes.d.ts +45 -29
  29. package/dist/observability/genai-attributes.js +19 -1
  30. package/dist/runtime/channel-store.d.ts +73 -1
  31. package/dist/runtime/channel-store.js +179 -63
  32. package/dist/runtime/context-impl.d.ts +10 -1
  33. package/dist/runtime/context-impl.js +17 -0
  34. package/dist/runtime/durable/harness-checkpoints.d.ts +5 -1
  35. package/dist/runtime/durable/harness-checkpoints.js +8 -2
  36. package/dist/runtime/durable/step-ledger.d.ts +14 -4
  37. package/dist/runtime/durable/step-ledger.js +22 -5
  38. package/dist/types/checkpoint.d.ts +31 -0
  39. package/dist/types/checkpoint.js +29 -0
  40. package/dist/until/combinators.d.ts +10 -0
  41. package/dist/until/combinators.js +37 -13
  42. package/dist/until/predicates.js +4 -2
  43. package/package.json +3 -3
  44. package/schema/noetic-workflow.schema.json +57 -0
@@ -11,6 +11,12 @@
11
11
  * into the single `execution:<id>:snapshot` key would rewrite a growing blob on every
12
12
  * step — O(n²) bytes over a run.
13
13
  *
14
+ * An entry's key is one of two forms (spec 23a § Identity). By default it is the step's
15
+ * execution path (`ContextImpl.currentPath()`): positional, so a resumed run lines up
16
+ * recorded outputs with steps by where they ran. A step with a `replayKey` is keyed
17
+ * `key:<replayKey>` instead: content-addressed, so it finds its output wherever it
18
+ * runs. A `durable: false` step has no key and is never recorded.
19
+ *
14
20
  * Sharding bounds the cost of one append but not the total, so retention is bounded on
15
21
  * two axes (`StepLedgerRetention`): an oversized entry is dropped rather than written,
16
22
  * and past `maxEntries` the oldest entry is evicted. Both degradations are safe by
@@ -21,9 +27,14 @@ import { storageGetMany } from '@noetic-tools/context';
21
27
  import { NoeticConfigError } from '@noetic-tools/types';
22
28
  import { z } from 'zod';
23
29
  //#region Entry
24
- /** @public One completed step, keyed by its execution path. */
30
+ /** Marks a ledger key as a step's `replayKey` rather than its execution path. */
31
+ export const REPLAY_KEY_PREFIX = 'key:';
32
+ /** @public One completed step, keyed by its execution path or its `key:` replay key. */
25
33
  export const StepLedgerEntrySchema = z.object({
26
- /** Execution path key — see `ContextImpl.currentPath()`. Unique per dispatch. */
34
+ /**
35
+ * Ledger key: the execution path (`ContextImpl.currentPath()`, unique per dispatch)
36
+ * or `key:<replayKey>` for a step that sets `replayKey`.
37
+ */
27
38
  path: z.string().min(1),
28
39
  stepId: z.string(),
29
40
  kind: z.string(),
@@ -197,9 +208,11 @@ export class StepLedger {
197
208
  }
198
209
  /**
199
210
  * The recorded output for `path`, when a previous run completed the same step there.
200
- * A divergence (different step id or kind at this path) discards the entry and every
201
- * entry recorded beneath it, then returns undefined so the step runs fresh — the
202
- * subtree's recorded outputs belong to a branch that no longer exists.
211
+ * A divergence (different step id or kind at this key) returns undefined so the step
212
+ * runs fresh. At a positional path it also discards every entry recorded beneath it —
213
+ * the subtree's recorded outputs belong to a branch that no longer exists. A `key:`
214
+ * entry discards only itself: its children are keyed by their own paths or keys, not
215
+ * nested under it.
203
216
  */
204
217
  take(path, step) {
205
218
  const entry = this.replayable.get(path);
@@ -207,6 +220,10 @@ export class StepLedger {
207
220
  return undefined;
208
221
  }
209
222
  if (entry.stepId !== step.id || entry.kind !== step.kind) {
223
+ if (path.startsWith(REPLAY_KEY_PREFIX)) {
224
+ this.replayable.delete(path);
225
+ return undefined;
226
+ }
210
227
  this.discardSubtree(path);
211
228
  return undefined;
212
229
  }
@@ -72,6 +72,26 @@ export declare const ItemLogSnapshotSchema: z.ZodObject<{
72
72
  }, z.core.$strip>;
73
73
  /** @public Item log portion of a snapshot. */
74
74
  export type ItemLogSnapshot = z.infer<typeof ItemLogSnapshotSchema>;
75
+ /**
76
+ * @public
77
+ * Channel state an execution used, captured so a restored execution sees the
78
+ * same values and queued items. Only stored data is kept — parked receivers,
79
+ * parked senders, and subscribers are live promises that cannot round-trip.
80
+ * Topic channels hold no stored data, so they never appear.
81
+ */
82
+ export declare const ChannelSnapshotSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
83
+ name: z.ZodString;
84
+ mode: z.ZodLiteral<"value">;
85
+ value: z.ZodOptional<z.ZodUnknown>;
86
+ hasValue: z.ZodBoolean;
87
+ }, z.core.$strip>, z.ZodObject<{
88
+ name: z.ZodString;
89
+ mode: z.ZodLiteral<"queue">;
90
+ capacity: z.ZodNumber;
91
+ queue: z.ZodArray<z.ZodUnknown>;
92
+ }, z.core.$strip>], "mode">;
93
+ /** @public One channel's stored state inside a checkpoint snapshot. */
94
+ export type ChannelSnapshot = z.infer<typeof ChannelSnapshotSchema>;
75
95
  /**
76
96
  * @public
77
97
  * Top-level checkpoint payload persisted through a `CheckpointStore`. Carries
@@ -101,6 +121,17 @@ export declare const CheckpointSnapshotSchema: z.ZodObject<{
101
121
  itemLog: z.ZodObject<{
102
122
  items: z.ZodArray<z.ZodUnknown>;
103
123
  }, z.core.$strip>;
124
+ channels: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
125
+ name: z.ZodString;
126
+ mode: z.ZodLiteral<"value">;
127
+ value: z.ZodOptional<z.ZodUnknown>;
128
+ hasValue: z.ZodBoolean;
129
+ }, z.core.$strip>, z.ZodObject<{
130
+ name: z.ZodString;
131
+ mode: z.ZodLiteral<"queue">;
132
+ capacity: z.ZodNumber;
133
+ queue: z.ZodArray<z.ZodUnknown>;
134
+ }, z.core.$strip>], "mode">>>;
104
135
  capturedAt: z.ZodString;
105
136
  }, z.core.$strip>;
106
137
  /** @public Snapshot of an execution suitable for `CheckpointStore.save()`. */
@@ -68,6 +68,29 @@ export const PendingAskUserSnapshotSchema = z.object({
68
68
  export const ItemLogSnapshotSchema = z.object({
69
69
  items: z.array(z.unknown()),
70
70
  });
71
+ /**
72
+ * @public
73
+ * Channel state an execution used, captured so a restored execution sees the
74
+ * same values and queued items. Only stored data is kept — parked receivers,
75
+ * parked senders, and subscribers are live promises that cannot round-trip.
76
+ * Topic channels hold no stored data, so they never appear.
77
+ */
78
+ export const ChannelSnapshotSchema = z.discriminatedUnion('mode', [
79
+ z.object({
80
+ name: z.string(),
81
+ mode: z.literal('value'),
82
+ /** The last value written. Absent when the channel was never written. */
83
+ value: z.unknown().optional(),
84
+ hasValue: z.boolean(),
85
+ }),
86
+ z.object({
87
+ name: z.string(),
88
+ mode: z.literal('queue'),
89
+ capacity: z.number(),
90
+ /** Queued items, oldest first. */
91
+ queue: z.array(z.unknown()),
92
+ }),
93
+ ]);
71
94
  /**
72
95
  * @public
73
96
  * Top-level checkpoint payload persisted through a `CheckpointStore`. Carries
@@ -84,6 +107,12 @@ export const CheckpointSnapshotSchema = z.object({
84
107
  cwd: CwdSnapshotSchema.nullable(),
85
108
  askUser: z.array(PendingAskUserSnapshotSchema),
86
109
  itemLog: ItemLogSnapshotSchema,
110
+ /**
111
+ * Stored state of every channel the execution used. Optional so snapshots
112
+ * written before channels were captured still load (they restore with no
113
+ * channel state).
114
+ */
115
+ channels: z.array(ChannelSnapshotSchema).optional(),
87
116
  /** ISO-8601 timestamp of when the snapshot was taken. */
88
117
  capturedAt: z.string(),
89
118
  });
@@ -2,6 +2,11 @@ import type { Until } from '@noetic-tools/types';
2
2
  /**
3
3
  * Combines predicates with OR semantics; stops the loop when any predicate says stop.
4
4
  *
5
+ * Predicates run in order and the first stop verdict short-circuits, so it is
6
+ * returned as is. When none stops, the combined verdict carries every
7
+ * predicate's `reason` (joined with `'; '`) and `feedback` (joined with `'\n'`),
8
+ * so a failing `until.verified()` still hands its feedback to `prepareNext`.
9
+ *
5
10
  * @public
6
11
  * @param predicates - Until predicates to evaluate.
7
12
  * @returns A combined `Until` that short-circuits on the first stop verdict.
@@ -10,6 +15,11 @@ export declare function any(...predicates: Until[]): Until;
10
15
  /**
11
16
  * Combines predicates with AND semantics; stops the loop only when all predicates say stop.
12
17
  *
18
+ * Predicates run in order and the first continue verdict short-circuits, so it
19
+ * is returned as is. When all stop, the combined verdict carries every
20
+ * predicate's `reason` (joined with `'; '`, default `'All predicates
21
+ * satisfied'`) and `feedback` (joined with `'\n'`).
22
+ *
13
23
  * @public
14
24
  * @param predicates - Until predicates to evaluate.
15
25
  * @returns A combined `Until` that requires all predicates to be satisfied.
@@ -1,26 +1,36 @@
1
1
  /**
2
2
  * Combines predicates with OR semantics; stops the loop when any predicate says stop.
3
3
  *
4
+ * Predicates run in order and the first stop verdict short-circuits, so it is
5
+ * returned as is. When none stops, the combined verdict carries every
6
+ * predicate's `reason` (joined with `'; '`) and `feedback` (joined with `'\n'`),
7
+ * so a failing `until.verified()` still hands its feedback to `prepareNext`.
8
+ *
4
9
  * @public
5
10
  * @param predicates - Until predicates to evaluate.
6
11
  * @returns A combined `Until` that short-circuits on the first stop verdict.
7
12
  */
8
13
  export function any(...predicates) {
9
14
  return async (snapshot) => {
15
+ const verdicts = [];
10
16
  for (const pred of predicates) {
11
17
  const verdict = await pred(snapshot);
12
18
  if (verdict.stop) {
13
19
  return verdict;
14
20
  }
21
+ verdicts.push(verdict);
15
22
  }
16
- return {
17
- stop: false,
18
- };
23
+ return merge(false, verdicts);
19
24
  };
20
25
  }
21
26
  /**
22
27
  * Combines predicates with AND semantics; stops the loop only when all predicates say stop.
23
28
  *
29
+ * Predicates run in order and the first continue verdict short-circuits, so it
30
+ * is returned as is. When all stop, the combined verdict carries every
31
+ * predicate's `reason` (joined with `'; '`, default `'All predicates
32
+ * satisfied'`) and `feedback` (joined with `'\n'`).
33
+ *
24
34
  * @public
25
35
  * @param predicates - Until predicates to evaluate.
26
36
  * @returns A combined `Until` that requires all predicates to be satisfied.
@@ -30,20 +40,34 @@ export function all(...predicates) {
30
40
  const verdicts = [];
31
41
  for (const pred of predicates) {
32
42
  const verdict = await pred(snapshot);
33
- verdicts.push(verdict);
34
43
  if (!verdict.stop) {
35
- return {
36
- stop: false,
37
- };
44
+ return verdict;
38
45
  }
46
+ verdicts.push(verdict);
39
47
  }
40
- const reasons = verdicts
41
- .map((v) => v.reason)
42
- .filter(Boolean)
43
- .join('; ');
48
+ const merged = merge(true, verdicts);
44
49
  return {
45
- stop: true,
46
- reason: reasons || 'All predicates satisfied',
50
+ ...merged,
51
+ reason: merged.reason ?? 'All predicates satisfied',
47
52
  };
48
53
  };
49
54
  }
55
+ /** Folds the verdicts that decided a combinator into one, leaving out empty fields. */
56
+ function merge(stop, verdicts) {
57
+ const verdict = {
58
+ stop,
59
+ };
60
+ const reason = join(verdicts.map((v) => v.reason), '; ');
61
+ if (reason !== undefined) {
62
+ verdict.reason = reason;
63
+ }
64
+ const feedback = join(verdicts.map((v) => v.feedback), '\n');
65
+ if (feedback !== undefined) {
66
+ verdict.feedback = feedback;
67
+ }
68
+ return verdict;
69
+ }
70
+ function join(parts, separator) {
71
+ const present = parts.filter((p) => p !== undefined && p !== '');
72
+ return present.length === 0 ? undefined : present.join(separator);
73
+ }
@@ -18,7 +18,9 @@ export const until = {
18
18
  maxCost(usd) {
19
19
  return (snap) => ({
20
20
  stop: snap.cost >= usd,
21
- reason: snap.cost >= usd ? `Reached max cost ($${usd})` : undefined,
21
+ reason: snap.cost >= usd
22
+ ? `Cost $${snap.cost.toFixed(2)} exceeded budget $${usd.toFixed(2)}`
23
+ : undefined,
22
24
  });
23
25
  },
24
26
  /** Stops the loop when elapsed time reaches `ms` milliseconds. @public */
@@ -52,7 +54,7 @@ export const until = {
52
54
  const result = await fn(snap.lastOutput);
53
55
  return {
54
56
  stop: result.pass,
55
- reason: result.pass ? 'Verification passed' : undefined,
57
+ reason: result.pass ? 'Verification passed' : 'Verification failed',
56
58
  feedback: result.feedback,
57
59
  };
58
60
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noetic-tools/core",
3
- "version": "5.6.0",
3
+ "version": "5.8.0",
4
4
  "type": "module",
5
5
  "description": "Step primitives, interpreter, runtime, and context layer contract for Noetic agents.",
6
6
  "license": "Apache-2.0",
@@ -68,8 +68,8 @@
68
68
  }
69
69
  },
70
70
  "dependencies": {
71
- "@noetic-tools/context": "^1.4.0",
72
- "@noetic-tools/types": "^3.4.0",
71
+ "@noetic-tools/context": "^1.5.0",
72
+ "@noetic-tools/types": "^3.6.0",
73
73
  "@openrouter/agent": "^0.11.0",
74
74
  "@openrouter/sdk": "1.4.25",
75
75
  "zod": "^4.0.0"
@@ -32,6 +32,9 @@
32
32
  "type": "string",
33
33
  "minLength": 1
34
34
  },
35
+ "durable": {
36
+ "type": "boolean"
37
+ },
35
38
  "model": {
36
39
  "type": "string"
37
40
  },
@@ -83,6 +86,18 @@
83
86
  "items": {
84
87
  "type": "string"
85
88
  }
89
+ },
90
+ "serviceTier": {
91
+ "type": "string",
92
+ "enum": [
93
+ "auto",
94
+ "default",
95
+ "fast",
96
+ "flex",
97
+ "priority",
98
+ "scale",
99
+ "ultrafast"
100
+ ]
86
101
  }
87
102
  },
88
103
  "additionalProperties": false
@@ -124,6 +139,9 @@
124
139
  "type": "string",
125
140
  "minLength": 1
126
141
  },
142
+ "durable": {
143
+ "type": "boolean"
144
+ },
127
145
  "toolName": {
128
146
  "type": "string",
129
147
  "minLength": 1
@@ -154,6 +172,9 @@
154
172
  "type": "string",
155
173
  "minLength": 1
156
174
  },
175
+ "durable": {
176
+ "type": "boolean"
177
+ },
157
178
  "execute": {
158
179
  "type": "string",
159
180
  "minLength": 1
@@ -213,6 +234,9 @@
213
234
  "type": "string",
214
235
  "minLength": 1
215
236
  },
237
+ "durable": {
238
+ "type": "boolean"
239
+ },
216
240
  "routes": {
217
241
  "minItems": 1,
218
242
  "type": "array",
@@ -256,6 +280,9 @@
256
280
  "type": "string",
257
281
  "minLength": 1
258
282
  },
283
+ "durable": {
284
+ "type": "boolean"
285
+ },
259
286
  "model": {
260
287
  "type": "string",
261
288
  "minLength": 1
@@ -432,6 +459,9 @@
432
459
  "type": "string",
433
460
  "minLength": 1
434
461
  },
462
+ "durable": {
463
+ "type": "boolean"
464
+ },
435
465
  "decide": {
436
466
  "type": "object",
437
467
  "properties": {
@@ -443,6 +473,9 @@
443
473
  "type": "string",
444
474
  "minLength": 1
445
475
  },
476
+ "durable": {
477
+ "type": "boolean"
478
+ },
446
479
  "model": {
447
480
  "type": "string",
448
481
  "minLength": 1
@@ -652,6 +685,9 @@
652
685
  "type": "string",
653
686
  "minLength": 1
654
687
  },
688
+ "durable": {
689
+ "type": "boolean"
690
+ },
655
691
  "mode": {
656
692
  "type": "string",
657
693
  "enum": [
@@ -706,6 +742,9 @@
706
742
  "type": "string",
707
743
  "minLength": 1
708
744
  },
745
+ "durable": {
746
+ "type": "boolean"
747
+ },
709
748
  "child": {
710
749
  "$ref": "#/$defs/WorkflowNode"
711
750
  },
@@ -740,6 +779,9 @@
740
779
  "type": "string",
741
780
  "minLength": 1
742
781
  },
782
+ "durable": {
783
+ "type": "boolean"
784
+ },
743
785
  "child": {
744
786
  "$ref": "#/$defs/WorkflowNode"
745
787
  },
@@ -771,6 +813,9 @@
771
813
  "type": "string",
772
814
  "minLength": 1
773
815
  },
816
+ "durable": {
817
+ "type": "boolean"
818
+ },
774
819
  "body": {
775
820
  "$ref": "#/$defs/WorkflowNode"
776
821
  },
@@ -802,6 +847,9 @@
802
847
  "type": "string",
803
848
  "minLength": 1
804
849
  },
850
+ "durable": {
851
+ "type": "boolean"
852
+ },
805
853
  "steps": {
806
854
  "minItems": 1,
807
855
  "type": "array",
@@ -828,6 +876,9 @@
828
876
  "type": "string",
829
877
  "minLength": 1
830
878
  },
879
+ "durable": {
880
+ "type": "boolean"
881
+ },
831
882
  "step": {
832
883
  "$ref": "#/$defs/WorkflowNode"
833
884
  },
@@ -862,6 +913,9 @@
862
913
  "type": "string",
863
914
  "minLength": 1
864
915
  },
916
+ "durable": {
917
+ "type": "boolean"
918
+ },
865
919
  "document": {
866
920
  "$ref": "#"
867
921
  },
@@ -890,6 +944,9 @@
890
944
  "type": "string",
891
945
  "minLength": 1
892
946
  },
947
+ "durable": {
948
+ "type": "boolean"
949
+ },
893
950
  "agent": {
894
951
  "type": "string",
895
952
  "minLength": 1