@noetic-tools/context 1.1.0 → 1.2.1

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 (43) hide show
  1. package/README.md +2 -3
  2. package/dist/context/cache-anchoring.d.ts +103 -0
  3. package/dist/context/cache-anchoring.js +273 -0
  4. package/dist/context/layer-lifecycle.d.ts +3 -5
  5. package/dist/context/layer-lifecycle.js +12 -2
  6. package/dist/context/layer-provides.d.ts +1 -1
  7. package/dist/context/layer-provides.js +1 -1
  8. package/dist/context/layer-usage.d.ts +18 -3
  9. package/dist/context/layer-usage.js +10 -1
  10. package/dist/context/layers/{file-reference.d.ts → filesystem.d.ts} +1 -1
  11. package/dist/context/layers/{file-reference.js → filesystem.js} +60 -3
  12. package/dist/context/layers/{history-window.d.ts → history.d.ts} +4 -4
  13. package/dist/context/layers/{history-window.js → history.js} +6 -6
  14. package/dist/context/layers/{static-content.d.ts → instructions.d.ts} +3 -2
  15. package/dist/context/layers/{static-content.js → instructions.js} +5 -2
  16. package/dist/context/layers/{observational-context.d.ts → observations.d.ts} +12 -12
  17. package/dist/context/layers/{observational-context.js → observations.js} +7 -7
  18. package/dist/context/layers/plan-prompts.d.ts +1 -1
  19. package/dist/context/layers/plan-prompts.js +16 -16
  20. package/dist/context/layers/plan-state.d.ts +31 -0
  21. package/dist/context/layers/plan-state.js +12 -0
  22. package/dist/context/layers/{plan-context.d.ts → plan.d.ts} +6 -28
  23. package/dist/context/layers/plan.js +697 -0
  24. package/dist/context/layers/{working-memory-context.d.ts → scratchpad.d.ts} +13 -13
  25. package/dist/context/layers/{working-memory-context.js → scratchpad.js} +8 -8
  26. package/dist/context/layers/steering.d.ts +1 -0
  27. package/dist/context/layers/steering.js +6 -1
  28. package/dist/context/layers/{durable-task-state.d.ts → task-state.d.ts} +20 -20
  29. package/dist/context/layers/{durable-task-state.js → task-state.js} +9 -9
  30. package/dist/context/layers/{temporal-context.d.ts → temporal.d.ts} +5 -5
  31. package/dist/context/layers/{temporal-context.js → temporal.js} +11 -5
  32. package/dist/context/layers/{tool-context-layer.d.ts → tool-calls.d.ts} +2 -2
  33. package/dist/context/layers/{tool-context-layer.js → tool-calls.js} +1 -1
  34. package/dist/context/projector.d.ts +27 -8
  35. package/dist/context/projector.js +42 -13
  36. package/dist/context/strip-unresolved.d.ts +1 -1
  37. package/dist/context/strip-unresolved.js +1 -1
  38. package/dist/index.d.ts +10 -10
  39. package/dist/index.js +10 -10
  40. package/package.json +2 -2
  41. package/dist/context/layers/plan-context.js +0 -683
  42. package/dist/deprecated.d.ts +0 -43
  43. package/dist/deprecated.js +0 -28
@@ -1,20 +1,20 @@
1
1
  import type { ZodType } from 'zod';
