Why WOS

Long-term memory for AI agents.

WOS is a memory API. You store a user's memories once, then recall only the relevant ones for each query and pass them to your model's prompt.

Retrieval is purely semantic, with no keyword or BM25 matching, so recall quality is identical across languages. Each query returns a small, bounded context no matter how much you have stored, and no model is ever run over your stored memories.

Core operations

  • store - save a memory for a user.
  • recall - get the relevant memories for a query. This is the main call.
  • search - raw semantic search over stored memories.
  • supersede - update or replace a memory that is out of date.
  • forget - delete a single memory or an entire user (GDPR).
Pick a section on the left for detail on each topic.
Model

Three models, one lineage.

WOS models are named for how people have kept knowledge through history - Tablet, Scroll, Codex. Stone, scroll, bound book: each one does more for your agent than the last.

Tablet

Live
Carved in stone · store & recall

A lean, fast, low-cost way to inscribe and recall memory - the foundation every model builds on.

Scroll

Live
Unrolled · LLM-assisted recall

Adds a language model to read your question more closely and bring back a fuller context, so scattered evidence comes back together rather than one piece short.

Codex

Next
Bound & indexed · self-routing

Opens to the right page on its own - choosing the memory and tools each moment needs, and getting sharper the more it's used.

Tablet 1's full benchmark report is on the benchmark page.

Cost

Pay us $2. Save many times that on your LLM.

WOS feeds your LLM ~1,200 tokens per query - a bounded, relevant slice - instead of stuffing the full history into every prompt. The gap is enormous, and it grows with your history.

LLM cost per 1,000 queries Based on Tablet 1
User history100K
Queries / month1,000
Your LLM
45× cheaper - you save $244/mo
Without WOS$250.00
With WOS$5.50

Every $1 spent on WOS saves ~$98 on the LLM. Bigger history or a pricier model → bigger ROI.

Where the savings come from

  • Without WOS you stuff the whole history into each prompt - 100K tokens × $2.50/1M = $0.25 per query, at GPT-4o input rates (about 2× that on Opus-tier models).
  • With WOS you ingest once ($2/1M), then each query is a tiny retrieve ($3/1M × 1,200) plus your LLM on just ~1,200 tokens.
  • The fewer tokens your LLM reads, the less you pay - and WOS keeps that number flat as memory grows.
Context shrink = history ÷ tokens fed, not cost (the calculator above prices each retrieve).  25K → 21× · 100K → 83× · 200K → 167×.
Multilingual

Every language, the same accuracy.

Retrieval is pure semantic - embeddings only, zero keyword or BM25 matching. So recall quality is identical whether your users write in 日本語, 中文, Español, or English.

Lexical matching like BM25 is tuned to the shape of a particular language - morphology, spacing, script. In a multilingual store that means retrieval quality varies by language. WOS uses no lexical matching at all, so every language goes through the same path.

One store, three languages at once

You don't pick a language per store - mix them freely. Below, one user's memory holds Japanese, English, and Spanish at the same time, and every question finds the right memory regardless of language. This is a real exchange against the live API:

# one user, three languages stored together
mem.add("彼女はコーヒーより紅茶が好き", user_id="alice")                      # Japanese
mem.add("she works at a design studio in Brooklyn", user_id="alice")       # English
mem.add("A ella le encanta hacer senderismo los sábados", user_id="alice")  # Spanish
Actual results - each question crosses into a different language
"¿Qué bebe ella?"               -> 彼女はコーヒーより紅茶が好き
"what does she do on weekends?" -> A ella le encanta hacer senderismo los sábados
"彼女の仕事は?"                  -> she works at a design studio in Brooklyn

No translation step, no language detection, no per-language config. Memories and questions are placed by meaning, not by language - if the meaning matches, the language doesn't matter.

Three languages here is just what fits on a page - there is no supported-language list to be on. The same live test also passes with 中文, Русский, and العربية memories, all verified against the production API.

Why we banned keywords on purpose

Lexical scoring such as BM25 strengthens retrieval for some languages more than others, which gets in the way when one store holds many languages. So we removed it from the engine entirely and enforce that rule in code review: with any lexical scoring in the path, recall quality would differ by language.

LongMemEval is English-only, so it does not measure multilingual recall. The demo above is how you can verify it directly against the live API.
Architecture

No model runs over your memories.

