@noetic-tools/core 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +8 -6
  2. package/dist/adapters/in-memory-subprocess/step-completion.js +1 -1
  3. package/dist/adapters/openrouter.d.ts +20 -2
  4. package/dist/adapters/openrouter.js +102 -6
  5. package/dist/builders/control-flow-builders.d.ts +13 -13
  6. package/dist/builders/control-flow-builders.js +11 -11
  7. package/dist/{patterns → builders}/dynamic-workflow.d.ts +1 -1
  8. package/dist/{patterns → builders}/dynamic-workflow.js +18 -12
  9. package/dist/builders/every.d.ts +14 -14
  10. package/dist/builders/every.js +16 -16
  11. package/dist/builders/layer-provides-builders.d.ts +1 -1
  12. package/dist/builders/layer-provides-builders.js +1 -1
  13. package/dist/builders/provide-builder.d.ts +5 -12
  14. package/dist/builders/provide-builder.js +9 -10
  15. package/dist/builders/spawn-builder.d.ts +0 -2
  16. package/dist/builders/spawn-builder.js +2 -3
  17. package/dist/builders/step-builders.d.ts +66 -55
  18. package/dist/builders/step-builders.js +118 -111
  19. package/dist/builders/tool-builder.d.ts +18 -22
  20. package/dist/builders/tool-builder.js +6 -4
  21. package/dist/builders/workflow-hydrator.d.ts +3 -3
  22. package/dist/builders/workflow-hydrator.js +59 -63
  23. package/dist/builders/workflow-step.d.ts +13 -25
  24. package/dist/builders/workflow-step.js +13 -18
  25. package/dist/conditions/conditions.d.ts +2 -2
  26. package/dist/conditions/conditions.js +1 -1
  27. package/dist/harness/agent-harness.d.ts +58 -40
  28. package/dist/harness/agent-harness.js +125 -51
  29. package/dist/harness/deps/context.d.ts +1 -1
  30. package/dist/harness/deps/context.js +1 -1
  31. package/dist/harness/deps/interpreter.d.ts +1 -0
  32. package/dist/harness/deps/interpreter.js +1 -0
  33. package/dist/harness/deps/types.d.ts +2 -2
  34. package/dist/harness/model-call.d.ts +1 -1
  35. package/dist/harness/model-call.js +45 -23
  36. package/dist/index.d.ts +23 -35
  37. package/dist/index.js +10 -26
  38. package/dist/interpreter/action-deps.d.ts +2 -2
  39. package/dist/interpreter/action-deps.js +1 -1
  40. package/dist/interpreter/action-types.d.ts +1 -1
  41. package/dist/interpreter/collect-tools.js +7 -7
  42. package/dist/interpreter/context-assembly.d.ts +50 -0
  43. package/dist/interpreter/context-assembly.js +547 -0
  44. package/dist/interpreter/execute-action.d.ts +5 -5
  45. package/dist/interpreter/execute-action.js +337 -208
  46. package/dist/interpreter/execute-control.d.ts +9 -9
  47. package/dist/interpreter/execute-control.js +27 -27
  48. package/dist/interpreter/execute-sub-harness.d.ts +1 -1
  49. package/dist/interpreter/execute-sub-harness.js +7 -26
  50. package/dist/interpreter/execute.d.ts +1 -1
  51. package/dist/interpreter/execute.js +26 -25
  52. package/dist/interpreter/step-dispatcher.d.ts +1 -1
  53. package/dist/interpreter/step-dispatcher.js +3 -3
  54. package/dist/interpreter/structured-output.d.ts +14 -0
  55. package/dist/interpreter/structured-output.js +40 -0
  56. package/dist/interpreter/typeguards.d.ts +1 -1
  57. package/dist/interpreter/typeguards.js +1 -1
  58. package/dist/observability/genai-attributes.d.ts +20 -0
  59. package/dist/observability/genai-attributes.js +20 -0
  60. package/dist/portable/builders.d.ts +3 -3
  61. package/dist/portable/builders.js +6 -5
  62. package/dist/portable/context-layers.d.ts +1 -1
  63. package/dist/portable/context-layers.js +1 -1
  64. package/dist/runtime/broadcaster-utils.d.ts +1 -1
  65. package/dist/runtime/broadcaster-utils.js +1 -1
  66. package/dist/runtime/context-impl.d.ts +8 -10
  67. package/dist/runtime/context-impl.js +8 -12
  68. package/dist/runtime/durable/step-ledger.d.ts +2 -2
  69. package/dist/runtime/durable/step-ledger.js +3 -3
  70. package/dist/runtime/session-runner.d.ts +12 -1
  71. package/dist/runtime/session-runner.js +33 -1
  72. package/dist/runtime/tool-context.js +0 -2
  73. package/dist/schemas/workflow.d.ts +3 -3
  74. package/dist/schemas/workflow.js +2 -2
  75. package/dist/types/checkpoint.d.ts +11 -2
  76. package/dist/types/checkpoint.js +10 -1
  77. package/dist/{runtime → util}/detached-handle.js +1 -1
  78. package/package.json +6 -4
  79. package/schema/noetic-workflow.schema.json +24 -11
  80. package/dist/builders/context-option.d.ts +0 -23
  81. package/dist/builders/context-option.js +0 -22
  82. package/dist/deprecated.d.ts +0 -30
  83. package/dist/deprecated.js +0 -28
  84. package/dist/patterns/interview.d.ts +0 -53
  85. package/dist/patterns/interview.js +0 -113
  86. package/dist/patterns/plans.d.ts +0 -44
  87. package/dist/patterns/plans.js +0 -125
  88. package/dist/patterns/ralph-wiggum.d.ts +0 -19
  89. package/dist/patterns/ralph-wiggum.js +0 -37
  90. package/dist/patterns/react.d.ts +0 -19
  91. package/dist/patterns/react.js +0 -41
  92. /package/dist/{runtime → util}/detached-handle.d.ts +0 -0
