Pourquoi WOS

Une mémoire à long terme pour les agents IA.

WOS est une API de mémoire. Vous stockez une fois les souvenirs d'un utilisateur, puis ne rappelez que ceux qui sont pertinents pour chaque requête et les transmettez au prompt de votre modèle.

La récupération est purement sémantique, sans correspondance par mots-clés ni BM25 : la qualité du rappel est donc identique d'une langue à l'autre. Chaque requête renvoie un contexte réduit et borné, quel que soit le volume stocké, et aucun modèle n'est jamais exécuté sur vos souvenirs stockés.

Opérations de base

  • store - enregistre un souvenir pour un utilisateur.
  • recall - récupère les souvenirs pertinents pour une requête. C'est l'appel principal.
  • search - recherche sémantique brute sur les souvenirs stockés.
  • supersede - met à jour ou remplace un souvenir devenu obsolète.
  • forget - supprime un souvenir unique ou un utilisateur entier (RGPD).
Choisissez une section à gauche pour le détail de chaque sujet.
Modèle

Trois modèles, une même lignée.

Les modèles WOS portent le nom des supports par lesquels l'humanité a conservé le savoir au fil de l'histoire - Tablet, Scroll, Codex. La pierre, le rouleau, le livre relié : chacun apporte davantage à votre agent que le précédent.

Tablet

Disponible
Gravé dans la pierre · store & recall

Un moyen sobre, rapide et économique d'inscrire et de rappeler la mémoire - la fondation sur laquelle chaque modèle s'appuie.

Scroll

Disponible
Déroulé · rappel assisté par LLM

Ajoute un modèle de langage qui lit votre question de plus près et ramène un contexte plus complet, pour que les indices dispersés reviennent ensemble au lieu qu'il en manque un.

Codex

À venir
Relié & indexé · routage autonome

S'ouvre de lui-même à la bonne page - il choisit la mémoire et les outils dont chaque instant a besoin, et s'affine à mesure qu'on l'utilise.

Le rapport de benchmark complet de Tablet 1 se trouve sur la page benchmark.

Coût

Payez-nous $2. Économisez plusieurs fois ce montant sur votre LLM.

WOS fournit à votre LLM ~1,200 tokens par requête - une tranche bornée et pertinente - au lieu d'entasser l'historique complet dans chaque prompt. L'écart est énorme, et il grandit avec votre historique.

Coût LLM pour 1,000 requêtes Basé sur Tablet 1
Historique utilisateur100K
Requêtes / mois1,000
Votre LLM
45× moins cher - vous économisez $244/mois
Sans WOS$250.00
Avec WOS$5.50

Chaque $1 dépensé sur WOS fait économiser ~$98 sur le LLM. Un historique plus grand ou un modèle plus cher → un ROI plus grand.

D'où viennent les économies

  • Sans WOS, vous entassez l'historique entier dans chaque prompt - 100K tokens × $2.50/1M = $0.25 par requête, au tarif d'entrée de GPT-4o (environ 2× plus sur les modèles de classe Opus).
  • Avec WOS, vous ingérez une fois ($2/1M), puis chaque requête n'est qu'une petite récupération ($3/1M × 1,200) plus votre LLM sur seulement ~1,200 tokens.
  • Moins votre LLM lit de tokens, moins vous payez - et WOS garde ce nombre constant à mesure que la mémoire grandit.
Réduction de contexte = historique ÷ tokens fournis, pas le coût (le calculateur ci-dessus facture chaque récupération).  25K → 21× · 100K → 83× · 200K → 167×.
Multilingue

Toutes les langues, la même précision.

La récupération est purement sémantique - embeddings uniquement, zéro correspondance par mots-clés ou BM25. La qualité du rappel est donc identique que vos utilisateurs écrivent en 日本語, en 中文, en Español ou en anglais.

La correspondance lexicale comme BM25 est calibrée sur la forme d'une langue donnée - morphologie, espacement, écriture. Dans un store multilingue, la qualité de récupération varie alors selon la langue. WOS n'utilise aucune correspondance lexicale : toutes les langues empruntent le même chemin.

Un seul store, trois langues à la fois

Vous ne choisissez pas une langue par store - mélangez-les librement. Ci-dessous, la mémoire d'un même utilisateur contient à la fois du japonais, de l'anglais et de l'espagnol, et chaque question retrouve le bon souvenir quelle que soit la langue. Il s'agit d'un échange réel avec l'API en production :

# 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
Résultats réels - chaque question bascule vers une langue différente
"¿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

Aucune étape de traduction, aucune détection de langue, aucune configuration par langue. Souvenirs et questions sont placés selon le sens, pas selon la langue - si le sens correspond, la langue n'a pas d'importance.

Trois langues ici, c'est simplement ce qui tient sur une page - il n'existe aucune liste de langues prises en charge à laquelle figurer. Le même test en conditions réelles passe aussi avec des souvenirs en 中文, en Русский et en العربية, tous vérifiés contre l'API de production.

Pourquoi nous avons banni les mots-clés à dessein

Un scoring lexical tel que BM25 renforce la récupération pour certaines langues plus que pour d'autres, ce qui pose problème quand un même store contient de nombreuses langues. Nous l'avons donc entièrement retiré du moteur et faisons respecter cette règle en revue de code : avec le moindre scoring lexical dans le chemin, la qualité du rappel varierait selon la langue.

LongMemEval est uniquement en anglais et ne mesure donc pas le rappel multilingue. La démonstration ci-dessus montre comment le vérifier directement contre l'API en production.
Architecture

Aucun modèle ne s'exécute sur vos souvenirs.

Le stockage est verbatim et le moteur cherche par embeddings - économique, rapide et déterministe. Un modèle n'est jamais exécuté sur vos souvenirs stockés. Tablet n'utilise aucun modèle ; Scroll et Codex en ajoutent un autour du moteur pour de meilleurs résultats, mais il ne voit jamais que votre requête, jamais ce que vous avez stocké.

  • Moteur déterministe. Le moteur renvoie les mêmes souvenirs pour la même requête, à chaque fois - c'est pourquoi la variance de notre benchmark provient uniquement du modèle lecteur.
  • Économique à grande échelle. Aucun coût de génération pour stocker ou récupérer : votre facture suit le stockage - pas l'usage de modèle - à mesure que la mémoire grandit.

Vos mots, intacts

Une conception courante exécute un modèle de langage à l'écriture pour extraire et réécrire des « faits » à partir du texte. Cette conception sacrifie trois choses : un coût de génération à chaque écriture, une latence supplémentaire, et le stockage d'une paraphrase du modèle plutôt que des mots d'origine. WOS fait le choix inverse - il stocke ce qui a été dit, sans modification, et laisse votre LLM interpréter à la lecture, le texte original en main.

Ce que WOS n'est pas : ni une base vectorielle à opérer vous-même, ni un framework RAG à assembler. Aucun modèle n'est jamais exécuté sur vos données stockées - ce chemin est purement embeddings. Scroll et Codex utilisent bien un modèle de langage pour de meilleurs résultats, mais il ne voit jamais que votre requête, jamais vos souvenirs stockés - et il ne s'entraîne jamais sur vos données ni ne les collecte.
Preuve

90.7%, mesuré et reproductible.

90.7% sur LongMemEval-S, en moyenne sur 5 runs indépendants (σ 0.5%, aucun trié sur le volet), noté par le juge canonique GPT-4o du benchmark.

Sur le même benchmark, les scores varient fortement selon le protocole de notation - le juge, le prompt, et ce que la couche de récupération est autorisée à faire. Nous utilisons le juge tiers canonique GPT-4o du benchmark tel que publié, ne modifions rien pour coller au test, et publions le code de notation et le prompt du lecteur pour que chacun puisse reproduire exactement les 90.7%.

Le protocole, en un tableau

ÉlémentCe que nous faisons
Jeu de donnéesLongMemEval-S (nettoyé), ~240K tokens d'historique par question
JugeLe juge canonique GPT-4o du benchmark - un tiers, pas nous
Runs5 runs indépendants, chaque score publié, moyenne rapportée (σ 0.5%)
LecteurModèle lecteur et prompt fixes, publiés verbatim

Ce qui garantit l'honnêteté : un juge tiers, le prompt du lecteur publié tel quel, une récupération purement sémantique, et chaque run rapporté - pas seulement le meilleur. Le moteur de récupération est déterministe - relancez-le et vous obtenez les mêmes souvenirs.

Nous gravissons des benchmarks plus difficiles

Nous testons sur le benchmark standard le plus difficile que nous n'avons pas encore conquis - et le chiffre affiché est le record parmi tous les modèles WOS, réécrit chaque fois qu'un meilleur modèle sort. Passé 94%, nous passons à un benchmark plus difficile.

LongMemEval-SEn cours
Tablet 185.2%
Scroll 190.7%
Juge GPT-4o · meilleur score parmi tous les modèles WOS94% pour passer au suivant
Voir le rapport complet
Tarifs