Storage is verbatim and the engine searches by embeddings - cheap, fast, and deterministic. A model is never run over your stored memories. Tablet uses no model at all; Scroll and Codex add one around the engine for stronger results, but it only ever sees your query, never what you stored.

  • Deterministic engine. The engine returns the same memories for the same query, every time - which is why our benchmark variance comes only from the reader model.
  • Cheap at scale. No generation cost to store or retrieve, so your bill tracks storage - not model usage - as memory grows.

Your words, untouched

One common design runs a language model at write time to extract and rewrite "facts" from the text. That design trades three things: generation cost on every write, added latency, and storage of a model's paraphrase rather than the original words. WOS makes the opposite trade - it stores what was said, unchanged, and lets your LLM do the interpreting at read time with the original text in hand.

What WOS is not: not a vector DB you have to operate, and not a RAG framework you have to assemble. No model is ever run over your stored data - that path is pure embeddings. Scroll and Codex do use a language model for stronger results, but it only ever sees your query, never your stored memories - and it never trains on your data or collects it.
Proof

90.7%, measured and reproducible.

90.7% on LongMemEval-S, averaged over 5 independent runs (σ 0.5%, none cherry-picked), graded by the benchmark's canonical GPT-4o judge.

On the same benchmark, scores vary widely with the grading protocol - the judge, the prompt, and what the retrieval layer is allowed to do. We use the benchmark's canonical third-party GPT-4o judge as published, change nothing to fit the test, and publish the scoring code and reader prompt so anyone can reproduce the 90.7% exactly.

The protocol, in one table

ItemWhat we do
DatasetLongMemEval-S (cleaned), ~240K tokens of history per question
JudgeThe benchmark's canonical GPT-4o judge - a third party, not us
Runs5 independent runs, every score published, mean reported (σ 0.5%)
ReaderFixed reader model and prompt, published verbatim

What keeps it honest: a third-party judge, the reader prompt published unchanged, purely semantic retrieval, and every run reported - not just the best. The retrieval engine is deterministic - run it again and you get the same memories.

We climb harder benchmarks

We test on the hardest standard benchmark we haven't yet conquered - and the number is the high-water mark across every WOS model, rewritten each time a better one ships. Clear 94%, and we graduate to a harder benchmark.

LongMemEval-SIn progress
Tablet 185.2%
Scroll 190.7%
GPT-4o judge · best across all WOS models94% to graduate
See the full report
Pricing

Two token rates per model,
plus $0.0001 per request.

Per million tokens plus a flat $0.0001 per request, pay as you go. No subscription, no storage rent, no memory caps. You pay when your agent writes or reads - never for what it remembers.

ModelInput / 1MOutput / 1M
Tablet 1$2$3Live
Scroll 1$4$8Live
Codex 1--TBD
  • $0.0001 per request. A flat fee on every API call, on top of token usage.
  • Storage is free. Ingest pays once; keeping it costs you nothing. No count limit, no retention limit.
  • We store it. We never train on it, use it, or look at it. Your agent's memory is yours - we only organize it so you can retrieve it.
  • Why Tablet is this cheap: its engine runs no model, so our cost is embeddings and disk - not GPUs. Scroll and Codex add a model, which is what their higher price covers.
Other billing models charge monthly for stored volume or cap memory counts by plan. WOS charges nothing for stored data, regardless of volume or age.

Rate limits by usage tier →

For developers

Three calls: store, recall, answer.

One API. The recall() call returns short-term, long-term, and surrounding context in a single round-trip, ready to drop into your prompt.

1

Store

add() your user's facts and turns. Embedded on the way in - no LLM.

2

Recall

recall() returns short-term + long-term + context in one call - a bounded, fixed-size context.

3

Answer

Feed that bounded context to your LLM - any provider, your key.

from wontopos import Client
mem = Client(api_key="wos-...")
mem.add("she prefers tea over coffee", user_id="alice")
# one call: short + long + context
ctx = mem.recall("what does alice drink?", user_id="alice")
Quickstart

Your first recall in 5 minutes.

One key, one install line, three calls - your agent has memory. Every snippet on this page was actually run; responses are shown verbatim.

1

Get an API key

Create one in the console. A 155-character key starting with wos-live- is shown once. Keep it in an environment variable - never in code.

2

Install

pip install wontopos        # Python
npm install wontopos        # TypeScript / JavaScript
cargo add wontopos          # Rust
# curl - nothing to install, just set WOS_API_KEY
3

Create a store, then store & recall

A store is the user_id you read and write under. Stores are explicit: create one first (the call below), then store and recall under it. Store - embedded on the way in, no LLM call. Recall - short-term + long-term + context in one round-trip.

from wontopos import Client

