agentfootprint 9.4.0 → 9.5.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/AGENTS.md +2 -1
- package/dist/esm/memory/define.d.ts +2 -1
- package/dist/esm/memory/define.js +57 -8
- package/dist/esm/memory/define.js.map +1 -1
- package/dist/esm/memory/define.types.d.ts +30 -4
- package/dist/esm/memory/define.types.js +6 -0
- package/dist/esm/memory/define.types.js.map +1 -1
- package/dist/esm/memory/index.d.ts +1 -0
- package/dist/esm/memory/index.js +3 -0
- package/dist/esm/memory/index.js.map +1 -1
- package/dist/esm/memory/pipeline/default.d.ts +16 -0
- package/dist/esm/memory/pipeline/default.js +8 -1
- package/dist/esm/memory/pipeline/default.js.map +1 -1
- package/dist/esm/memory/stages/filterByDecay.d.ts +67 -0
- package/dist/esm/memory/stages/filterByDecay.js +44 -0
- package/dist/esm/memory/stages/filterByDecay.js.map +1 -0
- package/dist/esm/memory/stages/index.d.ts +2 -0
- package/dist/esm/memory/stages/index.js +1 -0
- package/dist/esm/memory/stages/index.js.map +1 -1
- package/dist/esm/memory/strategies.d.ts +133 -0
- package/dist/esm/memory/strategies.js +336 -0
- package/dist/esm/memory/strategies.js.map +1 -0
- package/dist/memory/define.js +57 -8
- package/dist/memory/define.js.map +1 -1
- package/dist/memory/define.types.js +6 -0
- package/dist/memory/define.types.js.map +1 -1
- package/dist/memory/index.js +6 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/pipeline/default.js +8 -1
- package/dist/memory/pipeline/default.js.map +1 -1
- package/dist/memory/stages/filterByDecay.js +48 -0
- package/dist/memory/stages/filterByDecay.js.map +1 -0
- package/dist/memory/stages/index.js +4 -1
- package/dist/memory/stages/index.js.map +1 -1
- package/dist/memory/strategies.js +343 -0
- package/dist/memory/strategies.js.map +1 -0
- package/dist/types/memory/define.d.ts +2 -1
- package/dist/types/memory/define.d.ts.map +1 -1
- package/dist/types/memory/define.types.d.ts +30 -4
- package/dist/types/memory/define.types.d.ts.map +1 -1
- package/dist/types/memory/index.d.ts +1 -0
- package/dist/types/memory/index.d.ts.map +1 -1
- package/dist/types/memory/pipeline/default.d.ts +16 -0
- package/dist/types/memory/pipeline/default.d.ts.map +1 -1
- package/dist/types/memory/stages/filterByDecay.d.ts +68 -0
- package/dist/types/memory/stages/filterByDecay.d.ts.map +1 -0
- package/dist/types/memory/stages/index.d.ts +2 -0
- package/dist/types/memory/stages/index.d.ts.map +1 -1
- package/dist/types/memory/strategies.d.ts +134 -0
- package/dist/types/memory/strategies.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named memory strategies — what each one does, which TYPES accept it, and
|
|
3
|
+
* what the host must supply before it can run.
|
|
4
|
+
*
|
|
5
|
+
* `MEMORY_STRATEGIES` is a const of seven bare strings. A string is enough to
|
|
6
|
+
* WRITE `strategy: { kind: … }` and not nearly enough to OFFER the choice: a
|
|
7
|
+
* host rendering a strategy picker off that const offers seven options, and
|
|
8
|
+
* learns which of them this deployment can actually run by calling
|
|
9
|
+
* `defineMemory` and reading the exception. That is a selector that discovers
|
|
10
|
+
* its own capabilities by failing.
|
|
11
|
+
*
|
|
12
|
+
* So each strategy declares itself, and `listMemoryStrategies()` enumerates
|
|
13
|
+
* the declarations. The shape mirrors the influence exemplar in this same
|
|
14
|
+
* package (`listInfluenceStrategies()` → `{ name, description, requirements,
|
|
15
|
+
* scorer }`), with two deliberate differences:
|
|
16
|
+
*
|
|
17
|
+
* • the id is `kind`, not `name` — it is the value the caller writes into
|
|
18
|
+
* `strategy.kind`, so a picker's option value IS the id;
|
|
19
|
+
* • there is no `scorer`, because a memory strategy is not a function the
|
|
20
|
+
* host calls; what it needs instead is `types` — the memory TYPES that
|
|
21
|
+
* accept it, which is the other half of "can I offer this?" (`decay` on a
|
|
22
|
+
* SEMANTIC store is refused however good the host's credentials are).
|
|
23
|
+
*
|
|
24
|
+
* `requirements` is what a host must SUPPLY (an embedder, an LLM, a store
|
|
25
|
+
* that can search). Empty means the strategy runs anywhere, at $0.
|
|
26
|
+
*
|
|
27
|
+
* Pattern: declared capability descriptors + the two guards that enforce
|
|
28
|
+
* them — SHAPE first (`assertStrategyShape`: is this even a
|
|
29
|
+
* strategy?), REQUIREMENTS after (`assertStrategyRequirements`: can
|
|
30
|
+
* this deployment run it?).
|
|
31
|
+
* Role: memory/ layer-1, beside the const it describes.
|
|
32
|
+
* Emits: N/A — build-time only.
|
|
33
|
+
*
|
|
34
|
+
* @see ./define.types.ts for `MEMORY_STRATEGIES` and the strategy union
|
|
35
|
+
* @see ../lib/influence-core/strategies.ts for the exemplar this follows
|
|
36
|
+
*/
|
|
37
|
+
import { type DefineMemoryOptions, type MemoryStrategyKind, type MemoryType } from './define.types.js';
|
|
38
|
+
/**
|
|
39
|
+
* What a strategy needs the host to supply before it can run. A picker greys
|
|
40
|
+
* out (or refuses to offer) strategies whose requirements it cannot meet.
|
|
41
|
+
*
|
|
42
|
+
* Three well-known values ship; the type stays open so a consumer's own
|
|
43
|
+
* strategy descriptor can name something else:
|
|
44
|
+
*
|
|
45
|
+
* - `'embedder'` — an `Embedder` that turns text into a vector.
|
|
46
|
+
* - `'vector-store'` — a store that implements `search()`. Not the same
|
|
47
|
+
* requirement as an embedder: the embedder makes the
|
|
48
|
+
* query vector, the store is what ranks against it,
|
|
49
|
+
* and a deployment can easily have one without the
|
|
50
|
+
* other (`RedisStore` is a full memory store with no
|
|
51
|
+
* `search()` at all).
|
|
52
|
+
* - `'llm'` — a chat provider the strategy calls on the host's
|
|
53
|
+
* behalf.
|
|
54
|
+
*/
|
|
55
|
+
export type MemoryStrategyRequirement = 'embedder' | 'vector-store' | 'llm' | (string & Record<never, never>);
|
|
56
|
+
/**
|
|
57
|
+
* A memory strategy, described: enough for a host to render it in a picker
|
|
58
|
+
* and know, before it offers the option, whether this deployment can run it.
|
|
59
|
+
*/
|
|
60
|
+
export interface MemoryStrategyInfo {
|
|
61
|
+
/** The value written as `strategy.kind` — a member of `MEMORY_STRATEGIES`. */
|
|
62
|
+
readonly kind: MemoryStrategyKind;
|
|
63
|
+
/** One-or-two-sentence plain description, current-truth caveats included. */
|
|
64
|
+
readonly description: string;
|
|
65
|
+
/** What the host must supply. Empty = runs anywhere, no dependency, $0. */
|
|
66
|
+
readonly requirements: readonly MemoryStrategyRequirement[];
|
|
67
|
+
/** The memory TYPES that accept this strategy. Any other pair is refused. */
|
|
68
|
+
readonly types: readonly MemoryType[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Every memory strategy, described — cheapest first, in the order the docs
|
|
72
|
+
* teach them. Frozen: a host renders its picker straight off this.
|
|
73
|
+
*
|
|
74
|
+
* @example Offer only what this deployment can actually run
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { listMemoryStrategies } from 'agentfootprint/memory';
|
|
77
|
+
*
|
|
78
|
+
* const available = new Set(embedder ? ['embedder', 'vector-store'] : []);
|
|
79
|
+
* const offerable = listMemoryStrategies().filter(
|
|
80
|
+
* (s) => s.types.includes('episodic') && s.requirements.every((r) => available.has(r)),
|
|
81
|
+
* );
|
|
82
|
+
* // → window, budget, decay, hybrid — the four that cost nothing to run.
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function listMemoryStrategies(): readonly MemoryStrategyInfo[];
|
|
86
|
+
/**
|
|
87
|
+
* One strategy's description by `kind`, or `undefined` for a string that is
|
|
88
|
+
* not a strategy at all.
|
|
89
|
+
*/
|
|
90
|
+
export declare function memoryStrategyInfo(kind: string): MemoryStrategyInfo | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Refuse a strategy whose SHAPE is wrong, before anything reads into it.
|
|
93
|
+
*
|
|
94
|
+
* WHY it runs FIRST, ahead of both the pipeline dispatch and the
|
|
95
|
+
* requirements walk: those two read FIELDS off the strategy
|
|
96
|
+
* (`strategies[0]`, `s.embedder`, `for (const sub of strategy.strategies)`),
|
|
97
|
+
* and a field read off a shape that never had it is a `TypeError` with the
|
|
98
|
+
* library's internals in the text — `Cannot read properties of undefined
|
|
99
|
+
* (reading '0')` for `{ kind: 'hybrid', size: 5 }`, which names neither the
|
|
100
|
+
* option the caller got wrong nor the one they meant. A caller cannot act on
|
|
101
|
+
* that. This guard turns every such read into a refusal that names the
|
|
102
|
+
* field, shows the line that would have worked, and points at the catalogue.
|
|
103
|
+
*
|
|
104
|
+
* It checks SHAPE only — is this an object, does it carry a `kind`, does a
|
|
105
|
+
* `hybrid` carry the array that makes it a hybrid. It deliberately does NOT
|
|
106
|
+
* judge whether the kind is real or legal for the type: the dispatch refuses
|
|
107
|
+
* those and names the alternative for that type, which is the better message.
|
|
108
|
+
*
|
|
109
|
+
* @param strategy the strategy exactly as the caller wrote it — `unknown`,
|
|
110
|
+
* because the whole point is that it may not be a `Strategy`.
|
|
111
|
+
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
|
|
112
|
+
*/
|
|
113
|
+
export declare function assertStrategyShape(strategy: unknown, site: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* Refuse a config whose strategy declares a requirement the caller did not
|
|
116
|
+
* supply — by name, at BUILD, with the fix in the message.
|
|
117
|
+
*
|
|
118
|
+
* WHY it runs LAST, after the pipeline dispatch rather than before it: the
|
|
119
|
+
* dispatch's own refusals know more than this one does. `defineMemory`'s
|
|
120
|
+
* TOP_K arm knows about the `ranksBy: 'server-text'` exemption AND about the
|
|
121
|
+
* write half it takes away; the CAUSAL arm knows that exemption does not
|
|
122
|
+
* apply to it; the EXTRACT arm knows the `llm` matters only for
|
|
123
|
+
* `extractor: 'llm'`. Speaking first would replace those messages with a
|
|
124
|
+
* blunter one. This is the BACKSTOP: it says something only when nothing
|
|
125
|
+
* better already did, and its job is that no declared requirement can go
|
|
126
|
+
* unchecked — the failure it exists to prevent is a missing dependency
|
|
127
|
+
* surfacing as a `TypeError` from five frames inside a stage, halfway
|
|
128
|
+
* through a paid run.
|
|
129
|
+
*
|
|
130
|
+
* @param options the config as written by the caller.
|
|
131
|
+
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
|
|
132
|
+
*/
|
|
133
|
+
export declare function assertStrategyRequirements(options: DefineMemoryOptions, site: string): void;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named memory strategies — what each one does, which TYPES accept it, and
|
|
3
|
+
* what the host must supply before it can run.
|
|
4
|
+
*
|
|
5
|
+
* `MEMORY_STRATEGIES` is a const of seven bare strings. A string is enough to
|
|
6
|
+
* WRITE `strategy: { kind: … }` and not nearly enough to OFFER the choice: a
|
|
7
|
+
* host rendering a strategy picker off that const offers seven options, and
|
|
8
|
+
* learns which of them this deployment can actually run by calling
|
|
9
|
+
* `defineMemory` and reading the exception. That is a selector that discovers
|
|
10
|
+
* its own capabilities by failing.
|
|
11
|
+
*
|
|
12
|
+
* So each strategy declares itself, and `listMemoryStrategies()` enumerates
|
|
13
|
+
* the declarations. The shape mirrors the influence exemplar in this same
|
|
14
|
+
* package (`listInfluenceStrategies()` → `{ name, description, requirements,
|
|
15
|
+
* scorer }`), with two deliberate differences:
|
|
16
|
+
*
|
|
17
|
+
* • the id is `kind`, not `name` — it is the value the caller writes into
|
|
18
|
+
* `strategy.kind`, so a picker's option value IS the id;
|
|
19
|
+
* • there is no `scorer`, because a memory strategy is not a function the
|
|
20
|
+
* host calls; what it needs instead is `types` — the memory TYPES that
|
|
21
|
+
* accept it, which is the other half of "can I offer this?" (`decay` on a
|
|
22
|
+
* SEMANTIC store is refused however good the host's credentials are).
|
|
23
|
+
*
|
|
24
|
+
* `requirements` is what a host must SUPPLY (an embedder, an LLM, a store
|
|
25
|
+
* that can search). Empty means the strategy runs anywhere, at $0.
|
|
26
|
+
*
|
|
27
|
+
* Pattern: declared capability descriptors + the two guards that enforce
|
|
28
|
+
* them — SHAPE first (`assertStrategyShape`: is this even a
|
|
29
|
+
* strategy?), REQUIREMENTS after (`assertStrategyRequirements`: can
|
|
30
|
+
* this deployment run it?).
|
|
31
|
+
* Role: memory/ layer-1, beside the const it describes.
|
|
32
|
+
* Emits: N/A — build-time only.
|
|
33
|
+
*
|
|
34
|
+
* @see ./define.types.ts for `MEMORY_STRATEGIES` and the strategy union
|
|
35
|
+
* @see ../lib/influence-core/strategies.ts for the exemplar this follows
|
|
36
|
+
*/
|
|
37
|
+
import { MEMORY_STRATEGIES, MEMORY_TYPES, } from './define.types.js';
|
|
38
|
+
import { resolveRankingMode } from './store/capability.js';
|
|
39
|
+
const WINDOW = Object.freeze({
|
|
40
|
+
kind: MEMORY_STRATEGIES.WINDOW,
|
|
41
|
+
description: 'Keeps the last `size` entries — messages on an Episodic store, facts or beats on the ' +
|
|
42
|
+
'others. A pure rule: no LLM, no embeddings, nothing to configure but the number. The ' +
|
|
43
|
+
'right default for short-to-medium chats.',
|
|
44
|
+
requirements: Object.freeze([]),
|
|
45
|
+
types: Object.freeze([MEMORY_TYPES.EPISODIC, MEMORY_TYPES.SEMANTIC, MEMORY_TYPES.NARRATIVE]),
|
|
46
|
+
});
|
|
47
|
+
const BUDGET = Object.freeze({
|
|
48
|
+
kind: MEMORY_STRATEGIES.BUDGET,
|
|
49
|
+
description: 'Injects as many recent entries as fit a token budget, and injects none at all when the ' +
|
|
50
|
+
'budget is below the floor. Free — the count is estimated, not modelled by a tokenizer ' +
|
|
51
|
+
'call. The decision (pick / skip-empty / skip-no-budget) is recorded as branch evidence.',
|
|
52
|
+
requirements: Object.freeze([]),
|
|
53
|
+
types: Object.freeze([MEMORY_TYPES.EPISODIC]),
|
|
54
|
+
});
|
|
55
|
+
const SUMMARIZE = Object.freeze({
|
|
56
|
+
kind: MEMORY_STRATEGIES.SUMMARIZE,
|
|
57
|
+
description: 'Names a cheap LLM to compress older turns while the most recent `recent` turns stay ' +
|
|
58
|
+
'raw. HONEST CAVEAT (9.5.0): the compression stage exists but is not composed into the ' +
|
|
59
|
+
'pipeline `defineMemory` builds, so what runs today is the last `recent` entries, ' +
|
|
60
|
+
'verbatim — the `llm` is required and not yet called. Until it is wired, ' +
|
|
61
|
+
'`.compaction({ summarizer, model })` on the Agent is the summarizer that does run.',
|
|
62
|
+
requirements: Object.freeze(['llm']),
|
|
63
|
+
types: Object.freeze([MEMORY_TYPES.EPISODIC]),
|
|
64
|
+
});
|
|
65
|
+
const TOP_K = Object.freeze({
|
|
66
|
+
kind: MEMORY_STRATEGIES.TOP_K,
|
|
67
|
+
description: 'Embeds the query and returns the closest stored entries above a threshold — strictly: ' +
|
|
68
|
+
'when nothing clears the threshold, nothing is injected. The embedder is exempt for a ' +
|
|
69
|
+
"store that declares `ranksBy: 'server-text'`, which takes the question as words and " +
|
|
70
|
+
'ranks it on its own side.',
|
|
71
|
+
requirements: Object.freeze(['embedder', 'vector-store']),
|
|
72
|
+
types: Object.freeze([MEMORY_TYPES.SEMANTIC, MEMORY_TYPES.CAUSAL]),
|
|
73
|
+
});
|
|
74
|
+
const EXTRACT = Object.freeze({
|
|
75
|
+
kind: MEMORY_STRATEGIES.EXTRACT,
|
|
76
|
+
description: 'Distills each turn into structured facts or narrative beats on the WRITE side. ' +
|
|
77
|
+
"`extractor: 'pattern'` is regex heuristics — free, no dependency, which is why this " +
|
|
78
|
+
"strategy requires nothing. `extractor: 'llm'` additionally needs an `llm`, and is " +
|
|
79
|
+
'refused without one.',
|
|
80
|
+
requirements: Object.freeze([]),
|
|
81
|
+
types: Object.freeze([MEMORY_TYPES.SEMANTIC, MEMORY_TYPES.NARRATIVE]),
|
|
82
|
+
});
|
|
83
|
+
const DECAY = Object.freeze({
|
|
84
|
+
kind: MEMORY_STRATEGIES.DECAY,
|
|
85
|
+
description: 'Lets old memory fade: each loaded entry is scored by age against a half-life and ' +
|
|
86
|
+
'dropped below `minScore`, so a long-running agent stops rehearsing last month. Free — ' +
|
|
87
|
+
'arithmetic on a timestamp, no LLM and no embeddings.',
|
|
88
|
+
requirements: Object.freeze([]),
|
|
89
|
+
types: Object.freeze([MEMORY_TYPES.EPISODIC]),
|
|
90
|
+
});
|
|
91
|
+
const HYBRID = Object.freeze({
|
|
92
|
+
kind: MEMORY_STRATEGIES.HYBRID,
|
|
93
|
+
description: 'Composes several strategies on one store. It requires nothing of its own — each ' +
|
|
94
|
+
'sub-strategy carries its own requirements, and every one of them is checked at build ' +
|
|
95
|
+
'so a hybrid cannot smuggle in a strategy this deployment cannot run.',
|
|
96
|
+
requirements: Object.freeze([]),
|
|
97
|
+
types: Object.freeze([MEMORY_TYPES.EPISODIC, MEMORY_TYPES.SEMANTIC, MEMORY_TYPES.NARRATIVE]),
|
|
98
|
+
});
|
|
99
|
+
const BUILT_IN = Object.freeze([
|
|
100
|
+
WINDOW,
|
|
101
|
+
BUDGET,
|
|
102
|
+
SUMMARIZE,
|
|
103
|
+
TOP_K,
|
|
104
|
+
EXTRACT,
|
|
105
|
+
DECAY,
|
|
106
|
+
HYBRID,
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* Every memory strategy, described — cheapest first, in the order the docs
|
|
110
|
+
* teach them. Frozen: a host renders its picker straight off this.
|
|
111
|
+
*
|
|
112
|
+
* @example Offer only what this deployment can actually run
|
|
113
|
+
* ```ts
|
|
114
|
+
* import { listMemoryStrategies } from 'agentfootprint/memory';
|
|
115
|
+
*
|
|
116
|
+
* const available = new Set(embedder ? ['embedder', 'vector-store'] : []);
|
|
117
|
+
* const offerable = listMemoryStrategies().filter(
|
|
118
|
+
* (s) => s.types.includes('episodic') && s.requirements.every((r) => available.has(r)),
|
|
119
|
+
* );
|
|
120
|
+
* // → window, budget, decay, hybrid — the four that cost nothing to run.
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function listMemoryStrategies() {
|
|
124
|
+
return BUILT_IN;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* One strategy's description by `kind`, or `undefined` for a string that is
|
|
128
|
+
* not a strategy at all.
|
|
129
|
+
*/
|
|
130
|
+
export function memoryStrategyInfo(kind) {
|
|
131
|
+
return BUILT_IN.find((s) => s.kind === kind);
|
|
132
|
+
}
|
|
133
|
+
// ─── The guard that makes the SHAPE true ────────────────────────────
|
|
134
|
+
/**
|
|
135
|
+
* One correct line per kind — what a caller writes to get that strategy.
|
|
136
|
+
* These are quoted verbatim into refusals, so a reader can copy the fix out
|
|
137
|
+
* of the message instead of going to look for a doc.
|
|
138
|
+
*/
|
|
139
|
+
const EXAMPLE = Object.freeze({
|
|
140
|
+
[MEMORY_STRATEGIES.WINDOW]: `{ kind: 'window', size: 20 }`,
|
|
141
|
+
[MEMORY_STRATEGIES.BUDGET]: `{ kind: 'budget', maxEntries: 20 }`,
|
|
142
|
+
[MEMORY_STRATEGIES.SUMMARIZE]: `{ kind: 'summarize', recent: 5, llm }`,
|
|
143
|
+
[MEMORY_STRATEGIES.TOP_K]: `{ kind: 'topK', topK: 3, embedder }`,
|
|
144
|
+
[MEMORY_STRATEGIES.EXTRACT]: `{ kind: 'extract', extractor: 'pattern' }`,
|
|
145
|
+
[MEMORY_STRATEGIES.DECAY]: `{ kind: 'decay', halfLifeMs: 86_400_000 }`,
|
|
146
|
+
[MEMORY_STRATEGIES.HYBRID]: `{ kind: 'hybrid', strategies: [{ kind: 'window', size: 20 }] }`,
|
|
147
|
+
});
|
|
148
|
+
const LISTING_HINT = ` \`listMemoryStrategies()\` describes all seven kinds — what each one does, which memory ` +
|
|
149
|
+
`TYPES accept it, and what it needs supplied.`;
|
|
150
|
+
/**
|
|
151
|
+
* Refuse a strategy whose SHAPE is wrong, before anything reads into it.
|
|
152
|
+
*
|
|
153
|
+
* WHY it runs FIRST, ahead of both the pipeline dispatch and the
|
|
154
|
+
* requirements walk: those two read FIELDS off the strategy
|
|
155
|
+
* (`strategies[0]`, `s.embedder`, `for (const sub of strategy.strategies)`),
|
|
156
|
+
* and a field read off a shape that never had it is a `TypeError` with the
|
|
157
|
+
* library's internals in the text — `Cannot read properties of undefined
|
|
158
|
+
* (reading '0')` for `{ kind: 'hybrid', size: 5 }`, which names neither the
|
|
159
|
+
* option the caller got wrong nor the one they meant. A caller cannot act on
|
|
160
|
+
* that. This guard turns every such read into a refusal that names the
|
|
161
|
+
* field, shows the line that would have worked, and points at the catalogue.
|
|
162
|
+
*
|
|
163
|
+
* It checks SHAPE only — is this an object, does it carry a `kind`, does a
|
|
164
|
+
* `hybrid` carry the array that makes it a hybrid. It deliberately does NOT
|
|
165
|
+
* judge whether the kind is real or legal for the type: the dispatch refuses
|
|
166
|
+
* those and names the alternative for that type, which is the better message.
|
|
167
|
+
*
|
|
168
|
+
* @param strategy the strategy exactly as the caller wrote it — `unknown`,
|
|
169
|
+
* because the whole point is that it may not be a `Strategy`.
|
|
170
|
+
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
|
|
171
|
+
*/
|
|
172
|
+
export function assertStrategyShape(strategy, site) {
|
|
173
|
+
checkShape(strategy, site, 'strategy');
|
|
174
|
+
}
|
|
175
|
+
function checkShape(value, site, path) {
|
|
176
|
+
if (typeof value !== 'object' || value === null) {
|
|
177
|
+
throw new Error(notAStrategyObject(value, site, path));
|
|
178
|
+
}
|
|
179
|
+
const kind = value.kind;
|
|
180
|
+
if (typeof kind !== 'string' || kind === '') {
|
|
181
|
+
throw new Error(missingKind(site, path));
|
|
182
|
+
}
|
|
183
|
+
if (kind !== MEMORY_STRATEGIES.HYBRID)
|
|
184
|
+
return;
|
|
185
|
+
// A hybrid IS its list. Every other kind can survive a missing field on a
|
|
186
|
+
// default; this one has nothing left to be.
|
|
187
|
+
const subs = value.strategies;
|
|
188
|
+
if (!Array.isArray(subs) || subs.length === 0) {
|
|
189
|
+
throw new Error(hybridWithoutStrategies(subs, site, path));
|
|
190
|
+
}
|
|
191
|
+
subs.forEach((sub, index) => checkShape(sub, site, `${path}.strategies[${index}]`));
|
|
192
|
+
}
|
|
193
|
+
/** `'window'`, `42`, `null` — anything that is not an object at all. */
|
|
194
|
+
function notAStrategyObject(value, site, path) {
|
|
195
|
+
const written = typeof value === 'string'
|
|
196
|
+
? `the string \`'${value}'\``
|
|
197
|
+
: value === undefined
|
|
198
|
+
? 'nothing at all'
|
|
199
|
+
: `\`${String(value)}\``;
|
|
200
|
+
// A bare string is almost always a caller reaching for the right kind with
|
|
201
|
+
// the wrong syntax — so answer with THAT kind's line, not a generic one.
|
|
202
|
+
const example = typeof value === 'string' && memoryStrategyInfo(value) !== undefined
|
|
203
|
+
? EXAMPLE[value]
|
|
204
|
+
: EXAMPLE[MEMORY_STRATEGIES.WINDOW];
|
|
205
|
+
return (`${site}: \`${path}\` must be an OBJECT with a \`kind\` — got ${written}.\n` +
|
|
206
|
+
` A bare kind name is not a strategy: the kind names the rule, and the object's other ` +
|
|
207
|
+
`fields configure it.\n` +
|
|
208
|
+
` Fix: ${path}: ${example}\n` +
|
|
209
|
+
LISTING_HINT);
|
|
210
|
+
}
|
|
211
|
+
/** `{}`, or an object whose `kind` is not a string. */
|
|
212
|
+
function missingKind(site, path) {
|
|
213
|
+
return (`${site}: \`${path}\` has no \`kind\` — that field is what says which strategy this is, ` +
|
|
214
|
+
`so there is nothing here to build.\n` +
|
|
215
|
+
` Fix: ${path}: ${EXAMPLE[MEMORY_STRATEGIES.WINDOW]}\n` +
|
|
216
|
+
LISTING_HINT);
|
|
217
|
+
}
|
|
218
|
+
/** `{ kind: 'hybrid' }` — the field-report shape, and its neighbours. */
|
|
219
|
+
function hybridWithoutStrategies(subs, site, path) {
|
|
220
|
+
const written = !Array.isArray(subs)
|
|
221
|
+
? subs === undefined
|
|
222
|
+
? 'none was passed'
|
|
223
|
+
: `\`strategies\` is not an array (got \`${typeof subs}\`)`
|
|
224
|
+
: 'the array is empty';
|
|
225
|
+
return (`${site}: the \`hybrid\` strategy needs a non-empty \`strategies\` array and ${written} — a ` +
|
|
226
|
+
`hybrid is defined by the strategies it composes, so an empty one has nothing to compose ` +
|
|
227
|
+
`and no behaviour of its own.\n` +
|
|
228
|
+
` Fix: ${path}: { kind: 'hybrid', strategies: [{ kind: 'window', size: 20 }, ` +
|
|
229
|
+
`{ kind: 'topK', topK: 3, embedder }] }\n` +
|
|
230
|
+
` Or: drop \`hybrid\` and name the one rule you meant, e.g. ` +
|
|
231
|
+
`${EXAMPLE[MEMORY_STRATEGIES.WINDOW]}.\n` +
|
|
232
|
+
LISTING_HINT);
|
|
233
|
+
}
|
|
234
|
+
// ─── The guard that makes the declaration true ──────────────────────
|
|
235
|
+
/**
|
|
236
|
+
* Refuse a config whose strategy declares a requirement the caller did not
|
|
237
|
+
* supply — by name, at BUILD, with the fix in the message.
|
|
238
|
+
*
|
|
239
|
+
* WHY it runs LAST, after the pipeline dispatch rather than before it: the
|
|
240
|
+
* dispatch's own refusals know more than this one does. `defineMemory`'s
|
|
241
|
+
* TOP_K arm knows about the `ranksBy: 'server-text'` exemption AND about the
|
|
242
|
+
* write half it takes away; the CAUSAL arm knows that exemption does not
|
|
243
|
+
* apply to it; the EXTRACT arm knows the `llm` matters only for
|
|
244
|
+
* `extractor: 'llm'`. Speaking first would replace those messages with a
|
|
245
|
+
* blunter one. This is the BACKSTOP: it says something only when nothing
|
|
246
|
+
* better already did, and its job is that no declared requirement can go
|
|
247
|
+
* unchecked — the failure it exists to prevent is a missing dependency
|
|
248
|
+
* surfacing as a `TypeError` from five frames inside a stage, halfway
|
|
249
|
+
* through a paid run.
|
|
250
|
+
*
|
|
251
|
+
* @param options the config as written by the caller.
|
|
252
|
+
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
|
|
253
|
+
*/
|
|
254
|
+
export function assertStrategyRequirements(options, site) {
|
|
255
|
+
checkStrategy(options.strategy, options.type, options.store, site, undefined);
|
|
256
|
+
}
|
|
257
|
+
function checkStrategy(strategy, type, store, site, insideHybrid) {
|
|
258
|
+
const info = memoryStrategyInfo(strategy.kind);
|
|
259
|
+
// An unknown kind, or a kind this TYPE does not accept, is not this guard's
|
|
260
|
+
// business — the dispatch refuses both, and names the alternative.
|
|
261
|
+
if (!info || !info.types.includes(type))
|
|
262
|
+
return;
|
|
263
|
+
for (const requirement of info.requirements) {
|
|
264
|
+
if (isSatisfied(requirement, strategy, type, store, site))
|
|
265
|
+
continue;
|
|
266
|
+
throw new Error(missingRequirement(requirement, info, site, insideHybrid === true));
|
|
267
|
+
}
|
|
268
|
+
if (strategy.kind === MEMORY_STRATEGIES.HYBRID) {
|
|
269
|
+
// A hybrid is only as runnable as its parts, and the pipelines it builds
|
|
270
|
+
// do not all read every part — so the parts are checked HERE, where the
|
|
271
|
+
// caller's words still exist, rather than trusted to whatever the
|
|
272
|
+
// composed pipeline happens to consume.
|
|
273
|
+
for (const sub of strategy.strategies)
|
|
274
|
+
checkStrategy(sub, type, store, site, true);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function isSatisfied(requirement, strategy, type, store, site) {
|
|
278
|
+
// Read through a structural shape: the union's arms declare each other's
|
|
279
|
+
// fields as `never`, so the union itself cannot be asked these questions.
|
|
280
|
+
const s = strategy;
|
|
281
|
+
switch (requirement) {
|
|
282
|
+
case 'embedder':
|
|
283
|
+
// The one exemption, and it is a fact about the STORE: a backend that
|
|
284
|
+
// ranks TEXT on its own side has nothing here to embed. It does not
|
|
285
|
+
// extend to CAUSAL, which matches a stored query VECTOR — there is
|
|
286
|
+
// always a vector to produce there.
|
|
287
|
+
if (type !== MEMORY_TYPES.CAUSAL && resolveRankingMode(store, site) === 'server-text') {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return s.embedder !== undefined;
|
|
291
|
+
case 'vector-store':
|
|
292
|
+
return typeof store.search === 'function';
|
|
293
|
+
case 'llm':
|
|
294
|
+
return s.llm !== undefined;
|
|
295
|
+
default:
|
|
296
|
+
// A requirement this library does not know how to check is not a
|
|
297
|
+
// requirement it may fail the caller over.
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function missingRequirement(requirement, info, site, insideHybrid) {
|
|
302
|
+
const where = insideHybrid
|
|
303
|
+
? `the \`${info.kind}\` strategy inside \`hybrid\``
|
|
304
|
+
: `the \`${info.kind}\` strategy`;
|
|
305
|
+
const declared = ` Every strategy declares what it needs — \`listMemoryStrategies()\` reports ` +
|
|
306
|
+
`\`requirements: [${info.requirements.map((r) => `'${r}'`).join(', ')}]\` for this one, so a ` +
|
|
307
|
+
`host can check before it offers the choice.\n`;
|
|
308
|
+
switch (requirement) {
|
|
309
|
+
case 'embedder':
|
|
310
|
+
return (`${site}: ${where} needs an \`embedder\` and none was passed — somebody has to turn ` +
|
|
311
|
+
`the query into a vector before a store can rank it.\n` +
|
|
312
|
+
declared +
|
|
313
|
+
` Fix: pass \`embedder\` on the strategy (\`mockEmbedder()\` for dev/tests), or omit ` +
|
|
314
|
+
`it only for a store that declares \`ranksBy: 'server-text'\` and embeds on its own side.`);
|
|
315
|
+
case 'vector-store':
|
|
316
|
+
return (`${site}: ${where} needs a store that can \`search()\`, and the store you passed does ` +
|
|
317
|
+
`not implement one — nothing would ever rank the entries written to it.\n` +
|
|
318
|
+
declared +
|
|
319
|
+
` Fix: pass a vector-capable store — \`InMemoryStore\` (dev/tests), ` +
|
|
320
|
+
`\`sqliteVectorStore\` (durable, one file), \`pgVectorStore\`, \`s3VectorsStore\`, or any ` +
|
|
321
|
+
`adapter that ranks the vectors you give it.\n` +
|
|
322
|
+
` Or: keep this store and pick a strategy that ranks nothing — \`window\` (last N) ` +
|
|
323
|
+
`or \`budget\` (fit-to-tokens).`);
|
|
324
|
+
case 'llm':
|
|
325
|
+
return (`${site}: ${where} names an \`llm\` and none was passed.\n` +
|
|
326
|
+
declared +
|
|
327
|
+
` Fix: pass \`llm\` on the strategy — a cheap model is the point of it.\n` +
|
|
328
|
+
` Or: use \`{ kind: 'window', size: <recent> }\`, which needs nothing, and which is ` +
|
|
329
|
+
`what this strategy's read path does today (its compression stage is not yet wired).`);
|
|
330
|
+
default:
|
|
331
|
+
return (`${site}: ${where} declares \`${requirement}\` and it was not supplied.\n` +
|
|
332
|
+
declared +
|
|
333
|
+
` Fix: supply it, or pick a strategy whose \`requirements\` are empty.`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=strategies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategies.js","sourceRoot":"","sources":["../../../src/memory/strategies.ts"],"names":[],"mappings":"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"}
|
package/dist/memory/define.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
32
|
exports.unwrapMemoryFlowChart = exports.defineMemory = void 0;
|
|
33
33
|
const asRoleRefusal_js_1 = require("./asRoleRefusal.js");
|
|
34
|
+
const strategies_js_1 = require("./strategies.js");
|
|
34
35
|
const capability_js_1 = require("./store/capability.js");
|
|
35
36
|
const default_js_1 = require("./pipeline/default.js");
|
|
36
37
|
const ephemeral_js_1 = require("./pipeline/ephemeral.js");
|
|
@@ -54,7 +55,8 @@ const define_types_js_1 = require("./define.types.js");
|
|
|
54
55
|
* | --------- | ------------- | ------------------------ |
|
|
55
56
|
* | EPISODIC | WINDOW | defaultPipeline |
|
|
56
57
|
* | EPISODIC | BUDGET | defaultPipeline |
|
|
57
|
-
* | EPISODIC | SUMMARIZE | defaultPipeline
|
|
58
|
+
* | EPISODIC | SUMMARIZE | defaultPipeline (the compression stage is NOT composed in yet — see `listMemoryStrategies()`) |
|
|
59
|
+
* | EPISODIC | DECAY | defaultPipeline + filterByDecay stage |
|
|
58
60
|
* | SEMANTIC | TOP_K | semanticPipeline |
|
|
59
61
|
* | SEMANTIC | EXTRACT | factPipeline |
|
|
60
62
|
* | SEMANTIC | WINDOW | factPipeline (recency-load) |
|
|
@@ -69,6 +71,14 @@ const define_types_js_1 = require("./define.types.js");
|
|
|
69
71
|
function defineMemory(options) {
|
|
70
72
|
validate(options);
|
|
71
73
|
const pipeline = buildPipeline(options);
|
|
74
|
+
// The declared-requirements backstop (9.5.0), deliberately AFTER the
|
|
75
|
+
// dispatch: the arms above know things this check does not (the
|
|
76
|
+
// server-text exemption, that it does not apply to CAUSAL, that EXTRACT's
|
|
77
|
+
// `llm` matters only for `extractor: 'llm'`), so they speak first and this
|
|
78
|
+
// says something only when nothing better did. What it guarantees is that
|
|
79
|
+
// no requirement `listMemoryStrategies()` DECLARES can go unchecked —
|
|
80
|
+
// including the ones a composed pipeline would have quietly ignored.
|
|
81
|
+
(0, strategies_js_1.assertStrategyRequirements)(options, `defineMemory[${options.id}]`);
|
|
72
82
|
// `readOnly` drops the write half entirely (8.8.0). Not "writes are
|
|
73
83
|
// skipped at runtime" — the subflow is never compiled and never mounted,
|
|
74
84
|
// so a read-only memory has no write stage in its chart, no write commit
|
|
@@ -105,6 +115,14 @@ function validate(options) {
|
|
|
105
115
|
throw new Error(`defineMemory[id=${options.id}]: \`store\` is required. ` +
|
|
106
116
|
'Pass `new InMemoryStore()` for dev/tests, or a backed store for production.');
|
|
107
117
|
}
|
|
118
|
+
// SHAPE before anything reads a field (9.5.1). Both the dispatch below and
|
|
119
|
+
// the requirements walk read fields off the strategy — `h.strategies[0]`,
|
|
120
|
+
// `for (const sub of strategy.strategies)` — and a field read off a shape
|
|
121
|
+
// that never had it is a `TypeError` from inside this library, naming
|
|
122
|
+
// neither the option the caller got wrong nor the one they meant.
|
|
123
|
+
// Field-reported: `{ kind: 'hybrid', size: 5 }` produced `Cannot read
|
|
124
|
+
// properties of undefined (reading '0')`.
|
|
125
|
+
(0, strategies_js_1.assertStrategyShape)(options.strategy, `defineMemory[${options.id}]`);
|
|
108
126
|
// The shorthand and the spelled-out rule EXCLUDE. Accepting both would
|
|
109
127
|
// mean one of two numbers silently loses, and the recording would name
|
|
110
128
|
// a `k` the run did not use.
|
|
@@ -164,10 +182,18 @@ function buildEpisodicPipeline(options) {
|
|
|
164
182
|
return (0, default_js_1.defaultPipeline)(config);
|
|
165
183
|
}
|
|
166
184
|
case define_types_js_1.MEMORY_STRATEGIES.SUMMARIZE: {
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// strategy carries an `llm`
|
|
185
|
+
// WHAT THIS ACTUALLY DOES, as of 9.5.0: loads the last `recent` turns
|
|
186
|
+
// and stops. The `summarize` stage exists (stages/summarize.ts) and is
|
|
187
|
+
// composed into NOTHING — the comment that used to sit here said the
|
|
188
|
+
// wire helpers add it "when the strategy carries an `llm`", and they
|
|
189
|
+
// never have. Verified by counting calls: eight turns through this
|
|
190
|
+
// pipeline with a counting provider, zero `complete()` calls.
|
|
191
|
+
// It is left as-is rather than half-wired because the compressor
|
|
192
|
+
// needs things this strategy does not carry (a model name, cost
|
|
193
|
+
// accounting, the separate-instance law `.compaction()` enforces), and
|
|
194
|
+
// the Agent already has that door. `listMemoryStrategies()` says so in
|
|
195
|
+
// the strategy's own description so a reader learns it from the
|
|
196
|
+
// library rather than from a token bill.
|
|
171
197
|
const sum = s;
|
|
172
198
|
const config = { store: options.store, loadCount: sum.recent };
|
|
173
199
|
return (0, default_js_1.defaultPipeline)(config);
|
|
@@ -180,6 +206,10 @@ function buildEpisodicPipeline(options) {
|
|
|
180
206
|
const h = s;
|
|
181
207
|
const inner = h.strategies[0];
|
|
182
208
|
if (!inner) {
|
|
209
|
+
// Unreachable since 9.5.1 — `assertStrategyShape` refuses a missing,
|
|
210
|
+
// non-array or empty `strategies` in `validate()`, with the field
|
|
211
|
+
// named and a working line in the message. Kept as the index-access
|
|
212
|
+
// backstop the type system asks for, not as the message a caller sees.
|
|
183
213
|
throw new Error(`defineMemory[${options.id}]: HYBRID strategy requires at least one sub-strategy.`);
|
|
184
214
|
}
|
|
185
215
|
return buildEpisodicPipeline({ ...options, strategy: inner });
|
|
@@ -190,9 +220,28 @@ function buildEpisodicPipeline(options) {
|
|
|
190
220
|
case define_types_js_1.MEMORY_STRATEGIES.TOP_K:
|
|
191
221
|
throw new Error(`defineMemory[${options.id}]: TOP_K strategy on EPISODIC type requires a vector store. ` +
|
|
192
222
|
'Use type=SEMANTIC for vector retrieval, or type=EPISODIC with strategy=WINDOW for recency.');
|
|
193
|
-
case define_types_js_1.MEMORY_STRATEGIES.DECAY:
|
|
194
|
-
|
|
195
|
-
|
|
223
|
+
case define_types_js_1.MEMORY_STRATEGIES.DECAY: {
|
|
224
|
+
// Wired in 9.5.0. Until then this arm threw "not yet wired" while
|
|
225
|
+
// `MEMORY_STRATEGIES` went on offering the choice — a const that
|
|
226
|
+
// advertised seven strategies and built six.
|
|
227
|
+
const d = s;
|
|
228
|
+
if (!Number.isFinite(d.halfLifeMs) || d.halfLifeMs < 0) {
|
|
229
|
+
throw new Error(`defineMemory[${options.id}]: DECAY needs a \`halfLifeMs\` that is a non-negative ` +
|
|
230
|
+
`number of milliseconds — how long before an untouched entry is worth half as ` +
|
|
231
|
+
`much. Got \`${String(d.halfLifeMs)}\`.\n` +
|
|
232
|
+
` A negative half-life inverts the curve (older scores HIGHER), which no config ` +
|
|
233
|
+
`means to say, so it is refused rather than obeyed.\n` +
|
|
234
|
+
` Fix: a day is \`86_400_000\`; an hour is \`3_600_000\`.`);
|
|
235
|
+
}
|
|
236
|
+
const config = {
|
|
237
|
+
store: options.store,
|
|
238
|
+
decay: {
|
|
239
|
+
halfLifeMs: d.halfLifeMs,
|
|
240
|
+
...(d.minScore !== undefined && { minScore: d.minScore }),
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
return (0, default_js_1.defaultPipeline)(config);
|
|
244
|
+
}
|
|
196
245
|
default: {
|
|
197
246
|
const _exhaustive = s;
|
|
198
247
|
void _exhaustive;
|