@@ -2,32 +2,19 @@ import { ItemSchemaRegistry } from '@noetic-tools/types';
2
2
  import type { LayerStateStore, RecallCache } from './deps/context.js';
3
3
  import type { DetachedSpawnOverrides } from './deps/interpreter.js';
4
4
  import type { CheckpointStore, EventBroadcaster, RestoreCheckpointOptions, StepLedgerRetention, StepLedgerStore } from './deps/runtime.js';
5
- import type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, ContextData, ContextLayer, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, ProjectionPolicy, RecallLayerOutput, SessionScope, ShellAdapter, Span, SteeringDecision, Step, StorageAdapter, StreamEvent, StreamingItem, SubHarnessSession, SubprocessAdapter, Tool, TraceExporter, ZodType } from './deps/types.js';
5
+ import type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, ContextCacheConfig, ContextCacheStore, ContextData, ContextLayer, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaConfig, LLMResponse, LlmProviderConfig, ProjectionPolicy, RecallLayerOutput, SessionScope, SessionUsage, ShellAdapter, Span, SteeringDecision, Step, StorageAdapter, StreamEvent, StreamingItem, SubHarnessSession, SubprocessAdapter, Tool, TraceExporter, ZodType } from './deps/types.js';
6
6
  export { createStreamIdleWatchdog } from './model-call.js';