Deux tarifs de tokens par modèle,
plus $0.0001 par requête.

Par million de tokens plus $0.0001 fixe par requête, à l'usage. Pas d'abonnement, pas de loyer de stockage, pas de plafond de mémoire. Vous payez quand votre agent écrit ou lit - jamais pour ce qu'il retient.

ModèleEntrée / 1MSortie / 1M
Tablet 1$2$3Disponible
Scroll 1$4$8Disponible
Codex 1--À définir
  • $0.0001 par requête. Des frais fixes sur chaque appel API, en plus de l'usage en tokens.
  • Le stockage est gratuit. L'ingestion se paie une fois ; la conservation ne coûte rien. Aucune limite de nombre, aucune limite de rétention.
  • Nous le stockons. Nous ne nous entraînons jamais dessus, ne l'utilisons jamais et ne le regardons jamais. La mémoire de votre agent vous appartient - nous ne faisons que l'organiser pour que vous puissiez la retrouver.
  • Pourquoi Tablet est si peu cher : son moteur n'exécute aucun modèle, notre coût se limite donc aux embeddings et au disque - pas aux GPU. Scroll et Codex ajoutent un modèle, et c'est ce que couvre leur prix plus élevé.
D'autres modèles de facturation font payer chaque mois le volume stocké ou plafonnent le nombre de souvenirs selon le plan. WOS ne facture rien pour les données stockées, quel que soit leur volume ou leur âge.

Limites de débit par palier d'utilisation →

Pour les développeurs

Trois appels : store, recall, answer.

Une seule API. L'appel recall() renvoie le court terme, le long terme et le contexte environnant en un seul aller-retour, prêt à être inséré dans votre prompt.

1

Store

add() enregistre faits et échanges : les mots de votre utilisateur, ceux de l'assistant (speaker "me") ou ceux d'une personne nommée. Encodé à l'entrée, sans appel LLM.

2

Recall

recall() renvoie court terme + long terme + contexte en un seul appel - un contexte borné, de taille fixe.

3

Answer

Fournissez ce contexte borné à votre LLM - n'importe quel fournisseur, votre clé.

from wontopos import Client
mem = Client(api_key="wos-...")
mem.add("she prefers tea over coffee", user_id="alice")
mem.add("I suggested the jasmine tea", user_id="alice", speaker="me")  # its own words
# one call: short + long + context
ctx = mem.recall("what does alice drink?", user_id="alice")

Les souvenirs portent un locuteur. Par défaut ce sont les mots de votre utilisateur, speaker "me" enregistre ce que l'assistant a dit lui-même, et un nom comme "Bob" retient qui l'a dit, pour se souvenir par personne.

Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.
Démarrage rapide

Votre premier recall en 5 minutes.

Une clé, une ligne d'installation, trois appels - votre agent a de la mémoire. Chaque extrait de cette page a réellement été exécuté ; les réponses sont montrées verbatim.

1

Obtenir une clé API

Créez-en une dans la console. Une clé de 155 caractères commençant par wos-live- est affichée une seule fois. Gardez-la dans une variable d'environnement - jamais dans le code.

2

Installation

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

Créez un store, puis store & recall

Un store est le user_id sous lequel vous lisez et écrivez. Les stores sont explicites : créez-en un d'abord (l'appel ci-dessous), puis stockez et rappelez sous celui-ci. Store - encodé en embeddings à l'entrée, aucun appel LLM. Recall - court terme + long terme + contexte en un seul aller-retour.

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?"}'
Réponse réelle - create_store()
{"user_id": "alice", "status": "created"}
Réponse réelle - add()
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Définissez le store une seule fois. Passez user_id au client et chaque appel l'utilise - inutile de le répéter ; surchargez un appel isolé en lui passant user_id. Les stores sont explicites : stocker dans un store inexistant ou rappeler depuis celui-ci renvoie 404 - créez-le d'abord. Chaque compte démarre avec un store default : sans aucun user_id, le chemin zéro configuration fonctionne donc tel quel. Voir Stores pour les lister et les gérer.

recall() renvoie quatre blocs - short_term (tours récents), long_term (souvenirs pertinents), context (ce qui entourait la meilleure correspondance) et une instruction indiquant au LLM comment les utiliser. Insérez l'ensemble tel quel dans votre prompt.

Fonctionne dans toutes les langues. Stockez en anglais, interrogez en coréen, en japonais ou en chinois - le même souvenir revient. Recherche par embeddings, pas de correspondance par mots-clés.

Chaque méthode, par langage →

Stores

Stores - créer, lister, supprimer.

Un store est le user_id sous lequel vous lisez et écrivez - un espace mémoire isolé par utilisateur final, agent ou sujet. Les stores sont explicites : créez-en un avant d'y stocker ou d'y rappeler, sinon l'appel renvoie 404. Chaque compte démarre avec un store default, vous pouvez donc commencer sans appel de création.

Comment l'isolation s'emboîte. Un compte possède des workspaces ; chaque workspace isole sa propre mémoire, ses clés API et son usage (la facturation est partagée au niveau du compte). Un store vit à l'intérieur d'un workspace : les clés d'un même workspace partagent ses stores, et deux workspaces différents ne voient jamais la mémoire l'un de l'autre. 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"}'
Réponse réelle - create
{ "user_id": "alice", "status": "created" }   // "exists" if it already did
Réponse réelle - 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 sur un store inexistant
{ "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." } }
Utilisez un store par utilisateur final ("alice", "user_42") pour garder la mémoire de chaque personne séparée, ou un unique store default pour un agent personnel. Vous pouvez aussi créer et parcourir les stores dans la console (Memory ids → Issue) sans écrire de code. La suppression d'un store est définitive - elle efface tous les souvenirs qu'il contient.
Cache de rappel

Les rappels répétés, au dixième du prix.

Activez-le par requête et WOS met en cache le résultat de recherche sous le texte de la requête, avec les mêmes règles de préfixe que le prompt caching des LLM. Tant que le cache est chaud, une requête répétée ou prolongée réutilise le résultat précédent, et la partie en cache est facturée à 10% du tarif normal par token.

Tablet et Scroll uniquement. Le cache fonctionne sur tous les modèles Tablet et Scroll, actuels et futurs. Codex ne le prend pas en charge : Codex raisonne sur vos mémoires et apprend entre les appels, la même question peut donc légitimement revenir avec une réponse différente, et un résultat en cache serait faux par conception. Envoyer cache_control à Codex renvoie un 403 clair.

Une conversation, trois tours

Voici ce qui se passe réellement quand un agent continue de parler à sa mémoire. À chaque tour, la conversation accumulée est envoyée comme requête, avec cache_control activé.

writeTour 1 - « Alice : J’ai déménagé à Lisbonne au printemps dernier. »

La requête complète est recherchée et mise en cache : entrée à 2x (TTL de 5 minutes).

extendTour 2 - le même texte plus « Bob : Quel temps fait-il là-bas ? »

Seule la phrase de Bob est encodée et recherchée. L’ancienne partie coûte 0,1x, la nouvelle phrase 2x, et le cache se termine désormais sur elle.

hitTour 3 - exactement la même requête encore (un réessai, un rafraîchissement)

Aucun appel au moteur. Tout à 0,1x : la remise de 90%.

Les tarifs

OpérationFacturation des tokensSignification
Écriture du cache - TTL 5 minutesLa première requête. Son résultat est conservé 5 minutes, et chaque lecture fait glisser la fenêtre vers l’avant.
Écriture du cache - TTL 1 heureLa première requête, conservée pendant une heure entière.
Lecture du cache - hit ou hit de préfixe0.1×Chaque requête après l’écriture : la partie en cache coûte un dixième du tarif normal par token.

Ce que ça économise

Un exemple concret : votre agent envoie une conversation de 3 000 tokens comme requête et la répète ou la prolonge 10 fois en cinq minutes. Sans cache, cela fait 30 000 tokens d’entrée au plein tarif. Avec un cache de 5 minutes, c’est 6 000 pour la première écriture (2x) plus environ 2 700 pour les neuf lectures en cache : 8 700 tokens facturés, 71% de moins. Plus la conversation dure, plus l’économie grandit.

La règle du préfixe

La correspondance se fait sur le début de la requête. Si le début reste identique et que du nouveau texte est seulement ajouté à la fin, la partie en cache est réutilisée et seule la nouvelle partie est recherchée. Si quelque chose change avant la fin du texte en cache, rien ne peut être réutilisé.

prefix match
cached    [ A B C D E F G ]

○   [ A B C D E F G ] E
✗   [ B C D E F G ] E

hit - le début est inchangé, E est la seule partie nouvelle
miss - le début a changé, toute la requête est donc recherchée et mise en cache à nouveau