2
- export type WorkingMemoryContextState = string | Record<string, unknown>;
3
- export interface WorkingMemoryContextConfig {
2
+ export type ScratchpadState = string | Record<string, unknown>;
3
+ export interface ScratchpadConfig {
4
4
  scope?: 'thread' | 'resource';
5
5
  schema?: ZodType;
6
6
  template?: string;
7
7
  readOnly?: boolean;
8
8
  }
9
9
  /**
10
- * Creates a mutable working context layer that the model can update via the `working-context/update` tool.
10
+ * Creates a mutable scratchpad context layer that the model can update via the `scratchpad/update` tool.
11
11
  *
12
12
  * @public
13
13
  * @param config - Optional configuration for scope, Zod schema, template, and read-only mode.
14
14
  * @returns A `ContextLayer` providing scratchpad state the model can read and write.
15
15
  */
16
- export declare function workingMemoryContext(config?: WorkingMemoryContextConfig): {
17
- id: 'working-context';
16
+ export declare function scratchpad(config?: ScratchpadConfig): {
17
+ id: 'scratchpad';
18
18
  name: string;
19
19
  slot: 100;
20
20
  scope: "resource" | "thread";
@@ -23,24 +23,24 @@ export declare function workingMemoryContext(config?: WorkingMemoryContextConfig
23
23
  max: number;
24
24
  };
25
25
  provides: {
26
- snapshot: import("@noetic-tools/types").LayerDataDecl<WorkingMemoryContextState, WorkingMemoryContextState>;
27
- update: import("@noetic-tools/types").LayerFunctionDecl<Record<string, unknown>, void, WorkingMemoryContextState>;
26
+ snapshot: import("@noetic-tools/types").LayerDataDecl<ScratchpadState, ScratchpadState>;
27
+ update: import("@noetic-tools/types").LayerFunctionDecl<Record<string, unknown>, void, ScratchpadState>;
28
28
  };
29
29
  hooks: {
30
30
  init({ storage }: import("@noetic-tools/types").InitParams): Promise<{
31
31
  state: {};
32
32
  } | {
33
- state: WorkingMemoryContextState;
33
+ state: ScratchpadState;
34
34
  }>;
35
- recall({ state }: import("@noetic-tools/types").RecallParams<WorkingMemoryContextState>): Promise<{
35
+ recall({ state }: import("@noetic-tools/types").RecallParams<ScratchpadState>): Promise<{
36
36
  items: import("@noetic-tools/types").InputMessageItem[];
37
37
  tokenCount: number;
38
38
  } | null>;
39
- store({ newItems, state }: import("@noetic-tools/types").StoreParams<WorkingMemoryContextState>): Promise<{
40
- state: WorkingMemoryContextState;
39
+ store({ newItems, state }: import("@noetic-tools/types").StoreParams<ScratchpadState>): Promise<{
40
+ state: ScratchpadState;
41
41
  } | undefined>;
42
- onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<WorkingMemoryContextState>): Promise<{
43
- childState: WorkingMemoryContextState;
42
+ onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<ScratchpadState>): Promise<{
43
+ childState: ScratchpadState;
44
44
  } | null>;
45
45
  };
46
46
  };
@@ -1,7 +1,7 @@
1
1
  import { createMessage, estimateTokens, Slot } from '@noetic-tools/types';
2
2
  import { z } from 'zod';
3
3
  import { findFunctionCall } from '../function-call-utils.js';
4
- import { layerData, layerFn } from '../layer-provides.js';
4
+ import { layerData, layerFunction } from '../layer-provides.js';
5
5
  function isPlainObject(value) {
6
6
  return typeof value === 'object' && value !== null && !Array.isArray(value);
7
7
  }
@@ -33,7 +33,7 @@ function deepMerge(target, source, depth = 0) {
33
33
  return out;
34
34
  }
35
35
  /**
36
- * Validate a MERGED working-context state against the configured schema (a
36
+ * Validate a MERGED scratchpad state against the configured schema (a
37
37
  * pure gate — the original value is returned, transforms are not applied).
38
38
  * Validating the merged state (not the raw update) keeps partial updates
39
39
  * legal under schemas with required fields. Throws on failure so the update
@@ -45,7 +45,7 @@ function applySchema(schema, merged) {
45
45
  }
46
46
  const result = schema.safeParse(merged);
47
47
  if (!result.success) {
48
- throw new Error(`working-context update rejected by schema: ${result.error.message}`);
48
+ throw new Error(`scratchpad update rejected by schema: ${result.error.message}`);
49
49
  }
50
50
  return merged;
51
51
  }
@@ -66,17 +66,17 @@ function safeMerge(state, args) {
66
66
  return merged;
67
67
  }
68
68
  /**
69
- * Creates a mutable working context layer that the model can update via the `working-context/update` tool.
69
+ * Creates a mutable scratchpad context layer that the model can update via the `scratchpad/update` tool.
70
70
  *
71
71
  * @public
72
72
  * @param config - Optional configuration for scope, Zod schema, template, and read-only mode.
73
73
  * @returns A `ContextLayer` providing scratchpad state the model can read and write.
74
74
  */
75
- export function workingMemoryContext(config) {
75
+ export function scratchpad(config) {
76
76
  const scope = config?.scope ?? 'thread';
77
77
  return {
78
- id: 'working-context',
79
- name: 'Working Memory',
78
+ id: 'scratchpad',
79
+ name: 'Scratchpad',
80
80
  slot: Slot.WORKING_MEMORY,
81
81
  scope,
82
82
  budget: {
@@ -87,7 +87,7 @@ export function workingMemoryContext(config) {
87
87
  snapshot: layerData({
88
88
  read: (state) => state,
89
89
  }),
90
- update: layerFn({
90
+ update: layerFunction({
91
91
  description: 'Update the agent working context with new key-value pairs.',
92
92
  input: z.record(z.string(), z.unknown()),
93
93
  output: z.void(),
@@ -11,6 +11,7 @@ export declare function steering(config: SteeringConfig): {
11
11
  name: string;
12
12
  slot: 90;
13
13
  scope: ContextScope;
14
+ placement: "live";
14
15
  budget: {
15
16
  min: number;
16
17
  max: number;
@@ -9,7 +9,7 @@ function requireCallModel(params, ruleId) {
9
9
  throw new NoeticConfigError({
10
10
  code: 'MISSING_CALL_MODEL',
11
11
  message: `LLM-evaluated steering rule "${ruleId}" requires a callModel but none is available.`,
12
- hint: 'Pass `llm: { provider: "openrouter", apiKey: "..." }` to AgentHarness or set OPENROUTER_API_KEY.',
12
+ hint: 'Pass `callModelDefaults: { apiKey: "..." }` (defaults to the Noetic platform) to AgentHarness or set NOETIC_API_KEY. For direct OpenRouter, use `callModelDefaults: { provider: "openrouter", apiKey: "..." }` or set OPENROUTER_API_KEY.',
13
13
  });
14
14
  }
15
15
  }
@@ -162,6 +162,11 @@ export function steering(config) {
162
162
  name: 'Steering',
163
163
  slot: Slot.STEERING,
164
164
  scope,
165
+ // `recall` drains the pending queue as it renders, so its output can never
166
+ // be replayed from a pin — the drain is already committed, and serving an
167
+ // older render would lose the feedback for good. Rendering after history
168
+ // also puts guidance where the model weighs it most.
169
+ placement: 'live',
165
170
  budget: {
166
171
  min: 0,
167
172
  max: 500,
@@ -1,4 +1,4 @@
1
- export interface DurableTaskState {
1
+ export interface TaskState {
2
2
  checkpoints: Array<{
3
3
  timestamp: number;
4
4
  depth: number;
@@ -7,7 +7,7 @@ export interface DurableTaskState {
7
7
  data: Record<string, unknown>;
8
8
  }
9
9
  /**
10
- * How a child's `data` map is merged into the parent's at a spawn/fork return.
10
+ * How a child's `data` map is merged into the parent's at a spawn/inParallel return.
11
11
  *
12
12
  * - `'shallow'` — `{ ...parent, ...child }`. Concurrent children that write the
13
13
  * same key clobber each other (last to return wins). Fine for a single child.
@@ -18,15 +18,15 @@ export interface DurableTaskState {
18
18
  *
19
19
  * @public
20
20
  */
21
- export type DurableTaskDataMerge = 'shallow' | 'namespace';
22
- /** @public Options for {@link durableTaskState}. */
23
- export interface DurableTaskStateOptions {
21
+ export type TaskStateDataMerge = 'shallow' | 'namespace';
22
+ /** @public Options for {@link taskState}. */
23
+ export interface TaskStateOptions {
24
24
  /**
25
25
  * Merge strategy for `data` at a child boundary. Defaults to `'shallow'`.
26
26
  * Use `'namespace'` for coordinator/worker fan-out, where several children
27
27
  * return concurrently into one parent.
28
28
  */
29
- mergeData?: DurableTaskDataMerge;
29
+ mergeData?: TaskStateDataMerge;
30
30
  }
31
31
  /**
32
32
  * Creates a context layer that persists task checkpoints, files, and arbitrary data across iterations.
@@ -36,8 +36,8 @@ export interface DurableTaskStateOptions {
36
36
  * (newest 50 kept) and `recall` trims its render to the allocated budget.
37
37
  *
38
38
  * The layer is writable from the model: `provides` exposes
39
- * `durable-task-state/recordArtifact` (append a produced/modified file path)
40
- * and `durable-task-state/setTaskData` (record a structured result under a
39
+ * `task-state/recordArtifact` (append a produced/modified file path)
40
+ * and `task-state/setTaskData` (record a structured result under a
41
41
  * key). Both survive the spawn boundary — a worker's artifacts merge back into
42
42
  * its coordinator via `onReturn`.
43
43
  *
@@ -45,8 +45,8 @@ export interface DurableTaskStateOptions {
45
45
  * @param opts - Layer options; `mergeData` selects the `data` merge strategy at a child boundary.
46
46
  * @returns A `ContextLayer` scoped to the thread with durable task state.
47
47
  */
48
- export declare function durableTaskState(opts?: DurableTaskStateOptions): {
49
- id: 'durable-task-state';
48
+ export declare function taskState(opts?: TaskStateOptions): {
49
+ id: 'task-state';
50
50
  name: string;
51
51
  slot: number;
52
52
  scope: "thread";
@@ -60,28 +60,28 @@ export declare function durableTaskState(opts?: DurableTaskStateOptions): {
60
60
  provides: {
61
61
  recordArtifact: import("@noetic-tools/types").LayerFunctionDecl<{
62
62
  path: string;
63
- }, string, DurableTaskState>;
63
+ }, string, TaskState>;
64
64
  setTaskData: import("@noetic-tools/types").LayerFunctionDecl<{
65
65
  key: string;
66
66
  value: unknown;
67
- }, string, DurableTaskState>;
67
+ }, string, TaskState>;
68
68
  };
69
69
  hooks: {
70
70
  init({ storage }: import("@noetic-tools/types").InitParams): Promise<{
71
- state: DurableTaskState;
71
+ state: TaskState;
72
72
  }>;
73
- recall({ state, budget }: import("@noetic-tools/types").RecallParams<DurableTaskState>): Promise<{
73
+ recall({ state, budget }: import("@noetic-tools/types").RecallParams<TaskState>): Promise<{
74
74
  items: import("@noetic-tools/types").InputMessageItem[];
75
75
  tokenCount: number;
76
76
  } | null>;
77
- store({ state, ctx }: import("@noetic-tools/types").StoreParams<DurableTaskState>): Promise<{
78
- state: DurableTaskState;
77
+ store({ state, ctx }: import("@noetic-tools/types").StoreParams<TaskState>): Promise<{
78
+ state: TaskState;
79
79
  }>;
80
- onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<DurableTaskState>): Promise<{
81
- childState: DurableTaskState;
80
+ onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<TaskState>): Promise<{
81
+ childState: TaskState;
82
82
  items: never[];
83
83
  }>;
84
- onReturn({ childState, parentState, childCtx }: import("@noetic-tools/types").ReturnParams<DurableTaskState>): Promise<{
84
+ onReturn({ childState, parentState, childCtx }: import("@noetic-tools/types").ReturnParams<TaskState>): Promise<{
85
85
  parentState: {
86
86
  checkpoints: {
87
87
  timestamp: number;
@@ -91,7 +91,7 @@ export declare function durableTaskState(opts?: DurableTaskStateOptions): {
91
91
  data: Record<string, unknown>;
92
92
  };
93
93
  }>;
94
- onComplete({ state, outcome, ctx }: import("@noetic-tools/types").CompleteParams<DurableTaskState>): Promise<{
94
+ onComplete({ state, outcome, ctx }: import("@noetic-tools/types").CompleteParams<TaskState>): Promise<{
95
95
  state: {
96
96
  files: string[];
97
97
  data: {
@@ -1,6 +1,6 @@
1
1
  import { createMessage, estimateTokens, Slot } from '@noetic-tools/types';
2
2
  import { z } from 'zod';
3
- import { layerFn } from '../layer-provides.js';
3
+ import { layerFunction } from '../layer-provides.js';
4
4
  /**
5
5
  * Hard cap on retained checkpoints. store() appends one per model call and
6
6
  * onReturn concatenates the child's list, so without a cap the thread-scoped
@@ -37,8 +37,8 @@ function mergeChildData({ parent, childState, childExecutionId, mergeData, }) {
37
37
  * (newest 50 kept) and `recall` trims its render to the allocated budget.
38
38
  *
39
39
  * The layer is writable from the model: `provides` exposes
40
- * `durable-task-state/recordArtifact` (append a produced/modified file path)
41
- * and `durable-task-state/setTaskData` (record a structured result under a
40
+ * `task-state/recordArtifact` (append a produced/modified file path)
41
+ * and `task-state/setTaskData` (record a structured result under a
42
42
  * key). Both survive the spawn boundary — a worker's artifacts merge back into
43
43
  * its coordinator via `onReturn`.
44
44
  *
@@ -46,11 +46,11 @@ function mergeChildData({ parent, childState, childExecutionId, mergeData, }) {
46
46
  * @param opts - Layer options; `mergeData` selects the `data` merge strategy at a child boundary.
47
47
  * @returns A `ContextLayer` scoped to the thread with durable task state.
48
48
  */
49
- export function durableTaskState(opts = {}) {
49
+ export function taskState(opts = {}) {
50
50
  const mergeData = opts.mergeData ?? 'shallow';
51
51
  return {
52
- id: 'durable-task-state',
53
- name: 'Durable Task State',
52
+ id: 'task-state',
53
+ name: 'Task State',
54
54
  slot: Slot.WORKING_MEMORY + 10, // 110
55
55
  // 'thread' (not 'execution'): the layer's purpose is to persist task state
56
56
  // ACROSS executions/iterations within a thread. 'execution' scope rotates
@@ -65,7 +65,7 @@ export function durableTaskState(opts = {}) {
65
65
  store: 30_000,
66
66
  },
67
67
  provides: {
68
- recordArtifact: layerFn({
68
+ recordArtifact: layerFunction({
69
69
  description: 'Record a file this task produced or modified. Recorded paths survive the task boundary and merge back into the parent task.',
70
70
  input: z.object({
71
71
  path: z.string().min(1),
@@ -90,7 +90,7 @@ export function durableTaskState(opts = {}) {
90
90
  };
91
91
  },
92
92
  }),
93
- setTaskData: layerFn({
93
+ setTaskData: layerFunction({
94
94
  description: 'Record a structured result for this task under a key (e.g. a PR url, a verdict, a summary). Values survive the task boundary and merge back into the parent task.',
95
95
  input: z.object({
96
96
  key: z.string().min(1),
@@ -136,7 +136,7 @@ export function durableTaskState(opts = {}) {
136
136
  }
137
137
  let view = state;
138
138
  let text = renderTaskState(view);
139
- // `budget > 0` is the fail-open convention (see staticContent): a zero
139
+ // `budget > 0` is the fail-open convention (see instructions): a zero
140
140
  // allocation must not delete the task state from the view.
141
141
  if (budget > 0) {
142
142
  // Halve the OLDEST checkpoints while the render exceeds the budget —
@@ -23,7 +23,7 @@ export interface TemporalSearchResult {
23
23
  }
24
24
  /**
25
25
  * Extracts timestamped facts from a batch of conversation text. Host-injected so
26
- * the layer stays LLM-agnostic and tree-shakable (mirrors `observationalContext`'s
26
+ * the layer stays LLM-agnostic and tree-shakable (mirrors `observations`'s
27
27
  * `observer`). When omitted, the layer only buffers — it never fabricates facts.
28
28
  * @public
29
29
  */
@@ -42,10 +42,10 @@ export type FactSearcher = (input: {
42
42
  now: string;
43
43
  }) => Promise<TemporalSearchResult>;
44
44
  /**
45
- * Configuration for {@link temporalContext}.
45
+ * Configuration for {@link temporal}.
46
46
  * @public
47
47
  */
48
- export interface TemporalContextConfig {
48
+ export interface TemporalConfig {
49
49
  /** Returns "now" for date grounding/extraction. Defaults to the system clock. */
50
50
  now?: () => Date;
51
51
  /** Context scope. Defaults to `'resource'` (long-term, cross-session). */
@@ -78,7 +78,7 @@ interface TemporalState {
78
78
  * returns matching facts and, when applicable, a resolved (possibly fuzzy) date.
79
79
  *
80
80
  * The layer is LLM-agnostic: the host injects `extract` and `search` callbacks
81
- * (mirrors `observationalContext`'s `observer`), keeping `context/` tree-shakable.
81
+ * (mirrors `observations`'s `observer`), keeping `context/` tree-shakable.
82
82
  * Without them, the layer still buffers text and the search tool returns the raw
83
83
  * ledger — it never fabricates facts. A `<current_datetime>` grounding block is
84
84
  * injected on recall by default to anchor relative-time reasoning.
@@ -87,5 +87,5 @@ interface TemporalState {
87
87
  * @param config - Clock, scope, injected LLM callbacks, and tuning knobs.
88
88
  * @returns A `ContextLayer` with a fact-ledger store + `searchMemory` tool.
89
89
  */
90
- export declare function temporalContext(config?: TemporalContextConfig): ContextLayer<TemporalState>;
90
+ export declare function temporal(config?: TemporalConfig): ContextLayer<TemporalState>;
91
91
  export {};
@@ -1,6 +1,6 @@
1
1
  import { collectInputText, collectOutputText, createMessage, estimateTokens, Slot, } from '@noetic-tools/types';
2
2
  import { z } from 'zod';
3
- import { layerFn } from '../layer-provides.js';
3
+ import { layerFunction } from '../layer-provides.js';
4
4
  function emptyState() {
5
5
  return {
6
6
  facts: {},
@@ -188,7 +188,7 @@ async function accumulate(s, texts, cfg) {
188
188
  * returns matching facts and, when applicable, a resolved (possibly fuzzy) date.
189
189
  *
190
190
  * The layer is LLM-agnostic: the host injects `extract` and `search` callbacks
191
- * (mirrors `observationalContext`'s `observer`), keeping `context/` tree-shakable.
191
+ * (mirrors `observations`'s `observer`), keeping `context/` tree-shakable.
192
192
  * Without them, the layer still buffers text and the search tool returns the raw
193
193
  * ledger — it never fabricates facts. A `<current_datetime>` grounding block is
194
194
  * injected on recall by default to anchor relative-time reasoning.
@@ -197,7 +197,7 @@ async function accumulate(s, texts, cfg) {
197
197
  * @param config - Clock, scope, injected LLM callbacks, and tuning knobs.
198
198
  * @returns A `ContextLayer` with a fact-ledger store + `searchMemory` tool.
199
199
  */
200
- export function temporalContext(config) {
200
+ export function temporal(config) {
201
201
  const now = config?.now ?? (() => new Date());
202
202
  const threshold = config?.bufferThreshold ?? DEFAULT_BUFFER_THRESHOLD_TOKENS;
203
203
  const maxFacts = config?.maxFacts ?? DEFAULT_MAX_FACTS;
@@ -207,10 +207,16 @@ export function temporalContext(config) {
207
207
  const search = config?.search;
208
208
  return {
209
209
  id: 'temporal',
210
- name: 'Temporal Context',
210
+ name: 'Temporal',
211
211
  // Grounding sits near the top of the window, before reasoning content.
212
212
  slot: Slot.REMINDER,
213
213
  scope: config?.scope ?? 'resource',
214
+ // The `<current_datetime>` block changes every turn by construction, so
215
+ // anchoring it would re-bill the whole window each time. Churn telemetry
216
+ // would reach the same verdict — this just skips the learning cost. With
217
+ // grounding off, only the slow-moving fact ledger remains, which is worth
218
+ // anchoring, so leave that to `'auto'`.
219
+ placement: groundDateTime ? 'live' : 'auto',
214
220
  budget: {
215
221
  min: 0,
216
222
  max: injectLedger ? 800 : 200,
@@ -222,7 +228,7 @@ export function temporalContext(config) {
222
228
  onItemAppend: 60_000,
223
229
  },
224
230
  provides: {
225
- searchMemory: layerFn({
231
+ searchMemory: layerFunction({
226
232
  description: 'Search remembered facts about the user by topic or fact, and resolve relative time. Returns matching facts and, when the query implies a time, a resolved date (which may be approximate).',
227
233
  input: z.object({
228
234
  query: z.string(),
@@ -1,5 +1,5 @@
1
1
  import type { ContextLayer, Tool } from '@noetic-tools/types';
2
- interface ToolContextLayerOpts {
2
+ interface ToolCallsOptions {
3
3
  slot?: number;
4
4
  }
5
5
  /**
@@ -11,5 +11,5 @@ interface ToolContextLayerOpts {
11
11
  * @param opts - Optional slot override for the generated layers.
12
12
  * @returns An array of `ContextLayer` instances, one per unique tool context id.
13
13
  */
14
- export declare function toolContextLayer(tools: ReadonlyArray<Tool>, opts?: ToolContextLayerOpts): ContextLayer[];
14
+ export declare function toolCalls(tools: ReadonlyArray<Tool>, opts?: ToolCallsOptions): ContextLayer[];
15
15
  export {};
@@ -9,7 +9,7 @@ const EXECUTION_SCOPE = 'execution';
9
9
  * @param opts - Optional slot override for the generated layers.
10
10
  * @returns An array of `ContextLayer` instances, one per unique tool context id.
11
11
  */
12
- export function toolContextLayer(tools, opts) {
12
+ export function toolCalls(tools, opts) {
13
13
  const seen = new Map();
14
14
  for (const t of tools) {
15
15
  if (!t.context) {
@@ -1,19 +1,38 @@
1
1
  import type { Item, ProjectionPolicy } from '@noetic-tools/types';
2
2
  interface AssembleViewParams {
3
3
  systemPromptItems: Item[];
4
+ /** Anchor band — layer output rendered before history, where a prompt cache can hold it. */
4
5
  layerOutputItems: Item[];
5
6
  historyItems: Item[];
7
+ /** Live band — layer output rendered after history, slot-ascending. */
8
+ liveLayerItems?: Item[];
9
+ /** Supersedes for anchored layers whose pinned output went stale. */
10
+ deltaItems?: Item[];
11
+ /** Items that must land last, after everything else. Never dropped. */
12
+ tailItems?: Item[];
6
13
  policy?: ProjectionPolicy;
7
14
  }
8
15
  /**
9
- * Assemble the model's context window from system prompt items, context-layer
10
- * recall output (slot-ascending), and conversation history.
16
+ * Assemble the model's context window in bands:
11
17
  *
12
- * Without a `policy` the inputs are concatenated as-is (optionally sliding the
13
- * history window by `windowSize`). With a `policy` the assembled view is held to
14
- * a hard token budget: system items are always kept; layer output is kept
15
- * low-slot-first (highest-slot dropped when tight); history takes the remainder,
16
- * keeping the most recent turns.
18
+ * ```
19
+ * system | anchor layers | history | live layers | supersedes | tail
20
+ * ```
21
+ *
22
+ * The split exists for the prompt cache, which matches on a prefix. Stable
23
+ * layer output sits ahead of history where it can be cached; volatile output
24
+ * sits after it, where re-rendering costs almost nothing. Both bands arrive
25
+ * slot-ascending.
26
+ *
27
+ * Without a `policy` the bands are concatenated as-is (optionally sliding the
28
+ * history window by `windowSize`). With a `policy` the view is held to a hard
29
+ * token budget, claimed in this order: system items, then anchor output, then
30
+ * live output, then supersedes, then the tail — with history taking whatever
31
+ * remains and keeping the most recent turns.
32
+ *
33
+ * Supersedes are claimed ahead of history, and all together or not at all: a
34
+ * dropped supersede would leave the model reading a pinned block the runtime
35
+ * knows is stale. The caller re-anchors instead — see `prepareBandedView`.
17
36
  */
18
- export declare function assembleView({ systemPromptItems, layerOutputItems, historyItems, policy, }: AssembleViewParams): Item[];
37
+ export declare function assembleView({ systemPromptItems, layerOutputItems, historyItems, liveLayerItems, deltaItems, tailItems, policy, }: AssembleViewParams): Item[];
19
38
  export {};
@@ -57,33 +57,62 @@ function keepRecentWithinBudget(items, budget, windowSize) {
57
57
  //#endregion
58
58
  //#region Public API
59
59
  /**
60
- * Assemble the model's context window from system prompt items, context-layer
61
- * recall output (slot-ascending), and conversation history.
60
+ * Assemble the model's context window in bands:
62
61
  *
63
- * Without a `policy` the inputs are concatenated as-is (optionally sliding the
64
- * history window by `windowSize`). With a `policy` the assembled view is held to
65
- * a hard token budget: system items are always kept; layer output is kept
66
- * low-slot-first (highest-slot dropped when tight); history takes the remainder,
67
- * keeping the most recent turns.
62
+ * ```
63
+ * system | anchor layers | history | live layers | supersedes | tail
64
+ * ```
65
+ *
66
+ * The split exists for the prompt cache, which matches on a prefix. Stable
67
+ * layer output sits ahead of history where it can be cached; volatile output
68
+ * sits after it, where re-rendering costs almost nothing. Both bands arrive
69
+ * slot-ascending.
70
+ *
71
+ * Without a `policy` the bands are concatenated as-is (optionally sliding the
72
+ * history window by `windowSize`). With a `policy` the view is held to a hard
73
+ * token budget, claimed in this order: system items, then anchor output, then
74
+ * live output, then supersedes, then the tail — with history taking whatever
75
+ * remains and keeping the most recent turns.
76
+ *
77
+ * Supersedes are claimed ahead of history, and all together or not at all: a
78
+ * dropped supersede would leave the model reading a pinned block the runtime
79
+ * knows is stale. The caller re-anchors instead — see `prepareBandedView`.
68
80
  */
69
- export function assembleView({ systemPromptItems, layerOutputItems, historyItems, policy, }) {
81
+ export function assembleView({ systemPromptItems, layerOutputItems, historyItems, liveLayerItems = [], deltaItems = [], tailItems = [], policy, }) {
70
82
  if (!policy) {
71
83
  return [
72
84
  ...systemPromptItems,
73
85
  ...layerOutputItems,
74
86
  ...historyItems,
87
+ ...liveLayerItems,
88
+ ...deltaItems,
89
+ ...tailItems,
75
90
  ];
76
91
  }
77
92
  const budget = Math.max(0, policy.tokenBudget - policy.responseReserve);
78
93
  // System items are never dropped — they anchor the conversation.
79
- const afterSystem = Math.max(0, budget - totalTokens(systemPromptItems));
80
- const keptLayers = keepFrontWithinBudget(layerOutputItems, afterSystem);
81
- const afterLayers = Math.max(0, afterSystem - totalTokens(keptLayers));
82
- const keptHistory = keepRecentWithinBudget(historyItems, afterLayers, policy.windowSize);
94
+ let left = Math.max(0, budget - totalTokens(systemPromptItems));
95
+ const keptAnchor = keepFrontWithinBudget(layerOutputItems, left);
96
+ left = Math.max(0, left - totalTokens(keptAnchor));
97
+ const keptLive = keepFrontWithinBudget(liveLayerItems, left);
98
+ left = Math.max(0, left - totalTokens(keptLive));
99
+ // The tail carries steering guidance, which must reach the model whatever the
100
+ // budget says — it is the correction the retry exists to deliver.
101
+ left = Math.max(0, left - totalTokens(tailItems));
102
+ // Supersedes are never dropped either. Each one corrects a pinned block that
103
+ // is already in the view, so dropping one leaves the model reading content
104
+ // the runtime knows is stale — silent corruption, and far worse than losing
105
+ // a turn of history. History absorbs the cost; `deltaBudgetFraction` keeps
106
+ // the supersedes from growing large enough for that to hurt.
107
+ left = Math.max(0, left - totalTokens(deltaItems));
108
+ const keptHistory = keepRecentWithinBudget(historyItems, left, policy.windowSize);
83
109
  return [
84
110
  ...systemPromptItems,
85
- ...keptLayers,
111
+ ...keptAnchor,
86
112
  ...keptHistory,
113
+ ...keptLive,
114
+ ...deltaItems,
115
+ ...tailItems,
87
116
  ];
88
117
  }
89
118
  //#endregion
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Mirrors Claude Code's `filterUnresolvedToolUses`. Used by the CLI's
8
8
  * session-resume path to drop dangling pairs left by a crash, and by the
9
- * `historyWindow` context layer to clean orphans at the slice boundary.
9
+ * `history` context layer to clean orphans at the slice boundary.
10
10
  */
11
11
  import type { Item } from '@noetic-tools/types';
12
12
  /** @public */
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Mirrors Claude Code's `filterUnresolvedToolUses`. Used by the CLI's
8
8
  * session-resume path to drop dangling pairs left by a crash, and by the
9
- * `historyWindow` context layer to clean orphans at the slice boundary.
9
+ * `history` context layer to clean orphans at the slice boundary.
10
10
  */
11
11
  /** @public */
12
12
  export function stripUnresolvedToolCalls(items) {
package/dist/index.d.ts CHANGED
@@ -6,24 +6,24 @@
6
6
  export type { ContextScope, ExecutionContext, ExecutionOutcome, LayerCallModelRequest, } from '@noetic-tools/types';
7
7
  export * from '@noetic-tools/types/contract';
8
8
  export * from './context/budget.js';
9
+ export * from './context/cache-anchoring.js';
9
10
  export * from './context/exec-context-factory.js';
10
11
  export * from './context/function-call-utils.js';
11
12
  export * from './context/layer-api.js';
12
13
  export * from './context/layer-lifecycle.js';
13
14
  export * from './context/layer-provides.js';
14
15
  export * from './context/layer-usage.js';
15
- export * from './context/layers/durable-task-state.js';
16
- export * from './context/layers/file-reference.js';
17
- export * from './context/layers/history-window.js';
18
- export * from './context/layers/observational-context.js';
19
- export * from './context/layers/plan-context.js';
20
- export * from './context/layers/static-content.js';
16
+ export * from './context/layers/filesystem.js';
17
+ export * from './context/layers/history.js';
18
+ export * from './context/layers/instructions.js';
19
+ export * from './context/layers/observations.js';
20
+ export * from './context/layers/plan.js';
21
+ export * from './context/layers/scratchpad.js';
21
22
  export * from './context/layers/steering.js';
22
- export * from './context/layers/temporal-context.js';
23
- export * from './context/layers/tool-context-layer.js';
24
- export * from './context/layers/working-memory-context.js';
23
+ export * from './context/layers/task-state.js';
24
+ export * from './context/layers/temporal.js';
25
+ export * from './context/layers/tool-calls.js';
25
26
  export * from './context/projector.js';
26
27
  export * from './context/scope.js';
27
28
  export * from './context/storage-batch.js';
28
29
  export * from './context/strip-unresolved.js';
29
- export * from './deprecated.js';
package/dist/index.js CHANGED
@@ -8,24 +8,24 @@
8
8
  // @noetic-tools/context remains the one-stop import for context-layer authoring.
9
9
  export * from '@noetic-tools/types/contract';
10
10
  export * from './context/budget.js';
11
+ export * from './context/cache-anchoring.js';
11
12
  export * from './context/exec-context-factory.js';
12
13
  export * from './context/function-call-utils.js';
13
14
  export * from './context/layer-api.js';
14
15
  export * from './context/layer-lifecycle.js';
15
16
  export * from './context/layer-provides.js';
16
17
  export * from './context/layer-usage.js';
17
- export * from './context/layers/durable-task-state.js';
18
- export * from './context/layers/file-reference.js';
19
- export * from './context/layers/history-window.js';
20
- export * from './context/layers/observational-context.js';
21
- export * from './context/layers/plan-context.js';
22
- export * from './context/layers/static-content.js';
18
+ export * from './context/layers/filesystem.js';
19
+ export * from './context/layers/history.js';
20
+ export * from './context/layers/instructions.js';
21
+ export * from './context/layers/observations.js';
22
+ export * from './context/layers/plan.js';
23
+ export * from './context/layers/scratchpad.js';
23
24
  export * from './context/layers/steering.js';
24
- export * from './context/layers/temporal-context.js';
25
- export * from './context/layers/tool-context-layer.js';
26
- export * from './context/layers/working-memory-context.js';
25
+ export * from './context/layers/task-state.js';
26
+ export * from './context/layers/temporal.js';
27
+ export * from './context/layers/tool-calls.js';
27
28
  export * from './context/projector.js';
28
29
  export * from './context/scope.js';
29
30
  export * from './context/storage-batch.js';
30
31
  export * from './context/strip-unresolved.js';
31
- export * from './deprecated.js';