@framers/agentos 0.1.247 → 0.1.249
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/memory/CognitiveMemoryManager.d.ts +47 -0
- package/dist/memory/CognitiveMemoryManager.d.ts.map +1 -1
- package/dist/memory/CognitiveMemoryManager.js +54 -0
- package/dist/memory/CognitiveMemoryManager.js.map +1 -1
- package/dist/memory/core/types.d.ts +31 -0
- package/dist/memory/core/types.d.ts.map +1 -1
- package/dist/memory/index.d.ts +13 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +24 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/ingest/SessionSummarizer.d.ts +155 -0
- package/dist/memory/ingest/SessionSummarizer.d.ts.map +1 -0
- package/dist/memory/ingest/SessionSummarizer.js +178 -0
- package/dist/memory/ingest/SessionSummarizer.js.map +1 -0
- package/dist/memory/retrieval/fact-supersession/FactSupersession.d.ts +82 -0
- package/dist/memory/retrieval/fact-supersession/FactSupersession.d.ts.map +1 -0
- package/dist/memory/retrieval/fact-supersession/FactSupersession.js +159 -0
- package/dist/memory/retrieval/fact-supersession/FactSupersession.js.map +1 -0
- package/dist/memory/retrieval/fact-supersession/index.d.ts +10 -0
- package/dist/memory/retrieval/fact-supersession/index.d.ts.map +1 -0
- package/dist/memory/retrieval/fact-supersession/index.js +9 -0
- package/dist/memory/retrieval/fact-supersession/index.js.map +1 -0
- package/dist/memory/retrieval/hybrid/HybridRetriever.d.ts +151 -0
- package/dist/memory/retrieval/hybrid/HybridRetriever.d.ts.map +1 -0
- package/dist/memory/retrieval/hybrid/HybridRetriever.js +287 -0
- package/dist/memory/retrieval/hybrid/HybridRetriever.js.map +1 -0
- package/dist/memory/retrieval/hybrid/index.d.ts +12 -0
- package/dist/memory/retrieval/hybrid/index.d.ts.map +1 -0
- package/dist/memory/retrieval/hybrid/index.js +10 -0
- package/dist/memory/retrieval/hybrid/index.js.map +1 -0
- package/dist/memory/retrieval/hybrid/reciprocalRankFusion.d.ts +87 -0
- package/dist/memory/retrieval/hybrid/reciprocalRankFusion.d.ts.map +1 -0
- package/dist/memory/retrieval/hybrid/reciprocalRankFusion.js +88 -0
- package/dist/memory/retrieval/hybrid/reciprocalRankFusion.js.map +1 -0
- package/dist/memory/retrieval/session/SessionRetriever.d.ts +123 -0
- package/dist/memory/retrieval/session/SessionRetriever.d.ts.map +1 -0
- package/dist/memory/retrieval/session/SessionRetriever.js +220 -0
- package/dist/memory/retrieval/session/SessionRetriever.js.map +1 -0
- package/dist/memory/retrieval/session/SessionSummaryStore.d.ts +122 -0
- package/dist/memory/retrieval/session/SessionSummaryStore.d.ts.map +1 -0
- package/dist/memory/retrieval/session/SessionSummaryStore.js +142 -0
- package/dist/memory/retrieval/session/SessionSummaryStore.js.map +1 -0
- package/dist/memory/retrieval/session/index.d.ts +12 -0
- package/dist/memory/retrieval/session/index.d.ts.map +1 -0
- package/dist/memory/retrieval/session/index.js +10 -0
- package/dist/memory/retrieval/session/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SessionSummarizer.ts
|
|
3
|
+
* @description Session-level contextual retrieval (Anthropic Sep 2024
|
|
4
|
+
* variant, adapted to conversational memory).
|
|
5
|
+
*
|
|
6
|
+
* ## What this does
|
|
7
|
+
*
|
|
8
|
+
* For each session in a benchmark case (e.g. a `conv-26` chat thread in
|
|
9
|
+
* LOCOMO or a `haystack_sessions[i]` entry in LongMemEval), an LLM
|
|
10
|
+
* generates a dense 50–100 token summary that captures the topic,
|
|
11
|
+
* key user-stated facts (names, numbers, dates, preferences), and key
|
|
12
|
+
* assistant-stated facts. That summary is then prepended to *every*
|
|
13
|
+
* chunk produced from that session before embedding — giving the
|
|
14
|
+
* embedding vector global session context it would otherwise lack.
|
|
15
|
+
*
|
|
16
|
+
* ## Why session-granularity (not per-chunk)
|
|
17
|
+
*
|
|
18
|
+
* Anthropic's canonical
|
|
19
|
+
* {@link https://www.anthropic.com/news/contextual-retrieval Contextual Retrieval}
|
|
20
|
+
* prepends context *per chunk*. That's right for heterogeneous documents
|
|
21
|
+
* where each chunk might cover a different topic. Conversational data is
|
|
22
|
+
* different: a session is a topically-coherent thread. Adjacent chunks in
|
|
23
|
+
* the same session share context, so per-chunk contextualization would
|
|
24
|
+
* fire ~10× as many LLM calls at the *same* summarization model for the
|
|
25
|
+
* same downstream embedding benefit — this is a same-model
|
|
26
|
+
* granularity comparison, not a cross-reader pricing claim.
|
|
27
|
+
*
|
|
28
|
+
* The closest industry analog is Mastra Observational Memory's Observer
|
|
29
|
+
* phase (rewrites full messages into dense observations). Ours is a
|
|
30
|
+
* lighter variant — we *prepend* a summary to preserve the original
|
|
31
|
+
* chunk text verbatim, rather than rewriting it.
|
|
32
|
+
*
|
|
33
|
+
* ## Caching
|
|
34
|
+
*
|
|
35
|
+
* Summaries are cached to disk under `<cacheDir>/<sha256-hex>.txt`. The
|
|
36
|
+
* cache key hashes the session text, model id, and template version so
|
|
37
|
+
* any of those three changing invalidates the cache cleanly. Mirrors
|
|
38
|
+
* the {@link CachedEmbedder} pattern for consistency.
|
|
39
|
+
*
|
|
40
|
+
* ## Cost
|
|
41
|
+
*
|
|
42
|
+
* Single LLM call per unique session. For LongMemEval-S (~50 sessions
|
|
43
|
+
* per case × 500 cases) with gpt-5-mini / Haiku pricing:
|
|
44
|
+
* ~25,000 calls × 50–100 output tokens × 2,000 input tokens (session)
|
|
45
|
+
* ≈ $50–90 one-time across all cases. Cached thereafter.
|
|
46
|
+
*
|
|
47
|
+
* ## Expected lift
|
|
48
|
+
*
|
|
49
|
+
* Anthropic's published numbers: −49% retrieval failure with contextual
|
|
50
|
+
* embeddings alone, −67% when combined with reranking. We already have
|
|
51
|
+
* Cohere rerank-v3.5 wired, so the upper bound applies. Our Phase A
|
|
52
|
+
* multi-session ceiling of 50% is the main target; expected lift
|
|
53
|
+
* +8–15pp on multi-session categories across LongMemEval and LOCOMO.
|
|
54
|
+
*
|
|
55
|
+
* @module agentos-bench/cognitive/SessionSummarizer
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Callable that invokes a chat LLM given a system + user prompt and
|
|
59
|
+
* returns the generated text. The bench constructs one from the
|
|
60
|
+
* existing {@link IReader} so summarization reuses the same pricing +
|
|
61
|
+
* timeout plumbing as the benchmark's reader.
|
|
62
|
+
*/
|
|
63
|
+
export type SessionSummarizerInvoker = (system: string, user: string) => Promise<{
|
|
64
|
+
text: string;
|
|
65
|
+
tokensIn: number;
|
|
66
|
+
tokensOut: number;
|
|
67
|
+
model: string;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Options for constructing a {@link SessionSummarizer}.
|
|
71
|
+
*/
|
|
72
|
+
export interface SessionSummarizerOptions {
|
|
73
|
+
/** LLM invoker — produces the summary text. */
|
|
74
|
+
invoker: SessionSummarizerInvoker;
|
|
75
|
+
/**
|
|
76
|
+
* Optional directory for persistent disk cache. When set, summaries
|
|
77
|
+
* survive across process restarts and re-runs. Mirrors the
|
|
78
|
+
* {@link CachedEmbedder} cache layout.
|
|
79
|
+
*/
|
|
80
|
+
cacheDir?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Model identifier baked into the cache key so switching models
|
|
83
|
+
* invalidates the cache automatically. Should match the invoker's
|
|
84
|
+
* underlying model.
|
|
85
|
+
*/
|
|
86
|
+
modelId: string;
|
|
87
|
+
/**
|
|
88
|
+
* Maximum tokens to ask the LLM to emit. Default 140 (generous headroom
|
|
89
|
+
* over the 50–100 target; truncate post-hoc if needed).
|
|
90
|
+
*/
|
|
91
|
+
maxTokens?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Template version. Bump whenever the summarization prompt changes so
|
|
94
|
+
* disk caches from prior versions are invalidated.
|
|
95
|
+
* Current: `'v1-2026-04-19'`.
|
|
96
|
+
*/
|
|
97
|
+
templateVersion?: string;
|
|
98
|
+
/** Optional cost-tracker hook. Called after every uncached call. */
|
|
99
|
+
onCallCost?: (tokensIn: number, tokensOut: number, model: string) => void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Summary cache stats for diagnostics / budget tracking.
|
|
103
|
+
*/
|
|
104
|
+
export interface SummarizerStats {
|
|
105
|
+
hits: number;
|
|
106
|
+
misses: number;
|
|
107
|
+
writes: number;
|
|
108
|
+
/** Total tokens consumed on uncached LLM calls. */
|
|
109
|
+
tokensIn: number;
|
|
110
|
+
tokensOut: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* LLM-backed session summarizer with a persistent on-disk cache.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const summarizer = new SessionSummarizer({
|
|
118
|
+
* invoker: async (system, user) => {
|
|
119
|
+
* const resp = await reader.invoke({ system, user, maxTokens: 140, temperature: 0 });
|
|
120
|
+
* return { text: resp.text, tokensIn: resp.tokensIn, tokensOut: resp.tokensOut, model: resp.model };
|
|
121
|
+
* },
|
|
122
|
+
* cacheDir: '/path/to/data/.session-summary-cache',
|
|
123
|
+
* modelId: 'gpt-5-mini',
|
|
124
|
+
* });
|
|
125
|
+
*
|
|
126
|
+
* const summary = await summarizer.summarize('conv-26-session-3', sessionText);
|
|
127
|
+
* // => "User discussed adopting a new rescue dog from a Portland shelter..."
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare class SessionSummarizer {
|
|
131
|
+
private readonly opts;
|
|
132
|
+
/** Running stats for diagnostics. */
|
|
133
|
+
readonly stats: SummarizerStats;
|
|
134
|
+
private readonly systemPrompt;
|
|
135
|
+
private readonly templateVersion;
|
|
136
|
+
private readonly maxTokens;
|
|
137
|
+
constructor(opts: SessionSummarizerOptions);
|
|
138
|
+
/**
|
|
139
|
+
* Summarize a single session. Returns cached result if available,
|
|
140
|
+
* otherwise calls the LLM and writes to cache.
|
|
141
|
+
*
|
|
142
|
+
* @param sessionKey — a stable identifier for the session (e.g. `${caseId}:${sessionId}`).
|
|
143
|
+
* Used only for logging; the cache key is content-addressed.
|
|
144
|
+
* @param sessionText — the raw text of the session (all turns concatenated).
|
|
145
|
+
*/
|
|
146
|
+
summarize(_sessionKey: string, sessionText: string): Promise<string>;
|
|
147
|
+
/**
|
|
148
|
+
* Build the SHA-256 cache key from session content + model + template.
|
|
149
|
+
* Exposed for tests; callers should use {@link summarize}.
|
|
150
|
+
*/
|
|
151
|
+
computeCacheKey(sessionText: string): string;
|
|
152
|
+
/** Expose the resolved template version — useful for cache-key fingerprints in other layers. */
|
|
153
|
+
getTemplateVersion(): string;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=SessionSummarizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionSummarizer.d.ts","sourceRoot":"","sources":["../../../src/memory/ingest/SessionSummarizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAMH;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,KACT,OAAO,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+CAA+C;IAC/C,OAAO,EAAE,wBAAwB,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAgBD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAiB;IAchB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAbjC,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAM7B;IAEF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEN,IAAI,EAAE,wBAAwB;IAM3D;;;;;;;OAOG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqD1E;;;OAGG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAU5C,gGAAgG;IAChG,kBAAkB,IAAI,MAAM;CAG7B"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SessionSummarizer.ts
|
|
3
|
+
* @description Session-level contextual retrieval (Anthropic Sep 2024
|
|
4
|
+
* variant, adapted to conversational memory).
|
|
5
|
+
*
|
|
6
|
+
* ## What this does
|
|
7
|
+
*
|
|
8
|
+
* For each session in a benchmark case (e.g. a `conv-26` chat thread in
|
|
9
|
+
* LOCOMO or a `haystack_sessions[i]` entry in LongMemEval), an LLM
|
|
10
|
+
* generates a dense 50–100 token summary that captures the topic,
|
|
11
|
+
* key user-stated facts (names, numbers, dates, preferences), and key
|
|
12
|
+
* assistant-stated facts. That summary is then prepended to *every*
|
|
13
|
+
* chunk produced from that session before embedding — giving the
|
|
14
|
+
* embedding vector global session context it would otherwise lack.
|
|
15
|
+
*
|
|
16
|
+
* ## Why session-granularity (not per-chunk)
|
|
17
|
+
*
|
|
18
|
+
* Anthropic's canonical
|
|
19
|
+
* {@link https://www.anthropic.com/news/contextual-retrieval Contextual Retrieval}
|
|
20
|
+
* prepends context *per chunk*. That's right for heterogeneous documents
|
|
21
|
+
* where each chunk might cover a different topic. Conversational data is
|
|
22
|
+
* different: a session is a topically-coherent thread. Adjacent chunks in
|
|
23
|
+
* the same session share context, so per-chunk contextualization would
|
|
24
|
+
* fire ~10× as many LLM calls at the *same* summarization model for the
|
|
25
|
+
* same downstream embedding benefit — this is a same-model
|
|
26
|
+
* granularity comparison, not a cross-reader pricing claim.
|
|
27
|
+
*
|
|
28
|
+
* The closest industry analog is Mastra Observational Memory's Observer
|
|
29
|
+
* phase (rewrites full messages into dense observations). Ours is a
|
|
30
|
+
* lighter variant — we *prepend* a summary to preserve the original
|
|
31
|
+
* chunk text verbatim, rather than rewriting it.
|
|
32
|
+
*
|
|
33
|
+
* ## Caching
|
|
34
|
+
*
|
|
35
|
+
* Summaries are cached to disk under `<cacheDir>/<sha256-hex>.txt`. The
|
|
36
|
+
* cache key hashes the session text, model id, and template version so
|
|
37
|
+
* any of those three changing invalidates the cache cleanly. Mirrors
|
|
38
|
+
* the {@link CachedEmbedder} pattern for consistency.
|
|
39
|
+
*
|
|
40
|
+
* ## Cost
|
|
41
|
+
*
|
|
42
|
+
* Single LLM call per unique session. For LongMemEval-S (~50 sessions
|
|
43
|
+
* per case × 500 cases) with gpt-5-mini / Haiku pricing:
|
|
44
|
+
* ~25,000 calls × 50–100 output tokens × 2,000 input tokens (session)
|
|
45
|
+
* ≈ $50–90 one-time across all cases. Cached thereafter.
|
|
46
|
+
*
|
|
47
|
+
* ## Expected lift
|
|
48
|
+
*
|
|
49
|
+
* Anthropic's published numbers: −49% retrieval failure with contextual
|
|
50
|
+
* embeddings alone, −67% when combined with reranking. We already have
|
|
51
|
+
* Cohere rerank-v3.5 wired, so the upper bound applies. Our Phase A
|
|
52
|
+
* multi-session ceiling of 50% is the main target; expected lift
|
|
53
|
+
* +8–15pp on multi-session categories across LongMemEval and LOCOMO.
|
|
54
|
+
*
|
|
55
|
+
* @module agentos-bench/cognitive/SessionSummarizer
|
|
56
|
+
*/
|
|
57
|
+
import { promises as fs } from 'node:fs';
|
|
58
|
+
import { createHash } from 'node:crypto';
|
|
59
|
+
import path from 'node:path';
|
|
60
|
+
/** Default summarization prompt — see file docstring for rationale. */
|
|
61
|
+
const DEFAULT_SYSTEM_PROMPT = [
|
|
62
|
+
'You produce a concise search-retrieval summary of a conversation session.',
|
|
63
|
+
'Your output will be prepended to individual turn-level chunks before vector embedding, so the embedding captures the session-wide context each chunk alone would miss.',
|
|
64
|
+
'Target length: 50–100 tokens. No preamble, no sign-off — emit only the summary.',
|
|
65
|
+
'Structure the summary as dense prose:',
|
|
66
|
+
' 1. The topic or theme of the session (one short clause).',
|
|
67
|
+
' 2. The specific facts the user stated — names, numbers, dates, preferences, decisions, named items (e.g. "Wells Fargo mortgage", "turbinado sugar", "mid-century dresser").',
|
|
68
|
+
' 3. The specific facts the assistant stated, suggested, or provided (numbers, recommendations, named entities).',
|
|
69
|
+
'Be concrete. Use exact nouns and numbers from the conversation. Do not generalize. Do not editorialize.',
|
|
70
|
+
].join(' ');
|
|
71
|
+
const DEFAULT_TEMPLATE_VERSION = 'v1-2026-04-19';
|
|
72
|
+
/**
|
|
73
|
+
* LLM-backed session summarizer with a persistent on-disk cache.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const summarizer = new SessionSummarizer({
|
|
78
|
+
* invoker: async (system, user) => {
|
|
79
|
+
* const resp = await reader.invoke({ system, user, maxTokens: 140, temperature: 0 });
|
|
80
|
+
* return { text: resp.text, tokensIn: resp.tokensIn, tokensOut: resp.tokensOut, model: resp.model };
|
|
81
|
+
* },
|
|
82
|
+
* cacheDir: '/path/to/data/.session-summary-cache',
|
|
83
|
+
* modelId: 'gpt-5-mini',
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* const summary = await summarizer.summarize('conv-26-session-3', sessionText);
|
|
87
|
+
* // => "User discussed adopting a new rescue dog from a Portland shelter..."
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export class SessionSummarizer {
|
|
91
|
+
constructor(opts) {
|
|
92
|
+
this.opts = opts;
|
|
93
|
+
/** Running stats for diagnostics. */
|
|
94
|
+
this.stats = {
|
|
95
|
+
hits: 0,
|
|
96
|
+
misses: 0,
|
|
97
|
+
writes: 0,
|
|
98
|
+
tokensIn: 0,
|
|
99
|
+
tokensOut: 0,
|
|
100
|
+
};
|
|
101
|
+
this.systemPrompt = DEFAULT_SYSTEM_PROMPT;
|
|
102
|
+
this.templateVersion = opts.templateVersion ?? DEFAULT_TEMPLATE_VERSION;
|
|
103
|
+
this.maxTokens = opts.maxTokens ?? 140;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Summarize a single session. Returns cached result if available,
|
|
107
|
+
* otherwise calls the LLM and writes to cache.
|
|
108
|
+
*
|
|
109
|
+
* @param sessionKey — a stable identifier for the session (e.g. `${caseId}:${sessionId}`).
|
|
110
|
+
* Used only for logging; the cache key is content-addressed.
|
|
111
|
+
* @param sessionText — the raw text of the session (all turns concatenated).
|
|
112
|
+
*/
|
|
113
|
+
async summarize(_sessionKey, sessionText) {
|
|
114
|
+
const trimmed = sessionText.trim();
|
|
115
|
+
if (!trimmed)
|
|
116
|
+
return '';
|
|
117
|
+
const cacheKey = this.computeCacheKey(trimmed);
|
|
118
|
+
// Try disk cache
|
|
119
|
+
if (this.opts.cacheDir) {
|
|
120
|
+
const cachePath = path.join(this.opts.cacheDir, `${cacheKey}.txt`);
|
|
121
|
+
try {
|
|
122
|
+
const cached = await fs.readFile(cachePath, 'utf8');
|
|
123
|
+
this.stats.hits += 1;
|
|
124
|
+
return cached;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// File doesn't exist or can't be read — fall through to LLM call.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.stats.misses += 1;
|
|
131
|
+
const response = await this.opts.invoker(this.systemPrompt, trimmed);
|
|
132
|
+
const summary = response.text.trim();
|
|
133
|
+
this.stats.tokensIn += response.tokensIn;
|
|
134
|
+
this.stats.tokensOut += response.tokensOut;
|
|
135
|
+
if (this.opts.onCallCost) {
|
|
136
|
+
this.opts.onCallCost(response.tokensIn, response.tokensOut, response.model);
|
|
137
|
+
}
|
|
138
|
+
// Persist to disk cache (best-effort; exclusive create so concurrent
|
|
139
|
+
// writers don't tear). Non-fatal on write failure — caller still
|
|
140
|
+
// gets the summary for this call.
|
|
141
|
+
if (this.opts.cacheDir) {
|
|
142
|
+
try {
|
|
143
|
+
await fs.mkdir(this.opts.cacheDir, { recursive: true });
|
|
144
|
+
const cachePath = path.join(this.opts.cacheDir, `${cacheKey}.txt`);
|
|
145
|
+
await fs.writeFile(cachePath, summary, { encoding: 'utf8', flag: 'wx' });
|
|
146
|
+
this.stats.writes += 1;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const code = err?.code;
|
|
150
|
+
// EEXIST means another writer got there first — their content is
|
|
151
|
+
// valid. Any other error is logged but non-fatal.
|
|
152
|
+
if (code !== 'EEXIST') {
|
|
153
|
+
// eslint-disable-next-line no-console
|
|
154
|
+
console.warn(`[SessionSummarizer] Failed to write cache for key ${cacheKey.slice(0, 8)}...: ${String(err)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return summary;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the SHA-256 cache key from session content + model + template.
|
|
162
|
+
* Exposed for tests; callers should use {@link summarize}.
|
|
163
|
+
*/
|
|
164
|
+
computeCacheKey(sessionText) {
|
|
165
|
+
return createHash('sha256')
|
|
166
|
+
.update(this.opts.modelId)
|
|
167
|
+
.update('\n')
|
|
168
|
+
.update(this.templateVersion)
|
|
169
|
+
.update('\n')
|
|
170
|
+
.update(sessionText)
|
|
171
|
+
.digest('hex');
|
|
172
|
+
}
|
|
173
|
+
/** Expose the resolved template version — useful for cache-key fingerprints in other layers. */
|
|
174
|
+
getTemplateVersion() {
|
|
175
|
+
return this.templateVersion;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=SessionSummarizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionSummarizer.js","sourceRoot":"","sources":["../../../src/memory/ingest/SessionSummarizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file FactSupersession.ts
|
|
3
|
+
* @description Post-retrieval filter that uses an LLM to identify and
|
|
4
|
+
* drop memory traces whose factual claims have been superseded by
|
|
5
|
+
* later traces about the same subject.
|
|
6
|
+
*
|
|
7
|
+
* ## What this does
|
|
8
|
+
*
|
|
9
|
+
* Given a query and a list of retrieved `ScoredMemoryTrace`s, fires
|
|
10
|
+
* one LLM call with a strict JSON output contract. The LLM returns
|
|
11
|
+
* `{ dropIds: string[] }` — trace IDs to remove. The class filters
|
|
12
|
+
* the input list and returns the survivors in original order.
|
|
13
|
+
*
|
|
14
|
+
* ## Failure modes (never throws)
|
|
15
|
+
*
|
|
16
|
+
* - Parse error → return original traces + `parse-failed` diagnostic.
|
|
17
|
+
* - Schema mismatch → return original + `schema-mismatch` diagnostic.
|
|
18
|
+
* - Timeout → return original + `timeout` diagnostic.
|
|
19
|
+
* - LLM throws → return original + `llm-error` diagnostic.
|
|
20
|
+
* - All IDs dropped (adversarial output) → safety clamp, return
|
|
21
|
+
* original + `drop-all-rejected` diagnostic.
|
|
22
|
+
*
|
|
23
|
+
* ## Why this exists
|
|
24
|
+
*
|
|
25
|
+
* The baseline + Hybrid retrieval surfaces BOTH statements when a
|
|
26
|
+
* user has updated a fact ("I live in NYC" + "I moved to Berlin").
|
|
27
|
+
* The reader sometimes picks the older or hedges. A supersession
|
|
28
|
+
* pass gives the reader only the canonical current state.
|
|
29
|
+
*
|
|
30
|
+
* @module agentos/memory/retrieval/fact-supersession/FactSupersession
|
|
31
|
+
*/
|
|
32
|
+
import type { ScoredMemoryTrace } from '../../core/types.js';
|
|
33
|
+
export type LlmInvoker = (systemPrompt: string, userPrompt: string) => Promise<string>;
|
|
34
|
+
/** Options for constructing a {@link FactSupersession}. */
|
|
35
|
+
export interface FactSupersessionOptions {
|
|
36
|
+
/** LLM invoker used for the supersession pass. */
|
|
37
|
+
llmInvoker: LlmInvoker;
|
|
38
|
+
/** Max traces to send to the LLM. @default 10 */
|
|
39
|
+
maxTraces?: number;
|
|
40
|
+
/** Max wall-clock ms before timeout fallback. @default 8000 */
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/** Per-call input to {@link FactSupersession.resolve}. */
|
|
44
|
+
export interface FactSupersessionInput {
|
|
45
|
+
traces: ScoredMemoryTrace[];
|
|
46
|
+
query: string;
|
|
47
|
+
}
|
|
48
|
+
/** Per-call output from {@link FactSupersession.resolve}. */
|
|
49
|
+
export interface FactSupersessionResult {
|
|
50
|
+
/** Traces surviving the filter, in original order. */
|
|
51
|
+
traces: ScoredMemoryTrace[];
|
|
52
|
+
/** IDs dropped by the LLM (subset of input trace IDs). */
|
|
53
|
+
droppedIds: string[];
|
|
54
|
+
diagnostics: {
|
|
55
|
+
llmLatencyMs: number;
|
|
56
|
+
parseOk: boolean;
|
|
57
|
+
notes?: string[];
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Post-retrieval fact supersession filter.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const fs = new FactSupersession({
|
|
66
|
+
* llmInvoker: async (system, user) => (await reader.invoke({ system, user, maxTokens: 200, temperature: 0 })).text,
|
|
67
|
+
* });
|
|
68
|
+
* const result = await fs.resolve({ traces: retrieval.retrieved, query: caseQuery });
|
|
69
|
+
* // Feed `result.traces` to the reader instead of `retrieval.retrieved`.
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare class FactSupersession {
|
|
73
|
+
private readonly llmInvoker;
|
|
74
|
+
private readonly maxTraces;
|
|
75
|
+
private readonly timeoutMs;
|
|
76
|
+
constructor(opts: FactSupersessionOptions);
|
|
77
|
+
resolve(input: FactSupersessionInput): Promise<FactSupersessionResult>;
|
|
78
|
+
private buildUserPrompt;
|
|
79
|
+
private invokeWithTimeout;
|
|
80
|
+
private parseDropIds;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=FactSupersession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FactSupersession.d.ts","sourceRoot":"","sources":["../../../../src/memory/retrieval/fact-supersession/FactSupersession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,MAAM,UAAU,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEvF,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,kDAAkD;IAClD,UAAU,EAAE,UAAU,CAAC;IACvB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,6DAA6D;AAC7D,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,0DAA0D;IAC1D,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE;QACX,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH;AAkBD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,IAAI,EAAE,uBAAuB;IAMnC,OAAO,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAkE5E,OAAO,CAAC,eAAe;YAST,iBAAiB;IAS/B,OAAO,CAAC,YAAY;CAmBrB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file FactSupersession.ts
|
|
3
|
+
* @description Post-retrieval filter that uses an LLM to identify and
|
|
4
|
+
* drop memory traces whose factual claims have been superseded by
|
|
5
|
+
* later traces about the same subject.
|
|
6
|
+
*
|
|
7
|
+
* ## What this does
|
|
8
|
+
*
|
|
9
|
+
* Given a query and a list of retrieved `ScoredMemoryTrace`s, fires
|
|
10
|
+
* one LLM call with a strict JSON output contract. The LLM returns
|
|
11
|
+
* `{ dropIds: string[] }` — trace IDs to remove. The class filters
|
|
12
|
+
* the input list and returns the survivors in original order.
|
|
13
|
+
*
|
|
14
|
+
* ## Failure modes (never throws)
|
|
15
|
+
*
|
|
16
|
+
* - Parse error → return original traces + `parse-failed` diagnostic.
|
|
17
|
+
* - Schema mismatch → return original + `schema-mismatch` diagnostic.
|
|
18
|
+
* - Timeout → return original + `timeout` diagnostic.
|
|
19
|
+
* - LLM throws → return original + `llm-error` diagnostic.
|
|
20
|
+
* - All IDs dropped (adversarial output) → safety clamp, return
|
|
21
|
+
* original + `drop-all-rejected` diagnostic.
|
|
22
|
+
*
|
|
23
|
+
* ## Why this exists
|
|
24
|
+
*
|
|
25
|
+
* The baseline + Hybrid retrieval surfaces BOTH statements when a
|
|
26
|
+
* user has updated a fact ("I live in NYC" + "I moved to Berlin").
|
|
27
|
+
* The reader sometimes picks the older or hedges. A supersession
|
|
28
|
+
* pass gives the reader only the canonical current state.
|
|
29
|
+
*
|
|
30
|
+
* @module agentos/memory/retrieval/fact-supersession/FactSupersession
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Canonical supersession system prompt. Strict rules: supersession
|
|
34
|
+
* requires contradiction between two claims about the same (subject,
|
|
35
|
+
* predicate); complementary facts never supersede.
|
|
36
|
+
*/
|
|
37
|
+
const SUPERSESSION_SYSTEM_PROMPT = `You are a fact-supersession analyzer for memory retrieval. Given a user question and N retrieved memory traces, identify traces containing FACTS that have been SUPERSEDED by later traces about the same subject.
|
|
38
|
+
|
|
39
|
+
Rules:
|
|
40
|
+
1. Supersession requires contradiction — two traces making DIFFERENT claims about the same (subject, predicate).
|
|
41
|
+
2. Use the timestamp field to order claims chronologically. The LATER trace wins.
|
|
42
|
+
3. Traces about DIFFERENT subjects are never mutually superseding.
|
|
43
|
+
4. Complementary facts (different predicates about the same subject) never supersede each other.
|
|
44
|
+
5. Return a JSON object: {"dropIds": ["id1", "id2"]}. Drop ONLY the outdated ones. Return {"dropIds": []} if no supersession detected.
|
|
45
|
+
|
|
46
|
+
Do not drop traces that are not clearly superseded.`;
|
|
47
|
+
/**
|
|
48
|
+
* Post-retrieval fact supersession filter.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const fs = new FactSupersession({
|
|
53
|
+
* llmInvoker: async (system, user) => (await reader.invoke({ system, user, maxTokens: 200, temperature: 0 })).text,
|
|
54
|
+
* });
|
|
55
|
+
* const result = await fs.resolve({ traces: retrieval.retrieved, query: caseQuery });
|
|
56
|
+
* // Feed `result.traces` to the reader instead of `retrieval.retrieved`.
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class FactSupersession {
|
|
60
|
+
constructor(opts) {
|
|
61
|
+
this.llmInvoker = opts.llmInvoker;
|
|
62
|
+
this.maxTraces = opts.maxTraces ?? 10;
|
|
63
|
+
this.timeoutMs = opts.timeoutMs ?? 8000;
|
|
64
|
+
}
|
|
65
|
+
async resolve(input) {
|
|
66
|
+
const start = Date.now();
|
|
67
|
+
const notes = [];
|
|
68
|
+
if (input.traces.length === 0) {
|
|
69
|
+
return {
|
|
70
|
+
traces: [],
|
|
71
|
+
droppedIds: [],
|
|
72
|
+
diagnostics: { llmLatencyMs: 0, parseOk: true },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const window = input.traces.slice(0, this.maxTraces);
|
|
76
|
+
const userPrompt = this.buildUserPrompt(input.query, window);
|
|
77
|
+
let llmText;
|
|
78
|
+
try {
|
|
79
|
+
llmText = await this.invokeWithTimeout(userPrompt);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const reason = err?.message?.includes('timeout')
|
|
83
|
+
? 'fact-supersession:timeout'
|
|
84
|
+
: 'fact-supersession:llm-error';
|
|
85
|
+
notes.push(reason);
|
|
86
|
+
return {
|
|
87
|
+
traces: input.traces,
|
|
88
|
+
droppedIds: [],
|
|
89
|
+
diagnostics: { llmLatencyMs: Date.now() - start, parseOk: false, notes },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const parsed = this.parseDropIds(llmText);
|
|
93
|
+
if (!parsed.ok) {
|
|
94
|
+
notes.push(parsed.reason);
|
|
95
|
+
return {
|
|
96
|
+
traces: input.traces,
|
|
97
|
+
droppedIds: [],
|
|
98
|
+
diagnostics: { llmLatencyMs: Date.now() - start, parseOk: false, notes },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (parsed.dropIds.length >= input.traces.length) {
|
|
102
|
+
notes.push('fact-supersession:drop-all-rejected');
|
|
103
|
+
return {
|
|
104
|
+
traces: input.traces,
|
|
105
|
+
droppedIds: [],
|
|
106
|
+
diagnostics: { llmLatencyMs: Date.now() - start, parseOk: true, notes },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const dropSet = new Set(parsed.dropIds);
|
|
110
|
+
const filtered = input.traces.filter((t) => !dropSet.has(t.id));
|
|
111
|
+
const realDropped = input.traces
|
|
112
|
+
.filter((t) => dropSet.has(t.id))
|
|
113
|
+
.map((t) => t.id);
|
|
114
|
+
return {
|
|
115
|
+
traces: filtered,
|
|
116
|
+
droppedIds: realDropped,
|
|
117
|
+
diagnostics: {
|
|
118
|
+
llmLatencyMs: Date.now() - start,
|
|
119
|
+
parseOk: true,
|
|
120
|
+
notes: notes.length > 0 ? notes : undefined,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
buildUserPrompt(query, traces) {
|
|
125
|
+
const lines = traces.map((t) => {
|
|
126
|
+
const ts = new Date(t.createdAt).toISOString();
|
|
127
|
+
const content = t.content.length > 500 ? `${t.content.slice(0, 500)}...` : t.content;
|
|
128
|
+
return `[id=${t.id} | ts=${ts} | "${content}"]`;
|
|
129
|
+
});
|
|
130
|
+
return `Question: ${query}\n\nTraces (id | timestamp | content):\n${lines.join('\n')}\n\nReturn JSON only.`;
|
|
131
|
+
}
|
|
132
|
+
async invokeWithTimeout(userPrompt) {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const timer = setTimeout(() => reject(new Error('fact-supersession timeout')), this.timeoutMs);
|
|
135
|
+
this.llmInvoker(SUPERSESSION_SYSTEM_PROMPT, userPrompt)
|
|
136
|
+
.then((text) => { clearTimeout(timer); resolve(text); })
|
|
137
|
+
.catch((err) => { clearTimeout(timer); reject(err); });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
parseDropIds(text) {
|
|
141
|
+
const cleaned = text.trim().replace(/^```(?:json)?/i, '').replace(/```$/i, '').trim();
|
|
142
|
+
let obj;
|
|
143
|
+
try {
|
|
144
|
+
obj = JSON.parse(cleaned);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { ok: false, reason: 'fact-supersession:parse-failed' };
|
|
148
|
+
}
|
|
149
|
+
if (!obj || typeof obj !== 'object' || !Array.isArray(obj.dropIds)) {
|
|
150
|
+
return { ok: false, reason: 'fact-supersession:schema-mismatch' };
|
|
151
|
+
}
|
|
152
|
+
const arr = obj.dropIds;
|
|
153
|
+
if (!arr.every((v) => typeof v === 'string')) {
|
|
154
|
+
return { ok: false, reason: 'fact-supersession:schema-mismatch' };
|
|
155
|
+
}
|
|
156
|
+
return { ok: true, dropIds: arr };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=FactSupersession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FactSupersession.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/fact-supersession/FactSupersession.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module agentos/memory/retrieval/fact-supersession
|
|
3
|
+
* @description Post-retrieval LLM-based supersession filter — drops
|
|
4
|
+
* memory traces whose factual claims have been superseded by later
|
|
5
|
+
* traces about the same subject. Used to push knowledge-update
|
|
6
|
+
* accuracy past the ceiling hit by pure retrieval + rerank.
|
|
7
|
+
*/
|
|
8
|
+
export { FactSupersession } from './FactSupersession.js';
|
|
9
|
+
export type { FactSupersessionOptions, FactSupersessionInput, FactSupersessionResult, } from './FactSupersession.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/memory/retrieval/fact-supersession/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module agentos/memory/retrieval/fact-supersession
|
|
3
|
+
* @description Post-retrieval LLM-based supersession filter — drops
|
|
4
|
+
* memory traces whose factual claims have been superseded by later
|
|
5
|
+
* traces about the same subject. Used to push knowledge-update
|
|
6
|
+
* accuracy past the ceiling hit by pure retrieval + rerank.
|
|
7
|
+
*/
|
|
8
|
+
export { FactSupersession } from './FactSupersession.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/memory/retrieval/fact-supersession/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|