Trois règles à retenir

  • Prolonger remet en cache jusqu’à la nouvelle fin. Après [A B C D E F G] + E, le cache se termine désormais à E : la fin est facturée une fois au tarif d’écriture, et le tour suivant peut de nouveau faire correspondre tout A..E comme préfixe.
  • Un seul préfixe contigu par requête. Une requête ne peut pas être découpée en deux segments de cache ; seul son début peut correspondre.
  • Les écritures invalident instantanément. Tout store, store-turn, bulk-store, forget, supersede ou suppression du store jette son cache : une réponse en cache ne peut jamais être périmée.

Comment l’activer

hits = mem.search(
    "...the conversation so far...", user_id="alice",
    cache_control={"ttl": "5m"},   # or "1h"
)
const hits = await mem.search(
  "...the conversation so far...", "alice", 10,
  { cache_control: { ttl: "5m" } },   // or "1h"
);
let hits = mem.search_with(
    "...the conversation so far...", "alice", 10,
    serde_json::json!({"cache_control": {"ttl": "5m"}}),   // or "1h"
).await?;
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":"...the conversation so far...",
       "cache_control":{"ttl":"5m"}}'   # or "1h"
Réponse - l’objet cache indique ce qui s’est passé
{ "memories": [ ... ],
  "cache": { "status": "hit",              // "write" | "hit" | "extend"
             "ttl": "5m",
             "cache_read_input_tokens": 412,
             "cache_creation_input_tokens": 0 } }

Aucun SDK n'est nécessaire pour tout cela. Le caching est un champ sur un appel HTTP, il fonctionne donc depuis n'importe quel langage de programmation. L'onglet curl est la recette universelle, et les SDK Python, TypeScript et Rust ne sont que des enrobages pratiques du même appel.

Le cache est isolé par store et par modèle dans votre espace de travail, et il est désactivé par défaut : sans cache_control, rien ne change dans vos requêtes.
Qui l'a dit

Une mémoire qui sait qui l'a dit.

On se souvient par personne : ce que Bob a promis, ce que vous avez dit que vous feriez. Étiquetez chaque souvenir avec un locuteur et votre agent fait pareil, sur tous les modèles Tablet et Scroll.

Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.

Une équipe, trois souvenirs

Un magasin garde les voix séparées. Enregistrez une personne une fois, sauvegardez chaque propos sous son locuteur, puis demandez par personne.

addEnregistrez Bob une fois : POST /speakers, ou add_speaker("Bob") dans les SDK.

Le magasin connaît désormais Bob. La limite de 50 se compte ici, à l'enregistrement ; les appels de stockage ne renvoient jamais d'erreur de limite.

BobBob dit que l'échéance passe à mardi. Enregistrez-le avec speaker "Bob".

Le souvenir appartient désormais à Bob : chaque recherche qui le renvoie l'indique.

meVotre assistant promet le résumé pour vendredi. Enregistrez ses propres mots avec speaker "me".

Ses propres mots sont aussi mémorisés, et "me" ne compte jamais dans la limite.

askPlus tard : « qu'a dit Bob sur l'échéance ? » Cherchez avec speaker "Bob".

Seuls les mots de Bob reviennent. Les mots d'une personne ne reviennent jamais comme ceux d'une autre.

Trois règles à retenir

  • "me", c'est l'assistant lui-même. Jamais enregistré, jamais compté. Réservé et en minuscules : speaker: "Me" ou "ME" renvoie 400 invalid_request_error au lieu d'être converti en silence.
  • La limite se compte à l'enregistrement : 50 par magasin pour commencer. Au-delà, l'enregistrement renvoie 400 invalid_request_error avec speaker_limit: 50 dans le corps d'erreur. Stocker avec un nom non enregistré renvoie aussi 400 et ne stocke rien. Filtrer la recherche par un nom non enregistré renvoie 404 not_found_error. Branchez sur le statut et les champs, pas sur le texte du message ; nous comptons relever la limite.
  • Les étiquettes vivent dans chaque lecture. Les résultats de recherche, le contexte long terme de recall et les résultats d'engram portent leur locuteur : le modèle sait toujours à qui appartiennent les mots qu'il tient. Passez speaker à une recherche pour n'obtenir que les mots d'une personne. Un supersede conserve le locuteur ; forget le supprime.
  • Les noms sont en Unicode : toutes les langues fonctionnent. さくら, Иван et 하늘 sont des locuteurs valides, et l'attribution se comporte de la même façon dans toutes les langues. La correspondance est exacte après trim et normalisation Unicode : Bob et bob sont deux personnes. Les noms vont jusqu'à 80 caractères.
errors - verbatim
# POST /speakers past the limit
{ "type": "error",
  "error": { "type": "invalid_request_error",
             "message": "This store already has 50 registered speakers, ...",
             "speaker_limit": 50 } }

# store with an unregistered name → 400, nothing stored
{ "type": "error",
  "error": { "type": "invalid_request_error",
             "message": "speaker 'Bob' is not registered in this store. Register it first: ...",
             "speaker": "Bob" } }

# search filtered by an unregistered name → 404
{ "type": "error",
  "error": { "type": "not_found_error",
             "message": "speaker 'Bob' is not registered in this store.",
             "speaker": "Bob" } }

Deux notes de portée. speaker s'attache à add / store : add_turn mémorise l'échange entier, et les étiquettes par personne et le filtre viennent des souvenirs enregistrés avec un speaker explicite. Et les passages de session (expand) sont des composites de plusieurs souvenirs, donc sans étiquette ; un filtre speaker renvoie toujours des souvenirs atomiques et étiquetés. Et un contenu identique est dédupliqué vers le premier souvenir : ce store renvoie status "duplicate" avec une note explicite, sans locuteur attaché.

Nous testons cela à la dure : des souvenirs sans aucun nom dans le texte, rappelés par personne. L'attribution vient du registre des locuteurs, pas de la correspondance de mots, donc le comportement est identique dans toutes les langues.

L'utiliser

mem.add_speaker("Bob", user_id="alice")  # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")
hits = mem.search("what did Bob say about the deadline?", user_id="alice", speaker="Bob")
mem.list_speakers(user_id="alice")
mem.remove_speaker("Bob", user_id="alice")  # memories stay, the tag goes
await mem.addSpeaker("Bob", "alice");  # once per person; "me" needs no registration
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
await mem.add("I promised the summary by Friday", "alice", { speaker: "me" });
const hits = await mem.search("what did Bob say about the deadline?", "alice", 10, { speaker: "Bob" });
await mem.listSpeakers("alice");
await mem.removeSpeaker("Bob", "alice");  // memories stay, the tag goes
mem.add_speaker("Bob", "alice").await?;  # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", "alice", json!({"speaker": "Bob"})).await?;
mem.add("I promised the summary by Friday", "alice", json!({"speaker": "me"})).await?;
let hits = mem.search_with("what did Bob say about the deadline?", "alice", 10, json!({"speaker": "Bob"})).await?;
mem.list_speakers("alice").await?;
mem.remove_speaker("Bob", "alice").await?;  // memories stay, the tag goes
curl -X POST https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # once per person

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":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}'

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 did Bob say about the deadline?","speaker":"Bob"}'

curl "https://api.wontopos.com/api/v1/memory/speakers?user_id=alice" -H "X-API-Key: $WOS_API_KEY"

curl -X DELETE https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # memories stay, the tag goes
response
{ "memories": [
    { "content": "Bob said the deadline moved to Tuesday",
      "speaker": "Bob", ... } ] }
GET /speakers
{ "user_id": "alice",
  "speakers": [ { "speaker": "Bob", "memories": 2, "created_at": "2026-07-10T04:20:39Z" } ],
  "count": 1, "limit": 50 }

La liste montre qui le magasin connaît, avec le nombre de souvenirs par personne face à la limite. Retirer n'efface que l'enregistrement : les souvenirs restent, seule l'étiquette part.

Module · Beta

MCP : la mémoire pour les outils d'IA

Le cœur de WOS, c'est l'API et les SDK. Le serveur MCP est un module par-dessus : la même mémoire, branchée sur des outils que vous n'avez pas construits - Claude Code, Claude Desktop, Cursor.

Une ligne d'installation donne à l'agent neuf outils de mémoire qu'il utilise seul. Et comme la mémoire vit dans votre compte, ce qu'un outil écrit, tous les autres s'en souviennent - y compris les agents que vous construisez avec le SDK.

Ce que ça permet

  • Un Claude Code qui se souvient de votre projet. Décisions, correctifs, préférences - rappelés à la session suivante sans rien réexpliquer.
  • Commencez dans ChatGPT, continuez dans Claude. Même store, même mémoire : la conversation traverse les outils au lieu de repartir de zéro.
  • Votre propre agent reste dans la boucle. Ce que Claude Code apprend, un agent SDK s'en souvient - et ce que votre agent stocke, Claude Code le rappelle en retour.

Fonctionne dans Claude Code, Claude Desktop, Cursor, Windsurf et tout hôte MCP. ChatGPT atteint la même mémoire via Actions plus la spec OpenAPI.

Installation

claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp

