Memoria a largo plazo para agentes de IA.
WOS es una API de memoria. Almacenas las memorias de un usuario una sola vez, luego recuperas solo las relevantes para cada consulta y las pasas al prompt de tu modelo.
La recuperación es puramente semántica, sin coincidencia de palabras clave ni BM25, por lo que la calidad del recall es idéntica en todos los idiomas. Cada consulta devuelve un contexto pequeño y acotado sin importar cuánto hayas almacenado, y nunca se ejecuta un modelo sobre tus memorias almacenadas.
Operaciones principales
store- guarda una memoria de un usuario.recall- obtiene las memorias relevantes para una consulta. Esta es la llamada principal.search- búsqueda semántica directa sobre las memorias almacenadas.supersede- actualiza o reemplaza una memoria desactualizada.forget- elimina una sola memoria o un usuario completo (GDPR).
Tres modelos, un mismo linaje.
Los modelos de WOS llevan el nombre de cómo la humanidad ha conservado el conocimiento a lo largo de la historia - Tablet, Scroll, Book. Piedra, pergamino, libro encuadernado: cada uno hace más por tu agente que el anterior.
Tablet
DisponibleUna forma ligera, rápida y de bajo costo de inscribir y recuperar memoria - la base sobre la que se construye cada modelo.
Scroll
DisponibleAñade un modelo de lenguaje que lee tu pregunta con más detenimiento y trae de vuelta un contexto más completo, de modo que la evidencia dispersa regresa reunida en lugar de llegar con una pieza de menos.
Book
PróximamenteSe abre solo en la página correcta - eligiendo la memoria y las herramientas que cada momento necesita, y afinándose cuanto más se usa.
El informe completo del benchmark de Tablet 1 está en la página de benchmarks.
Páganos $2. Ahorra muchas veces eso en tu LLM.
WOS entrega a tu LLM ~1,200 tokens por consulta - un fragmento acotado y relevante - en lugar de meter todo el historial en cada prompt. La diferencia es enorme, y crece con tu historial.
Cada $1 gastado en WOS ahorra ~$98 en el LLM. Un historial más grande o un modelo más caro → mayor ROI.
De dónde viene el ahorro
- Sin WOS metes todo el historial en cada prompt -
100K tokens × $2.50/1M = $0.25por consulta, a tarifas de entrada de GPT-4o (aproximadamente el doble en modelos de nivel Opus). - Con WOS ingieres una sola vez (
$2/1M), y luego cada consulta es una recuperación diminuta ($3/1M × 1,200) más tu LLM sobre apenas ~1,200 tokens. - Cuantos menos tokens lea tu LLM, menos pagas - y WOS mantiene esa cifra estable a medida que la memoria crece.
Todos los idiomas, la misma precisión.
La recuperación es puramente semántica - solo embeddings, cero coincidencia de palabras clave o BM25. Así que la calidad del recall es idéntica ya sea que tus usuarios escriban en 日本語, 中文, Español o English.
La coincidencia léxica como BM25 está ajustada a la forma de un idioma en particular - morfología, espaciado, escritura. En un store multilingüe eso significa que la calidad de la recuperación varía según el idioma. WOS no usa ninguna coincidencia léxica, así que todos los idiomas pasan por el mismo camino.
Un store, tres idiomas a la vez
No eliges un idioma por store - mézclalos libremente. Abajo, la memoria de un usuario contiene japonés, inglés y español al mismo tiempo, y cada pregunta encuentra la memoria correcta sin importar el idioma. Este es un intercambio real contra la API en vivo:
# 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
Sin paso de traducción, sin detección de idioma, sin configuración por idioma. Las memorias y las preguntas se ubican por significado, no por idioma - si el significado coincide, el idioma no importa.
Por qué prohibimos las palabras clave a propósito
La puntuación léxica como BM25 refuerza la recuperación en unos idiomas más que en otros, lo cual estorba cuando un store contiene muchos idiomas. Así que la eliminamos del motor por completo y hacemos cumplir esa regla en la revisión de código: con cualquier puntuación léxica en el camino, la calidad del recall diferiría según el idioma.
Ningún modelo se ejecuta sobre tus memorias.
El almacenamiento es literal y el motor busca por embeddings - barato, rápido y determinista. Nunca se ejecuta un modelo sobre tus memorias almacenadas. Tablet no usa ningún modelo; Scroll y Book añaden uno alrededor del motor para obtener mejores resultados, pero solo ve tu consulta, nunca lo que almacenaste.
- Motor determinista. El motor devuelve las mismas memorias para la misma consulta, siempre - por eso la varianza de nuestro benchmark proviene únicamente del modelo lector.
- Barato a escala. Sin costo de generación al almacenar o recuperar, así que tu factura sigue al almacenamiento - no al uso de modelos - a medida que la memoria crece.
Tus palabras, intactas
Un diseño común ejecuta un modelo de lenguaje al momento de escribir para extraer y reescribir "hechos" del texto. Ese diseño sacrifica tres cosas: costo de generación en cada escritura, latencia añadida y el almacenamiento de la paráfrasis de un modelo en lugar de las palabras originales. WOS hace el intercambio opuesto - almacena lo que se dijo, sin cambios, y deja que tu LLM haga la interpretación al momento de leer, con el texto original en mano.
67,5 %, medido y reproducible.
67,5 % en BEAM 1M, promediado sobre 5 ejecuciones independientes (σ 0,22 %, ninguna seleccionada a conveniencia), calificado por gpt-4.1-mini con el prompt de evaluación del propio benchmark.
En el mismo benchmark, las puntuaciones varían mucho según el protocolo de evaluación: el juez, el prompt y lo que se le permite hacer a la capa de recuperación. Calificamos con el juez que trae el propio repositorio de los autores, usamos su prompt de evaluación tal cual, no cambiamos nada para ajustarnos a la prueba y publicamos el harness, el código de puntuación y el prompt del lector para que cualquiera pueda reproducir exactamente el 67,5 %.
El protocolo, en una tabla
| Elemento | Qué hacemos |
|---|---|
| Dataset | BEAM 1M - 35 conversaciones, 74.630 turnos, 2,2 millones de memorias, 700 preguntas |
| Juez | gpt-4.1-mini con temperature 0, ejecutando el prompt de evaluación del propio BEAM: el valor por defecto en el repositorio de los autores, no un juez elegido por nosotros |
| Ejecuciones | 5 ejecuciones independientes, todos los puntajes publicados, se reporta la media (σ 0,22 %) |
| Lector | Modelo lector y prompt fijos, publicados textualmente |
Lo que lo mantiene honesto: un juez de terceros, el prompt del lector publicado sin cambios, recuperación puramente semántica, y cada ejecución reportada - no solo la mejor. El motor de recuperación es determinista - ejecútalo de nuevo y obtienes las mismas memorias.
Escalamos benchmarks más difíciles
Probamos en el benchmark estándar más difícil que aún no hemos conquistado - y la cifra es la marca máxima entre todos los modelos de WOS, reescrita cada vez que sale uno mejor. Al superar el 94%, nos graduamos a un benchmark más difícil.
Benchmark anterior LongMemEval-S Superado
Dos tarifas de tokens por modelo,
más $0.0001 por solicitud.
Por millón de tokens más una tarifa fija de $0.0001 por solicitud, pago por uso. Sin suscripción, sin renta de almacenamiento, sin topes de memoria. Pagas cuando tu agente escribe o lee - nunca por lo que recuerda.
| Modelo | Entrada / 1M | Salida / 1M | |
|---|---|---|---|
| Tablet | $2 | $3 | Disponible |
| Scroll | $4 | $8 | Disponible |
| Book | - | - | Por definir |
- $0.0001 por solicitud. Una tarifa fija en cada llamada a la API, además del uso de tokens.
- El almacenamiento es gratis. La ingesta se paga una vez; conservarlo no te cuesta nada. Sin límite de cantidad, sin límite de retención.
- Nosotros lo almacenamos. Nunca entrenamos con él, lo usamos ni lo miramos. La memoria de tu agente es tuya - solo la organizamos para que puedas recuperarla.
- Por qué Tablet es tan barato: su motor no ejecuta ningún modelo, así que nuestro costo son embeddings y disco - no GPUs. Scroll y Book añaden un modelo, y eso es lo que cubre su precio más alto.
Tres llamadas: store, recall, responder.
Una sola API. La llamada recall() devuelve el contexto de corto plazo, largo plazo y entorno en un solo viaje de ida y vuelta, listo para insertar en tu prompt.
Store
add() guarda hechos y turnos: las palabras de tu usuario, las del propio asistente (speaker "me") o las de una persona con nombre. Se incrusta al entrar, sin llamada a LLM.
Recall
recall() devuelve corto plazo + largo plazo + contexto en una sola llamada - un contexto acotado y de tamaño fijo.
Responder
Entrega ese contexto acotado a tu LLM - cualquier proveedor, tu clave.
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")
Los recuerdos llevan hablante. Por defecto son las palabras de tu usuario, speaker "me" guarda lo que dijo el propio asistente, y un nombre como "Bob" recuerda quién lo dijo, para poder recordar por persona.
Tu primer recall en 5 minutos.
Una clave, una línea de instalación, tres llamadas - tu agente ya tiene memoria. Cada fragmento de esta página se ejecutó de verdad; las respuestas se muestran textualmente.
Obtén una clave de API
Crea una en la consola. Una clave de 155 caracteres que empieza con wos-live- se muestra una sola vez. Guárdala en una variable de entorno - nunca en el código.
Instalar
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
Crea un store, luego almacena & recupera
Un store es el user_id bajo el cual lees y escribes. Los stores son explícitos: crea uno primero (la llamada de abajo), luego almacena y recupera bajo él. Store - embebido al entrar, sin llamada a LLM. Recall - corto plazo + largo plazo + contexto en un solo viaje de ida y vuelta.
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 al cliente y todas las llamadas lo usarán - no hace falta repetirlo; anula una llamada puntual pasándole user_id. Los stores son explícitos: almacenar en o recuperar de un store que no existe devuelve 404 - créalo primero. Toda cuenta empieza con un store default, así que sin ningún user_id la ruta sin configuración simplemente funciona. Consulta Stores para listarlos y administrarlos.recall() devuelve cuatro bloques - short_term (turnos recientes), long_term (memorias relevantes), context (lo que rodeaba a la mejor coincidencia) y una instruction que le dice al LLM cómo usarlos. Inserta el conjunto completo en tu prompt.
Todos los métodos, por lenguaje →
Un solo cliente, distintos ajustes
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 - crear, listar, eliminar.
Un store es el user_id bajo el cual lees y escribes - un espacio de memoria aislado por usuario final, agente o tema. Los stores son explícitos: crea uno antes de almacenar en él o recuperar de él, o la llamada devuelve 404. Toda cuenta empieza con un store default, así que puedes comenzar sin una llamada de creación.
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") para mantener separada la memoria de cada persona, o un único store default para un agente personal. También puedes crear y explorar stores en la consola (Memory ids → Issue) sin escribir código. Eliminar un store es permanente - borra todas las memorias que contiene. Los ids de almacén se pliegan antes de guardarse: se pasan a minúsculas y todo lo que quede fuera de [a-z0-9_] se convierte en _, así que Alice.Smith y alice-smith nombran el mismo almacén. Un segundo id que se pliegue sobre uno existente se rechaza con 409 en lugar de compartirse en silencio. El id también debe cumplir [A-Za-z0-9][A-Za-z0-9._-]{0,63}, por lo que una dirección de correo o un nombre no latino no puede ser un id de almacén: use un identificador interno.Listar y eliminar stores
mem.list_stores() # [{"user_id","created_at"}, …]
mem.delete_store("alice") # the store and every memory in itRecuperaciones repetidas, a la décima parte del precio.
Actívalo por solicitud y WOS almacenará en caché el resultado de búsqueda bajo el texto de la consulta, con las mismas reglas de prefijo que el prompt caching de los LLM. Mientras la caché está viva, una consulta repetida o extendida reutiliza el resultado anterior, y la parte cacheada se factura al 10% de la tarifa normal por token.
Una conversación, tres turnos
Esto es lo que ocurre realmente cuando un agente sigue hablando con su memoria. Cada turno envía la conversación acumulada como consulta, con cache_control activado.
La consulta completa se busca y se cachea: entrada a 2x (TTL de 5 minutos).
Solo la frase de Bob se convierte en embeddings y se busca. La parte antigua cuesta 0.1x, la frase nueva 2x, y la caché ahora termina en ella.
Ninguna llamada al motor. Todo a 0.1x: el descuento del 90%.
Las tarifas
| Operación | Facturación de tokens | Qué significa |
|---|---|---|
| Escritura de caché - TTL 5 minutos | 2× | La primera solicitud. Su resultado se conserva 5 minutos, y cada lectura desliza la ventana hacia adelante. |
| Escritura de caché - TTL 1 hora | 3× | La primera solicitud, conservada durante una hora completa. |
| Lectura de caché - acierto o acierto de prefijo | 0.1× | Cada solicitud posterior a la escritura: la parte cacheada cuesta una décima parte de la tarifa normal por token. |
Cuánto ahorra
Un ejemplo concreto: tu agente envía una conversación de 3.000 tokens como consulta y la repite o continúa 10 veces en cinco minutos. Sin caché, son 30.000 tokens de entrada a precio completo. Con una caché de 5 minutos son 6.000 por la primera escritura (2x) más unos 2.700 por las nueve lecturas cacheadas: 8.700 tokens facturados, un 71% menos. Cuanto más larga la conversación, mayor el ahorro.
La regla del prefijo
La coincidencia se hace sobre el inicio de la consulta. Si el inicio se mantiene idéntico y solo se añade texto nuevo al final, la parte cacheada se reutiliza y solo se busca la parte nueva. Si algo cambia antes del final del texto cacheado, no se puede reutilizar nada.
cached [ A B C D E F G ] ○ [ A B C D E F G ] E ✗ [ B C D E F G ] E
○ acierto - el inicio no cambió, E es la única parte nueva
✗ fallo - el inicio cambió, así que toda la consulta se busca y se cachea de nuevo
Tres reglas para recordar
- Extender vuelve a cachear hasta la nueva cola. Tras [A B C D E F G] + E, la caché ahora termina en E: la cola se factura una vez a la tarifa de escritura, y el siguiente turno puede volver a usar todo A..E como prefijo.
- Un solo prefijo contiguo por solicitud. Una consulta no puede dividirse en dos segmentos cacheados; solo su inicio puede coincidir.
- Las escrituras invalidan al instante. Cualquier store, store-turn, bulk-store, forget, supersede o eliminación del almacén descarta su caché, así que una respuesta cacheada nunca puede quedar obsoleta.
Cómo activarlo
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 } }No necesitas un SDK para nada de esto. El caching es un campo en una llamada HTTP, así que funciona desde cualquier lenguaje de programación. La pestaña curl es la receta universal, y los SDK de Python, TypeScript y Rust son envoltorios de conveniencia sobre exactamente la misma llamada.
Memoria que sabe quién lo dijo.
Las personas recuerdan por persona: qué prometió Bob, qué dijiste que harías. Etiqueta cada recuerdo con un hablante y tu agente hará lo mismo, en todos los modelos Tablet y Scroll.
Un equipo, tres recuerdos
Un almacén mantiene separadas muchas voces. Registra a una persona una vez, guarda cada comentario con su hablante y luego pregunta por persona.
El almacén ya conoce a Bob. El límite de 50 se cuenta aquí, al registrar; las llamadas de guardado nunca devuelven un error de límite.
El recuerdo ahora es de Bob: cada búsqueda que lo devuelve lo indica.
Lo que dice el asistente también se recuerda, y "me" nunca cuenta para el límite.
Solo vuelven las palabras de Bob. Las palabras de una persona nunca vuelven como las de otra.
Tres reglas para recordar
- "me" es el propio asistente. Nunca se registra ni cuenta. Reservado y en minúsculas:
speaker: "Me"o"ME"devuelve400 invalid_request_erroren lugar de convertirse en silencio. - El límite se cuenta al registrar: 50 por almacén para empezar. Registrar por encima devuelve
400 invalid_request_errorconspeaker_limit: 50en el cuerpo del error. Guardar con un nombre sin registrar también devuelve400y no guarda nada. Filtrar la búsqueda por un nombre sin registrar devuelve404 not_found_error. Ramifica por el código y los campos, no por el texto del mensaje; planeamos subir el límite. - Las etiquetas viven en cada lectura. Los resultados de búsqueda, el contexto de largo plazo de recall y los resultados de engram llevan su hablante, así que el modelo siempre sabe de quién son las palabras. Pasa speaker en una búsqueda para obtener solo las de una persona. Un supersede conserva el hablante; forget lo elimina.
- Los nombres son Unicode: cualquier idioma funciona. さくら, Иван y 하늘 son hablantes válidos, y la atribución se comporta igual en todos los idiomas. La coincidencia es exacta tras recortar y normalizar Unicode, así que
Bobybobson dos personas distintas. Los nombres llegan hasta 80 caracteres.
# 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" } }
Dos notas de alcance. speaker va en add / store: add_turn recuerda el intercambio completo, y las etiquetas por persona y el filtro vienen de recuerdos con speaker explícito. Y los pasajes de sesión (expand) son compuestos de varios recuerdos, así que no llevan etiqueta; un filtro speaker siempre devuelve recuerdos atómicos y etiquetados. Y una escritura cuyo significado se acerca lo suficiente a una memoria ya guardada se descarta: la coincidencia es semántica, no textual. Ese store devuelve status "duplicate" con una nota explícita, no guarda nada y no adjunta hablante. Un hecho genuinamente nuevo que solo varía en un detalle de uno existente ("alergia al marisco" tras "alergia a los cacahuetes") cae bajo la misma regla, así que lea status en lugar de suponer que la escritura se realizó.
Lo probamos de la forma difícil: recuerdos guardados sin nombres en el texto, recuperados por persona. La atribución viene del registro de hablantes, no de coincidencias de palabras, así que se comporta igual en todos los idiomas.
Cómo usarlo
mem.add_speaker("Bob", user_id="alice") # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")
hits = mem.search("what did Bob say about the deadline?", user_id="alice", speaker="Bob")
mem.list_speakers(user_id="alice")
mem.remove_speaker("Bob", user_id="alice") # memories stay, the tag goes{ "memories": [
{ "content": "Bob said the deadline moved to Tuesday",
"speaker": "Bob", ... } ] }{ "user_id": "alice",
"speakers": [ { "speaker": "Bob", "memories": 2, "created_at": "2026-07-10T04:20:39Z" } ],
"count": 1, "limit": 50 }La lista muestra a quién conoce el almacén con conteos por persona frente al límite. Quitar solo borra el registro: sus recuerdos quedan, solo se va la etiqueta.
Leer las memorias de una sola persona
by_speaker devuelve lo que dijo una persona, las más recientes primero, sin consulta. "me" devuelve las palabras del propio asistente. La misma paginación por cursor que las imágenes: devuelva next_before y next_skip_ids en la llamada siguiente.
page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]| Campo | Qué hace |
|---|---|
| memories | Las memorias, las más recientes primero. La misma forma que devuelve una búsqueda. |
| chunks | Fragmentos a nivel de frase que hay detrás de esas memorias - lo que una eliminación borraría realmente. Suele ser mayor que el número de memorias. Muéstrelo antes de que alguien confirme una eliminación. También se informa como points_to_delete. |
| next_before | Cursor para la página siguiente, junto con next_skip_ids. Hacen falta los dos porque varias memorias pueden compartir la misma marca de tiempo. |
speaker aquí es la etiqueta escrita al guardar, no una búsqueda sobre el texto. Una memoria guardada sin hablante es accesible por búsqueda, pero nunca por by_speaker, tampoco bajo "me".Listar hablantes, recorrerlos y darlos de baja
mem.list_speakers() # who is registered
mem.by_speaker("Bob") # what Bob said, newest first
mem.remove_speaker("Bob") # unregister; the memories stayImágenes
Una memoria puede llevar una imagen. El motor indexa la imagen, así que una consulta de texto en cualquier idioma coincide con ella aunque el registro no tenga pie de imagen, título ni texto alternativo.
Guardar una imagen
Pase un objeto image a la llamada add habitual. content puede ir vacío; entonces la imagen se puede buscar por sí sola.
mem.add("at the beach", image={"data": b64}) # caption + image
mem.add("", image={"data": b64}) # the image IS the memorydata es obligatorio. Tanto el prefijo data:image/jpeg;base64, como los saltos de línea que añaden base64 y openssl se eliminan automáticamente.
| Campo | Qué hace |
|---|---|
| data | Base64 de la imagen. Obligatorio. El techo de tamaño es un ajuste del servidor, no una constante del SDK - /health lo publica como memory.images.max_bytes. |
| reference | Dónde está su propia copia del original. Se guarda como cadena de texto y nosotros nunca la descargamos. |
| taken_at | RFC3339, normalmente tomado del EXIF. Rellena event_date cuando ese campo está vacío, de modo que la memoria se ordena por la fecha en que se tomó la imagen y no por la de subida. |
Encontrar una imagen
No hay una búsqueda de imágenes aparte. search y recall devuelven las imágenes junto con el texto, ordenadas en la misma clasificación.
Trabajar con las imágenes ya guardadas
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)| Llamada | Qué hace |
|---|---|
| get_image | Los bytes originales, como (bytes, content_type). El tipo se deduce de los bytes, no del nombre con el que se subió el archivo. Una memoria sin imagen lanza un error en lugar de devolver algo vacío. |
| list_images | Una página, las más recientes primero, más count - el total del store, no el tamaño de la página. La paginación es por cursor: devuelva next_before y next_skip_ids en la llamada siguiente. Hacen falta los dos porque varias imágenes pueden compartir la misma marca de tiempo. |
| forget_image | Elimina la imagen y conserva el texto. Una imagen guardada sin pie de imagen es la memoria, así que ahí elimina también la memoria. |
Pase preview=True a forget_image para obtener memory_kept sin modificar nada. iter_images pagina por usted.
Cuánto cuesta una imagen
Una imagen se factura en tokens, la misma unidad que el texto. Tokens = área en píxeles / 556,7. Por encima de 1.568 px en el lado largo el recuento se hace a 1.568 px, así que una imagen de 2.500 px cuesta lo mismo que una de 1.568 px.
| Imagen | Tamaño contabilizado | 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 |
Techo: 4.417 tokens por imagen. Reservamos ese techo contra su saldo antes de la llamada y cobramos después el valor medido, que nunca es mayor.
Cuántas vuelven
Por defecto 1, máximo 5 imágenes por respuesta. Cinco imágenes rondan los 20.000 tokens.
| Campo | Qué hace |
|---|---|
| max_images | De 0 a 5. Imágenes que puede llevar una sola respuesta. Por defecto 1. 0 devuelve solo texto. Un valor fuera de rango se rechaza en lugar de ajustarse. |
verify
verify permite que una búsqueda haga pasadas adicionales. Cada pasada excluye lo que devolvieron las anteriores, así que una segunda pasada alcanza memorias que la primera no alcanzó.
Un entero de 0 a 3 en search y recall. Es el número de pasadas adicionales, así que 3 permite cuatro recuperaciones. Por defecto 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)En ese bucle no se ejecuta ningún modelo de lenguaje
Cada pasada lleva los ids ya devueltos; el motor los excluye y busca más allá de ellos. La consulta no se reformula, así que los resultados son deterministas para una misma solicitud y no interviene ninguna credencial de modelo. Su código decide si gasta otra pasada.
Lo que se envía y lo que vuelve
| Llamada | Qué hace |
|---|---|
| verify | De 0 a 3. Pasadas adicionales permitidas. Un valor fuera de rango se rechaza con un 400 en lugar de ajustarse en silencio. |
| verify_used | Cuántas pasadas adicionales se hicieron realmente. Puede ser menos de las que pidió. |
Las pasadas se detienen antes de tiempo cuando una no devuelve nada nuevo, y las pasadas no usadas no se facturan. Si falla una pasada posterior, se devuelven los resultados reunidos hasta ese momento.
MCP: memoria para herramientas de IA
El núcleo de WOS es la API y los SDK. El servidor MCP es un complemento encima: la misma memoria, enchufada a herramientas que no construiste tú - Claude Code, Claude Desktop, Cursor.
Una línea de instalación da al agente nueve herramientas de memoria que usa por su cuenta. Y como la memoria vive en tu cuenta, lo que escribe una herramienta lo recuerdan todas las demás, incluidos los agentes que construyas con el SDK.
Qué puedes hacer con esto
- Un Claude Code que recuerda tu proyecto. Decisiones, fixes, preferencias: recuperados en la siguiente sesión sin re-explicar nada.
- Empieza en ChatGPT, continúa en Claude. Mismo store, misma memoria: la conversación cruza herramientas en vez de reiniciarse.
- Tu propio agente sigue en el circuito. Lo que aprende Claude Code, un agente del SDK lo recuerda - y lo que guarda tu agente, Claude Code lo recuerda de vuelta.
Funciona en Claude Code, Claude Desktop, Cursor, Windsurf y cualquier host MCP. ChatGPT llega a la misma memoria vía Actions más la spec OpenAPI.
Instalación
claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcpEl agente recibe nueve herramientas - recall · remember · search · update · forget · list_memories · engram · stats · create_store - cada una descrita para que sepa por sí solo cuándo usarlas.
Spec OpenAPI
El mapa completo y legible por máquinas de la API: cada endpoint, petición, respuesta y error.
OpenAPI es el formato estándar de la industria para describir una API HTTP en un archivo legible por máquinas.
https://api.wontopos.com/openapi.jsonQué puedes hacer con esto
Postman: File → Import → pega la URL y todos los endpoints aparecen como colección clicable. ChatGPT: crea un GPT, añade una Action, pega la misma URL. Codegen: openapi-generator -i .../openapi.json -g go genera un cliente en un lenguaje que no publicamos.
Impórtalo en Postman, genera un cliente en un lenguaje que no publicamos, conecta ChatGPT Actions o ejecuta checks de contrato en CI. Un test lo fija a las rutas reales: no puede desviarse.
llms.txt
Toda la API en una página de texto que una IA puede leer.
llms.txt es una convención web: una página de texto plano en la raíz del sitio que le cuenta a una IA todo lo que necesita sobre un producto.
https://wontopos.com/llms.txtPonlo en tu IDE o agente de código y sabrá cómo construir sobre WOS: auth, endpoints, patrones, errores. Se actualiza con cada release.
Los mismos hechos que la spec OpenAPI, distinta audiencia: la spec es estructura precisa para herramientas; este archivo es prosa que una IA (o una persona) lee de una pasada. Ambos se actualizan con cada release.
Tu memoria dentro de cada herramienta de IA
Un solo comando da a Claude Code, Claude Desktop, Cursor o cualquier host MCP una memoria a largo plazo respaldada por tu cuenta WOS. Sin código de integración: el agente recibe nueve herramientas de memoria y decide cuándo usarlas.
Instalación
Claude Code, una línea (crea antes una clave en la consola):
claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp
# pick which store it remembers into (optional): add --env WONTOPOS_USER_ID=my-projectAdd to Cursor → · Add to VS Code →
Env opcional: WONTOPOS_USER_ID fija el store por defecto, WONTOPOS_MODEL el motor, WONTOPOS_BASE_URL un despliegue autoalojado. WONTOPOS_READ_ONLY=1 cambia a solo lectura (solo recall/búsqueda/listado).
Antes de compartir un store
- Usa una clave dedicada. Las claves llevan su workspace: una clave solo para MCP acota lo que las herramientas conectadas pueden tocar, y puedes rotarla en la consola sin tocar las claves de tu app.
- Modo solo lectura.
WONTOPOS_READ_ONLY=1no registra ninguna herramienta de escritura: el agente puede recordar, buscar, listar memorias, ejecutar engramas y leer estadísticas, pero no guardar, actualizar, olvidar ni borrar. Ideal para agentes que deben consultar la memoria, no poseerla. - Mantén la confirmación de herramientas activada. Los hosts MCP preguntan antes de ejecutar herramientas por defecto: déjala activa sobre todo para
forget, porque los borrados se comparten entre todas las herramientas del store. - Todo lo guardado lo puede recordar cualquier herramienta con la clave. Nunca guardes secretos - claves de API, contraseñas - como memorias.
- Las memorias recordadas son datos, no instrucciones. Las descripciones de las herramientas se lo dicen explícitamente al agente. Aun así, no guardes texto de terceros no confiable en un store que un agente autónomo obedece.
- Los borrados también se comparten. Un forget o delete_all desde una herramienta borra para todas.
- "me" es el agente que escribe en el store. Si varios agentes comparten uno, sus voces "me" se mezclan. Da a cada agente su propio store (
WONTOPOS_USER_ID) para identidades separadas. - Paga una sola cuenta. Todas las herramientas conectadas consumen el mismo saldo y límite de tasa.
Luego, solo habla
El agente llama a la herramienta remember. Queda guardado de forma duradera: que termine la sesión no cambia nada.
Una sesión nueva no tiene historial. El agente llama a recall y responde desde la memoria: los viernes.
Cosas que puedes decir
- "Este repo usa pnpm, recuérdalo" →
rememberlo guarda; la siguiente sesión ya lo sabe. - "¿Qué formato de error acordamos la semana pasada?" →
recalltrae la decisión de vuelta al contexto. - "En realidad, la fecha límite pasó al viernes" → el agente ve que contradice lo que recordó y llama a
updatepara corregir esa memoria en el sitio. - "Eso está mal, olvídalo" → el agente encuentra el id y llama a
forget; tu host pide confirmación antes. - "¿Qué recuerdas de mí?" →
list_memoriesrecorre todo lo guardado, para que el agente responda o ponga orden.
No hay nada especial que decir: son frases normales, no comandos. El agente lee la descripción de cada herramienta y elige solo.
Las nueve herramientas
recall- Contexto en una llamada: turnos recientes más memorias relevantes. Su descripción indica al agente llamarla primero cuando importe el contexto pasado.remember- Guarda un hecho o decisión duradera.speaker: "me"marca las palabras del propio agente; un nombre registrado, quién lo dijo.search- Búsqueda semántica, con un filtrospeakerpor persona — yfilterspara acotarla por FECHA o tema ("¿qué decidimos en junio?"), el único eje que el significado por sí solo no puede acotar.update- Sustituye una memoria cuyo hecho cambió, conservando el rastro en vez de borrarlo.forget- Borra una memoria por id.list_memories- Recorre todo lo almacenado, para responder "¿qué recuerdas de mí?" o hacer limpieza.engram- Ejecuta una tubería multisalto integrada (deep_recall, timeline, gather) cuando una sola búsqueda no basta.stats- Cuánto hay en un almacén: útil antes de una limpieza y para confirmar que una escritura llegó.create_store- Los stores son explícitos: uno por usuario final, proyecto o agente.
¿SDK o MCP?
- El SDK va dentro de una app que tú escribes. Tu código decide exactamente cuándo guardar y qué recordar: determinista, tipado, versionado. ¿Construyes un producto? SDK.
- MCP se enchufa a una herramienta de IA que no escribiste tú. El agente decide cuándo usar la memoria, guiado por las descripciones - cero código. Para Claude Code, Claude Desktop, Cursor o dar memoria a un asistente ya hecho.
Debajo, la misma API y los mismos stores: una app hecha con el SDK y una sesión de Claude Code por MCP comparten una memoria. Se elige por superficie, no uno u otro.
Una memoria a través de todas las herramientas
La memoria pertenece a la cuenta, no a la herramienta. El mismo store escrito desde ChatGPT (Actions más la spec OpenAPI) se recuerda en Claude Code y en tus propios agentes, y al revés: una conversación empezada en una herramienta continúa en otra.
Y como es un solo store, puedes salir de Claude Code y seguir hablando donde construyes: un agente del SDK con la misma clave y store recuerda todo lo que Claude Code acaba de aprender, y lo que guarde tu agente, Claude Code lo recuerda en la siguiente sesión.
npx wontopos-mcp): con este método tu clave se queda en tu entorno y nunca se nos envía como parte de una sesión MCP. Envuelve el SDK de TypeScript, así que los reintentos automáticos, el rechazo de redirecciones y el enmascarado de la clave se aplican tal cual.Claude Code
La vía principal: un comando en tu terminal y cada sesión empieza con memoria.
- Crea una clave de API en la consola. La clave lleva su workspace: una clave = un espacio de memoria.
- Registra el servidor.
--scope userlo hace disponible en todos los proyectos; sin él, solo lo ve el proyecto actual. - Compruébalo: ejecuta
/mcpdentro de Claude Code;wontoposdebe aparecer con nueve herramientas. - Hazlo automático: una línea en tu
CLAUDE.md- "cuando importe el contexto pasado, llama primero a wontopos recall" - y cada sesión empieza con memoria sin pedirlo.
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
Añade el bloque de abajo a claude_desktop_config.json (Ajustes → Developer → Edit Config), reinicia la app y aparecen las nueve herramientas. Nota: claude.ai en web y móvil necesita un servidor MCP remoto, que WOS aún no ofrece; la app de escritorio es la vía soportada.
# claude_desktop_config.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Cursor
Añade el bloque de abajo a ~/.cursor/mcp.json, o pulsa el botón de un clic, y reinicia Cursor. El agente toma las nueve herramientas.
# ~/.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 (modo agente de Copilot) lee los servidores MCP de .vscode/mcp.json del proyecto: añade el bloque de abajo o pulsa el botón de un clic.
# .vscode/mcp.json
{ "servers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Windsurf
Windsurf (Cascade) lee ~/.codeium/windsurf/mcp_config.json: añade el bloque de abajo y recarga; aparecen las mismas nueve herramientas.
# ~/.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
Los conectores MCP de ChatGPT solo aceptan servidores remotos, así que la vía soportada hoy es un GPT personalizado con una Action: crea un GPT, añade una Action, pega la URL de la spec OpenAPI de abajo y configura tu clave como cabecera de auth. Ese GPT llamará a la misma memoria que tus demás herramientas.
# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-KeyMismo store, misma memoria: lo que ChatGPT guarda por la Action, Claude Code lo recuerda por MCP, y al revés.
Gemini CLI
Gemini CLI lee los servidores MCP de ~/.gemini/settings.json: añade el bloque de abajo, reinicia la CLI y las mismas nueve herramientas aparecen también ahí.
# ~/.gemini/settings.json
{ "mcpServers": {
"wontopos": {
"command": "npx",
"args": ["-y", "wontopos-mcp"],
"env": { "WONTOPOS_API_KEY": "wos-live-...",
"WONTOPOS_USER_ID": "my-project" }
} } }Python - todos los métodos, tres grupos.
Escribir, leer, eliminar. Cada ejemplo de abajo se ejecutó contra la API en vivo el 2026-08-01; las respuestas son textuales.
pip install wontopos
from wontopos import Client mem = Client(api_key="wos-live-...") # or read from an env var
Elige un modelo
La clave de API elige qué memoria (tu cuenta); el modelo elige qué motor la lee. Todos los modelos comparten una misma memoria, así que puedes almacenar con uno y recuperar con otro. Define un valor por defecto en el cliente; anula una llamada puntual pasando model=.
mem = Client(api_key="wos-live-...", model="tablet-1") # default engine mem.recall("...", user_id="alice") # tablet-1 mem.recall("...", user_id="alice", model="scroll-1") # or pick a model per call
list_models
El catálogo - los ids que puedes pasar a model y si cada uno está disponible. Los modelos con memory: "shared" leen el mismo store; "isolated" mantiene el suyo propio. No requiere clave de API.
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
Confirma la conexión y que tu clave de API funciona: una comprobación de una línea.
mem.ping() # True, or raises AuthenticationError / PaymentRequiredError
El catálogo de arriba siempre refleja los modelos disponibles en este momento - pasa cualquier otro id y obtendrás un error claro. Los modelos nuevos aparecen ahí automáticamente cuando se lanzan.
Escribir
add
Almacena una memoria. Embebida al entrar - sin llamada a LLM, pagas solo 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
Almacena un turno de conversación (usuario + asistente) en la memoria de corto y largo plazo a la vez.
mem.add_turn("hi", "hello!", user_id="alice")
{"status": "ok"}speaker
Cada recuerdo puede llevar quién lo dijo. Registra a una persona una vez y luego pasa su nombre como speaker; "me" (las palabras del propio asistente) nunca necesita registro. La búsqueda también acepta speaker, para recordar solo las palabras de una persona.
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
Rellena un bloque grande de texto. Se trocea y embebe del lado del servidor - ideal para importar historial existente.
mem.add_bulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", user_id="alice")
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
Un hecho cambió. La memoria antigua se marca como reemplazada (se conserva como historial); la nueva ocupa su lugar en el recall.
mem.update("576700aa-...", "she switched to coffee this year", user_id="alice")
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Leer
search
Búsqueda semántica, lo más relevante primero. Embedding puro - sin coincidencia de palabras clave, así que cualquier idioma encuentra cualquier memoria. El SDK devuelve directamente el arreglo de memorias; el cuerpo HTTP sin procesar se muestra abajo. En un modelo con carril propio (Scroll 1.2+) el servicio responde en dos carriles y el SDK devuelve ambos fusionados, así que el array puede contener MÁS de max_results. Dimensione su ventana de prompt según lo que recibe, no según el número solicitado.
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"
}]| Campo | Significado |
|---|---|
| similarity | Similitud de embedding bruta con tu consulta (0–1). |
| is_superseded | True si este hecho fue reemplazado por update(). |
| search_ms | Tiempo de recuperación del lado del servidor. |
recall
Un solo viaje de ida y vuelta devuelve todo lo que tu LLM necesita - pega el resultado directamente en tu prompt: un contexto acotado y de tamaño fijo sin importar cuánto hayas almacenado.
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
Turnos de conversación recientes (memoria de corto plazo), los más antiguos primero.
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
Conteos de memoria de un usuario.
mem.stats("alice")
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Recupera una memoria por id - el id que devolvió add o list_memories. Solo el texto original almacenado y sus metadatos, nunca el vector. Un id de otro store, o una memoria borrada o invalidada, devuelve 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
Lista las memorias de un almacén: solo el texto original que guardaste y sus metadatos, nunca el vector. Paginado por cursor: reenvía el next_cursor devuelto para la página siguiente.
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
Recorre todas las memorias sin gestionar el cursor, o trae el almacén entero de una vez.
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
Eliminar
delete
Elimina una sola memoria por id.
mem.delete("alice", memory_id="576700aa-...")
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Borra todo lo de un usuario - una llamada, lista para GDPR.
mem.delete_all("alice")
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Errores y fiabilidad
Cada fallo es un error tipado: captura el concreto (límite de tasa, autenticación, pago) o todos con el WosError base.
from wontopos import PaymentRequiredError, NotFoundError try: mem.add("...", user_id="alice") except NotFoundError: mem.create_store("alice") # store didn't exist yet except PaymentRequiredError: top_up() # out of credit - don't retry
rate_limit
Lee la cuota restante tras cualquier llamada y reduce el ritmo antes de llegar al límite.
mem.search("...", user_id="alice") rl = mem.rate_limit # {"limit": 150, "remaining": 3, "reset": ...}
search_self
Ambos carriles en una sola llamada en un modelo de memoria propia (Scroll 1.2+): lo que dijeron otros y las palabras PROPIAS del agente, separadas para que quien lee nunca confunda quién habló.
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
Pregunta al servicio qué engramas y formas de entrega puede ejecutar el modelo seleccionado, en vez de fijar nombres que quedan obsoletos en cuanto sale uno nuevo.
cat = mem.list_engrams() [e["name"] for e in cat["engrams"]] # ask, never hard-code
filters
Acota la búsqueda a una parte del almacén. Se aplica antes del ranking, así que obtienes las mejores coincidencias dentro del filtro, no un top-N filtrado después.
mem.search("what did we decide", user_id="alice", filters={ "categories": ["work"], "event_from": "2026-01-01", # when it HAPPENED })
idempotency_key
Hace segura la repetición de una escritura. Úsala cuando el reintento es tuyo - un trabajo que murió y se relanzó, una cola que reentrega.
mem.add("she prefers tea", "alice", idempotency_key=f"import:{row.id}")
import:row-42), nunca una constante: una clave reutilizada en dos escrituras distintas reproduce la primera y la segunda se pierde en silencio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].with_timeout / with_retries
Ajusta un único punto de llamada sin tocar el cliente que ya construiste: un clon con más tiempo de espera para un backfill grande, o sin reintentos dentro de tu propio bucle de reintento.
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 - todos los métodos, tres grupos.
Escribir, leer, eliminar. Cada ejemplo de abajo se ejecutó contra la API en vivo el 2026-08-01; las respuestas son textuales.
npm install wontopos
import { Client } from "wontopos"; const mem = new Client({ apiKey: "wos-live-..." });
Elige un modelo
La clave de API elige qué memoria (tu cuenta); el modelo elige qué motor la lee. Todos los modelos comparten una misma memoria, así que puedes almacenar con uno y recuperar con otro. Define un valor por defecto en el constructor; anula una llamada puntual con 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
El catálogo - los ids que puedes pasar a model y si cada uno está disponible. Los modelos con memory: "shared" leen el mismo store; "isolated" mantiene el suyo propio. No requiere clave de API.
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
Confirma la conexión y que tu clave de API funciona: una comprobación de una línea.
await mem.ping(); // true, or throws AuthenticationError / PaymentRequiredError
El catálogo de arriba siempre refleja los modelos disponibles en este momento - pasa cualquier otro id y obtendrás un error claro. Los modelos nuevos aparecen ahí automáticamente cuando se lanzan.
Escribir
add
Almacena una memoria. Embebida al entrar - sin llamada a LLM, pagas solo 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
Almacena un turno de conversación (usuario + asistente) en la memoria de corto y largo plazo a la vez.
await mem.addTurn("hi", "hello!", "alice");
{"status": "ok"}speaker
Cada recuerdo puede llevar quién lo dijo. Registra a una persona una vez y luego pasa su nombre como speaker; "me" (las palabras del propio asistente) nunca necesita registro. La búsqueda también acepta speaker, para recordar solo las palabras de una persona.
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
Rellena un bloque grande de texto. Se trocea y embebe del lado del servidor - ideal para importar historial existente.
await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
Un hecho cambió. La memoria antigua se marca como reemplazada (se conserva como historial); la nueva ocupa su lugar en el recall.
await mem.update("576700aa-...", "she switched to coffee this year", "alice");
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Leer
search
Búsqueda semántica, lo más relevante primero. Embedding puro - sin coincidencia de palabras clave, así que cualquier idioma encuentra cualquier memoria. El SDK devuelve directamente el arreglo de memorias; el cuerpo HTTP sin procesar se muestra abajo. En un modelo con carril propio (Scroll 1.2+) el servicio responde en dos carriles y el SDK devuelve ambos fusionados, así que el array puede contener MÁS de max_results. Dimensione su ventana de prompt según lo que recibe, no según el número solicitado.
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"
}]| Campo | Significado |
|---|---|
| similarity | Similitud de embedding bruta con tu consulta (0–1). |
| is_superseded | True si este hecho fue reemplazado por update(). |
| search_ms | Tiempo de recuperación del lado del servidor. |
recall
Un solo viaje de ida y vuelta devuelve todo lo que tu LLM necesita - pega el resultado directamente en tu prompt: un contexto acotado y de tamaño fijo sin importar cuánto hayas almacenado.
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
Turnos de conversación recientes (memoria de corto plazo), los más antiguos primero.
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
Conteos de memoria de un usuario.
await mem.stats("alice");
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Recupera una memoria por id - el id que devolvió add o list_memories. Solo el texto original almacenado y sus metadatos, nunca el vector. Un id de otro store, o una memoria borrada o invalidada, devuelve 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
Lista las memorias de un almacén: solo el texto original que guardaste y sus metadatos, nunca el vector. Paginado por cursor: reenvía el next_cursor devuelto para la página siguiente.
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
Recorre todas las memorias sin gestionar el cursor, o trae el almacén entero de una vez.
for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content); const everything = await mem.exportMemories("alice");
Eliminar
delete
Elimina una sola memoria por id.
await mem.delete("alice", "576700aa-...");
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}deleteAll
Borra todo lo de un usuario - una llamada, lista para GDPR.
await mem.deleteAll("alice");
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Errores y fiabilidad
Cada fallo es un error tipado: captura el concreto (límite de tasa, autenticación, pago) o todos con el WosError base.
import { NotFoundError, PaymentRequiredError } from "wontopos"; try { await mem.add("...", "alice"); } catch (e) { if (e instanceof NotFoundError) await mem.createStore("alice"); else if (e instanceof PaymentRequiredError) topUp(); // out of credit else throw e; }
rateLimit
Lee la cuota restante tras cualquier llamada y reduce el ritmo antes de llegar al límite.
await mem.search("...", "alice"); const rl = mem.rateLimit; // { limit: 150, remaining: 3, reset: ... }
searchSelf
Ambos carriles en una sola llamada en un modelo de memoria propia (Scroll 1.2+): lo que dijeron otros y las palabras PROPIAS del agente, separadas para que quien lee nunca confunda quién habló.
const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice"); // memories = what others said · self_memories = the agent's OWN words
listEngrams
Pregunta al servicio qué engramas y formas de entrega puede ejecutar el modelo seleccionado, en vez de fijar nombres que quedan obsoletos en cuanto sale uno nuevo.
const { engrams, forms } = await mem.listEngrams(); // ask, never hard-code
filters
Acota la búsqueda a una parte del almacén. Se aplica antes del ranking, así que obtienes las mejores coincidencias dentro del filtro, no un top-N filtrado después.
await mem.search("what did we decide", "alice", 10, { filters: { categories: ["work"], event_from: "2026-01-01" }, // when it HAPPENED });
idempotencyKey
Hace segura la repetición de una escritura. Úsala cuando el reintento es tuyo - un trabajo que murió y se relanzó, una cola que reentrega.
await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
import:row-42), nunca una constante: una clave reutilizada en dos escrituras distintas reproduce la primera y la segunda se pierde en silencio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].withTimeout / withRetries
Ajusta un único punto de llamada sin tocar el cliente que ya construiste: un clon con más tiempo de espera para un backfill grande, o sin reintentos dentro de tu propio bucle de reintento.
await mem.withTimeout(120_000).addBulk(bigBlob, "alice"); // this slow call only await mem.withRetries(0).add("...", "alice"); // you retry, not the SDK
Rust - todos los métodos, tres grupos.
Escribir, leer, eliminar. Cada ejemplo de abajo se ejecutó contra la API en vivo el 2026-08-01; las respuestas son textuales.
cargo add wontopos
use wontopos::Client; let mem = Client::new("wos-live-...");
Elige un modelo
La clave de API elige qué memoria (tu cuenta); el modelo elige qué motor la lee. Todos los modelos comparten una misma memoria, así que puedes almacenar con uno y recuperar con otro. Define un valor por defecto con with_model(); encadénalo de nuevo para anular una llamada puntual.
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
El catálogo - los ids que puedes pasar a with_model y si cada uno está disponible. Los modelos con memory: "shared" leen el mismo store; "isolated" mantiene el suyo propio. No requiere clave de API.
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
Confirma la conexión y que tu clave de API funciona: una comprobación de una línea.
mem.ping().await?; // Ok(true), or Err whose .kind() is Auth / PaymentRequired
El catálogo de arriba siempre refleja los modelos disponibles en este momento - pasa cualquier otro id y obtendrás un error claro. Los modelos nuevos aparecen ahí automáticamente cuando se lanzan.
Escribir
add
Almacena una memoria. Embebida al entrar - sin llamada a LLM, pagas solo 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
Almacena un turno de conversación (usuario + asistente) en la memoria de corto y largo plazo a la vez.
mem.add_turn("hi", "hello!", "alice").await?;
{"status": "ok"}speaker
Cada recuerdo puede llevar quién lo dijo. Registra a una persona una vez y luego pasa su nombre como speaker; "me" (las palabras del propio asistente) nunca necesita registro. La búsqueda también acepta speaker, para recordar solo las palabras de una persona.
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
Rellena un bloque grande de texto. Se trocea y embebe del lado del servidor - ideal para importar historial existente.
mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}update
Un hecho cambió. La memoria antigua se marca como reemplazada (se conserva como historial); la nueva ocupa su lugar en el recall.
mem.update("576700aa-...", "she switched to coffee this year", "alice").await?;
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
"old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}Leer
search
Búsqueda semántica, lo más relevante primero. Embedding puro - sin coincidencia de palabras clave, así que cualquier idioma encuentra cualquier memoria. El SDK devuelve directamente el arreglo de memorias; el cuerpo HTTP sin procesar se muestra abajo. En un modelo con carril propio (Scroll 1.2+) el servicio responde en dos carriles y el SDK devuelve ambos fusionados, así que el array puede contener MÁS de max_results. Dimensione su ventana de prompt según lo que recibe, no según el número solicitado.
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"
}]| Campo | Significado |
|---|---|
| similarity | Similitud de embedding bruta con tu consulta (0–1). |
| is_superseded | True si este hecho fue reemplazado por update(). |
| search_ms | Tiempo de recuperación del lado del servidor. |
recall
Un solo viaje de ida y vuelta devuelve todo lo que tu LLM necesita - pega el resultado directamente en tu prompt: un contexto acotado y de tamaño fijo sin importar cuánto hayas almacenado.
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
Turnos de conversación recientes (memoria de corto plazo), los más antiguos primero.
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
Conteos de memoria de un usuario.
mem.stats("alice").await?;
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}get
Recupera una memoria por id - el id que devolvió add o list_memories. Solo el texto original almacenado y sus metadatos, nunca el vector. Un id de otro store, o una memoria borrada o invalidada, devuelve 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
Lista las memorias de un almacén: solo el texto original que guardaste y sus metadatos, nunca el vector. Paginado por cursor: reenvía el next_cursor devuelto para la página siguiente.
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
Recorre todas las memorias sin gestionar el cursor, o trae el almacén entero de una vez.
let all = mem.list_all_memories("alice").await?; // every page, collected
Eliminar
delete
Elimina una sola memoria por id.
mem.delete("alice", "576700aa-...").await?;
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}delete_all
Borra todo lo de un usuario - una llamada, lista para GDPR.
mem.delete_all("alice").await?;
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}Errores y fiabilidad
Cada fallo es un error tipado: captura el concreto (límite de tasa, autenticación, pago) o todos con el WosError base.
use wontopos::ErrorKind; match mem.search("...", "alice", 10).await { Ok(hits) => { /* use hits */ } Err(e) if e.kind() == ErrorKind::NotFound => { mem.create_store("alice").await?; } Err(e) if e.is_rate_limited() => { /* back off */ } Err(e) => return Err(e), }
rate_limit
Lee la cuota restante tras cualquier llamada y reduce el ritmo antes de llegar al límite.
mem.search("...", "alice", 10).await?; let rl = mem.rate_limit(); // Some(RateLimit { remaining: Some(3), .. })
search_self
Ambos carriles en una sola llamada en un modelo de memoria propia (Scroll 1.2+): lo que dijeron otros y las palabras PROPIAS del agente, separadas para que quien lee nunca confunda quién habló.
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
Pregunta al servicio qué engramas y formas de entrega puede ejecutar el modelo seleccionado, en vez de fijar nombres que quedan obsoletos en cuanto sale uno nuevo.
let cat = mem.list_engrams().await?; // ask, never hard-code
filters
Acota la búsqueda a una parte del almacén. Se aplica antes del ranking, así que obtienes las mejores coincidencias dentro del filtro, no un top-N filtrado después.
mem.search_with("what did we decide", "alice", 10, json!({"filters": { "categories": ["work"], "event_from": "2026-01-01" // when it HAPPENED }})).await?;
add_idempotent
Hace segura la repetición de una escritura. Úsala cuando el reintento es tuyo - un trabajo que murió y se relanzó, una cola que reentrega.
mem.add_idempotent("she prefers tea", "alice", json!({}), &format!("import:{}", row.id)).await?;
import:row-42), nunca una constante: una clave reutilizada en dos escrituras distintas reproduce la primera y la segunda se pierde en silencio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].with_timeout / with_retries
Ajusta un único punto de llamada sin tocar el cliente que ya construiste: un clon con más tiempo de espera para un backfill grande, o sin reintentos dentro de tu propio bucle de reintento.
mem.with_timeout(120).add_bulk(big_blob, "alice", "general").await?; mem.with_retries(0).add("...", "alice", json!({})).await?;
curl - sin instalación, los mismos métodos.
No hay SDK que instalar - cualquier cliente HTTP funciona. Configura tu clave una vez y llama a los mismos endpoints que envuelven los SDK. URL base https://api.wontopos.com, autenticación vía X-API-Key, JSON de entrada y salida.
# set your key once (never hard-code it) export WOS_API_KEY="wos-live-..."
Escribir
store
Almacena una memoria. Embebida al entrar - sin llamada a LLM.
curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"she prefers tea over coffee"}'
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}store-turn
Almacena un turno de conversación (usuario + asistente) en la memoria de corto y largo plazo a la vez.
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
Cada recuerdo puede llevar quién lo dijo. Registra a una persona una vez y luego pasa su nombre como speaker; "me" (las palabras del propio asistente) nunca necesita registro. La búsqueda también acepta speaker, para recordar solo las palabras de una persona.
curl -X POST https://api.wontopos.com/api/v1/memory/speakers \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","speaker":"Bob"}' # once per person curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"I promised to send the report on Friday","metadata":{"speaker":"me"}}' curl -X POST https://api.wontopos.com/api/v1/memory/store \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","content":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}' curl -X POST https://api.wontopos.com/api/v1/memory/search \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what did Bob say about deadlines?","speaker":"Bob"}'
supersede
Un hecho cambió - la memoria antigua se marca como reemplazada, la nueva ocupa su lugar en el recall.
curl -X POST https://api.wontopos.com/api/v1/memory/supersede \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","old_memory_id":"576700aa-...","new_content":"she switched to coffee this year"}'
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}bulk-store
Carga un historial largo en una sola llamada - troceado e incrustado en el servidor.
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
Hace segura la repetición de una escritura. Úsala cuando el reintento es tuyo - un trabajo que murió y se relanzó, una cola que reentrega.
# 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), nunca una constante: una clave reutilizada en dos escrituras distintas reproduce la primera y la segunda se pierde en silencio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].Leer
search
Búsqueda semántica, lo más relevante primero. Embedding puro - cualquier idioma encuentra cualquier memoria.
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
Acota la búsqueda a una parte del almacén. Se aplica antes del ranking, así que obtienes las mejores coincidencias dentro del filtro, no un top-N filtrado después.
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
Lee una memoria por el id que devolvió store o list - texto original y metadatos, sin vectores.
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
Recorre todo un almacén, cursor a cursor. Sirve para explorar o exportar.
curl -X POST https://api.wontopos.com/api/v1/memory/list \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","limit":100}' # pass next_cursor back for the next page
{"count": 3, "memories": [{"id": "1a1cfc47-...", "content": "...", "category": "general",
"created_at": "2026-07-31T18:04:51.937117314+00:00", "event_date": null, "is_superseded": false}],
"next_cursor": "722c08e5-8998-4882-979e-d71995b5b4af", "user_id": "docs_livetest"}recall
Un solo viaje de ida y vuelta devuelve corto plazo + largo plazo + contexto + una instrucción. Pégalo directamente en tu prompt.
curl -X POST https://api.wontopos.com/api/v1/memory/recall \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice","query":"what does she drink?"}'
{"short_term": {"count": 2, "turns": [...]},
"long_term": {"count": 4, "memories": [{"content": "she prefers tea over coffee", "similarity": 0.63}]},
"context": {"count": 4, "around_top_memory": ["[match] she prefers tea over coffee"]},
"instruction": "Use short_term for recent context, long_term for relevant past memories..."}Eliminar
forget
Elimina una memoria por id, u omítelo para eliminar todo lo de un usuario (GDPR).
curl -X POST https://api.wontopos.com/api/v1/memory/forget \ -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \ -d '{"user_id":"alice"}' # omit memory_id = delete all
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}Todos los endpoints + campos del cuerpo →
Variantes exclusivas de Rust
Python y TypeScript los reciben como argumentos opcionales. Rust estable no tiene argumentos por defecto ni con nombre, así que cada uno es un método propio en lugar de un builder que haya que completar.
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 se exporta también como iter_images, el mismo nombre que usan los otros dos SDK - quien llega desde esa documentación escribe primero ese nombre.
Engrams
Herramientas de recall invocables que tu modelo puede llamar - cada una es una estrategia de recuperación distinta sobre la misma memoria. Usa una, o ejecuta varias a la vez.
Regularmente se lanzan más engramas - esta lista crece.
Memoir & Archive Scroll 1.2+
Esta es una forma de entrega, no una herramienta invocable. En Scroll 1.2 y superiores, elígela por llamada - form: "memoir" o form: "archive" - y ese recall, incluida una búsqueda simple, vuelve con el tiempo escrito de esa manera.
Time_awareness Scroll 1.2+
Una forma de entrega - se elige por llamada. Pasa form - memoir o archive - en cualquier llamada de un modelo compatible (Scroll 1.2 y superiores), y la respuesta vuelve renderizada de esa manera: una búsqueda simple, un recall o cualquier engrama. En los SDK es un campo form, como tz; por HTTP es el encabezado X-WOS-Form. Un Memoir se lee como recuerda una persona; un Archive conserva un registro exacto - la diferencia se nota sobre todo en cómo escribe el tiempo cada uno.
Memoir
form: "memoir"Cuenta lo que pasó y cómo un momento llevó al siguiente, con esa noción suave del tiempo que recuerda una persona - se lee como experiencia, no como una lista.
Archive
form: "archive"Devuelve las coincidencias como registros exactos - tiempo transcurrido preciso y anclas absolutas, estructurado para que un modelo lo lea de inmediato.
store / add bajo un user_id (ese user_id es el store de esa persona). Almacena primero; después cualquier recall - incluida la búsqueda simple de abajo - vuelve etiquetado con el tiempo. Consulta el Inicio rápido para almacenar.# 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 es el desplazamiento UTC del llamador en horas - para que "esta mañana" y el límite de día de las 4am caigan en su hora local. Omítelo para UTC; por HTTP es el encabezado X-WOS-Timezone. A grandes rasgos, por región: EE. UU. Este -5, EE. UU. Centro -6, EE. UU. Oeste -8 · Reino Unido / Lisboa 0 · Europa Central +1 · Europa Oriental +2 · India +5.5 · China / Singapur +8 · Corea / Japón +9 · Sídney +10. (Hora estándar - el horario de verano desplaza algunas regiones en +1; pasa el que tus usuarios realmente usen.)
La misma búsqueda, dos formas - las memorias son idénticas, solo cambia time:
{ "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)" }
] }| Transcurrido | Memoir | Archive |
|---|---|---|
| 3 min | a few minutes ago | 3 minutes ago |
| 14 min | about 15 minutes ago | 14 minutes ago |
| 30 min | half an hour ago | 30 minutes ago |
| 50 min | about an hour ago | 50 minutes ago |
| 2 h | a couple hours ago | 2 hours ago, at 13:10 |
| 8 h | this morning | 8 hours ago, at 07:10 |
| ayer p. m. | yesterday afternoon | yesterday at 14:00 |
| anoche | last night | 17 hours ago, at 22:00 |
| 2 días | a couple days ago | 2 days ago (Tue 15:10) |
| 6 días | several days ago | 6 days ago (Fri 15:10) |
| 9 días | about a week ago | last week (Jun 16) |
| 16 días | a couple weeks ago | 2 weeks ago (Jun 09) |
| 35 días | about a month ago | last month (May 21) |
| 60 días | a couple months ago | 2 months ago (Apr 2026) |
| 180 días | about half a year ago | 6 months ago (Dec 2025) |
| 380 días | about a year ago | last year (Jun 2025) |
| 800 días | a couple years ago | 2 years ago (Apr 2024) |
| 1500 días | about 4 years ago | 4 years ago (May 2022) |
Cada valor de arriba es la salida real del renderizador. Mira las dos filas de "ayer": un Memoir separa la tarde de la noche anterior - un día es un sueño - mientras que un Archive escribe una sola hora de reloj y no traza ninguna línea entre día y noche.
Cómo lee el tiempo cada modo
Memoir - como la gente realmente lo dice. Los momentos recientes se mantienen bastante nítidos (unos 15 minutos, media hora), y luego la redacción se ensancha cuanto más atrás vas - un par de semanas, cerca de medio año, un par de años - igual que la memoria misma se afloja con la distancia. Dentro de un día deja el reloj por un punto de referencia: esta mañana, anoche, ayer por la tarde. Y un día es un sueño, no un tic del calendario: el límite se sitúa alrededor de las 4am hora local, así que una noche larga todavía se lee como la misma velada, no como si ya fuera mañana.
Archive - preciso, siempre con un ancla. Cada línea lleva el tiempo transcurrido exacto más una referencia absoluta desde la que un modelo puede calcular, y el ancla se afina cuanto más cerca está: una hora de reloj para hoy (hace 8 horas, a las 07:10), un día de la semana y hora esta semana (hace 2 días (mar 15:10)), una fecha este mes (la semana pasada (16 jun)), y mes y año más allá (hace 6 meses (dic 2025)). Nunca vago, nunca equivocado.
deep_recall
Recall multisalto. Busca tu consulta, luego toma la mejor coincidencia y vuelve a buscar sobre su contenido - trayendo contexto enlazado que una sola búsqueda pasaría por alto. Ideal cuando las memorias se referencian entre sí (una persona → sus proyectos → los detalles). Devuelve hasta ~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 (entrada + salida), contado con el mismo tokenizador que el resto de la API; sin tarifas ocultas por engrama. ¿Necesitas varios a la vez? Llámalos de forma concurrente - cada engrama es una solicitud independiente.timeline
Recall ordenado por tiempo. Devuelve las memorias ordenadas de más reciente a más antigua según cuándo ocurrió el evento, no por relevancia. Para preguntas de "cuándo pasó X", historial y secuencia. Devuelve hasta 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 (entrada + salida), contado con el mismo tokenizador que el resto de la API; sin tarifas ocultas por engrama. ¿Necesitas varios a la vez? Llámalos de forma concurrente - cada engrama es una solicitud independiente.gather
Recolección amplia. Busca y luego se expande alrededor de las tres mejores coincidencias - una red más amplia que deep_recall. Úsala para traer todo lo relacionado con una persona, proyecto o tema en una sola llamada. Devuelve hasta ~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 (entrada + salida), contado con el mismo tokenizador que el resto de la API; sin tarifas ocultas por engrama. ¿Necesitas varios a la vez? Llámalos de forma concurrente - cada engrama es una solicitud independiente.equilibrium
Corrección de deriva. La búsqueda semántica se estrecha a medida que avanza una sesión: la consulta lleva el estado actual, así que trae recuerdos del mismo estado y el turno siguiente se inclina más hacia ese lado. Este engrama vuelve a ensanchar el resultado por tres ejes que la consulta no controla: la dispersión en el tiempo, la asociación que se aleja de la consulta y la parte sustancial del almacén. Úselo cuando las respuestas empiecen a repetirse o a aplanarse. Para un dato concreto conviene más deep_recall o gather, que se mantienen cerca de la consulta. Devuelve hasta 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 (entrada + salida), contado con el mismo tokenizador que el resto de la API; sin tarifas ocultas por engrama. ¿Necesitas varios a la vez? Llámalos de forma concurrente - cada engrama es una solicitud independiente.tone_stabilizer
Su propia voz. Las sesiones largas apartan al asistente de su registro: las respuestas se alargan, se vuelven informes o toman el ánimo del último tramo. El autorrecuerdo corriente lo empeora, porque encaja con el estado actual y devuelve las líneas más recientes como si fueran el carácter. Este engrama devuelve en su lugar las palabras propias de antes de ese tramo. Necesita turnos guardados con speaker me; si no hay ninguno, devuelve vacío en vez de adivinar. Devuelve hasta 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 (entrada + salida), contado con el mismo tokenizador que el resto de la API; sin tarifas ocultas por engrama. ¿Necesitas varios a la vez? Llámalos de forma concurrente - cada engrama es una solicitud independiente.Todos los endpoints, una URL base.
No se requiere SDK - cualquier cliente HTTP funciona. URL base https://api.wontopos.com, autenticación vía el encabezado X-API-Key, JSON de entrada y salida. Las operaciones de memoria son POST; la administración de stores usa POST / GET / DELETE sobre /collection. El store debe existir primero (ver Stores) o las operaciones dentro del store devuelven 404.
Encabezados
| Encabezado | Qué hace |
|---|---|
| X-API-Key | Obligatorio en cada llamada. Tu clave, emitida en la consola. |
| X-WOS-Model | Opcional. Qué motor responde. Omítelo y se usa el valor por defecto de la cuenta. GET /api/v1/models lista los modelos que tu clave puede seleccionar; un endpoint que un motor anterior no puede atender responde 501 y nombra ese modelo. |
| Idempotency-Key | Opcional, en las escrituras. La misma clave con el mismo cuerpo reproduce la primera respuesta en lugar de volver a almacenar - mira la nota de abajo. |
Endpoint
| Endpoint | Propósito | Campos del cuerpo |
|---|---|---|
| POST /api/v1/memory/collection | crear un store | user_id |
| GET /api/v1/memory/collections | listar tus stores | (ninguno) |
| DELETE /api/v1/memory/collection | eliminar un store + sus memorias | user_id |
| /api/v1/memory/store | almacenar una memoria | user_id · content · metadata? (event_date · speaker) · image? |
| /api/v1/memory/store-turn | almacenar un turno de conversación | user_id · user_msg · assistant_msg |
| POST /api/v1/memory/speakers | registrar un hablante (explícito, hasta 50) | user_id · speaker |
| GET /api/v1/memory/speakers | listar hablantes registrados + conteos | user_id |
| DELETE /api/v1/memory/speakers | dar de baja un hablante (los recuerdos quedan) | user_id · speaker |
| /api/v1/memory/by-speaker | lo que dijo una persona, de lo más reciente («me» = el agente) | user_id · speaker · limit? · before? · skip_ids? |
| POST /api/v1/memory/image | los bytes originales de una memoria con imagen | user_id · memory_id |
| DELETE /api/v1/memory/image | quitar la imagen y conservar el texto | user_id · memory_id · preview? |
| /api/v1/memory/images | las imágenes de un store, de lo más reciente (+ el total) | user_id · limit? · before? · skip_ids? |
| /api/v1/memory/lineage | la cadena de ediciones de una memoria, de lo más antiguo | user_id · memory_id |
| /api/v1/won/revisions | cuánto de un store se ha reescrito. Gratis | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/revisions | la misma llamada bajo el plano memory. Gratis | user_id · include? · limit? · before? · skip_ids? |
| /api/v1/memory/bulk-store | rellenar un bloque de texto | user_id · content · category? · timestamp? |
| /api/v1/memory/search | búsqueda semántica | user_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images? |
| /api/v1/memory/recall | corto + largo + contexto | user_id · query · limit? · context_limit? |
| /api/v1/memory/get | una memoria por id | user_id · memory_id |
| /api/v1/memory/list | recorrer un almacén por páginas | user_id · limit? · cursor? |
| /api/v1/memory/history | turnos recientes | user_id |
| /api/v1/memory/stats | conteos de memoria | user_id |
| /api/v1/memory/supersede | reemplazar un hecho que cambió | user_id · old_memory_id · new_content |
| /api/v1/memory/forget | eliminar una (o todas) | user_id · memory_id? (omitir = eliminar todo) |
| GET /api/v1/engram | engramas que este modelo puede ejecutar | (ninguno) |
| POST /api/v1/engram/run | ejecutar un engrama | name · user_id · query · form? · tz? |
| GET /api/v1/models | modelos disponibles | (ninguno) |
Idempotency-Key. La misma clave con el mismo cuerpo reproduce la primera respuesta en lugar de volver a almacenar (10 minutos); la misma clave con un cuerpo distinto responde 422. Solo se cachean los 2xx, así que una llamada fallida se puede reintentar de inmediato.# 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)"}Las mismas funciones para todos.
Los niveles solo elevan tus límites.
Todos los niveles ejecutan el motor completo - la misma calidad de recall, los mismos idiomas, todos los métodos. Los niveles avanzan automáticamente hasta el Nivel 5 a medida que crecen tus compras acumuladas de crédito, sin solicitudes ni llamadas de ventas. Enterprise (Nivel 6) es la única excepción.
Límites de gasto
Cada nivel limita cuánto puedes gastar por mes calendario. Avanzas de inmediato cuando tus compras acumuladas de crédito alcanzan el siguiente umbral.
| Nivel de uso | Compra de crédito | Límite de gasto mensual |
|---|---|---|
| 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 | Habla con nosotros | Sin límite |
Límites de velocidad
Los límites de velocidad son por cuenta - todas las claves de API de una cuenta comparten un mismo límite, que escala con tu nivel. Excederlo devuelve un 429 con un encabezado retry-after; espera (1s → 2s → 4s) y reintenta. Todos los endpoints son compatibles con la idempotencia, así que reintentar es seguro.
| Nivel | Solicitudes por minuto |
|---|---|
| Tier 1 | 150 |
| Tier 2 | 300 |
| Tier 3 | 600 |
| Tier 4 | 1,500 |
| Tier 5 | 3,000 |
| Tier 6 - Enterprise | Personalizado |
Enterprise (Nivel 6) obtiene límites de velocidad personalizados, un SLA, soporte dedicado y una licencia opcional de autoalojamiento - habla con nosotros.
Llamadas gratuitas
Unos pocos endpoints no tienen ningún cargo - están reunidos bajo Won. En lugar de un precio tienen dos límites.
- 10 solicitudes por minuto, por endpoint. Cada endpoint gratuito mantiene su propio contador, así que gastar uno no gasta otro.
- 300 solicitudes por hora, compartidas. Todos los endpoints gratuitos consumen un único cupo horario por cuenta.
Ninguno de los dos se alcanza en un uso normal, y ninguno afecta a los límites de pago de arriba.
Cuando algo sale mal.
Los errores vuelven como un sobre JSON con un type estable, un mensaje legible y un request_id que puedes enviarnos al reportar un problema.
{"type": "error", "error": {
"type": "authentication_error",
"message": "Invalid or revoked API key.",
"request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
}}| HTTP | Significado | Qué hacer |
|---|---|---|
| 400 | Cuerpo mal formado (campo faltante o de tipo incorrecto) | El mensaje indica el campo exacto - corrige y reintenta. |
| 401 | Clave de API inválida o revocada | Verifica la clave; emite una nueva en la consola. |
| 402 | Saldo agotado, sin tarjeta registrada, o tope de nivel alcanzado | Recarga saldo o añade una tarjeta en la consola. La respuesta incluye balance_cents y floor_cents, así puedes saber cuál de los dos te detuvo. |
| 404 | No existe esa memoria, store o imagen | Verifica el id. get_image también responde 404 cuando la memoria existe pero no lleva ninguna imagen. |
| 409 | Ese nombre ya está en uso | Los nombres de store y de workspace son únicos dentro de una cuenta - elige otro. |
| 413 | Cuerpo de la solicitud por encima de 10MB | Base64 ocupa alrededor de un 33% más que el archivo que codifica, así que redimensiona la imagen antes de codificarla. |
| 429 | Límite de velocidad excedido | El SDK ya los reintenta por ti, con espera exponencial y jitter, respetando Retry-After. Recibir uno significa que los reintentos se agotaron - baja tu concurrencia en lugar de envolverlo en un bucle propio. |
| 501 | El motor de este modelo no implementa ese endpoint | Las imágenes y el historial de revisiones necesitan un motor más reciente. GET /api/v1/models lista qué modelos sirven qué. |
| 5xx | Problema del lado del servidor | Reintenta con espera exponencial, pero no a ciegas. El SDK no reintenta automáticamente un 5xx aquí, porque cada llamada de esta API es un POST y el servidor puede haber guardado ya tu solicitud. Reenvíala con una clave de idempotencia para que una repetición no pueda escribir dos veces, e incluye el request_id si nos contactas. |
Todo error es un WosError, y cada estado tiene además su propia clase - BadRequestError, AuthenticationError, PaymentRequiredError, NotFoundError, ConflictError, RateLimitError, ServerError, APIConnectionError. Captura la que quieras manejar en vez de comparar números.
# 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
Algunos errores nunca llegan hasta nosotros. La clave de API, el id del store, la clave de idempotencia y la imagen se comprueban antes de que salga la solicitud, y ahí se lanza ValueError o TypeError - no WosError. Un except WosError por sí solo no los atrapará.
Los límites de velocidad son por cuenta, se comparten entre todas tus claves y escalan con tu nivel - consulta Niveles de uso. El uso de tu cuenta se muestra en la consola.
lineage
La cadena de ediciones que hay detrás de una memoria, de la más antigua a la más reciente. revisions indica cuánto se movió un store; esto indica qué le pasó a un hecho concreto.
revisions, esta es una llamada facturada normal, porque devuelve contenido de memorias.Pase el id de cualquier memoria de la cadena. Las versiones reemplazadas se conservan en lugar de borrarse, así que una búsqueda que solo devuelve el hecho vigente se puede rastrear hacia atrás.
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 }
] }| Campo | Qué hace |
|---|---|
| chain | Las versiones, de la más antigua a la más reciente. Cada una lleva los mismos campos que una memoria, más los cuatro de abajo. |
| changed_at | Cuándo se reemplazó esta versión (RFC3339), o null mientras siga vigente. |
| action | Qué ocurrió en este eslabón - cómo se relacionó el reemplazo con esta versión. |
| confidence | Cuánta certeza tenía el motor sobre esa relación, de 0 a 1. |
| is_current | True para la única versión que sigue vigente. Exactamente una por cadena. |
| truncated | True cuando la cadena era más larga de lo que el servicio recorre. Los pasos devueltos siguen siendo los más antiguos. |
Para qué sirve
Dos usos. Depuración: por qué una memoria dice hoy lo que dice. Y permitir que un asistente consulte su propio historial - un hecho corregido tres veces es un tipo de hecho distinto de uno escrito una sola vez, y solo la cadena lo muestra.
Won es para quien lee la memoria.
La mayor parte de esta API responde con memoria. Won responde sobre ella: cuánto se ha reescrito de un store y hasta dónde se puede confiar en él. Está pensado para el lado que lee, normalmente el asistente que estás construyendo, y no para la persona de la que hablan las memorias.
Wontopos es Won + Topos, un solo lugar donde vive la memoria. Won es la parte de ese lugar que informa sobre la memoria en vez de devolverla. Estas llamadas son gratuitas, de solo lectura, y nunca tocan la recuperación: preguntar no le cuesta nada a tu usuario y no cambia nada de lo que está recordado.
Lo que hay disponible ahora
Por ahora, una sola llamada.
| Llamada | Qué hace |
|---|---|
| POST /won/revisions | Cuánto se ha modificado este store desde que se escribió. Dos números y dos frases que los explican. |
Un ejemplo resuelto
Use la proporción, no el recuento en bruto. 3 de 40 y 30 de 40 requieren un tratamiento distinto.
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 y excludes se devuelven como frases y no como banderas, porque quien llama suele ser un modelo. Las eliminaciones no se cuentan.
Precio y límites
| Regla | Valor |
|---|---|
| Precio | Ninguno. Las llamadas gratuitas se saltan los controles de facturación - sin cargo por tokens, sin tarifa por solicitud y sin registro de uso. |
| Por minuto | 10 por minuto, por cuenta y por endpoint. Gastar el minuto de un endpoint no gasta el de otro. |
| Por hora | 300 por hora, por cuenta, compartidas por todas las llamadas gratuitas. Este límite ignora la ruta, así que añadir endpoints gratuitos no eleva el total que una cuenta puede gastar. |
| Frente al tráfico de pago | Separados en ambos sentidos. Estas llamadas no pueden ralentizar sus búsquedas y sus búsquedas no pueden agotar estas. Las claves de una misma cuenta comparten los contadores, así que tener más claves no multiplica el cupo. |
Ambos techos responden 429 con Retry-After en segundos y un mensaje que nombra cuál de los dos se ha alcanzado.
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, para clientes publicados antes de que existiera la superficie Won. Es el mismo manejador y el mismo presupuesto, no un segundo cupo. El código nuevo debe usar la dirección Won.Cuánto se ha reescrito de un store
revisions responde revised de total: cuántas memorias de un store fueron alteradas después de haber sido escritas. Vale la pena preguntarlo antes de apoyarte en la memoria para algo que importa, o cuando un dato recordado no encaja con lo que el usuario está diciendo ahora. Un store donde tres de cada diez hechos han sido reemplazados merece menos confianza que uno que nadie ha editado.
Recuentos
Cuenta lo que tocó una transformación - reemplazado, actualizado, retractado y imágenes eliminadas.
mem.revisions()
# {"revised": 3, "unrevised": 37, "total": 40, …}| Campo | Qué significa |
|---|---|
| revised | Memorias que ha tocado una transformación. |
| unrevised | Memorias que nada ha tocado desde que se escribieron. revised + unrevised siempre es igual a total - es un valor derivado, no contado aparte, así que una escritura concurrente no puede hacer que los tres no cuadren. |
| total | Memorias que hay en el store. |
| counts / excludes | Frases llanas, no banderas, que detallan qué cubren los números. Quien llama suele ser un modelo. |
Leer la lista
Pase include para obtener las memorias en sí, no solo cuántas hay. Si lo omite, recibe solo los recuentos, que es la llamada barata.
page = mem.revisions(include="revised", limit=20)
page["memories"], page["matched"], page["has_more"]| Campo | Qué hace |
|---|---|
| include | "revised" o "unrevised". Cualquier otro valor se rechaza con un 400 en lugar de recurrir a los recuentos - una errata que descarta la lista en silencio se ve igual que un store vacío. |
| limit | De 5 a 20, por defecto 20. Un valor fuera de rango, o de tipo incorrecto, se rechaza en lugar de ajustarse. |
| matched | Total de filas que hay detrás de esta página, no el tamaño de la página. |
| ordered_by | El servicio declara su propio orden: primero lo guardado más recientemente, no lo editado más recientemente. |
| next_before | Cursor para la página siguiente, junto con next_skip_ids. Devuelva los dos; los ids se acumulan de una página a otra. |
revised bajo. Este número te dice cuánto se reescribió, no cuánto ha desaparecido.Más allá de la ventana de contexto.
WOS recupera de historiales de 1.4M tokens - mucho más grandes que cualquier ventana de contexto de un LLM - y aun así entrega un fragmento compacto de ~1,470 tokens.
La memoria de tu agente no está limitada por lo que cabe en un prompt. Lo conserva todo y recupera solo lo que importa, sin importar cuánto crezca el historial.
Privado, y tuyo.
Tus datos permanecen en tu store. Nunca entrenamos con ellos, los vemos ni los reutilizamos - solo los organizamos para que puedas recuperarlos.
- BYOK. Tu clave de LLM se envía por solicitud y nunca se almacena.
- Aislado. Las memorias tienen alcance por cuenta y luego por
user_id. - Borrado GDPR & autoalojamiento. Una llamada borra a un usuario; ejecuta el motor en tu propio entorno si lo prefieres.