@noetic-tools/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/types.d.ts +2 -2
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +1 -1
- package/dist/adapters/openrouter.d.ts +2 -6
- package/dist/adapters/openrouter.js +1 -4
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +2 -3
- package/dist/builders/control-flow-builders.js +1 -2
- package/dist/builders/every.d.ts +2 -3
- package/dist/builders/every.js +1 -1
- package/dist/builders/layer-provides-builders.d.ts +1 -1
- package/dist/builders/layer-provides-builders.js +1 -1
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +1 -1
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +1 -1
- package/dist/builders/spawn-builder.d.ts +2 -3
- package/dist/builders/spawn-builder.js +1 -1
- package/dist/builders/step-builders.d.ts +2 -6
- package/dist/builders/step-builders.js +1 -1
- package/dist/builders/tool-builder.d.ts +1 -3
- package/dist/builders/tool-builder.js +1 -1
- package/dist/builders/workflow-hydrator.d.ts +2 -3
- package/dist/builders/workflow-hydrator.js +1 -2
- package/dist/conditions/conditions.d.ts +2 -3
- package/dist/conditions/conditions.js +1 -1
- package/dist/harness/agent-harness.d.ts +13 -3
- package/dist/harness/agent-harness.js +48 -5
- package/dist/harness/deps/memory.d.ts +2 -5
- package/dist/harness/deps/memory.js +1 -4
- package/dist/harness/deps/types.d.ts +3 -16
- package/dist/harness/deps/types.js +1 -1
- package/dist/harness/model-call.d.ts +1 -3
- package/dist/harness/model-call.js +1 -2
- package/dist/harness/model-schema.d.ts +2 -4
- package/dist/harness/model-schema.js +1 -1
- package/dist/index.d.ts +28 -49
- package/dist/index.js +9 -27
- package/dist/interpreter/action-deps.d.ts +4 -8
- package/dist/interpreter/action-deps.js +2 -6
- package/dist/interpreter/action-types.d.ts +3 -8
- package/dist/interpreter/action-types.js +1 -1
- package/dist/interpreter/collect-tools.d.ts +1 -2
- package/dist/interpreter/collect-tools.js +1 -1
- package/dist/interpreter/execute-action.js +104 -26
- package/dist/interpreter/execute-control.d.ts +1 -2
- package/dist/interpreter/execute-control.js +1 -3
- package/dist/interpreter/execute.d.ts +2 -3
- package/dist/interpreter/execute.js +1 -2
- package/dist/interpreter/message-helpers.d.ts +1 -1
- package/dist/interpreter/message-helpers.js +1 -1
- package/dist/interpreter/step-dispatcher.d.ts +2 -5
- package/dist/interpreter/typeguards.d.ts +3 -4
- package/dist/interpreter/typeguards.js +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +1 -1
- package/dist/patterns/dynamic-workflow.d.ts +2 -5
- package/dist/patterns/dynamic-workflow.js +1 -2
- package/dist/patterns/flow.d.ts +2 -2
- package/dist/patterns/flow.js +1 -1
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/interview.js +1 -1
- package/dist/patterns/plans.d.ts +2 -3
- package/dist/patterns/plans.js +1 -1
- package/dist/patterns/ralph-wiggum.d.ts +2 -3
- package/dist/patterns/react.d.ts +2 -3
- package/dist/portable/memory-layers.d.ts +1 -7
- package/dist/portable/memory-layers.js +1 -7
- package/dist/portable.d.ts +2 -2
- package/dist/portable.js +2 -2
- package/dist/runtime/broadcaster-utils.d.ts +1 -1
- package/dist/runtime/channel-store.d.ts +1 -1
- package/dist/runtime/channel-store.js +1 -2
- package/dist/runtime/context-deps.d.ts +3 -3
- package/dist/runtime/context-deps.js +2 -2
- package/dist/runtime/context-type-deps.d.ts +2 -11
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +2 -3
- package/dist/runtime/detached-handle.js +1 -3
- package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
- package/dist/runtime/durable/checkpoint-store.js +1 -1
- package/dist/runtime/durable/harness-checkpoints.d.ts +2 -5
- package/dist/runtime/durable/runnable-loop.d.ts +1 -3
- package/dist/runtime/durable/session-seed.d.ts +1 -1
- package/dist/runtime/durable/stall-nudge.d.ts +1 -1
- package/dist/runtime/event-broadcaster.d.ts +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +1 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +1 -2
- package/dist/runtime/session-runner.d.ts +1 -4
- package/dist/runtime/session-streams.d.ts +1 -2
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +2 -2
- package/dist/runtime/step-registry.js +1 -2
- package/dist/runtime/tool-memory.d.ts +1 -3
- package/dist/types/mutable-context.d.ts +2 -3
- package/dist/types/step-registrar.d.ts +1 -1
- package/dist/unstable.d.ts +5 -9
- package/dist/unstable.js +3 -6
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +2 -2
- package/package.json +6 -4
- package/dist/errors/noetic-config-error.d.ts +0 -41
- package/dist/errors/noetic-config-error.js +0 -44
- package/dist/errors/noetic-error.d.ts +0 -18
- package/dist/errors/noetic-error.js +0 -54
- package/dist/memory/budget.d.ts +0 -23
- package/dist/memory/budget.js +0 -117
- package/dist/memory/exec-context-factory.d.ts +0 -25
- package/dist/memory/exec-context-factory.js +0 -30
- package/dist/memory/flow-schema.d.ts +0 -52
- package/dist/memory/flow-schema.js +0 -102
- package/dist/memory/function-call-utils.d.ts +0 -6
- package/dist/memory/function-call-utils.js +0 -27
- package/dist/memory/layer-api.d.ts +0 -14
- package/dist/memory/layer-api.js +0 -91
- package/dist/memory/layer-lifecycle.d.ts +0 -160
- package/dist/memory/layer-lifecycle.js +0 -552
- package/dist/memory/layer-provides.d.ts +0 -32
- package/dist/memory/layer-provides.js +0 -33
- package/dist/memory/layer-usage.d.ts +0 -16
- package/dist/memory/layer-usage.js +0 -54
- package/dist/memory/layers/durable-task-state.d.ts +0 -79
- package/dist/memory/layers/durable-task-state.js +0 -118
- package/dist/memory/layers/file-reference.d.ts +0 -55
- package/dist/memory/layers/file-reference.js +0 -594
- package/dist/memory/layers/history-window.d.ts +0 -29
- package/dist/memory/layers/history-window.js +0 -114
- package/dist/memory/layers/observational-memory.d.ts +0 -53
- package/dist/memory/layers/observational-memory.js +0 -109
- package/dist/memory/layers/plan.d.ts +0 -55
- package/dist/memory/layers/plan.js +0 -374
- package/dist/memory/layers/static-content.d.ts +0 -27
- package/dist/memory/layers/static-content.js +0 -35
- package/dist/memory/layers/steering.d.ts +0 -51
- package/dist/memory/layers/steering.js +0 -258
- package/dist/memory/layers/temporal.d.ts +0 -91
- package/dist/memory/layers/temporal.js +0 -245
- package/dist/memory/layers/tool-memory-layer.d.ts +0 -16
- package/dist/memory/layers/tool-memory-layer.js +0 -43
- package/dist/memory/layers/working-memory.d.ts +0 -44
- package/dist/memory/layers/working-memory.js +0 -92
- package/dist/memory/projector.d.ts +0 -10
- package/dist/memory/projector.js +0 -19
- package/dist/memory/scope.d.ts +0 -3
- package/dist/memory/scope.js +0 -31
- package/dist/memory/strip-unresolved.d.ts +0 -13
- package/dist/memory/strip-unresolved.js +0 -35
- package/dist/schemas/item.d.ts +0 -35
- package/dist/schemas/item.js +0 -155
- package/dist/types/channel.d.ts +0 -29
- package/dist/types/channel.js +0 -1
- package/dist/types/common.d.ts +0 -68
- package/dist/types/common.js +0 -1
- package/dist/types/context-parts/item-log.d.ts +0 -6
- package/dist/types/context-parts/item-log.js +0 -1
- package/dist/types/context-parts/layer-usage.d.ts +0 -18
- package/dist/types/context-parts/layer-usage.js +0 -1
- package/dist/types/context.d.ts +0 -202
- package/dist/types/context.js +0 -1
- package/dist/types/detached.d.ts +0 -17
- package/dist/types/detached.js +0 -7
- package/dist/types/error.d.ts +0 -66
- package/dist/types/error.js +0 -1
- package/dist/types/fs-adapter.d.ts +0 -51
- package/dist/types/fs-adapter.js +0 -3
- package/dist/types/harness-result.d.ts +0 -43
- package/dist/types/harness-result.js +0 -2
- package/dist/types/items.d.ts +0 -131
- package/dist/types/items.js +0 -2
- package/dist/types/memory-context.d.ts +0 -39
- package/dist/types/memory-context.js +0 -1
- package/dist/types/memory.d.ts +0 -306
- package/dist/types/memory.js +0 -13
- package/dist/types/observability.d.ts +0 -30
- package/dist/types/observability.js +0 -1
- package/dist/types/runtime.d.ts +0 -256
- package/dist/types/runtime.js +0 -1
- package/dist/types/shell-adapter.d.ts +0 -34
- package/dist/types/shell-adapter.js +0 -10
- package/dist/types/steering.d.ts +0 -89
- package/dist/types/steering.js +0 -14
- package/dist/types/step.d.ts +0 -231
- package/dist/types/step.js +0 -1
- package/dist/types/subprocess-adapter.d.ts +0 -157
- package/dist/types/subprocess-adapter.js +0 -3
- package/dist/types/tool-context.d.ts +0 -34
- package/dist/types/tool-context.js +0 -1
- package/dist/types/tool.d.ts +0 -64
- package/dist/types/tool.js +0 -1
- package/dist/util/framework-cast.d.ts +0 -10
- package/dist/util/framework-cast.js +0 -16
- package/dist/util/message-helpers.d.ts +0 -17
- package/dist/util/message-helpers.js +0 -64
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { NoeticConfigError } from '../../errors/noetic-config-error.js';
|
|
2
|
-
import { Slot } from '../../types/memory.js';
|
|
3
|
-
import { LedgerEntryKind, SteeringAction } from '../../types/steering.js';
|
|
4
|
-
import { createMessage, estimateTokens, extractAssistantText } from '../../util/message-helpers.js';
|
|
5
|
-
import { mostRestrictive } from '../layer-lifecycle.js';
|
|
6
|
-
//#region Constants
|
|
7
|
-
const DEFAULT_MAX_LEDGER_ENTRIES = 100;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region Rule Evaluation Helpers
|
|
10
|
-
function requireCallModel(params, ruleId) {
|
|
11
|
-
if (!params.ctx.callModel) {
|
|
12
|
-
throw new NoeticConfigError({
|
|
13
|
-
code: 'MISSING_CALL_MODEL',
|
|
14
|
-
message: `LLM-evaluated steering rule "${ruleId}" requires a callModel but none is available.`,
|
|
15
|
-
hint: 'Pass `llm: { provider: "openrouter", apiKey: "..." }` to AgentHarness or set OPENROUTER_API_KEY.',
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function evaluateProgrammaticRule(rule, params) {
|
|
20
|
-
if (!rule.predicate) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
return rule.predicate(params);
|
|
24
|
-
}
|
|
25
|
-
async function evaluateLlmRuleSync(rule, params) {
|
|
26
|
-
if (!rule.llmEval) {
|
|
27
|
-
return {
|
|
28
|
-
action: SteeringAction.Allow,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
const { callModel } = params.ctx;
|
|
32
|
-
if (!callModel) {
|
|
33
|
-
return {
|
|
34
|
-
action: SteeringAction.Allow,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
const contextSummary = 'toolName' in params
|
|
38
|
-
? `Tool: ${params.toolName}, Args: ${JSON.stringify(params.toolArgs)}`
|
|
39
|
-
: `Response items: ${params.response.items.length}`;
|
|
40
|
-
const prompt = `${rule.llmEval.prompt}\n\nContext: ${contextSummary}\n\nRespond with exactly "ALLOW", "DENY", or "GUIDE: <guidance text>".`;
|
|
41
|
-
const userMessage = createMessage(prompt, 'user');
|
|
42
|
-
const model = rule.llmEval.model ?? 'openai/gpt-4o-mini';
|
|
43
|
-
const response = await callModel({
|
|
44
|
-
model,
|
|
45
|
-
items: [
|
|
46
|
-
userMessage,
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
const text = extractAssistantText(response.items).trim().toUpperCase();
|
|
50
|
-
if (text.startsWith('DENY')) {
|
|
51
|
-
return {
|
|
52
|
-
action: SteeringAction.Deny,
|
|
53
|
-
guidance: text.slice(5).trim() || undefined,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
if (text.startsWith('GUIDE:')) {
|
|
57
|
-
return {
|
|
58
|
-
action: SteeringAction.Guide,
|
|
59
|
-
guidance: text.slice(6).trim(),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
action: SteeringAction.Allow,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function fireLlmRuleAsync({ rule, params, pendingAsync }) {
|
|
67
|
-
evaluateLlmRuleSync(rule, params)
|
|
68
|
-
.then((decision) => {
|
|
69
|
-
if (decision.action !== SteeringAction.Allow) {
|
|
70
|
-
pendingAsync.push({
|
|
71
|
-
ruleId: rule.id,
|
|
72
|
-
guidance: decision.guidance ?? `Rule ${rule.id}: ${decision.action}`,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
.catch(() => {
|
|
77
|
-
// Async LLM eval failures are silently swallowed — they must not disrupt the agent
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function trimLedger(ledger, max) {
|
|
81
|
-
if (ledger.length <= max) {
|
|
82
|
-
return ledger;
|
|
83
|
-
}
|
|
84
|
-
return ledger.slice(ledger.length - max);
|
|
85
|
-
}
|
|
86
|
-
async function evaluateRules({ rules, hookName, params, state, }) {
|
|
87
|
-
const applicable = rules.filter((r) => r.appliesTo.includes(hookName));
|
|
88
|
-
const decisions = [];
|
|
89
|
-
for (const rule of applicable) {
|
|
90
|
-
// Programmatic predicate (fast)
|
|
91
|
-
const programmatic = evaluateProgrammaticRule(rule, params);
|
|
92
|
-
if (programmatic) {
|
|
93
|
-
decisions.push(programmatic);
|
|
94
|
-
if (programmatic.action === SteeringAction.Deny) {
|
|
95
|
-
return mostRestrictive(decisions);
|
|
96
|
-
}
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
// LLM eval — throws NoeticConfigError if callModel is missing
|
|
100
|
-
if (!rule.llmEval) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
requireCallModel(params, rule.id);
|
|
104
|
-
if (rule.llmEval.mode === 'sync') {
|
|
105
|
-
const decision = await evaluateLlmRuleSync(rule, params);
|
|
106
|
-
decisions.push(decision);
|
|
107
|
-
if (decision.action === SteeringAction.Deny) {
|
|
108
|
-
return mostRestrictive(decisions);
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
// Async — fire and forget
|
|
113
|
-
fireLlmRuleAsync({
|
|
114
|
-
rule,
|
|
115
|
-
params,
|
|
116
|
-
pendingAsync: state.pendingAsync,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return mostRestrictive(decisions);
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
|
-
//#region Public API
|
|
123
|
-
/**
|
|
124
|
-
* Creates a steering memory layer that evaluates rules before tool calls and after model calls.
|
|
125
|
-
*
|
|
126
|
-
* @public
|
|
127
|
-
* @param config - Steering configuration including rules, scope, and max ledger size.
|
|
128
|
-
* @returns A `MemoryLayer` that enforces steering rules via allow/deny/guide decisions.
|
|
129
|
-
*/
|
|
130
|
-
export function steering(config) {
|
|
131
|
-
const maxLedger = config.maxLedgerEntries ?? DEFAULT_MAX_LEDGER_ENTRIES;
|
|
132
|
-
const scope = config.scope ?? 'execution';
|
|
133
|
-
return {
|
|
134
|
-
id: 'steering',
|
|
135
|
-
name: 'Steering',
|
|
136
|
-
slot: Slot.STEERING,
|
|
137
|
-
scope,
|
|
138
|
-
budget: {
|
|
139
|
-
min: 0,
|
|
140
|
-
max: 500,
|
|
141
|
-
},
|
|
142
|
-
timeouts: {
|
|
143
|
-
beforeToolCall: 5e3,
|
|
144
|
-
afterModelCall: 1e4,
|
|
145
|
-
},
|
|
146
|
-
hooks: {
|
|
147
|
-
async init({ storage }) {
|
|
148
|
-
const saved = await storage.get('state');
|
|
149
|
-
const state = saved ?? {
|
|
150
|
-
ledger: [],
|
|
151
|
-
pendingAsync: [],
|
|
152
|
-
};
|
|
153
|
-
return {
|
|
154
|
-
state,
|
|
155
|
-
};
|
|
156
|
-
},
|
|
157
|
-
async recall({ state }) {
|
|
158
|
-
if (state.pendingAsync.length === 0) {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
const feedback = state.pendingAsync.map((p) => `[${p.ruleId}] ${p.guidance}`).join('\n');
|
|
162
|
-
state.pendingAsync = [];
|
|
163
|
-
const content = `<steering_feedback>\n${feedback}\n</steering_feedback>`;
|
|
164
|
-
return {
|
|
165
|
-
items: [
|
|
166
|
-
createMessage(content, 'developer'),
|
|
167
|
-
],
|
|
168
|
-
tokenCount: estimateTokens(content),
|
|
169
|
-
state,
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
async store() {
|
|
173
|
-
// Model turn ledger entries are recorded in afterModelCall to avoid duplicates
|
|
174
|
-
return undefined;
|
|
175
|
-
},
|
|
176
|
-
async beforeToolCall({ toolName, toolArgs, ctx, state }) {
|
|
177
|
-
const decision = await evaluateRules({
|
|
178
|
-
rules: config.rules,
|
|
179
|
-
hookName: 'beforeToolCall',
|
|
180
|
-
params: {
|
|
181
|
-
toolName,
|
|
182
|
-
toolArgs,
|
|
183
|
-
ctx,
|
|
184
|
-
state,
|
|
185
|
-
},
|
|
186
|
-
state,
|
|
187
|
-
});
|
|
188
|
-
const entry = {
|
|
189
|
-
kind: LedgerEntryKind.ToolCall,
|
|
190
|
-
timestamp: Date.now(),
|
|
191
|
-
toolName,
|
|
192
|
-
toolArgs,
|
|
193
|
-
ruleId: decision.action !== SteeringAction.Allow ? 'steering' : undefined,
|
|
194
|
-
action: decision.action,
|
|
195
|
-
guidance: decision.guidance,
|
|
196
|
-
};
|
|
197
|
-
state.ledger.push(entry);
|
|
198
|
-
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
199
|
-
return {
|
|
200
|
-
decision,
|
|
201
|
-
state,
|
|
202
|
-
};
|
|
203
|
-
},
|
|
204
|
-
async afterModelCall({ response, ctx, state }) {
|
|
205
|
-
const decision = await evaluateRules({
|
|
206
|
-
rules: config.rules,
|
|
207
|
-
hookName: 'afterModelCall',
|
|
208
|
-
params: {
|
|
209
|
-
response,
|
|
210
|
-
ctx,
|
|
211
|
-
state,
|
|
212
|
-
},
|
|
213
|
-
state,
|
|
214
|
-
});
|
|
215
|
-
const entry = {
|
|
216
|
-
kind: LedgerEntryKind.ModelTurn,
|
|
217
|
-
timestamp: Date.now(),
|
|
218
|
-
tokenUsage: {
|
|
219
|
-
input: response.usage.inputTokens,
|
|
220
|
-
output: response.usage.outputTokens,
|
|
221
|
-
},
|
|
222
|
-
ruleId: decision.action !== SteeringAction.Allow ? 'steering' : undefined,
|
|
223
|
-
action: decision.action,
|
|
224
|
-
guidance: decision.guidance,
|
|
225
|
-
};
|
|
226
|
-
state.ledger.push(entry);
|
|
227
|
-
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
228
|
-
return {
|
|
229
|
-
decision,
|
|
230
|
-
state,
|
|
231
|
-
};
|
|
232
|
-
},
|
|
233
|
-
async onSpawn({ parentState }) {
|
|
234
|
-
return {
|
|
235
|
-
childState: {
|
|
236
|
-
ledger: structuredClone(parentState.ledger),
|
|
237
|
-
pendingAsync: [],
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
},
|
|
241
|
-
async onComplete({ state, outcome }) {
|
|
242
|
-
const entry = {
|
|
243
|
-
kind: LedgerEntryKind.Custom,
|
|
244
|
-
timestamp: Date.now(),
|
|
245
|
-
custom: {
|
|
246
|
-
outcome,
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
state.ledger.push(entry);
|
|
250
|
-
state.ledger = trimLedger(state.ledger, maxLedger);
|
|
251
|
-
return {
|
|
252
|
-
state,
|
|
253
|
-
};
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
//#endregion
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { MemoryLayer, MemoryScope } from '../../types/memory.js';
|
|
2
|
-
/**
|
|
3
|
-
* A single remembered fact tied to an instant.
|
|
4
|
-
* @public
|
|
5
|
-
*/
|
|
6
|
-
export interface TemporalFact {
|
|
7
|
-
/** ISO-8601 timestamp the fact is associated with (when it happened / was stated). */
|
|
8
|
-
ts: string;
|
|
9
|
-
/** The fact, stated tersely. */
|
|
10
|
-
fact: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Result of a temporal memory search.
|
|
14
|
-
* @public
|
|
15
|
-
*/
|
|
16
|
-
export interface TemporalSearchResult {
|
|
17
|
-
/** Matching facts, most relevant first. */
|
|
18
|
-
facts: string[];
|
|
19
|
-
/** A resolved date for the query, when one applies (ISO-8601 or a natural phrase). */
|
|
20
|
-
date?: string;
|
|
21
|
-
/** True when `date` is approximate ("around March", "a few weeks ago"). */
|
|
22
|
-
fuzzy?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Extracts timestamped facts from a batch of conversation text. Host-injected so
|
|
26
|
-
* the layer stays LLM-agnostic and tree-shakable (mirrors `observationalMemory`'s
|
|
27
|
-
* `observer`). When omitted, the layer only buffers — it never fabricates facts.
|
|
28
|
-
* @public
|
|
29
|
-
*/
|
|
30
|
-
export type FactExtractor = (input: {
|
|
31
|
-
transcript: string;
|
|
32
|
-
now: string;
|
|
33
|
-
}) => Promise<TemporalFact[]>;
|
|
34
|
-
/**
|
|
35
|
-
* Searches the stored fact ledger for a query, optionally resolving a date.
|
|
36
|
-
* Host-injected. When omitted, search falls back to returning the raw ledger.
|
|
37
|
-
* @public
|
|
38
|
-
*/
|
|
39
|
-
export type FactSearcher = (input: {
|
|
40
|
-
query: string;
|
|
41
|
-
facts: ReadonlyArray<TemporalFact>;
|
|
42
|
-
now: string;
|
|
43
|
-
}) => Promise<TemporalSearchResult>;
|
|
44
|
-
/**
|
|
45
|
-
* Configuration for {@link temporalMemory}.
|
|
46
|
-
* @public
|
|
47
|
-
*/
|
|
48
|
-
export interface TemporalMemoryConfig {
|
|
49
|
-
/** Returns "now" for date grounding/extraction. Defaults to the system clock. */
|
|
50
|
-
now?: () => Date;
|
|
51
|
-
/** Memory scope. Defaults to `'resource'` (long-term, cross-session). */
|
|
52
|
-
scope?: MemoryScope;
|
|
53
|
-
/** LLM-backed fact extractor. Without it, `store` only buffers (no extraction). */
|
|
54
|
-
extract?: FactExtractor;
|
|
55
|
-
/** LLM-backed fact searcher. Without it, `searchMemory` returns the raw ledger. */
|
|
56
|
-
search?: FactSearcher;
|
|
57
|
-
/** Buffered output tokens that trigger an extraction pass. Default 2000. */
|
|
58
|
-
bufferThreshold?: number;
|
|
59
|
-
/** Maximum facts retained (oldest dropped beyond this). Default 200. */
|
|
60
|
-
maxFacts?: number;
|
|
61
|
-
/** Inject a `<current_datetime>` grounding block on recall. Default true. */
|
|
62
|
-
groundDateTime?: boolean;
|
|
63
|
-
/** Inject the fact ledger on recall (vs. on-demand via the search tool). Default false. */
|
|
64
|
-
injectLedger?: boolean;
|
|
65
|
-
}
|
|
66
|
-
interface TemporalState {
|
|
67
|
-
/** ISO timestamp → facts recorded at that instant. */
|
|
68
|
-
facts: Record<string, string[]>;
|
|
69
|
-
/** Accumulated output text awaiting extraction. */
|
|
70
|
-
buffer: string[];
|
|
71
|
-
bufferTokens: number;
|
|
72
|
-
version: number;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* A non-atomic, LLM-backed memory layer that extracts timestamped facts from the
|
|
76
|
-
* conversation into a key-value ledger (key = ISO timestamp) and exposes a
|
|
77
|
-
* `temporal/searchMemory` tool for relative-time recall — given a fact/query it
|
|
78
|
-
* returns matching facts and, when applicable, a resolved (possibly fuzzy) date.
|
|
79
|
-
*
|
|
80
|
-
* The layer is LLM-agnostic: the host injects `extract` and `search` callbacks
|
|
81
|
-
* (mirrors `observationalMemory`'s `observer`), keeping `memory/` tree-shakable.
|
|
82
|
-
* Without them, the layer still buffers text and the search tool returns the raw
|
|
83
|
-
* ledger — it never fabricates facts. A `<current_datetime>` grounding block is
|
|
84
|
-
* injected on recall by default to anchor relative-time reasoning.
|
|
85
|
-
*
|
|
86
|
-
* @public
|
|
87
|
-
* @param config - Clock, scope, injected LLM callbacks, and tuning knobs.
|
|
88
|
-
* @returns A `MemoryLayer` with a fact-ledger store + `searchMemory` tool.
|
|
89
|
-
*/
|
|
90
|
-
export declare function temporalMemory(config?: TemporalMemoryConfig): MemoryLayer<TemporalState>;
|
|
91
|
-
export {};
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { Slot } from '../../types/memory.js';
|
|
3
|
-
import { createMessage, estimateTokens, isOutputText } from '../../util/message-helpers.js';
|
|
4
|
-
import { layerFn } from '../layer-provides.js';
|
|
5
|
-
function emptyState() {
|
|
6
|
-
return {
|
|
7
|
-
facts: {},
|
|
8
|
-
buffer: [],
|
|
9
|
-
bufferTokens: 0,
|
|
10
|
-
version: 0,
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
const DEFAULT_BUFFER_THRESHOLD_TOKENS = 2_000;
|
|
14
|
-
const DEFAULT_MAX_FACTS = 200;
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region Date grounding
|
|
17
|
-
const WEEKDAYS = [
|
|
18
|
-
'Sun',
|
|
19
|
-
'Mon',
|
|
20
|
-
'Tue',
|
|
21
|
-
'Wed',
|
|
22
|
-
'Thu',
|
|
23
|
-
'Fri',
|
|
24
|
-
'Sat',
|
|
25
|
-
];
|
|
26
|
-
/** Formats an instant as `YYYY/MM/DD (Ddd) HH:MM` using local fields. */
|
|
27
|
-
function formatNow(date) {
|
|
28
|
-
const yyyy = date.getFullYear();
|
|
29
|
-
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
30
|
-
const dd = String(date.getDate()).padStart(2, '0');
|
|
31
|
-
const day = WEEKDAYS[date.getDay()];
|
|
32
|
-
const hh = String(date.getHours()).padStart(2, '0');
|
|
33
|
-
const min = String(date.getMinutes()).padStart(2, '0');
|
|
34
|
-
return `${yyyy}/${mm}/${dd} (${day}) ${hh}:${min}`;
|
|
35
|
-
}
|
|
36
|
-
function renderDateTimeBlock(date) {
|
|
37
|
-
return [
|
|
38
|
-
'<current_datetime>',
|
|
39
|
-
`Now: ${formatNow(date)}`,
|
|
40
|
-
'Resolve relative time ("N days/weeks/months ago", "last/next <weekday>",',
|
|
41
|
-
'"before/after", "first/earliest/most recent", "how long since/until") against',
|
|
42
|
-
'Now above and compute the difference explicitly before answering. Use the',
|
|
43
|
-
'temporal/searchMemory tool to look up when something happened.',
|
|
44
|
-
'</current_datetime>',
|
|
45
|
-
].join('\n');
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region Ledger helpers
|
|
49
|
-
/** Flattens the KV ledger into a flat, chronologically sorted fact list. */
|
|
50
|
-
function ledgerToFacts(state) {
|
|
51
|
-
const out = [];
|
|
52
|
-
for (const [ts, facts] of Object.entries(state.facts)) {
|
|
53
|
-
for (const fact of facts) {
|
|
54
|
-
out.push({
|
|
55
|
-
ts,
|
|
56
|
-
fact,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
out.sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
/** Merges new facts into the KV ledger, capping total facts to `maxFacts` (oldest dropped). */
|
|
64
|
-
function mergeFacts(state, incoming, maxFacts) {
|
|
65
|
-
const facts = {};
|
|
66
|
-
for (const [ts, list] of Object.entries(state.facts)) {
|
|
67
|
-
facts[ts] = [
|
|
68
|
-
...list,
|
|
69
|
-
];
|
|
70
|
-
}
|
|
71
|
-
for (const { ts, fact } of incoming) {
|
|
72
|
-
const list = facts[ts] ?? [];
|
|
73
|
-
if (!list.includes(fact)) {
|
|
74
|
-
list.push(fact);
|
|
75
|
-
}
|
|
76
|
-
facts[ts] = list;
|
|
77
|
-
}
|
|
78
|
-
// Cap: drop oldest timestamps until under maxFacts.
|
|
79
|
-
let total = 0;
|
|
80
|
-
for (const list of Object.values(facts)) {
|
|
81
|
-
total += list.length;
|
|
82
|
-
}
|
|
83
|
-
if (total <= maxFacts) {
|
|
84
|
-
return facts;
|
|
85
|
-
}
|
|
86
|
-
const sortedTs = Object.keys(facts).sort();
|
|
87
|
-
for (const ts of sortedTs) {
|
|
88
|
-
if (total <= maxFacts) {
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
total -= facts[ts].length;
|
|
92
|
-
delete facts[ts];
|
|
93
|
-
}
|
|
94
|
-
return facts;
|
|
95
|
-
}
|
|
96
|
-
/** Extracts assistant/output text from newly appended items for buffering. */
|
|
97
|
-
function collectText(newItems) {
|
|
98
|
-
return newItems
|
|
99
|
-
.filter((i) => i.type === 'message')
|
|
100
|
-
.map((i) => i.content
|
|
101
|
-
.filter(isOutputText)
|
|
102
|
-
.map((c) => c.text)
|
|
103
|
-
.join(''))
|
|
104
|
-
.filter((t) => t.length > 0);
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region Layer
|
|
108
|
-
/**
|
|
109
|
-
* A non-atomic, LLM-backed memory layer that extracts timestamped facts from the
|
|
110
|
-
* conversation into a key-value ledger (key = ISO timestamp) and exposes a
|
|
111
|
-
* `temporal/searchMemory` tool for relative-time recall — given a fact/query it
|
|
112
|
-
* returns matching facts and, when applicable, a resolved (possibly fuzzy) date.
|
|
113
|
-
*
|
|
114
|
-
* The layer is LLM-agnostic: the host injects `extract` and `search` callbacks
|
|
115
|
-
* (mirrors `observationalMemory`'s `observer`), keeping `memory/` tree-shakable.
|
|
116
|
-
* Without them, the layer still buffers text and the search tool returns the raw
|
|
117
|
-
* ledger — it never fabricates facts. A `<current_datetime>` grounding block is
|
|
118
|
-
* injected on recall by default to anchor relative-time reasoning.
|
|
119
|
-
*
|
|
120
|
-
* @public
|
|
121
|
-
* @param config - Clock, scope, injected LLM callbacks, and tuning knobs.
|
|
122
|
-
* @returns A `MemoryLayer` with a fact-ledger store + `searchMemory` tool.
|
|
123
|
-
*/
|
|
124
|
-
export function temporalMemory(config) {
|
|
125
|
-
const now = config?.now ?? (() => new Date());
|
|
126
|
-
const threshold = config?.bufferThreshold ?? DEFAULT_BUFFER_THRESHOLD_TOKENS;
|
|
127
|
-
const maxFacts = config?.maxFacts ?? DEFAULT_MAX_FACTS;
|
|
128
|
-
const groundDateTime = config?.groundDateTime ?? true;
|
|
129
|
-
const injectLedger = config?.injectLedger ?? false;
|
|
130
|
-
const extract = config?.extract;
|
|
131
|
-
const search = config?.search;
|
|
132
|
-
return {
|
|
133
|
-
id: 'temporal',
|
|
134
|
-
name: 'Temporal Memory',
|
|
135
|
-
// Grounding sits near the top of the window, before reasoning content.
|
|
136
|
-
slot: Slot.REMINDER,
|
|
137
|
-
scope: config?.scope ?? 'resource',
|
|
138
|
-
budget: {
|
|
139
|
-
min: 0,
|
|
140
|
-
max: injectLedger ? 800 : 200,
|
|
141
|
-
},
|
|
142
|
-
timeouts: {
|
|
143
|
-
store: 60_000,
|
|
144
|
-
},
|
|
145
|
-
provides: {
|
|
146
|
-
searchMemory: layerFn({
|
|
147
|
-
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).',
|
|
148
|
-
input: z.object({
|
|
149
|
-
query: z.string(),
|
|
150
|
-
}),
|
|
151
|
-
output: z.object({
|
|
152
|
-
facts: z.array(z.string()),
|
|
153
|
-
date: z.string().optional(),
|
|
154
|
-
fuzzy: z.boolean().optional(),
|
|
155
|
-
}),
|
|
156
|
-
execute: async (args, state) => {
|
|
157
|
-
const facts = ledgerToFacts(state ?? emptyState());
|
|
158
|
-
if (!search) {
|
|
159
|
-
return {
|
|
160
|
-
result: {
|
|
161
|
-
facts: facts.map((f) => `[${f.ts}] ${f.fact}`),
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
const result = await search({
|
|
166
|
-
query: args.query,
|
|
167
|
-
facts,
|
|
168
|
-
now: formatNow(now()),
|
|
169
|
-
});
|
|
170
|
-
return {
|
|
171
|
-
result,
|
|
172
|
-
};
|
|
173
|
-
},
|
|
174
|
-
}),
|
|
175
|
-
},
|
|
176
|
-
hooks: {
|
|
177
|
-
async init({ storage }) {
|
|
178
|
-
const saved = await storage.get('state');
|
|
179
|
-
return {
|
|
180
|
-
state: saved ?? emptyState(),
|
|
181
|
-
};
|
|
182
|
-
},
|
|
183
|
-
async recall({ state }) {
|
|
184
|
-
const blocks = [];
|
|
185
|
-
if (groundDateTime) {
|
|
186
|
-
blocks.push(renderDateTimeBlock(now()));
|
|
187
|
-
}
|
|
188
|
-
if (injectLedger) {
|
|
189
|
-
const facts = ledgerToFacts(state ?? emptyState());
|
|
190
|
-
if (facts.length > 0) {
|
|
191
|
-
const lines = facts.map((f) => `- [${f.ts}] ${f.fact}`).join('\n');
|
|
192
|
-
blocks.push(`<remembered_facts>\n${lines}\n</remembered_facts>`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (blocks.length === 0) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
const text = blocks.join('\n\n');
|
|
199
|
-
return {
|
|
200
|
-
items: [
|
|
201
|
-
createMessage(text, 'developer'),
|
|
202
|
-
],
|
|
203
|
-
tokenCount: estimateTokens(text),
|
|
204
|
-
};
|
|
205
|
-
},
|
|
206
|
-
async store({ newItems, state }) {
|
|
207
|
-
const s = state ?? emptyState();
|
|
208
|
-
const texts = collectText(newItems);
|
|
209
|
-
const newBuffer = [
|
|
210
|
-
...s.buffer,
|
|
211
|
-
...texts,
|
|
212
|
-
];
|
|
213
|
-
const newTokens = texts.reduce((sum, t) => sum + estimateTokens(t), 0);
|
|
214
|
-
const totalBufferTokens = s.bufferTokens + newTokens;
|
|
215
|
-
if (extract && totalBufferTokens >= threshold && newBuffer.length > 0) {
|
|
216
|
-
const extracted = await extract({
|
|
217
|
-
transcript: newBuffer.join('\n'),
|
|
218
|
-
now: formatNow(now()),
|
|
219
|
-
});
|
|
220
|
-
return {
|
|
221
|
-
state: {
|
|
222
|
-
facts: mergeFacts(s, extracted, maxFacts),
|
|
223
|
-
buffer: [],
|
|
224
|
-
bufferTokens: 0,
|
|
225
|
-
version: s.version + 1,
|
|
226
|
-
},
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
state: {
|
|
231
|
-
...s,
|
|
232
|
-
buffer: newBuffer,
|
|
233
|
-
bufferTokens: totalBufferTokens,
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
},
|
|
237
|
-
async onSpawn({ parentState }) {
|
|
238
|
-
return {
|
|
239
|
-
childState: structuredClone(parentState),
|
|
240
|
-
};
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { MemoryLayer } from '../../types/memory.js';
|
|
2
|
-
import type { Tool } from '../../types/tool.js';
|
|
3
|
-
interface ToolMemoryLayerOpts {
|
|
4
|
-
slot?: number;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Generates one MemoryLayer per unique memory id among the provided tools.
|
|
8
|
-
* Tools sharing the same `memory.id` share a single layer (and thus state).
|
|
9
|
-
*
|
|
10
|
-
* @public
|
|
11
|
-
* @param tools - Array of tools to extract memory declarations from.
|
|
12
|
-
* @param opts - Optional slot override for the generated layers.
|
|
13
|
-
* @returns An array of `MemoryLayer` instances, one per unique tool memory id.
|
|
14
|
-
*/
|
|
15
|
-
export declare function toolMemoryLayer(tools: ReadonlyArray<Tool>, opts?: ToolMemoryLayerOpts): MemoryLayer[];
|
|
16
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Slot } from '../../types/memory.js';
|
|
2
|
-
const EXECUTION_SCOPE = 'execution';
|
|
3
|
-
/**
|
|
4
|
-
* Generates one MemoryLayer per unique memory id among the provided tools.
|
|
5
|
-
* Tools sharing the same `memory.id` share a single layer (and thus state).
|
|
6
|
-
*
|
|
7
|
-
* @public
|
|
8
|
-
* @param tools - Array of tools to extract memory declarations from.
|
|
9
|
-
* @param opts - Optional slot override for the generated layers.
|
|
10
|
-
* @returns An array of `MemoryLayer` instances, one per unique tool memory id.
|
|
11
|
-
*/
|
|
12
|
-
export function toolMemoryLayer(tools, opts) {
|
|
13
|
-
const seen = new Map();
|
|
14
|
-
for (const t of tools) {
|
|
15
|
-
if (!t.memory) {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
const memId = t.memory.id ?? t.name;
|
|
19
|
-
if (seen.has(memId)) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
seen.set(memId, t.memory);
|
|
23
|
-
}
|
|
24
|
-
const slot = opts?.slot ?? Slot.WORKING_MEMORY + 10;
|
|
25
|
-
return [
|
|
26
|
-
...seen.entries(),
|
|
27
|
-
].map(([memId, decl]) => ({
|
|
28
|
-
id: memId,
|
|
29
|
-
name: memId,
|
|
30
|
-
slot,
|
|
31
|
-
scope: EXECUTION_SCOPE,
|
|
32
|
-
hooks: {
|
|
33
|
-
async init() {
|
|
34
|
-
return {
|
|
35
|
-
state: decl.init(),
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
async recall({ state }) {
|
|
39
|
-
return decl.recall(state);
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { ZodType } from 'zod';
|
|
2
|
-
export type WorkingMemoryState = string | Record<string, unknown>;
|
|
3
|
-
export interface WorkingMemoryConfig {
|
|
4
|
-
scope?: 'thread' | 'resource';
|
|
5
|
-
schema?: ZodType;
|
|
6
|
-
template?: string;
|
|
7
|
-
readOnly?: boolean;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Creates a mutable working memory layer that the model can update via the `working-memory/update` tool.
|
|
11
|
-
*
|
|
12
|
-
* @public
|
|
13
|
-
* @param config - Optional configuration for scope, Zod schema, template, and read-only mode.
|
|
14
|
-
* @returns A `MemoryLayer` providing scratchpad state the model can read and write.
|
|
15
|
-
*/
|
|
16
|
-
export declare function workingMemory(config?: WorkingMemoryConfig): {
|
|
17
|
-
id: "working-memory";
|
|
18
|
-
name: string;
|
|
19
|
-
slot: 100;
|
|
20
|
-
scope: "thread" | "resource";
|
|
21
|
-
budget: {
|
|
22
|
-
min: number;
|
|
23
|
-
max: number;
|
|
24
|
-
};
|
|
25
|
-
provides: {
|
|
26
|
-
snapshot: import("../../index.js").LayerDataDecl<WorkingMemoryState, WorkingMemoryState>;
|
|
27
|
-
update: import("../../index.js").LayerFunctionDecl<Record<string, unknown>, void, WorkingMemoryState>;
|
|
28
|
-
};
|
|
29
|
-
hooks: {
|
|
30
|
-
init({ storage }: import("../../index.js").InitParams): Promise<{
|
|
31
|
-
state: WorkingMemoryState;
|
|
32
|
-
}>;
|
|
33
|
-
recall({ state }: import("../../index.js").RecallParams<WorkingMemoryState>): Promise<{
|
|
34
|
-
items: import("../../index.js").InputMessageItem[];
|
|
35
|
-
tokenCount: number;
|
|
36
|
-
} | null>;
|
|
37
|
-
store({ newItems, state }: import("../../index.js").StoreParams<WorkingMemoryState>): Promise<{
|
|
38
|
-
state: WorkingMemoryState;
|
|
39
|
-
} | undefined>;
|
|
40
|
-
onSpawn({ parentState }: import("../../index.js").SpawnParams<WorkingMemoryState>): Promise<{
|
|
41
|
-
childState: WorkingMemoryState;
|
|
42
|
-
} | null>;
|
|
43
|
-
};
|
|
44
|
-
};
|