Wontopos — long-term memory for AI agents. Pure semantic retrieval, identical recall in every language, ~100× lower LLM bill.
Project description
Wontopos — long-term memory for AI agents
pip install wontopos
from wontopos import Client
mem = Client(api_key="wos-...")
# Each end-user / agent / topic gets its own store — create it once.
# (A "default" store already exists, so you can skip this and omit the id.)
mem.create_store("alice")
mem.add("she prefers tea over coffee", user_id="alice")
# one call → short-term + long-term + context, ready for your LLM prompt
ctx = mem.recall("what does alice drink?", user_id="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. Your LLM bill stops growing with history.
Methods
| Method | Purpose |
|---|---|
add(content, user_id, **metadata) |
Store one memory |
add_turn(user_msg, assistant_msg, user_id?) |
Store a conversation exchange |
add_bulk(content, user_id, category=, timestamp=) |
Backfill a long history |
update(old_memory_id, new_content, user_id?) |
Supersede an old fact |
search(query, user_id, limit=10, **opts) |
Semantic search |
recall(query, user_id) |
One-call context (short + long + surrounding) |
history(user_id) |
Recent turns (short-term) |
stats(user_id) |
Counts |
get(user_id, memory_id) |
Fetch one memory by id (original text, no vector) |
list_memories(user_id, limit=100, cursor=) |
Browse/export a store's raw memories, paged |
delete(user_id, memory_id) |
Delete one memory |
delete_all(user_id) |
GDPR erase (delete every memory for the user) |
add_speaker(speaker, user_id?) |
Register a person (explicit, up to 50 to start) |
list_speakers(user_id?) |
Registered people + per-person memory counts |
remove_speaker(speaker, user_id?) |
Unregister; memories stay, the tag goes |
All methods take a user_id — it names the store: one isolated memory space per end-user, agent, or topic, then per account (your API key). 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, so you can recall one person's words only.
mem.add_speaker("Bob", user_id="alice") # once per person
mem.add("I promised to send the report on Friday", user_id="alice", speaker="me")
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.search("what did Bob say about deadlines?", user_id="alice", speaker="Bob")
A store registers up to 50 people to start (a limit we plan to raise);
"me" never needs registration and never counts against it.
Async
Same surface, awaitable — needs the extra:
pip install "wontopos[async]"
from wontopos import AsyncClient
async with AsyncClient(api_key="wos-...", user_id="alice") as mem:
await mem.add("she prefers tea over coffee")
hits = await mem.search("what does alice drink?")
Every Client method exists on AsyncClient with identical arguments and
semantics (retries, redirect refusal, guards). Close with async with or
await mem.aclose().
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):
hits = mem.search("...the conversation so far...", user_id="alice",
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 withClient(retries=...);retries=0disables. - Redirects refused — the API key never follows a 3xx to another host.
- Timeouts — 30s per attempt by default (
Client(timeout=...)). - Key never in logs —
repr(client)masks the API key. - Wipe guard —
delete()without amemory_idraises instead of silently meaning "delete everything"; wiping a store is only ever the explicitdelete_all(user_id)/delete_store(user_id).
Security
Built in, none of it configurable off:
- TLS 1.2 floor and certificate verification that cannot be disabled.
- 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 a file otherwise becomes a mystery 401) and inner whitespace is rejected; model names are validated before they reach a header.
Client.from_env()readsWONTOPOS_API_KEY(orWOS_API_KEY) — keep keys out of source code.- Plain-HTTP base URLs on non-local hosts warn. One dependency (
requests, floor>=2.32for its certificate-verification fix).
Errors
Any non-2xx response raises WosError(status, message). When the server sent a
request id it's on e.request_id — include it when contacting support.
from wontopos import Client, WosError
try:
mem.search("...", user_id="alice")
except WosError as e:
if e.status == 401:
print("API key invalid or revoked")
elif e.status == 429:
print("Rate limited — back off") # already retried twice by then
else:
print(e.status, e.message, e.request_id)
Self-host
Point at your own engine:
mem = Client(api_key="...", base_url="https://wos.your-host.com")
Links
- Homepage: https://wontopos.com
- API reference: https://wontopos.com/en/why (Developers tab)
Changelog
- 2.2.10 — docs: the memory relevance field is
similarity(notscore); responses are plain dicts, so no code change — version bumped for lockstep with the TS/RustMemorytype fix. - 2.2.9 —
AsyncClient(same surface,pip install "wontopos[async]"); security round 2: TLS 1.2 floor, 64MB response cap, key/model-name hygiene,Client.from_env(),requests>=2.32floor. - 2.2.8 — reliability + hardening: automatic retries (429/502/503 + connection
errors, backoff + jitter, honors
Retry-After), redirects refused, key masked inrepr,WosError.request_id, guard againstdelete()withoutmemory_id, plain-HTTP warning,User-Agent,with_model(). - 2.2.7 — speakers:
add_speaker/list_speakers/remove_speaker. - 2.2.6 — docs:
user_id= 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wontopos-2.2.21.tar.gz.
File metadata
- Download URL: wontopos-2.2.21.tar.gz
- Upload date:
- Size: 35.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
823a2359429f6735cde5e228b000a96f090e814d176260318066d999dbb88336
|
|
| MD5 |
d0dc126a5db2954df8f4a6250945068d
|
|
| BLAKE2b-256 |
137b01d53eff8111a92004728260ee295f81800d4d67ea59ebcf92007321bb79
|
File details
Details for the file wontopos-2.2.21-py3-none-any.whl.
File metadata
- Download URL: wontopos-2.2.21-py3-none-any.whl
- Upload date:
- Size: 24.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0230a386ce8c26dffab49ce80eb9c3ed164ed033bd360efea4e42edca0646d94
|
|
| MD5 |
cf92429e896a8d1b8291c55d0a3948ac
|
|
| BLAKE2b-256 |
a059d4441aa0f2812ab958a7543551ea17e4e303c3bae6f7ef6e88e8691737e3
|