L'agent reçoit neuf outils - recall · remember · search · update · forget · list_memories · engram · stats · create_store - chacun décrit pour qu'il sache seul quand les utiliser.

Le module lui-même est gratuit et publié sur npm : vous ne payez que le tarif d'usage normal des appels d'API qu'il effectue. Nécessite Node 18+ et une clé créée dans la console.

Ouvrir la page développeur

Module

Spec OpenAPI

La carte complète et lisible par machine de l'API : chaque endpoint, requête, réponse et erreur.

OpenAPI est le format standard de l'industrie pour décrire une API HTTP dans un fichier lisible par machine.

https://api.wontopos.com/openapi.json

Ce que ça permet

Postman : File → Import → collez l'URL, les 16 endpoints apparaissent en collection cliquable. ChatGPT : créez un GPT, ajoutez une Action, collez la même URL. Codegen : openapi-generator -i .../openapi.json -g go génère un client dans un langage que nous ne publions pas.

Importez-la dans Postman, générez un client dans un langage que nous ne publions pas, branchez ChatGPT Actions ou lancez des checks de contrat en CI. Un test l'épingle aux routes réelles : aucune dérive possible.

Module

llms.txt

Toute l'API sur une page de texte qu'une IA peut lire.

llms.txt est une convention du web : une page en texte brut à la racine du site qui dit à une IA tout ce qu'elle doit savoir d'un produit.

https://wontopos.com/llms.txt

Déposez-le dans votre IDE ou agent de code : il sait construire sur WOS - auth, endpoints, patterns, erreurs. Mis à jour à chaque release.

Les mêmes faits que la spec OpenAPI, un autre public : la spec est une structure précise pour les outils ; ce fichier est de la prose qu'une IA (ou une personne) lit d'une traite. Les deux se mettent à jour à chaque release.

Model Context Protocol · Beta

Votre mémoire dans chaque outil d'IA

Une seule commande donne à Claude Code, Claude Desktop, Cursor ou tout hôte MCP une mémoire à long terme adossée à votre compte WOS. Aucun code d'intégration : l'agent reçoit neuf outils de mémoire et décide quand les utiliser.

MCP est en bêta. Les neuf outils fonctionnent déjà et sont testés, mais la surface peut encore changer le temps de la finaliser. L'API et les SDK en dessous sont stables et versionnés.

Installation