mem = Client(api_key="wos-live-...", user_id="alice")  # set the store once
mem.create_store()              # create it (stores are explicit)
mem.add("she prefers tea over coffee")  # no user_id needed

# one call → short-term + long-term + context
ctx = mem.recall("what does alice drink?")
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-...", userId: "alice" });  // set the store once
await mem.createStore();            // create it (stores are explicit)
await mem.add("she prefers tea over coffee");  // no userId needed

// one call → short-term + long-term + context
const ctx = await mem.recall("what does alice drink?");
use wontopos::Client;

let mem = Client::new("wos-live-...").with_user("alice");  // set the store once
mem.create_store(None).await?;            // create it (stores are explicit)
mem.add("she prefers tea over coffee", None, json!({})).await?;

// one call → short-term + long-term + context
let ctx = mem.recall("what does alice drink?", None).await?;
# create the store once - stores are explicit
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# store - embedded on the way in, no LLM call
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'

# one call → short-term + long-term + context
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does alice drink?"}'
Actual response - create_store()
{"user_id": "alice", "status": "created"}
Actual response - add()
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Set the store once. Pass user_id to the client and every call uses it - no need to repeat it; override any single call by passing user_id to it. Stores are explicit: storing into or recalling from a store that doesn't exist returns 404 - create it first. Every account starts with a default store, so with no user_id at all the zero-setup path just works. See Stores to list and manage them.

recall() returns four blocks - short_term (recent turns), long_term (relevant memories), context (what surrounded the best match), and an instruction telling the LLM how to use them. Drop the whole thing into your prompt.

Works in any language. Store in English, ask in Korean, Japanese, or Chinese - the same memory comes back. Embedding search, not keyword matching.

Every method, per language →

Stores

Stores - create, list, delete.

A store is the user_id you read and write under - one isolated memory space per end-user, agent, or topic. Stores are explicit: create one before you store into or recall from it, or the call returns 404. Every account starts with a default store, so you can begin without a create call.

