Langzeitgedächtnis für KI-Agenten.
WOS ist eine Memory-API. Sie speichern die Erinnerungen eines Nutzers einmal, rufen dann zu jeder Abfrage nur die relevanten ab und übergeben sie an den Prompt Ihres Modells.
Der Abruf ist rein semantisch, ohne Keyword- oder BM25-Matching, daher ist die Recall-Qualität in allen Sprachen identisch. Jede Abfrage liefert einen kleinen, begrenzten Kontext zurück, unabhängig davon, wie viel Sie gespeichert haben, und über Ihre gespeicherten Erinnerungen läuft niemals ein Modell.
Kernoperationen
store- speichert eine Erinnerung für einen Nutzer.recall- liefert die relevanten Erinnerungen zu einer Abfrage. Dies ist der zentrale Aufruf.search- rohe semantische Suche über gespeicherte Erinnerungen.supersede- aktualisiert oder ersetzt eine veraltete Erinnerung.forget- löscht eine einzelne Erinnerung oder einen ganzen Nutzer (DSGVO).
Drei Modelle, eine Linie.
WOS-Modelle sind danach benannt, wie Menschen im Lauf der Geschichte Wissen bewahrt haben - Tablet, Scroll, Book. Steintafel, Schriftrolle, gebundenes Buch: Jedes leistet mehr für Ihren Agenten als das vorherige.
Tablet
LiveEine schlanke, schnelle und kostengünstige Art, Erinnerungen einzuschreiben und abzurufen - das Fundament, auf dem jedes Modell aufbaut.
Scroll
LiveErgänzt ein Sprachmodell, das Ihre Frage genauer liest und einen vollständigeren Kontext zurückbringt - verstreute Belege kommen so wieder zusammen, statt dass ein Teil fehlt.
Book
DemnächstSchlägt von selbst die richtige Seite auf - wählt in jedem Moment das passende Gedächtnis und die passenden Werkzeuge und wird mit jeder Nutzung schärfer.
Der vollständige Benchmark-Bericht zu Tablet 1 steht auf der Benchmark-Seite.
Zahlen Sie uns $2. Sparen Sie ein Vielfaches davon bei Ihrem LLM.
WOS führt Ihrem LLM ~1.200 Tokens pro Abfrage zu - einen begrenzten, relevanten Ausschnitt - statt die gesamte Historie in jeden Prompt zu stopfen. Der Abstand ist enorm und wächst mit Ihrer Historie.
Jeder für WOS ausgegebene $1 spart ~$98 beim LLM. Größere Historie oder ein teureres Modell → größerer ROI.
Woher die Einsparungen kommen
- Ohne WOS packen Sie die gesamte Historie in jeden Prompt -
100K tokens × $2.50/1M = $0.25pro Abfrage, zu GPT-4o-Eingabepreisen (bei Modellen der Opus-Klasse etwa das Doppelte). - Mit WOS nehmen Sie die Daten einmal auf (
$2/1M); danach ist jede Abfrage ein winziger Abruf ($3/1M × 1,200) plus Ihr LLM auf nur ~1.200 Tokens. - Je weniger Tokens Ihr LLM liest, desto weniger zahlen Sie - und WOS hält diese Zahl konstant, während das Gedächtnis wächst.
Jede Sprache, dieselbe Genauigkeit.
Der Abruf ist rein semantisch - ausschließlich Embeddings, null Keyword- oder BM25-Matching. Die Recall-Qualität ist daher identisch, ob Ihre Nutzer auf 日本語, 中文, Español oder Englisch schreiben.
Lexikalisches Matching wie BM25 ist auf die Gestalt einer bestimmten Sprache abgestimmt - Morphologie, Leerzeichen, Schriftsystem. In einem mehrsprachigen Store heißt das: Die Abrufqualität variiert je nach Sprache. WOS verwendet überhaupt kein lexikalisches Matching, sodass jede Sprache denselben Weg nimmt.
Ein Store, drei Sprachen zugleich
Sie wählen keine Sprache pro Store - mischen Sie frei. Unten enthält das Gedächtnis eines Nutzers gleichzeitig Japanisch, Englisch und Spanisch, und jede Frage findet die richtige Erinnerung, unabhängig von der Sprache. Dies ist ein echter Austausch mit der Live-API:
# one user, three languages stored together mem.add("彼女はコーヒーより紅茶が好き", user_id="alice") # Japanese mem.add("she works at a design studio in Brooklyn", user_id="alice") # English mem.add("A ella le encanta hacer senderismo los sábados", user_id="alice") # Spanish
"¿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
Kein Übersetzungsschritt, keine Spracherkennung, keine Konfiguration pro Sprache. Erinnerungen und Fragen werden nach Bedeutung eingeordnet, nicht nach Sprache - passt die Bedeutung, spielt die Sprache keine Rolle.
Warum wir Keywords bewusst verbannt haben
Lexikalisches Scoring wie BM25 stärkt den Abruf für manche Sprachen mehr als für andere - hinderlich, wenn ein Store viele Sprachen enthält. Wir haben es daher vollständig aus der Engine entfernt und setzen diese Regel im Code-Review durch: Mit jeglichem lexikalischem Scoring im Pfad würde die Recall-Qualität je nach Sprache abweichen.
Kein Modell läuft über Ihre Erinnerungen.
Die Speicherung erfolgt wortgetreu, und die Engine sucht über Embeddings - günstig, schnell und deterministisch. Über Ihre gespeicherten Erinnerungen läuft niemals ein Modell. Tablet verwendet gar kein Modell; Scroll und Book setzen für stärkere Ergebnisse eines um die Engine herum ein, doch es sieht immer nur Ihre Abfrage, nie das, was Sie gespeichert haben.
- Deterministische Engine. Die Engine liefert für dieselbe Abfrage jedes Mal dieselben Erinnerungen - deshalb stammt die Varianz in unserem Benchmark allein vom Reader-Modell.
- Günstig im großen Maßstab. Keine Generierungskosten beim Speichern oder Abrufen - Ihre Rechnung folgt dem Speicherplatz, nicht der Modellnutzung, während das Gedächtnis wächst.
Ihre Worte, unangetastet
Ein verbreitetes Design lässt beim Schreiben ein Sprachmodell laufen, um "Fakten" aus dem Text zu extrahieren und umzuschreiben. Dieses Design bezahlt dreifach: Generierungskosten bei jedem Schreibvorgang, zusätzliche Latenz und die Speicherung der Paraphrase eines Modells statt der ursprünglichen Worte. WOS trifft die entgegengesetzte Wahl - es speichert das Gesagte unverändert und lässt Ihr LLM die Interpretation zur Lesezeit übernehmen, mit dem Originaltext in der Hand.
67,5 %, gemessen und reproduzierbar.
67,5 % auf BEAM 1M, gemittelt über 5 unabhängige Läufe (σ 0,22 %, keiner handverlesen), bewertet von gpt-4.1-mini mit dem Judging-Prompt des Benchmarks selbst.
Auf demselben Benchmark schwanken die Ergebnisse stark mit dem Bewertungsprotokoll - dem Judge, dem Prompt und dem, was die Abrufschicht tun darf. Wir bewerten mit dem Judge, den das Repository der Autoren selbst mitliefert, verwenden ihren Judging-Prompt wie geschrieben, passen nichts an den Test an und veröffentlichen Harness, Bewertungscode und Reader-Prompt, sodass jeder die 67,5 % exakt reproduzieren kann.
Das Protokoll in einer Tabelle
| Punkt | Unser Vorgehen |
|---|---|
| Datensatz | BEAM 1M - 35 Gespräche, 74.630 Turns, 2,2 Millionen Erinnerungen, 700 Fragen |
| Judge | gpt-4.1-mini bei temperature 0, mit BEAMs eigenem Judging-Prompt - der Standard im Repository der Autoren, kein von uns gewählter Judge |
| Läufe | 5 unabhängige Läufe, jeder Wert veröffentlicht, Mittelwert berichtet (σ 0,22 %) |
| Reader | Festes Reader-Modell und fester Prompt, wortwörtlich veröffentlicht |
Was es ehrlich hält: ein Judge von dritter Seite, der unverändert veröffentlichte Reader-Prompt, rein semantischer Abruf und die Veröffentlichung jedes Laufs - nicht nur des besten. Die Abruf-Engine ist deterministisch - führen Sie sie erneut aus, erhalten Sie dieselben Erinnerungen.
Wir erklimmen härtere Benchmarks
Wir testen auf dem härtesten Standard-Benchmark, den wir noch nicht bezwungen haben - und die Zahl ist die Bestmarke über alle WOS-Modelle hinweg, neu geschrieben, sobald ein besseres erscheint. Überschreiten wir klar 94%, steigen wir zu einem härteren Benchmark auf.
Früherer Benchmark LongMemEval-S Bestanden
Zwei Token-Preise pro Modell,
plus $0.0001 pro Anfrage.
Pro Million Tokens plus pauschal $0.0001 pro Anfrage, Bezahlung nach Verbrauch. Kein Abonnement, keine Speichermiete, keine Gedächtnisgrenzen. Sie zahlen, wenn Ihr Agent schreibt oder liest - nie für das, was er sich merkt.
| Modell | Eingabe / 1M | Ausgabe / 1M | |
|---|---|---|---|
| Tablet | $2 | $3 | Live |
| Scroll | $4 | $8 | Live |
| Book | - | - | Noch offen |
- $0.0001 pro Anfrage. Eine Pauschalgebühr auf jeden API-Aufruf, zusätzlich zur Token-Nutzung.
- Speicherung ist kostenlos. Die Aufnahme wird einmal bezahlt; das Aufbewahren kostet Sie nichts. Kein Mengenlimit, keine Aufbewahrungsgrenze.
- Wir speichern es. Wir trainieren nie darauf, verwenden es nicht und sehen es nicht ein. Das Gedächtnis Ihres Agenten gehört Ihnen - wir organisieren es nur, damit Sie es abrufen können.
- Warum Tablet so günstig ist: Seine Engine führt kein Modell aus, unsere Kosten sind also Embeddings und Festplatten - keine GPUs. Scroll und Book ergänzen ein Modell, und genau das deckt ihr höherer Preis ab.
Drei Aufrufe: Speichern, Abrufen, Antworten.
Eine API. Der Aufruf recall() liefert Kurzzeit-, Langzeit- und Umgebungskontext in einem einzigen Roundtrip - bereit zum Einfügen in Ihren Prompt.
Speichern
add() speichert Fakten und Gespräche: die Worte deines Nutzers, die eigenen des Assistenten (speaker "me") oder die einer benannten Person. Beim Eingang eingebettet, ohne LLM-Aufruf.
Abrufen
recall() liefert Kurzzeit + Langzeit + Kontext in einem Aufruf - ein begrenzter Kontext fester Größe.
Antworten
Übergeben Sie diesen begrenzten Kontext an Ihr LLM - beliebiger Anbieter, Ihr Schlüssel.
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")
Erinnerungen tragen einen Sprecher. Standard sind die Worte deines Nutzers, speaker "me" speichert, was der Assistent selbst gesagt hat, und ein Name wie "Bob" merkt sich, wer es gesagt hat — Erinnern nach Person.
Ihr erster Recall in 5 Minuten.
Ein Schlüssel, eine Installationszeile, drei Aufrufe - und Ihr Agent hat ein Gedächtnis. Jedes Snippet auf dieser Seite wurde tatsächlich ausgeführt; die Antworten sind wortwörtlich wiedergegeben.
API-Schlüssel erstellen
Erstellen Sie einen in der Konsole. Ein 155 Zeichen langer Schlüssel, der mit wos-live- beginnt, wird einmal angezeigt. Bewahren Sie ihn in einer Umgebungsvariable auf - niemals im Code.
Installieren
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
Store anlegen, dann speichern & abrufen
Ein Store ist die user_id, unter der Sie lesen und schreiben. Stores sind explizit: Legen Sie zuerst einen an (der Aufruf unten), dann speichern Sie hinein und rufen daraus ab. Speichern - beim Eingang eingebettet, kein LLM-Aufruf. Abrufen - Kurzzeit + Langzeit + Kontext in einem Roundtrip.
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 an den Client, und jeder Aufruf verwendet sie - kein Wiederholen nötig; überschreiben Sie einen einzelnen Aufruf, indem Sie ihm user_id übergeben. Stores sind explizit: Das Speichern in einen oder Abrufen aus einem Store, der nicht existiert, liefert 404 - legen Sie ihn zuerst an. Jedes Konto startet mit einem default-Store, sodass der Pfad ganz ohne user_id ohne jede Einrichtung funktioniert. Siehe Stores zum Auflisten und Verwalten.recall() liefert vier Blöcke - short_term (jüngste Gesprächszüge), long_term (relevante Erinnerungen), context (das Umfeld des besten Treffers) und eine instruction, die dem LLM sagt, wie es sie verwenden soll. Fügen Sie das Ganze in Ihren Prompt ein.
Ein Client, andere Einstellungen
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 - anlegen, auflisten, löschen.
Ein Store ist die user_id, unter der Sie lesen und schreiben - ein isolierter Gedächtnisraum pro Endnutzer, Agent oder Thema. Stores sind explizit: Legen Sie einen an, bevor Sie hineinspeichern oder daraus abrufen, sonst liefert der Aufruf 404. Jedes Konto startet mit einem default-Store, Sie können also ohne Create-Aufruf beginnen.
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"), um die Erinnerungen jeder Person getrennt zu halten, oder einen einzelnen default-Store für einen persönlichen Agenten. Stores lassen sich auch in der Konsole anlegen und durchsehen (Memory ids → Issue), ganz ohne Code. Das Löschen eines Stores ist endgültig - es entfernt jede Erinnerung darin. Store-Ids werden vor der Speicherung zusammengefaltet - kleingeschrieben, alles außerhalb von [a-z0-9_] wird zu _ -, sodass Alice.Smith und alice-smith denselben Store bezeichnen. Eine zweite Id, die auf eine vorhandene fällt, wird mit 409 abgelehnt statt stillschweigend geteilt. Die Id muss außerdem [A-Za-z0-9][A-Za-z0-9._-]{0,63} entsprechen; eine E-Mail-Adresse oder ein nicht-lateinischer Name kann daher keine Store-Id sein - verwenden Sie stattdessen eine interne Id.Stores auflisten und löschen
mem.list_stores() # [{"user_id","created_at"}, …]
mem.delete_store("alice") # the store and every memory in itWiederholte Abrufe, zum Zehntel des Preises.
Aktiviere es pro Anfrage, und WOS cached das Suchergebnis unter dem Abfragetext, mit denselben Präfixregeln wie beim Prompt Caching von LLMs. Solange der Cache warm ist, nutzt eine wiederholte oder erweiterte Abfrage das vorherige Ergebnis wieder, und der gecachte Teil wird mit 10% des normalen Token-Preises berechnet.
Ein Gespräch, drei Runden
So sieht es wirklich aus, wenn ein Agent immer weiter mit seinem Gedächtnis spricht. Jede Runde schickt das bisherige Gespräch als Abfrage, mit aktiviertem cache_control.
Die gesamte Abfrage wird gesucht und gecacht: Eingabe zum 2-fachen (TTL 5 Minuten).
Nur Bobs Satz wird eingebettet und gesucht. Der alte Teil kostet 0,1x, der neue Satz 2x, und der Cache endet jetzt dort.
Gar kein Engine-Aufruf. Alles zu 0,1x: der 90%-Rabatt.
Die Preise
| Vorgang | Token-Abrechnung | Bedeutung |
|---|---|---|
| Cache-Schreiben - TTL 5 Minuten | 2× | Die erste Anfrage. Ihr Ergebnis wird 5 Minuten aufbewahrt, und jedes Lesen verschiebt das Fenster nach vorn. |
| Cache-Schreiben - TTL 1 Stunde | 3× | Die erste Anfrage, eine volle Stunde aufbewahrt. |
| Cache-Lesen - Treffer oder Präfix-Treffer | 0.1× | Jede Anfrage nach dem Schreiben: Der gecachte Teil kostet ein Zehntel des normalen Token-Preises. |
Was es spart
Ein konkretes Beispiel: Dein Agent schickt ein Gespräch mit 3.000 Tokens als Abfrage und wiederholt oder verlängert es 10-mal innerhalb von fünf Minuten. Ohne Caching sind das 30.000 Eingabe-Tokens zum vollen Preis. Mit einem 5-Minuten-Cache sind es 6.000 für das erste Schreiben (2x) plus rund 2.700 für die neun Cache-Lesevorgänge: 8.700 abgerechnete Tokens, 71% weniger. Je länger das Gespräch läuft, desto größer die Ersparnis.
Die Präfixregel
Der Abgleich erfolgt am Anfang der Abfrage. Bleibt der Anfang identisch und wird nur neuer Text angehängt, wird der gecachte Teil wiederverwendet und nur der neue Teil gesucht. Ändert sich etwas vor dem Ende des gecachten Textes, kann nichts wiederverwendet werden.
cached [ A B C D E F G ] ○ [ A B C D E F G ] E ✗ [ B C D E F G ] E
○ Treffer - der Anfang ist unverändert, nur E ist neu
✗ Fehltreffer - der Anfang hat sich geändert, die gesamte Abfrage wird neu gesucht und neu gecacht
Drei Regeln zum Merken
- Erweitern cached bis zum neuen Ende neu. Nach [A B C D E F G] + E endet der Cache jetzt bei E: Das Ende wird einmal zum Schreibtarif berechnet, und die nächste Runde kann wieder ganz A..E als Präfix abgleichen.
- Ein zusammenhängendes Präfix pro Anfrage. Eine Abfrage kann nicht in zwei Cache-Segmente geteilt werden; nur ihr Anfang kann übereinstimmen.
- Schreibvorgänge invalidieren sofort. Jedes store, store-turn, bulk-store, forget, supersede oder Löschen des Stores verwirft dessen Cache. Eine gecachte Antwort kann nie veraltet sein.
So wird es aktiviert
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 } }Für all das brauchst du kein SDK. Caching ist ein Feld in einem HTTP-Aufruf und funktioniert daher aus jeder Programmiersprache. Der curl-Tab ist das universelle Rezept, und die Python-, TypeScript- und Rust-SDKs sind bequeme Hüllen um genau denselben Aufruf.
Gedächtnis, das weiß, wer es gesagt hat.
Menschen erinnern sich nach Person: was Bob versprochen hat, was du zugesagt hast. Gib jeder Erinnerung einen Sprecher und dein Agent tut dasselbe, auf jedem Tablet- und Scroll-Modell.
Ein Team, drei Erinnerungen
Ein Store hält viele Stimmen auseinander. Registriere eine Person einmal, speichere jede Äußerung unter ihrem Sprecher und frage später nach Person.
Der Store kennt Bob jetzt. Das 50er-Limit wird hier bei der Registrierung gezählt; Speicheraufrufe liefern nie einen Limit-Fehler.
Die Erinnerung gehört jetzt Bob: jede Suche, die sie zurückgibt, sagt das auch.
Auch die eigenen Worte werden erinnert, und "me" zählt nie gegen das Limit.
Nur Bobs Worte kommen zurück. Die Worte einer Person kommen nie als die einer anderen zurück.
Drei Regeln zum Merken
- "me" ist der Assistent selbst. Nie registriert, nie gezählt. Reserviert und kleingeschrieben:
speaker: "Me"oder"ME"liefert400 invalid_request_errorstatt stiller Umdeutung. - Das Limit zählt bei der Registrierung: zunächst 50 pro Store. Darüber hinaus liefert die Registrierung
400 invalid_request_errormitspeaker_limit: 50im Fehlerbody. Speichern mit unregistriertem Namen liefert ebenfalls400und speichert nichts. Suche mit unregistriertem Namen im Filter liefert404 not_found_error. Verzweige auf Status und Felder, nicht auf den Meldungstext; wir planen, das Limit anzuheben. - Labels leben in jeder Leseoperation. Suchergebnisse, der Langzeit-Kontext von recall und Engram-Ergebnisse tragen ihren Sprecher — das Modell weiß immer, wessen Worte es hält. Mit speaker in der Suche kommen nur die Worte einer Person zurück. Ein supersede behält den Sprecher; forget entfernt ihn.
- Namen sind Unicode: jede Sprache funktioniert. さくら, Иван und 하늘 sind gültige Sprecher, und die Zuordnung verhält sich in jeder Sprache identisch. Der Abgleich ist exakt nach Trim und Unicode-Normalisierung, also sind
Bobundbobzwei Personen. Namen sind bis 80 Zeichen lang.
# 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" } }
Zwei Randnotizen. speaker gehört zu add / store: add_turn merkt sich den ganzen Austausch, Personen-Labels und der Filter kommen aus Erinnerungen mit explizitem speaker. Und Sitzungspassagen (expand) sind Komposite mehrerer Erinnerungen, tragen also kein Label; ein speaker-Filter liefert immer atomare, gelabelte Erinnerungen. Und ein Schreibvorgang, dessen Bedeutung einer gespeicherten Erinnerung nahe genug kommt, wird verworfen - entscheidend ist die Bedeutung, nicht der Wortlaut: ein solcher store liefert status "duplicate" mit expliziter note, speichert nichts und hängt keinen Sprecher an. Auch eine wirklich neue Tatsache, die sich nur in einem Detail von einer vorhandenen unterscheidet ("Schalentierallergie" nach "Erdnussallergie"), fällt unter dieselbe Regel - lesen Sie also status, statt anzunehmen, der Schreibvorgang sei angekommen.
Wir testen das auf die harte Tour: Erinnerungen ohne Namen im Text, abgerufen pro Person. Die Zuordnung kommt aus dem Sprecher-Register, nicht aus Wortabgleich, und verhält sich daher in jeder Sprache gleich.
So nutzt du es
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 }Die Liste zeigt, wen der Store kennt, mit Erinnerungszahl pro Person gegen das Limit. Entfernen löscht nur die Registrierung: die Erinnerungen bleiben, nur das Namenslabel geht.
Die Erinnerungen einer einzelnen Person lesen
by_speaker gibt zurück, was eine Person gesagt hat, neueste zuerst, ohne Suchanfrage. "me" liefert die eigenen Äußerungen des Assistenten. Gleiche Cursor-Paginierung wie bei Bilder: Geben Sie next_before und next_skip_ids zurück.
page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]| Feld | Was er tut |
|---|---|
| memories | Die Erinnerungen, neueste zuerst. Gleiche Struktur, wie eine Suche sie zurückgibt. |
| chunks | Fragmente auf Satzebene hinter diesen Erinnerungen - was ein Löschvorgang tatsächlich entfernen würde. Üblicherweise größer als die Anzahl der Erinnerungen und vor einer Bestätigung anzuzeigen. Wird auch als points_to_delete gemeldet. |
| next_before | Cursor für die nächste Seite, zusammen mit next_skip_ids. Beides ist nötig, weil Erinnerungen denselben Zeitstempel haben können. |
speaker ist hier die beim Speichern geschriebene Markierung, keine Suche über den Text. Eine ohne Sprecher gespeicherte Erinnerung ist über die Suche erreichbar, aber nie über by_speaker, auch nicht unter "me".Sprecher auflisten, durchsehen und entfernen
mem.list_speakers() # who is registered
mem.by_speaker("Bob") # what Bob said, newest first
mem.remove_speaker("Bob") # unregister; the memories stayBilder
Eine Erinnerung kann ein Bild enthalten. Die Engine indexiert das Bild, sodass eine Textanfrage in jeder Sprache darauf passt, auch wenn der Datensatz keine Bildunterschrift, keinen Titel und keinen Alt-Text hat.
Ein Bild speichern
Übergeben Sie ein image-Objekt an den normalen add-Aufruf. content darf leer sein; das Bild ist dann für sich allein durchsuchbar.
mem.add("at the beach", image={"data": b64}) # caption + image
mem.add("", image={"data": b64}) # the image IS the memorydata ist erforderlich. Ein Präfix data:image/jpeg;base64, sowie die von base64 und openssl eingefügten Zeilenumbrüche werden für Sie entfernt.
| Feld | Was er tut |
|---|---|
| data | Base64 des Bildes. Erforderlich. Die Größenobergrenze ist eine Servereinstellung, keine SDK-Konstante - /health meldet sie als memory.images.max_bytes. |
| reference | Wo Ihre eigene Kopie des Originals liegt. Wird als Zeichenkette gespeichert und von uns nie abgerufen. |
| taken_at | RFC3339, üblicherweise aus EXIF. Füllt event_date, wenn dieses leer ist, sodass die Erinnerung nach dem Aufnahmezeitpunkt sortiert wird und nicht nach dem Zeitpunkt des Hochladens. |
Ein Bild finden
Es gibt keine separate Bildsuche. search und recall geben Bilder zusammen mit Text zurück, gemeinsam gerankt.
Mit vorhandenen Bilder arbeiten
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)| Aufruf | Was er tut |
|---|---|
| get_image | Die Originalbytes, als (bytes, content_type). Der Typ wird aus den Bytes ermittelt, nicht aus dem Namen, unter dem hochgeladen wurde. Eine Erinnerung ohne Bild löst einen Fehler aus, statt etwas Leeres zurückzugeben. |
| list_images | Eine Seite, neueste zuerst, plus count - die Gesamtzahl im Store, nicht die Seitengröße. Die Paginierung läuft über einen Cursor: Geben Sie next_before und next_skip_ids zurück. Beides ist nötig, weil Bilder denselben Zeitstempel haben können. |
| forget_image | Entfernt das Bild und behält den Text. Ein ohne Bildunterschrift gespeichertes Bild ist die Erinnerung, dort wird also auch die Erinnerung gelöscht. |
Übergeben Sie preview=True an forget_image, um memory_kept zu erhalten, ohne etwas zu ändern. iter_images übernimmt die Paginierung für Sie.
Was ein Bild kostet
Ein Bild wird in Token abgerechnet, derselben Einheit wie Text. Token = Fläche in Pixeln / 556,7. Ab 1.568 px an der langen Kante wird mit 1.568 px gerechnet, ein Bild mit 2.500 px kostet also so viel wie eines mit 1.568 px.
| Bild | Gezählt bei | 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 |
Obergrenze: 4.417 Token pro Bild. Wir reservieren diese Obergrenze vor dem Aufruf von Ihrem Guthaben und rechnen danach den gemessenen Wert ab, der nie höher ausfällt.
Wie viele zurückkommen
Standard 1, maximal 5 Bilder pro Antwort. Fünf Bilder sind knapp 20.000 Tokens.
| Feld | Was er tut |
|---|---|
| max_images | 0 bis 5. Bilder, die eine einzelne Antwort enthalten darf. Standard 1. 0 gibt nur Text zurück. Werte außerhalb des Bereichs werden abgelehnt, nicht begrenzt. |
verify
verify erlaubt einer Suche zusätzliche Durchläufe. Jeder Durchlauf schließt aus, was frühere Durchläufe zurückgegeben haben, sodass ein zweiter Durchlauf Erinnerungen erreicht, die der erste nicht erreicht hat.
Eine Ganzzahl 0-3 bei search und recall. Es ist die Anzahl der zusätzlichen Durchläufe, 3 erlaubt also vier Abrufe. Standard 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)In dieser Schleife läuft kein Sprachmodell
Die Durchläufe führen die bereits zurückgegebenen IDs mit; die Engine schließt sie aus und sucht darüber hinaus. Die Anfrage wird nicht umformuliert, die Ergebnisse sind für eine gegebene Anfrage also deterministisch, und es sind keine Modell-Zugangsdaten beteiligt. Ihr Code entscheidet, ob ein weiterer Durchlauf aufgewendet wird.
Was Sie senden und was zurückkommt
| Aufruf | Was er tut |
|---|---|
| verify | 0 bis 3. Zulässige zusätzliche Durchläufe. Werte außerhalb des Bereichs werden mit einem 400 abgelehnt, statt stillschweigend begrenzt zu werden. |
| verify_used | Wie viele zusätzliche Durchläufe tatsächlich ausgeführt wurden. Kann niedriger sein als angefordert. |
Die Durchläufe brechen vorzeitig ab, sobald einer nichts Neues zurückgibt; nicht genutzte Durchläufe werden nicht berechnet. Schlägt ein späterer Durchlauf fehl, werden die bis dahin gesammelten Ergebnisse zurückgegeben.
MCP - Gedächtnis für KI-Tools
Der Kern von WOS sind API und SDKs. Der MCP-Server ist ein Add-on darüber: dasselbe Gedächtnis, eingesteckt in Tools, die du nicht gebaut hast - Claude Code, Claude Desktop, Cursor.
Eine Installationszeile gibt dem Agenten neun Gedächtnis-Tools, die er selbstständig nutzt. Und weil das Gedächtnis im Konto lebt, erinnert jedes andere Tool, was eines schreibt - auch Agenten, die du mit dem SDK baust.
Was damit möglich ist
- Ein Claude Code, der sich an dein Projekt erinnert. Entscheidungen, Bugfixes, Vorlieben - in der nächsten Sitzung abgerufen, ohne etwas neu zu erklären.
- In ChatGPT anfangen, in Claude weitermachen. Gleicher Store, gleiches Gedächtnis - das Gespräch wechselt das Tool, statt neu zu starten.
- Dein eigener Agent bleibt im Kreis. Was Claude Code lernt, ruft ein SDK-Agent ab - und was dein Agent speichert, erinnert Claude Code zurück.
Läuft in Claude Code, Claude Desktop, Cursor, Windsurf und jedem MCP-Host. ChatGPT erreicht dasselbe Gedächtnis über Actions plus die OpenAPI-Spec.
Installation
claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcpDer Agent bekommt neun Tools - recall · remember · search · update · forget · list_memories · engram · stats · create_store - jedes so beschrieben, dass er selbst weiß, wann er sie nutzt.
OpenAPI-Spec
Die vollständige maschinenlesbare Karte der API - jeder Endpoint, Request, Response und Fehler.
OpenAPI ist das Industriestandard-Format, um eine HTTP-API in einer maschinenlesbaren Datei zu beschreiben.
https://api.wontopos.com/openapi.jsonWas damit möglich ist
Postman: File → Import → URL einfügen - alle Endpoints erscheinen als klickbare Collection. ChatGPT: GPT erstellen, Action hinzufügen, dieselbe URL einfügen. Codegen: openapi-generator -i .../openapi.json -g go baut einen Client in einer Sprache, die wir nicht ausliefern.
In Postman importieren, einen Client in einer Sprache generieren, die wir nicht ausliefern, ChatGPT Actions anbinden oder Contract-Checks in CI fahren. Ein Test pinnt sie an die echten Routen - sie kann nicht driften.
llms.txt
Die ganze API auf einer Textseite, die eine KI lesen kann.
llms.txt ist eine Web-Konvention: eine Klartextseite an der Site-Root, die einer KI alles Nötige über ein Produkt sagt.
https://wontopos.com/llms.txtIn die IDE oder den Coding-Agenten legen - er weiß sofort, wie man auf WOS baut: Auth, Endpoints, Patterns, Fehler. Mit jedem Release aktualisiert.
Dieselben Fakten wie die OpenAPI-Spec, anderes Publikum: Die Spec ist präzise Struktur für Tools, diese Datei ist Prosa, die eine KI (oder ein Mensch) in einem Zug liest. Beide aktualisieren sich mit jedem Release.
Dein Gedächtnis in jedem KI-Tool
Ein Befehl gibt Claude Code, Claude Desktop, Cursor oder jedem MCP-Host ein Langzeitgedächtnis auf Basis deines WOS-Kontos. Kein Integrationscode: Der Agent bekommt neun Gedächtnis-Tools und entscheidet selbst, wann er sie nutzt.
Installation
Claude Code, eine Zeile (Key vorher in der Konsole erstellen):
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 →
Optionale Env: WONTOPOS_USER_ID setzt den Standard-Store, WONTOPOS_MODEL die Engine, WONTOPOS_BASE_URL ein Self-Hosted-Deployment. WONTOPOS_READ_ONLY=1 schaltet auf Nur-Lesen (nur Recall/Suche/Liste).
Bevor du einen Store teilst
- Nutze einen eigenen Key. Keys tragen ihren Workspace: Ein nur für MCP erstellter Key begrenzt, was verbundene Tools überhaupt erreichen - und lässt sich in der Konsole rotieren, ohne die App-Keys anzufassen.
- Nur-Lese-Modus.
WONTOPOS_READ_ONLY=1registriert gar keine Schreib-Tools: Der Agent kann erinnern, suchen, Erinnerungen auflisten, Engrams ausführen und Statistiken lesen, aber nichts speichern, aktualisieren, vergessen oder löschen. Richtig für Agenten, die Gedächtnis nutzen, nicht besitzen sollen. - Tool-Bestätigung anlassen. MCP-Hosts fragen standardmäßig vor jedem Tool-Lauf - lass das besonders für
forgetan, denn Löschungen teilen sich alle Tools des Stores. - Alles Gespeicherte kann jedes Tool mit dem Key abrufen. Speichere niemals Geheimnisse - API-Keys, Passwörter - als Erinnerungen.
- Erinnerte Inhalte sind Daten, keine Anweisungen. Die Tool-Beschreibungen sagen das dem Agenten explizit. Speichere trotzdem keinen nicht vertrauenswürdigen Fremdtext in einem Store, dem ein autonomer Agent folgt.
- Löschungen werden ebenfalls geteilt. Ein forget oder delete_all aus einem Tool löscht für alle.
- "me" meint den Agenten, der in den Store schreibt. Teilen sich mehrere Agenten einen Store, verschmelzen ihre "me"-Stimmen. Gib jedem Agenten einen eigenen Store (
WONTOPOS_USER_ID) für getrennte Identitäten. - Ein Konto zahlt. Alle verbundenen Tools ziehen vom selben Guthaben und Rate-Limit.
Dann einfach reden
Der Agent ruft das Tool remember auf. Dauerhaft im Store gespeichert: Das Sitzungsende ändert nichts.
Eine neue Sitzung hat null Verlauf. Der Agent ruft recall auf und antwortet aus dem Gedächtnis: freitags.
Sag einfach
- "Dieses Repo nutzt pnpm, merk dir das" →
rememberspeichert es; die nächste Sitzung weiß es schon. - "Welches Fehlerformat hatten wir letzte Woche festgelegt?" →
recallholt die Entscheidung zurück in den Kontext. - „Die Deadline ist jetzt Freitag.“ → der Agent merkt, dass es dem widerspricht, was er abgerufen hat, und ruft
updateauf, um die Erinnerung an Ort und Stelle zu korrigieren. - "Das ist falsch, vergiss es" → der Agent findet die Memory-Id und ruft
forgetauf - dein Host fragt vorher nach. - „Was weißt du über mich?“ →
list_memoriesblättert durch alles Gespeicherte, damit der Agent antworten oder aufräumen kann.
Nichts Besonderes zu formulieren - das sind normale Sätze, keine Befehle. Der Agent liest die Tool-Beschreibungen und wählt selbst.
Die neun Tools
recall- Kontext in einem Aufruf: jüngste Turns plus relevante Langzeiterinnerungen. Die Tool-Beschreibung weist den Agenten an, es zuerst aufzurufen, wenn Vergangenes zählt.remember- Speichert einen dauerhaften Fakt oder Beschluss.speaker: "me"markiert die eigenen Worte des Agenten; ein registrierter Name, wer es gesagt hat.search- Semantische Suche, mit optionalemspeaker-Filter je Person — undfilters, um sie zeitlich oder thematisch einzugrenzen ("Was haben wir im Juni entschieden?"), die eine Achse, die Bedeutung allein nicht eingrenzen kann.update- Ersetzt eine Erinnerung, deren Fakt sich geändert hat, und behält die Spur, statt sie zu löschen.forget- Löscht eine Erinnerung per id.list_memories- Blättert durch alles Gespeicherte, damit der Agent "Was weißt du über mich?" beantworten oder aufräumen kann.engram- Führt eine eingebaute Multi-Hop-Pipeline aus (deep_recall, timeline, gather), wenn eine einzelne Suche nicht reicht.stats- Wie viel in einem Store liegt - nützlich vor dem Aufräumen und um zu bestätigen, dass ein Schreibvorgang angekommen ist.create_store- Stores sind explizit: einer pro Endnutzer, Projekt oder Agent.
SDK oder MCP?
- Das SDK gehört in eine App, die du schreibst. Dein Code entscheidet exakt, wann gespeichert und was erinnert wird - deterministisch, typisiert, versioniert. Du baust ein Produkt? SDK.
- MCP steckt man in ein KI-Tool, das du nicht geschrieben hast. Der Agent entscheidet anhand der Tool-Beschreibungen, wann er Gedächtnis nutzt - null Code. Richtig für Claude Code, Claude Desktop, Cursor oder um einem fertigen Assistenten Gedächtnis zu geben.
Darunter dieselbe API, dieselben Stores - eine SDK-App und eine Claude-Code-Sitzung über MCP teilen ein Gedächtnis. Man wählt pro Oberfläche, nicht entweder-oder.
Ein Gedächtnis über alle Tools hinweg
Das Gedächtnis gehört dem Konto, nicht dem Tool. Derselbe Store, aus ChatGPT (Actions plus OpenAPI-Spec) geschrieben, wird in Claude Code und in deinen eigenen Agenten erinnert, und umgekehrt: Ein Gespräch, das in einem Tool beginnt, geht in einem anderen weiter.
Und weil es ein Store ist, kannst du Claude Code verlassen und dort weiterreden, wo du baust: Ein SDK-Agent mit demselben Key und Store erinnert alles, was Claude Code gerade gelernt hat - und was dein Agent speichert, erinnert Claude Code in der nächsten Sitzung.
npx wontopos-mcp): Bei dieser Methode bleibt dein Key in deiner Umgebung und wird nie als Teil einer MCP-Sitzung an uns gesendet. Es umschließt das TypeScript-SDK, sodass automatische Wiederholungen, Redirect-Verweigerung und Key-Maskierung unverändert gelten.Claude Code
Der Hauptweg: ein Befehl im Terminal, und jede Sitzung startet mit Gedächtnis.
- Erstelle einen API-Key in der Konsole. Der Key trägt seinen Workspace: ein Key = ein Gedächtnisraum.
- Registriere den Server.
--scope usermacht ihn in jedem Projekt verfügbar; ohne sieht ihn nur das aktuelle Projekt. - Prüfen:
/mcpin Claude Code ausführen -wontopossollte mit neun Tools gelistet sein. - Mach es automatisch: eine Zeile in deiner
CLAUDE.md- "wenn Vergangenes zählt, zuerst wontopos recall aufrufen" - und jede Sitzung startet ungefragt mit Gedächtnis.
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
Füge den Block unten in claude_desktop_config.json ein (Einstellungen → Developer → Edit Config), starte die App neu - die neun Tools erscheinen. Hinweis: claude.ai im Web und mobil braucht einen Remote-MCP-Server, den WOS noch nicht anbietet - die Desktop-App ist der unterstützte Weg.
# claude_desktop_config.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Cursor
Füge den Block unten in ~/.cursor/mcp.json ein - oder nutze den Ein-Klick-Button - und starte Cursor neu. Der Agent übernimmt die neun Tools.
# ~/.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 (Copilot-Agentenmodus) liest MCP-Server aus .vscode/mcp.json im Projekt - füge den Block unten ein oder nutze den Ein-Klick-Button.
# .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) liest ~/.codeium/windsurf/mcp_config.json: Block unten einfügen und neu laden - dieselben neun Tools erscheinen.
# ~/.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
ChatGPTs MCP-Konnektoren akzeptieren nur Remote-Server; der unterstützte Weg ist heute ein Custom GPT mit einer Action: GPT erstellen, Action hinzufügen, die OpenAPI-Spec-URL unten einfügen und den API-Key als Auth-Header setzen. Dieses GPT ruft dann dasselbe Gedächtnis auf wie deine anderen Tools.
# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-KeyGleicher Store, gleiches Gedächtnis: Was ChatGPT über die Action speichert, erinnert Claude Code über MCP - und umgekehrt.
Gemini CLI
Gemini CLI liest MCP-Server aus ~/.gemini/settings.json: Block unten einfügen, CLI neu starten - dieselben neun Tools erscheinen auch dort.
# ~/.gemini/settings.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Python - jede Methode, drei Gruppen.
Schreiben, Lesen, Löschen. Jedes Beispiel unten wurde am 2026-08-01 gegen die Live-API ausgeführt; die Antworten sind wortwörtlich wiedergegeben.
pip install wontopos
from wontopos import Client mem = Client(api_key="wos-live-...") # or read from an env var
Modell wählen
Der API-Schlüssel bestimmt, welches Gedächtnis (Ihr Konto); das Modell bestimmt, welche Engine es liest. Alle Modelle teilen sich ein Gedächtnis, Sie können also mit einem speichern und mit einem anderen abrufen. Setzen Sie einen Standard am Client; überschreiben Sie einen einzelnen Aufruf, indem Sie ihm model= übergeben.
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
Der Katalog - die IDs, die Sie an model übergeben können, und ob sie jeweils live sind. Modelle mit memory: "shared" lesen denselben Store; "isolated" führt einen eigenen. Benötigt keinen API-Schlüssel.
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
Verbindung und Gültigkeit des API-Schlüssels in einer Zeile prüfen.
mem.ping() # True, or raises AuthenticationError / PaymentRequiredError
Der Katalog oben spiegelt stets die aktuell verfügbaren Modelle wider - übergeben Sie eine andere ID, erhalten Sie einen klaren Fehler. Neue Modelle erscheinen dort automatisch, sobald sie ausgeliefert werden.
Schreiben
add
Eine Erinnerung speichern. Beim Eingang eingebettet - kein LLM-Aufruf, Sie zahlen nur 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
Einen Gesprächszug (Nutzer + Assistent) zugleich ins Kurzzeit- und Langzeitgedächtnis speichern.
mem.add_turn("hi", "hello!", user_id="alice")
{"status": "ok"}speaker
Jede Erinnerung kann tragen, wer sie gesagt hat. Registriere eine Person einmal und übergib danach ihren Namen als speaker; "me" (die eigenen Worte des Assistenten) braucht nie eine Registrierung. Auch die Suche akzeptiert speaker, um nur die Worte einer Person zu holen.
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
Einen großen Textblock nachladen. Serverseitig zerlegt und eingebettet - ideal zum Import bestehender Historie.
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
Ein Fakt hat sich geändert. Die alte Erinnerung wird als ersetzt markiert (für die Historie behalten); die neue nimmt ihren Platz im Recall ein.
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"}Lesen
search
Semantische Suche, das Relevanteste zuerst. Reines Embedding - kein Keyword-Matching, jede Sprache findet also jede Erinnerung. Das SDK gibt das memories-Array direkt zurück; der rohe HTTP-Body ist unten abgebildet. Bei einem Modell mit eigener Spur (Scroll 1.2+) antwortet der Dienst in zwei Spuren und das SDK gibt beide zusammengeführt zurück - das Array kann also MEHR als max_results enthalten. Bemessen Sie Ihr Prompt-Fenster an dem, was Sie erhalten, nicht an der angeforderten Zahl.
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"
}]| Feld | Bedeutung |
|---|---|
| similarity | Rohe Embedding-Ähnlichkeit zu Ihrer Abfrage (0–1). |
| is_superseded | True, wenn dieser Fakt durch update() ersetzt wurde. |
| search_ms | Serverseitige Abrufzeit. |
recall
Ein Roundtrip liefert alles, was Ihr LLM braucht - fügen Sie das Ergebnis direkt in Ihren Prompt ein: ein begrenzter Kontext fester Größe, egal wie viel Sie gespeichert haben.
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
Jüngste Gesprächszüge (Kurzzeitgedächtnis), älteste zuerst.
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
Anzahl der Erinnerungen für einen Nutzer.
mem.stats("alice")
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Ruft eine einzelne Erinnerung per id ab - die id, die add oder list_memories zurückgegeben hat. Nur der gespeicherte Originaltext plus Metadaten, nie der Vektor. Eine id aus einem anderen Store oder eine gelöschte/invalidierte Erinnerung ergibt 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
Listet die Erinnerungen eines Stores auf - nur der gespeicherte Originaltext und seine Metadaten, nie der Vektor. Seitenweise per Cursor: den zurückgegebenen next_cursor für die nächste Seite mitgeben.
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
Alle Erinnerungen ohne Cursor-Verwaltung durchlaufen oder den ganzen Store auf einmal holen.
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
Löschen
delete
Eine einzelne Erinnerung per ID löschen.
mem.delete("alice", memory_id="576700aa-...")
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Alles für einen Nutzer löschen - ein Aufruf, DSGVO-bereit.
mem.delete_all("alice")
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Fehler und Zuverlässigkeit
Jeder Fehler ist typisiert - fange den konkreten (Rate-Limit, Auth, Zahlung) oder alle mit dem Basis-WosError.
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
Lies das verbleibende Kontingent nach jedem Aufruf und drossle, bevor du das Limit erreichst.
mem.search("...", user_id="alice") rl = mem.rate_limit # {"limit": 150, "remaining": 3, "reset": ...}
search_self
Beide Spuren aus einem Aufruf bei einem Selbstgedächtnis-Modell (Scroll 1.2+): was andere sagten und die EIGENEN Worte des Agenten - getrennt gehalten, damit der Leser nie verwechselt, wer was gesagt hat.
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
Frag den Dienst, welche Engramme und Delivery-Formen das gewählte Modell ausführen kann, statt Namen fest zu verdrahten, die veralten, sobald ein neues erscheint.
cat = mem.list_engrams() [e["name"] for e in cat["engrams"]] # ask, never hard-code
filters
Grenzt die Suche auf einen Teil des Stores ein. Wird vor dem Ranking angewendet, du bekommst also die besten Treffer innerhalb des Filters - kein nachträglich gefiltertes Top-N.
mem.search("what did we decide", user_id="alice", filters={ "categories": ["work"], "event_from": "2026-01-01", # when it HAPPENED })
idempotency_key
Macht das Wiederholen eines Schreibvorgangs sicher. Nutze ihn, wenn der Retry von dir kommt - ein Job, der abgebrochen und neu gestartet wurde, eine Queue, die erneut zustellt.
mem.add("she prefers tea", "alice", idempotency_key=f"import:{row.id}")
import:row-42), nie eine Konstante: ein Schlüssel, der für zwei verschiedene Schreibvorgänge wiederverwendet wird, spielt den ersten erneut ab - der zweite geht stillschweigend verloren. Format: 1-128 Zeichen aus [A-Za-z0-9._:-].with_timeout / with_retries
Stelle eine einzelne Aufrufstelle ein, ohne den bereits gebauten Client anzufassen: ein Klon mit längerem Timeout für einen großen Backfill oder mit abgeschalteten Retries innerhalb deiner eigenen Retry-Schleife.
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 - jede Methode, drei Gruppen.
Schreiben, Lesen, Löschen. Jedes Beispiel unten wurde am 2026-08-01 gegen die Live-API ausgeführt; die Antworten sind wortwörtlich wiedergegeben.
npm install wontopos
import { Client } from "wontopos"; const mem = new Client({ apiKey: "wos-live-..." });
Modell wählen
Der API-Schlüssel bestimmt, welches Gedächtnis (Ihr Konto); das Modell bestimmt, welche Engine es liest. Alle Modelle teilen sich ein Gedächtnis, Sie können also mit einem speichern und mit einem anderen abrufen. Setzen Sie einen Standard im Konstruktor; überschreiben Sie einen einzelnen Aufruf mit 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
Der Katalog - die IDs, die Sie an model übergeben können, und ob sie jeweils live sind. Modelle mit memory: "shared" lesen denselben Store; "isolated" führt einen eigenen. Benötigt keinen API-Schlüssel.
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
Verbindung und Gültigkeit des API-Schlüssels in einer Zeile prüfen.
await mem.ping(); // true, or throws AuthenticationError / PaymentRequiredError
Der Katalog oben spiegelt stets die aktuell verfügbaren Modelle wider - übergeben Sie eine andere ID, erhalten Sie einen klaren Fehler. Neue Modelle erscheinen dort automatisch, sobald sie ausgeliefert werden.
Schreiben
add
Eine Erinnerung speichern. Beim Eingang eingebettet - kein LLM-Aufruf, Sie zahlen nur 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
Einen Gesprächszug (Nutzer + Assistent) zugleich ins Kurzzeit- und Langzeitgedächtnis speichern.
await mem.addTurn("hi", "hello!", "alice");
{"status": "ok"}speaker
Jede Erinnerung kann tragen, wer sie gesagt hat. Registriere eine Person einmal und übergib danach ihren Namen als speaker; "me" (die eigenen Worte des Assistenten) braucht nie eine Registrierung. Auch die Suche akzeptiert speaker, um nur die Worte einer Person zu holen.
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
Einen großen Textblock nachladen. Serverseitig zerlegt und eingebettet - ideal zum Import bestehender Historie.
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
Ein Fakt hat sich geändert. Die alte Erinnerung wird als ersetzt markiert (für die Historie behalten); die neue nimmt ihren Platz im Recall ein.
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"}Lesen
search
Semantische Suche, das Relevanteste zuerst. Reines Embedding - kein Keyword-Matching, jede Sprache findet also jede Erinnerung. Das SDK gibt das memories-Array direkt zurück; der rohe HTTP-Body ist unten abgebildet. Bei einem Modell mit eigener Spur (Scroll 1.2+) antwortet der Dienst in zwei Spuren und das SDK gibt beide zusammengeführt zurück - das Array kann also MEHR als max_results enthalten. Bemessen Sie Ihr Prompt-Fenster an dem, was Sie erhalten, nicht an der angeforderten Zahl.
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"
}]| Feld | Bedeutung |
|---|---|
| similarity | Rohe Embedding-Ähnlichkeit zu Ihrer Abfrage (0–1). |
| is_superseded | True, wenn dieser Fakt durch update() ersetzt wurde. |
| search_ms | Serverseitige Abrufzeit. |
recall
Ein Roundtrip liefert alles, was Ihr LLM braucht - fügen Sie das Ergebnis direkt in Ihren Prompt ein: ein begrenzter Kontext fester Größe, egal wie viel Sie gespeichert haben.
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
Jüngste Gesprächszüge (Kurzzeitgedächtnis), älteste zuerst.
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
Anzahl der Erinnerungen für einen Nutzer.
await mem.stats("alice");
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Ruft eine einzelne Erinnerung per id ab - die id, die add oder list_memories zurückgegeben hat. Nur der gespeicherte Originaltext plus Metadaten, nie der Vektor. Eine id aus einem anderen Store oder eine gelöschte/invalidierte Erinnerung ergibt 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
Listet die Erinnerungen eines Stores auf - nur der gespeicherte Originaltext und seine Metadaten, nie der Vektor. Seitenweise per Cursor: den zurückgegebenen next_cursor für die nächste Seite mitgeben.
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
Alle Erinnerungen ohne Cursor-Verwaltung durchlaufen oder den ganzen Store auf einmal holen.
for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content); const everything = await mem.exportMemories("alice");
Löschen
delete
Eine einzelne Erinnerung per ID löschen.
await mem.delete("alice", "576700aa-...");
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}deleteAll
Alles für einen Nutzer löschen - ein Aufruf, DSGVO-bereit.
await mem.deleteAll("alice");
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Fehler und Zuverlässigkeit
Jeder Fehler ist typisiert - fange den konkreten (Rate-Limit, Auth, Zahlung) oder alle mit dem Basis-WosError.
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
Lies das verbleibende Kontingent nach jedem Aufruf und drossle, bevor du das Limit erreichst.
await mem.search("...", "alice"); const rl = mem.rateLimit; // { limit: 150, remaining: 3, reset: ... }
searchSelf
Beide Spuren aus einem Aufruf bei einem Selbstgedächtnis-Modell (Scroll 1.2+): was andere sagten und die EIGENEN Worte des Agenten - getrennt gehalten, damit der Leser nie verwechselt, wer was gesagt hat.
const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice"); // memories = what others said · self_memories = the agent's OWN words
listEngrams
Frag den Dienst, welche Engramme und Delivery-Formen das gewählte Modell ausführen kann, statt Namen fest zu verdrahten, die veralten, sobald ein neues erscheint.
const { engrams, forms } = await mem.listEngrams(); // ask, never hard-code
filters
Grenzt die Suche auf einen Teil des Stores ein. Wird vor dem Ranking angewendet, du bekommst also die besten Treffer innerhalb des Filters - kein nachträglich gefiltertes Top-N.
await mem.search("what did we decide", "alice", 10, { filters: { categories: ["work"], event_from: "2026-01-01" }, // when it HAPPENED });
idempotencyKey
Macht das Wiederholen eines Schreibvorgangs sicher. Nutze ihn, wenn der Retry von dir kommt - ein Job, der abgebrochen und neu gestartet wurde, eine Queue, die erneut zustellt.
await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
import:row-42), nie eine Konstante: ein Schlüssel, der für zwei verschiedene Schreibvorgänge wiederverwendet wird, spielt den ersten erneut ab - der zweite geht stillschweigend verloren. Format: 1-128 Zeichen aus [A-Za-z0-9._:-].withTimeout / withRetries
Stelle eine einzelne Aufrufstelle ein, ohne den bereits gebauten Client anzufassen: ein Klon mit längerem Timeout für einen großen Backfill oder mit abgeschalteten Retries innerhalb deiner eigenen Retry-Schleife.
await mem.withTimeout(120_000).addBulk(bigBlob, "alice"); // this slow call only await mem.withRetries(0).add("...", "alice"); // you retry, not the SDK
Rust - jede Methode, drei Gruppen.
Schreiben, Lesen, Löschen. Jedes Beispiel unten wurde am 2026-08-01 gegen die Live-API ausgeführt; die Antworten sind wortwörtlich wiedergegeben.
cargo add wontopos
use wontopos::Client; let mem = Client::new("wos-live-...");
Modell wählen
Der API-Schlüssel bestimmt, welches Gedächtnis (Ihr Konto); das Modell bestimmt, welche Engine es liest. Alle Modelle teilen sich ein Gedächtnis, Sie können also mit einem speichern und mit einem anderen abrufen. Setzen Sie einen Standard mit with_model(); verketten Sie es erneut, um einen einzelnen Aufruf zu überschreiben.
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
Der Katalog - die IDs, die Sie an with_model übergeben können, und ob sie jeweils live sind. Modelle mit memory: "shared" lesen denselben Store; "isolated" führt einen eigenen. Benötigt keinen API-Schlüssel.
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
Verbindung und Gültigkeit des API-Schlüssels in einer Zeile prüfen.
mem.ping().await?; // Ok(true), or Err whose .kind() is Auth / PaymentRequired
Der Katalog oben spiegelt stets die aktuell verfügbaren Modelle wider - übergeben Sie eine andere ID, erhalten Sie einen klaren Fehler. Neue Modelle erscheinen dort automatisch, sobald sie ausgeliefert werden.
Schreiben
add
Eine Erinnerung speichern. Beim Eingang eingebettet - kein LLM-Aufruf, Sie zahlen nur 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
Einen Gesprächszug (Nutzer + Assistent) zugleich ins Kurzzeit- und Langzeitgedächtnis speichern.
mem.add_turn("hi", "hello!", "alice").await?;
{"status": "ok"}speaker
Jede Erinnerung kann tragen, wer sie gesagt hat. Registriere eine Person einmal und übergib danach ihren Namen als speaker; "me" (die eigenen Worte des Assistenten) braucht nie eine Registrierung. Auch die Suche akzeptiert speaker, um nur die Worte einer Person zu holen.
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
Einen großen Textblock nachladen. Serverseitig zerlegt und eingebettet - ideal zum Import bestehender Historie.
mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
Ein Fakt hat sich geändert. Die alte Erinnerung wird als ersetzt markiert (für die Historie behalten); die neue nimmt ihren Platz im Recall ein.
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"}Lesen
search
Semantische Suche, das Relevanteste zuerst. Reines Embedding - kein Keyword-Matching, jede Sprache findet also jede Erinnerung. Das SDK gibt das memories-Array direkt zurück; der rohe HTTP-Body ist unten abgebildet. Bei einem Modell mit eigener Spur (Scroll 1.2+) antwortet der Dienst in zwei Spuren und das SDK gibt beide zusammengeführt zurück - das Array kann also MEHR als max_results enthalten. Bemessen Sie Ihr Prompt-Fenster an dem, was Sie erhalten, nicht an der angeforderten Zahl.
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"
}]| Feld | Bedeutung |
|---|---|
| similarity | Rohe Embedding-Ähnlichkeit zu Ihrer Abfrage (0–1). |
| is_superseded | True, wenn dieser Fakt durch update() ersetzt wurde. |
| search_ms | Serverseitige Abrufzeit. |
recall
Ein Roundtrip liefert alles, was Ihr LLM braucht - fügen Sie das Ergebnis direkt in Ihren Prompt ein: ein begrenzter Kontext fester Größe, egal wie viel Sie gespeichert haben.
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
Jüngste Gesprächszüge (Kurzzeitgedächtnis), älteste zuerst.
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
Anzahl der Erinnerungen für einen Nutzer.
mem.stats("alice").await?;
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Ruft eine einzelne Erinnerung per id ab - die id, die add oder list_memories zurückgegeben hat. Nur der gespeicherte Originaltext plus Metadaten, nie der Vektor. Eine id aus einem anderen Store oder eine gelöschte/invalidierte Erinnerung ergibt 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
Listet die Erinnerungen eines Stores auf - nur der gespeicherte Originaltext und seine Metadaten, nie der Vektor. Seitenweise per Cursor: den zurückgegebenen next_cursor für die nächste Seite mitgeben.
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
Alle Erinnerungen ohne Cursor-Verwaltung durchlaufen oder den ganzen Store auf einmal holen.
let all = mem.list_all_memories("alice").await?; // every page, collected
Löschen
delete
Eine einzelne Erinnerung per ID löschen.
mem.delete("alice", "576700aa-...").await?;
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Alles für einen Nutzer löschen - ein Aufruf, DSGVO-bereit.
mem.delete_all("alice").await?;
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Fehler und Zuverlässigkeit
Jeder Fehler ist typisiert - fange den konkreten (Rate-Limit, Auth, Zahlung) oder alle mit dem Basis-WosError.
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
Lies das verbleibende Kontingent nach jedem Aufruf und drossle, bevor du das Limit erreichst.
mem.search("...", "alice", 10).await?; let rl = mem.rate_limit(); // Some(RateLimit { remaining: Some(3), .. })
search_self
Beide Spuren aus einem Aufruf bei einem Selbstgedächtnis-Modell (Scroll 1.2+): was andere sagten und die EIGENEN Worte des Agenten - getrennt gehalten, damit der Leser nie verwechselt, wer was gesagt hat.
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
Frag den Dienst, welche Engramme und Delivery-Formen das gewählte Modell ausführen kann, statt Namen fest zu verdrahten, die veralten, sobald ein neues erscheint.
let cat = mem.list_engrams().await?; // ask, never hard-code
filters
Grenzt die Suche auf einen Teil des Stores ein. Wird vor dem Ranking angewendet, du bekommst also die besten Treffer innerhalb des Filters - kein nachträglich gefiltertes Top-N.
mem.search_with("what did we decide", "alice", 10, json!({"filters": { "categories": ["work"], "event_from": "2026-01-01" // when it HAPPENED }})).await?;
add_idempotent
Macht das Wiederholen eines Schreibvorgangs sicher. Nutze ihn, wenn der Retry von dir kommt - ein Job, der abgebrochen und neu gestartet wurde, eine Queue, die erneut zustellt.
mem.add_idempotent("she prefers tea", "alice", json!({}), &format!("import:{}", row.id)).await?;
import:row-42), nie eine Konstante: ein Schlüssel, der für zwei verschiedene Schreibvorgänge wiederverwendet wird, spielt den ersten erneut ab - der zweite geht stillschweigend verloren. Format: 1-128 Zeichen aus [A-Za-z0-9._:-].with_timeout / with_retries
Stelle eine einzelne Aufrufstelle ein, ohne den bereits gebauten Client anzufassen: ein Klon mit längerem Timeout für einen großen Backfill oder mit abgeschalteten Retries innerhalb deiner eigenen Retry-Schleife.
mem.with_timeout(120).add_bulk(big_blob, "alice", "general").await?; mem.with_retries(0).add("...", "alice", json!({})).await?;
curl - keine Installation, dieselben Methoden.
Kein SDK zu installieren - jeder HTTP-Client funktioniert. Setzen Sie Ihren Schlüssel einmal und rufen Sie dieselben Endpunkte auf, die die SDKs kapseln. Basis-URL https://api.wontopos.com, Authentifizierung über X-API-Key, JSON rein und raus.
# set your key once (never hard-code it) export WOS_API_KEY="wos-live-..."
Schreiben
store
Eine Erinnerung speichern. Beim Eingang eingebettet - kein LLM-Aufruf.
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
Einen Gesprächszug (Nutzer + Assistent) zugleich ins Kurzzeit- und Langzeitgedächtnis speichern.
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
Jede Erinnerung kann tragen, wer sie gesagt hat. Registriere eine Person einmal und übergib danach ihren Namen als speaker; "me" (die eigenen Worte des Assistenten) braucht nie eine Registrierung. Auch die Suche akzeptiert speaker, um nur die Worte einer Person zu holen.
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
Ein Fakt hat sich geändert - die alte Erinnerung wird als ersetzt markiert, die neue nimmt ihren Platz im Recall ein.
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
Eine lange Historie in einem Aufruf nachladen - serverseitig aufgeteilt und eingebettet.
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
Macht das Wiederholen eines Schreibvorgangs sicher. Nutze ihn, wenn der Retry von dir kommt - ein Job, der abgebrochen und neu gestartet wurde, eine Queue, die erneut zustellt.
# 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), nie eine Konstante: ein Schlüssel, der für zwei verschiedene Schreibvorgänge wiederverwendet wird, spielt den ersten erneut ab - der zweite geht stillschweigend verloren. Format: 1-128 Zeichen aus [A-Za-z0-9._:-].Lesen
search
Semantische Suche, das Relevanteste zuerst. Reines Embedding - jede Sprache findet jede Erinnerung.
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
Grenzt die Suche auf einen Teil des Stores ein. Wird vor dem Ranking angewendet, du bekommst also die besten Treffer innerhalb des Filters - kein nachträglich gefiltertes Top-N.
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
Liest eine Erinnerung anhand der id, die store oder list zurückgab - Originaltext und Metadaten, keine Vektoren.
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
Blättert Cursor für Cursor durch alles in einem Store - zum Durchsehen oder Exportieren.
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
Ein Roundtrip liefert Kurzzeit + Langzeit + Kontext + eine Instruktion. Fügen Sie es direkt in Ihren Prompt ein.
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..."}Löschen
forget
Eine Erinnerung per ID löschen - oder die ID weglassen, um alles für einen Nutzer zu löschen (DSGVO).
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"}Jeder Endpunkt + Body-Felder →
Varianten nur in Rust
Python und TypeScript nehmen diese als optionale Argumente entgegen. Stable Rust kennt keine Standard- oder Schlüsselwortargumente, daher ist jede Variante eine eigene Methode und kein Builder, den Sie abschließen müssen.
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 wird zusätzlich als iter_images exportiert, passend zu dem Namen, den die beiden anderen SDKs verwenden - wer aus jener Dokumentation kommt, tippt zuerst diesen Namen.
Engrams
Aufrufbare Recall-Werkzeuge, die Ihr Modell einsetzen kann - jedes eine eigene Abrufstrategie über dasselbe Gedächtnis. Nutzen Sie eines oder führen Sie mehrere zugleich aus.
Regelmäßig erscheinen weitere Engramme - diese Liste wächst.
Memoir & Archive Scroll 1.2+
Dies ist eine Auslieferungs-Form, kein aufrufbares Werkzeug. Ab Scroll 1.2 wählen Sie sie pro Aufruf - form: "memoir" oder form: "archive" - und dieser Recall, eine einfache Suche eingeschlossen, kommt mit entsprechend geschriebener Zeit zurück.
Time_awareness Scroll 1.2+
Eine Auslieferungsform - pro Aufruf gewählt. Übergeben Sie form - memoir oder archive - bei einem beliebigen Aufruf eines formfähigen Modells (ab Scroll 1.2), und die Antwort kommt entsprechend gerendert zurück: eine einfache Suche, ein Recall oder jedes Engramm. In den SDKs ist es ein form-Feld wie tz; über HTTP der Header X-WOS-Form. Ein Memoir liest sich, wie ein Mensch sich erinnert; ein Archive führt ein exaktes Protokoll - der Unterschied zeigt sich am deutlichsten darin, wie beide die Zeit schreiben.
Memoir
form: "memoir"Erzählt, was geschah und wie ein Moment zum nächsten führte, mit dem weichen Zeitgefühl menschlicher Erinnerung - liest sich als Erlebnis, nicht als Liste.
Archive
form: "archive"Liefert Treffer als exakte Datensätze - präzise verstrichene Zeit und absolute Anker, so strukturiert, dass ein Modell sie direkt ablesen kann.
store-/add-Aufruf unter einer user_id (diese user_id ist der Store dieser Person). Zuerst speichern; danach kommt jeder Recall - die einfache Suche unten eingeschlossen - mit Zeitmarkierung zurück. Siehe Schnellstart zum Speichern.# 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 ist der UTC-Versatz des Aufrufers in Stunden - damit "heute Morgen" und die 4-Uhr-Tagesgrenze in dessen Ortszeit landen. Weglassen für UTC; über HTTP ist es der X-WOS-Timezone-Header. Grob nach Region: US-Ostküste -5, US-Zentral -6, US-Westküste -8 · UK / Lissabon 0 · Mitteleuropa +1 · Osteuropa +2 · Indien +5.5 · China / Singapur +8 · Korea / Japan +9 · Sydney +10. (Normalzeit - die Sommerzeit verschiebt manche Regionen um +1; übergeben Sie, was für Ihre Nutzer tatsächlich gilt.)
Dieselbe Suche, zwei Formen - die Erinnerungen sind identisch, nur time ändert sich:
{ "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)" }
] }| Verstrichen | 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 Std. | a couple hours ago | 2 hours ago, at 13:10 |
| 8 Std. | this morning | 8 hours ago, at 07:10 |
| gestern Nachm. | yesterday afternoon | yesterday at 14:00 |
| letzte Nacht | last night | 17 hours ago, at 22:00 |
| 2 Tage | a couple days ago | 2 days ago (Tue 15:10) |
| 6 Tage | several days ago | 6 days ago (Fri 15:10) |
| 9 Tage | about a week ago | last week (Jun 16) |
| 16 Tage | a couple weeks ago | 2 weeks ago (Jun 09) |
| 35 Tage | about a month ago | last month (May 21) |
| 60 Tage | a couple months ago | 2 months ago (Apr 2026) |
| 180 Tage | about half a year ago | 6 months ago (Dec 2025) |
| 380 Tage | about a year ago | last year (Jun 2025) |
| 800 Tage | a couple years ago | 2 years ago (Apr 2024) |
| 1500 Tage | about 4 years ago | 4 years ago (May 2022) |
Jeder Wert oben ist die tatsächliche Ausgabe des Renderers. Betrachten Sie die beiden "gestern"-Zeilen: Ein Memoir trennt den Nachmittag von der letzten Nacht - ein Tag ist ein Schlaf - während ein Archive eine einzelne Uhrzeit schreibt und keine Tag-Nacht-Grenze zieht.
Wie jeder Modus die Zeit liest
Memoir - so, wie Menschen es tatsächlich sagen. Jüngste Momente bleiben recht scharf (etwa 15 Minuten, eine halbe Stunde), dann weitet sich die Formulierung, je weiter man zurückgeht - ein paar Wochen, etwa ein halbes Jahr, ein paar Jahre - so, wie sich Erinnerung selbst mit der Entfernung lockert. Innerhalb eines Tages weicht die Uhr einem Orientierungspunkt: heute Morgen, letzte Nacht, gestern Nachmittag. Und ein Tag ist ein Schlaf, kein Kalendersprung: Die Grenze liegt bei etwa 4 Uhr Ortszeit, sodass eine späte Nacht noch als derselbe Abend gilt, nicht schon als morgen.
Archive - präzise, stets mit Anker. Jede Zeile trägt die exakte verstrichene Zeit plus eine absolute Referenz, mit der ein Modell rechnen kann, und der Anker wird enger, je näher es rückt: eine Uhrzeit für heute (vor 8 Stunden, um 07:10), Wochentag und Uhrzeit für diese Woche (vor 2 Tagen (Di 15:10)), ein Datum für diesen Monat (letzte Woche (16. Jun)), Monat und Jahr darüber hinaus (vor 6 Monaten (Dez 2025)). Nie vage, nie falsch.
deep_recall
Multi-Hop-Recall. Sucht nach Ihrer Abfrage, nimmt dann den besten Treffer und sucht erneut nach dessen Inhalt - und holt so verknüpften Kontext ein, den eine einzelne Suche übersehen würde. Am stärksten, wenn Erinnerungen aufeinander verweisen (eine Person → ihre Projekte → Details). Liefert bis zu ~12.
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 (Eingabe + Ausgabe), gezählt mit demselben Tokenizer wie der Rest der API; keine versteckte Gebühr pro Engramm. Mehrere zugleich nötig? Rufen Sie sie parallel auf - jedes Engramm ist eine unabhängige Anfrage.timeline
Zeitlich geordneter Recall. Liefert Erinnerungen neueste zuerst, sortiert nach dem Zeitpunkt des Ereignisses, nicht nach Relevanz. Für "Wann war X", Verlauf und Reihenfolgefragen. Liefert bis zu 15.
events = mem.engram("timeline", "project milestones", user_id="alice"){ "engram": "timeline", "hops": 1, "count": 15,
"memories": [ ... ],
"usage": { "input_tokens": 100, "output_tokens": 736 } }usage (Eingabe + Ausgabe), gezählt mit demselben Tokenizer wie der Rest der API; keine versteckte Gebühr pro Engramm. Mehrere zugleich nötig? Rufen Sie sie parallel auf - jedes Engramm ist eine unabhängige Anfrage.gather
Breites Sammeln. Sucht und erweitert dann um die besten drei Treffer herum - ein weiteres Netz als deep_recall. Damit holen Sie alles zu einer Person, einem Projekt oder einem Thema in einem Aufruf ein. Liefert bis zu ~18.
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 (Eingabe + Ausgabe), gezählt mit demselben Tokenizer wie der Rest der API; keine versteckte Gebühr pro Engramm. Mehrere zugleich nötig? Rufen Sie sie parallel auf - jedes Engramm ist eine unabhängige Anfrage.equilibrium
Driftkorrektur. Semantische Suche verengt sich im Verlauf einer Sitzung: die Anfrage trägt den aktuellen Zustand, holt Erinnerungen desselben Zustands und der nächste Zug neigt sich weiter dorthin. Dieses Engram weitet das Ergebnis entlang dreier Achsen, die die Anfrage nicht kontrolliert: Streuung über die Zeit, Assoziation weg von der Anfrage und der inhaltlich tragende Teil des Speichers. Nutzen Sie es, wenn Antworten kreisen oder flach werden. Für einen konkreten Fakt passen deep_recall oder gather besser, die näher an der Anfrage bleiben. Liefert bis zu 12.
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 (Eingabe + Ausgabe), gezählt mit demselben Tokenizer wie der Rest der API; keine versteckte Gebühr pro Engramm. Mehrere zugleich nötig? Rufen Sie sie parallel auf - jedes Engramm ist eine unabhängige Anfrage.tone_stabilizer
Die eigene Stimme. Lange Sitzungen ziehen einen Assistenten aus seiner Tonlage: Antworten werden länger, geraten zum Bericht oder nehmen die Stimmung der letzten Strecke an. Gewöhnliches Selbst-Recall verschlimmert das, weil es den aktuellen Zustand trifft und die neuesten Zeilen zurückgibt, als wären sie der Charakter. Dieses Engram liefert stattdessen die eigenen Worte von vor dieser Strecke. Es braucht Beiträge, die mit speaker me gespeichert wurden; ohne solche gibt es nichts zurück statt zu raten. Liefert bis zu 10.
# 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 (Eingabe + Ausgabe), gezählt mit demselben Tokenizer wie der Rest der API; keine versteckte Gebühr pro Engramm. Mehrere zugleich nötig? Rufen Sie sie parallel auf - jedes Engramm ist eine unabhängige Anfrage.Jeder Endpunkt, eine Basis-URL.
Kein SDK erforderlich - jeder HTTP-Client funktioniert. Basis-URL https://api.wontopos.com, Authentifizierung über den X-API-Key-Header, JSON rein und raus. Memory-Operationen sind POST; die Verwaltung von Stores nutzt POST / GET / DELETE auf /collection. Ein Store muss zuerst existieren (siehe Stores), sonst liefern Operationen im Store 404.
Header
| Header | Wozu er dient |
|---|---|
| X-API-Key | Bei jedem Aufruf erforderlich. Ihr Schlüssel, in der Konsole ausgestellt. |
| X-WOS-Model | Optional. Legt fest, welche Engine antwortet. Lassen Sie ihn weg, wird der Standard des Kontos verwendet. GET /api/v1/models listet die Modelle auf, die Ihr Schlüssel wählen darf; ein Endpunkt, den eine ältere Engine nicht bedienen kann, antwortet mit 501 und nennt dieses Modell. |
| Idempotency-Key | Optional, bei Schreibvorgängen. Derselbe Schlüssel mit demselben Body spielt die erste Antwort erneut ab, statt erneut zu speichern - siehe den Hinweis unten. |
Endpunkt
| Endpunkt | Zweck | Body-Felder |
|---|---|---|
| POST /api/v1/memory/collection | Store anlegen | user_id |
| GET /api/v1/memory/collections | Ihre Stores auflisten | (keine) |
| DELETE /api/v1/memory/collection | Store + seine Erinnerungen löschen | user_id |
| /api/v1/memory/store | eine Erinnerung speichern | user_id · content · metadata? (event_date · speaker) · image? |
| /api/v1/memory/store-turn | einen Gesprächszug speichern | user_id · user_msg · assistant_msg |
| POST /api/v1/memory/speakers | Sprecher registrieren (explizit, bis 50) | user_id · speaker |
| GET /api/v1/memory/speakers | registrierte Sprecher + Zähler auflisten | user_id |
| DELETE /api/v1/memory/speakers | Sprecher abmelden (Erinnerungen bleiben) | user_id · speaker |
| /api/v1/memory/by-speaker | was eine Person gesagt hat, neueste zuerst („me“ = der Agent) | user_id · speaker · limit? · before? · skip_ids? |
| POST /api/v1/memory/image | die Originalbytes einer Bild-Erinnerung | user_id · memory_id |
| DELETE /api/v1/memory/image | das Bild entfernen, die Bildunterschrift behalten | user_id · memory_id · preview? |
| /api/v1/memory/images | die Bilder eines Stores, neueste zuerst (+ Gesamtzahl) | user_id · limit? · before? · skip_ids? |
| /api/v1/memory/lineage | die Änderungskette einer Erinnerung, älteste zuerst | user_id · memory_id |
| /api/v1/won/revisions | wie viel eines Stores überschrieben wurde. Kostenlos | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/revisions | derselbe Aufruf unter der Memory-Ebene. Kostenlos | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/bulk-store | einen Textblock nachladen | user_id · content · category? · timestamp? |
| /api/v1/memory/search | semantische Suche | user_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images? |
| /api/v1/memory/recall | Kurzzeit + Langzeit + Kontext | user_id · query · limit? · context_limit? |
| /api/v1/memory/get | eine Erinnerung per id | user_id · memory_id |
| /api/v1/memory/list | einen Store durchblättern | user_id · limit? · cursor? |
| /api/v1/memory/history | jüngste Gesprächszüge | user_id |
| /api/v1/memory/stats | Anzahl der Erinnerungen | user_id |
| /api/v1/memory/supersede | einen geänderten Fakt ersetzen | user_id · old_memory_id · new_content |
| /api/v1/memory/forget | eine (oder alle) löschen | user_id · memory_id? (weglassen = alle löschen) |
| GET /api/v1/engram | Engramme, die dieses Modell ausführen kann | (keine) |
| POST /api/v1/engram/run | ein Engramm ausführen | name · user_id · query · form? · tz? |
| GET /api/v1/models | verfügbare Modelle | (keine) |
Idempotency-Key-Header. Derselbe Schlüssel mit demselben Body spielt die erste Antwort erneut ab, statt erneut zu speichern (10 Minuten); derselbe Schlüssel mit einem anderen Body antwortet mit 422. Nur 2xx werden zwischengespeichert, ein fehlgeschlagener Aufruf ist also sofort wiederholbar.# 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)"}Dieselben Funktionen für alle.
Stufen erhöhen nur Ihre Limits.
Jede Stufe nutzt die vollständige Engine - dieselbe Recall-Qualität, dieselben Sprachen, jede Methode. Die Stufen steigen automatisch bis Stufe 5, während Ihre kumulierten Guthabenkäufe wachsen - ohne Antrag oder Vertriebsgespräch. Enterprise (Stufe 6) ist die einzige Ausnahme.
Ausgabenlimits
Jede Stufe begrenzt, wie viel Sie pro Kalendermonat ausgeben können. Sie steigen sofort auf, sobald Ihre kumulierten Guthabenkäufe die nächste Schwelle erreichen.
| Nutzungsstufe | Guthabenkauf | Monatliches Ausgabenlimit |
|---|---|---|
| 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 | Sprechen Sie mit uns | Kein Limit |
Rate-Limits
Rate-Limits gelten pro Konto - alle API-Schlüssel eines Kontos teilen sich ein Limit, das mit Ihrer Stufe skaliert. Bei Überschreitung kommt ein 429 mit einem retry-after-Header zurück; warten Sie gestaffelt (1s → 2s → 4s) und versuchen Sie es erneut. Jeder Endpunkt ist idempotenzfreundlich, Wiederholungen sind also sicher.
| Stufe | Anfragen pro Minute |
|---|---|
| Tier 1 | 150 |
| Tier 2 | 300 |
| Tier 3 | 600 |
| Tier 4 | 1,500 |
| Tier 5 | 3,000 |
| Tier 6 - Enterprise | Individuell |
Enterprise (Stufe 6) erhält individuelle Rate-Limits, ein SLA, dedizierten Support und optional eine Self-Host-Lizenz - sprechen Sie mit uns.
Kostenlose Aufrufe
Ein paar Endpunkte kosten überhaupt nichts - sie sind unter Won zusammengefasst. Statt eines Preises haben sie zwei Limits.
- 10 Anfragen pro Minute, pro Endpunkt. Jeder kostenlose Endpunkt hat seinen eigenen Bucket, das Verbrauchen des einen verbraucht also nicht den anderen.
- 300 Anfragen pro Stunde, gemeinsam. Alle kostenlosen Endpunkte schöpfen aus einem Stundenkontingent pro Konto.
Beides wird im normalen Betrieb nicht erreicht, und beides berührt die oben genannten kostenpflichtigen Limits nicht.
Wenn etwas schiefgeht.
Fehler kommen als JSON-Umschlag zurück, mit einem stabilen type, einer menschenlesbaren Meldung und einer request_id, die Sie uns bei der Meldung eines Problems mitschicken können.
{"type": "error", "error": {
"type": "authentication_error",
"message": "Invalid or revoked API key.",
"request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
}}| HTTP | Bedeutung | Was zu tun ist |
|---|---|---|
| 400 | Fehlerhafter Body (fehlendes Feld oder falscher Typ) | Die Meldung nennt das genaue Feld - korrigieren und erneut versuchen. |
| 401 | Ungültiger oder widerrufener API-Schlüssel | Prüfen Sie den Schlüssel; stellen Sie in der Konsole einen neuen aus. |
| 402 | Guthaben aufgebraucht, keine Karte hinterlegt oder Stufenlimit erreicht | Laden Sie in der Konsole Guthaben auf oder hinterlegen Sie eine Karte. Die Antwort enthält balance_cents und floor_cents, sodass Sie erkennen, was Sie gestoppt hat. |
| 404 | Erinnerung, Store oder Bild existiert nicht | Prüfen Sie die id. get_image antwortet auch dann mit 404, wenn die Erinnerung zwar existiert, aber kein Bild trägt. |
| 409 | Dieser Name ist bereits vergeben | Store- und Workspace-Namen sind innerhalb eines Kontos eindeutig - wählen Sie einen anderen. |
| 413 | Request-Body über 10MB | Base64 ist rund 33% größer als die Datei, die es kodiert - verkleinern Sie das Bild also, bevor Sie es kodieren. |
| 429 | Rate-Limit erreicht | Das SDK wiederholt diese Anfragen bereits für Sie, mit Backoff und Jitter, und beachtet dabei Retry-After. Wenn Sie einen solchen Fehler erhalten, sind die Wiederholungen aufgebraucht - senken Sie die Zahl gleichzeitiger Anfragen, statt eine eigene Schleife darum zu legen. |
| 501 | Die Engine dieses Modells implementiert diesen Endpunkt nicht | Bilder und Revisionsverlauf benötigen eine neuere Engine. GET /api/v1/models listet auf, welches Modell was bedient. |
| 5xx | Serverseitiges Problem | Mit Backoff erneut versuchen, aber nicht blind. Das SDK wiederholt einen 5xx hier nicht automatisch, denn jeder Aufruf dieser API ist ein POST und der Server hat Ihre Anfrage möglicherweise bereits gespeichert. Senden Sie sie mit einem Idempotenzschlüssel erneut, damit eine Wiederholung nicht doppelt schreiben kann, und geben Sie die request_id an, wenn Sie uns kontaktieren. |
Jeder Fehler ist ein WosError, und jeder Status hat zusätzlich eine eigene Klasse - BadRequestError, AuthenticationError, PaymentRequiredError, NotFoundError, ConflictError, RateLimitError, ServerError, APIConnectionError. Fangen Sie die ab, die Sie behandeln wollen, statt Zahlen zu vergleichen.
# 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
Manche Fehler erreichen uns nie. Der API-Schlüssel, die Store-id, der Idempotenzschlüssel und das Bild werden alle geprüft, bevor die Anfrage hinausgeht, und dabei wird ValueError oder TypeError ausgelöst - nicht WosError. Ein except WosError allein fängt sie nicht ab.
Rate-Limits gelten pro Konto, geteilt über alle Ihre Schlüssel, und skalieren mit Ihrer Stufe - siehe Nutzungsstufen. Die Nutzung Ihres Kontos sehen Sie in der Konsole.
lineage
Die Kette der Änderungen hinter einer Erinnerung, älteste zuerst. revisions gibt an, wie stark sich ein Store verändert hat; dies gibt an, was mit einer einzelnen Tatsache geschehen ist.
revisions ist dies ein normal abgerechneter Aufruf, da er Inhalte von Erinnerungen zurückgibt.Übergeben Sie die ID einer beliebigen Erinnerung in der Kette. Ersetzte Versionen werden aufbewahrt statt gelöscht, sodass eine Suche, die nur die aktuelle Tatsache zurückgibt, weiterhin zurückverfolgt werden kann.
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 }
] }| Feld | Was er tut |
|---|---|
| chain | Die Versionen, älteste zuerst. Jede enthält dieselben Felder wie eine Erinnerung, dazu die vier folgenden. |
| changed_at | Wann diese Version ersetzt wurde (RFC3339), oder null, solange sie noch gilt. |
| action | Was an diesem Kettenglied geschah - wie die Ersetzung zu dieser Version stand. |
| confidence | Wie sicher sich die Engine bei dieser Beziehung war, 0-1. |
| is_current | True für die eine Version, die noch gilt. Genau eine pro Kette. |
| truncated | True, wenn die Kette länger war, als der Dienst sie verfolgt. Die zurückgegebenen Schritte sind weiterhin die ältesten. |
Wofür es gedacht ist
Zwei Anwendungsfälle. Debugging: warum eine Erinnerung heute so lautet, wie sie lautet. Und einem Assistenten den Blick auf seine eigene Historie zu ermöglichen - eine dreimal korrigierte Tatsache ist eine andere Art von Tatsache als eine einmal geschriebene, und nur die Kette zeigt das.
Won ist für diejenigen gedacht, die die Erinnerung lesen.
Der größte Teil dieser API antwortet mit Erinnerung. Won antwortet über sie: wie viel von einem Store neu geschrieben wurde und wie weit man ihm vertrauen kann. Gedacht ist das für die lesende Seite, meist für den Assistenten, den Sie bauen, und nicht für die Person, von der die Erinnerungen handeln.
Wontopos ist Won + Topos, ein Ort, an dem Erinnerung wohnt. Won ist der Teil dieses Ortes, der über die Erinnerung Auskunft gibt, statt sie zurückzugeben. Diese Aufrufe sind kostenlos, nur lesend, und rühren den Abruf nie an: Das Fragen kostet Ihre Nutzer nichts und ändert nichts an dem, was gespeichert ist.
Was es heute umfasst
Heute ein Aufruf.
| Aufruf | Was er tut |
|---|---|
| POST /won/revisions | Wie viel von diesem Store seit dem Schreiben verändert wurde. Zwei Zahlen und zwei Sätze, die sie erläutern. |
Ein durchgerechnetes Beispiel
Verwenden Sie das Verhältnis, nicht die absolute Zahl. 3 von 40 und 30 von 40 erfordern eine unterschiedliche Behandlung.
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 und excludes werden als Sätze zurückgegeben, nicht als Flags, da der Aufrufer oft ein Modell ist. Löschungen werden nicht gezählt.
Preis und Limits
| Regel | Wert |
|---|---|
| Preis | Keiner. Kostenlose Aufrufe umgehen die Abrechnungsschranken - keine Token-Berechnung, keine Gebühr pro Anfrage und keine erfasste Nutzung. |
| Pro Minute | 10 pro Minute, pro Konto und pro Endpunkt. Die Minute eines Endpunkts zu verbrauchen, verbraucht nicht die eines anderen. |
| Pro Stunde | 300 pro Stunde, pro Konto, geteilt von allen kostenlosen Aufrufen. Dieses Limit ignoriert den Pfad, zusätzliche kostenlose Endpunkte erhöhen also nicht die Gesamtmenge, die ein Konto verbrauchen kann. |
| Gegenüber kostenpflichtigem Traffic | In beide Richtungen getrennt. Diese Aufrufe können Ihre Suchen nicht verlangsamen, und Ihre Suchen können diese nicht aufbrauchen. Schlüssel eines Kontos teilen sich die Buckets, mehr Schlüssel vervielfachen das Kontingent also nicht. |
Beide Obergrenzen antworten mit 429 und Retry-After in Sekunden sowie einer Meldung, die nennt, welche davon erreicht wurde.
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, für Clients, die vor der Won-Oberfläche veröffentlicht wurden. Es sind derselbe Handler und dasselbe Budget, kein zweites Kontingent. Neuer Code sollte die Won-Adresse verwenden.Wie viel von einem Store neu geschrieben wurde
revisions antwortet mit revised von total: wie viele Erinnerungen in einem Store nach dem Schreiben verändert wurden. Es lohnt sich, danach zu fragen, bevor Sie sich bei etwas Wichtigem auf das Gedächtnis stützen, oder wenn ein abgerufener Fakt nicht zu dem passt, was der Nutzer gerade sagt. Ein Store, in dem drei von zehn Fakten ersetzt wurden, verdient weniger Vertrauen als einer, den niemand bearbeitet hat.
Anzahlen
Gezählt wird, was eine Transformation berührt hat - ersetzt, aktualisiert, zurückgezogen sowie entfernte Bilder.
mem.revisions()
# {"revised": 3, "unrevised": 37, "total": 40, …}| Feld | Bedeutung |
|---|---|
| revised | Erinnerungen, die eine Transformation berührt hat. |
| unrevised | Erinnerungen, die seit dem Schreiben nichts verändert hat. revised + unrevised ergibt immer total - der Wert ist abgeleitet und wird nicht separat gezählt, ein gleichzeitiger Schreibvorgang kann die drei also nicht in Widerspruch bringen. |
| total | Erinnerungen im Store. |
| counts / excludes | Einfache Sätze, keine Flags, die ausformulieren, was die Zahlen abdecken. Der Aufrufer ist oft ein Modell. |
Die Liste lesen
Übergeben Sie include, um die Erinnerungen selbst zu erhalten, nicht nur deren Anzahl. Ohne diesen Parameter erhalten Sie nur die Zählwerte, was der günstige Aufruf ist.
page = mem.revisions(include="revised", limit=20)
page["memories"], page["matched"], page["has_more"]| Feld | Was er tut |
|---|---|
| include | "revised" oder "unrevised". Jeder andere Wert wird mit einem 400 abgelehnt, statt auf die Zählwerte zurückzufallen - ein Tippfehler, der die Liste stillschweigend weglässt, sieht genau wie ein leerer Store aus. |
| limit | 5 bis 20, Standard 20. Werte außerhalb des Bereichs oder mit falschem Typ werden abgelehnt, nicht begrenzt. |
| matched | Gesamtzahl der Zeilen hinter dieser Seite, nicht die Größe der Seite. |
| ordered_by | Der Dienst nennt seine eigene Sortierung: zuletzt gespeichert zuerst, nicht zuletzt bearbeitet. |
| next_before | Cursor für die nächste Seite, zusammen mit next_skip_ids. Geben Sie beides zurück; die IDs sammeln sich über die Seiten hinweg an. |
revised. Diese Zahl sagt Ihnen, wie viel neu geschrieben wurde, nicht wie viel verschwunden ist.Jenseits des Kontextfensters.
WOS ruft aus Historien von 1.4M Tokens ab - weit größer als jedes LLM-Kontextfenster - und liefert dennoch einen kompakten Ausschnitt von ~1.470 Tokens zurück.
Das Gedächtnis Ihres Agenten ist nicht dadurch begrenzt, was in einen Prompt passt. Es behält alles und ruft nur das Wesentliche ab, egal wie groß die Historie wird.
Privat, und Ihr Eigentum.
Ihre Daten bleiben in Ihrem Store. Wir trainieren nie darauf, sehen sie nicht ein und verwenden sie nicht weiter - wir organisieren sie nur, damit Sie sie abrufen können.
- BYOK. Ihr LLM-Schlüssel wird pro Anfrage gesendet und nie gespeichert.
- Isoliert. Erinnerungen sind pro Konto und darin pro
user_idabgegrenzt. - DSGVO-Löschung & Self-Hosting. Ein Aufruf löscht einen Nutzer vollständig; auf Wunsch betreiben Sie die Engine in Ihrer eigenen Umgebung.