topic-memory 0.1.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.
@@ -0,0 +1,23 @@
1
+ import type { CanonicalExchange, CanonicalTopic, LatestTopicWorkerRun, MemoryStorage } from '../types.js';
2
+ export interface IndexedDbMemoryStorageOptions {
3
+ dbName?: string;
4
+ indexedDB?: IDBFactory;
5
+ }
6
+ export declare class IndexedDbMemoryStorage implements MemoryStorage {
7
+ private readonly dbName;
8
+ private readonly idb;
9
+ constructor(options?: IndexedDbMemoryStorageOptions);
10
+ listExchanges(): Promise<CanonicalExchange[]>;
11
+ putExchange(exchange: CanonicalExchange): Promise<void>;
12
+ clearExchanges(): Promise<void>;
13
+ listTopics(): Promise<CanonicalTopic[]>;
14
+ replaceTopics(topics: CanonicalTopic[]): Promise<void>;
15
+ clearTopics(): Promise<void>;
16
+ getLatestTopicWorkerRun(): Promise<LatestTopicWorkerRun | null>;
17
+ saveLatestTopicWorkerRun(run: LatestTopicWorkerRun): Promise<void>;
18
+ clearLatestTopicWorkerRun(): Promise<void>;
19
+ private getAll;
20
+ private write;
21
+ private open;
22
+ }
23
+ //# sourceMappingURL=indexeddb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/storage/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAM1G,MAAM,WAAW,6BAA6B;IAAG,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,UAAU,CAAC;CAAE;AAE3F,qBAAa,sBAAuB,YAAW,aAAa;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;gBAErB,OAAO,GAAE,6BAAkC;IAOjD,aAAa,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAI7C,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IACvD,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAIvC,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAC5B,uBAAuB,IAAI,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAI/D,wBAAwB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;YAElC,MAAM;YAWN,KAAK;IAWnB,OAAO,CAAC,IAAI;CAiBb"}
@@ -0,0 +1,95 @@
1
+ const EXCHANGE_STORE = 'canonical_exchanges';
2
+ const TOPIC_STORE = 'topics';
3
+ const RUN_STORE = 'latest_topic_worker_run';
4
+ export class IndexedDbMemoryStorage {
5
+ dbName;
6
+ idb;
7
+ constructor(options = {}) {
8
+ this.dbName = options.dbName ?? 'topic-memory:v1';
9
+ const factory = options.indexedDB ?? globalThis.indexedDB;
10
+ if (!factory)
11
+ throw new Error('IndexedDB is not available in this environment');
12
+ this.idb = factory;
13
+ }
14
+ async listExchanges() {
15
+ const records = await this.getAll(EXCHANGE_STORE);
16
+ return records.sort((a, b) => a.sequence - b.sequence);
17
+ }
18
+ async putExchange(exchange) { await this.write(EXCHANGE_STORE, (store) => store.put(exchange)); }
19
+ async clearExchanges() { await this.write(EXCHANGE_STORE, (store) => store.clear()); }
20
+ async listTopics() {
21
+ const records = await this.getAll(TOPIC_STORE);
22
+ return records.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0) || a.topicId.localeCompare(b.topicId));
23
+ }
24
+ async replaceTopics(topics) {
25
+ const db = await this.open();
26
+ await new Promise((resolve, reject) => {
27
+ const tx = db.transaction(TOPIC_STORE, 'readwrite');
28
+ const store = tx.objectStore(TOPIC_STORE);
29
+ store.clear();
30
+ for (const topic of topics)
31
+ store.put(topic);
32
+ tx.oncomplete = () => { db.close(); resolve(); };
33
+ tx.onerror = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB topic transaction failed')); };
34
+ tx.onabort = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB topic transaction aborted')); };
35
+ });
36
+ }
37
+ async clearTopics() { await this.write(TOPIC_STORE, (store) => store.clear()); }
38
+ async getLatestTopicWorkerRun() {
39
+ const records = await this.getAll(RUN_STORE);
40
+ return records.sort((a, b) => b.createdAt - a.createdAt)[0] ?? null;
41
+ }
42
+ async saveLatestTopicWorkerRun(run) {
43
+ const db = await this.open();
44
+ await new Promise((resolve, reject) => {
45
+ const tx = db.transaction(RUN_STORE, 'readwrite');
46
+ const store = tx.objectStore(RUN_STORE);
47
+ store.clear();
48
+ store.put(run);
49
+ tx.oncomplete = () => { db.close(); resolve(); };
50
+ tx.onerror = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB run transaction failed')); };
51
+ tx.onabort = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB run transaction aborted')); };
52
+ });
53
+ }
54
+ async clearLatestTopicWorkerRun() { await this.write(RUN_STORE, (store) => store.clear()); }
55
+ async getAll(storeName) {
56
+ const db = await this.open();
57
+ return new Promise((resolve, reject) => {
58
+ const tx = db.transaction(storeName, 'readonly');
59
+ const request = tx.objectStore(storeName).getAll();
60
+ tx.oncomplete = () => { db.close(); resolve(request.result ?? []); };
61
+ tx.onerror = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB read failed')); };
62
+ tx.onabort = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB read aborted')); };
63
+ });
64
+ }
65
+ async write(storeName, action) {
66
+ const db = await this.open();
67
+ await new Promise((resolve, reject) => {
68
+ const tx = db.transaction(storeName, 'readwrite');
69
+ action(tx.objectStore(storeName));
70
+ tx.oncomplete = () => { db.close(); resolve(); };
71
+ tx.onerror = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB write failed')); };
72
+ tx.onabort = () => { const error = tx.error; db.close(); reject(error ?? new Error('IndexedDB write aborted')); };
73
+ });
74
+ }
75
+ open() {
76
+ return new Promise((resolve, reject) => {
77
+ const request = this.idb.open(this.dbName, 1);
78
+ request.onupgradeneeded = () => {
79
+ const db = request.result;
80
+ if (!db.objectStoreNames.contains(EXCHANGE_STORE)) {
81
+ const store = db.createObjectStore(EXCHANGE_STORE, { keyPath: 'id' });
82
+ store.createIndex('sequence', 'sequence', { unique: true });
83
+ store.createIndex('status', 'status', { unique: false });
84
+ }
85
+ if (!db.objectStoreNames.contains(TOPIC_STORE))
86
+ db.createObjectStore(TOPIC_STORE, { keyPath: 'topicId' });
87
+ if (!db.objectStoreNames.contains(RUN_STORE))
88
+ db.createObjectStore(RUN_STORE, { keyPath: 'runId' });
89
+ };
90
+ request.onerror = () => reject(request.error ?? new Error('Failed to open IndexedDB memory database'));
91
+ request.onsuccess = () => resolve(request.result);
92
+ });
93
+ }
94
+ }
95
+ //# sourceMappingURL=indexeddb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexeddb.js","sourceRoot":"","sources":["../../src/storage/indexeddb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ export type CanonicalExchangeStatus = 'pending' | 'completed' | 'failed';
2
+ export type CanonicalTopicStatus = 'open' | 'provisional' | 'finalized';
3
+ export interface CanonicalExchange {
4
+ id: string;
5
+ sequence: number;
6
+ userText: string;
7
+ userSentAt: number;
8
+ assistantText: string;
9
+ assistantCompletedAt: number | null;
10
+ status: CanonicalExchangeStatus;
11
+ failureReason: string | null;
12
+ source: 'live';
13
+ }
14
+ export interface CanonicalTopicSpan {
15
+ startSequence: number;
16
+ endSequence: number;
17
+ }
18
+ export interface CanonicalTopic {
19
+ topicId: string;
20
+ status: CanonicalTopicStatus;
21
+ labelTerms: string[];
22
+ retrievalTerms: string[];
23
+ spans: CanonicalTopicSpan[];
24
+ startedAt: number | null;
25
+ endedAt: number | null;
26
+ updatedAt: number;
27
+ source: 'topic_worker_v1';
28
+ }
29
+ export type TopicWorkerRunValidationStatus = 'accepted' | 'rejected' | 'failed';
30
+ export interface LatestTopicWorkerRun {
31
+ runId: string;
32
+ createdAt: number;
33
+ inputText: string;
34
+ rawOutput: string;
35
+ validationStatus: TopicWorkerRunValidationStatus;
36
+ validationError: string | null;
37
+ acceptedTopics: CanonicalTopic[];
38
+ requestModel: string;
39
+ }
40
+ export interface MemoryLlmRequest {
41
+ system: string;
42
+ user: string;
43
+ maxTokens: number;
44
+ temperature?: number;
45
+ topP?: number;
46
+ }
47
+ export interface MemoryLlm {
48
+ complete(input: MemoryLlmRequest): Promise<string>;
49
+ }
50
+ export interface MemoryStorage {
51
+ listExchanges(): Promise<CanonicalExchange[]>;
52
+ putExchange(exchange: CanonicalExchange): Promise<void>;
53
+ clearExchanges(): Promise<void>;
54
+ listTopics(): Promise<CanonicalTopic[]>;
55
+ replaceTopics(topics: CanonicalTopic[]): Promise<void>;
56
+ clearTopics(): Promise<void>;
57
+ getLatestTopicWorkerRun(): Promise<LatestTopicWorkerRun | null>;
58
+ saveLatestTopicWorkerRun(run: LatestTopicWorkerRun): Promise<void>;
59
+ clearLatestTopicWorkerRun(): Promise<void>;
60
+ }
61
+ export interface RetrieveResult {
62
+ recentContext: CanonicalExchange[];
63
+ topicDirectory: string;
64
+ selectedTopicIds: string[];
65
+ openedTopicPackets: string[];
66
+ memoryContext: string;
67
+ needsTimeMetadata: boolean;
68
+ trace: {
69
+ selectorInput: string;
70
+ selectorRawOutput: string;
71
+ selectorError: string | null;
72
+ };
73
+ }
74
+ export interface TopicWorkerResult {
75
+ ran: boolean;
76
+ reason: 'completed_exchange_gate' | 'active_tail_gate' | 'accepted' | 'rejected' | 'failed';
77
+ run: LatestTopicWorkerRun | null;
78
+ }
79
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,227 @@
1
+ # Architecture & Capacity Notes
2
+
3
+ [简体中文](./ARCHITECTURE.zh-CN.md)
4
+
5
+ This appendix explains the design assumptions behind Topic Memory v0.1. It is not required for installation or integration.
6
+
7
+ ## 1. What Topic Memory actually scales
8
+
9
+ Topic Memory does **not** increase an LLM's native context window.
10
+
11
+ Instead, it separates two quantities that are often treated as if they were the same:
12
+
13
+ - **stored conversation history** — how much history the application can preserve;
14
+ - **per-request prompt history** — how much of that history must be sent to a model for one reply.
15
+
16
+ A raw-history design tends toward:
17
+
18
+ ```text
19
+ per-request history tokens ≈ total exchanges × average tokens per exchange
20
+ ```
21
+
22
+ Topic Memory tends toward:
23
+
24
+ ```text
25
+ per-request memory tokens
26
+ ≈ recent context
27
+ + Topic Directory
28
+ + a small number of reopened topic packets
29
+ ```
30
+
31
+ The full Canonical Transcript remains in storage and is not replayed on every request.
32
+
33
+ ## 2. The v0.1 retrieval path
34
+
35
+ Let:
36
+
37
+ - `N` = total completed exchanges stored;
38
+ - `g` = average exchanges represented by one topic;
39
+ - `d` = average tokens in one Topic Directory entry;
40
+ - `k` = number of reopened topics, with `k <= 3` in v0.1;
41
+ - `t` = average tokens per completed exchange;
42
+ - `r` = number of recent exchanges, fixed at `5` in v0.1.
43
+
44
+ The approximate number of topics is:
45
+
46
+ ```text
47
+ T ≈ N / g
48
+ ```
49
+
50
+ The Topic Directory cost is approximately:
51
+
52
+ ```text
53
+ DirectoryTokens ≈ T × d
54
+ ≈ (N / g) × d
55
+ ```
56
+
57
+ The recent-context cost is approximately:
58
+
59
+ ```text
60
+ RecentTokens ≈ r × t
61
+ = 5 × t
62
+ ```
63
+
64
+ If a reopened topic contains roughly `g` exchanges, the maximum reopened historical text is approximately:
65
+
66
+ ```text
67
+ OpenedTopicTokens ≈ k × g × t
68
+ ```
69
+
70
+ So a rough v0.1 memory prompt estimate is:
71
+
72
+ ```text
73
+ MemoryPromptTokens
74
+ ≈ (N / g) × d
75
+ + 5 × t
76
+ + k × g × t
77
+ + fixed prompt overhead
78
+ ```
79
+
80
+ This is not constant-time retrieval: the v0.1 Topic Directory grows with the number of topics. The important difference is that the full raw transcript does not grow inside every prompt.
81
+
82
+ ## 3. Illustrative 600 → 5,000 exchange sizing example
83
+
84
+ The following is a capacity calculation, **not a benchmark result and not a guaranteed maximum**.
85
+
86
+ Assume:
87
+
88
+ - model context window: `128,000 tokens`;
89
+ - approximately `8,000 tokens` reserved for system instructions, current input, output headroom, and other application context;
90
+ - available historical prompt budget: approximately `120,000 tokens`;
91
+ - average completed exchange: `200 tokens` across user + assistant;
92
+ - average topic size: `8 exchanges`;
93
+ - average Topic Directory entry: `60 tokens`;
94
+ - recent context: `5 exchanges`;
95
+ - maximum reopened topics: `3`.
96
+
97
+ ### Raw-history approach
98
+
99
+ ```text
100
+ 120,000 / 200 = 600 exchanges
101
+ ```
102
+
103
+ Under these assumptions, replaying every historical exchange reaches the approximate historical prompt budget at around **600 completed exchanges**.
104
+
105
+ ### Topic Memory with 5,000 stored exchanges
106
+
107
+ Number of topics:
108
+
109
+ ```text
110
+ 5,000 / 8 = 625 topics
111
+ ```
112
+
113
+ Topic Directory:
114
+
115
+ ```text
116
+ 625 × 60 = 37,500 tokens
117
+ ```
118
+
119
+ Recent context:
120
+
121
+ ```text
122
+ 5 × 200 = 1,000 tokens
123
+ ```
124
+
125
+ Up to three reopened topics:
126
+
127
+ ```text
128
+ 3 × 8 × 200 = 4,800 tokens
129
+ ```
130
+
131
+ Subtotal:
132
+
133
+ ```text
134
+ 37,500 + 1,000 + 4,800 = 43,300 tokens
135
+ ```
136
+
137
+ Allowing roughly 1,000–2,000 additional tokens for selector instructions, timestamps, formatting, and other fixed overhead gives an illustrative total of approximately:
138
+
139
+ ```text
140
+ 44,000–45,000 memory-related tokens
141
+ ```
142
+
143
+ In this example:
144
+
145
+ ```text
146
+ 5,000 / 600 ≈ 8.33×
147
+ ```
148
+
149
+ So the system can *represent and selectively reopen* about **8.3× more completed-exchange history** than the raw 600-exchange prompt example, while keeping the memory-related prompt far below the cost of replaying all 5,000 exchanges.
150
+
151
+ For comparison, replaying all 5,000 exchanges at 200 tokens each would be roughly:
152
+
153
+ ```text
154
+ 5,000 × 200 = 1,000,000 tokens
155
+ ```
156
+
157
+ The illustrative Topic Memory retrieval prompt of ~44k–45k tokens is about **95% smaller** than replaying that full one-million-token history.
158
+
159
+ Again: these numbers describe one set of assumptions. They do not mean Topic Memory guarantees exactly 5,000 exchanges or that 600 exchanges is a universal limit.
160
+
161
+ ## 4. Why 5,000 is not a hard maximum
162
+
163
+ The storage layer can preserve more than 5,000 exchanges. v0.1's more relevant scaling constraint is the Topic Directory.
164
+
165
+ Because the selector currently sees the entire Topic Directory, its prompt cost grows approximately with:
166
+
167
+ ```text
168
+ O(number of topics)
169
+ ```
170
+
171
+ If topics average eight exchanges each, 5,000 exchanges produce about 625 topic entries. If the archive becomes much larger, the directory itself eventually becomes the dominant prompt cost.
172
+
173
+ A future version could reduce this cost with techniques such as:
174
+
175
+ - hierarchical topic directories;
176
+ - coarse-to-fine topic routing;
177
+ - server-side lexical or semantic pre-filtering;
178
+ - time-partitioned directories;
179
+ - vector or hybrid retrieval before the Memory Selector.
180
+
181
+ Those are intentionally outside v0.1.
182
+
183
+ ## 5. Why topics instead of a single rolling summary
184
+
185
+ A rolling summary is cheap, but repeated summarization can discard exact details and flatten chronology.
186
+
187
+ Topic Memory keeps two different representations:
188
+
189
+ 1. **Topic metadata** for lightweight discovery.
190
+ 2. **Canonical Transcript spans** for exact recovery.
191
+
192
+ The Topic Worker is therefore not trying to replace the original conversation with a summary. It creates an index that points back to the original evidence.
193
+
194
+ This matters when the user asks questions such as:
195
+
196
+ - "What did I say about that project last month?"
197
+ - "Which option did we reject before?"
198
+ - "When did I tell you that?"
199
+ - "What exactly happened in that earlier conversation?"
200
+
201
+ The selector can identify a topic, then the SDK can reopen the original exchanges behind it.
202
+
203
+ ## 6. Long-context research context
204
+
205
+ The design motivation is consistent with a broader observation in long-context LLM research: a larger context window does not automatically imply perfect retrieval from every position in a long prompt.
206
+
207
+ Related work includes:
208
+
209
+ - **Liu et al., "Lost in the Middle: How Language Models Use Long Contexts"** — shows that retrieval performance can depend strongly on where relevant information appears in long input contexts.
210
+ - **Maharana et al., "Evaluating Very Long-Term Conversational Memory of LLM Agents" (LoCoMo, ACL 2024)** — evaluates conversations of up to 600 turns and reports that long-term conversational memory remains challenging even with long-context and retrieval approaches.
211
+ - **Banerjee et al., "APEX-MEM: Agentic Semi-Structured Memory with Temporal Reasoning for Long-Term Conversational AI" (ACL 2026)** — explores semi-structured, temporally grounded memory with retrieval-time resolution of relevant historical information.
212
+
213
+ Topic Memory v0.1 is an independent implementation and does not claim to reproduce the methods or benchmark results of those systems. These references are included only as context for the general design choice of storing long history externally and retrieving compact, relevant evidence at reply time.
214
+
215
+ References:
216
+
217
+ - https://arxiv.org/abs/2307.03172
218
+ - https://aclanthology.org/2024.acl-long.747/
219
+ - https://aclanthology.org/2026.acl-long.749/
220
+
221
+ ## 7. Practical interpretation
222
+
223
+ The safest way to describe v0.1 is:
224
+
225
+ > Topic Memory converts an ever-growing raw transcript into a persistent archive plus a lightweight topic index, then reopens only a few relevant transcript spans for each new request.
226
+
227
+ Its benefit is not a guaranteed number of remembered exchanges. Its benefit is that the amount of conversation you can preserve becomes much less tightly coupled to the amount of conversation you must resend on every model call.