Claude Code, une ligne (créez d'abord une clé dans la console) :

claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick which store it remembers into (optional): add --env WONTOPOS_USER_ID=my-project
# ~/.cursor/mcp.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# .vscode/mcp.json
{ "servers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# Claude Desktop and any other MCP host
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to Cursor →  ·  Add to VS Code →

Env optionnel : WONTOPOS_USER_ID fixe le store par défaut, WONTOPOS_MODEL le moteur, WONTOPOS_BASE_URL un déploiement auto-hébergé. WONTOPOS_READ_ONLY=1 passe en lecture seule (recall/recherche/liste uniquement).

Avant de partager un store

  • Utilisez une clé dédiée. Les clés portent leur workspace : une clé créée juste pour MCP borne ce que les outils connectés peuvent toucher - et vous pouvez la faire tourner dans la console sans toucher aux clés de votre app.
  • Mode lecture seule. WONTOPOS_READ_ONLY=1 n'enregistre aucun outil d'écriture : l'agent peut rappeler, chercher, lister les mémoires, exécuter des engrams et lire les stats, mais ni stocker, ni mettre à jour, ni oublier, ni supprimer. Pour les agents qui doivent consulter la mémoire, pas la posséder.
  • Gardez la confirmation d'outils activée. Les hôtes MCP demandent avant d'exécuter un outil par défaut - gardez-la surtout pour forget, car les suppressions sont partagées par tous les outils du store.
  • Tout ce qui est stocké est rappelable par chaque outil détenant la clé. Ne stockez jamais de secrets - clés d'API, mots de passe - comme mémoires.
  • Les mémoires rappelées sont des données, pas des instructions. Les descriptions des outils le disent explicitement à l'agent. Malgré tout, ne stockez pas de texte tiers non fiable dans un store qu'un agent autonome suit.
  • Les suppressions aussi sont partagées. Un forget ou delete_all depuis un outil efface pour tous.
  • « me » désigne l'agent qui écrit dans le store. Si plusieurs agents partagent un store, leurs voix « me » se mélangent. Donnez à chaque agent son propre store (WONTOPOS_USER_ID) pour des identités séparées.
  • Un seul compte paie. Tous les outils connectés puisent dans le même solde et la même limite de débit.

Ensuite, parlez simplement

youretiens qu'on livre le vendredi

L'agent appelle l'outil remember. Stocké durablement : la fin de la session ne change rien.

new sessionon livre quand ?

Une nouvelle session n'a aucun historique. L'agent appelle recall et répond de mémoire : le vendredi.

Ce que vous pouvez dire

  • « Ce repo utilise pnpm, retiens-le » → remember le stocke ; la session suivante le sait déjà.
  • « Quel format d'erreur avait-on choisi la semaine dernière ? » → recall ramène la décision dans le contexte.
  • « En fait, la deadline est passée à vendredi » → l'agent voit que cela contredit ce qu'il a rappelé et appelle update pour corriger la mémoire sur place.
  • « C'est faux, oublie ça » → l'agent trouve l'id et appelle forget - votre hôte demande confirmation d'abord.
  • « Que retiens-tu de moi ? » → list_memories parcourt tout ce qui est stocké, pour que l'agent réponde ou fasse le ménage.

Rien de spécial à formuler : ce sont des phrases ordinaires, pas des commandes. L'agent lit la description de chaque outil et choisit seul.

Les neuf outils

  • recall - Le contexte en un appel : tours récents plus mémoires pertinentes. Sa description dit à l'agent de l'appeler en premier dès que le passé compte.
  • remember - Stocke un fait ou une décision durable. speaker: "me" marque les mots de l'agent ; un nom enregistré, qui l'a dit.
  • search - Recherche sémantique, avec filtre optionnel par personne (speaker).
  • forget - Supprime une mémoire par id.
  • speakers - Enregistre, liste ou retire les personnes qu'un store connaît.
  • create_store - Les stores sont explicites : un par utilisateur final, projet ou agent.

SDK ou MCP ?

  • Le SDK va dans une app que vous écrivez. Votre code décide exactement quand stocker et quoi rappeler - déterministe, typé, versionné. Vous construisez un produit ? SDK.
  • MCP se branche sur un outil d'IA que vous n'avez pas écrit. L'agent décide quand utiliser la mémoire, guidé par les descriptions - zéro code. Pour Claude Code, Claude Desktop, Cursor, ou donner une mémoire à un assistant fini.

Dessous, la même API et les mêmes stores : une app bâtie sur le SDK et une session Claude Code via MCP partagent une seule mémoire. On choisit par surface, pas l'un ou l'autre.

Une mémoire à travers tous les outils

La mémoire appartient au compte, pas à l'outil. Le même store écrit depuis ChatGPT (Actions plus la spec OpenAPI) se rappelle dans Claude Code et dans vos propres agents, et inversement : une conversation commencée dans un outil continue dans un autre.

Et comme c'est un seul store, vous pouvez quitter Claude Code et continuer là où vous construisez : un agent SDK avec la même clé et le même store se souvient de tout ce que Claude Code vient d'apprendre - et ce que votre agent stocke, Claude Code s'en souvient à la session suivante.

Tourne en local via stdio (npx wontopos-mcp) : votre clé reste dans votre environnement et ne transite par aucun serveur tiers. Il enveloppe le SDK TypeScript : retries automatiques, refus des redirections et masquage de la clé s'appliquent tels quels.
Model Context Protocol · Beta

Claude Code

La voie principale : une commande dans votre terminal et chaque session démarre avec la mémoire.

  1. Créez une clé d'API dans la console. La clé porte son workspace : une clé = un espace mémoire.
  2. Enregistrez le serveur. --scope user le rend disponible dans tous les projets ; sans lui, seul le projet courant le voit.
  3. Vérifiez : lancez /mcp dans Claude Code - wontopos doit apparaître avec neuf outils.
  4. Rendez-le automatique : une ligne dans votre CLAUDE.md - « quand le passé compte, appelle d'abord wontopos recall » - et chaque session démarre avec la mémoire sans qu'on le demande.
claude mcp add wontopos --scope user \
  --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick a store (optional): add --env WONTOPOS_USER_ID=my-project
Model Context Protocol · Beta

Claude Desktop

Ajoutez le bloc ci-dessous à claude_desktop_config.json (Réglages → Developer → Edit Config), relancez l'app : les neuf outils apparaissent. Note : claude.ai web et mobile exige un serveur MCP distant, que WOS n'offre pas encore - l'app de bureau est la voie supportée.

# claude_desktop_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
Model Context Protocol · Beta

Cursor

Ajoutez le bloc ci-dessous à ~/.cursor/mcp.json - ou pressez le bouton en un clic - et relancez Cursor. L'agent récupère les neuf outils.

# ~/.cursor/mcp.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to Cursor →

Model Context Protocol · Beta

VS Code

VS Code (mode agent Copilot) lit les serveurs MCP dans .vscode/mcp.json du projet : ajoutez le bloc ci-dessous ou pressez le bouton en un clic.

# .vscode/mcp.json
{ "servers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to VS Code →

Model Context Protocol · Beta

Windsurf

Windsurf (Cascade) lit ~/.codeium/windsurf/mcp_config.json : ajoutez le bloc ci-dessous et rechargez - les neuf mêmes outils apparaissent.

# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
Model Context Protocol · Beta

ChatGPT

Les connecteurs MCP de ChatGPT n'acceptent que des serveurs distants ; la voie supportée aujourd'hui est un GPT personnalisé avec une Action : créez un GPT, ajoutez une Action, collez l'URL de la spec OpenAPI ci-dessous et mettez votre clé en en-tête d'auth. Ce GPT appellera la même mémoire que vos autres outils.

# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-Key

Même store, même mémoire : ce que ChatGPT stocke via l'Action, Claude Code le rappelle via MCP - et inversement.

Model Context Protocol · Beta

Gemini CLI

Gemini CLI lit les serveurs MCP dans ~/.gemini/settings.json : ajoutez le bloc ci-dessous, relancez la CLI et les neuf mêmes outils apparaissent là aussi.

# ~/.gemini/settings.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
SDK Python

Python - chaque méthode, en trois groupes.

Écrire, lire, supprimer. Chaque exemple ci-dessous a été exécuté contre l'API en production le 2026-07-10 ; les réponses sont verbatim.

pip install wontopos
from wontopos import Client

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

Choisir un modèle

La clé API détermine quelle mémoire (votre compte) ; le modèle détermine quel moteur la lit. Tous les modèles partagent une même mémoire : vous pouvez donc stocker avec l'un et rappeler avec un autre. Définissez un défaut sur le client ; surchargez un appel isolé en passant 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="scroll-1")  # or pick a model per call

list_models ✓ live-tested

Le catalogue - les ids que vous pouvez passer à model et la disponibilité de chacun. Les modèles memory: "shared" lisent le même store ; "isolated" garde le sien. Aucune clé API requise.

mem.list_models()
Réponse réelle
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping ✓ live-tested

Vérifiez la connexion et que votre clé API fonctionne : un contrôle en une ligne.

mem.ping()   # True, or raises AuthenticationError / PaymentRequiredError

Le catalogue ci-dessus reflète toujours les modèles disponibles à l'instant même - passez tout autre id et vous obtenez une erreur claire. Les nouveaux modèles y apparaissent automatiquement dès leur sortie.

Écrire

add ✓ live-tested

Stocke un souvenir. Encodé en embeddings à l'entrée - aucun appel LLM, vous ne payez que les embeddings.

mem.add("she prefers tea over coffee", user_id="alice")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")  # its own words - no registration needed
Réponse réelle
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn ✓ live-tested

Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.

mem.add_turn("hi", "hello!", user_id="alice")
Réponse réelle
{"status": "ok"}

speaker ✓ live-tested

Chaque souvenir peut porter son locuteur. Enregistrez une personne une fois, puis passez son nom comme speaker ; "me" (les mots de l'assistant) n'a jamais besoin d'enregistrement. La recherche accepte aussi speaker, pour ne rappeler que les mots d'une personne.

mem.add_speaker("Bob", user_id="alice")  # once per person; "me" needs no registration
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")
response
{"memories": [{"content": "Bob said the deadline moved to Tuesday", "speaker": "Bob", ...}]}
Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.

add_bulk ✓ live-tested

Importe un gros bloc de texte. Découpé et encodé côté serveur - idéal pour importer un historique existant.

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

update ✓ live-tested

Un fait a changé. L'ancien souvenir est marqué superseded (conservé pour l'historique) ; le nouveau prend sa place dans le recall.

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

Lire

search ✓ live-tested

Recherche sémantique, du plus pertinent au moins pertinent. Pur embedding - aucune correspondance par mots-clés, n'importe quelle langue trouve donc n'importe quel souvenir. Le SDK renvoie directement le tableau memories ; le corps HTTP brut est montré ci-dessous.

r = mem.search("what does she drink?", user_id="alice", limit=1)
Réponse réelle (corps HTTP)
{"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-07-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
ChampSignification
similaritySimilarité d'embedding brute avec votre requête (0–1).
is_supersededVrai si ce fait a été remplacé par update().
search_msTemps de récupération côté serveur.

recall ✓ live-tested

Un seul aller-retour renvoie tout ce dont votre LLM a besoin - collez le résultat directement dans votre prompt : un contexte borné, de taille fixe, quel que soit le volume stocké.

ctx = mem.recall("what does she drink?", user_id="alice")
Réponse réelle (forme - listes raccourcies)
{"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

Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.

turns = mem.history("alice")
Réponse réelle (corps HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Compteurs de souvenirs pour un utilisateur.

mem.stats("alice")
Réponse réelle
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get ✓ live-tested

Récupère une mémoire par id - l'id renvoyé par add ou list_memories. Uniquement le texte original stocké et ses métadonnées, jamais le vecteur. Un id d'un autre store, ou une mémoire supprimée ou invalidée, renvoie 404.

m = mem.get("alice", memory_id="576700aa-...")
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "source_type": "user_said",
 "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}

list_memories ✓ live-tested

Liste les mémoires d'un espace : uniquement le texte original que vous avez stocké et ses métadonnées, jamais le vecteur. Paginé par curseur : renvoyez le next_cursor reçu pour la page suivante.

page = mem.list_memories("alice", limit=100)
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "source_type": "user_said",
    "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
 ]}

iter_memories · export_memories

Parcourez toutes les mémoires sans gérer le curseur, ou récupérez tout l'espace d'un coup.

for m in mem.iter_memories("alice"):   # every page, no cursor bookkeeping
    print(m["id"], m["content"])
everything = mem.export_memories("alice")   # the whole store as a list

Supprimer

delete ✓ live-tested

Supprime un souvenir unique par id.

mem.delete("alice", memory_id="576700aa-...")
Réponse réelle
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all ✓ live-tested

Efface tout pour un utilisateur - un seul appel, conforme RGPD.

mem.delete_all("alice")
Réponse réelle
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erreurs et fiabilité

Chaque échec est une erreur typée : attrapez le cas précis (limite de débit, authentification, paiement) ou tous avec le WosError de base.

from wontopos import PaymentRequiredError, NotFoundError
try:
    mem.add("...", user_id="alice")
except NotFoundError:
    mem.create_store("alice")   # store didn't exist yet
except PaymentRequiredError:
    top_up()                       # out of credit - don't retry

rate_limit

Lisez le quota restant après chaque appel et ralentissez avant d'atteindre la limite.

mem.search("...", user_id="alice")
rl = mem.rate_limit   # {"limit": 150, "remaining": 3, "reset": ...}
SDK TypeScript

TypeScript - chaque méthode, en trois groupes.

Écrire, lire, supprimer. Chaque exemple ci-dessous a été exécuté contre l'API en production le 2026-07-10 ; les réponses sont verbatim.

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

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

Choisir un modèle

La clé API détermine quelle mémoire (votre compte) ; le modèle détermine quel moteur la lit. Tous les modèles partagent une même mémoire : vous pouvez donc stocker avec l'un et rappeler avec un autre. Définissez un défaut dans le constructeur ; surchargez un appel isolé avec withModel().

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

listModels ✓ live-tested

Le catalogue - les ids que vous pouvez passer à model et la disponibilité de chacun. Les modèles memory: "shared" lisent le même store ; "isolated" garde le sien. Aucune clé API requise.

await mem.listModels();
Réponse réelle
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping ✓ live-tested

Vérifiez la connexion et que votre clé API fonctionne : un contrôle en une ligne.

await mem.ping();   // true, or throws AuthenticationError / PaymentRequiredError

Le catalogue ci-dessus reflète toujours les modèles disponibles à l'instant même - passez tout autre id et vous obtenez une erreur claire. Les nouveaux modèles y apparaissent automatiquement dès leur sortie.

Écrire

add ✓ live-tested

Stocke un souvenir. Encodé en embeddings à l'entrée - aucun appel LLM, vous ne payez que les embeddings.

await mem.add("she prefers tea over coffee", "alice");
await mem.add("I promised the summary by Friday", "alice", { speaker: "me" });  // its own words - no registration needed
Réponse réelle
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

addTurn ✓ live-tested

Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.

await mem.addTurn("hi", "hello!", "alice");
Réponse réelle
{"status": "ok"}

speaker ✓ live-tested

Chaque souvenir peut porter son locuteur. Enregistrez une personne une fois, puis passez son nom comme speaker ; "me" (les mots de l'assistant) n'a jamais besoin d'enregistrement. La recherche accepte aussi speaker, pour ne rappeler que les mots d'une personne.

await mem.addSpeaker("Bob", "alice");  # once per person; "me" needs no registration
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" });
Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.

addBulk ✓ live-tested

Importe un gros bloc de texte. Découpé et encodé côté serveur - idéal pour importer un historique existant.

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

update ✓ live-tested

Un fait a changé. L'ancien souvenir est marqué superseded (conservé pour l'historique) ; le nouveau prend sa place dans le recall.

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

Lire

search ✓ live-tested

Recherche sémantique, du plus pertinent au moins pertinent. Pur embedding - aucune correspondance par mots-clés, n'importe quelle langue trouve donc n'importe quel souvenir. Le SDK renvoie directement le tableau memories ; le corps HTTP brut est montré ci-dessous.

const r = await mem.search("what does she drink?", "alice", 1);
Réponse réelle (corps HTTP)
{"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-07-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
ChampSignification
similaritySimilarité d'embedding brute avec votre requête (0–1).
is_supersededVrai si ce fait a été remplacé par update().
search_msTemps de récupération côté serveur.

recall ✓ live-tested

Un seul aller-retour renvoie tout ce dont votre LLM a besoin - collez le résultat directement dans votre prompt : un contexte borné, de taille fixe, quel que soit le volume stocké.

const ctx = await mem.recall("what does she drink?", "alice");
Réponse réelle (forme - listes raccourcies)
{"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

Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.

const turns = await mem.history("alice");
Réponse réelle (corps HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Compteurs de souvenirs pour un utilisateur.

await mem.stats("alice");
Réponse réelle
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get ✓ live-tested

Récupère une mémoire par id - l'id renvoyé par add ou list_memories. Uniquement le texte original stocké et ses métadonnées, jamais le vecteur. Un id d'un autre store, ou une mémoire supprimée ou invalidée, renvoie 404.

const m = await mem.get("alice", "576700aa-...");
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "source_type": "user_said",
 "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}

listMemories ✓ live-tested

Liste les mémoires d'un espace : uniquement le texte original que vous avez stocké et ses métadonnées, jamais le vecteur. Paginé par curseur : renvoyez le next_cursor reçu pour la page suivante.

const page = await mem.listMemories("alice", { limit: 100 });
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "source_type": "user_said",
    "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
 ]}

iterMemories · exportMemories

Parcourez toutes les mémoires sans gérer le curseur, ou récupérez tout l'espace d'un coup.

for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content);
const everything = await mem.exportMemories("alice");

Supprimer

delete ✓ live-tested

Supprime un souvenir unique par id.

await mem.delete("alice", "576700aa-...");
Réponse réelle
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

deleteAll ✓ live-tested

Efface tout pour un utilisateur - un seul appel, conforme RGPD.

await mem.deleteAll("alice");
Réponse réelle
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erreurs et fiabilité

Chaque échec est une erreur typée : attrapez le cas précis (limite de débit, authentification, paiement) ou tous avec le WosError de base.

import { NotFoundError, PaymentRequiredError } from "wontopos";
try {
  await mem.add("...", "alice");
} catch (e) {
  if (e instanceof NotFoundError) await mem.createStore("alice");
  else if (e instanceof PaymentRequiredError) topUp();   // out of credit
  else throw e;
}

rateLimit

Lisez le quota restant après chaque appel et ralentissez avant d'atteindre la limite.

await mem.search("...", "alice");
const rl = mem.rateLimit;   // { limit: 150, remaining: 3, reset: ... }
SDK Rust

Rust - chaque méthode, en trois groupes.

Écrire, lire, supprimer. Chaque exemple ci-dessous a été exécuté contre l'API en production le 2026-07-10 ; les réponses sont verbatim.

cargo add wontopos
use wontopos::Client;

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

Choisir un modèle

La clé API détermine quelle mémoire (votre compte) ; le modèle détermine quel moteur la lit. Tous les modèles partagent une même mémoire : vous pouvez donc stocker avec l'un et rappeler avec un autre. Définissez un défaut avec with_model() ; chaînez-le à nouveau pour surcharger un appel isolé.

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

list_models ✓ live-tested

Le catalogue - les ids que vous pouvez passer à with_model et la disponibilité de chacun. Les modèles memory: "shared" lisent le même store ; "isolated" garde le sien. Aucune clé API requise.

mem.list_models().await?;
Réponse réelle
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping ✓ live-tested

Vérifiez la connexion et que votre clé API fonctionne : un contrôle en une ligne.

mem.ping().await?;   // Ok(true), or Err whose .kind() is Auth / PaymentRequired

Le catalogue ci-dessus reflète toujours les modèles disponibles à l'instant même - passez tout autre id et vous obtenez une erreur claire. Les nouveaux modèles y apparaissent automatiquement dès leur sortie.

Écrire

add ✓ live-tested

Stocke un souvenir. Encodé en embeddings à l'entrée - aucun appel LLM, vous ne payez que les embeddings.

mem.add("she prefers tea over coffee", "alice", json!({})).await?;
mem.add("I promised the summary by Friday", "alice", json!({"speaker": "me"})).await?;  // its own words - no registration needed
Réponse réelle
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn ✓ live-tested

Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.

mem.add_turn("hi", "hello!", "alice").await?;
Réponse réelle
{"status": "ok"}

speaker ✓ live-tested

Chaque souvenir peut porter son locuteur. Enregistrez une personne une fois, puis passez son nom comme speaker ; "me" (les mots de l'assistant) n'a jamais besoin d'enregistrement. La recherche accepte aussi speaker, pour ne rappeler que les mots d'une personne.

mem.add_speaker("Bob", "alice").await?;  # once per person; "me" needs no registration
mem.add("I promised to send the report on Friday", "alice", json!({"speaker": "me"})).await?;
mem.add("Bob said the deadline moved to Tuesday", "alice", json!({"speaker": "Bob"})).await?;
mem.search_with("what did Bob say about deadlines?", "alice", 10, json!({"speaker": "Bob"})).await?;
Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.

add_bulk ✓ live-tested

Importe un gros bloc de texte. Découpé et encodé côté serveur - idéal pour importer un historique existant.

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

update ✓ live-tested

Un fait a changé. L'ancien souvenir est marqué superseded (conservé pour l'historique) ; le nouveau prend sa place dans le recall.

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

Lire

search ✓ live-tested

Recherche sémantique, du plus pertinent au moins pertinent. Pur embedding - aucune correspondance par mots-clés, n'importe quelle langue trouve donc n'importe quel souvenir. Le SDK renvoie directement le tableau memories ; le corps HTTP brut est montré ci-dessous.

let r = mem.search("what does she drink?", "alice", 1).await?;
Réponse réelle (corps HTTP)
{"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-07-10T04:20:39.688276876Z"
 }], "search_ms": 315, "total_found": 1}
ChampSignification
similaritySimilarité d'embedding brute avec votre requête (0–1).
is_supersededVrai si ce fait a été remplacé par update().
search_msTemps de récupération côté serveur.

recall ✓ live-tested

Un seul aller-retour renvoie tout ce dont votre LLM a besoin - collez le résultat directement dans votre prompt : un contexte borné, de taille fixe, quel que soit le volume stocké.

let ctx = mem.recall("what does she drink?", "alice").await?;
Réponse réelle (forme - listes raccourcies)
{"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

Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.

let turns = mem.history("alice").await?;
Réponse réelle (corps HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats ✓ live-tested

Compteurs de souvenirs pour un utilisateur.

mem.stats("alice").await?;
Réponse réelle
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get ✓ live-tested

Récupère une mémoire par id - l'id renvoyé par add ou list_memories. Uniquement le texte original stocké et ses métadonnées, jamais le vecteur. Un id d'un autre store, ou une mémoire supprimée ou invalidée, renvoie 404.

let m = mem.get("alice", "576700aa-...").await?;
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "source_type": "user_said",
 "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}

list_memories ✓ live-tested

Liste les mémoires d'un espace : uniquement le texte original que vous avez stocké et ses métadonnées, jamais le vecteur. Paginé par curseur : renvoyez le next_cursor reçu pour la page suivante.

mem.list_memories("alice", 100, None).await?;
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "source_type": "user_said",
    "created_at": "2026-07-10T04:20:39Z", "event_date": null, "is_superseded": false}
 ]}

list_all_memories

Parcourez toutes les mémoires sans gérer le curseur, ou récupérez tout l'espace d'un coup.

let all = mem.list_all_memories("alice").await?;   // every page, collected

Supprimer

delete ✓ live-tested

Supprime un souvenir unique par id.

mem.delete("alice", "576700aa-...").await?;
Réponse réelle
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all ✓ live-tested

Efface tout pour un utilisateur - un seul appel, conforme RGPD.

mem.delete_all("alice").await?;
Réponse réelle
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erreurs et fiabilité

Chaque échec est une erreur typée : attrapez le cas précis (limite de débit, authentification, paiement) ou tous avec le WosError de base.

use wontopos::ErrorKind;
match mem.search("...", "alice", 10).await {
    Ok(hits) => { /* use hits */ }
    Err(e) if e.kind() == ErrorKind::NotFound => { mem.create_store("alice").await?; }
    Err(e) if e.is_rate_limited() => { /* back off */ }
    Err(e) => return Err(e),
}

rate_limit

Lisez le quota restant après chaque appel et ralentissez avant d'atteindre la limite.

mem.search("...", "alice", 10).await?;
let rl = mem.rate_limit();   // Some(RateLimit { remaining: Some(3), .. })
curl

curl - aucune installation, mêmes méthodes.

Aucun SDK à installer - n'importe quel client HTTP convient. Définissez votre clé une fois et appelez les mêmes endpoints que les SDK encapsulent. URL de base https://api.wontopos.com, authentification via X-API-Key, JSON en entrée comme en sortie.

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

Écrire

store ✓ live-tested

Stocke un souvenir. Encodé en embeddings à l'entrée - aucun appel LLM.

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"}'
Réponse réelle
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

store-turn ✓ live-tested

Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.

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!"}'
Réponse réelle
{"status": "ok"}

speaker ✓ live-tested

Chaque souvenir peut porter son locuteur. Enregistrez une personne une fois, puis passez son nom comme speaker ; "me" (les mots de l'assistant) n'a jamais besoin d'enregistrement. La recherche accepte aussi speaker, pour ne rappeler que les mots d'une personne.

curl -X POST https://api.wontopos.com/api/v1/memory/speakers \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","speaker":"Bob"}'   # once per person

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":"I promised to send the report on Friday","metadata":{"speaker":"me"}}'

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":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}'

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 did Bob say about deadlines?","speaker":"Bob"}'
Les locuteurs sont explicites, comme les magasins. Enregistrez d'abord la personne, puis stockez sous son nom : une faute de frappe ne devient jamais silencieusement une nouvelle personne. Un magasin enregistre jusqu'à 50 personnes pour commencer (nous comptons relever ce plafond), et "me" n'a jamais besoin d'enregistrement ni ne compte.

supersede ✓ live-tested

Un fait a changé - l'ancien souvenir est marqué superseded, le nouveau prend sa place dans le 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"}'
Réponse réelle
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}

Lire

search ✓ live-tested

Recherche sémantique, du plus pertinent au moins pertinent. Pur embedding - n'importe quelle langue trouve n'importe quel souvenir.

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}'
Réponse réelle
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
   "similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}