How isolation nests. An account owns workspaces; each workspace isolates its own memory, API keys, and usage (billing is shared at the account). A store lives inside a workspace: keys in the same workspace share its stores, and different workspaces never see each other's memory. account → workspace → store (user_id) → memories.
mem.create_store("alice")        # create (idempotent)
mem.list_stores()              # [{"user_id","created_at"}, ...]
mem.delete_store("alice")        # delete the store + all its memories
await mem.createStore("alice");
await mem.listStores();          // [{ user_id, created_at }, ...]
await mem.deleteStore("alice");     // store + all its memories
mem.create_store("alice").await?;
let stores = mem.list_stores().await?;
mem.delete_store("alice").await?;
# create
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
# list
curl https://api.wontopos.com/api/v1/memory/collections -H "X-API-Key: $WOS_API_KEY"
# delete (store + all its memories)
curl -X DELETE https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
Actual response - create
{ "user_id": "alice", "status": "created" }   // "exists" if it already did
Actual response - list
{ "collections": [
  { "user_id": "default", "created_at": "2026-06-26T02:23:14Z" },
  { "user_id": "alice",   "created_at": "2026-06-26T02:24:01Z" }
], "count": 2 }
Recall on a store that doesn't exist
{ "error": { "type": "not_found_error",
  "message": "Store 'ghost' does not exist. Create it first with
              POST /api/v1/memory/collection {\"user_id\":\"ghost\"}, then store or recall." } }
Use one store per end-user ("alice", "user_42") to keep each person's memory separate, or a single default store for a personal agent. You can also create and browse stores in the console (Memory ids → Issue) without writing code. Deleting a store is permanent - it drops every memory under it.
Python SDK

Python - every method, three groups.

Write, read, delete. Every example below was run against the live API on 2026-06-10; responses are verbatim.

pip install wontopos
from wontopos import Client

mem = Client(api_key="wos-live-...")  # or read from an env var

Choose a model

The API key picks which memory (your account); the model picks which engine reads it. Every model shares one memory, so you can store with one and recall with another. Set a default on the client; override a single call by passing model=.

mem = Client(api_key="wos-live-...", model="tablet-1")  # default engine
mem.recall("...", user_id="alice")                  # tablet-1
mem.recall("...", user_id="alice", model="tablet-1")  # or pick a model per call

list_models ✓ live-tested

The catalog - the ids you can pass to model and whether each is live. memory: "shared" models read the same store; "isolated" keeps its own. Needs no API key.

mem.list_models()
Actual response
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"}]

The catalog above always reflects the models available right now - pass any other id and you get a clear error. New models appear there automatically when they ship.

Write

add ✓ live-tested

Store one memory. Embedded on the way in - no LLM call, you pay embeddings only.

mem.add("she prefers tea over coffee", user_id="alice")
Actual response
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn ✓ live-tested

Store one conversation turn (user + assistant) into short-term and long-term memory at once.

mem.add_turn("alice", "hi", "hello!")
Actual response
{"status": "ok"}

add_bulk ✓ live-tested

Backfill a large blob of text. Chunked and embedded server-side - ideal for importing existing history.

mem.add_bulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", user_id="alice")
Actual response
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update ✓ live-tested

A fact changed. The old memory is marked superseded (kept for history); the new one takes its place in recall.

mem.update("alice", old_memory_id="576700aa-...", new_content="she switched to coffee this year")
Actual response
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Read

search ✓ live-tested

Semantic search, most relevant first. Pure embedding - no keyword matching, so any language finds any memory. The SDK returns the memories array directly; the raw HTTP body is shown below.

r = mem.search("what does she drink?", user_id="alice", limit=1)
Actual response (HTTP body)
{"memories": [{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-06-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
FieldMeaning
similarityRaw embedding similarity to your query (0–1).
is_supersededTrue if this fact was replaced by update().
search_msServer-side retrieval time.

recall ✓ live-tested

One round-trip returns everything your LLM needs - paste the result straight into your prompt: a bounded, fixed-size context no matter how much you've stored.

ctx = mem.recall("what does she drink?", user_id="alice")
Actual response (shape - lists shortened)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history ✓ live-tested

Recent conversation turns (short-term memory), oldest first.

turns = mem.history("alice")
Actual response (HTTP body)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-06-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-06-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Memory counts for one user.

mem.stats("alice")
Actual response
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

Delete

delete ✓ live-tested

Delete a single memory by id.

mem.delete("alice", memory_id="576700aa-...")
Actual response
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all ✓ live-tested

Erase everything for one user - one call, GDPR-ready.

mem.delete_all("alice")
Actual response
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}
TypeScript SDK

TypeScript - every method, three groups.

Write, read, delete. Every example below was run against the live API on 2026-06-10; responses are verbatim.

npm install wontopos
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-..." });

Choose a model

The API key picks which memory (your account); the model picks which engine reads it. Every model shares one memory, so you can store with one and recall with another. Set a default in the constructor; override a single call with withModel().

const mem = new Client({ apiKey: "wos-live-...", model: "tablet-1" });  // default
mem.recall("...", "alice");                          // tablet-1
mem.withModel("tablet-1").recall("...", "alice");  // or pick a model per call

listModels ✓ live-tested

The catalog - the ids you can pass to model and whether each is live. memory: "shared" models read the same store; "isolated" keeps its own. Needs no API key.

await mem.listModels();
Actual response
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"}]

The catalog above always reflects the models available right now - pass any other id and you get a clear error. New models appear there automatically when they ship.

Write

add ✓ live-tested

Store one memory. Embedded on the way in - no LLM call, you pay embeddings only.

await mem.add("she prefers tea over coffee", "alice");
Actual response
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

addTurn ✓ live-tested

Store one conversation turn (user + assistant) into short-term and long-term memory at once.

await mem.addTurn("alice", "hi", "hello!");
Actual response
{"status": "ok"}

addBulk ✓ live-tested

Backfill a large blob of text. Chunked and embedded server-side - ideal for importing existing history.

await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
Actual response
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update ✓ live-tested

A fact changed. The old memory is marked superseded (kept for history); the new one takes its place in recall.

await mem.update("alice", "576700aa-...", "she switched to coffee this year");
Actual response
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Read

search ✓ live-tested

Semantic search, most relevant first. Pure embedding - no keyword matching, so any language finds any memory. The SDK returns the memories array directly; the raw HTTP body is shown below.

const r = await mem.search("what does she drink?", "alice", 1);
Actual response (HTTP body)
{"memories": [{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-06-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
FieldMeaning
similarityRaw embedding similarity to your query (0–1).
is_supersededTrue if this fact was replaced by update().
search_msServer-side retrieval time.

recall ✓ live-tested

One round-trip returns everything your LLM needs - paste the result straight into your prompt: a bounded, fixed-size context no matter how much you've stored.

const ctx = await mem.recall("what does she drink?", "alice");
Actual response (shape - lists shortened)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history ✓ live-tested

Recent conversation turns (short-term memory), oldest first.

const turns = await mem.history("alice");
Actual response (HTTP body)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-06-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-06-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Memory counts for one user.

await mem.stats("alice");
Actual response
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

Delete

delete ✓ live-tested

Delete a single memory by id.

await mem.delete("alice", "576700aa-...");
Actual response
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

deleteAll ✓ live-tested

Erase everything for one user - one call, GDPR-ready.

await mem.deleteAll("alice");
Actual response
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}
Rust SDK

Rust - every method, three groups.

Write, read, delete. Every example below was run against the live API on 2026-06-10; responses are verbatim.

cargo add wontopos
use wontopos::Client;

let mem = Client::new("wos-live-...");

Choose a model

The API key picks which memory (your account); the model picks which engine reads it. Every model shares one memory, so you can store with one and recall with another. Set a default with with_model(); chain it again to override a single call.

let mem = Client::new("wos-live-...").with_model("tablet-1");  // default
mem.recall("...", "alice").await?;                       // tablet-1
mem.with_model("tablet-1").recall("...", "alice").await?;  // or pick a model per call

list_models ✓ live-tested

The catalog - the ids you can pass to with_model and whether each is live. memory: "shared" models read the same store; "isolated" keeps its own. Needs no API key.

mem.list_models().await?;
Actual response
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"}]

The catalog above always reflects the models available right now - pass any other id and you get a clear error. New models appear there automatically when they ship.

Write

add ✓ live-tested

Store one memory. Embedded on the way in - no LLM call, you pay embeddings only.

mem.add("she prefers tea over coffee", "alice", json!({})).await?;
Actual response
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn ✓ live-tested

Store one conversation turn (user + assistant) into short-term and long-term memory at once.

mem.add_turn("alice", "hi", "hello!").await?;
Actual response
{"status": "ok"}

add_bulk ✓ live-tested

Backfill a large blob of text. Chunked and embedded server-side - ideal for importing existing history.

mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
Actual response
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update ✓ live-tested

A fact changed. The old memory is marked superseded (kept for history); the new one takes its place in recall.

mem.update("alice", "576700aa-...", "she switched to coffee this year").await?;
Actual response
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Read

search ✓ live-tested

Semantic search, most relevant first. Pure embedding - no keyword matching, so any language finds any memory. The SDK returns the memories array directly; the raw HTTP body is shown below.

let r = mem.search("what does she drink?", "alice", 1).await?;
Actual response (HTTP body)
{"memories": [{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-06-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
FieldMeaning
similarityRaw embedding similarity to your query (0–1).
is_supersededTrue if this fact was replaced by update().
search_msServer-side retrieval time.

recall ✓ live-tested

One round-trip returns everything your LLM needs - paste the result straight into your prompt: a bounded, fixed-size context no matter how much you've stored.

let ctx = mem.recall("what does she drink?", "alice").await?;
Actual response (shape - lists shortened)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history ✓ live-tested

Recent conversation turns (short-term memory), oldest first.

let turns = mem.history("alice").await?;
Actual response (HTTP body)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-06-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-06-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Memory counts for one user.

mem.stats("alice").await?;
Actual response
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

Delete

delete ✓ live-tested

Delete a single memory by id.

mem.delete("alice", "576700aa-...").await?;
Actual response
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all ✓ live-tested

Erase everything for one user - one call, GDPR-ready.

mem.delete_all("alice").await?;
Actual response
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}
curl

curl - no install, same methods.

No SDK to install - any HTTP client works. Set your key once and call the same endpoints the SDKs wrap. Base URL https://api.wontopos.com, auth via X-API-Key, JSON in and out.

# set your key once (never hard-code it)
export WOS_API_KEY="wos-live-..."

Write

store ✓ live-tested

Store one memory. Embedded on the way in - no LLM call.

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
Actual response
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

store-turn ✓ live-tested

Store one conversation turn (user + assistant) into short and long-term memory at once.

curl -X POST https://api.wontopos.com/api/v1/memory/store-turn \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","user_msg":"hi","assistant_msg":"hello!"}'
Actual response
{"status": "ok"}

supersede ✓ live-tested

A fact changed - the old memory is marked superseded, the new one takes its place in recall.

curl -X POST https://api.wontopos.com/api/v1/memory/supersede \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","old_memory_id":"576700aa-...","new_content":"she switched to coffee this year"}'
Actual response
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}

Read

search ✓ live-tested

Semantic search, most relevant first. Pure embedding - any language finds any memory.

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does she drink?","max_results":1}'
Actual response
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
   "similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}

recall ✓ live-tested

One round-trip returns short-term + long-term + context + an instruction. Paste it straight into your prompt.

curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does she drink?"}'
Actual response (shape)
{"short_term": {"count": 2, "turns": [...]},
 "long_term":  {"count": 4, "memories": [{"content": "she prefers tea over coffee", "similarity": 0.63}]},
 "context":    {"count": 4, "around_top_memory": ["[match] she prefers tea over coffee"]},
 "instruction": "Use short_term for recent context, long_term for relevant past memories..."}

Delete

forget ✓ live-tested

Delete one memory by id, or omit it to delete everything for a user (GDPR).

curl -X POST https://api.wontopos.com/api/v1/memory/forget \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'  # omit memory_id = delete all
Actual response
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}

Every endpoint + body fields →

Engrams

Engrams

Callable recall tools your model can invoke - each one a different retrieval strategy over the same memory. Use one, or run several at once.

Coming soon. The general engrams below are no-LLM retrieval pipelines, so they run on every tier from Tablet 1 up. The API serves store and recall today, so these calls aren't live yet. Memoir and Archive, a separate model mode, are covered in their own section below.

More engrams ship regularly - this list grows.

Memoir & Archive Coming soon

This one is a model mode, not a callable tool. Pick it on the model and every recall, a plain search included, comes back with time written that way. Coming soon.

All engrams Engrams

Time_awareness Coming soon

A model mode, not a per-call option. Pick the model - Time_awareness-memoir or Time_awareness-archive - and every recall comes back rendered that way: a plain search, a recall, or any engram, no extra parameter. A Memoir reads the way a person remembers; an Archive keeps an exact record - the difference shows up most in how each writes time. The Time_awareness- prefix leaves room for more capabilities later.

Memoir

Time_awareness-memoir
Remembered like a person · a narrative

Tells what happened and how one moment led to the next, with the soft sense of time a person recalls - read as experience, not a list.

Archive

Time_awareness-archive
Kept as a record · precise time

Returns matches as exact records - precise elapsed time and absolute anchors, structured for a model to read straight off.

It renders memories you've already stored - it doesn't create them. Each memory is one store / add call under a user_id (that user_id is that person's store). Store first; then any recall - the plain search below included - comes back time-tagged. See Quickstart to store.
# plain search - no engram - on a memoir model
r = mem.search("what does Alice drink?", user_id="alice", model="Time_awareness-memoir", tz=9)
# every hit gets a .time field → "a couple weeks ago"  (archive model → "2 weeks ago (Jun 09)")
// plain search - no engram - on a memoir model
const r = await mem.withModel("Time_awareness-memoir").search("what does Alice drink?", "alice", 10, { tz: 9 });
// model picks the mode; tz over HTTP via the X-WOS-Timezone header
let r = mem.with_model("Time_awareness-memoir").search("what does Alice drink?", "alice", 10).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: wos-live-..." -H "X-WOS-Model: Time_awareness-memoir" -H "X-WOS-Timezone: 9" \
  -d '{"user_id":"alice","query":"what does Alice drink?"}'
# each memory comes back with a "time" field; use Time_awareness-archive for exact time

tz is the caller's UTC offset in hours - so "this morning" and the 4am day boundary land in their local time. Omit it for UTC; over HTTP it's the X-WOS-Timezone header. Roughly, by region: US East -5, US Central -6, US West -8 · UK / Lisbon 0 · Central Europe +1 · Eastern Europe +2 · India +5.5 · China / Singapore +8 · Korea / Japan +9 · Sydney +10. (Standard time - daylight saving shifts some regions by +1; pass whatever your users are actually on.)

Same search, two models - the memories are identical, only time changes:

Result · Time_awareness-memoir
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "a couple weeks ago" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday afternoon" },
  { "content": "Alice moved to Brooklyn",          "time": "about half a year ago" }
] }
Result · Time_awareness-archive
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "2 weeks ago (Jun 09)" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday at 14:00" },
  { "content": "Alice moved to Brooklyn",          "time": "6 months ago (Dec 2025)" }
] }
ElapsedMemoirArchive
3 mina few minutes ago3 minutes ago
14 minabout 15 minutes ago14 minutes ago
30 minhalf an hour ago30 minutes ago
50 minabout an hour ago50 minutes ago
2 hra couple hours ago2 hours ago, at 13:10
8 hrthis morning8 hours ago, at 07:10
yesterday pmyesterday afternoonyesterday at 14:00
last nightlast night17 hours ago, at 22:00
2 daysa couple days ago2 days ago (Tue 15:10)
6 daysseveral days ago6 days ago (Fri 15:10)
9 daysabout a week agolast week (Jun 16)
16 daysa couple weeks ago2 weeks ago (Jun 09)
35 daysabout a month agolast month (May 21)
60 daysa couple months ago2 months ago (Apr 2026)
180 daysabout half a year ago6 months ago (Dec 2025)
380 daysabout a year agolast year (Jun 2025)
800 daysa couple years ago2 years ago (Apr 2024)
1500 daysabout 4 years ago4 years ago (May 2022)

Every value above is the renderer's real output. Look at the two "yesterday" rows: a Memoir splits afternoon from last night - a day is one sleep - while an Archive writes a single clock time and draws no day or night line.

How each mode reads time

Memoir - the way people actually say it. Recent moments stay fairly sharp (about 15 minutes, half an hour), then the wording widens the further back you go - a couple weeks, about half a year, a couple years - the way memory itself loosens with distance. Inside a day it drops the clock for a landmark: this morning, last night, yesterday afternoon. And a day is one sleep, not a calendar tick: the boundary sits around 4am local time, so a late night still reads as the same evening, not already tomorrow.

Archive - precise, always with an anchor. Every line carries the exact elapsed time plus an absolute reference a model can compute from, and the anchor tightens as it nears: a clock for today (8 hours ago, at 07:10), a weekday and clock this week (2 days ago (Tue 15:10)), a date this month (last week (Jun 16)), a month and year beyond (6 months ago (Dec 2025)). Never vague, never wrong.

Memoir and Archive are a model mode applied to every recall - a plain search, a recall, or an engram - not a per-call option. The model tier (Tablet → Scroll → Codex) sets how much the engine does; the mode (memoir / archive) sets how it writes time. Coming soon.
All engrams Engrams

deep_recall

Multi-hop recall. Searches your query, then takes the top match and searches again on its content - pulling in linked context a single search would miss. Best when memories reference each other (a person → their projects → details). Returns up to ~12.

out = mem.engram("deep_recall", "what should I know about Alice?", user_id="alice")
const out = await mem.engram("deep_recall", "what should I know about Alice?", "alice");
let out = mem.engram("deep_recall", "what should I know about Alice?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"deep_recall","user_id":"alice","query":"what should I know about Alice?"}'
Response
{ "engram": "deep_recall", "hops": 2, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 5, "output_tokens": 61 } }
Metered by tokens - every call returns usage (input + output), counted by the same tokenizer as the rest of the API; no hidden per-engram fee. Need several at once? Call them concurrently - each engram is an independent request.
All engrams Engrams

timeline

Time-ordered recall. Returns memories sorted newest-first by when the event happened, not by relevance. For "when did X", history, and sequence questions. Returns up to 15.

events = mem.engram("timeline", "project milestones", user_id="alice")
const events = await mem.engram("timeline", "project milestones", "alice");
let events = mem.engram("timeline", "project milestones", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"timeline","user_id":"alice","query":"project milestones"}'
Response
{ "engram": "timeline", "hops": 1, "count": 15,
  "memories": [ ... ],
  "usage": { "input_tokens": 4, "output_tokens": 88 } }
Metered by tokens - every call returns usage (input + output), counted by the same tokenizer as the rest of the API; no hidden per-engram fee. Need several at once? Call them concurrently - each engram is an independent request.
All engrams Engrams

gather

Broad gather. Searches, then expands around the top three matches - a wider net than deep_recall. Use it to pull in everything related to a person, project, or topic in one call. Returns up to ~18.

related = mem.engram("gather", "everything about Project Atlas", user_id="alice")
const related = await mem.engram("gather", "everything about Project Atlas", "alice");
let related = mem.engram("gather", "everything about Project Atlas", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"gather","user_id":"alice","query":"everything about Project Atlas"}'
Response
{ "engram": "gather", "hops": 4, "count": 18,
  "memories": [ ... ],
  "usage": { "input_tokens": 6, "output_tokens": 142 } }
Metered by tokens - every call returns usage (input + output), counted by the same tokenizer as the rest of the API; no hidden per-engram fee. Need several at once? Call them concurrently - each engram is an independent request.
HTTP API

Every endpoint, one base URL.

No SDK required - any HTTP client works. Base URL https://api.wontopos.com, auth via the X-API-Key header, JSON in and out. Memory ops are POST; managing stores uses POST / GET / DELETE on /collection. A store must exist first (see Stores) or in-store ops return 404.

EndpointPurposeBody fields
POST /api/v1/memory/collectioncreate a storeuser_id
GET /api/v1/memory/collectionslist your stores(none)
DELETE /api/v1/memory/collectiondelete a store + its memoriesuser_id
/api/v1/memory/storestore one memoryuser_id · content · metadata?
/api/v1/memory/store-turnstore a conversation turnuser_id · user_msg · assistant_msg
/api/v1/memory/bulk-storebackfill a text blobuser_id · content · category?
/api/v1/memory/searchsemantic searchuser_id · query · max_results?
/api/v1/memory/recallshort + long + contextuser_id · query
/api/v1/memory/historyrecent turnsuser_id
/api/v1/memory/statsmemory countsuser_id
/api/v1/memory/supersedereplace a changed factuser_id · old_memory_id · new_content
/api/v1/memory/forgetdelete one (or all)user_id · memory_id? (omit = delete all)
# create the store once (stores are explicit)
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# store a memory
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"she prefers tea over coffee"}'

# recall - one call, ready for your prompt
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does alice drink?"}'
Actual response - store
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Usage tiers

Same features for everyone.
Tiers only raise your limits.

Every tier runs the full engine - same recall quality, same languages, every method. Tiers advance automatically through Tier 5 as your cumulative credit purchases grow, with no application or sales call. Enterprise (Tier 6) is the one exception.

Spend limits

Each tier caps how much you can spend per calendar month. You advance immediately when your cumulative credit purchases reach the next threshold.

Usage tierCredit purchaseMonthly spend limit
Tier 1$5$100
Tier 2$40$500
Tier 3$200$1,000
Tier 4$400$5,000
Tier 5$1,000$25,000
Tier 6 - EnterpriseTalk to usNo limit

Rate limits

Rate limits are per account - every API key on an account shares one limit, which scales with your tier. Exceeding it returns a 429 with a retry-after header; back off (1s → 2s → 4s) and retry. Every endpoint is idempotent-friendly, so retries are safe.

TierRequests per minute
Tier 1150
Tier 2300
Tier 3600
Tier 41,500
Tier 53,000
Tier 6 - EnterpriseCustom

Enterprise (Tier 6) gets custom rate limits, an SLA, dedicated support, and an optional self-host license - talk to us.

Pricing is usage-based: tokens plus a flat $0.0001 per request. Tablet is $2 per 1M input tokens, $3 per 1M output. Storage is free with no caps. See why we price it this way.
Errors & limits

When something goes wrong.

Errors come back as a JSON envelope with a stable type, a human message, and a request_id you can send us when reporting an issue.

Actual response - invalid key (HTTP 401)
{"type": "error", "error": {
   "type": "authentication_error",
   "message": "Invalid or revoked API key.",
   "request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
 }}
HTTPMeaningWhat to do
401Invalid or revoked API keyCheck the key; issue a new one in the console.
422Malformed body (missing/wrong-type field)The message names the exact field - fix and retry.
429Rate limitedBack off exponentially (1s → 2s → 4s) and retry. Safe: all endpoints are idempotent-friendly.
5xxServer-side problemRetry with backoff; include request_id if you contact us.
# SDK error handling (Python)
from wontopos import Client, WosError

try:
    mem.search("...", user_id="alice")
except WosError as e:
    if e.status == 401: ...  # bad key
    elif e.status == 429: ...  # back off and retry
Key safety. Your key is shown once at creation and stored only as a hash on our side. Keep it in an environment variable; if it leaks, revoke it in the console - revocation is immediate.

Rate limits are per account, shared across all your keys, and scale with your tier - see Usage tiers. Your account's usage is shown in the console.

Self-host

Your servers, your data.

The engine can run inside your own infrastructure - same API, same SDKs. Point the client at your host and nothing else changes.

mem = Client(api_key="...", base_url="https://wos.your-host.com")
const mem = new Client({ apiKey: "...", baseUrl: "https://wos.your-host.com" });
let mem = Client::with_base_url("...", "https://wos.your-host.com");
  • Data residency. Memories never leave your network.
  • Same surface. The same methods and endpoints work identically.
  • Licensing. Self-host packages are arranged per deployment - contact us.
Scale

Beyond the context window.

WOS recalls from histories of 1.4M tokens - far larger than any LLM context window - and still hands back a tight ~1,470-token slice.

Your agent's memory isn't capped by what fits in a prompt. It keeps everything and retrieves only what matters, no matter how large the history grows.

Privacy

Private, and yours.

Your data stays in your store. We never train on it, view it, or reuse it - we only organize it so you can retrieve it.

  • BYOK. Your LLM key is sent per request and never stored.
  • Isolated. Memories are scoped per account, then per user_id.
  • GDPR delete & self-host. One call wipes a user; run the engine in your own environment if you prefer.