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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ziningshu-code
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # Topic Memory
2
+
3
+ **A drop-in long-term memory layer for LLM apps.**
4
+
5
+ [简体中文](./README.zh-CN.md) · [Integration guide](./docs/USAGE.md) · [Architecture & capacity notes](./docs/ARCHITECTURE.md)
6
+
7
+ Topic Memory gives an existing chat app or agent a structured way to remember old conversations without sending the entire transcript to the model on every request.
8
+
9
+ It keeps the full conversation as the source of truth, organizes older exchanges into topic-based memory, retrieves only the topics that matter for the current message, and returns a ready-to-inject `memoryContext` for your own Main LLM.
10
+
11
+ > **What it is:** a memory plugin/SDK for an LLM application.
12
+ > **What it is not:** a chatbot, a model provider, or a replacement for your Main LLM.
13
+
14
+ ## What can it do?
15
+
16
+ Topic Memory is useful when your AI needs to remember things that happened far earlier in a conversation, for example:
17
+
18
+ - user preferences, recurring habits, names, places, and personal context;
19
+ - decisions made hundreds or thousands of exchanges ago;
20
+ - project history, requirements, previous attempts, and unresolved tasks;
21
+ - earlier events where the exact wording or timing may matter;
22
+ - long-running conversations where replaying the full transcript would become expensive or exceed the model context window.
23
+
24
+ Unlike a single rolling summary, Topic Memory keeps the canonical transcript. A retrieved topic can therefore reopen the original exchanges behind that topic instead of relying only on a compressed summary.
25
+
26
+ ## The model roles — important
27
+
28
+ There are two layers, and they should not be confused:
29
+
30
+ ### Memory LLM
31
+
32
+ The **Memory LLM** powers the memory system itself.
33
+
34
+ By default, the same Memory LLM instance performs two jobs:
35
+
36
+ 1. **Topic Worker** — organizes completed conversation exchanges into topic instances and writes a lightweight topic index.
37
+ 2. **Memory Selector** — reads the current user message, recent context, and Topic Directory, then chooses up to three older topics to reopen.
38
+
39
+ You may configure separate models for these two jobs, but most integrations can use one Memory LLM for both.
40
+
41
+ ### Your Main LLM
42
+
43
+ Your **Main LLM** is still your own user-facing chat model.
44
+
45
+ Topic Memory never generates the final reply and never takes ownership of your Main LLM. It returns `memoryContext`; your application decides how to inject that context into the Main LLM prompt.
46
+
47
+ ```text
48
+ User message
49
+
50
+ ├─→ Topic Memory retrieves relevant old context
51
+ │ ├─ Recent 5 completed exchanges
52
+ │ ├─ Topic Directory
53
+ │ └─ Opened Topic Packets
54
+
55
+ └─→ Your Main LLM receives memoryContext and writes the reply
56
+ ```
57
+
58
+ ## How it works
59
+
60
+ The v0.1 pipeline is intentionally simple:
61
+
62
+ 1. **Canonical Transcript**
63
+ Every exchange is stored as `pending`, `completed`, or `failed`. The full transcript remains the source of truth.
64
+
65
+ 2. **Topic Worker**
66
+ After at least six completed exchanges exist, the Topic Worker processes the active tail of the conversation. It groups related exchanges into topic instances and stores:
67
+ - topic keywords;
68
+ - retrieval terms;
69
+ - exact transcript spans;
70
+ - topic status and timing metadata.
71
+
72
+ 3. **Topic Directory**
73
+ The SDK builds a compact index of available topics. The Main LLM does not need the entire historical transcript just to decide what to remember.
74
+
75
+ 4. **Memory Selector**
76
+ Before a new reply, the selector sees the current message, the latest five completed exchanges, and the Topic Directory. It selects at most three relevant topic IDs.
77
+
78
+ 5. **Opened Topic Packets**
79
+ Selected topics are reopened from their exact Canonical Transcript spans. When timing matters, exchange timestamps can be included.
80
+
81
+ 6. **Main LLM**
82
+ Topic Memory returns the restored material as `memoryContext`. Your own Main LLM uses it only when relevant to the current message.
83
+
84
+ ```text
85
+ Canonical Transcript
86
+
87
+
88
+ Topic Worker
89
+
90
+
91
+ Topic Store ─────→ Topic Directory
92
+
93
+ Current message ─────────┤
94
+ Recent 5 exchanges ──────┤
95
+
96
+ Memory Selector
97
+
98
+ up to 3 topic IDs
99
+
100
+
101
+ Open Topic Packets
102
+
103
+
104
+ memoryContext
105
+
106
+
107
+ Your Main LLM
108
+ ```
109
+
110
+ Selector failure safely degrades to an empty long-term `memoryContext`; it does not have to block the host chat flow.
111
+
112
+ ## Why not just keep appending the entire transcript?
113
+
114
+ A large context window is still a finite prompt budget, and long-context models do not always use information uniformly well across very long inputs. Topic Memory instead separates **how much history you store** from **how much history you send on one request**.
115
+
116
+ Under one illustrative 128k-context workload, a raw-history design with about 200 tokens per completed exchange reaches roughly 600 exchanges when ~120k tokens are reserved for conversation history. With Topic Memory, a 5,000-exchange archive grouped at roughly eight exchanges per topic can be represented by a Topic Directory plus at most three reopened topics in roughly 43k–45k memory-related tokens under the assumptions documented in the appendix.
117
+
118
+ That is approximately **8.3× more represented conversation history** in this example, while sending substantially less historical text per request than replaying all 5,000 exchanges.
119
+
120
+ **This is a theoretical sizing example, not a guaranteed hard limit or benchmark result.** Actual capacity depends on message length, tokenization, topic size, model context window, and how many topics accumulate. v0.1's main scaling constraint is that the Topic Directory grows with the number of topics.
121
+
122
+ See [Architecture & capacity notes](./docs/ARCHITECTURE.md) for the formula, assumptions, caveats, and related research.
123
+
124
+ ## Install
125
+
126
+ This repository is currently distributed as source code. Clone it directly or build a tarball with `npm pack`.
127
+
128
+ ```bash
129
+ npm install
130
+ npm run build
131
+ npm pack
132
+ ```
133
+
134
+ ## Configure a Memory LLM
135
+
136
+ The built-in adapter uses an OpenAI-compatible `/chat/completions` endpoint:
137
+
138
+ ```bash
139
+ MEMORY_LLM_BASE_URL=https://your-openai-compatible-endpoint.example/v1
140
+ MEMORY_LLM_API_KEY=replace-me
141
+ MEMORY_LLM_MODEL=your-memory-model
142
+ ```
143
+
144
+ Keep paid provider credentials on a trusted backend or proxy. Do not ship them inside a public browser bundle.
145
+
146
+ ## 5-minute Quick Start
147
+
148
+ ```ts
149
+ import {
150
+ createMemory,
151
+ createOpenAICompatibleMemoryLlm,
152
+ InMemoryStorage,
153
+ } from 'topic-memory';
154
+
155
+ const memoryLlm = createOpenAICompatibleMemoryLlm({
156
+ baseUrl: process.env.MEMORY_LLM_BASE_URL!,
157
+ apiKey: process.env.MEMORY_LLM_API_KEY,
158
+ model: process.env.MEMORY_LLM_MODEL!,
159
+ });
160
+
161
+ const memory = createMemory({
162
+ storage: new InMemoryStorage(),
163
+ llm: memoryLlm,
164
+ });
165
+
166
+ async function handleUserMessage(userMessage: string) {
167
+ const pending = await memory.begin(userMessage);
168
+
169
+ try {
170
+ const retrieved = await memory.retrieve({ userMessage });
171
+
172
+ // This is YOUR existing user-facing model call.
173
+ const assistantReply = await myOwnMainLlm({
174
+ userMessage,
175
+ memoryContext: retrieved.memoryContext,
176
+ recentContext: retrieved.recentContext,
177
+ });
178
+
179
+ await memory.completeExchange({
180
+ exchangeId: pending.id,
181
+ assistantText: assistantReply,
182
+ });
183
+
184
+ await memory.maybeRunTopicWorker();
185
+ return assistantReply;
186
+ } catch (error) {
187
+ await memory.failExchange({
188
+ exchangeId: pending.id,
189
+ failureReason: error instanceof Error ? error.message : String(error),
190
+ });
191
+ throw error;
192
+ }
193
+ }
194
+ ```
195
+
196
+ The SDK does **not** call `myOwnMainLlm`; that function represents the Main LLM integration your application already has.
197
+
198
+ For a complete integration walkthrough, see [docs/USAGE.md](./docs/USAGE.md).
199
+
200
+ ## Integration timing
201
+
202
+ ```text
203
+ User message
204
+ → memory.begin()
205
+ → memory.retrieve()
206
+ → Your Main LLM
207
+ → memory.completeExchange()
208
+ → memory.maybeRunTopicWorker()
209
+ ```
210
+
211
+ If the Main LLM request fails after `begin()`, call `memory.failExchange(...)`.
212
+
213
+ ## The first six completed exchanges
214
+
215
+ Topic Worker does not run until at least **6 completed exchanges** exist. Before then:
216
+
217
+ - Canonical Transcript is still recorded;
218
+ - `recentContext` still returns recent completed history;
219
+ - `memoryContext` may be empty because no topic has been created yet.
220
+
221
+ This is expected behavior.
222
+
223
+ ## Public API
224
+
225
+ ```ts
226
+ createMemory
227
+ MemoryEngine
228
+ InMemoryStorage
229
+ IndexedDbMemoryStorage
230
+ createOpenAICompatibleMemoryLlm
231
+ ```
232
+
233
+ Main engine methods:
234
+
235
+ ```ts
236
+ begin
237
+ beginExchange
238
+ completeExchange
239
+ failExchange
240
+ maybeRunTopicWorker
241
+ retrieve
242
+ listExchanges
243
+ listTopics
244
+ getLatestTopicWorkerRun
245
+ clear
246
+ ```
247
+
248
+ `retrieve()` returns:
249
+
250
+ ```ts
251
+ {
252
+ recentContext,
253
+ topicDirectory,
254
+ selectedTopicIds,
255
+ openedTopicPackets,
256
+ memoryContext,
257
+ needsTimeMetadata,
258
+ trace,
259
+ }
260
+ ```
261
+
262
+ ## Storage
263
+
264
+ For demos and tests:
265
+
266
+ ```ts
267
+ new InMemoryStorage()
268
+ ```
269
+
270
+ For browser persistence:
271
+
272
+ ```ts
273
+ new IndexedDbMemoryStorage()
274
+ ```
275
+
276
+ For production backends, implement the exported `MemoryStorage` interface and connect your own database.
277
+
278
+ ## Advanced configuration
279
+
280
+ Default: one Memory LLM handles Topic Worker and Selector.
281
+
282
+ ```ts
283
+ createMemory({ storage, llm: memoryLlm })
284
+ ```
285
+
286
+ Advanced: split the two memory jobs.
287
+
288
+ ```ts
289
+ createMemory({
290
+ storage,
291
+ topicWorker: topicWorkerLlm,
292
+ selector: selectorLlm,
293
+ });
294
+ ```
295
+
296
+ Again, neither configuration replaces your Main LLM.
297
+
298
+ ## Failure behavior
299
+
300
+ - **Main LLM fails:** call `failExchange`.
301
+ - **Topic Worker provider fails:** the failure is recorded and existing topics remain.
302
+ - **Topic Worker returns invalid structure:** the output is rejected and not written to Topic Store.
303
+ - **Selector fails:** long-term `memoryContext` falls back to empty.
304
+ - **No older topic is relevant:** `memoryContext` is empty by design.
305
+
306
+ ## Validation
307
+
308
+ The repository CI validates the package as an actual consumer would use it:
309
+
310
+ ```bash
311
+ npm run build
312
+ npm run typecheck
313
+ npm test
314
+ npm pack --dry-run
315
+ npm run smoke:consumer
316
+ ```
317
+
318
+ `smoke:consumer` packs the SDK, installs the tarball into a fresh temporary Node project, imports only public package exports, runs the memory pipeline, and verifies that a simulated host-owned Main LLM receives a non-empty `memoryContext`.
319
+
320
+ ## Non-goals
321
+
322
+ v0.1 does not manage persona, Big Five traits, relationship state, proactive messaging, UI, embeddings, vector databases, or the host application's Main LLM.
323
+
324
+ ## License
325
+
326
+ MIT