recall ✓ live-tested

Un seul aller-retour renvoie court terme + long terme + contexte + une instruction. Collez-le directement dans votre 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?"}'
Réponse réelle (forme)
{"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..."}

Supprimer

forget ✓ live-tested

Supprime un souvenir par id, ou omettez l'id pour tout supprimer pour un utilisateur (RGPD).

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
Réponse réelle
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}

Chaque endpoint + champs du corps →

Engrammes

Engrams

Des outils de rappel invocables que votre modèle peut appeler - chacun étant une stratégie de récupération différente sur la même mémoire. Utilisez-en un, ou lancez-en plusieurs à la fois.

Disponible dès maintenant. Les engrammes généraux ci-dessous sont des pipelines de récupération sans LLM : ils fonctionnent donc sur tous les paliers à partir de Tablet 1. Memoir et Archive, un mode de modèle distinct, sont traités dans leur propre section ci-dessous.

De nouveaux engrammes sortent régulièrement - cette liste s'allonge.

Memoir & Archive Scroll 1.2+

Celui-ci est une forme de livraison, pas un outil invocable. À partir de Scroll 1.2, choisissez-la à chaque appel - form: "memoir" ou form: "archive" - et ce rappel, simple recherche comprise, revient avec le temps écrit de cette façon.

Tous les engrammes Engrammes

