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 +21 -0
- package/README.md +326 -0
- package/README.zh-CN.md +387 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +375 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +10 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +43 -0
- package/dist/llm.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +34 -0
- package/dist/prompts.js.map +1 -0
- package/dist/storage/in-memory.d.ts +16 -0
- package/dist/storage/in-memory.d.ts.map +1 -0
- package/dist/storage/in-memory.js +24 -0
- package/dist/storage/in-memory.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +23 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +95 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/ARCHITECTURE.md +227 -0
- package/docs/ARCHITECTURE.zh-CN.md +251 -0
- package/docs/USAGE.md +327 -0
- package/docs/USAGE.zh-CN.md +345 -0
- package/package.json +56 -0
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
|