@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.
Files changed (203) hide show
  1. package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
  2. package/dist/adapters/in-memory-fs-adapter.js +1 -1
  3. package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
  4. package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +2 -2
  5. package/dist/adapters/in-memory-subprocess/metadata.d.ts +1 -1
  6. package/dist/adapters/in-memory-subprocess/types.d.ts +2 -2
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +1 -1
  8. package/dist/adapters/openrouter.d.ts +2 -6
  9. package/dist/adapters/openrouter.js +1 -4
  10. package/dist/builders/channel-builder.d.ts +1 -1
  11. package/dist/builders/control-flow-builders.d.ts +2 -3
  12. package/dist/builders/control-flow-builders.js +1 -2
  13. package/dist/builders/every.d.ts +2 -3
  14. package/dist/builders/every.js +1 -1
  15. package/dist/builders/layer-provides-builders.d.ts +1 -1
  16. package/dist/builders/layer-provides-builders.js +1 -1
  17. package/dist/builders/loop-builder.d.ts +2 -2
  18. package/dist/builders/loop-builder.js +1 -1
  19. package/dist/builders/memory-builder.d.ts +1 -1
  20. package/dist/builders/memory-builder.js +1 -1
  21. package/dist/builders/provide-builder.d.ts +2 -2
  22. package/dist/builders/provide-builder.js +1 -1
  23. package/dist/builders/spawn-builder.d.ts +2 -3
  24. package/dist/builders/spawn-builder.js +1 -1
  25. package/dist/builders/step-builders.d.ts +2 -6
  26. package/dist/builders/step-builders.js +1 -1
  27. package/dist/builders/tool-builder.d.ts +1 -3
  28. package/dist/builders/tool-builder.js +1 -1
  29. package/dist/builders/workflow-hydrator.d.ts +2 -3
  30. package/dist/builders/workflow-hydrator.js +1 -2
  31. package/dist/conditions/conditions.d.ts +2 -3
  32. package/dist/conditions/conditions.js +1 -1
  33. package/dist/harness/agent-harness.d.ts +13 -3
  34. package/dist/harness/agent-harness.js +48 -5
  35. package/dist/harness/deps/memory.d.ts +2 -5
  36. package/dist/harness/deps/memory.js +1 -4
  37. package/dist/harness/deps/types.d.ts +3 -16
  38. package/dist/harness/deps/types.js +1 -1
  39. package/dist/harness/model-call.d.ts +1 -3
  40. package/dist/harness/model-call.js +1 -2
  41. package/dist/harness/model-schema.d.ts +2 -4
  42. package/dist/harness/model-schema.js +1 -1
  43. package/dist/index.d.ts +28 -49
  44. package/dist/index.js +9 -27
  45. package/dist/interpreter/action-deps.d.ts +4 -8
  46. package/dist/interpreter/action-deps.js +2 -6
  47. package/dist/interpreter/action-types.d.ts +3 -8
  48. package/dist/interpreter/action-types.js +1 -1
  49. package/dist/interpreter/collect-tools.d.ts +1 -2
  50. package/dist/interpreter/collect-tools.js +1 -1
  51. package/dist/interpreter/execute-action.js +104 -26
  52. package/dist/interpreter/execute-control.d.ts +1 -2
  53. package/dist/interpreter/execute-control.js +1 -3
  54. package/dist/interpreter/execute.d.ts +2 -3
  55. package/dist/interpreter/execute.js +1 -2
  56. package/dist/interpreter/message-helpers.d.ts +1 -1
  57. package/dist/interpreter/message-helpers.js +1 -1
  58. package/dist/interpreter/step-dispatcher.d.ts +2 -5
  59. package/dist/interpreter/typeguards.d.ts +3 -4
  60. package/dist/interpreter/typeguards.js +1 -1
  61. package/dist/observability/span-impl.d.ts +1 -1
  62. package/dist/observability/trace-exporter.d.ts +1 -1
  63. package/dist/patterns/dynamic-workflow.d.ts +2 -5
  64. package/dist/patterns/dynamic-workflow.js +1 -2
  65. package/dist/patterns/flow.d.ts +2 -2
  66. package/dist/patterns/flow.js +1 -1
  67. package/dist/patterns/interview.d.ts +2 -2
  68. package/dist/patterns/interview.js +1 -1
  69. package/dist/patterns/plans.d.ts +2 -3
  70. package/dist/patterns/plans.js +1 -1
  71. package/dist/patterns/ralph-wiggum.d.ts +2 -3
  72. package/dist/patterns/react.d.ts +2 -3
  73. package/dist/portable/memory-layers.d.ts +1 -7
  74. package/dist/portable/memory-layers.js +1 -7
  75. package/dist/portable.d.ts +2 -2
  76. package/dist/portable.js +2 -2
  77. package/dist/runtime/broadcaster-utils.d.ts +1 -1
  78. package/dist/runtime/channel-store.d.ts +1 -1
  79. package/dist/runtime/channel-store.js +1 -2
  80. package/dist/runtime/context-deps.d.ts +3 -3
  81. package/dist/runtime/context-deps.js +2 -2
  82. package/dist/runtime/context-type-deps.d.ts +2 -11
  83. package/dist/runtime/cwd-helpers.d.ts +1 -1
  84. package/dist/runtime/detached-handle.d.ts +2 -3
  85. package/dist/runtime/detached-handle.js +1 -3
  86. package/dist/runtime/durable/checkpoint-store.d.ts +1 -1
  87. package/dist/runtime/durable/checkpoint-store.js +1 -1
  88. package/dist/runtime/durable/harness-checkpoints.d.ts +2 -5
  89. package/dist/runtime/durable/runnable-loop.d.ts +1 -3
  90. package/dist/runtime/durable/session-seed.d.ts +1 -1
  91. package/dist/runtime/durable/stall-nudge.d.ts +1 -1
  92. package/dist/runtime/event-broadcaster.d.ts +1 -1
  93. package/dist/runtime/in-memory-storage.d.ts +1 -1
  94. package/dist/runtime/in-memory-storage.js +1 -1
  95. package/dist/runtime/item-log-impl.d.ts +1 -3
  96. package/dist/runtime/item-log-impl.js +1 -1
  97. package/dist/runtime/message-queue.d.ts +1 -2
  98. package/dist/runtime/session-runner.d.ts +1 -4
  99. package/dist/runtime/session-streams.d.ts +1 -2
  100. package/dist/runtime/session-streams.js +1 -1
  101. package/dist/runtime/step-registry.d.ts +2 -2
  102. package/dist/runtime/step-registry.js +1 -2
  103. package/dist/runtime/tool-memory.d.ts +1 -3
  104. package/dist/types/mutable-context.d.ts +2 -3
  105. package/dist/types/step-registrar.d.ts +1 -1
  106. package/dist/unstable.d.ts +5 -9
  107. package/dist/unstable.js +3 -6
  108. package/dist/until/combinators.d.ts +1 -1
  109. package/dist/until/never.d.ts +1 -1
  110. package/dist/until/predicates.d.ts +2 -2
  111. package/package.json +6 -4
  112. package/dist/errors/noetic-config-error.d.ts +0 -41
  113. package/dist/errors/noetic-config-error.js +0 -44
  114. package/dist/errors/noetic-error.d.ts +0 -18
  115. package/dist/errors/noetic-error.js +0 -54
  116. package/dist/memory/budget.d.ts +0 -23
  117. package/dist/memory/budget.js +0 -117
  118. package/dist/memory/exec-context-factory.d.ts +0 -25
  119. package/dist/memory/exec-context-factory.js +0 -30
  120. package/dist/memory/flow-schema.d.ts +0 -52
  121. package/dist/memory/flow-schema.js +0 -102
  122. package/dist/memory/function-call-utils.d.ts +0 -6
  123. package/dist/memory/function-call-utils.js +0 -27
  124. package/dist/memory/layer-api.d.ts +0 -14
  125. package/dist/memory/layer-api.js +0 -91
  126. package/dist/memory/layer-lifecycle.d.ts +0 -160
  127. package/dist/memory/layer-lifecycle.js +0 -552
  128. package/dist/memory/layer-provides.d.ts +0 -32
  129. package/dist/memory/layer-provides.js +0 -33
  130. package/dist/memory/layer-usage.d.ts +0 -16
  131. package/dist/memory/layer-usage.js +0 -54
  132. package/dist/memory/layers/durable-task-state.d.ts +0 -79
  133. package/dist/memory/layers/durable-task-state.js +0 -118
  134. package/dist/memory/layers/file-reference.d.ts +0 -55
  135. package/dist/memory/layers/file-reference.js +0 -594
  136. package/dist/memory/layers/history-window.d.ts +0 -29
  137. package/dist/memory/layers/history-window.js +0 -114
  138. package/dist/memory/layers/observational-memory.d.ts +0 -53
  139. package/dist/memory/layers/observational-memory.js +0 -109
  140. package/dist/memory/layers/plan.d.ts +0 -55
  141. package/dist/memory/layers/plan.js +0 -374
  142. package/dist/memory/layers/static-content.d.ts +0 -27
  143. package/dist/memory/layers/static-content.js +0 -35
  144. package/dist/memory/layers/steering.d.ts +0 -51
  145. package/dist/memory/layers/steering.js +0 -258
  146. package/dist/memory/layers/temporal.d.ts +0 -91
  147. package/dist/memory/layers/temporal.js +0 -245
  148. package/dist/memory/layers/tool-memory-layer.d.ts +0 -16
  149. package/dist/memory/layers/tool-memory-layer.js +0 -43
  150. package/dist/memory/layers/working-memory.d.ts +0 -44
  151. package/dist/memory/layers/working-memory.js +0 -92
  152. package/dist/memory/projector.d.ts +0 -10
  153. package/dist/memory/projector.js +0 -19
  154. package/dist/memory/scope.d.ts +0 -3
  155. package/dist/memory/scope.js +0 -31
  156. package/dist/memory/strip-unresolved.d.ts +0 -13
  157. package/dist/memory/strip-unresolved.js +0 -35
  158. package/dist/schemas/item.d.ts +0 -35
  159. package/dist/schemas/item.js +0 -155
  160. package/dist/types/channel.d.ts +0 -29
  161. package/dist/types/channel.js +0 -1
  162. package/dist/types/common.d.ts +0 -68
  163. package/dist/types/common.js +0 -1
  164. package/dist/types/context-parts/item-log.d.ts +0 -6
  165. package/dist/types/context-parts/item-log.js +0 -1
  166. package/dist/types/context-parts/layer-usage.d.ts +0 -18
  167. package/dist/types/context-parts/layer-usage.js +0 -1
  168. package/dist/types/context.d.ts +0 -202
  169. package/dist/types/context.js +0 -1
  170. package/dist/types/detached.d.ts +0 -17
  171. package/dist/types/detached.js +0 -7
  172. package/dist/types/error.d.ts +0 -66
  173. package/dist/types/error.js +0 -1
  174. package/dist/types/fs-adapter.d.ts +0 -51
  175. package/dist/types/fs-adapter.js +0 -3
  176. package/dist/types/harness-result.d.ts +0 -43
  177. package/dist/types/harness-result.js +0 -2
  178. package/dist/types/items.d.ts +0 -131
  179. package/dist/types/items.js +0 -2
  180. package/dist/types/memory-context.d.ts +0 -39
  181. package/dist/types/memory-context.js +0 -1
  182. package/dist/types/memory.d.ts +0 -306
  183. package/dist/types/memory.js +0 -13
  184. package/dist/types/observability.d.ts +0 -30
  185. package/dist/types/observability.js +0 -1
  186. package/dist/types/runtime.d.ts +0 -256
  187. package/dist/types/runtime.js +0 -1
  188. package/dist/types/shell-adapter.d.ts +0 -34
  189. package/dist/types/shell-adapter.js +0 -10
  190. package/dist/types/steering.d.ts +0 -89
  191. package/dist/types/steering.js +0 -14
  192. package/dist/types/step.d.ts +0 -231
  193. package/dist/types/step.js +0 -1
  194. package/dist/types/subprocess-adapter.d.ts +0 -157
  195. package/dist/types/subprocess-adapter.js +0 -3
  196. package/dist/types/tool-context.d.ts +0 -34
  197. package/dist/types/tool-context.js +0 -1
  198. package/dist/types/tool.d.ts +0 -64
  199. package/dist/types/tool.js +0 -1
  200. package/dist/util/framework-cast.d.ts +0 -10
  201. package/dist/util/framework-cast.js +0 -16
  202. package/dist/util/message-helpers.d.ts +0 -17
  203. 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
- };