Time_awareness Scroll 1.2+

Une forme de livraison - choisie à chaque appel. Passez form - memoir ou archive - avec n'importe quel appel sur un modèle compatible (Scroll 1.2 et au-delà), et la réponse revient rendue de cette façon : une simple recherche, un recall ou n'importe quel engramme. Dans les SDK, c'est un champ form, comme tz ; en HTTP, c'est l'en-tête X-WOS-Form. Un Memoir se lit comme une personne se souvient ; une Archive tient un registre exact - la différence se voit surtout dans la façon dont chacun écrit le temps.

Memoir

form: "memoir"
Souvenu comme une personne · un récit

Raconte ce qui s'est passé et comment un moment a mené au suivant, avec le sens diffus du temps propre au souvenir humain - à lire comme une expérience, pas comme une liste.

Archive

form: "archive"
Tenu comme un registre · temps précis

Renvoie les correspondances comme des enregistrements exacts - temps écoulé précis et ancres absolues, structurés pour qu'un modèle les lise directement.

Il met en forme des souvenirs déjà stockés - il n'en crée pas. Chaque souvenir est un appel store / add sous un user_id (ce user_id est le store de cette personne). Stockez d'abord ; ensuite tout rappel - y compris la simple recherche ci-dessous - revient horodaté. Voir Démarrage rapide pour stocker.
# plain search - no engram - the memoir form on Scroll 1.2
r = mem.search("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
# every hit gets a .time field → "a couple weeks ago"  (form="archive" → "2 weeks ago (Jun 09)")
// plain search - no engram - the memoir form on Scroll 1.2
const r = await mem.withModel("scroll-1.2").search("what does Alice drink?", "alice", 10, { form: "memoir", tz: 9 });
// search_with merges extra fields into the body - form picks the render, tz the local clock
let r = mem.with_model("scroll-1.2").search_with("what does Alice drink?", "alice", 10, json!({"form": "memoir", "tz": 9})).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: wos-live-..." -H "X-WOS-Model: scroll-1.2" -H "X-WOS-Form: memoir" -H "X-WOS-Timezone: 9" \
  -d '{"user_id":"alice","query":"what does Alice drink?"}'
# each memory comes back with a "time" field; use X-WOS-Form: archive for exact time

tz est le décalage UTC de l'appelant, en heures - ainsi « ce matin » et la limite de journée à 4 h du matin tombent dans son heure locale. Omettez-le pour UTC ; en HTTP, c'est l'en-tête X-WOS-Timezone. En gros, par région : côte Est des États-Unis -5, Centre -6, côte Ouest -8 · Royaume-Uni / Lisbonne 0 · Europe centrale +1 · Europe de l'Est +2 · Inde +5.5 · Chine / Singapour +8 · Corée / Japon +9 · Sydney +10. (Heure standard - l'heure d'été décale certaines régions de +1 ; passez ce que vos utilisateurs utilisent réellement.)

Même recherche, deux formes - les souvenirs sont identiques, seul time change :

Résultat · form: 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" }
] }
Résultat · form: 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)" }
] }
ÉcouléMemoirArchive
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 ha couple hours ago2 hours ago, at 13:10
8 hthis morning8 hours ago, at 07:10
hier ap.-midiyesterday afternoonyesterday at 14:00
la nuit dernièrelast night17 hours ago, at 22:00
2 joursa couple days ago2 days ago (Tue 15:10)
6 joursseveral days ago6 days ago (Fri 15:10)
9 joursabout a week agolast week (Jun 16)
16 joursa couple weeks ago2 weeks ago (Jun 09)
35 joursabout a month agolast month (May 21)
60 joursa couple months ago2 months ago (Apr 2026)
180 joursabout half a year ago6 months ago (Dec 2025)
380 joursabout a year agolast year (Jun 2025)
800 joursa couple years ago2 years ago (Apr 2024)
1500 joursabout 4 years ago4 years ago (May 2022)

Chaque valeur ci-dessus est la sortie réelle du moteur de rendu. Regardez les deux lignes « hier » : un Memoir sépare l'après-midi de la nuit dernière - une journée est un sommeil - tandis qu'une Archive écrit une seule heure d'horloge et ne trace aucune frontière entre jour et nuit.

Comment chaque mode lit le temps

Memoir - comme les gens le disent vraiment. Les moments récents restent assez nets (environ 15 minutes, une demi-heure), puis la formulation s'élargit à mesure qu'on remonte - quelques semaines, environ six mois, quelques années - comme la mémoire elle-même se relâche avec la distance. À l'intérieur d'une journée, l'horloge cède la place à un repère : ce matin, la nuit dernière, hier après-midi. Et une journée est un sommeil, pas un tic de calendrier : la frontière se situe vers 4 h du matin, heure locale, si bien qu'une fin de soirée tardive se lit encore comme le même soir, pas déjà comme le lendemain.

Archive - précis, toujours avec une ancre. Chaque ligne porte le temps écoulé exact plus une référence absolue à partir de laquelle un modèle peut calculer, et l'ancre se resserre à mesure qu'on se rapproche : une heure d'horloge pour aujourd'hui (il y a 8 heures, à 07:10), un jour de semaine et une heure cette semaine (il y a 2 jours (mar. 15:10)), une date ce mois-ci (la semaine dernière (16 juin)), un mois et une année au-delà (il y a 6 mois (déc. 2025)). Jamais vague, jamais faux.

