wontopos
v2.2.17
Published
Wontopos — long-term memory for AI agents. Pure semantic retrieval, identical recall in every language, ~100× lower LLM bill.
Maintainers
Readme
Wontopos — long-term memory for AI agents
npm install wontoposimport { Client } from "wontopos";
const mem = new Client({ apiKey: "wos-..." });
await mem.add("she prefers tea over coffee", "alice");
// one call → short-term + long-term + context, ready for your LLM prompt
const ctx = await mem.recall("what does alice drink?", "alice");Why
- Pure semantic retrieval — no keyword/BM25. Identical recall in every language (한국어 · 日本語 · 中文 · English).
- No LLM in the loop —
store/search/recallnever call a language model. You pay embeddings, not generation. - Bounded retrieval —
recall()returns a small, fixed-size slice (~1,200 tokens) regardless of how much you've stored.
Methods
add · addTurn · addBulk · update · search · recall · history · stats · get · listMemories · delete · deleteAll
All methods take { userId } — it names the store: one isolated memory space per end-user, agent, or topic, then per account. WHO said each memory inside a store is the speaker tag below — storing the assistant's own words never needs a separate id.
Who said it (speakers)
Every memory can carry a speaker: "me" for the assistant's own words, or a
person's name. Speakers are explicit, like stores: register a person once,
then store under their name — a typo can never silently become a new person.
Search accepts a speaker too, to recall one person's words only.
await mem.addSpeaker("Bob", "alice"); // once per person
await mem.add("I promised to send the report on Friday", "alice", { speaker: "me" });
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "Bob" });listSpeakers() shows the registered people with per-person memory counts;
removeSpeaker() unregisters (memories stay, the tag goes). A store registers
up to 50 people to start (a limit we plan to raise); "me" never needs
registration and never counts against it.
Recall caching
Opt in per search and repeated or extended queries reuse the previous result at 10% of the normal rate (Tablet and Scroll models):
const hits = await mem.search("...the conversation so far...", "alice", 10,
{ cache_control: { ttl: "5m" } }); // or "1h"Reliability
Built in, no configuration needed:
- Automatic retries — 429 / 502 / 503 and connection errors retry twice with
exponential backoff + jitter, honoring the server's
Retry-After. Tune withnew Client({ maxRetries });0disables. - Redirects refused — the API key never follows a 3xx to another host.
- Timeouts — 30s per attempt by default (
timeoutMs). - Key never in logs —
JSON.stringify(client)and Node'sconsole.log(client)print a masked key. - Wipe guard —
delete()without amemoryIdthrows instead of silently meaning "delete everything"; wiping a store is only ever the explicitdeleteAll(userId)/deleteStore(userId).
Security
Built in, none of it configurable off:
- Redirects refused — a 3xx is an error, so the key never follows one to another host.
- Response size cap — anything over 64MB is refused instead of buffered.
- Key hygiene — keys are trimmed (a stray newline from an env var otherwise becomes a mystery 401) and inner whitespace is rejected; model names are validated before they reach a header.
Client.fromEnv()readsWONTOPOS_API_KEY(orWOS_API_KEY) — keep keys out of source code.- TLS certificate verification is never touched (runtime defaults; Node 18+ floors TLS at 1.2). Plain-HTTP base URLs on non-local hosts warn. Zero runtime dependencies.
Typed responses
Every method returns a documented shape — StoreResult, UpdateResult,
RecallResult, EngramResult, StatsResult, SpeakersList, Memory[], ... —
each with an index signature, so new server fields flow through without an SDK
update. search options are typed too (SearchOptions: cache_control,
speaker, plus pass-through).
const hits: Memory[] = await mem.search("what did Bob say?", "alice", 10, { speaker: "Bob" });
console.log(hits[0].speaker, hits[0].time_bucket); // typed: string | undefinedErrors
Any non-2xx response throws WosError with status, message, and — when the
server sent one — requestId (include it when contacting support).
import { Client, WosError } from "wontopos";
try {
await mem.search("...", "alice");
} catch (e) {
if (e instanceof WosError) {
if (e.status === 401) console.error("API key invalid or revoked");
else if (e.status === 429) console.error("Rate limited"); // already retried twice by then
else console.error(e.status, e.message, e.requestId);
}
}Self-host
const mem = new Client({ apiKey: "...", baseUrl: "https://wos.your-host.com" });Links
- Homepage: https://wontopos.com
- API reference: https://wontopos.com/en/why (Developers tab)
Changelog
- 2.2.10 —
Memoryfix: the relevance field issimilarity(notscore, which was never in the response); added typedimportance,category,is_superseded,superseded_by,created_at,event_date. - 2.2.9 — typed responses for every method (index-signature friendly) +
typed
SearchOptions; security round 2: 64MB response cap, key/model-name hygiene,Client.fromEnv(). - 2.2.8 — reliability + hardening: automatic retries (429/502/503 + connection
errors, backoff + jitter, honors
Retry-After), redirects refused, key masked intoJSON/inspect,WosError.requestId, guard againstdelete()withoutmemoryId, plain-HTTP warning,User-Agent. - 2.2.7 — speakers:
addSpeaker/listSpeakers/removeSpeaker. - 2.2.6 — docs:
userId= store, clarified everywhere. - 2.2.5 — docs: speakers + recall caching sections.
- 2.2.4 — Python/TypeScript/Rust converge on one version; all three now release in lockstep (same version, same surface). Patch releases are additive.
License: MIT.
