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).
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, Book. La pierre, le rouleau, le livre relié : chacun apporte davantage à votre agent que le précédent.
Tablet
DisponibleUn moyen sobre, rapide et économique d'inscrire et de rappeler la mémoire - la fondation sur laquelle chaque modèle s'appuie.
Scroll
DisponibleAjoute 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.
Book
À venirS'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.
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.
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.25par 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.
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
"¿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.
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.
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 Book 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.
67,5 %, mesuré et reproductible.
67,5 % sur BEAM 1M, en moyenne sur 5 runs indépendants (σ 0,22 %, aucun trié sur le volet), noté par gpt-4.1-mini avec le prompt de jugement du benchmark lui-même.
Sur le même benchmark, les scores varient beaucoup selon le protocole de notation : le juge, le prompt et ce que la couche de récupération a le droit de faire. Nous notons avec le juge que le dépôt des auteurs fournit lui-même, utilisons leur prompt de jugement tel quel, ne changeons rien pour coller au test, et publions le harness, le code de notation et le prompt du lecteur afin que chacun puisse reproduire exactement les 67,5 %.
Le protocole, en un tableau
| Élément | Ce que nous faisons |
|---|---|
| Jeu de données | BEAM 1M - 35 conversations, 74 630 tours, 2,2 millions de souvenirs, 700 questions |
| Juge | gpt-4.1-mini à temperature 0, avec le prompt de jugement propre à BEAM - la valeur par défaut du dépôt des auteurs, pas un juge que nous avons choisi |
| Runs | 5 runs indépendants, chaque score publié, moyenne rapportée (σ 0,22 %) |
| Lecteur | Modè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.
Benchmark antérieur LongMemEval-S Franchi
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èle | Entrée / 1M | Sortie / 1M | |
|---|---|---|---|
| Tablet | $2 | $3 | Disponible |
| Scroll | $4 | $8 | Disponible |
| Book | - | - | À 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 Book ajoutent un modèle, et c'est ce que couvre leur prix plus élevé.
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.
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.
Recall
recall() renvoie court terme + long terme + contexte en un seul appel - un contexte borné, de taille fixe.
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.
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.
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.
Installation
pip install wontopos # Python npm install wontopos # TypeScript / JavaScript cargo add wontopos # Rust # curl - nothing to install, just set WOS_API_KEY # latest: SDK v2.2.32 · MCP v1.0.15
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?"){"user_id": "alice", "status": "created"}{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}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.
Un seul client, plusieurs configurations
mem = Client.from_env() # reads WONTOPOS_API_KEY
scroll = mem.with_model("scroll-1.2") # this copy only: another engine
alice = mem.with_user("alice") # this copy only: another default storeStores - 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.
mem.create_store("alice") # create (idempotent)
mem.list_stores() # [{"user_id","created_at"}, ...]
mem.delete_store("alice") # delete the store + all its memories{ "user_id": "alice", "status": "created" } // "exists" if it already did{ "collections": [
{ "user_id": "default", "created_at": "2026-06-26T02:23:14Z" },
{ "user_id": "alice", "created_at": "2026-06-26T02:24:01Z" }
], "count": 2 }{ "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." } }"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. Les ids de stockage sont repliés avant enregistrement - mis en minuscules, tout caractère hors [a-z0-9_] devenant _ - de sorte que Alice.Smith et alice-smith désignent le même stockage. Un second id qui se replie sur un id existant est refusé par un 409 plutôt que partagé en silence. L'id doit aussi respecter [A-Za-z0-9][A-Za-z0-9._-]{0,63} : une adresse e-mail ou un nom non latin ne peut donc pas servir d'id - utilisez un identifiant interne.Lister et supprimer des stores
mem.list_stores() # [{"user_id","created_at"}, …]
mem.delete_store("alice") # the store and every memory in itLes 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.
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é.
La requête complète est recherchée et mise en cache : entrée à 2x (TTL de 5 minutes).
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.
Aucun appel au moteur. Tout à 0,1x : la remise de 90%.
Les tarifs
| Opération | Facturation des tokens | Signification |
|---|---|---|
| Écriture du cache - TTL 5 minutes | 2× | La 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 heure | 3× | La première requête, conservée pendant une heure entière. |
| Lecture du cache - hit ou hit de préfixe | 0.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é.
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"
){ "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.
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.
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.
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.
Le souvenir appartient désormais à Bob : chaque recherche qui le renvoie l'indique.
Ses propres mots sont aussi mémorisés, et "me" ne compte jamais dans la limite.
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"renvoie400 invalid_request_errorau 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_erroravecspeaker_limit: 50dans le corps d'erreur. Stocker avec un nom non enregistré renvoie aussi400et ne stocke rien. Filtrer la recherche par un nom non enregistré renvoie404 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 :
Bobetbobsont deux personnes. Les noms vont jusqu'à 80 caractères.
# 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 une écriture dont le sens est suffisamment proche d'un souvenir déjà stocké est abandonnée - la correspondance porte sur le sens, pas sur le texte : ce store renvoie status "duplicate" avec une note explicite, n'enregistre rien et n'attache aucun locuteur. Un fait réellement nouveau qui ne varie que par un détail d'un fait existant (« allergie aux crustacés » après « allergie aux arachides ») tombe sous la même règle : lisez status plutôt que de supposer que l'écriture a abouti.
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{ "memories": [
{ "content": "Bob said the deadline moved to Tuesday",
"speaker": "Bob", ... } ] }{ "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.
Lire les souvenirs d'une personne
by_speaker renvoie ce qu'une personne a dit, du plus récent au plus ancien, sans requête. "me" donne les mots de l'assistant lui-même. Même pagination par curseur que pour les images : renvoyez next_before et next_skip_ids.
page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]| Champ | Ce qu'il fait |
|---|---|
| memories | Les souvenirs, du plus récent au plus ancien. Même forme que ce que renvoie une recherche. |
| chunks | Les fragments au niveau de la phrase derrière ces souvenirs - ce qu'une suppression retirerait réellement. Généralement plus nombreux que les souvenirs eux-mêmes ; à afficher avant toute confirmation de suppression. Également renvoyé sous points_to_delete. |
| next_before | Curseur de la page suivante, avec next_skip_ids. Les deux sont nécessaires car plusieurs souvenirs peuvent partager un même horodatage. |
speaker désigne ici l'étiquette écrite au moment du stockage, pas une recherche sur le texte. Un souvenir stocké sans locuteur reste accessible par recherche mais jamais par by_speaker, y compris sous "me".Lister, parcourir et supprimer des locuteurs
mem.list_speakers() # who is registered
mem.by_speaker("Bob") # what Bob said, newest first
mem.remove_speaker("Bob") # unregister; the memories stayPhotographies
Un souvenir peut porter une image. Le moteur indexe l'image : une requête textuelle dans n'importe quelle langue y correspond donc même lorsque l'enregistrement n'a ni légende, ni titre, ni texte alternatif.
En stocker une
Passez un objet image à l'appel add ordinaire. content peut être vide ; l’image est alors trouvable par recherche à elle seule.
mem.add("at the beach", image={"data": b64}) # caption + image
mem.add("", image={"data": b64}) # the image IS the memorydata est obligatoire. Un préfixe data:image/jpeg;base64, et les retours à la ligne ajoutés par base64 et openssl sont supprimés automatiquement.
| Champ | Ce qu'il fait |
|---|---|
| data | Base64 de l'image. Obligatoire. Le plafond de taille est un réglage serveur, pas une constante du SDK : /health le publie sous memory.images.max_bytes. |
| reference | L'emplacement de votre propre copie de l'original. Stocké sous forme de chaîne et jamais récupéré par nos soins. |
| taken_at | RFC3339, généralement issu de l'EXIF. Renseigne event_date lorsque ce champ est vide, de sorte que le souvenir est trié selon la date de prise de vue et non selon la date d'envoi. |
En trouver une
Il n'existe pas de recherche de images distincte. search et recall renvoient les images avec le texte, classés ensemble.
Manipuler celles que vous avez
data, mime = mem.get_image(memory_id=mid)
page = mem.list_images(limit=50) # page["count"] = store total
mem.forget_image(memory_id=mid, preview=True)| Appel | Ce qu'il fait |
|---|---|
| get_image | Les octets d'origine, sous la forme (bytes, content_type). Le type est déduit des octets, pas du nom donné au fichier envoyé. Un souvenir sans image lève une erreur au lieu de renvoyer un contenu vide. |
| list_images | Une page, du plus récent au plus ancien, plus count - le total du store, pas la taille de la page. La pagination se fait par curseur : renvoyez next_before et next_skip_ids. Les deux sont nécessaires car plusieurs images peuvent partager un même horodatage. |
| forget_image | Supprime l’image et conserve le texte. Une image stockée sans légende est le souvenir : dans ce cas, le souvenir est supprimé lui aussi. |
Passez preview=True à forget_image pour obtenir memory_kept sans rien modifier. iter_images gère la pagination à votre place.
Ce que coûte une image
Une image est facturée en jetons, la même unité que le texte. Jetons = surface en pixels / 556,7. Au-delà de 1 568 px sur le côté long, le décompte se fait à 1 568 px : une image de 2 500 px coûte donc autant qu’une de 1 568 px.
| Image | Compté à | Tokens |
|---|---|---|
| 700 × 700 | as sent | 881 |
| 1000 × 1000 | as sent | 1,797 |
| 1568 × 1568 | as sent | 4,417 |
| 1920 × 1080 | 1568 × 882 | 2,485 |
| 2500 × 1875 | 1568 × 1176 | 3,313 |
| 2500 × 2500 | 1568 × 1568 | 4,417 |
Plafond : 4 417 jetons par image. Nous réservons ce plafond sur votre solde avant l’appel et facturons ensuite la valeur mesurée, qui n’est jamais supérieure.
Combien en reviennent
Valeur par défaut 1, maximum 5 images par réponse. Cinq images représentent près de 20 000 tokens.
| Champ | Ce qu'il fait |
|---|---|
| max_images | 0 à 5. Nombre de images qu'une seule réponse peut porter. Valeur par défaut 1. 0 ne renvoie que le texte. Une valeur hors bornes est rejetée plutôt que ramenée dans les bornes. |
verify
verify permet à une recherche d'effectuer des passes supplémentaires. Chaque passe exclut ce que les passes précédentes ont renvoyé : une deuxième passe atteint donc des souvenirs que la première n'a pas atteints.
Un entier de 0 à 3 sur search et recall. Il s'agit du nombre de passes supplémentaires : 3 autorise donc quatre récupérations. Valeur par défaut 0.
hits = mem.search("what did I eat", verify=3)
# the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)Aucun modèle de langage ne s'exécute dans cette boucle
Les passes transportent les identifiants déjà renvoyés ; le moteur les exclut et cherche au-delà. La requête n'est pas reformulée : les résultats sont donc déterministes pour une requête donnée et aucune clé d'accès à un modèle n'intervient. C'est votre code qui décide de dépenser une passe de plus.
Ce que vous envoyez et ce qui revient
| Appel | Ce qu'il fait |
|---|---|
| verify | 0 à 3. Nombre de passes supplémentaires autorisées. Une valeur hors bornes est rejetée avec un 400 plutôt que ramenée silencieusement dans les bornes. |
| verify_used | Nombre de passes supplémentaires réellement effectuées. Peut être inférieur à ce que vous avez demandé. |
Les passes s'arrêtent dès que l'une d'elles ne renvoie rien de nouveau, et les passes inutilisées ne sont pas facturées. Si une passe ultérieure échoue, les résultats déjà rassemblés sont renvoyés.
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-mcpL'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.
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.jsonCe que ça permet
Postman : File → Import → collez l'URL, tous les 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.
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.txtDé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.
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.
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-projectAdd 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=1n'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
L'agent appelle l'outil remember. Stocké durablement : la fin de la session ne change rien.
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 » →
rememberle stocke ; la session suivante le sait déjà. - « Quel format d'erreur avait-on choisi la semaine dernière ? » →
recallramè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
updatepour 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_memoriesparcourt 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 un filtrespeakerpar personne — etfilterspour la borner par DATE ou par sujet (« qu'avons-nous décidé en juin ? »), le seul axe que le sens seul ne peut pas restreindre.update- Remplace un souvenir dont le fait a changé, en gardant la trace au lieu de l'effacer.forget- Supprime une mémoire par id.list_memories- Parcourt tout ce qui est stocké, pour répondre à « que retiens-tu de moi ? » ou faire le ménage.engram- Exécute un pipeline multi-sauts intégré (deep_recall, timeline, gather) quand une seule recherche ne suffit pas.stats- Ce que contient un magasin - utile avant un nettoyage, et pour confirmer qu'une écriture a bien abouti.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.
npx wontopos-mcp) : avec cette méthode, votre clé reste dans votre environnement et ne nous est jamais envoyée dans le cadre d'une session MCP. Il enveloppe le SDK TypeScript : réessais automatiques, refus des redirections et masquage de la clé s'appliquent tels quels.Claude Code
La voie principale : une commande dans votre terminal et chaque session démarre avec la mémoire.
- Créez une clé d'API dans la console. La clé porte son workspace : une clé = un espace mémoire.
- Enregistrez le serveur.
--scope userle rend disponible dans tous les projets ; sans lui, seul le projet courant le voit. - Vérifiez : lancez
/mcpdans Claude Code -wontoposdoit apparaître avec neuf outils. - 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-projectClaude 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" }
} } }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" }
} } }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" }
} } }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" }
} } }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-KeyMême store, même mémoire : ce que ChatGPT stocke via l'Action, Claude Code le rappelle via MCP - et inversement.
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" }
} } }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-08-01 ; 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
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()[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "tablet-2", "name": "Tablet 2", "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
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
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
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}add_turn
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")
{"status": "ok"}speaker
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")
[{"content": "Bob said the deadline moved to Tuesday", "speaker": "Bob", ...}]add_bulk
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")
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
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")
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Lire
search
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. Sur un modèle doté d'une voie propre (Scroll 1.2+), le service répond en deux voies et le SDK renvoie les deux fusionnées : le tableau peut donc contenir PLUS que max_results. Dimensionnez votre fenêtre de prompt sur ce que vous recevez, pas sur le nombre demandé.
r = mem.search("what does she drink?", user_id="alice", limit=1)
[{
"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"
}]| Champ | Signification |
|---|---|
| similarity | Similarité d'embedding brute avec votre requête (0–1). |
| is_superseded | Vrai si ce fait a été remplacé par update(). |
| search_ms | Temps de récupération côté serveur. |
recall
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")
{"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
Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.
turns = mem.history("alice")
{"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
Compteurs de souvenirs pour un utilisateur.
mem.stats("alice")
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
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-...")
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}{"memory": {"id": "8bd090de-...", "content": "the office moved to the seventh floor in June",
"category": "general", "created_at": "2026-07-31T18:20:30.531518060+00:00", "event_date": null,
"is_superseded": false, "superseded_by": null}, "user_id": "docs_livetest"}list_memories
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)
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}
]}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
Supprime un souvenir unique par id.
mem.delete("alice", memory_id="576700aa-...")
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Efface tout pour un utilisateur - un seul appel, conforme RGPD.
mem.delete_all("alice")
{"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": ...}
search_self
Les deux voies en un seul appel sur un modèle à mémoire de soi (Scroll 1.2+) : ce que les autres ont dit, et les propres mots de l'agent - séparés, pour que le lecteur ne confonde jamais qui a parlé.
r = mem.search_self("what did I promise?", user_id="alice") r["memories"] # what others said / general memories r["self_memories"] # the agent's OWN words (speaker "me")
list_engrams
Demandez au service quels engrammes et formes de rendu le modèle sélectionné peut exécuter, au lieu de coder en dur des noms qui deviennent obsolètes dès qu'un nouveau sort.
cat = mem.list_engrams() [e["name"] for e in cat["engrams"]] # ask, never hard-code
filters
Restreint la recherche à une partie du magasin. Appliqué avant le classement : vous obtenez les meilleures correspondances à l'intérieur du filtre, pas un top-N filtré après coup.
mem.search("what did we decide", user_id="alice", filters={ "categories": ["work"], "event_from": "2026-01-01", # when it HAPPENED })
idempotency_key
Rend sûr le fait de répéter une écriture. À utiliser quand la reprise vient de vous - un job interrompu puis relancé, une file qui redélivre.
mem.add("she prefers tea", "alice", idempotency_key=f"import:{row.id}")
import:row-42), jamais une constante : une clé réutilisée pour deux écritures différentes rejoue la première, et la seconde est perdue en silence. Format : 1 à 128 caractères parmi [A-Za-z0-9._:-].with_timeout / with_retries
Ajustez un seul point d'appel sans toucher au client déjà construit : un clone avec un délai plus long pour une grosse reprise, ou sans reprises à l'intérieur de votre propre boucle de retry.
mem.with_timeout(120).add_bulk(big_blob, "alice") # this slow call only mem.with_retries(0).add("...", "alice") # you retry, not the SDK
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-08-01 ; 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
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();
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "tablet-2", "name": "Tablet 2", "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
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
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
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}addTurn
Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.
await mem.addTurn("hi", "hello!", "alice");
{"status": "ok"}speaker
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" });
addBulk
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");
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
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");
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Lire
search
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. Sur un modèle doté d'une voie propre (Scroll 1.2+), le service répond en deux voies et le SDK renvoie les deux fusionnées : le tableau peut donc contenir PLUS que max_results. Dimensionnez votre fenêtre de prompt sur ce que vous recevez, pas sur le nombre demandé.
const r = await mem.search("what does she drink?", "alice", 1);
[{
"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"
}]| Champ | Signification |
|---|---|
| similarity | Similarité d'embedding brute avec votre requête (0–1). |
| is_superseded | Vrai si ce fait a été remplacé par update(). |
| search_ms | Temps de récupération côté serveur. |
recall
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");
{"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
Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.
const turns = await mem.history("alice");
{"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
Compteurs de souvenirs pour un utilisateur.
await mem.stats("alice");
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
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-...");
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}listMemories
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 });
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}
]}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
Supprime un souvenir unique par id.
await mem.delete("alice", "576700aa-...");
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}deleteAll
Efface tout pour un utilisateur - un seul appel, conforme RGPD.
await mem.deleteAll("alice");
{"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: ... }
searchSelf
Les deux voies en un seul appel sur un modèle à mémoire de soi (Scroll 1.2+) : ce que les autres ont dit, et les propres mots de l'agent - séparés, pour que le lecteur ne confonde jamais qui a parlé.
const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice"); // memories = what others said · self_memories = the agent's OWN words
listEngrams
Demandez au service quels engrammes et formes de rendu le modèle sélectionné peut exécuter, au lieu de coder en dur des noms qui deviennent obsolètes dès qu'un nouveau sort.
const { engrams, forms } = await mem.listEngrams(); // ask, never hard-code
filters
Restreint la recherche à une partie du magasin. Appliqué avant le classement : vous obtenez les meilleures correspondances à l'intérieur du filtre, pas un top-N filtré après coup.
await mem.search("what did we decide", "alice", 10, { filters: { categories: ["work"], event_from: "2026-01-01" }, // when it HAPPENED });
idempotencyKey
Rend sûr le fait de répéter une écriture. À utiliser quand la reprise vient de vous - un job interrompu puis relancé, une file qui redélivre.
await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
import:row-42), jamais une constante : une clé réutilisée pour deux écritures différentes rejoue la première, et la seconde est perdue en silence. Format : 1 à 128 caractères parmi [A-Za-z0-9._:-].withTimeout / withRetries
Ajustez un seul point d'appel sans toucher au client déjà construit : un clone avec un délai plus long pour une grosse reprise, ou sans reprises à l'intérieur de votre propre boucle de retry.
await mem.withTimeout(120_000).addBulk(bigBlob, "alice"); // this slow call only await mem.withRetries(0).add("...", "alice"); // you retry, not the SDK
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-08-01 ; 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
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?;
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
{"id": "tablet-2", "name": "Tablet 2", "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
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
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
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}add_turn
Stocke un tour de conversation (utilisateur + assistant) à la fois en mémoire court terme et long terme.
mem.add_turn("hi", "hello!", "alice").await?;
{"status": "ok"}speaker
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?;
add_bulk
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?;
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
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?;
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Lire
search
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. Sur un modèle doté d'une voie propre (Scroll 1.2+), le service répond en deux voies et le SDK renvoie les deux fusionnées : le tableau peut donc contenir PLUS que max_results. Dimensionnez votre fenêtre de prompt sur ce que vous recevez, pas sur le nombre demandé.
let r = mem.search("what does she drink?", "alice", 1).await?;
[{
"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"
}]| Champ | Signification |
|---|---|
| similarity | Similarité d'embedding brute avec votre requête (0–1). |
| is_superseded | Vrai si ce fait a été remplacé par update(). |
| search_ms | Temps de récupération côté serveur. |
recall
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?;
{"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
Tours de conversation récents (mémoire court terme), du plus ancien au plus récent.
let turns = mem.history("alice").await?;
{"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
Compteurs de souvenirs pour un utilisateur.
mem.stats("alice").await?;
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
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?;
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}list_memories
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?;
{"count": 2, "next_cursor": null, "memories": [
{"id": "576700aa-...", "content": "she prefers tea over coffee",
"category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
"is_superseded": false, "superseded_by": null}
]}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
Supprime un souvenir unique par id.
mem.delete("alice", "576700aa-...").await?;
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Efface tout pour un utilisateur - un seul appel, conforme RGPD.
mem.delete_all("alice").await?;
{"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), .. })
search_self
Les deux voies en un seul appel sur un modèle à mémoire de soi (Scroll 1.2+) : ce que les autres ont dit, et les propres mots de l'agent - séparés, pour que le lecteur ne confonde jamais qui a parlé.
let r = mem.search_self("what did I promise?", "alice", 10).await?; // r.memories = what others said · r.self_memories = the agent's OWN words
list_engrams
Demandez au service quels engrammes et formes de rendu le modèle sélectionné peut exécuter, au lieu de coder en dur des noms qui deviennent obsolètes dès qu'un nouveau sort.
let cat = mem.list_engrams().await?; // ask, never hard-code
filters
Restreint la recherche à une partie du magasin. Appliqué avant le classement : vous obtenez les meilleures correspondances à l'intérieur du filtre, pas un top-N filtré après coup.
mem.search_with("what did we decide", "alice", 10, json!({"filters": { "categories": ["work"], "event_from": "2026-01-01" // when it HAPPENED }})).await?;
add_idempotent
Rend sûr le fait de répéter une écriture. À utiliser quand la reprise vient de vous - un job interrompu puis relancé, une file qui redélivre.
mem.add_idempotent("she prefers tea", "alice", json!({}), &format!("import:{}", row.id)).await?;
import:row-42), jamais une constante : une clé réutilisée pour deux écritures différentes rejoue la première, et la seconde est perdue en silence. Format : 1 à 128 caractères parmi [A-Za-z0-9._:-].with_timeout / with_retries
Ajustez un seul point d'appel sans toucher au client déjà construit : un clone avec un délai plus long pour une grosse reprise, ou sans reprises à l'intérieur de votre propre boucle de retry.
mem.with_timeout(120).add_bulk(big_blob, "alice", "general").await?; mem.with_retries(0).add("...", "alice", json!({})).await?;
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
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"}'
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}store-turn
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!"}'
{"status": "ok"}speaker
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"}'
supersede
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"}'
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}bulk-store
Reprend un long historique en un seul appel - découpé et encodé côté serveur.
curl -X POST https://api.wontopos.com/api/v1/memory/bulk-store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"...a long history...","category":"general"}'
{"elapsed_secs": 0.138589761, "status": "ok", "stored": 1, "total_chunks": 1}Idempotency-Key
Rend sûr le fait de répéter une écriture. À utiliser quand la reprise vient de vous - un job interrompu puis relancé, une file qui redélivre.
# same key + same body = the FIRST response is replayed, nothing is stored twice curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: import:row-42" \ -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
import:row-42), jamais une constante : une clé réutilisée pour deux écritures différentes rejoue la première, et la seconde est perdue en silence. Format : 1 à 128 caractères parmi [A-Za-z0-9._:-].Lire
search
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}'
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
"similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}search + filters
Restreint la recherche à une partie du magasin. Appliqué avant le classement : vous obtenez les meilleures correspondances à l'intérieur du filtre, pas un top-N filtré après coup.
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 we decide", "filters":{"categories":["work"],"event_from":"2026-01-01","event_to":"2026-06-30"}}'
get
Lit un souvenir à partir de l'id renvoyé par store ou list - texte d'origine et métadonnées, sans les vecteurs.
curl -X POST https://api.wontopos.com/api/v1/memory/get \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","memory_id":"576700aa-f0e0-4c26-99a0-10e2d5b0d624"}'
list
Parcourt tout un magasin, curseur après curseur. Pour explorer ou exporter.
curl -X POST https://api.wontopos.com/api/v1/memory/list \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","limit":100}' # pass next_cursor back for the next page
{"count": 3, "memories": [{"id": "1a1cfc47-...", "content": "...", "category": "general",
"created_at": "2026-07-31T18:04:51.937117314+00:00", "event_date": null, "is_superseded": false}],
"next_cursor": "722c08e5-8998-4882-979e-d71995b5b4af", "user_id": "docs_livetest"}recall
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?"}'
{"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
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
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}Chaque endpoint + champs du corps →
Variantes propres à Rust
Python et TypeScript les acceptent comme arguments optionnels. Rust stable n'a ni arguments par défaut ni arguments nommés : chacun est donc une méthode distincte plutôt qu'un builder qu'il faut terminer.
mem.add_with(text, None, json!({}), extra) // add + extra body fields
mem.search_opts(q, None, 10, &opts) // search + verify / max_images
mem.search_with(q, None, 10, extra) // search + any other field
mem.recall_with(q, None, extra) // recall + extra
mem.search_self_with(q, None, 10, extra) // self lane + extra
mem.engram_with(name, q, None, extra) // engram + extra
mem.update_idempotent(old, new, None, key) // update + Idempotency-Key
mem.add_turn_idempotent(u, a, None, key)
mem.add_bulk_idempotent(text, None, cat, key)
mem.revisions_page(None, "revised", 20, None, None)
mem.list_all_images(None, None) // = iter_images, collectedlist_all_images est aussi exporté sous le nom iter_images, qui correspond au nom employé par les deux autres SDK : un lecteur venant de ces docs saisit ce nom en premier.
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.
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.
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"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"Renvoie les correspondances comme des enregistrements exacts - temps écoulé précis et ancres absolues, structurés pour qu'un modèle les lise directement.
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.# the memoir form on a plain search — and on recall, the LLM's one-call context
r = mem.search("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
ctx = mem.recall("what does Alice drink?", user_id="alice", model="scroll-1.2", form="memoir", tz=9)
# every memory's .time reads "a couple weeks ago" (archive → "2 weeks ago (Jun 09)") — the LLM sees human timetz 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 :
{ "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" }
] }{ "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é | Memoir | Archive |
|---|---|---|
| 3 min | a few minutes ago | 3 minutes ago |
| 14 min | about 15 minutes ago | 14 minutes ago |
| 30 min | half an hour ago | 30 minutes ago |
| 50 min | about an hour ago | 50 minutes ago |
| 2 h | a couple hours ago | 2 hours ago, at 13:10 |
| 8 h | this morning | 8 hours ago, at 07:10 |
| hier ap.-midi | yesterday afternoon | yesterday at 14:00 |
| la nuit dernière | last night | 17 hours ago, at 22:00 |
| 2 jours | a couple days ago | 2 days ago (Tue 15:10) |
| 6 jours | several days ago | 6 days ago (Fri 15:10) |
| 9 jours | about a week ago | last week (Jun 16) |
| 16 jours | a couple weeks ago | 2 weeks ago (Jun 09) |
| 35 jours | about a month ago | last month (May 21) |
| 60 jours | a couple months ago | 2 months ago (Apr 2026) |
| 180 jours | about half a year ago | 6 months ago (Dec 2025) |
| 380 jours | about a year ago | last year (Jun 2025) |
| 800 jours | a couple years ago | 2 years ago (Apr 2024) |
| 1500 jours | about 4 years ago | 4 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.
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"){ "engram": "deep_recall", "hops": 2, "count": 12,
"memories": [ ... ],
"usage": { "input_tokens": 200, "output_tokens": 589 } }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.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"){ "engram": "timeline", "hops": 1, "count": 15,
"memories": [ ... ],
"usage": { "input_tokens": 100, "output_tokens": 736 } }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.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"){ "engram": "gather", "hops": 4, "count": 18,
"memories": [ ... ],
"usage": { "input_tokens": 400, "output_tokens": 637 } }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.equilibrium
Correction de dérive. La recherche sémantique se resserre au fil d'une session : la requête porte l'état courant, elle ramène donc des souvenirs du même état et le tour suivant penche davantage du même côté. Cet engramme réélargit le résultat selon trois axes que la requête ne contrôle pas : la dispersion dans le temps, l'association qui s'éloigne de la requête, et la part substantielle du magasin. À utiliser quand les réponses tournent en boucle ou s'aplatissent. Pour un fait précis, deep_recall ou gather conviennent mieux, car ils restent proches de la requête. Renvoie jusqu'à 12 éléments.
wide = mem.engram("equilibrium", "how have things been lately?", user_id="alice"){ "engram": "equilibrium", "hops": 3, "count": 12,
"memories": [ ... ],
"usage": { "input_tokens": 300, "output_tokens": 293 } }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.tone_stabilizer
Sa propre voix. Les longues sessions écartent un assistant de son registre : les réponses s'allongent, tournent au rapport, ou prennent l'humeur de la dernière période. Le rappel de soi ordinaire aggrave la chose, car il épouse l'état courant et renvoie les lignes les plus récentes comme si elles étaient le caractère. Cet engramme renvoie plutôt les mots de l'agent d'avant cette période. Il faut des tours enregistrés avec speaker me ; sans eux il ne renvoie rien plutôt que de deviner. Renvoie jusqu'à 10 éléments.
# store the assistant's turns as speaker "me", then pull its own register back
mem.add("I keep answers short unless you ask for detail.", user_id="alice", speaker="me")
mine = mem.engram("tone_stabilizer", "how do I usually answer?", user_id="alice"){ "engram": "tone_stabilizer", "hops": 2, "count": 10,
"memories": [ { "content": "I keep answers short unless you ask for detail.", "speaker": "me" }, ... ],
"usage": { "input_tokens": 200, "output_tokens": 442 } }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.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.
En-têtes
| En-tête | Ce qu'il fait |
|---|---|
| X-API-Key | Requis à chaque appel. Votre clé, émise dans la console. |
| X-WOS-Model | Optionnel. Détermine quel moteur répond. Omettez-le et la valeur par défaut du compte est utilisée. GET /api/v1/models liste les modèles que votre clé peut sélectionner ; un endpoint qu'un moteur plus ancien ne peut pas servir répond 501 et nomme ce modèle. |
| Idempotency-Key | Optionnel, sur les écritures. La même clé avec le même corps rejoue la première réponse au lieu de stocker à nouveau - voir la note ci-dessous. |
Endpoint
| Endpoint | Rôle | Champs du corps |
|---|---|---|
| POST /api/v1/memory/collection | créer un store | user_id |
| GET /api/v1/memory/collections | lister vos stores | (aucun) |
| DELETE /api/v1/memory/collection | supprimer un store + ses souvenirs | user_id |
| /api/v1/memory/store | stocker un souvenir | user_id · content · metadata? (event_date · speaker) · image? |
| /api/v1/memory/store-turn | stocker un tour de conversation | user_id · user_msg · assistant_msg |
| POST /api/v1/memory/speakers | enregistrer un locuteur (explicite, 50 max) | user_id · speaker |
| GET /api/v1/memory/speakers | lister les locuteurs enregistrés + compteurs | user_id |
| DELETE /api/v1/memory/speakers | désinscrire un locuteur (souvenirs conservés) | user_id · speaker |
| /api/v1/memory/by-speaker | ce qu’une personne a dit, du plus récent au plus ancien (« me » = l’agent) | user_id · speaker · limit? · before? · skip_ids? |
| POST /api/v1/memory/image | les octets d’origine d’un souvenir image | user_id · memory_id |
| DELETE /api/v1/memory/image | retirer l’image, garder la légende | user_id · memory_id · preview? |
| /api/v1/memory/images | les images d’un store, du plus récent (+ le total) | user_id · limit? · before? · skip_ids? |
| /api/v1/memory/lineage | la chaîne de modifications d’un souvenir, du plus ancien | user_id · memory_id |
| /api/v1/won/revisions | quelle part d’un store a été réécrite. Gratuit | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/revisions | le même appel sous le plan memory. Gratuit | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/bulk-store | importer un bloc de texte | user_id · content · category? · timestamp? |
| /api/v1/memory/search | recherche sémantique | user_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images? |
| /api/v1/memory/recall | court + long + contexte | user_id · query · limit? · context_limit? |
| /api/v1/memory/get | un souvenir par id | user_id · memory_id |
| /api/v1/memory/list | parcourir un magasin page par page | user_id · limit? · cursor? |
| /api/v1/memory/history | tours récents | user_id |
| /api/v1/memory/stats | compteurs de souvenirs | user_id |
| /api/v1/memory/supersede | remplacer un fait modifié | user_id · old_memory_id · new_content |
| /api/v1/memory/forget | en supprimer un (ou tous) | user_id · memory_id? (omis = tout supprimer) |
| GET /api/v1/engram | engrammes que ce modèle peut exécuter | (aucun) |
| POST /api/v1/engram/run | exécuter un engramme | name · user_id · query · form? · tz? |
| GET /api/v1/models | modèles disponibles | (aucun) |
Idempotency-Key. La même clé avec le même corps rejoue la première réponse au lieu de stocker à nouveau (10 minutes) ; la même clé avec un corps différent répond 422. Seuls les 2xx sont mis en cache, un appel en échec est donc immédiatement réessayable.# 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?"}'
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}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'utilisation | Achat de crédits | Limite 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 - Enterprise | Contactez-nous | Sans 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.
| Palier | Requêtes par minute |
|---|---|
| Tier 1 | 150 |
| Tier 2 | 300 |
| Tier 3 | 600 |
| Tier 4 | 1,500 |
| Tier 5 | 3,000 |
| Tier 6 - Enterprise | Sur 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.
Appels gratuits
Quelques endpoints ne sont pas facturés du tout - ils sont regroupés sous Won. À la place d’un prix, ils ont deux plafonds.
- 10 requêtes par minute, par endpoint. Chaque endpoint gratuit dispose de son propre compteur : en épuiser un n'épuise pas les autres.
- 300 requêtes par heure, partagées. Tous les endpoints gratuits puisent dans un même quota horaire par compte.
Aucune des deux n'est atteinte en usage ordinaire, et aucune n'affecte les limites payantes ci-dessus.
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.
{"type": "error", "error": {
"type": "authentication_error",
"message": "Invalid or revoked API key.",
"request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
}}| HTTP | Signification | Que faire |
|---|---|---|
| 400 | Corps malformé (champ manquant ou de mauvais type) | Le message nomme le champ exact - corrigez et réessayez. |
| 401 | Clé API invalide ou révoquée | Vérifiez la clé ; émettez-en une nouvelle dans la console. |
| 402 | Solde épuisé, aucune carte enregistrée, ou plafond de palier atteint | Rechargez votre solde ou enregistrez une carte dans la console. La réponse contient balance_cents et floor_cents, ce qui vous indique lequel des deux vous a arrêté. |
| 404 | Souvenir, store ou image introuvable | Vérifiez l'id. get_image répond également 404 lorsque le souvenir existe mais ne porte aucune image. |
| 409 | Ce nom est déjà pris | Les noms de store et de workspace sont uniques au sein d'un compte - choisissez-en un autre. |
| 413 | Corps de requête au-delà de 10MB | Le Base64 pèse environ 33% de plus que le fichier qu'il encode, redimensionnez donc l'image avant de l'encoder. |
| 429 | Limite de débit atteinte | Le SDK réessaie déjà pour vous, avec backoff et jitter, en respectant Retry-After. En recevoir une signifie que les tentatives sont épuisées - réduisez le nombre d'appels simultanés plutôt que d'enrouler votre propre boucle autour. |
| 501 | Le moteur de ce modèle n'implémente pas cet endpoint | Les images et l'historique des révisions demandent un moteur plus récent. GET /api/v1/models indique quels modèles servent quoi. |
| 5xx | Problème côté serveur | Réessayez avec backoff, mais pas aveuglément. Le SDK ne réessaie pas automatiquement un 5xx ici, car chaque appel de cette API est un POST et le serveur a peut-être déjà enregistré votre requête. Renvoyez-la avec une clé d'idempotence pour qu'une répétition ne puisse pas écrire deux fois, et joignez le request_id si vous nous contactez. |
Chaque erreur est un WosError, et chaque statut possède en plus sa propre classe - BadRequestError, AuthenticationError, PaymentRequiredError, NotFoundError, ConflictError, RateLimitError, ServerError, APIConnectionError. Attrapez celle que vous voulez traiter au lieu de comparer des numéros.
# SDK error handling (Python) from wontopos import Client, WosError, RateLimitError, PaymentRequiredError try: mem.search("...", user_id="alice") except PaymentRequiredError: ... # 402 - top up except RateLimitError: ... # 429 - the SDK already retried; slow down except WosError as e: ... # e.status, e.message, e.request_id except (ValueError, TypeError): ... # never left the client
Certaines erreurs ne nous parviennent jamais. La clé API, l'id du store, la clé d'idempotence et l'image sont toutes vérifiées avant l'envoi de la requête, et celles-là lèvent ValueError ou TypeError - pas WosError. Un except WosError seul ne les attrapera pas.
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.
lineage
La chaîne des modifications derrière un souvenir, de la plus ancienne à la plus récente. revisions indique l'ampleur des changements d'un store ; celui-ci indique ce qui est arrivé à un fait précis.
revisions, il s'agit d'un appel facturé normalement, car il renvoie du contenu de souvenirs.Passez l'id de n'importe quel souvenir de la chaîne. Les versions remplacées sont conservées et non supprimées : une recherche qui ne renvoie que le fait courant reste donc traçable jusqu'à son origine.
chain = mem.lineage(memory_id=mid)["chain"]
for step in chain:
print(step["changed_at"], step["action"], step["content"]){ "memory_id": "m_9", "count": 3, "truncated": false,
"chain": [
{ "memory_id": "m_3", "content": "lives in Seoul",
"created_at": "2026-03-02T…", "changed_at": "2026-06-11T…",
"action": "replaced", "confidence": 0.94,
"superseded_by": "m_7", "is_current": false },
{ "memory_id": "m_7", "content": "moved to Busan", … },
{ "memory_id": "m_9", "content": "Haeundae, specifically",
"changed_at": null, "superseded_by": null, "is_current": true }
] }| Champ | Ce qu'il fait |
|---|---|
| chain | Les versions, de la plus ancienne à la plus récente. Chacune porte les mêmes champs qu'un souvenir, plus les quatre ci-dessous. |
| changed_at | Date à laquelle cette version a été remplacée (RFC3339), ou null tant qu'elle est encore en vigueur. |
| action | Ce qui s'est passé à ce maillon - la relation entre le remplacement et cette version. |
| confidence | Le degré de certitude du moteur quant à cette relation, de 0 à 1. |
| is_current | Vrai pour la seule version encore en vigueur. Exactement une par chaîne. |
| truncated | Vrai lorsque la chaîne était plus longue que ce que le service parcourt. Les étapes renvoyées restent les plus anciennes. |
À quoi cela sert
Deux usages. Le débogage : comprendre pourquoi un souvenir se lit ainsi aujourd'hui. Et permettre à un assistant de consulter son propre historique : un fait corrigé trois fois n'est pas de même nature qu'un fait écrit une seule fois, et seule la chaîne le montre.
Won s'adresse à ceux qui lisent la mémoire.
La plus grande partie de cette API répond avec la mémoire. Won répond à propos d'elle : quelle part d'un store a été réécrite, et jusqu'où on peut lui faire confiance. Cela s'adresse au côté qui lit, le plus souvent l'assistant que vous construisez, et non à la personne dont parlent les souvenirs.
Wontopos, c'est Won + Topos, un seul lieu où la mémoire habite. Won est la part de ce lieu qui renseigne sur la mémoire au lieu de la restituer. Ces appels sont gratuits, en lecture seule, et ne touchent jamais à la récupération : demander ne coûte rien à vos utilisateurs et ne change rien à ce qui est mémorisé.
Ce qui s'y trouve aujourd'hui
Un seul appel aujourd'hui.
| Appel | Ce qu'il fait |
|---|---|
| POST /won/revisions | La proportion de ce store qui a été modifiée depuis son écriture. Deux nombres et deux phrases qui les expliquent. |
Un exemple détaillé
Utilisez le ratio, pas le compte brut. 3 sur 40 et 30 sur 40 n'appellent pas le même traitement.
r = mem.revisions()
# {"revised": 3, "total": 40, "counts": "…", "excludes": "…"}
if r["revised"] / r["total"] > 0.1:
system += "Some of what you remember here has been corrected since."counts et excludes sont renvoyés sous forme de phrases plutôt que d'indicateurs, car l'appelant est souvent un modèle. Les suppressions ne sont pas comptées.
Prix et limites
| Règle | Valeur |
|---|---|
| Prix | Aucun. Les appels gratuits ne passent pas par la facturation : aucun débit de tokens, aucun frais par requête et aucune consommation enregistrée. |
| Par minute | 10 par minute, par compte et par endpoint. Épuiser la minute d'un endpoint n'épuise pas celle d'un autre. |
| Par heure | 300 par heure, par compte, partagées par tous les appels gratuits. Cette limite ignore le chemin : ajouter des endpoints gratuits n'augmente donc pas le total qu'un compte peut consommer. |
| Face au trafic payant | Séparées dans les deux sens. Ces appels ne peuvent pas ralentir vos recherches et vos recherches ne peuvent pas les épuiser. Les clés d'un même compte partagent les mêmes compteurs : détenir davantage de clés ne multiplie pas le quota. |
Les deux plafonds répondent 429 avec Retry-After en secondes et un message indiquant lequel a été atteint.
Retry-After: 41
{ "error": { "type": "rate_limit_error",
"message": "This endpoint is free and limited to 10 requests per
minute, counted per endpoint. Retry in 41s." } }/api/v1/memory/revisions, pour les clients publiés avant l'existence de la surface Won. Il s'agit du même handler et du même budget, pas d'un quota supplémentaire. Le nouveau code doit utiliser l'adresse Won.Quelle part d'un store a été réécrite
revisions répond revised sur total : combien de souvenirs d'un store ont été modifiés après avoir été écrits. Il vaut la peine de poser la question avant de vous appuyer sur la mémoire pour quelque chose qui compte, ou quand un fait rappelé ne colle pas à ce que l'utilisateur dit maintenant. Un store dont trois faits sur dix ont été remplacés mérite moins de confiance qu'un store que personne n'a modifié.
Compteurs
Il compte ce qu'une transformation a touché - remplacé, mis à jour, rétracté, ainsi que les images retirées.
mem.revisions()
# {"revised": 3, "unrevised": 37, "total": 40, …}| Champ | Signification |
|---|---|
| revised | Les souvenirs qu'une transformation a touchés. |
| unrevised | Les souvenirs auxquels rien n'a touché depuis leur écriture. revised + unrevised égale toujours total - cette valeur est dérivée et non comptée séparément, si bien qu'une écriture concurrente ne peut pas mettre les trois en désaccord. |
| total | Les souvenirs présents dans le store. |
| counts / excludes | Des phrases simples, et non des indicateurs, qui précisent ce que les nombres recouvrent. L'appelant est souvent un modèle. |
Lire la liste
Passez include pour obtenir les souvenirs eux-mêmes, et pas seulement leur nombre. Si vous l'omettez, vous n'obtenez que les compteurs, ce qui constitue l'appel le moins coûteux.
page = mem.revisions(include="revised", limit=20)
page["memories"], page["matched"], page["has_more"]| Champ | Ce qu'il fait |
|---|---|
| include | "revised" ou "unrevised". Toute autre valeur est refusée avec un 400 plutôt que de retomber sur les compteurs : une faute de frappe qui supprimerait silencieusement la liste serait indiscernable d'un store vide. |
| limit | 5 à 20, valeur par défaut 20. Une valeur hors bornes, ou d'un type incorrect, est refusée plutôt que ramenée dans les bornes. |
| matched | Nombre total de lignes derrière cette page, et non la taille de la page. |
| ordered_by | Le service indique lui-même son ordre de tri : du plus récemment stocké au plus ancien, et non du plus récemment modifié. |
| next_before | Curseur de la page suivante, avec next_skip_ids. Renvoyez les deux ; les ids s'accumulent au fil des pages. |
revised faible. Ce nombre vous dit quelle part a été réécrite, pas quelle part a disparu.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.
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.