Memoir et Archive rendent chaque rappel de la réponse - une simple recherche, un recall ou un engramme. Le palier de modèle (Tablet → Scroll → Codex) détermine ce que fait le moteur ; la forme (memoir / archive) détermine comment il écrit le temps. Disponible à partir de Scroll 1.2.
Tous les engrammes Engrammes

deep_recall

Rappel multi-sauts. Cherche votre requête, puis prend la meilleure correspondance et relance une recherche sur son contenu - ramenant du contexte lié qu'une recherche unique manquerait. Idéal quand les souvenirs se référencent entre eux (une personne → ses projets → les détails). Renvoie jusqu'à ~12 résultats.

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?"}'
Réponse
{ "engram": "deep_recall", "hops": 2, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 5, "output_tokens": 61 } }
Facturé en tokens - chaque appel renvoie usage (entrée + sortie), compté par le même tokenizer que le reste de l'API ; aucun frais caché par engramme. Besoin de plusieurs à la fois ? Appelez-les en parallèle - chaque engramme est une requête indépendante.
Tous les engrammes Engrammes

timeline

Rappel chronologique. Renvoie les souvenirs triés du plus récent au plus ancien selon la date de l'événement, pas selon la pertinence. Pour les questions « quand X a-t-il eu lieu », l'historique et les séquences. Renvoie jusqu'à 15 résultats.

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"}'
Réponse
{ "engram": "timeline", "hops": 1, "count": 15,
  "memories": [ ... ],
  "usage": { "input_tokens": 4, "output_tokens": 88 } }
Facturé en tokens - chaque appel renvoie usage (entrée + sortie), compté par le même tokenizer que le reste de l'API ; aucun frais caché par engramme. Besoin de plusieurs à la fois ? Appelez-les en parallèle - chaque engramme est une requête indépendante.
Tous les engrammes Engrammes

gather

Collecte large. Cherche, puis s'étend autour des trois meilleures correspondances - un filet plus large que deep_recall. Utilisez-le pour ramener tout ce qui touche à une personne, un projet ou un sujet en un seul appel. Renvoie jusqu'à ~18 résultats.

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"}'
Réponse
{ "engram": "gather", "hops": 4, "count": 18,
  "memories": [ ... ],
  "usage": { "input_tokens": 6, "output_tokens": 142 } }
Facturé en tokens - chaque appel renvoie usage (entrée + sortie), compté par le même tokenizer que le reste de l'API ; aucun frais caché par engramme. Besoin de plusieurs à la fois ? Appelez-les en parallèle - chaque engramme est une requête indépendante.
API HTTP

Chaque endpoint, une seule URL de base.

Aucun SDK requis - n'importe quel client HTTP convient. URL de base https://api.wontopos.com, authentification via l'en-tête X-API-Key, JSON en entrée comme en sortie. Les opérations mémoire sont en POST ; la gestion des stores utilise POST / GET / DELETE sur /collection. Un store doit exister au préalable (voir Stores), sinon les opérations dans le store renvoient 404.

EndpointRôleChamps du corps
POST /api/v1/memory/collectioncréer un storeuser_id
GET /api/v1/memory/collectionslister vos stores(aucun)
DELETE /api/v1/memory/collectionsupprimer un store + ses souvenirsuser_id
/api/v1/memory/storestocker un souveniruser_id · content · metadata? (speaker)
/api/v1/memory/store-turnstocker un tour de conversationuser_id · user_msg · assistant_msg
POST /api/v1/memory/speakersenregistrer un locuteur (explicite, 50 max)user_id · speaker
GET /api/v1/memory/speakerslister les locuteurs enregistrés + compteursuser_id
DELETE /api/v1/memory/speakersdésinscrire un locuteur (souvenirs conservés)user_id · speaker
/api/v1/memory/bulk-storeimporter un bloc de texteuser_id · content · category?
/api/v1/memory/searchrecherche sémantiqueuser_id · query · max_results? · speaker?
/api/v1/memory/recallcourt + long + contexteuser_id · query
/api/v1/memory/historytours récentsuser_id
/api/v1/memory/statscompteurs de souvenirsuser_id
/api/v1/memory/supersederemplacer un fait modifiéuser_id · old_memory_id · new_content
/api/v1/memory/forgeten supprimer un (ou tous)user_id · memory_id? (omis = tout supprimer)
# 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?"}'
Réponse réelle - store
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Paliers d'utilisation

Les mêmes fonctionnalités pour tous.
Les paliers ne font que relever vos limites.

Chaque palier exécute le moteur complet - même qualité de rappel, mêmes langues, chaque méthode. Les paliers progressent automatiquement jusqu'au Tier 5 à mesure que vos achats de crédits cumulés augmentent, sans dossier à déposer ni rendez-vous commercial. Enterprise (Tier 6) est la seule exception.

Limites de dépense

Chaque palier plafonne ce que vous pouvez dépenser par mois calendaire. Vous passez immédiatement au palier suivant dès que vos achats de crédits cumulés atteignent le seuil correspondant.

Palier d'utilisationAchat de créditsLimite de dépense mensuelle
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 - EnterpriseContactez-nousSans limite

Limites de débit

Les limites de débit sont par compte - toutes les clés API d'un compte partagent une même limite, qui augmente avec votre palier. La dépasser renvoie un 429 avec un en-tête retry-after ; patientez (1s → 2s → 4s) puis réessayez. Chaque endpoint est compatible avec l'idempotence, les nouvelles tentatives sont donc sans risque.

PalierRequêtes par minute
Tier 1150
Tier 2300
Tier 3600
Tier 41,500
Tier 53,000
Tier 6 - EnterpriseSur mesure

Enterprise (Tier 6) bénéficie de limites de débit sur mesure, d'un SLA, d'un support dédié et d'une licence d'auto-hébergement en option - contactez-nous.

La tarification est à l'usage : les tokens plus $0.0001 fixe par requête. Tablet est à $2 par 1M de tokens d'entrée, $3 par 1M en sortie. Le stockage est gratuit et sans plafond. Voir pourquoi nous tarifons ainsi.
Erreurs & limites

Quand quelque chose tourne mal.

Les erreurs reviennent sous forme d'enveloppe JSON avec un type stable, un message lisible et un request_id que vous pouvez nous transmettre pour signaler un problème.

Réponse réelle - clé invalide (HTTP 401)
{"type": "error", "error": {
   "type": "authentication_error",
   "message": "Invalid or revoked API key.",
   "request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
 }}
HTTPSignificationQue faire
401Clé API invalide ou révoquéeVérifiez la clé ; émettez-en une nouvelle dans la console.
422Corps malformé (champ manquant ou de mauvais type)Le message nomme le champ exact - corrigez et réessayez.
429Limite de débit atteintePatientez de façon exponentielle (1s → 2s → 4s) puis réessayez. Sans risque : tous les endpoints sont compatibles avec l'idempotence.
5xxProblème côté serveurRéessayez avec backoff ; joignez le request_id si vous nous contactez.
# 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
Sécurité des clés. Votre clé n'est affichée qu'une fois à la création et n'est stockée que sous forme de hash de notre côté. Gardez-la dans une variable d'environnement ; en cas de fuite, révoquez-la dans la console - la révocation est immédiate.

Les limites de débit sont par compte, partagées entre toutes vos clés, et augmentent avec votre palier - voir Paliers d'utilisation. L'usage de votre compte est visible dans la console.

Auto-hébergement

Vos serveurs, vos données.

Le moteur peut tourner dans votre propre infrastructure - même API, mêmes SDK. Pointez le client vers votre hôte et rien d'autre ne change.

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");
  • Résidence des données. Les souvenirs ne quittent jamais votre réseau.
  • Même surface. Les mêmes méthodes et endpoints fonctionnent à l'identique.
  • Licence. Les offres d'auto-hébergement sont définies par déploiement - contactez-nous.
Échelle

Au-delà de la fenêtre de contexte.

WOS rappelle depuis des historiques de 1.4M tokens - bien au-delà de toute fenêtre de contexte de LLM - et rend toujours une tranche serrée d'environ ~1,470 tokens.

La mémoire de votre agent n'est pas plafonnée par ce qui tient dans un prompt. Elle conserve tout et ne récupère que ce qui compte, quelle que soit la taille de l'historique.

Confidentialité

Privé, et à vous.

Vos données restent dans votre store. Nous ne nous entraînons jamais dessus, ne les consultons pas et ne les réutilisons pas - nous ne faisons que les organiser pour que vous puissiez les retrouver.

  • BYOK. Votre clé LLM est envoyée à chaque requête et jamais stockée.
  • Isolé. Les souvenirs sont cloisonnés par compte, puis par user_id.
  • Suppression RGPD & auto-hébergement. Un seul appel efface un utilisateur ; exécutez le moteur dans votre propre environnement si vous préférez.