@noetic-tools/context 1.7.0 → 1.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/context/exec-context-factory.js +16 -0
- package/dist/context/hook-call.d.ts +22 -10
- package/dist/context/hook-call.js +99 -43
- package/dist/context/layer-lifecycle.d.ts +12 -0
- package/dist/context/layer-lifecycle.js +93 -35
- package/dist/context/layers/plan-prompts.js +1 -1
- package/dist/context/layers/plan.js +0 -50
- package/dist/context/parent-updates.d.ts +44 -0
- package/dist/context/parent-updates.js +117 -0
- package/dist/context/span-export.d.ts +7 -0
- package/dist/context/span-export.js +13 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { estimateTokens } from '@noetic-tools/types';
|
|
2
|
+
import { exportSpans } from './span-export.js';
|
|
2
3
|
/**
|
|
3
4
|
* Maps a full `Context` to the lightweight `ExecutionContext` consumed by context layer hooks.
|
|
4
5
|
* Centralised here so the mapping stays consistent across agent-harness, layer-api, and the
|
|
@@ -24,7 +25,22 @@ export function contextToExecCtx(ctx, overrides) {
|
|
|
24
25
|
trace: {
|
|
25
26
|
setAttribute: (key, value) => ctx.span.setAttribute(key, value),
|
|
26
27
|
addEvent: (name, attributes) => ctx.span.addEvent(name, attributes),
|
|
28
|
+
createSpan: (name) => openSpan(ctx, name),
|
|
27
29
|
},
|
|
28
30
|
readLayerState: (layerId) => ctx.harness.getLayerState(readLayerStateId, layerId),
|
|
29
31
|
};
|
|
30
32
|
}
|
|
33
|
+
/** Opens a span under `ctx.span` that exports itself to the harness when it ends. */
|
|
34
|
+
function openSpan(ctx, name) {
|
|
35
|
+
const span = ctx.harness.createSpan(name, ctx.span);
|
|
36
|
+
return {
|
|
37
|
+
setAttribute: (key, value) => span.setAttribute(key, value),
|
|
38
|
+
addEvent: (event, attributes) => span.addEvent(event, attributes),
|
|
39
|
+
end: async () => {
|
|
40
|
+
span.end();
|
|
41
|
+
await exportSpans(ctx.harness.traceExporter, [
|
|
42
|
+
span,
|
|
43
|
+
]);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Timed, traced calls into context layer hooks. Each call of a traced hook
|
|
3
|
-
* one `layer.hook`
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
2
|
+
* Timed, traced calls into context layer hooks. Each call of a traced hook is
|
|
3
|
+
* recorded as one `layer.hook` record shaped as a `LayerTraceSpan`: a child
|
|
4
|
+
* span of the context's span when the context can open one
|
|
5
|
+
* (`ctx.trace.createSpan`), otherwise an event on the context's span. Never
|
|
6
|
+
* both, so a backend sees each call once.
|
|
6
7
|
*/
|
|
7
8
|
import type { ExecutionContext, LayerTraceSpan } from '@noetic-tools/types';
|
|
8
9
|
type TracedHook = LayerTraceSpan['hook'];
|
|
@@ -16,16 +17,27 @@ interface HookCall<T> {
|
|
|
16
17
|
/** Reads a recall's budget and item count from the hook's result. */
|
|
17
18
|
measure?: (result: T) => Pick<LayerTraceSpan, 'budget' | 'itemCount'>;
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
+
/** Name of the span (or span event) recording one layer hook call. */
|
|
20
21
|
export declare const LAYER_HOOK_EVENT = "layer.hook";
|
|
22
|
+
/** Attributes of a `layer.hook` span. A span times itself, so it carries no duration. */
|
|
23
|
+
export declare const LayerHookAttr: {
|
|
24
|
+
readonly LAYER_ID: "noetic.layer.id";
|
|
25
|
+
readonly HOOK: "noetic.layer.hook";
|
|
26
|
+
readonly STATUS: "noetic.layer.status";
|
|
27
|
+
readonly ITEM_COUNT: "noetic.layer.item_count";
|
|
28
|
+
readonly BUDGET_ALLOCATED: "noetic.layer.budget.allocated";
|
|
29
|
+
readonly BUDGET_USED: "noetic.layer.budget.used";
|
|
30
|
+
readonly BUDGET_YIELDED: "noetic.layer.budget.yielded";
|
|
31
|
+
};
|
|
21
32
|
/**
|
|
22
|
-
* Runs a layer hook under its timeout and records a `layer.hook`
|
|
23
|
-
* Rethrows whatever the hook threw (or the timeout), so callers
|
|
24
|
-
* failure handling.
|
|
33
|
+
* Runs a layer hook under its timeout and records a `layer.hook` span (or
|
|
34
|
+
* event) for it. Rethrows whatever the hook threw (or the timeout), so callers
|
|
35
|
+
* keep their own failure handling. The span opens before the hook runs and ends
|
|
36
|
+
* once it settles.
|
|
25
37
|
*/
|
|
26
38
|
export declare function callHook<T>({ ctx, layerId, hook, timeout, run, measure, }: HookCall<T>): Promise<T>;
|
|
27
39
|
/** Records a hook the layer defines but the lifecycle did not call. */
|
|
28
|
-
export declare function traceSkipped(ctx: ExecutionContext, layerId: string, hook: TracedHook): void
|
|
29
|
-
/** Runs a hook under a timeout without tracing it, for
|
|
40
|
+
export declare function traceSkipped(ctx: ExecutionContext, layerId: string, hook: TracedHook): Promise<void>;
|
|
41
|
+
/** Runs a hook under a timeout without tracing it, for calls that must leave no trace (a peek recall). */
|
|
30
42
|
export declare function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T>;
|
|
31
43
|
export {};
|
|
@@ -1,54 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Timed, traced calls into context layer hooks. Each call of a traced hook
|
|
3
|
-
* one `layer.hook`
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
2
|
+
* Timed, traced calls into context layer hooks. Each call of a traced hook is
|
|
3
|
+
* recorded as one `layer.hook` record shaped as a `LayerTraceSpan`: a child
|
|
4
|
+
* span of the context's span when the context can open one
|
|
5
|
+
* (`ctx.trace.createSpan`), otherwise an event on the context's span. Never
|
|
6
|
+
* both, so a backend sees each call once.
|
|
6
7
|
*/
|
|
7
8
|
//#endregion
|
|
8
9
|
//#region Public API
|
|
9
|
-
/**
|
|
10
|
+
/** Name of the span (or span event) recording one layer hook call. */
|
|
10
11
|
export const LAYER_HOOK_EVENT = 'layer.hook';
|
|
12
|
+
/** Attributes of a `layer.hook` span. A span times itself, so it carries no duration. */
|
|
13
|
+
export const LayerHookAttr = {
|
|
14
|
+
LAYER_ID: 'noetic.layer.id',
|
|
15
|
+
HOOK: 'noetic.layer.hook',
|
|
16
|
+
STATUS: 'noetic.layer.status',
|
|
17
|
+
ITEM_COUNT: 'noetic.layer.item_count',
|
|
18
|
+
BUDGET_ALLOCATED: 'noetic.layer.budget.allocated',
|
|
19
|
+
BUDGET_USED: 'noetic.layer.budget.used',
|
|
20
|
+
BUDGET_YIELDED: 'noetic.layer.budget.yielded',
|
|
21
|
+
};
|
|
11
22
|
/**
|
|
12
|
-
* Runs a layer hook under its timeout and records a `layer.hook`
|
|
13
|
-
* Rethrows whatever the hook threw (or the timeout), so callers
|
|
14
|
-
* failure handling.
|
|
23
|
+
* Runs a layer hook under its timeout and records a `layer.hook` span (or
|
|
24
|
+
* event) for it. Rethrows whatever the hook threw (or the timeout), so callers
|
|
25
|
+
* keep their own failure handling. The span opens before the hook runs and ends
|
|
26
|
+
* once it settles.
|
|
15
27
|
*/
|
|
16
28
|
export async function callHook({ ctx, layerId, hook, timeout, run, measure, }) {
|
|
29
|
+
const span = ctx.trace.createSpan?.(LAYER_HOOK_EVENT);
|
|
17
30
|
const started = performance.now();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
const outcome = await settle(withTimeout(run(), timeout));
|
|
32
|
+
const duration = performance.now() - started;
|
|
33
|
+
if (outcome.ok) {
|
|
34
|
+
await traceHook(ctx, span, {
|
|
21
35
|
layerId,
|
|
22
36
|
hook,
|
|
23
|
-
duration
|
|
37
|
+
duration,
|
|
24
38
|
status: 'ok',
|
|
25
|
-
...measure?.(
|
|
26
|
-
});
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
traceHook(ctx, {
|
|
31
|
-
layerId,
|
|
32
|
-
hook,
|
|
33
|
-
duration: performance.now() - started,
|
|
34
|
-
status: e instanceof HookTimeout ? 'timeout' : 'error',
|
|
35
|
-
error: {
|
|
36
|
-
message: e instanceof Error ? e.message : String(e),
|
|
37
|
-
},
|
|
39
|
+
...measure?.(outcome.value),
|
|
38
40
|
});
|
|
39
|
-
|
|
41
|
+
return outcome.value;
|
|
40
42
|
}
|
|
43
|
+
await traceHook(ctx, span, {
|
|
44
|
+
layerId,
|
|
45
|
+
hook,
|
|
46
|
+
duration,
|
|
47
|
+
status: outcome.error instanceof HookTimeout ? 'timeout' : 'error',
|
|
48
|
+
error: {
|
|
49
|
+
message: outcome.error instanceof Error ? outcome.error.message : String(outcome.error),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
throw outcome.error;
|
|
41
53
|
}
|
|
42
54
|
/** Records a hook the layer defines but the lifecycle did not call. */
|
|
43
|
-
export function traceSkipped(ctx, layerId, hook) {
|
|
44
|
-
traceHook(ctx, {
|
|
55
|
+
export async function traceSkipped(ctx, layerId, hook) {
|
|
56
|
+
await traceHook(ctx, ctx.trace.createSpan?.(LAYER_HOOK_EVENT), {
|
|
45
57
|
layerId,
|
|
46
58
|
hook,
|
|
47
59
|
duration: 0,
|
|
48
60
|
status: 'skipped',
|
|
49
61
|
});
|
|
50
62
|
}
|
|
51
|
-
/** Runs a hook under a timeout without tracing it, for
|
|
63
|
+
/** Runs a hook under a timeout without tracing it, for calls that must leave no trace (a peek recall). */
|
|
52
64
|
export async function withTimeout(promise, ms) {
|
|
53
65
|
if (ms <= 0) {
|
|
54
66
|
return promise;
|
|
@@ -68,25 +80,69 @@ class HookTimeout extends Error {
|
|
|
68
80
|
super(`Timeout after ${ms}ms`);
|
|
69
81
|
}
|
|
70
82
|
}
|
|
83
|
+
async function settle(promise) {
|
|
84
|
+
try {
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
value: await promise,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
error,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function traceHook(ctx, span, record) {
|
|
98
|
+
if (!span) {
|
|
99
|
+
ctx.trace.addEvent(LAYER_HOOK_EVENT, eventAttributes(record));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
for (const [key, value] of Object.entries(spanAttributes(record))) {
|
|
103
|
+
span.setAttribute(key, value);
|
|
104
|
+
}
|
|
105
|
+
await span.end();
|
|
106
|
+
}
|
|
107
|
+
function spanAttributes(record) {
|
|
108
|
+
const attributes = {
|
|
109
|
+
[LayerHookAttr.LAYER_ID]: record.layerId,
|
|
110
|
+
[LayerHookAttr.HOOK]: record.hook,
|
|
111
|
+
[LayerHookAttr.STATUS]: record.status,
|
|
112
|
+
};
|
|
113
|
+
if (record.itemCount !== undefined) {
|
|
114
|
+
attributes[LayerHookAttr.ITEM_COUNT] = record.itemCount;
|
|
115
|
+
}
|
|
116
|
+
if (record.budget) {
|
|
117
|
+
attributes[LayerHookAttr.BUDGET_ALLOCATED] = record.budget.allocated;
|
|
118
|
+
attributes[LayerHookAttr.BUDGET_USED] = record.budget.used;
|
|
119
|
+
attributes[LayerHookAttr.BUDGET_YIELDED] = record.budget.yielded;
|
|
120
|
+
}
|
|
121
|
+
if (record.error) {
|
|
122
|
+
attributes.error = true;
|
|
123
|
+
attributes['error.message'] = record.error.message;
|
|
124
|
+
}
|
|
125
|
+
return attributes;
|
|
126
|
+
}
|
|
71
127
|
/** Span event attributes hold only scalars, so nested fields flatten to dotted keys. */
|
|
72
|
-
function
|
|
128
|
+
function eventAttributes(record) {
|
|
73
129
|
const attributes = {
|
|
74
|
-
layerId:
|
|
75
|
-
hook:
|
|
76
|
-
duration:
|
|
77
|
-
status:
|
|
130
|
+
layerId: record.layerId,
|
|
131
|
+
hook: record.hook,
|
|
132
|
+
duration: record.duration,
|
|
133
|
+
status: record.status,
|
|
78
134
|
};
|
|
79
|
-
if (
|
|
80
|
-
attributes.itemCount =
|
|
135
|
+
if (record.itemCount !== undefined) {
|
|
136
|
+
attributes.itemCount = record.itemCount;
|
|
81
137
|
}
|
|
82
|
-
if (
|
|
83
|
-
attributes['budget.allocated'] =
|
|
84
|
-
attributes['budget.used'] =
|
|
85
|
-
attributes['budget.yielded'] =
|
|
138
|
+
if (record.budget) {
|
|
139
|
+
attributes['budget.allocated'] = record.budget.allocated;
|
|
140
|
+
attributes['budget.used'] = record.budget.used;
|
|
141
|
+
attributes['budget.yielded'] = record.budget.yielded;
|
|
86
142
|
}
|
|
87
|
-
if (
|
|
88
|
-
attributes['error.message'] =
|
|
143
|
+
if (record.error) {
|
|
144
|
+
attributes['error.message'] = record.error.message;
|
|
89
145
|
}
|
|
90
|
-
|
|
146
|
+
return attributes;
|
|
91
147
|
}
|
|
92
148
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContextLayer, DecisionRecord, ExecutionContext, Item, ItemLog, ItemSchemaRegistry, LLMResponse, ScopedStorage, SteeringDecision, StorageAdapter } from '@noetic-tools/types';
|
|
2
|
+
import type { LiveChild } from './parent-updates.js';
|
|
2
3
|
export interface LayerStateStore {
|
|
3
4
|
get<T>(executionId: string, layerId: string): T | undefined;
|
|
4
5
|
set<T>(executionId: string, layerId: string, state: T): void;
|
|
@@ -44,6 +45,17 @@ export interface LayerStateStore {
|
|
|
44
45
|
disable?(executionId: string, layerId: string): void;
|
|
45
46
|
/** Whether a layer was disabled for an execution. */
|
|
46
47
|
isDisabled?(executionId: string, layerId: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Register a running child. From now on every `set()` of the parent's state
|
|
50
|
+
* for a layer is delivered to that layer's `onParentUpdate` in the child, in
|
|
51
|
+
* the background. `cleanup(childExecutionId)` forgets the child. Optional so
|
|
52
|
+
* custom stores keep compiling; without it, children get no parent updates.
|
|
53
|
+
*/
|
|
54
|
+
adopt?(child: LiveChild): void;
|
|
55
|
+
/** Stop parent updates to a child and wait for deliveries already running. */
|
|
56
|
+
release?(childExecutionId: string): Promise<void>;
|
|
57
|
+
/** Number of registered children, for leak checks. */
|
|
58
|
+
liveChildren?(): number;
|
|
47
59
|
}
|
|
48
60
|
interface InitLayersParams {
|
|
49
61
|
layers: ContextLayer[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createMessage, defaultItemSchemaRegistry, estimateTokens, frameworkCast, NoeticConfigError, NoeticErrorImpl, SteeringAction, } from '@noetic-tools/types';
|
|
2
2
|
import { callHook, traceSkipped, withTimeout } from './hook-call.js';
|
|
3
|
+
import { createLiveChildren } from './parent-updates.js';
|
|
3
4
|
import { createScopedStorage, resolveScopeKey } from './scope.js';
|
|
4
5
|
/** Maximum allowed re-render depth to prevent infinite loops */
|
|
5
6
|
const MAX_RERENDER_DEPTH = 3;
|
|
@@ -29,7 +30,7 @@ export function createLayerStateStore(diagnostic) {
|
|
|
29
30
|
}
|
|
30
31
|
entry.inFlight = null;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
const store = {
|
|
33
34
|
get(executionId, layerId) {
|
|
34
35
|
return frameworkCast(states.get(executionId)?.get(layerId));
|
|
35
36
|
},
|
|
@@ -40,6 +41,7 @@ export function createLayerStateStore(diagnostic) {
|
|
|
40
41
|
states.set(executionId, execMap);
|
|
41
42
|
}
|
|
42
43
|
execMap.set(layerId, state);
|
|
44
|
+
children.notify(executionId, layerId, state);
|
|
43
45
|
const entry = durables.get(executionId)?.get(layerId);
|
|
44
46
|
if (!entry) {
|
|
45
47
|
return;
|
|
@@ -88,6 +90,7 @@ export function createLayerStateStore(diagnostic) {
|
|
|
88
90
|
return disabled.get(executionId)?.has(layerId) ?? false;
|
|
89
91
|
},
|
|
90
92
|
cleanup(executionId) {
|
|
93
|
+
children.drop(executionId);
|
|
91
94
|
states.delete(executionId);
|
|
92
95
|
durables.delete(executionId);
|
|
93
96
|
disabled.delete(executionId);
|
|
@@ -97,8 +100,24 @@ export function createLayerStateStore(diagnostic) {
|
|
|
97
100
|
durables.get(executionId)?.delete(layerId);
|
|
98
101
|
disabled.get(executionId)?.delete(layerId);
|
|
99
102
|
},
|
|
103
|
+
adopt(child) {
|
|
104
|
+
children.adopt(child);
|
|
105
|
+
},
|
|
106
|
+
release(childExecutionId) {
|
|
107
|
+
return children.release(childExecutionId);
|
|
108
|
+
},
|
|
109
|
+
liveChildren() {
|
|
110
|
+
return children.size;
|
|
111
|
+
},
|
|
100
112
|
diagnostic: report,
|
|
101
113
|
};
|
|
114
|
+
const children = createLiveChildren(store, (executionId, layer) => isLayerDisabled({
|
|
115
|
+
store,
|
|
116
|
+
executionId,
|
|
117
|
+
layer,
|
|
118
|
+
state: store.get(executionId, layer.id),
|
|
119
|
+
}));
|
|
120
|
+
return store;
|
|
102
121
|
}
|
|
103
122
|
/**
|
|
104
123
|
* Whether a layer must be skipped for this execution — either explicitly
|
|
@@ -198,7 +217,7 @@ export async function initLayers({ layers, ctx, storage, store, keep = false, })
|
|
|
198
217
|
}
|
|
199
218
|
const init = layer.hooks.init;
|
|
200
219
|
if (keep && store.has?.(ctx.executionId, layer.id)) {
|
|
201
|
-
traceSkipped(ctx, layer.id, 'init');
|
|
220
|
+
await traceSkipped(ctx, layer.id, 'init');
|
|
202
221
|
continue;
|
|
203
222
|
}
|
|
204
223
|
const scopeKey = resolveScopeKey(layer.scope, ctx);
|
|
@@ -274,7 +293,7 @@ export async function recallLayers({ layers, query, ctx, log, budgets, store, it
|
|
|
274
293
|
state: stored,
|
|
275
294
|
})) {
|
|
276
295
|
if (!peek) {
|
|
277
|
-
traceSkipped(ctx, layer.id, 'recall');
|
|
296
|
+
await traceSkipped(ctx, layer.id, 'recall');
|
|
278
297
|
}
|
|
279
298
|
continue; // was disabled
|
|
280
299
|
}
|
|
@@ -449,7 +468,7 @@ export async function storeLayers({ layers, response, ctx, log, store, recallCac
|
|
|
449
468
|
layer,
|
|
450
469
|
state,
|
|
451
470
|
})) {
|
|
452
|
-
traceSkipped(ctx, layer.id, 'store');
|
|
471
|
+
await traceSkipped(ctx, layer.id, 'store');
|
|
453
472
|
continue;
|
|
454
473
|
}
|
|
455
474
|
snapshots.push({
|
|
@@ -512,14 +531,22 @@ export async function notifyDecision({ layers, record, ctx, store, recallCache,
|
|
|
512
531
|
layer,
|
|
513
532
|
state,
|
|
514
533
|
})) {
|
|
534
|
+
await traceSkipped(ctx, layer.id, 'onDecision');
|
|
515
535
|
continue;
|
|
516
536
|
}
|
|
537
|
+
const onDecision = layer.hooks.onDecision;
|
|
517
538
|
try {
|
|
518
|
-
const result = await
|
|
519
|
-
state,
|
|
520
|
-
record,
|
|
539
|
+
const result = await callHook({
|
|
521
540
|
ctx,
|
|
522
|
-
|
|
541
|
+
layerId: layer.id,
|
|
542
|
+
hook: 'onDecision',
|
|
543
|
+
timeout: layer.timeouts?.onDecision ?? 5e3,
|
|
544
|
+
run: () => onDecision({
|
|
545
|
+
state,
|
|
546
|
+
record,
|
|
547
|
+
ctx,
|
|
548
|
+
}),
|
|
549
|
+
});
|
|
523
550
|
if (result && 'state' in result) {
|
|
524
551
|
store.set(ctx.executionId, layer.id, result.state);
|
|
525
552
|
if (recallCache && layer.recallMode === 'eventual') {
|
|
@@ -551,7 +578,7 @@ export async function disposeLayers({ layers, ctx, store, scoped = false, }) {
|
|
|
551
578
|
layer,
|
|
552
579
|
state,
|
|
553
580
|
})) {
|
|
554
|
-
traceSkipped(ctx, layer.id, 'dispose');
|
|
581
|
+
await traceSkipped(ctx, layer.id, 'dispose');
|
|
555
582
|
continue;
|
|
556
583
|
}
|
|
557
584
|
const dispose = layer.hooks.dispose;
|
|
@@ -607,7 +634,7 @@ export async function spawnLayers({ layers, parentCtx, childCtx, store, itemSche
|
|
|
607
634
|
layer,
|
|
608
635
|
state: parentState,
|
|
609
636
|
})) {
|
|
610
|
-
traceSkipped(parentCtx, layer.id, 'onSpawn');
|
|
637
|
+
await traceSkipped(parentCtx, layer.id, 'onSpawn');
|
|
611
638
|
continue;
|
|
612
639
|
}
|
|
613
640
|
const onSpawn = layer.hooks.onSpawn;
|
|
@@ -653,6 +680,9 @@ export async function spawnLayers({ layers, parentCtx, childCtx, store, itemSche
|
|
|
653
680
|
* `spawn_summary_failed` diagnostic plus a span event on the child's span.
|
|
654
681
|
*/
|
|
655
682
|
export async function returnLayers({ layers, stepId, parentCtx, childCtx, childLog, result, store, }) {
|
|
683
|
+
// The child has finished: no more parent updates, and the ones in flight
|
|
684
|
+
// land before its state is merged.
|
|
685
|
+
await store.release?.(childCtx.executionId);
|
|
656
686
|
const sorted = [
|
|
657
687
|
...layers,
|
|
658
688
|
].sort((a, b) => a.slot - b.slot);
|
|
@@ -667,7 +697,7 @@ export async function returnLayers({ layers, stepId, parentCtx, childCtx, childL
|
|
|
667
697
|
// initialized state can still be seeded from the child. Skip only when the
|
|
668
698
|
// child produced nothing.
|
|
669
699
|
if (childState === undefined) {
|
|
670
|
-
traceSkipped(childCtx, layer.id, 'onReturn');
|
|
700
|
+
await traceSkipped(childCtx, layer.id, 'onReturn');
|
|
671
701
|
continue;
|
|
672
702
|
}
|
|
673
703
|
const onReturn = layer.hooks.onReturn;
|
|
@@ -723,7 +753,7 @@ export async function completeLayers({ layers, ctx, log, outcome, store, }) {
|
|
|
723
753
|
layer,
|
|
724
754
|
state,
|
|
725
755
|
})) {
|
|
726
|
-
traceSkipped(ctx, layer.id, 'onComplete');
|
|
756
|
+
await traceSkipped(ctx, layer.id, 'onComplete');
|
|
727
757
|
continue;
|
|
728
758
|
}
|
|
729
759
|
const onComplete = layer.hooks.onComplete;
|
|
@@ -766,17 +796,24 @@ export async function beforeToolCallLayers({ layers, toolName, toolArgs, callId,
|
|
|
766
796
|
layer,
|
|
767
797
|
state,
|
|
768
798
|
})) {
|
|
799
|
+
await traceSkipped(ctx, layer.id, 'beforeToolCall');
|
|
769
800
|
continue;
|
|
770
801
|
}
|
|
802
|
+
const beforeToolCall = layer.hooks.beforeToolCall;
|
|
771
803
|
try {
|
|
772
|
-
const
|
|
773
|
-
const result = await withTimeout(layer.hooks.beforeToolCall({
|
|
774
|
-
toolName,
|
|
775
|
-
toolArgs,
|
|
776
|
-
callId,
|
|
804
|
+
const result = await callHook({
|
|
777
805
|
ctx,
|
|
778
|
-
|
|
779
|
-
|
|
806
|
+
layerId: layer.id,
|
|
807
|
+
hook: 'beforeToolCall',
|
|
808
|
+
timeout: layer.timeouts?.beforeToolCall ?? 5e3,
|
|
809
|
+
run: () => beforeToolCall({
|
|
810
|
+
toolName,
|
|
811
|
+
toolArgs,
|
|
812
|
+
callId,
|
|
813
|
+
ctx,
|
|
814
|
+
state,
|
|
815
|
+
}),
|
|
816
|
+
});
|
|
780
817
|
if (result.state !== undefined) {
|
|
781
818
|
store.set(ctx.executionId, layer.id, result.state);
|
|
782
819
|
}
|
|
@@ -819,15 +856,22 @@ export async function afterModelCallLayers({ layers, response, ctx, store, }) {
|
|
|
819
856
|
layer,
|
|
820
857
|
state,
|
|
821
858
|
})) {
|
|
859
|
+
await traceSkipped(ctx, layer.id, 'afterModelCall');
|
|
822
860
|
continue;
|
|
823
861
|
}
|
|
862
|
+
const afterModelCall = layer.hooks.afterModelCall;
|
|
824
863
|
try {
|
|
825
|
-
const
|
|
826
|
-
const result = await withTimeout(layer.hooks.afterModelCall({
|
|
827
|
-
response,
|
|
864
|
+
const result = await callHook({
|
|
828
865
|
ctx,
|
|
829
|
-
|
|
830
|
-
|
|
866
|
+
layerId: layer.id,
|
|
867
|
+
hook: 'afterModelCall',
|
|
868
|
+
timeout: layer.timeouts?.afterModelCall ?? 1e4,
|
|
869
|
+
run: () => afterModelCall({
|
|
870
|
+
response,
|
|
871
|
+
ctx,
|
|
872
|
+
state,
|
|
873
|
+
}),
|
|
874
|
+
});
|
|
831
875
|
if (result.state !== undefined) {
|
|
832
876
|
store.set(ctx.executionId, layer.id, result.state);
|
|
833
877
|
}
|
|
@@ -867,15 +911,22 @@ export async function projectHistoryLayers({ layers, items, ctx, store, }) {
|
|
|
867
911
|
layer,
|
|
868
912
|
state,
|
|
869
913
|
})) {
|
|
914
|
+
await traceSkipped(ctx, layer.id, 'projectHistory');
|
|
870
915
|
continue;
|
|
871
916
|
}
|
|
917
|
+
const projectHistory = layer.hooks.projectHistory;
|
|
872
918
|
try {
|
|
873
|
-
const
|
|
874
|
-
const result = await withTimeout(layer.hooks.projectHistory({
|
|
875
|
-
items: current,
|
|
919
|
+
const result = await callHook({
|
|
876
920
|
ctx,
|
|
877
|
-
|
|
878
|
-
|
|
921
|
+
layerId: layer.id,
|
|
922
|
+
hook: 'projectHistory',
|
|
923
|
+
timeout: layer.timeouts?.projectHistory ?? 5e3,
|
|
924
|
+
run: () => projectHistory({
|
|
925
|
+
items: current,
|
|
926
|
+
ctx,
|
|
927
|
+
state,
|
|
928
|
+
}),
|
|
929
|
+
});
|
|
879
930
|
current = result.items;
|
|
880
931
|
}
|
|
881
932
|
catch (e) {
|
|
@@ -913,16 +964,23 @@ export async function runAppendPipeline({ layers, items, ctx, log, store, }) {
|
|
|
913
964
|
layer,
|
|
914
965
|
state,
|
|
915
966
|
})) {
|
|
967
|
+
await traceSkipped(ctx, layer.id, 'onItemAppend');
|
|
916
968
|
continue;
|
|
917
969
|
}
|
|
970
|
+
const onItemAppend = layer.hooks.onItemAppend;
|
|
918
971
|
try {
|
|
919
|
-
const
|
|
920
|
-
const result = await withTimeout(layer.hooks.onItemAppend({
|
|
921
|
-
items: currentItems,
|
|
922
|
-
log,
|
|
972
|
+
const result = await callHook({
|
|
923
973
|
ctx,
|
|
924
|
-
|
|
925
|
-
|
|
974
|
+
layerId: layer.id,
|
|
975
|
+
hook: 'onItemAppend',
|
|
976
|
+
timeout: layer.timeouts?.onItemAppend ?? DEFAULT_ON_ITEM_APPEND_TIMEOUT,
|
|
977
|
+
run: () => onItemAppend({
|
|
978
|
+
items: currentItems,
|
|
979
|
+
log,
|
|
980
|
+
ctx,
|
|
981
|
+
state,
|
|
982
|
+
}),
|
|
983
|
+
});
|
|
926
984
|
// Update items for next layer in pipeline
|
|
927
985
|
currentItems = result.items;
|
|
928
986
|
// Update state if provided
|
|
@@ -290,7 +290,7 @@ ${structure.join('\n')}
|
|
|
290
290
|
|
|
291
291
|
**Shaping it**
|
|
292
292
|
- Keep the tree the user reviews SMALL — about seven top-level nodes, readable at a glance. Push the mechanics down.
|
|
293
|
-
- Factor detail into named workflows: define each with \`plan/setWorkflow\` \`{ "name": "<slug>", "document": {...} }\` and point at it with \`{ "kind": "subflow", "id": "...", "ref": "<slug>" }\`. Named workflows may reference each other
|
|
293
|
+
- Factor detail into named workflows: define each with \`plan/setWorkflow\` \`{ "name": "<slug>", "document": {...} }\` and point at it with \`{ "kind": "subflow", "id": "...", "ref": "<slug>" }\`. Named workflows may reference each other, and may recurse (a workflow may call itself) as long as a branch eventually stops the recursion; every ref must resolve or the exit is rejected.
|
|
294
294
|
- Fan out work that is genuinely independent; sequence work where one step needs the last one's output. Do not fan out for the look of it.
|
|
295
295
|
- Prefer \`invokeTool\` over \`callModel\` wherever the step is deterministic. A model turn that always does the same thing is a slow, expensive tool call.
|
|
296
296
|
- Always give a \`loop\` a \`maxIterations\`. Without one it stops at a hard ceiling of 1000 and fails the step.`;
|
|
@@ -161,49 +161,6 @@ function findDanglingRefs(state) {
|
|
|
161
161
|
}
|
|
162
162
|
return dangling;
|
|
163
163
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Detects a reference cycle among named workflows (self-reference included).
|
|
166
|
-
* Returns the cycle path for the rejection message, or null when acyclic.
|
|
167
|
-
*/
|
|
168
|
-
function findWorkflowCycle(workflows) {
|
|
169
|
-
const edges = new Map();
|
|
170
|
-
for (const [name, doc] of Object.entries(workflows)) {
|
|
171
|
-
edges.set(name, collectSubflowRefs(doc).filter((ref) => ref in workflows));
|
|
172
|
-
}
|
|
173
|
-
const visiting = new Set();
|
|
174
|
-
const done = new Set();
|
|
175
|
-
const path = [];
|
|
176
|
-
const visit = (name) => {
|
|
177
|
-
if (done.has(name)) {
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
if (visiting.has(name)) {
|
|
181
|
-
return [
|
|
182
|
-
...path.slice(path.indexOf(name)),
|
|
183
|
-
name,
|
|
184
|
-
];
|
|
185
|
-
}
|
|
186
|
-
visiting.add(name);
|
|
187
|
-
path.push(name);
|
|
188
|
-
for (const next of edges.get(name) ?? []) {
|
|
189
|
-
const cycle = visit(next);
|
|
190
|
-
if (cycle) {
|
|
191
|
-
return cycle;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
path.pop();
|
|
195
|
-
visiting.delete(name);
|
|
196
|
-
done.add(name);
|
|
197
|
-
return null;
|
|
198
|
-
};
|
|
199
|
-
for (const name of edges.keys()) {
|
|
200
|
-
const cycle = visit(name);
|
|
201
|
-
if (cycle) {
|
|
202
|
-
return cycle;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
164
|
function buildAllowedTools(config) {
|
|
208
165
|
if (!config?.additionalAllowedTools?.length) {
|
|
209
166
|
return ALLOWED_TOOLS_IN_PLAN_MODE;
|
|
@@ -462,13 +419,6 @@ async function executeExitPlanMode(args, state, options) {
|
|
|
462
419
|
state,
|
|
463
420
|
};
|
|
464
421
|
}
|
|
465
|
-
const cycle = findWorkflowCycle(state.workflows);
|
|
466
|
-
if (cycle) {
|
|
467
|
-
return {
|
|
468
|
-
result: `Cannot execute: workflows reference each other in a cycle: ${cycle.join(' -> ')}. Break the cycle before exiting.`,
|
|
469
|
-
state,
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
422
|
if (options.onExit) {
|
|
473
423
|
const { approved } = await options.onExit(state);
|
|
474
424
|
if (!approved) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live children and `onParentUpdate` delivery. A child execution is live from
|
|
3
|
+
* the moment its spawn (or inParallel path) starts running until it returns or
|
|
4
|
+
* is cleaned up. While it is live, every write of a parent layer's state is
|
|
5
|
+
* handed to the same layer's `onParentUpdate` in the child.
|
|
6
|
+
*/
|
|
7
|
+
import type { ContextLayer, ExecutionContext, ItemLog, ItemSchemaRegistry } from '@noetic-tools/types';
|
|
8
|
+
/** @public A running child execution that receives its parent's layer writes. */
|
|
9
|
+
export interface LiveChild {
|
|
10
|
+
/** Execution whose layer writes the child receives. */
|
|
11
|
+
parentExecutionId: string;
|
|
12
|
+
/** The child's execution context; hooks run on it and trace onto its span. */
|
|
13
|
+
ctx: ExecutionContext;
|
|
14
|
+
/** The layers active in the child. */
|
|
15
|
+
layers: ContextLayer[];
|
|
16
|
+
/** The child's item log, where returned items are appended. */
|
|
17
|
+
log: ItemLog;
|
|
18
|
+
itemSchemas?: ItemSchemaRegistry;
|
|
19
|
+
}
|
|
20
|
+
/** The slice of the layer state store that delivery reads and writes through. */
|
|
21
|
+
export interface ParentUpdateStore {
|
|
22
|
+
get<T>(executionId: string, layerId: string): T | undefined;
|
|
23
|
+
set<T>(executionId: string, layerId: string, state: T): void;
|
|
24
|
+
diagnostic: (layerId: string, hook: string, error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
/** Whether a layer's hooks are switched off for an execution. */
|
|
27
|
+
type SkipCheck = (executionId: string, layer: ContextLayer) => boolean;
|
|
28
|
+
export interface LiveChildren {
|
|
29
|
+
adopt(child: LiveChild): void;
|
|
30
|
+
/** Hands a parent write to every live child of `parentExecutionId`. */
|
|
31
|
+
notify(parentExecutionId: string, layerId: string, state: unknown): void;
|
|
32
|
+
/** Stops new deliveries to a child and waits for the ones already running. */
|
|
33
|
+
release(childExecutionId: string): Promise<void>;
|
|
34
|
+
/** Forgets a child at once; a delivery still running discards its result. */
|
|
35
|
+
drop(childExecutionId: string): void;
|
|
36
|
+
/** Number of live children, for leak checks. */
|
|
37
|
+
readonly size: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Builds the live-child registry a layer state store dispatches through.
|
|
41
|
+
* `store` is read lazily, so it may be the object under construction.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createLiveChildren(store: ParentUpdateStore, skips: SkipCheck): LiveChildren;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live children and `onParentUpdate` delivery. A child execution is live from
|
|
3
|
+
* the moment its spawn (or inParallel path) starts running until it returns or
|
|
4
|
+
* is cleaned up. While it is live, every write of a parent layer's state is
|
|
5
|
+
* handed to the same layer's `onParentUpdate` in the child.
|
|
6
|
+
*/
|
|
7
|
+
import { defaultItemSchemaRegistry } from '@noetic-tools/types';
|
|
8
|
+
import { callHook } from './hook-call.js';
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region Public API
|
|
11
|
+
const DEFAULT_TIMEOUT = 5e3;
|
|
12
|
+
/**
|
|
13
|
+
* Builds the live-child registry a layer state store dispatches through.
|
|
14
|
+
* `store` is read lazily, so it may be the object under construction.
|
|
15
|
+
*/
|
|
16
|
+
export function createLiveChildren(store, skips) {
|
|
17
|
+
const byChild = new Map();
|
|
18
|
+
const byParent = new Map();
|
|
19
|
+
function forget(childExecutionId) {
|
|
20
|
+
const entry = byChild.get(childExecutionId);
|
|
21
|
+
if (!entry) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
byChild.delete(childExecutionId);
|
|
25
|
+
const siblings = byParent.get(entry.child.parentExecutionId);
|
|
26
|
+
siblings?.delete(entry);
|
|
27
|
+
if (siblings?.size === 0) {
|
|
28
|
+
byParent.delete(entry.child.parentExecutionId);
|
|
29
|
+
}
|
|
30
|
+
entry.open = false;
|
|
31
|
+
return entry;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
adopt(child) {
|
|
35
|
+
const entry = {
|
|
36
|
+
child,
|
|
37
|
+
queue: Promise.resolve(),
|
|
38
|
+
open: true,
|
|
39
|
+
gone: false,
|
|
40
|
+
};
|
|
41
|
+
byChild.set(child.ctx.executionId, entry);
|
|
42
|
+
let siblings = byParent.get(child.parentExecutionId);
|
|
43
|
+
if (!siblings) {
|
|
44
|
+
siblings = new Set();
|
|
45
|
+
byParent.set(child.parentExecutionId, siblings);
|
|
46
|
+
}
|
|
47
|
+
siblings.add(entry);
|
|
48
|
+
},
|
|
49
|
+
notify(parentExecutionId, layerId, state) {
|
|
50
|
+
const siblings = byParent.get(parentExecutionId);
|
|
51
|
+
if (!siblings) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of siblings) {
|
|
55
|
+
const layer = entry.child.layers.find((l) => l.id === layerId);
|
|
56
|
+
if (!layer?.hooks.onParentUpdate) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
entry.queue = entry.queue.then(() => deliver({
|
|
60
|
+
entry,
|
|
61
|
+
layer,
|
|
62
|
+
parentState: state,
|
|
63
|
+
store,
|
|
64
|
+
skips,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async release(childExecutionId) {
|
|
69
|
+
const entry = forget(childExecutionId);
|
|
70
|
+
await entry?.queue;
|
|
71
|
+
},
|
|
72
|
+
drop(childExecutionId) {
|
|
73
|
+
const entry = forget(childExecutionId);
|
|
74
|
+
if (entry) {
|
|
75
|
+
entry.gone = true;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
get size() {
|
|
79
|
+
return byChild.size;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Runs one `onParentUpdate` call. Never rejects: failures become diagnostics. */
|
|
84
|
+
async function deliver({ entry, layer, parentState, store, skips }) {
|
|
85
|
+
const { ctx, log, itemSchemas = defaultItemSchemaRegistry } = entry.child;
|
|
86
|
+
const onParentUpdate = layer.hooks.onParentUpdate;
|
|
87
|
+
if (!entry.open || !onParentUpdate || skips(ctx.executionId, layer)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const result = await callHook({
|
|
92
|
+
ctx,
|
|
93
|
+
layerId: layer.id,
|
|
94
|
+
hook: 'onParentUpdate',
|
|
95
|
+
timeout: layer.timeouts?.onParentUpdate ?? DEFAULT_TIMEOUT,
|
|
96
|
+
run: () => onParentUpdate({
|
|
97
|
+
parentState,
|
|
98
|
+
childState: store.get(ctx.executionId, layer.id),
|
|
99
|
+
childCtx: ctx,
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
// A child cleaned up mid-call has no state left to write into.
|
|
103
|
+
if (!result || entry.gone) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if ('childState' in result) {
|
|
107
|
+
store.set(ctx.executionId, layer.id, result.childState);
|
|
108
|
+
}
|
|
109
|
+
for (const item of itemSchemas.extend(layer.itemSchemas).parseMany(result.items ?? [])) {
|
|
110
|
+
log.append(item);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
store.diagnostic(layer.id, 'onParentUpdate', e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Span, TraceExporter } from '@noetic-tools/types';
|
|
2
|
+
/**
|
|
3
|
+
* Hands ended spans to `exporter` without letting it fail the traced work: a
|
|
4
|
+
* rejecting (or throwing) exporter is reported once as a warning and
|
|
5
|
+
* swallowed, so tracing never changes what a hook, step, or call returns.
|
|
6
|
+
*/
|
|
7
|
+
export declare function exportSpans(exporter: TraceExporter, spans: Span[]): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands ended spans to `exporter` without letting it fail the traced work: a
|
|
3
|
+
* rejecting (or throwing) exporter is reported once as a warning and
|
|
4
|
+
* swallowed, so tracing never changes what a hook, step, or call returns.
|
|
5
|
+
*/
|
|
6
|
+
export async function exportSpans(exporter, spans) {
|
|
7
|
+
try {
|
|
8
|
+
await exporter.export(spans);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
console.warn('[noetic] trace export failed; the traced work is unaffected:', error);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* ContextLayer contract, lifecycle/budget/projection machinery, and built-in
|
|
4
4
|
* layer implementations.
|
|
5
5
|
*/
|
|
6
|
-
export type { ContextScope, ExecutionContext, ExecutionOutcome, LayerCallModelRequest, } from '@noetic-tools/types';
|
|
6
|
+
export type { ContextScope, ExecutionContext, ExecutionOutcome, ExecutionSpan, LayerCallModelRequest, } from '@noetic-tools/types';
|
|
7
7
|
export * from '@noetic-tools/types/contract';
|
|
8
8
|
export * from './context/budget.js';
|
|
9
9
|
export * from './context/cache-anchoring.js';
|
|
10
10
|
export * from './context/exec-context-factory.js';
|
|
11
11
|
export * from './context/function-call-utils.js';
|
|
12
|
-
export { LAYER_HOOK_EVENT } from './context/hook-call.js';
|
|
12
|
+
export { LAYER_HOOK_EVENT, LayerHookAttr } from './context/hook-call.js';
|
|
13
13
|
export * from './context/layer-api.js';
|
|
14
14
|
export * from './context/layer-lifecycle.js';
|
|
15
15
|
export * from './context/layer-provides.js';
|
|
@@ -25,7 +25,9 @@ export * from './context/layers/steering.js';
|
|
|
25
25
|
export * from './context/layers/task-state.js';
|
|
26
26
|
export * from './context/layers/temporal.js';
|
|
27
27
|
export * from './context/layers/tool-calls.js';
|
|
28
|
+
export type { LiveChild } from './context/parent-updates.js';
|
|
28
29
|
export * from './context/projector.js';
|
|
29
30
|
export * from './context/scope.js';
|
|
31
|
+
export * from './context/span-export.js';
|
|
30
32
|
export * from './context/storage-batch.js';
|
|
31
33
|
export * from './context/strip-unresolved.js';
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export * from './context/budget.js';
|
|
|
11
11
|
export * from './context/cache-anchoring.js';
|
|
12
12
|
export * from './context/exec-context-factory.js';
|
|
13
13
|
export * from './context/function-call-utils.js';
|
|
14
|
-
export { LAYER_HOOK_EVENT } from './context/hook-call.js';
|
|
14
|
+
export { LAYER_HOOK_EVENT, LayerHookAttr } from './context/hook-call.js';
|
|
15
15
|
export * from './context/layer-api.js';
|
|
16
16
|
export * from './context/layer-lifecycle.js';
|
|
17
17
|
export * from './context/layer-provides.js';
|
|
@@ -29,5 +29,6 @@ export * from './context/layers/temporal.js';
|
|
|
29
29
|
export * from './context/layers/tool-calls.js';
|
|
30
30
|
export * from './context/projector.js';
|
|
31
31
|
export * from './context/scope.js';
|
|
32
|
+
export * from './context/span-export.js';
|
|
32
33
|
export * from './context/storage-batch.js';
|
|
33
34
|
export * from './context/strip-unresolved.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noetic-tools/context",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Context layer system for Noetic agents: the ContextLayer contract, lifecycle, budget/projection machinery, and built-in layer implementations.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"zod": "^4.0.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@noetic-tools/types": "^3.
|
|
47
|
+
"@noetic-tools/types": "^3.10.0",
|
|
48
48
|
"zod": "^4.0.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|