wontopos 2.2.45 → 2.2.47
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/README.md +134 -21
- package/dist/cjs/wontopos.d.cts +117 -76
- package/dist/cjs/wontopos.js +98 -22
- package/dist/wontopos.d.ts +117 -76
- package/dist/wontopos.js +97 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,12 +24,47 @@ const ctx = await mem.recall("what does alice drink?", "alice");
|
|
|
24
24
|
## Why
|
|
25
25
|
|
|
26
26
|
- **The same in every language** — identical recall whichever language a memory was written in (Korean · Japanese · Chinese · English).
|
|
27
|
-
- **No LLM
|
|
28
|
-
- **Bounded retrieval** — `recall()` returns
|
|
27
|
+
- **No LLM reads your memories** — no language model reads your stored memories, on any model. You pay retrieval, not generation.
|
|
28
|
+
- **Bounded retrieval** — `recall()` returns the recent turns, at most `limit` long-term memories (default 10, up to 20) and at most `context_limit` rows of the surrounding conversation (default 10, up to 20), however much you've stored.
|
|
29
29
|
|
|
30
30
|
## Methods
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
| Method | What it does |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Write** | |
|
|
35
|
+
| `add(content, userId?, metadata?, opts?)` | Store one memory. Alias `store`. `opts.image` attaches a photo. |
|
|
36
|
+
| `addTurn(userMsg, assistantMsg, userId?)` | Store one exchange of a conversation. |
|
|
37
|
+
| `addBulk(content, userId?, category?, timestamp?)` | Backfill a long history in one call. |
|
|
38
|
+
| `update(oldMemoryId, newContent, userId?)` | Replace a fact that changed. The old version stays in its history. |
|
|
39
|
+
| **Read** | |
|
|
40
|
+
| `recall(query, userId?, opts?)` | The recent turns, the memories that match and the conversation around the best one, in one call. |
|
|
41
|
+
| `search(query, userId?, limit?, opts?)` | The memories that match, best first. |
|
|
42
|
+
| `searchFull(...)` | The same search with each part kept apart, `images` and `verify_used` included. |
|
|
43
|
+
| `searchSelf(...)` | The assistant's own words, kept apart from what others said. |
|
|
44
|
+
| `history(userId?)` | The recent conversation. |
|
|
45
|
+
| `get(userId, memoryId)` | One memory by id. |
|
|
46
|
+
| `listMemories` · `iterMemories` · `exportMemories` | Every memory in a store: a page at a time, as a stream, or all at once. |
|
|
47
|
+
| `lineage(userId, memoryId)` | The edits that followed one memory. |
|
|
48
|
+
| `revisions(userId?, opts?)` | How many of a store's memories were edited, and which. |
|
|
49
|
+
| `stats(userId?)` | Counts for a store. |
|
|
50
|
+
| `engram(name, query, userId?)` · `listEngrams()` | Built-in retrieval routines, and the ones a model can run. |
|
|
51
|
+
| **Photos** | |
|
|
52
|
+
| `listImages` · `iterImages` · `exportImages` | A store's image memories, newest first. |
|
|
53
|
+
| `getImage(userId, memoryId)` | The bytes of one image. |
|
|
54
|
+
| `forgetImage(userId, memoryId, { preview })` | Remove the photo from a memory. `preview: true` first says whether the memory itself stays. |
|
|
55
|
+
| **People** | |
|
|
56
|
+
| `addSpeaker` · `listSpeakers` · `removeSpeaker` | Register, list and unregister the people a store hears from. |
|
|
57
|
+
| `bySpeaker(speaker, userId?)` | What one person said, newest first. |
|
|
58
|
+
| **Stores** | |
|
|
59
|
+
| `createStore` · `listStores` · `deleteStore` | One isolated memory space per end-user, agent or topic. |
|
|
60
|
+
| `delete(userId, memoryId)` · `deleteAll(userId)` | Delete one memory, or every memory in a store. |
|
|
61
|
+
| **Account** | |
|
|
62
|
+
| `listModels()` | The models you can use, and what each can do. |
|
|
63
|
+
| `usage(days?)` | What this key, its workspace and its stores have spent. Free. |
|
|
64
|
+
| `ping()` | Check the key and the connection. |
|
|
65
|
+
| **Client** | |
|
|
66
|
+
| `withModel` · `withUser` · `withDeadline` · `withTimeout` · `withRetries` · `withSignal` | A copy of the client with another model, store, budget, timeout, retry count or cancel signal. |
|
|
67
|
+
| `Client.fromEnv()` · `rateLimit` | A client with the key from the environment; the last call's rate-limit numbers. |
|
|
33
68
|
|
|
34
69
|
Where the store id goes depends on whether the method takes a payload. A method that
|
|
35
70
|
writes or searches puts the payload first and the store second — `mem.add("...", "alice")`,
|
|
@@ -38,9 +73,32 @@ writes or searches puts the payload first and the store second — `mem.add("...
|
|
|
38
73
|
Most of them let you leave it out for the client's default store. Five take it
|
|
39
74
|
positionally and cannot — `get`, `getImage`, `delete`, `lineage` and `forgetImage` want
|
|
40
75
|
an explicit `undefined` there, because a second required argument follows it. Python
|
|
41
|
-
omits it in all five
|
|
76
|
+
omits it in all five, and Rust takes it positionally (`None` for the default store, where there is one).
|
|
42
77
|
Let your editor confirm the order: every signature is typed. A store is one isolated memory space per end-user, agent, or topic. WHO said each memory inside a store is the `speaker` tag below — storing the assistant's own words never needs a separate id.
|
|
43
78
|
|
|
79
|
+
## Putting recall into a prompt
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const ctx = await mem.recall(userMessage, "alice");
|
|
83
|
+
|
|
84
|
+
const system = [
|
|
85
|
+
"What you remember about this user, most relevant first:",
|
|
86
|
+
...ctx.long_term.memories.map((m) => `- ${m.content}`),
|
|
87
|
+
"",
|
|
88
|
+
"Around the most relevant memory:",
|
|
89
|
+
...ctx.context.around_top_memory,
|
|
90
|
+
"",
|
|
91
|
+
"The conversation so far:",
|
|
92
|
+
...ctx.short_term.turns.map((t) => `${t.role}: ${t.content}`),
|
|
93
|
+
].join("\n");
|
|
94
|
+
|
|
95
|
+
// Call your language model with `system` and the user's message, then store the exchange:
|
|
96
|
+
await mem.addTurn(userMessage, reply, "alice");
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Each row of `around_top_memory` starts with `[before] `, `[match] ` or `[after] `: where it
|
|
100
|
+
sits next to the best memory. Keep every list in the order it arrives.
|
|
101
|
+
|
|
44
102
|
## Who said it (speakers)
|
|
45
103
|
|
|
46
104
|
Every memory can carry a speaker: `"me"` for the assistant's own words, or a
|
|
@@ -56,19 +114,56 @@ await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "B
|
|
|
56
114
|
```
|
|
57
115
|
|
|
58
116
|
`listSpeakers()` shows the registered people with per-person memory counts;
|
|
59
|
-
`removeSpeaker()` unregisters (memories stay
|
|
117
|
+
`removeSpeaker()` unregisters (memories stay; the tag goes, except on `tablet-2`, which keeps it on memories already stored). A store registers
|
|
60
118
|
up to 50 people to start (a limit we plan to raise); `"me"` never needs
|
|
61
119
|
registration and never counts against it.
|
|
62
120
|
|
|
121
|
+
## Photos
|
|
122
|
+
|
|
123
|
+
On a model whose `listModels()` capabilities include `images`, a memory can carry a photo.
|
|
124
|
+
The caption is still required.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
128
|
+
|
|
129
|
+
const data = readFileSync("beach.jpg").toString("base64");
|
|
130
|
+
const stored = await mem.add("At the beach with Bob", "alice", {}, { image: { data, taken_at: "2026-08-14" } });
|
|
131
|
+
|
|
132
|
+
const { bytes, contentType } = await mem.getImage("alice", stored.id!);
|
|
133
|
+
writeFileSync(`beach.${contentType.split("/")[1]}`, bytes);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Both edges must be 700px or more, the long edge at most 2500px and the file at most 7MB;
|
|
137
|
+
anything else is refused (400) with the reason. A long edge over 1568px is stored at
|
|
138
|
+
1568px, so keep your own copy of the original. `search` returns image memories after
|
|
139
|
+
the others, and `listImages` pages through them newest first. `forgetImage` removes the
|
|
140
|
+
photo, and it can remove the whole memory with it: call it with `preview: true` first,
|
|
141
|
+
and `memory_kept` says whether the memory stays.
|
|
142
|
+
|
|
143
|
+
## Export, edit history and usage
|
|
144
|
+
|
|
145
|
+
- **Export:** `exportMemories(userId)` returns every memory in a store, the text you
|
|
146
|
+
stored and its metadata; `exportImages(userId)` every image memory. For a large store,
|
|
147
|
+
`iterMemories` and `iterImages` stream them a page at a time.
|
|
148
|
+
- **Edit history:** `update` replaces a fact that changed. `lineage(userId, memoryId)` lists
|
|
149
|
+
the versions from the id you pass forward, oldest first, and marks the one in force with
|
|
150
|
+
`is_current`; pass the first version's id to see the whole history. `revisions()` counts
|
|
151
|
+
how many of a store's memories were edited.
|
|
152
|
+
- **Usage:** `usage(days)` reports this key's spend, the workspace's spend over the window,
|
|
153
|
+
the stores that spent the most, and the balance. It is free, and works at a zero balance.
|
|
154
|
+
|
|
63
155
|
## Recall caching
|
|
64
156
|
|
|
65
|
-
Opt in per search and repeated
|
|
66
|
-
|
|
157
|
+
Opt in per search and a repeated query reuses the previous result at 10% of the
|
|
158
|
+
normal rate; an extended one reuses part of it (Tablet and Scroll models).
|
|
67
159
|
|
|
68
160
|
It is not free to turn on: the FIRST call writes the cache and bills the query
|
|
69
|
-
tokens at 2x for a `5m` TTL, 3x for `1h`.
|
|
70
|
-
|
|
71
|
-
|
|
161
|
+
tokens at 2x for a `5m` TTL, 3x for `1h`. The same query again inside the TTL bills
|
|
162
|
+
its query and its results at 0.1x. A query that extends the cached one (its text plus
|
|
163
|
+
more) bills the cached part at 0.1x, the new part at the write rate and its results
|
|
164
|
+
at the normal rate. A store keeps one cached query per model and set of options, so a
|
|
165
|
+
different query replaces it. So it pays for a query you repeat or extend, and costs
|
|
166
|
+
more for one you issue once — do not switch it on globally. Any write to the store invalidates its cache
|
|
72
167
|
at once, so a hit can never predate a new memory.
|
|
73
168
|
|
|
74
169
|
```ts
|
|
@@ -82,12 +177,22 @@ Built in, no configuration needed:
|
|
|
82
177
|
|
|
83
178
|
- **Automatic retries** — 429 and a 409 for a write already in flight always, and 408 /
|
|
84
179
|
502 / 503 / 504 or a connection error only when a retry cannot double-process a write.
|
|
85
|
-
The
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
180
|
+
The calls that are safe to send again retry all four: the reads sent as POST (`search`,
|
|
181
|
+
`recall`, `get`, `listMemories`, `stats`, ...), the GET and DELETE calls (`ping`,
|
|
182
|
+
`listStores`, `listSpeakers`, `listModels`, `listEngrams`, `usage`, `deleteStore`,
|
|
183
|
+
`removeSpeaker`, `forgetImage`), and the memory writes (`add`, `addTurn`, `addBulk`,
|
|
184
|
+
`update`). Those carry an idempotency key, so the service answers a repeat with the
|
|
185
|
+
first attempt's result instead of storing again. The other writes sent as POST
|
|
186
|
+
(`createStore`, `addSpeaker`, `delete`, `deleteAll`) get 429, that 409 and
|
|
187
|
+
connect-level failures only. Twice, with exponential backoff + jitter, honoring the
|
|
188
|
+
server's `Retry-After` up to 30s. Tune with `new Client({ maxRetries })`; `0` disables.
|
|
189
|
+
- **Your own retries** — pass `idempotencyKey` when the retry is yours (a job that is
|
|
190
|
+
re-run, a queue that redelivers). The service keeps a key for 24 hours and answers a
|
|
191
|
+
repeat with the first response, marked `replayed: true`. When the first attempt did
|
|
192
|
+
not come back with an answer, so whether it was applied is not known, a repeat
|
|
193
|
+
is refused with a `ConflictError` (409) rather than stored twice: check before sending
|
|
194
|
+
it again under a new key. Derive the key from what you store
|
|
195
|
+
(`` `import:${row.id}` ``), never a constant.
|
|
91
196
|
- **Redirects refused** — the API key never follows a 3xx to another host.
|
|
92
197
|
- **Timeouts** — 30s per attempt by default (`timeoutMs`), and a total budget for
|
|
93
198
|
the whole call across every retry with `deadlineMs` / `withDeadline(ms)`. At the
|
|
@@ -128,8 +233,13 @@ timeouts, redirect refusal and the response cap all still apply.
|
|
|
128
233
|
|
|
129
234
|
## Security
|
|
130
235
|
|
|
131
|
-
Built in
|
|
236
|
+
Built in:
|
|
132
237
|
|
|
238
|
+
- **No key in a browser page** — anyone who opens a page can read a key in it, and with
|
|
239
|
+
it every memory in its workspace. A client in a browser page that would send the key to
|
|
240
|
+
the service is refused: call Wontopos from your server, or point `baseUrl` at a proxy of
|
|
241
|
+
your own that holds the key. `dangerouslyAllowBrowser: true` turns the refusal off, for
|
|
242
|
+
a local demo with a key you will revoke.
|
|
133
243
|
- **Redirects refused** — a 3xx is an error, so the key never follows one to
|
|
134
244
|
another host.
|
|
135
245
|
- **Response size cap** — anything over 64MB is refused instead of buffered.
|
|
@@ -147,7 +257,8 @@ Built in, none of it configurable off:
|
|
|
147
257
|
Every method returns a documented shape — `StoreResult`, `UpdateResult`,
|
|
148
258
|
`RecallResult`, `EngramResult`, `StatsResult`, `SpeakersList`, `Memory[]`, ... —
|
|
149
259
|
each with an index signature, so new server fields flow through without an SDK
|
|
150
|
-
update. `
|
|
260
|
+
update. `searchSelf`'s result and `getImage`'s bytes are built by the client and
|
|
261
|
+
carry only their own fields. `search` options are typed too (`SearchOptions`: `cache_control`,
|
|
151
262
|
`speaker`, plus `extra` for pass-through).
|
|
152
263
|
|
|
153
264
|
```ts
|
|
@@ -157,7 +268,8 @@ const hits: Memory[] = await mem.search("what did Bob say?", "alice", 10, { spea
|
|
|
157
268
|
console.log(hits[0].speaker, hits[0].time_bucket); // typed: string | undefined
|
|
158
269
|
```
|
|
159
270
|
|
|
160
|
-
`hits`
|
|
271
|
+
`hits` holds memories best-first, then the assistant's own words and image memories in
|
|
272
|
+
their own order. Take it in the order given. `similarity` is a raw
|
|
161
273
|
closeness score, not the ranking key: what produces the order is internal and is
|
|
162
274
|
not returned, so sorting by `similarity` overrides the ranking and makes results
|
|
163
275
|
worse. There is no `score` field.
|
|
@@ -183,7 +295,8 @@ try {
|
|
|
183
295
|
|
|
184
296
|
A retired model answers `410`, thrown as `GoneError`: retrying cannot succeed, so name a
|
|
185
297
|
live model (`listModels()` lists them, with `retires_at` on one that is scheduled to
|
|
186
|
-
retire).
|
|
298
|
+
retire). Before the day, the client warns once with the date the model you use retires.
|
|
299
|
+
`deleteStore` still works under a retired model.
|
|
187
300
|
|
|
188
301
|
## A different API host
|
|
189
302
|
|
|
@@ -214,7 +327,7 @@ data to fix it.
|
|
|
214
327
|
## Changelog
|
|
215
328
|
|
|
216
329
|
The three clients release in lockstep — same version, same surface, same day. Patch
|
|
217
|
-
releases are additive.
|
|
330
|
+
releases are additive. Ten inside 2.2 were not, deliberately and each with its
|
|
218
331
|
reason; the changelog lists them.
|
|
219
332
|
|
|
220
333
|
See [CHANGELOG.md](https://github.com/wontopos/wontopos-node/blob/main/CHANGELOG.md).
|