@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.
- package/README.md +2 -3
- package/dist/context/cache-anchoring.d.ts +103 -0
- package/dist/context/cache-anchoring.js +273 -0
- package/dist/context/layer-lifecycle.d.ts +3 -5
- package/dist/context/layer-lifecycle.js +12 -2
- package/dist/context/layer-provides.d.ts +1 -1
- package/dist/context/layer-provides.js +1 -1
- package/dist/context/layer-usage.d.ts +18 -3
- package/dist/context/layer-usage.js +10 -1
- package/dist/context/layers/{file-reference.d.ts → filesystem.d.ts} +1 -1
- package/dist/context/layers/{file-reference.js → filesystem.js} +60 -3
- package/dist/context/layers/{history-window.d.ts → history.d.ts} +4 -4
- package/dist/context/layers/{history-window.js → history.js} +6 -6
- package/dist/context/layers/{static-content.d.ts → instructions.d.ts} +3 -2
- package/dist/context/layers/{static-content.js → instructions.js} +5 -2
- package/dist/context/layers/{observational-context.d.ts → observations.d.ts} +12 -12
- package/dist/context/layers/{observational-context.js → observations.js} +7 -7
- package/dist/context/layers/plan-prompts.d.ts +1 -1
- package/dist/context/layers/plan-prompts.js +16 -16
- package/dist/context/layers/plan-state.d.ts +31 -0
- package/dist/context/layers/plan-state.js +12 -0
- package/dist/context/layers/{plan-context.d.ts → plan.d.ts} +6 -28
- package/dist/context/layers/plan.js +697 -0
- package/dist/context/layers/{working-memory-context.d.ts → scratchpad.d.ts} +13 -13
- package/dist/context/layers/{working-memory-context.js → scratchpad.js} +8 -8
- package/dist/context/layers/steering.d.ts +1 -0
- package/dist/context/layers/steering.js +6 -1
- package/dist/context/layers/{durable-task-state.d.ts → task-state.d.ts} +20 -20
- package/dist/context/layers/{durable-task-state.js → task-state.js} +9 -9
- package/dist/context/layers/{temporal-context.d.ts → temporal.d.ts} +5 -5
- package/dist/context/layers/{temporal-context.js → temporal.js} +11 -5
- package/dist/context/layers/{tool-context-layer.d.ts → tool-calls.d.ts} +2 -2
- package/dist/context/layers/{tool-context-layer.js → tool-calls.js} +1 -1
- package/dist/context/projector.d.ts +27 -8
- package/dist/context/projector.js +42 -13
- package/dist/context/strip-unresolved.d.ts +1 -1
- package/dist/context/strip-unresolved.js +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +10 -10
- package/package.json +2 -2
- package/dist/context/layers/plan-context.js +0 -683
- package/dist/deprecated.d.ts +0 -43
- package/dist/deprecated.js +0 -28
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { ZodType } from 'zod';
|
|
2
|
-
export type
|
|
3
|
-
export interface
|
|
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
|
|
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
|
|
17
|
-
id: '
|
|
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<
|
|
27
|
-
update: import("@noetic-tools/types").LayerFunctionDecl<Record<string, unknown>, void,
|
|
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:
|
|
33
|
+
state: ScratchpadState;
|
|
34
34
|
}>;
|
|
35
|
-
recall({ state }: import("@noetic-tools/types").RecallParams<
|
|
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<
|
|
40
|
-
state:
|
|
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<
|
|
43
|
-
childState:
|
|
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,
|
|
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
|
|
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(`
|
|
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
|
|
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
|
|
75
|
+
export function scratchpad(config) {
|
|
76
76
|
const scope = config?.scope ?? 'thread';
|
|
77
77
|
return {
|
|
78
|
-
id: '
|
|
79
|
-
name: '
|
|
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:
|
|
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(),
|
|
@@ -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 `
|
|
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
|
|
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/
|
|
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
|
|
22
|
-
/** @public Options for {@link
|
|
23
|
-
export interface
|
|
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?:
|
|
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
|
-
* `
|
|
40
|
-
* and `
|
|
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
|
|
49
|
-
id: '
|
|
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,
|
|
63
|
+
}, string, TaskState>;
|
|
64
64
|
setTaskData: import("@noetic-tools/types").LayerFunctionDecl<{
|
|
65
65
|
key: string;
|
|
66
66
|
value: unknown;
|
|
67
|
-
}, string,
|
|
67
|
+
}, string, TaskState>;
|
|
68
68
|
};
|
|
69
69
|
hooks: {
|
|
70
70
|
init({ storage }: import("@noetic-tools/types").InitParams): Promise<{
|
|
71
|
-
state:
|
|
71
|
+
state: TaskState;
|
|
72
72
|
}>;
|
|
73
|
-
recall({ state, budget }: import("@noetic-tools/types").RecallParams<
|
|
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<
|
|
78
|
-
state:
|
|
77
|
+
store({ state, ctx }: import("@noetic-tools/types").StoreParams<TaskState>): Promise<{
|
|
78
|
+
state: TaskState;
|
|
79
79
|
}>;
|
|
80
|
-
onSpawn({ parentState }: import("@noetic-tools/types").SpawnParams<
|
|
81
|
-
childState:
|
|
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<
|
|
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<
|
|
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 {
|
|
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
|
-
* `
|
|
41
|
-
* and `
|
|
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
|
|
49
|
+
export function taskState(opts = {}) {
|
|
50
50
|
const mergeData = opts.mergeData ?? 'shallow';
|
|
51
51
|
return {
|
|
52
|
-
id: '
|
|
53
|
-
name: '
|
|
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:
|
|
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:
|
|
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
|
|
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 `
|
|
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
|
|
45
|
+
* Configuration for {@link temporal}.
|
|
46
46
|
* @public
|
|
47
47
|
*/
|
|
48
|
-
export interface
|
|
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 `
|
|
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
|
|
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 {
|
|
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 `
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
10
|
-
* recall output (slot-ascending), and conversation history.
|
|
16
|
+
* Assemble the model's context window in bands:
|
|
11
17
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
61
|
-
* recall output (slot-ascending), and conversation history.
|
|
60
|
+
* Assemble the model's context window in bands:
|
|
62
61
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
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
|
-
...
|
|
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
|
-
* `
|
|
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
|
-
* `
|
|
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/
|
|
16
|
-
export * from './context/layers/
|
|
17
|
-
export * from './context/layers/
|
|
18
|
-
export * from './context/layers/
|
|
19
|
-
export * from './context/layers/plan
|
|
20
|
-
export * from './context/layers/
|
|
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/
|
|
23
|
-
export * from './context/layers/
|
|
24
|
-
export * from './context/layers/
|
|
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/
|
|
18
|
-
export * from './context/layers/
|
|
19
|
-
export * from './context/layers/
|
|
20
|
-
export * from './context/layers/
|
|
21
|
-
export * from './context/layers/plan
|
|
22
|
-
export * from './context/layers/
|
|
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/
|
|
25
|
-
export * from './context/layers/
|
|
26
|
-
export * from './context/layers/
|
|
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';
|