@noetic-tools/core 5.7.0 → 5.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.
- package/dist/adapters/openrouter.d.ts +1 -0
- package/dist/adapters/openrouter.js +1 -0
- package/dist/builders/channel-builder.d.ts +18 -0
- package/dist/builders/control-flow-builders.d.ts +17 -30
- package/dist/builders/decide-builders.d.ts +5 -5
- package/dist/builders/decide-builders.js +4 -0
- package/dist/builders/every.d.ts +2 -2
- package/dist/builders/every.js +2 -0
- package/dist/builders/loop-builder.d.ts +1 -1
- package/dist/builders/loop-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +3 -2
- package/dist/builders/provide-builder.js +3 -0
- package/dist/builders/spawn-builder.d.ts +2 -3
- package/dist/builders/spawn-builder.js +2 -1
- package/dist/builders/step-builders.d.ts +8 -9
- package/dist/builders/step-builders.js +0 -1
- package/dist/builders/tool-builder.d.ts +2 -0
- package/dist/builders/tool-builder.js +2 -0
- package/dist/builders/workflow-hydrator.js +21 -14
- package/dist/builders/workflow-replay-key.d.ts +15 -0
- package/dist/builders/workflow-replay-key.js +46 -0
- package/dist/harness/agent-harness.d.ts +43 -11
- package/dist/harness/agent-harness.js +121 -40
- package/dist/harness/deps/interpreter.d.ts +1 -0
- package/dist/harness/deps/interpreter.js +1 -0
- package/dist/harness/deps/runtime.d.ts +3 -2
- package/dist/harness/deps/runtime.js +2 -2
- package/dist/harness/model-call.d.ts +3 -0
- package/dist/harness/model-call.js +19 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -3
- package/dist/interpreter/action-deps.d.ts +1 -1
- package/dist/interpreter/action-deps.js +1 -1
- package/dist/interpreter/child-span.d.ts +17 -0
- package/dist/interpreter/child-span.js +51 -0
- package/dist/interpreter/collect-tools.js +19 -54
- package/dist/interpreter/execute-action.js +30 -17
- package/dist/interpreter/execute-control.js +67 -9
- package/dist/interpreter/execute-decide.js +9 -27
- package/dist/interpreter/execute.d.ts +10 -0
- package/dist/interpreter/execute.js +69 -46
- package/dist/interpreter/model-error.d.ts +9 -0
- package/dist/interpreter/model-error.js +32 -0
- package/dist/interpreter/step-walk.d.ts +11 -0
- package/dist/interpreter/step-walk.js +41 -0
- package/dist/observability/genai-attributes.d.ts +52 -29
- package/dist/observability/genai-attributes.js +27 -1
- package/dist/runtime/channel-store.d.ts +73 -1
- package/dist/runtime/channel-store.js +179 -63
- package/dist/runtime/context-impl.d.ts +36 -32
- package/dist/runtime/context-impl.js +80 -45
- package/dist/runtime/durable/harness-checkpoints.d.ts +33 -16
- package/dist/runtime/durable/harness-checkpoints.js +74 -52
- package/dist/runtime/durable/step-ledger.d.ts +15 -5
- package/dist/runtime/durable/step-ledger.js +23 -6
- package/dist/schemas/workflow.d.ts +1 -1
- package/dist/types/checkpoint.d.ts +31 -0
- package/dist/types/checkpoint.js +29 -0
- package/dist/until/combinators.d.ts +10 -0
- package/dist/until/combinators.js +37 -13
- package/dist/until/predicates.js +4 -2
- package/package.json +3 -3
- package/schema/noetic-workflow.schema.json +432 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LayerStateStore } from '@noetic-tools/context';
|
|
2
2
|
import type { Context, Item, ItemSchemaRegistry, RestoreContextOptions } from '@noetic-tools/types';
|
|
3
|
+
import type { ChannelStore } from '../channel-store.js';
|
|
3
4
|
import type { EventBroadcaster } from '../event-broadcaster.js';
|
|
4
5
|
import type { CheckpointStore } from './checkpoint-store.js';
|
|
5
6
|
import type { StepLedgerRetention, StepLedgerStore } from './step-ledger.js';
|
|
@@ -28,6 +29,7 @@ export interface CheckpointHarnessHandle {
|
|
|
28
29
|
readonly stepLedgerStore?: StepLedgerStore;
|
|
29
30
|
readonly stepLedgerRetention?: StepLedgerRetention;
|
|
30
31
|
readonly layerStateStore: LayerStateStore;
|
|
32
|
+
readonly channelStore: Pick<ChannelStore, 'snapshot' | 'restore'>;
|
|
31
33
|
readonly itemSchemas: ItemSchemaRegistry;
|
|
32
34
|
createContext(opts?: RestoreCheckpointOptions & {
|
|
33
35
|
items?: Item[];
|
|
@@ -47,27 +49,42 @@ export interface CheckpointHarnessHandle {
|
|
|
47
49
|
*/
|
|
48
50
|
export declare function captureCheckpoint(h: CheckpointHarnessHandle, ctx: Context): Promise<void>;
|
|
49
51
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* the restored context observes the pre-crash state through
|
|
54
|
-
* `readLayerState` and the context projectors.
|
|
52
|
+
* What `restoreFromCheckpoint` rebuilt. `fromSnapshot` is false for a ledger-only
|
|
53
|
+
* restore, whose layer state was never recovered — the harness must then let layer
|
|
54
|
+
* `init` hydrate normally instead of keeping restored state.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export interface RestoredExecution {
|
|
59
|
+
ctx: Context;
|
|
60
|
+
fromSnapshot: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Rebuild a `Context` for `executionId` from what a previous run recorded: the
|
|
64
|
+
* checkpoint snapshot and the step-completion ledger. Either is enough. Returns
|
|
65
|
+
* `null` only when neither exists, so the caller starts a fresh execution.
|
|
66
|
+
*
|
|
67
|
+
* - With a snapshot, the item log, layer state, channel state, cwd, and thread/resource
|
|
68
|
+
* ids come from it. Layer state is replayed into `layerStateStore` under the original
|
|
69
|
+
* id; channel values and queued items refill the channel store for channels with no
|
|
70
|
+
* live state.
|
|
71
|
+
* - Without one, the context starts like `createContext(opts)` would — empty item
|
|
72
|
+
* log, no layer or channel state, the harness's root cwd, fresh thread id — and only
|
|
73
|
+
* its identity and ledger are recovered. Recorded steps replay; the rest run fresh.
|
|
74
|
+
*
|
|
75
|
+
* `opts` carries the host's own context wiring (broadcaster, parent, state, layer
|
|
76
|
+
* overrides). A snapshot cannot round-trip live objects, so a host that decorated
|
|
77
|
+
* the original context has to hand the same decoration back here. Snapshot-owned
|
|
78
|
+
* fields always win: identity, item log, and cwd come from the persisted record,
|
|
79
|
+
* never from `opts`.
|
|
63
80
|
*
|
|
64
|
-
*
|
|
65
|
-
* `
|
|
66
|
-
*
|
|
81
|
+
* A snapshot whose schema version is unknown throws
|
|
82
|
+
* `NoeticConfigError(CHECKPOINT_SCHEMA_MISMATCH)` from the store; the ledger is not
|
|
83
|
+
* used as a fallback, because resuming onto it would drop state the snapshot held.
|
|
67
84
|
*
|
|
68
85
|
* @internal
|
|
69
86
|
*/
|
|
70
|
-
export declare function restoreFromCheckpoint(h: CheckpointHarnessHandle, executionId: string, opts?: RestoreCheckpointOptions): Promise<
|
|
87
|
+
export declare function restoreFromCheckpoint(h: CheckpointHarnessHandle, executionId: string, opts?: RestoreCheckpointOptions): Promise<RestoredExecution | null>;
|
|
71
88
|
/**
|
|
72
89
|
* Discard every recovery record for one execution: the snapshot and the completion
|
|
73
90
|
* ledger. Clearing the snapshot alone would strand the ledger's shards under
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CheckpointSchemaVersion } from '../../types/checkpoint.js';
|
|
2
|
-
import { ContextImpl } from '../context-impl.js';
|
|
2
|
+
import { ContextImpl, rootIdOf } from '../context-impl.js';
|
|
3
3
|
import { StepLedger } from './step-ledger.js';
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region captureCheckpoint
|
|
@@ -47,6 +47,9 @@ export async function captureCheckpoint(h, ctx) {
|
|
|
47
47
|
...ctx.itemLog.items,
|
|
48
48
|
],
|
|
49
49
|
},
|
|
50
|
+
// Channel state belongs to the root execution, so a child checkpoint
|
|
51
|
+
// carries the channels its root used.
|
|
52
|
+
channels: h.channelStore.snapshot(rootIdOf(ctx)),
|
|
50
53
|
capturedAt: new Date().toISOString(),
|
|
51
54
|
};
|
|
52
55
|
try {
|
|
@@ -56,26 +59,28 @@ export async function captureCheckpoint(h, ctx) {
|
|
|
56
59
|
console.warn(`AgentHarness.checkpoint: failed to persist snapshot for execution "${ctx.id}":`, err);
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
//#endregion
|
|
60
|
-
//#region restoreFromCheckpoint
|
|
61
62
|
/**
|
|
62
|
-
* Rebuild a `Context` from a
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
63
|
+
* Rebuild a `Context` for `executionId` from what a previous run recorded: the
|
|
64
|
+
* checkpoint snapshot and the step-completion ledger. Either is enough. Returns
|
|
65
|
+
* `null` only when neither exists, so the caller starts a fresh execution.
|
|
66
|
+
*
|
|
67
|
+
* - With a snapshot, the item log, layer state, channel state, cwd, and thread/resource
|
|
68
|
+
* ids come from it. Layer state is replayed into `layerStateStore` under the original
|
|
69
|
+
* id; channel values and queued items refill the channel store for channels with no
|
|
70
|
+
* live state.
|
|
71
|
+
* - Without one, the context starts like `createContext(opts)` would — empty item
|
|
72
|
+
* log, no layer or channel state, the harness's root cwd, fresh thread id — and only
|
|
73
|
+
* its identity and ledger are recovered. Recorded steps replay; the rest run fresh.
|
|
67
74
|
*
|
|
68
|
-
* `opts` carries the host's own context wiring (broadcaster, parent, state,
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* fields always win: identity, item log, and cwd come from the persisted
|
|
74
|
-
* record, never from `opts`.
|
|
75
|
+
* `opts` carries the host's own context wiring (broadcaster, parent, state, layer
|
|
76
|
+
* overrides). A snapshot cannot round-trip live objects, so a host that decorated
|
|
77
|
+
* the original context has to hand the same decoration back here. Snapshot-owned
|
|
78
|
+
* fields always win: identity, item log, and cwd come from the persisted record,
|
|
79
|
+
* never from `opts`.
|
|
75
80
|
*
|
|
76
|
-
*
|
|
77
|
-
* `
|
|
78
|
-
*
|
|
81
|
+
* A snapshot whose schema version is unknown throws
|
|
82
|
+
* `NoeticConfigError(CHECKPOINT_SCHEMA_MISMATCH)` from the store; the ledger is not
|
|
83
|
+
* used as a fallback, because resuming onto it would drop state the snapshot held.
|
|
79
84
|
*
|
|
80
85
|
* @internal
|
|
81
86
|
*/
|
|
@@ -85,52 +90,69 @@ export async function restoreFromCheckpoint(h, executionId, opts) {
|
|
|
85
90
|
return null;
|
|
86
91
|
}
|
|
87
92
|
const snapshot = await store.load(executionId);
|
|
88
|
-
|
|
93
|
+
const recovered = await h.stepLedgerStore?.load(executionId);
|
|
94
|
+
if (!snapshot && (!recovered || recovered.entries.size === 0)) {
|
|
89
95
|
return null;
|
|
90
96
|
}
|
|
97
|
+
const ctx = snapshot ? contextFromSnapshot(h, snapshot, opts) : h.createContext(opts);
|
|
98
|
+
if (ctx instanceof ContextImpl) {
|
|
99
|
+
adoptExecution({
|
|
100
|
+
h,
|
|
101
|
+
ctx,
|
|
102
|
+
executionId,
|
|
103
|
+
recovered,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
ctx,
|
|
108
|
+
fromSnapshot: snapshot !== null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Replay a snapshot's layer and channel state and build the context its item log and cwd describe. */
|
|
112
|
+
function contextFromSnapshot(h, snapshot, opts) {
|
|
91
113
|
for (const [layerId, state] of Object.entries(snapshot.layers)) {
|
|
92
|
-
h.layerStateStore.set(executionId, layerId, state);
|
|
114
|
+
h.layerStateStore.set(snapshot.executionId, layerId, state);
|
|
93
115
|
}
|
|
94
|
-
|
|
95
|
-
const cwdInit = snapshot.cwd?.current ?? undefined;
|
|
116
|
+
h.channelStore.restore(snapshot.executionId, snapshot.channels ?? []);
|
|
96
117
|
/* Caller wiring first, snapshot second: a host may legitimately swap the context
|
|
97
118
|
* layers or hang the restored execution under a new parent, but it must never be
|
|
98
119
|
* able to override the identity/history the snapshot is the record of. */
|
|
99
|
-
|
|
120
|
+
return h.createContext({
|
|
100
121
|
...opts,
|
|
101
|
-
items,
|
|
122
|
+
items: h.itemSchemas.parseMany(snapshot.itemLog.items),
|
|
102
123
|
threadId: snapshot.threadId,
|
|
103
124
|
resourceId: snapshot.resourceId,
|
|
104
|
-
cwdInit,
|
|
125
|
+
cwdInit: snapshot.cwd?.current ?? undefined,
|
|
105
126
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
store: h.stepLedgerStore,
|
|
124
|
-
recovered,
|
|
125
|
-
retention: h.stepLedgerRetention,
|
|
126
|
-
}),
|
|
127
|
-
configurable: false,
|
|
128
|
-
writable: false,
|
|
129
|
-
enumerable: true,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Give `ctx` the original execution id and the ledger recorded under it.
|
|
130
|
+
* `createContext` already built a fresh ledger keyed to a throwaway id; without the
|
|
131
|
+
* recovered one `execute()` would replay nothing and re-run the whole tree. Both are
|
|
132
|
+
* assigned with `defineProperty` to keep `createContext`'s public options free of
|
|
133
|
+
* resume-only fields — adapter correlation across a restart needs the stable id.
|
|
134
|
+
*/
|
|
135
|
+
function adoptExecution({ h, ctx, executionId, recovered, }) {
|
|
136
|
+
Object.defineProperty(ctx, 'id', {
|
|
137
|
+
value: executionId,
|
|
138
|
+
configurable: false,
|
|
139
|
+
writable: false,
|
|
140
|
+
enumerable: true,
|
|
141
|
+
});
|
|
142
|
+
if (!h.stepLedgerStore || !recovered) {
|
|
143
|
+
return;
|
|
132
144
|
}
|
|
133
|
-
|
|
145
|
+
Object.defineProperty(ctx, 'ledger', {
|
|
146
|
+
value: new StepLedger({
|
|
147
|
+
executionId,
|
|
148
|
+
store: h.stepLedgerStore,
|
|
149
|
+
recovered,
|
|
150
|
+
retention: h.stepLedgerRetention,
|
|
151
|
+
}),
|
|
152
|
+
configurable: false,
|
|
153
|
+
writable: false,
|
|
154
|
+
enumerable: true,
|
|
155
|
+
});
|
|
134
156
|
}
|
|
135
157
|
//#endregion
|
|
136
158
|
//#region clearCheckpoint
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The step completion ledger: what a resumed execution replays instead of re-running.
|
|
3
3
|
*
|
|
4
4
|
* The frontier records which steps were IN FLIGHT at snapshot time; it says nothing
|
|
5
|
-
* about which ones finished, and a finished step leaves no trace once `leaveStep`
|
|
5
|
+
* about which ones finished, and a finished step leaves no trace once `leaveStep` drops
|
|
6
6
|
* it. Resume therefore needs a separate record — and it must carry each step's OUTPUT,
|
|
7
7
|
* not merely a "done" flag: steps consume the previous step's value, and a `callModel` step
|
|
8
8
|
* re-run to catch up produces a different one than the rest of the run already saw.
|
|
@@ -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
|
|
@@ -19,7 +25,9 @@
|
|
|
19
25
|
*/
|
|
20
26
|
import type { StorageAdapter } from '@noetic-tools/context';
|
|
21
27
|
import { z } from 'zod';
|
|
22
|
-
/**
|
|
28
|
+
/** Marks a ledger key as a step's `replayKey` rather than its execution path. */
|
|
29
|
+
export declare const REPLAY_KEY_PREFIX = "key:";
|
|
30
|
+
/** @public One completed step, keyed by its execution path or its `key:` replay key. */
|
|
23
31
|
export declare const StepLedgerEntrySchema: z.ZodObject<{
|
|
24
32
|
path: z.ZodString;
|
|
25
33
|
stepId: z.ZodString;
|
|
@@ -153,9 +161,11 @@ export declare class StepLedger {
|
|
|
153
161
|
get stats(): StepLedgerStats;
|
|
154
162
|
/**
|
|
155
163
|
* The recorded output for `path`, when a previous run completed the same step there.
|
|
156
|
-
* A divergence (different step id or kind at this
|
|
157
|
-
*
|
|
158
|
-
* subtree's recorded outputs belong to a branch that no longer exists.
|
|
164
|
+
* A divergence (different step id or kind at this key) returns undefined so the step
|
|
165
|
+
* runs fresh. At a positional path it also discards every entry recorded beneath it —
|
|
166
|
+
* the subtree's recorded outputs belong to a branch that no longer exists. A `key:`
|
|
167
|
+
* entry discards only itself: its children are keyed by their own paths or keys, not
|
|
168
|
+
* nested under it.
|
|
159
169
|
*/
|
|
160
170
|
take(path: string, step: {
|
|
161
171
|
id: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The step completion ledger: what a resumed execution replays instead of re-running.
|
|
3
3
|
*
|
|
4
4
|
* The frontier records which steps were IN FLIGHT at snapshot time; it says nothing
|
|
5
|
-
* about which ones finished, and a finished step leaves no trace once `leaveStep`
|
|
5
|
+
* about which ones finished, and a finished step leaves no trace once `leaveStep` drops
|
|
6
6
|
* it. Resume therefore needs a separate record — and it must carry each step's OUTPUT,
|
|
7
7
|
* not merely a "done" flag: steps consume the previous step's value, and a `callModel` step
|
|
8
8
|
* re-run to catch up produces a different one than the rest of the run already saw.
|
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
201
|
-
*
|
|
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
|
}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* consumers can validate workflow documents without importing core.
|
|
4
4
|
* This barrel keeps core's public and intra-core import paths unchanged.
|
|
5
5
|
*/
|
|
6
|
-
export type { AcpAgentWorkflowNode, CallModelWorkflowNode, ConditionalRoute, ConditionalWorkflowNode, DecideWorkflowNode, InParallelWorkflowNode, InvokeToolWorkflowNode, LoopWorkflowNode, OutputCodecRef, RouteSwitchWorkflowNode, RunCodeWorkflowNode, ScheduleWorkflowNode, SequenceWorkflowNode, SpawnWorkflowNode, SubflowWorkflowNode, UntilPredicate, WithContextWorkflowNode, WorkflowDocument, WorkflowGraph, WorkflowNode, } from '@noetic-tools/types';
|
|
6
|
+
export type { AcpAgentWorkflowNode, CallModelWorkflowNode, ConditionalRoute, ConditionalWorkflowNode, DecideWorkflowNode, InParallelWorkflowNode, InvokeToolWorkflowNode, LoopWorkflowNode, OutputCodecRef, RouteSwitchWorkflowNode, RunCodeWorkflowNode, ScheduleWorkflowNode, SequenceWorkflowNode, SpawnWorkflowNode, SubflowWorkflowNode, UntilPredicate, WithContextWorkflowNode, WorkflowDocument, WorkflowGraph, WorkflowNode, WorkflowReplayKey, } from '@noetic-tools/types';
|
|
7
7
|
export { MergeStrategy, MergeStrategySchema, UntilPredicateSchema, validateWorkflow, WorkflowDocumentSchema, WorkflowNodeSchema, walkWorkflow, workflowDepth, workflowGraph, } from '@noetic-tools/types';
|
|
@@ -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()`. */
|
package/dist/types/checkpoint.js
CHANGED
|
@@ -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
|
|
41
|
-
.map((v) => v.reason)
|
|
42
|
-
.filter(Boolean)
|
|
43
|
-
.join('; ');
|
|
48
|
+
const merged = merge(true, verdicts);
|
|
44
49
|
return {
|
|
45
|
-
|
|
46
|
-
reason:
|
|
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
|
+
}
|
package/dist/until/predicates.js
CHANGED
|
@@ -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
|
|
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' :
|
|
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.
|
|
3
|
+
"version": "5.9.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.
|
|
72
|
-
"@noetic-tools/types": "^3.
|
|
71
|
+
"@noetic-tools/context": "^1.6.0",
|
|
72
|
+
"@noetic-tools/types": "^3.7.0",
|
|
73
73
|
"@openrouter/agent": "^0.11.0",
|
|
74
74
|
"@openrouter/sdk": "1.4.25",
|
|
75
75
|
"zod": "^4.0.0"
|