@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,6 +1,7 @@
|
|
|
1
1
|
import { frameworkCast, NoeticErrorImpl } from '@noetic-tools/types';
|
|
2
2
|
import { emitFrameworkEvent, getBroadcaster, shouldEmit } from '../runtime/broadcaster-utils.js';
|
|
3
3
|
import { ContextImpl } from '../runtime/context-impl.js';
|
|
4
|
+
import { REPLAY_KEY_PREFIX } from '../runtime/durable/step-ledger.js';
|
|
4
5
|
import { DetachedHandleImpl } from '../util/detached-handle.js';
|
|
5
6
|
import { executeAcpAgent } from './execute-acp-agent.js';
|
|
6
7
|
import { executeCallModel, executeInvokeTool, executeRunCode, executeSpawn, executeWithContext, } from './execute-action.js';
|
|
@@ -112,21 +113,47 @@ export async function executeNoAdapter(step, input, ctx) {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* the
|
|
116
|
+
* @internal
|
|
117
|
+
* The ledger key this dispatch records and replays under, or undefined when the
|
|
118
|
+
* step bypasses the ledger (spec 23a § Identity). A `durable: false` step and a
|
|
119
|
+
* `schedule` (which never completes) are neither replayed nor recorded; the opt-out
|
|
120
|
+
* covers this step only, so its children still resolve their own keys. A
|
|
121
|
+
* `replayKey` replaces the positional path with `key:<key>`. A throwing `replayKey`
|
|
122
|
+
* costs this dispatch its durability, not its run: the step executes unrecorded.
|
|
123
|
+
*/
|
|
124
|
+
export function resolveLedgerKey(step, input, ctx) {
|
|
125
|
+
const impl = asContextImpl(frameworkCast(ctx));
|
|
126
|
+
if (!impl?.ledger || step.id.length === 0) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (step.durable === false || step.kind === 'schedule') {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
if (!step.replayKey) {
|
|
133
|
+
return impl.currentPath();
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
return `${REPLAY_KEY_PREFIX}${step.replayKey(input, ctx)}`;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.warn(`execute: replayKey for step "${step.id}" threw; running it without replay or recording:`, error instanceof Error ? error.message : String(error));
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resume path: when a previous run recorded this exact step at `ledgerKey`,
|
|
145
|
+
* replay its output instead of re-running, and emit `step_replayed`. Returns
|
|
146
|
+
* `{ hit: false }` when there is nothing to replay so the caller dispatches the
|
|
147
|
+
* step normally.
|
|
121
148
|
*/
|
|
122
149
|
function replayFromLedger(opts) {
|
|
123
|
-
const { ledger,
|
|
124
|
-
if (!ledger ||
|
|
150
|
+
const { ledger, ledgerKey, step, broadcaster, agentName } = opts;
|
|
151
|
+
if (!ledger || ledgerKey === undefined || ledger.isEmpty) {
|
|
125
152
|
return {
|
|
126
153
|
hit: false,
|
|
127
154
|
};
|
|
128
155
|
}
|
|
129
|
-
const replayed = ledger.take(
|
|
156
|
+
const replayed = ledger.take(ledgerKey, {
|
|
130
157
|
id: step.id,
|
|
131
158
|
kind: step.kind,
|
|
132
159
|
});
|
|
@@ -135,9 +162,6 @@ function replayFromLedger(opts) {
|
|
|
135
162
|
hit: false,
|
|
136
163
|
};
|
|
137
164
|
}
|
|
138
|
-
if (impl && step.id.length > 0) {
|
|
139
|
-
impl.leaveStep(step.id);
|
|
140
|
-
}
|
|
141
165
|
emitFrameworkEvent({
|
|
142
166
|
broadcaster,
|
|
143
167
|
agentName,
|
|
@@ -154,16 +178,16 @@ function replayFromLedger(opts) {
|
|
|
154
178
|
}
|
|
155
179
|
/**
|
|
156
180
|
* Record a completed step's output so a resumed run replays it rather than
|
|
157
|
-
* re-executing. A no-op without a ledger or ledger
|
|
181
|
+
* re-executing. A no-op without a ledger or ledger key. Only successes reach
|
|
158
182
|
* here — a step that threw skips this call in `execute` and runs again on resume.
|
|
159
183
|
*/
|
|
160
184
|
async function recordToLedger(opts) {
|
|
161
|
-
const { ledger,
|
|
162
|
-
if (!ledger ||
|
|
185
|
+
const { ledger, ledgerKey, step, output } = opts;
|
|
186
|
+
if (!ledger || ledgerKey === undefined) {
|
|
163
187
|
return;
|
|
164
188
|
}
|
|
165
189
|
await ledger.record({
|
|
166
|
-
path:
|
|
190
|
+
path: ledgerKey,
|
|
167
191
|
stepId: step.id,
|
|
168
192
|
kind: step.kind,
|
|
169
193
|
output,
|
|
@@ -220,56 +244,57 @@ export async function execute(step, input, ctx) {
|
|
|
220
244
|
data: startedData,
|
|
221
245
|
});
|
|
222
246
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
247
|
+
/* Register the step as in flight, so a checkpoint records it in the frontier, and
|
|
248
|
+
* run it on its own lane: a view of the context that carries this dispatch's path.
|
|
249
|
+
* Steps this step dispatches hang under that path, so concurrent dispatches on one
|
|
250
|
+
* context cannot interleave each other's paths. Steps with empty ids stay on the
|
|
251
|
+
* caller's lane — they are not reattachable by id and add no path segment. */
|
|
226
252
|
const impl = asContextImpl(baseCtx);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
const frame = {
|
|
254
|
+
stepId: step.id,
|
|
255
|
+
input,
|
|
256
|
+
};
|
|
257
|
+
const lane = impl && step.id.length > 0 ? impl.enterStep(frame) : null;
|
|
258
|
+
const stepCtx = lane ? frameworkCast(lane) : ctx;
|
|
233
259
|
/* Resume: a previous run that completed this exact step replays its recorded
|
|
234
|
-
* output instead of re-running it. `
|
|
235
|
-
* dispatch and reused by the record below
|
|
236
|
-
|
|
237
|
-
const ledgerPath = impl && step.id.length > 0 ? impl.currentPath() : undefined;
|
|
260
|
+
* output instead of re-running it. `ledgerKey`/`ledger` are captured before
|
|
261
|
+
* dispatch and reused by the record below. */
|
|
262
|
+
const ledgerKey = resolveLedgerKey(step, input, stepCtx);
|
|
238
263
|
const ledger = impl?.ledger;
|
|
239
264
|
const replay = replayFromLedger({
|
|
240
265
|
ledger,
|
|
241
|
-
|
|
242
|
-
impl,
|
|
266
|
+
ledgerKey,
|
|
243
267
|
step,
|
|
244
268
|
broadcaster,
|
|
245
269
|
agentName,
|
|
246
270
|
});
|
|
247
271
|
if (replay.hit) {
|
|
272
|
+
lane?.leaveStep(frame);
|
|
248
273
|
return replay.output;
|
|
249
274
|
}
|
|
250
275
|
let result;
|
|
251
276
|
try {
|
|
252
277
|
switch (step.kind) {
|
|
253
278
|
case 'runCode':
|
|
254
|
-
result = await dispatchViaAdapter(step, input,
|
|
279
|
+
result = await dispatchViaAdapter(step, input, stepCtx, () => executeRunCode(step, input, stepCtx));
|
|
255
280
|
break;
|
|
256
281
|
case 'callModel':
|
|
257
|
-
result = await executeCallModel(step, input,
|
|
282
|
+
result = await executeCallModel(step, input, stepCtx, baseCtx.layers);
|
|
258
283
|
break;
|
|
259
284
|
case 'acp-agent':
|
|
260
|
-
result = await executeAcpAgent(step, input,
|
|
285
|
+
result = await executeAcpAgent(step, input, stepCtx, baseCtx.layers);
|
|
261
286
|
break;
|
|
262
287
|
case 'invokeTool':
|
|
263
|
-
result = await executeInvokeTool(step, input,
|
|
288
|
+
result = await executeInvokeTool(step, input, stepCtx, baseCtx.harness);
|
|
264
289
|
break;
|
|
265
290
|
case 'decide':
|
|
266
|
-
result = await executeDecide(step, input,
|
|
291
|
+
result = await executeDecide(step, input, stepCtx, baseCtx.layers);
|
|
267
292
|
break;
|
|
268
293
|
case 'conditional':
|
|
269
|
-
result = await executeConditional(step, input,
|
|
294
|
+
result = await executeConditional(step, input, stepCtx, (s, i, c) => execute(s, i, c));
|
|
270
295
|
break;
|
|
271
296
|
case 'inParallel':
|
|
272
|
-
result = await executeInParallel(step, input,
|
|
297
|
+
result = await executeInParallel(step, input, stepCtx, (s, i, c) => execute(s, i, c), {
|
|
273
298
|
// Fork paths are child executions: the layer store lets their
|
|
274
299
|
// `onSpawn`/`onReturn` boundary run (see `createForkLayerBridge`).
|
|
275
300
|
layerStore: harnessWithLayerStore(baseCtx).layerStateStore,
|
|
@@ -277,16 +302,16 @@ export async function execute(step, input, ctx) {
|
|
|
277
302
|
});
|
|
278
303
|
break;
|
|
279
304
|
case 'spawn':
|
|
280
|
-
result = await dispatchViaAdapter(step, input,
|
|
305
|
+
result = await dispatchViaAdapter(step, input, stepCtx, () => executeSpawn(step, input, stepCtx, (s, i, c) => execute(s, i, c), buildSpawnOpts(stepCtx)));
|
|
281
306
|
break;
|
|
282
307
|
case 'withContext':
|
|
283
|
-
result = await executeWithContext(step, input,
|
|
308
|
+
result = await executeWithContext(step, input, stepCtx, (s, i, c) => execute(s, i, c));
|
|
284
309
|
break;
|
|
285
310
|
case 'loop':
|
|
286
|
-
result = await executeLoop(step, input,
|
|
311
|
+
result = await executeLoop(step, input, stepCtx, (s, i, c) => execute(s, i, c));
|
|
287
312
|
break;
|
|
288
313
|
case 'schedule':
|
|
289
|
-
result = await executeSchedule(step, input,
|
|
314
|
+
result = await executeSchedule(step, input, stepCtx, (s, i, c) => execute(s, i, c));
|
|
290
315
|
break;
|
|
291
316
|
default: {
|
|
292
317
|
const _exhaustive = step;
|
|
@@ -301,9 +326,7 @@ export async function execute(step, input, ctx) {
|
|
|
301
326
|
}
|
|
302
327
|
}
|
|
303
328
|
finally {
|
|
304
|
-
|
|
305
|
-
impl.leaveStep(step.id);
|
|
306
|
-
}
|
|
329
|
+
lane?.leaveStep(frame);
|
|
307
330
|
}
|
|
308
331
|
// Emit step_completed framework event (respects step.emit option)
|
|
309
332
|
const completedData = {
|
|
@@ -323,7 +346,7 @@ export async function execute(step, input, ctx) {
|
|
|
323
346
|
* again. */
|
|
324
347
|
await recordToLedger({
|
|
325
348
|
ledger,
|
|
326
|
-
|
|
349
|
+
ledgerKey,
|
|
327
350
|
step,
|
|
328
351
|
output: result,
|
|
329
352
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from './action-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Give an error from a model provider call the step's id and a Noetic kind.
|
|
4
|
+
*
|
|
5
|
+
* Noetic errors pass through untouched. A failure on an aborted context is
|
|
6
|
+
* `cancelled` (the abort is what broke the call), a 429 is `model_rate_limit`,
|
|
7
|
+
* and anything else is `step_failed` with the provider error as its cause.
|
|
8
|
+
*/
|
|
9
|
+
export declare function wrapModelError(e: unknown, stepId: string, ctx: Pick<Context, 'aborted' | 'abortReason'>): Error;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isNoeticError, NoeticConfigError, NoeticErrorImpl } from '@noetic-tools/types';
|
|
2
|
+
import { statusOf } from '../util/status-code.js';
|
|
3
|
+
/**
|
|
4
|
+
* Give an error from a model provider call the step's id and a Noetic kind.
|
|
5
|
+
*
|
|
6
|
+
* Noetic errors pass through untouched. A failure on an aborted context is
|
|
7
|
+
* `cancelled` (the abort is what broke the call), a 429 is `model_rate_limit`,
|
|
8
|
+
* and anything else is `step_failed` with the provider error as its cause.
|
|
9
|
+
*/
|
|
10
|
+
export function wrapModelError(e, stepId, ctx) {
|
|
11
|
+
if (isNoeticError(e) || e instanceof NoeticConfigError) {
|
|
12
|
+
return e;
|
|
13
|
+
}
|
|
14
|
+
if (ctx.aborted) {
|
|
15
|
+
return new NoeticErrorImpl({
|
|
16
|
+
kind: 'cancelled',
|
|
17
|
+
reason: ctx.abortReason ?? 'context aborted',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (statusOf(e) === 429) {
|
|
21
|
+
return new NoeticErrorImpl({
|
|
22
|
+
kind: 'model_rate_limit',
|
|
23
|
+
stepId,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return new NoeticErrorImpl({
|
|
27
|
+
kind: 'step_failed',
|
|
28
|
+
stepId,
|
|
29
|
+
cause: e instanceof Error ? e : new Error(String(e)),
|
|
30
|
+
retriesExhausted: false,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Step } from '@noetic-tools/types';
|
|
2
|
+
/**
|
|
3
|
+
* Whether `step` or any step nested in it satisfies `test`.
|
|
4
|
+
*
|
|
5
|
+
* Only statically declared children are visited: a `conditional` or
|
|
6
|
+
* `inParallel` built from functions exposes its children through
|
|
7
|
+
* `_optimizable` only when the builder could enumerate them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function someStep(step: Step, test: (step: Step) => boolean): boolean;
|
|
10
|
+
/** The steps nested directly inside `step`, as far as they are statically known. */
|
|
11
|
+
export declare function stepChildren(step: Step): ReadonlyArray<Step>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `step` or any step nested in it satisfies `test`.
|
|
3
|
+
*
|
|
4
|
+
* Only statically declared children are visited: a `conditional` or
|
|
5
|
+
* `inParallel` built from functions exposes its children through
|
|
6
|
+
* `_optimizable` only when the builder could enumerate them.
|
|
7
|
+
*/
|
|
8
|
+
export function someStep(step, test) {
|
|
9
|
+
if (test(step)) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return stepChildren(step).some((child) => someStep(child, test));
|
|
13
|
+
}
|
|
14
|
+
/** The steps nested directly inside `step`, as far as they are statically known. */
|
|
15
|
+
export function stepChildren(step) {
|
|
16
|
+
switch (step.kind) {
|
|
17
|
+
case 'callModel':
|
|
18
|
+
case 'runCode':
|
|
19
|
+
case 'invokeTool':
|
|
20
|
+
case 'decide':
|
|
21
|
+
case 'acp-agent':
|
|
22
|
+
return [];
|
|
23
|
+
case 'conditional':
|
|
24
|
+
case 'inParallel':
|
|
25
|
+
return step._optimizable ?? [];
|
|
26
|
+
case 'withContext':
|
|
27
|
+
case 'spawn':
|
|
28
|
+
return [
|
|
29
|
+
step.child,
|
|
30
|
+
];
|
|
31
|
+
case 'loop':
|
|
32
|
+
return step.steps;
|
|
33
|
+
case 'schedule':
|
|
34
|
+
return [
|
|
35
|
+
step.step,
|
|
36
|
+
];
|
|
37
|
+
default:
|
|
38
|
+
step;
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1,59 +1,82 @@
|
|
|
1
1
|
export declare const GenAI: {
|
|
2
|
-
readonly SYSTEM:
|
|
3
|
-
readonly REQUEST_MODEL:
|
|
4
|
-
readonly USAGE_INPUT_TOKENS:
|
|
5
|
-
readonly USAGE_OUTPUT_TOKENS:
|
|
2
|
+
readonly SYSTEM: "gen_ai.system";
|
|
3
|
+
readonly REQUEST_MODEL: "gen_ai.request.model";
|
|
4
|
+
readonly USAGE_INPUT_TOKENS: "gen_ai.usage.input_tokens";
|
|
5
|
+
readonly USAGE_OUTPUT_TOKENS: "gen_ai.usage.output_tokens";
|
|
6
6
|
/** Prompt tokens served from the provider's cache. Absent if unreported. */
|
|
7
|
-
readonly USAGE_CACHED_INPUT_TOKENS:
|
|
7
|
+
readonly USAGE_CACHED_INPUT_TOKENS: "gen_ai.usage.cached_input_tokens";
|
|
8
8
|
/** Prompt tokens written into the provider's cache. Absent if unreported. */
|
|
9
|
-
readonly USAGE_CACHE_WRITE_TOKENS:
|
|
10
|
-
readonly COST:
|
|
9
|
+
readonly USAGE_CACHE_WRITE_TOKENS: "gen_ai.usage.cache_write_tokens";
|
|
10
|
+
readonly COST: "gen_ai.cost";
|
|
11
11
|
};
|
|
12
12
|
export declare const ToolAttr: {
|
|
13
|
-
readonly NAME:
|
|
14
|
-
readonly NEEDS_APPROVAL:
|
|
13
|
+
readonly NAME: "tool.name";
|
|
14
|
+
readonly NEEDS_APPROVAL: "tool.needs_approval";
|
|
15
15
|
};
|
|
16
16
|
export declare const NoeticAttr: {
|
|
17
17
|
/** Full JSON-serialised `WorkflowDocument` (the DAG, lossless). */
|
|
18
|
-
readonly WORKFLOW_DOCUMENT:
|
|
18
|
+
readonly WORKFLOW_DOCUMENT: "noetic.workflow.document";
|
|
19
19
|
/** Workflow document schema version. */
|
|
20
|
-
readonly WORKFLOW_VERSION:
|
|
20
|
+
readonly WORKFLOW_VERSION: "noetic.workflow.version";
|
|
21
21
|
/** Count of declared nodes in the workflow tree. */
|
|
22
|
-
readonly WORKFLOW_NODE_COUNT:
|
|
22
|
+
readonly WORKFLOW_NODE_COUNT: "noetic.workflow.node_count";
|
|
23
23
|
/** JSON array of `{ id, kind }` for every declared node (flattened graph). */
|
|
24
|
-
readonly WORKFLOW_NODES:
|
|
24
|
+
readonly WORKFLOW_NODES: "noetic.workflow.nodes";
|
|
25
25
|
/** JSON array of `{ from, to }` parent→child edges between declared nodes. */
|
|
26
|
-
readonly WORKFLOW_EDGES:
|
|
27
|
-
/**
|
|
28
|
-
|
|
26
|
+
readonly WORKFLOW_EDGES: "noetic.workflow.edges";
|
|
27
|
+
/**
|
|
28
|
+
* Id of the step a span belongs to: the declared workflow node of an
|
|
29
|
+
* `llm.call`/`tool.call`, the `decide` step of a `decide.call`, the `spawn`
|
|
30
|
+
* step of a `spawn` span, the path step of an `inParallel.path` span.
|
|
31
|
+
*/
|
|
32
|
+
readonly NODE_ID: "noetic.node.id";
|
|
33
|
+
/** Id of the `inParallel` step an `inParallel.path` span belongs to. */
|
|
34
|
+
readonly PARALLEL_ID: "noetic.parallel.id";
|
|
35
|
+
/** Zero-based position of the path among its `inParallel` siblings. */
|
|
36
|
+
readonly PARALLEL_INDEX: "noetic.parallel.index";
|
|
37
|
+
/** `NoeticError` kind a `spawn`/`inParallel.path` span failed with. */
|
|
38
|
+
readonly ERROR_KIND: "noetic.error.kind";
|
|
29
39
|
/**
|
|
30
40
|
* Conversation/session this run belongs to (the run's `ctx.threadId`). Stamped
|
|
31
41
|
* on the root `workflow.run` span so every turn of a multi-turn session shares
|
|
32
42
|
* one id, letting a consumer group per-run traces back into their session.
|
|
33
43
|
*/
|
|
34
|
-
readonly SESSION_ID:
|
|
44
|
+
readonly SESSION_ID: "noetic.session.id";
|
|
35
45
|
/** Resource the session is scoped to (the run's `ctx.resourceId`), when set. */
|
|
36
|
-
readonly RESOURCE_ID:
|
|
46
|
+
readonly RESOURCE_ID: "noetic.resource.id";
|
|
37
47
|
/** Id of the anchoring epoch the assembled view belongs to. */
|
|
38
|
-
readonly CONTEXT_EPOCH_ID:
|
|
48
|
+
readonly CONTEXT_EPOCH_ID: "noetic.context.epoch.id";
|
|
39
49
|
/** Assemblies served by the current epoch, including this one. */
|
|
40
|
-
readonly CONTEXT_EPOCH_AGE:
|
|
50
|
+
readonly CONTEXT_EPOCH_AGE: "noetic.context.epoch.age";
|
|
41
51
|
/** Why the epoch re-anchored on this assembly, when it did. */
|
|
42
|
-
readonly CONTEXT_REANCHOR_REASON:
|
|
52
|
+
readonly CONTEXT_REANCHOR_REASON: "noetic.context.reanchor_reason";
|
|
43
53
|
/** Tokens in the anchor band (the cache-stable prefix). */
|
|
44
|
-
readonly CONTEXT_ANCHOR_TOKENS:
|
|
54
|
+
readonly CONTEXT_ANCHOR_TOKENS: "noetic.context.anchor_tokens";
|
|
45
55
|
/** Tokens in the live band (rendered after history). */
|
|
46
|
-
readonly CONTEXT_LIVE_TOKENS:
|
|
56
|
+
readonly CONTEXT_LIVE_TOKENS: "noetic.context.live_tokens";
|
|
47
57
|
/** Tokens spent superseding stale anchors. */
|
|
48
|
-
readonly CONTEXT_DELTA_TOKENS:
|
|
58
|
+
readonly CONTEXT_DELTA_TOKENS: "noetic.context.delta_tokens";
|
|
49
59
|
/** JSON array of `{ id, placement, served }` per contributing layer. */
|
|
50
|
-
readonly CONTEXT_LAYER_PLACEMENTS:
|
|
60
|
+
readonly CONTEXT_LAYER_PLACEMENTS: "noetic.context.layer_placements";
|
|
51
61
|
/** JSON array of `{ id, rate, rebillTokens }` per contributing layer. */
|
|
52
|
-
readonly CONTEXT_LAYER_CHURN:
|
|
62
|
+
readonly CONTEXT_LAYER_CHURN: "noetic.context.layer_churn";
|
|
53
63
|
/** `native` or `estimated` — where a decision's probabilities came from. */
|
|
54
|
-
readonly DECISION_SOURCE:
|
|
64
|
+
readonly DECISION_SOURCE: "noetic.decision.source";
|
|
55
65
|
/** Comma-separated ids of questions the model refused. */
|
|
56
|
-
readonly DECISION_REFUSED:
|
|
66
|
+
readonly DECISION_REFUSED: "noetic.decision.refused";
|
|
57
67
|
/** Prefix for per-question attributes: `noetic.decision.<id>.value` etc. */
|
|
58
|
-
readonly DECISION_PREFIX:
|
|
68
|
+
readonly DECISION_PREFIX: "noetic.decision.";
|
|
69
|
+
};
|
|
70
|
+
export declare const NoeticEvent: {
|
|
71
|
+
/** One per loop iteration: `{ loop, iteration, stop, reason? }`. */
|
|
72
|
+
readonly LOOP_VERDICT: "loop.verdict";
|
|
73
|
+
/** A channel dropped data or is waiting with no timeout: `{ kind, channelName, message, capacity?, operation? }`. */
|
|
74
|
+
readonly CHANNEL_WARNING: "channel.warning";
|
|
75
|
+
/**
|
|
76
|
+
* A layer's `onReturn` hook failed, so a spawn or inParallel path returned
|
|
77
|
+
* its result unchanged. Added to the child's span: `{ stepId, layerId,
|
|
78
|
+
* message }`. Defined in `@noetic-tools/context`, whose `returnLayers`
|
|
79
|
+
* emits it and cannot import core.
|
|
80
|
+
*/
|
|
81
|
+
readonly SPAWN_SUMMARY_FAILED: "spawn.summary_failed";
|
|
59
82
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SPAWN_SUMMARY_FAILED_EVENT } from '@noetic-tools/context';
|
|
1
2
|
// GenAI Semantic Convention attributes
|
|
2
3
|
export const GenAI = {
|
|
3
4
|
SYSTEM: 'gen_ai.system',
|
|
@@ -27,8 +28,18 @@ export const NoeticAttr = {
|
|
|
27
28
|
WORKFLOW_NODES: 'noetic.workflow.nodes',
|
|
28
29
|
/** JSON array of `{ from, to }` parent→child edges between declared nodes. */
|
|
29
30
|
WORKFLOW_EDGES: 'noetic.workflow.edges',
|
|
30
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Id of the step a span belongs to: the declared workflow node of an
|
|
33
|
+
* `llm.call`/`tool.call`, the `decide` step of a `decide.call`, the `spawn`
|
|
34
|
+
* step of a `spawn` span, the path step of an `inParallel.path` span.
|
|
35
|
+
*/
|
|
31
36
|
NODE_ID: 'noetic.node.id',
|
|
37
|
+
/** Id of the `inParallel` step an `inParallel.path` span belongs to. */
|
|
38
|
+
PARALLEL_ID: 'noetic.parallel.id',
|
|
39
|
+
/** Zero-based position of the path among its `inParallel` siblings. */
|
|
40
|
+
PARALLEL_INDEX: 'noetic.parallel.index',
|
|
41
|
+
/** `NoeticError` kind a `spawn`/`inParallel.path` span failed with. */
|
|
42
|
+
ERROR_KIND: 'noetic.error.kind',
|
|
32
43
|
/**
|
|
33
44
|
* Conversation/session this run belongs to (the run's `ctx.threadId`). Stamped
|
|
34
45
|
* on the root `workflow.run` span so every turn of a multi-turn session shares
|
|
@@ -60,3 +71,18 @@ export const NoeticAttr = {
|
|
|
60
71
|
/** Prefix for per-question attributes: `noetic.decision.<id>.value` etc. */
|
|
61
72
|
DECISION_PREFIX: 'noetic.decision.',
|
|
62
73
|
};
|
|
74
|
+
// Span event names. Steps that open no span of their own (e.g. `loop`) add
|
|
75
|
+
// these to the enclosing `ctx.span`.
|
|
76
|
+
export const NoeticEvent = {
|
|
77
|
+
/** One per loop iteration: `{ loop, iteration, stop, reason? }`. */
|
|
78
|
+
LOOP_VERDICT: 'loop.verdict',
|
|
79
|
+
/** A channel dropped data or is waiting with no timeout: `{ kind, channelName, message, capacity?, operation? }`. */
|
|
80
|
+
CHANNEL_WARNING: 'channel.warning',
|
|
81
|
+
/**
|
|
82
|
+
* A layer's `onReturn` hook failed, so a spawn or inParallel path returned
|
|
83
|
+
* its result unchanged. Added to the child's span: `{ stepId, layerId,
|
|
84
|
+
* message }`. Defined in `@noetic-tools/context`, whose `returnLayers`
|
|
85
|
+
* emits it and cannot import core.
|
|
86
|
+
*/
|
|
87
|
+
SPAWN_SUMMARY_FAILED: SPAWN_SUMMARY_FAILED_EVENT,
|
|
88
|
+
};
|
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
import type { Channel, ChannelHandle, ExternalChannel } from '@noetic-tools/types';
|
|
2
|
+
import type { ChannelSnapshot } from '../types/checkpoint.js';
|
|
3
|
+
/**
|
|
4
|
+
* A channel condition worth surfacing but not worth failing on. `AgentHarness`
|
|
5
|
+
* records these as `channel.warning` span events (spec 10); a bare store
|
|
6
|
+
* prints them with `console.warn`.
|
|
7
|
+
*/
|
|
8
|
+
export type ChannelWarning = {
|
|
9
|
+
/** An external sender hit a full queue and the oldest item was dropped. */
|
|
10
|
+
kind: 'overflow_drop';
|
|
11
|
+
channelName: string;
|
|
12
|
+
capacity: number;
|
|
13
|
+
message: string;
|
|
14
|
+
} | {
|
|
15
|
+
/** An external subscriber's private buffer filled and its oldest value was dropped. */
|
|
16
|
+
kind: 'subscriber_drop';
|
|
17
|
+
channelName: string;
|
|
18
|
+
capacity: number;
|
|
19
|
+
message: string;
|
|
20
|
+
} | {
|
|
21
|
+
/** A `recv` or parked `send` is waiting with `timeout: 0`, so nothing will time it out. */
|
|
22
|
+
kind: 'unbounded_wait';
|
|
23
|
+
channelName: string;
|
|
24
|
+
operation: 'recv' | 'send';
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
/** Options accepted by the `ChannelStore` constructor. */
|
|
28
|
+
export interface ChannelStoreOpts {
|
|
29
|
+
/** Receives every channel warning. Defaults to `warnToConsole`. */
|
|
30
|
+
onWarning?: (warning: ChannelWarning) => void;
|
|
31
|
+
}
|
|
32
|
+
/** Print a channel warning with the `[noetic]` prefix. */
|
|
33
|
+
export declare function warnToConsole(warning: ChannelWarning): void;
|
|
2
34
|
/** Options accepted by internal (back-pressured) `send`. */
|
|
3
35
|
export interface ChannelSendOpts {
|
|
4
36
|
/**
|
|
@@ -21,7 +53,34 @@ export declare class ChannelStore {
|
|
|
21
53
|
*/
|
|
22
54
|
private handleObserved;
|
|
23
55
|
private subscribersByExecution;
|
|
56
|
+
/**
|
|
57
|
+
* Channel name → ids of the root executions that used it, which decides
|
|
58
|
+
* the channels an execution's checkpoint carries. Ids stay after their
|
|
59
|
+
* execution closes: a host may checkpoint a finished run, and channel
|
|
60
|
+
* state itself lives for the harness lifetime (spec 06, Lifecycle).
|
|
61
|
+
*/
|
|
62
|
+
private users;
|
|
63
|
+
private readonly warn;
|
|
64
|
+
constructor(opts?: ChannelStoreOpts);
|
|
24
65
|
private getOrCreate;
|
|
66
|
+
/**
|
|
67
|
+
* Record that `executionId` (a root execution) uses `channel`, so the
|
|
68
|
+
* channel rides along in its checkpoints.
|
|
69
|
+
*/
|
|
70
|
+
claim<T>(channel: Channel<T>, executionId: string): void;
|
|
71
|
+
private addUser;
|
|
72
|
+
/**
|
|
73
|
+
* Stored state (values, queued items) of every channel `executionId` used.
|
|
74
|
+
* Parked receivers, parked senders, and subscribers are live promises and
|
|
75
|
+
* stay out; topic channels store nothing and are skipped.
|
|
76
|
+
*/
|
|
77
|
+
snapshot(executionId: string): ChannelSnapshot[];
|
|
78
|
+
/**
|
|
79
|
+
* Refill channel state from a checkpoint and mark `executionId` as a user.
|
|
80
|
+
* A channel that already has live state keeps it — live state is never
|
|
81
|
+
* older than a snapshot.
|
|
82
|
+
*/
|
|
83
|
+
restore(executionId: string, snapshots: readonly ChannelSnapshot[]): void;
|
|
25
84
|
/**
|
|
26
85
|
* Subscribe to wake notifications on a channel without consuming any value.
|
|
27
86
|
* Fired once on the next `send()` for any mode, then auto-removed. Returns
|
|
@@ -42,8 +101,12 @@ export declare class ChannelStore {
|
|
|
42
101
|
* - **topic**: deliver to current subscribers, resolve immediately.
|
|
43
102
|
*
|
|
44
103
|
* External callers use `ChannelHandle.send` (sync, drop-oldest) instead.
|
|
104
|
+
* `opts.timeout: 0` parks with no timeout and warns `unbounded_wait`.
|
|
105
|
+
*
|
|
106
|
+
* @throws `NoeticConfigError` with code `INVALID_CHANNEL_TIMEOUT` (as a
|
|
107
|
+
* rejection) when `opts.timeout` is negative.
|
|
45
108
|
*/
|
|
46
|
-
send<T>(channel: Channel<T>, value: T
|
|
109
|
+
send<T>(channel: Channel<T>, value: NoInfer<T>, opts?: ChannelSendOpts): Promise<void>;
|
|
47
110
|
/** Park an internal sender on a full queue until a slot frees (FIFO). */
|
|
48
111
|
private parkSender;
|
|
49
112
|
/**
|
|
@@ -53,6 +116,15 @@ export declare class ChannelStore {
|
|
|
53
116
|
private promotePendingSender;
|
|
54
117
|
/** Fire-and-reset the one-shot wake subscribers for a channel state. */
|
|
55
118
|
private fireWakes;
|
|
119
|
+
/**
|
|
120
|
+
* Receive by mode: value returns the current value, queue dequeues the
|
|
121
|
+
* head, topic waits for the next send. A receiver that has to wait gets
|
|
122
|
+
* `channel_timeout` after `timeout` ms; `0` waits with no timeout in every
|
|
123
|
+
* mode and warns `unbounded_wait`.
|
|
124
|
+
*
|
|
125
|
+
* @throws `NoeticConfigError` with code `INVALID_CHANNEL_TIMEOUT` (as a
|
|
126
|
+
* rejection) when `timeout` is negative.
|
|
127
|
+
*/
|
|
56
128
|
recv<T>(channel: Channel<T>, timeout?: number, signal?: AbortSignal): Promise<T>;
|
|
57
129
|
tryRecv<T>(channel: Channel<T>): T | null;
|
|
58
130
|
private waitWithTimeout;
|