7
- interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<string, unknown>> {
8
- name: string;
9
- initialStep?: Step<ContextData, string, string>;
10
- /** Default context layers applied to every context created via `createContext()` / `execute()`. */
11
- context?: ContextLayer[];
12
- /** @deprecated Renamed to `context`. */
13
- memory?: ContextLayer[];
14
- storage?: StorageAdapter;
15
- hooks?: AgentHooks;
7
+ /**
8
+ * Storage-scoped environment configuration for `AgentHarness`.
9
+ *
10
+ * @public
11
+ */
12
+ export interface StorageEnvironmentConfig {
16
13
  /**
17
- * Harness-wide tool pool. Merged (identity-deduplicated) with tools
18
- * collected from `initialStep` to form every context's `unifiedTools`.
19
- * Use this when the workflow step tree is static and tools are supplied
20
- * per harness instance rather than baked into individual `step.llm` calls.
14
+ * Key-value storage adapter backing context-layer persistence and the
15
+ * step-completion ledger. Defaults to an in-memory adapter.
21
16
  */
22
- tools?: Tool[];
23
- params: TParams;
24
- paramsSchema?: ZodType<TParams>;
25
- /** Filesystem adapter. Defaults to local node:fs when not provided. */
26
- fs?: FsAdapter;
27
- /** Shell adapter. Defaults to local sh when not provided. */
28
- shell?: ShellAdapter;
29
- /** Subprocess adapter. Defaults to an in-memory, same-process adapter. */
30
- subprocess?: SubprocessAdapter;
17
+ adapter?: StorageAdapter;
31
18
  /**
32
19
  * Checkpoint store used by `harness.checkpoint(ctx)` / `harness.restore()`.
33
20
  * When absent, checkpoint/restore are no-ops — a zero-config harness keeps
@@ -35,6 +22,8 @@ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<stri
35
22
  * to enable durable execution.
36
23
  */
37
24
  checkpointStore?: CheckpointStore;
25
+ /** Layer-state store override. Defaults to an in-memory store. */
26
+ layerStateStore?: LayerStateStore;
38
27
  /**
39
28
  * Bounds on the step-completion ledger that backs step-level resume. Only meaningful
40
29
  * alongside a `checkpointStore`. Defaults to `DEFAULT_STEP_LEDGER_RETENTION` — 128 KiB
@@ -43,25 +32,54 @@ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<stri
43
32
  * never correctness.
44
33
  */
45
34
  stepLedgerRetention?: StepLedgerRetention;
46
- llm?: LlmProviderConfig;
47
- /** Harness-wide item schema extensions. */
48
- itemSchemas?: ItemSchemaExtensions;
49
- /** Whether unknown extension item types must match a registered schema. Defaults to true. */
50
- strictItemSchemas?: boolean;
35
+ }
36
+ /**
37
+ * Execution-environment configuration for `AgentHarness`: the storage,
38
+ * filesystem, shell, and subprocess surfaces the agent runs against.
39
+ *
40
+ * @public
41
+ */
42
+ export interface AgentEnvironmentConfig {
43
+ /** Storage configuration: adapter, durability stores, ledger retention. */
44
+ storage?: StorageEnvironmentConfig;
45
+ /** Filesystem adapter. Defaults to an in-memory adapter. */
46
+ fs?: FsAdapter;
47
+ /** Shell adapter. Defaults to an in-memory adapter. */
48
+ shell?: ShellAdapter;
49
+ /** Subprocess adapter. Defaults to an in-memory, same-process adapter. */
50
+ subprocess?: SubprocessAdapter;
51
+ }
52
+ interface AgentHarnessOpts<TParams extends Record<string, unknown> = Record<string, unknown>> {
53
+ name: string;
54
+ /** Root step tree executed for every turn submitted via `execute()`. */
55
+ agentGraph?: Step<ContextData, string, string>;
56
+ /** Default context layers applied to every context created via `createContext()` / `execute()`. */
57
+ contextLayers?: ContextLayer[];
58
+ hooks?: AgentHooks;
59
+ /**
60
+ * Harness-wide tool pool. Merged (identity-deduplicated) with tools
61
+ * collected from `agentGraph` to form every context's `unifiedTools`.
62
+ * Use this when the workflow step tree is static and tools are supplied
63
+ * per harness instance rather than baked into individual `step.callModel` calls.
64
+ */
65
+ tools?: Tool[];
66
+ params: TParams;
67
+ paramsSchema?: ZodType<TParams>;
68
+ /** Execution environment: storage, filesystem, shell, subprocess. */
69
+ environment?: AgentEnvironmentConfig;
70
+ /** Default provider configuration for model calls made through this harness. */
71
+ callModelDefaults?: LlmProviderConfig;
72
+ /** Harness-wide item schema extensions and validation strictness. */
73
+ itemSchemas?: ItemSchemaConfig;
51
74
  /** Default projection policy for all LLM steps. Individual steps override via `step.projection`. */
52
75
  projection?: ProjectionPolicy;
53
76
  /** When true, every layer is recalled atomically regardless of its `recallMode`. */
54
77
  forceAtomicRecall?: boolean;
78
+ /** Tuning for prompt-cache anchoring. See `ContextCacheConfig` for the defaults. */
79
+ contextCache?: ContextCacheConfig;
55
80
  traceExporter?: TraceExporter;
56
- layerStateStore?: LayerStateStore;
57
81
  /** Default delivery mode for messages that don't specify one. Defaults to `next-turn`. */
58
82
  defaultDeliveryMode?: DeliveryMode;
59
- /**
60
- * Abort the in-flight model call if the provider stream emits no events for this
61
- * many milliseconds. Defaults to `DEFAULT_STREAM_IDLE_TIMEOUT_MS` (120s).
62
- * Pass `0` or a negative number to disable the watchdog.
63
- */
64
- streamIdleTimeoutMs?: number;
65
83
  /**
66
84
  * Initial working directory for the harness. Used as the seed value for the
67
85
  * shared `cwdState.cwd` on every Context this harness creates, including
@@ -127,7 +145,7 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
127
145
  * @internal
128
146
  */
129
147
  readonly stepLedgerRetention: Required<StepLedgerRetention>;
130
- private readonly initialStep?;
148
+ private readonly agentGraph?;
131
149
  /** Harness-wide tool pool merged into every context's `unifiedTools`. */
132
150
  private readonly harnessTools;
133
151
  /** @internal Context layers configured for this harness. Exposed non-private
@@ -140,7 +158,6 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
140
158
  private readonly rootRunDepth;
141
159
  private readonly callModelOverride?;
142
160
  private readonly defaultDeliveryMode;
143
- private readonly streamIdleTimeoutMs;
144
161
  private readonly sessions;
145
162
  /**
146
163
  * @internal Cross-step harness sessions keyed by `step.session.reuse`, kept
@@ -152,6 +169,8 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
152
169
  readonly layerStateStore: LayerStateStore;
153
170
  /** Per-harness memoization cache for `recallMode: 'eventual'` layers. */
154
171
  readonly recallCache: RecallCache;
172
+ /** Per-harness pinned anchor output and epoch bookkeeping, keyed by cache lineage. */
173
+ readonly contextCache: ContextCacheStore;
155
174
  /**
156
175
  * Execution ids whose layer `init` hooks have already run, so repeated/nested
157
176
  * `run()` calls and the session turn path never re-init (which would clobber
@@ -189,6 +208,7 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
189
208
  }): Promise<void>;
190
209
  getStatus(scope?: SessionScope): HarnessStatus;
191
210
  getQueueSize(scope?: SessionScope): number;
211
+ getUsage(scope?: SessionScope): SessionUsage;
192
212
  seedSessionHistory(threadId: string, items: ReadonlyArray<Item>): void;
193
213
  private getOrCreateSession;
194
214
  private requireSession;
@@ -221,9 +241,7 @@ export declare class AgentHarness<TParams extends Record<string, unknown> = Reco
221
241
  state?: unknown;
222
242
  threadId?: string;
223
243
  resourceId?: string;
224
- context?: ContextLayer[];
225
- /** @deprecated Renamed to `context`. */
226
- memory?: ContextLayer[];
244
+ contextLayers?: ContextLayer[];
227
245
  /**
228
246
  * Initial cwd for the new context. When set, takes precedence over both
229
247
  * the parent snapshot and the harness root cwd — used by worktree
@@ -2,13 +2,12 @@ import { frameworkCast, ItemSchemaRegistry, NoeticConfigError } from '@noetic-to
2
2
  import { SpanImpl } from '../observability/span-impl.js';
3
3
  import { NoopExporter } from '../observability/trace-exporter.js';
4
4
  import { createInMemoryFsAdapter, createInMemoryShellAdapter, createInMemorySubprocessAdapter, OpenRouter, } from './deps/adapters.js';
5
- import { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from './deps/context.js';
6
- import { collectAllTools, deduplicateTools, dispatchStepThroughAdapter, execute, } from './deps/interpreter.js';
5
+ import { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from './deps/context.js';
6
+ import { collectAllTools, deduplicateTools, dispatchStepThroughAdapter, execute, prepareBandedView, } from './deps/interpreter.js';
7
7
  import { buildItemStream, ChannelStore, ContextImpl, captureCheckpoint, clearCheckpoint, collectContextTree, createInMemoryStorage, createStepLedgerStore, filterReasoningStream, filterTextStream, resolveStepLedgerRetention, restoreFromCheckpoint, SessionRunner, StepLedger, snapshotCwdState, } from './deps/runtime.js';
8
8
  import { SteeringAction } from './deps/types.js';
9
9
  import { AgentHarnessModelCaller } from './model-call.js';
10
10
  export { createStreamIdleWatchdog } from './model-call.js';
11
- import { resolveContextOption } from '../builders/context-option.js';
12
11
  import { buildItemSchemaRegistry } from './model-schema.js';
13
12
  //#endregion
14
13
  const DEFAULT_THREAD_ID = '__default__';
@@ -77,7 +76,43 @@ export function resolveLlmClient(config, env) {
77
76
  cache: config?.cache ?? false,
78
77
  };
79
78
  }
80
- function createClient(config) {
79
+ /**
80
+ * Ask the provider to cache the prompt prefix.
81
+ *
82
+ * Anthropic caching is opt-in — without a breakpoint it caches nothing however
83
+ * stable the prefix is, measured against live models. OpenRouter accepts a
84
+ * `cache_control` directive on the request and places the breakpoints itself;
85
+ * providers that cache on their own (OpenAI, Gemini) ignore it.
86
+ *
87
+ * It has to be injected here rather than passed with the other request fields:
88
+ * the SDK validates the request against a generated schema that drops keys it
89
+ * does not know, and `cache_control` is one of them.
90
+ */
91
+ async function addCacheBreakpoint(request) {
92
+ const body = await request.clone().text();
93
+ if (!body) {
94
+ return request;
95
+ }
96
+ let payload;
97
+ try {
98
+ payload = JSON.parse(body);
99
+ }
100
+ catch {
101
+ return request;
102
+ }
103
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
104
+ return request;
105
+ }
106
+ return new Request(request, {
107
+ body: JSON.stringify({
108
+ ...payload,
109
+ cache_control: {
110
+ type: 'ephemeral',
111
+ },
112
+ }),
113
+ });
114
+ }
115
+ function createClient(config, contextCache) {
81
116
  const resolved = resolveLlmClient(config, readLlmEnv());
82
117
  if (!resolved) {
83
118
  return undefined;
@@ -88,20 +123,25 @@ function createClient(config) {
88
123
  if (resolved.serverURL) {
89
124
  options.serverURL = resolved.serverURL;
90
125
  }
91
- if (resolved.cache) {
92
- // Inject `X-OpenRouter-Cache: true` on every request so the upstream serves
93
- // identical model calls from cache without re-billing (deterministic re-runs).
94
- return new OpenRouter({
95
- ...options,
96
- hooks: {
97
- beforeRequest: (_ctx, request) => {
126
+ // A cache write costs more than a plain read, so the breakpoint only pays off
127
+ // when something is holding the prefix still — which is what anchoring does.
128
+ const wantsBreakpoint = contextCache?.enabled !== false;
129
+ if (!resolved.cache && !wantsBreakpoint) {
130
+ return new OpenRouter(options);
131
+ }
132
+ return new OpenRouter({
133
+ ...options,
134
+ hooks: {
135
+ beforeRequest: async (_ctx, request) => {
136
+ if (resolved.cache) {
137
+ // Serve identical model calls from OpenRouter's own response cache
138
+ // without re-billing (deterministic re-runs).
98
139
  request.headers.set('X-OpenRouter-Cache', 'true');
99
- return request;
100
- },
140
+ }
141
+ return wantsBreakpoint ? addCacheBreakpoint(request) : request;
101
142
  },
102
- });
103
- }
104
- return new OpenRouter(options);
143
+ },
144
+ });
105
145
  }
106
146
  //#region AgentHarness
107
147
  /**
@@ -137,7 +177,7 @@ export class AgentHarness {
137
177
  * @internal
138
178
  */
139
179
  stepLedgerRetention;
140
- initialStep;
180
+ agentGraph;
141
181
  /** Harness-wide tool pool merged into every context's `unifiedTools`. */
142
182
  harnessTools;
143
183
  /** @internal Context layers configured for this harness. Exposed non-private
@@ -150,7 +190,6 @@ export class AgentHarness {
150
190
  rootRunDepth = new Map();
151
191
  callModelOverride;
152
192
  defaultDeliveryMode;
153
- streamIdleTimeoutMs;
154
193
  sessions = new Map();
155
194
  /**
156
195
  * @internal Cross-step harness sessions keyed by `step.session.reuse`, kept
@@ -162,6 +201,8 @@ export class AgentHarness {
162
201
  layerStateStore;
163
202
  /** Per-harness memoization cache for `recallMode: 'eventual'` layers. */
164
203
  recallCache;
204
+ /** Per-harness pinned anchor output and epoch bookkeeping, keyed by cache lineage. */
205
+ contextCache;
165
206
  /**
166
207
  * Execution ids whose layer `init` hooks have already run, so repeated/nested
167
208
  * `run()` calls and the session turn path never re-init (which would clobber
@@ -182,43 +223,47 @@ export class AgentHarness {
182
223
  itemSchemas;
183
224
  constructor(opts) {
184
225
  const validatedParams = opts.paramsSchema ? opts.paramsSchema.parse(opts.params) : opts.params;
226
+ const environment = opts.environment;
227
+ const storageEnv = environment?.storage;
185
228
  this.config = {
186
229
  name: opts.name,
187
- storage: opts.storage ?? createInMemoryStorage(),
230
+ storage: storageEnv?.adapter ?? createInMemoryStorage(),
188
231
  hooks: opts.hooks,
189
232
  params: validatedParams,
190
233
  itemSchemas: opts.itemSchemas,
191
- strictItemSchemas: opts.strictItemSchemas ?? true,
192
234
  projection: opts.projection,
193
235
  forceAtomicRecall: opts.forceAtomicRecall,
236
+ contextCache: opts.contextCache,
194
237
  };
195
- this.fs = opts.fs ?? createInMemoryFsAdapter();
196
- this.shell = opts.shell ?? createInMemoryShellAdapter();
197
- this.subprocess = opts.subprocess ?? createInMemorySubprocessAdapter();
198
- this.checkpointStore = opts.checkpointStore;
199
- this.stepLedgerStore = opts.checkpointStore
238
+ this.fs = environment?.fs ?? createInMemoryFsAdapter();
239
+ this.shell = environment?.shell ?? createInMemoryShellAdapter();
240
+ this.subprocess = environment?.subprocess ?? createInMemorySubprocessAdapter();
241
+ this.checkpointStore = storageEnv?.checkpointStore;
242
+ this.stepLedgerStore = storageEnv?.checkpointStore
200
243
  ? createStepLedgerStore({
201
244
  storage: this.config.storage ?? createInMemoryStorage(),
202
245
  })
203
246
  : undefined;
204
- this.stepLedgerRetention = resolveStepLedgerRetention(opts.stepLedgerRetention);
205
- this.initialStep = opts.initialStep;
247
+ this.stepLedgerRetention = resolveStepLedgerRetention(storageEnv?.stepLedgerRetention);
248
+ this.agentGraph = opts.agentGraph;
206
249
  this.harnessTools = opts.tools ?? [];
207
- this._contextLayers = resolveContextOption(opts);
250
+ this._contextLayers = opts.contextLayers;
208
251
  this.callModelOverride = opts._testCallModel;
209
- this.client = opts._testCallModel ? undefined : createClient(opts.llm);
252
+ this.client = opts._testCallModel
253
+ ? undefined
254
+ : createClient(opts.callModelDefaults, opts.contextCache);
210
255
  this.channelStore = new ChannelStore();
211
256
  this.traceExporter = opts.traceExporter ?? new NoopExporter();
212
257
  this.layerStateStore =
213
- opts.layerStateStore ??
258
+ storageEnv?.layerStateStore ??
214
259
  createLayerStateStore((layerId, hook, error) => {
215
260
  console.warn(`[noetic] context layer '${layerId}' ${hook} error:`, error);
216
261
  });
217
262
  this.recallCache = createRecallCache();
263
+ this.contextCache = createContextCacheStore();
218
264
  this.defaultDeliveryMode = opts.defaultDeliveryMode ?? 'next-turn';
219
- this.streamIdleTimeoutMs = opts.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
220
- this.itemSchemas = new ItemSchemaRegistry(opts.itemSchemas, {
221
- strictUnknownExtensions: opts.strictItemSchemas ?? true,
265
+ this.itemSchemas = new ItemSchemaRegistry(opts.itemSchemas?.schemas, {
266
+ strictUnknownExtensions: opts.itemSchemas?.strict ?? true,
222
267
  });
223
268
  this.rootCwdState = {
224
269
  cwd: opts.initialCwd ?? '/',
@@ -239,11 +284,11 @@ export class AgentHarness {
239
284
  }
240
285
  //#region Session Accessors
241
286
  execute(input, options) {
242
- if (!this.initialStep) {
287
+ if (!this.agentGraph) {
243
288
  return Promise.reject(new NoeticConfigError({
244
289
  code: 'NO_STEP_CONFIGURED',
245
- message: 'No initialStep configured on this harness.',
246
- hint: 'Pass `initialStep` in constructor options, or use run() directly.',
290
+ message: 'No agentGraph configured on this harness.',
291
+ hint: 'Pass `agentGraph` in constructor options, or use run() directly.',
247
292
  }));
248
293
  }
249
294
  const threadId = options?.threadId ?? DEFAULT_THREAD_ID;
@@ -308,6 +353,17 @@ export class AgentHarness {
308
353
  const session = this.sessions.get(threadId);
309
354
  return session ? session.runner.queue.size : 0;
310
355
  }
356
+ getUsage(scope) {
357
+ const threadId = scope?.threadId ?? DEFAULT_THREAD_ID;
358
+ const session = this.sessions.get(threadId);
359
+ if (!session) {
360
+ return {
361
+ inputTokens: 0,
362
+ outputTokens: 0,
363
+ };
364
+ }
365
+ return session.runner.getUsage();
366
+ }
311
367
  seedSessionHistory(threadId, items) {
312
368
  const session = this.getOrCreateSession(threadId);
313
369
  session.accumulatedItems = [
@@ -340,15 +396,15 @@ export class AgentHarness {
340
396
  threadId,
341
397
  resourceId: perTurnOptions.resourceId,
342
398
  state: perTurnOptions.state,
343
- context: resolveContextOption(perTurnOptions),
399
+ contextLayers: perTurnOptions.contextLayers,
344
400
  _broadcaster: session.runner.broadcaster,
345
401
  });
346
402
  const ext = frameworkCast(ctx);
347
403
  ext._sessionQueue = session.runner.queue;
348
404
  ext._sessionBetweenRounds = true;
349
405
  ext._sessionRunnerAgentName = this.config.name;
350
- if (this.initialStep || this.harnessTools.length > 0) {
351
- const stepTools = this.initialStep ? collectAllTools(this.initialStep) : [];
406
+ if (this.agentGraph || this.harnessTools.length > 0) {
407
+ const stepTools = this.agentGraph ? collectAllTools(this.agentGraph) : [];
352
408
  this.setUnifiedTools(ctx, [
353
409
  ...stepTools,
354
410
  ...this.harnessTools,
@@ -357,11 +413,11 @@ export class AgentHarness {
357
413
  return ctx;
358
414
  },
359
415
  runTurn: async (ctx, _turn, signal) => {
360
- if (!this.initialStep) {
416
+ if (!this.agentGraph) {
361
417
  throw new NoeticConfigError({
362
418
  code: 'NO_STEP_CONFIGURED',
363
- message: 'No initialStep configured on this harness.',
364
- hint: 'Pass `initialStep` in constructor options.',
419
+ message: 'No agentGraph configured on this harness.',
420
+ hint: 'Pass `agentGraph` in constructor options.',
365
421
  });
366
422
  }
367
423
  // Wire signal-abort to context-abort so the interpreter bails cleanly.
@@ -375,7 +431,7 @@ export class AgentHarness {
375
431
  once: true,
376
432
  });
377
433
  }
378
- const result = await this.initAndRun(this.initialStep, '', ctx);
434
+ const result = await this.initAndRun(this.agentGraph, '', ctx);
379
435
  // Snapshot final items into session history for the next turn.
380
436
  session.accumulatedItems = [
381
437
  ...ctx.itemLog.items,
@@ -405,7 +461,7 @@ export class AgentHarness {
405
461
  itemSchemas: this.itemSchemas,
406
462
  client: this.client,
407
463
  callModelOverride: this.callModelOverride,
408
- streamIdleTimeoutMs: this.streamIdleTimeoutMs,
464
+ streamIdleTimeoutMs: DEFAULT_STREAM_IDLE_TIMEOUT_MS,
409
465
  harness: this,
410
466
  traceExporter: this.traceExporter,
411
467
  }).callModel(request);
@@ -495,10 +551,10 @@ export class AgentHarness {
495
551
  }
496
552
  createContext(opts) {
497
553
  const resolved = opts ?? {};
498
- // `ContextImpl` takes the layers as `layers`, so both option spellings are
554
+ // `ContextImpl` takes the layers as `layers`, so the option spelling is
499
555
  // dropped from `rest` rather than spread through under the wrong name.
500
- const { context: _context, memory: _memory, cwdInit, ...rest } = resolved;
501
- const effectiveLayers = resolveContextOption(resolved) ?? this._contextLayers;
556
+ const { contextLayers, cwdInit, ...rest } = resolved;
557
+ const effectiveLayers = contextLayers ?? this._contextLayers;
502
558
  const itemSchemas = buildItemSchemaRegistry({
503
559
  base: this.itemSchemas,
504
560
  layers: effectiveLayers,
@@ -651,7 +707,7 @@ export class AgentHarness {
651
707
  const ctx = this.createContext({
652
708
  items: historyItems,
653
709
  threadId,
654
- context: this._contextLayers,
710
+ contextLayers: this._contextLayers,
655
711
  });
656
712
  const layers = ctx.layers ?? [];
657
713
  if (layers.length === 0) {
@@ -660,14 +716,32 @@ export class AgentHarness {
660
716
  try {
661
717
  await this.ensureLayersInit(ctx);
662
718
  const recallResults = await this.recallLayers(layers, '', ctx);
663
- const layerOutputItems = recallResults.flatMap((r) => r.items);
664
- if (layerOutputItems.length === 0) {
719
+ // Band the output the way a real turn would, but read-only: a preview
720
+ // must not pin, count churn, or age the epoch, or looking at a
721
+ // conversation would change what the next turn sends.
722
+ const banded = await prepareBandedView({
723
+ recallResults,
724
+ layers,
725
+ execCtx: contextToExecCtx(ctx),
726
+ store: this.contextCache,
727
+ config: this.config.contextCache,
728
+ instructions: undefined,
729
+ policy: this.config.projection ?? DEFAULT_PROJECTION,
730
+ systemPromptItems: [],
731
+ budgets: new Map(),
732
+ readOnly: true,
733
+ });
734
+ if (banded.anchorItems.length === 0 &&
735
+ banded.liveItems.length === 0 &&
736
+ banded.deltaItems.length === 0) {
665
737
  return historyItems;
666
738
  }
667
739
  return assembleView({
668
740
  systemPromptItems: [],
669
- layerOutputItems,
741
+ layerOutputItems: banded.anchorItems,
670
742
  historyItems,
743
+ liveLayerItems: banded.liveItems,
744
+ deltaItems: banded.deltaItems,
671
745
  });
672
746
  }
673
747
  finally {
@@ -1,2 +1,2 @@
1
1
  export type { LayerStateStore, RecallCache } from '@noetic-tools/context';
2
- export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
2
+ export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
@@ -1 +1 @@
1
- export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
1
+ export { afterModelCallLayers, allocateBudgets, assembleView, beforeToolCallLayers, completeLayers, contextToExecCtx, createContextCacheStore, createLayerStateStore, createRecallCache, DEFAULT_PROJECTION, disposeLayers, executeRerender, initLayers, projectHistoryLayers, recallLayers, recallLayersAtomic, recallLayersEventual, resolveLayerTools, runAppendPipeline, storeLayers, } from '@noetic-tools/context';
@@ -1,4 +1,5 @@
1
1
  export { collectAllTools, deduplicateTools } from '../../interpreter/collect-tools.js';
2
+ export { prepareBandedView } from '../../interpreter/context-assembly.js';
2
3
  export { execute } from '../../interpreter/execute.js';
3
4
  export type { DetachedSpawnOverrides } from '../../interpreter/step-dispatcher.js';
4
5
  export { dispatchStepThroughAdapter } from '../../interpreter/step-dispatcher.js';
@@ -1,4 +1,5 @@
1
1
  export { collectAllTools, deduplicateTools } from '../../interpreter/collect-tools.js';
2
+ export { prepareBandedView } from '../../interpreter/context-assembly.js';
2
3
  export { execute } from '../../interpreter/execute.js';
3
4
  export { dispatchStepThroughAdapter } from '../../interpreter/step-dispatcher.js';
4
5
  export { isFunctionCall } from '../../interpreter/typeguards.js';
@@ -1,4 +1,4 @@
1
- export type { ContextData, ContextLayer, ExecutionContext, ProjectionPolicy, StorageAdapter, } from '@noetic-tools/context';
2
- export type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, RecallLayerOutput, SessionScope, ShellAdapter, Span, SteeringDecision, Step, StreamEvent, StreamingItem, SubHarnessSession, SubprocessAdapter, Tool, TraceExporter, } from '@noetic-tools/types';
1
+ export type { ContextCacheConfig, ContextCacheStore, ContextData, ContextLayer, ExecutionContext, ProjectionPolicy, StorageAdapter, } from '@noetic-tools/context';
2
+ export type { AgentConfig, AgentHarnessContract, AgentHooks, CallModelRequest, Channel, ChannelHandle, Context, CwdState, DeliveryMode, DetachedHandle, ExecuteInput, ExecuteOptions, ExternalChannel, FsAdapter, HarnessResponse, HarnessStatus, Item, ItemSchemaConfig, ItemSchemaExtensions, LLMResponse, LlmProviderConfig, RecallLayerOutput, SessionScope, SessionUsage, ShellAdapter, Span, SteeringDecision, Step, StreamEvent, StreamingItem, SubHarnessSession, SubprocessAdapter, Tool, TraceExporter, } from '@noetic-tools/types';
3
3
  export { SteeringAction } from '@noetic-tools/types';
4
4
  export type { ZodType } from 'zod';
@@ -10,7 +10,7 @@ interface StreamIdleWatchdog {
10
10
  /** @internal Create a watchdog that aborts `controller` when no
11
11
  * {@link StreamIdleWatchdog.reset reset} is called within `timeoutMs`. When
12
12
  * `timeoutMs <= 0`, returns an inert no-op so callers can always call `.reset()`
13
- * / `.stop()` without a branch. Starts armed: the caller is responsible for
13
+ * / `.stop()` without branching. Starts armed: the caller is responsible for
14
14
  * `.stop()` in a `finally`. `onTimeout` runs before the abort so observers can
15
15
  * emit a framework event with the original cause. Exported only for unit tests. */
16
16
  export declare function createStreamIdleWatchdog(timeoutMs: number, controller: AbortController, onTimeout?: () => void): StreamIdleWatchdog;