Por qué WOS

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).
Elige una sección a la izquierda para ver el detalle de cada tema.
Modelo

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

Disponible
Grabado en piedra · store & recall

Una forma ligera, rápida y de bajo costo de inscribir y recuperar memoria - la base sobre la que se construye cada modelo.

Scroll

Disponible
Desenrollado · recall asistido por LLM

Añ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óximamente
Encuadernado & indexado · autoenrutado

Se 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.

Costo

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.

Costo de LLM por 1,000 consultas Basado en Tablet 1
Historial del usuario100K
Consultas / mes1,000
Tu LLM
45× más barato - ahorras $244/mes
Sin WOS$250.00
Con WOS$5.50

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.25 por 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.
Reducción de contexto = historial ÷ tokens entregados, no costo (la calculadora de arriba cotiza cada recuperación).  25K → 21× · 100K → 83× · 200K → 167×.
Multilingüe

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
Resultados reales - cada pregunta cruza a un idioma distinto
"¿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.

Tres idiomas aquí es solo lo que cabe en una página - no existe una lista de idiomas soportados a la que haya que pertenecer. La misma prueba en vivo también pasa con memorias en 中文, Русский y العربية, todas verificadas contra la API de producción.

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.

LongMemEval es solo en inglés, por lo que no mide el recall multilingüe. La demo de arriba es la forma de verificarlo directamente contra la API en vivo.
Arquitectura

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.

Lo que WOS no es: no es una base de datos vectorial que tengas que operar, ni un framework RAG que tengas que ensamblar. Nunca se ejecuta un modelo sobre tus datos almacenados - ese camino es puramente embeddings. Scroll y Book sí usan un modelo de lenguaje para obtener mejores resultados, pero solo ve tu consulta, nunca tus memorias almacenadas - y nunca entrena con tus datos ni los recopila.
Prueba

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

ElementoQué hacemos
DatasetBEAM 1M - 35 conversaciones, 74.630 turnos, 2,2 millones de memorias, 700 preguntas
Juezgpt-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
Ejecuciones5 ejecuciones independientes, todos los puntajes publicados, se reporta la media (σ 0,22 %)
LectorModelo 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.

BEAM 1MEn curso
Tablet67.5%
juez gpt-4.1-mini · media de cinco ejecuciones94% para graduarse
Benchmark anterior LongMemEval-S Superado
Tablet95.7%
Scroll92.3%
Juez GPT-4o · el mejor entre todos los modelos de WOS94% para graduarse
Ver el informe completo
Precios

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.

ModeloEntrada / 1MSalida / 1M
Tablet$2$3Disponible
Scroll$4$8Disponible
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.
Otros modelos de facturación cobran mensualmente por el volumen almacenado o limitan la cantidad de memorias según el plan. WOS no cobra nada por los datos almacenados, sin importar su volumen o antigüedad.

Límites de velocidad por nivel de uso →

Para desarrolladores

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.

1

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.

2

Recall

recall() devuelve corto plazo + largo plazo + contexto en una sola llamada - un contexto acotado y de tamaño fijo.

3

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.

Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.
Inicio rápido

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.

1

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.

2

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
3

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?")
import { Client } from "wontopos";

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

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

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

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

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

# one call → short-term + long-term + context
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what does alice drink?"}'
Respuesta real - create_store()
{"user_id": "alice", "status": "created"}
Respuesta real - add()
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Configura el store una vez. Pasa 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.

Funciona en cualquier idioma. Almacena en inglés, pregunta en coreano, japonés o chino - vuelve la misma memoria. Búsqueda por embeddings, no coincidencia de palabras clave.

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 store
const alice = mem.withUser("alice");
const scroll = mem.withModel("scroll-1.2");
let alice = mem.with_user("alice");
let scroll = mem.with_model("scroll-1.2");
# curl has no copies — send model and user_id with each request
curl ... -d '{"user_id":"alice","model":"scroll-1.2","query":"…"}'
Stores

Stores - 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.

Cómo se anida el aislamiento. Una cuenta posee workspaces; cada workspace aísla su propia memoria, claves de API y uso (la facturación se comparte a nivel de cuenta). Un store vive dentro de un workspace: las claves del mismo workspace comparten sus stores, y los distintos workspaces nunca ven la memoria de los demás. cuenta → workspace → store (user_id) → memorias.
mem.create_store("alice")        # create (idempotent)
mem.list_stores()              # [{"user_id","created_at"}, ...]
mem.delete_store("alice")        # delete the store + all its memories
await mem.createStore("alice");
await mem.listStores();          // [{ user_id, created_at }, ...]
await mem.deleteStore("alice");     // store + all its memories
mem.create_store("alice").await?;
let stores = mem.list_stores().await?;
mem.delete_store("alice").await?;
# create
curl -X POST https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
# list
curl https://api.wontopos.com/api/v1/memory/collections -H "X-API-Key: $WOS_API_KEY"
# delete (store + all its memories)
curl -X DELETE https://api.wontopos.com/api/v1/memory/collection \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" -d '{"user_id":"alice"}'
Respuesta real - create
{ "user_id": "alice", "status": "created" }   // "exists" if it already did
Respuesta real - list
{ "collections": [
  { "user_id": "default", "created_at": "2026-06-26T02:23:14Z" },
  { "user_id": "alice",   "created_at": "2026-06-26T02:24:01Z" }
], "count": 2 }
Recall sobre un store que no existe
{ "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." } }
Usa un store por usuario final ("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 it
await mem.listStores();
await mem.deleteStore("alice");
let stores = mem.list_stores().await?;
mem.delete_store("alice").await?;
curl -X POST   .../api/v1/memory/collections -d '{}'
curl -X DELETE .../api/v1/memory/collection  -d '{"user_id":"alice"}'
Caché de recuperación

Recuperaciones 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.

Solo Tablet y Scroll. La caché funciona en todos los modelos Tablet y Scroll, actuales y futuros. Book no la admite: Book razona sobre tus memorias y aprende entre llamadas, así que la misma pregunta puede volver legítimamente con una respuesta distinta, y un resultado cacheado sería erróneo por diseño. Enviar cache_control a Book devuelve un 403 claro.

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.

writeTurno 1 - «Alice: Me mudé a Lisboa la primavera pasada.»

La consulta completa se busca y se cachea: entrada a 2x (TTL de 5 minutos).

extendTurno 2 - el mismo texto más «Bob: ¿Qué tal el clima allí?»

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.

hitTurno 3 - exactamente la misma consulta otra vez (un reintento, un refresco)

Ninguna llamada al motor. Todo a 0.1x: el descuento del 90%.

Las tarifas

OperaciónFacturación de tokensQué significa
Escritura de caché - TTL 5 minutosLa primera solicitud. Su resultado se conserva 5 minutos, y cada lectura desliza la ventana hacia adelante.
Escritura de caché - TTL 1 horaLa primera solicitud, conservada durante una hora completa.
Lectura de caché - acierto o acierto de prefijo0.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.

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

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

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"
)
const hits = await mem.search(
  "...the conversation so far...", "alice", 10,
  { cache_control: { ttl: "5m" } },   // or "1h"
);
let hits = mem.search_with(
    "...the conversation so far...", "alice", 10,
    serde_json::json!({"cache_control": {"ttl": "5m"}}),   // or "1h"
).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice",
       "query":"...the conversation so far...",
       "cache_control":{"ttl":"5m"}}'   # or "1h"
Respuesta - el objeto cache informa de lo que pasó
{ "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.

La caché está aislada por almacén y por modelo dentro de tu espacio de trabajo, y está desactivada por defecto: sin cache_control, nada cambia en tus solicitudes.
Quién lo dijo

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.

Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.

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.

addRegistra a Bob una vez: POST /speakers, o add_speaker("Bob") en los SDK.

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.

BobBob dice que la fecha límite pasó al martes. Guárdalo con speaker "Bob".

El recuerdo ahora es de Bob: cada búsqueda que lo devuelve lo indica.

meTu asistente promete el resumen para el viernes. Guarda sus propias palabras con speaker "me".

Lo que dice el asistente también se recuerda, y "me" nunca cuenta para el límite.

askDespués: "¿qué dijo Bob sobre la fecha límite?" Busca con speaker "Bob".

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" devuelve 400 invalid_request_error en 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_error con speaker_limit: 50 en el cuerpo del error. Guardar con un nombre sin registrar también devuelve 400 y no guarda nada. Filtrar la búsqueda por un nombre sin registrar devuelve 404 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 Bob y bob son dos personas distintas. Los nombres llegan hasta 80 caracteres.
errors - verbatim
# POST /speakers past the limit
{ "type": "error",
  "error": { "type": "invalid_request_error",
             "message": "This store already has 50 registered speakers, ...",
             "speaker_limit": 50 } }

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

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

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

curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"Bob said the deadline moved to Tuesday","metadata":{"speaker":"Bob"}}'

curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_API_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did Bob say about the deadline?","speaker":"Bob"}'

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

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

La 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"]
const page = await mem.bySpeaker("Bob", undefined, { limit: 50 });
let page = mem.by_speaker("Bob", None, 50, None, None).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/by-speaker \
  -H "X-API-Key: $WOS_KEY" \
  -d '{"user_id":"alice","speaker":"Bob","limit":50}'
CampoQué hace
memoriesLas memorias, las más recientes primero. La misma forma que devuelve una búsqueda.
chunksFragmentos 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_beforeCursor 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 stay
await mem.listSpeakers();
await mem.bySpeaker("Bob");
await mem.removeSpeaker("Bob");
mem.list_speakers(None).await?;
mem.by_speaker("Bob", None, None, None, None).await?;
mem.remove_speaker("Bob", None).await?;
curl -X GET    .../api/v1/memory/speakers   -d '{"user_id":"alice"}'
curl -X POST   .../api/v1/memory/by-speaker -d '{"user_id":"alice","speaker":"Bob"}'
curl -X DELETE .../api/v1/memory/speakers   -d '{"user_id":"alice","speaker":"Bob"}'
Desarrolladores

Imá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.

Compatible con Tablet 2 y superiores. Un motor que no implementa imágenes lo indica nombrando el modelo, en lugar de responder un 404 a secas, de modo que se distingue una función ausente de una memoria ausente. JPEG, PNG, GIF y WebP.

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 memory
// the image rides in the 4th argument; the 3rd is metadata
await mem.add("at the beach", undefined, {}, { image: { data: b64 } });
await mem.add("", undefined, {}, { image: { data: b64 } });   // the image IS the memory
let img = json!({"image": {"data": b64}});
mem.add_with("at the beach", None, json!({}), img.clone()).await?;
mem.add_with("", None, json!({}), img).await?;
curl -X POST https://api.wontopos.com/api/v1/memory/store \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","content":"","image":{"data":"<base64>"}}'

data 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.

CampoQué hace
dataBase64 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.
referenceDónde está su propia copia del original. Se guarda como cadena de texto y nosotros nunca la descargamos.
taken_atRFC3339, 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)
const { bytes, contentType } = await mem.getImage(undefined, mid);
const page = await mem.listImages(undefined, { limit: 50 });
await mem.forgetImage(undefined, mid, { preview: true });
let (bytes, mime) = mem.get_image(None, mid).await?;
let page = mem.list_images(None, 50, None, None).await?;
mem.forget_image(None, mid, true).await?;
# original bytes — the one call on this plane that is not JSON
curl -X POST   .../api/v1/memory/image  -d '{"user_id":"alice","memory_id":"m_1"}'
curl -X POST   .../api/v1/memory/images -d '{"user_id":"alice","limit":50}'
curl -X DELETE .../api/v1/memory/image  -d '{"user_id":"alice","memory_id":"m_1","preview":true}'
LlamadaQué hace
get_imageLos 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_imagesUna 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_imageElimina 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.

ImagenTamaño contabilizadoTokens
700 × 700as sent881
1000 × 1000as sent1,797
1568 × 1568as sent4,417
1920 × 10801568 × 8822,485
2500 × 18751568 × 11763,313
2500 × 25001568 × 15684,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.

Las imágenes demasiado grandes o demasiado pequeñas se rechazan con un 400. No las redimensionamos por usted. Ambos lados deben medir ≥ 700 px y el lado largo ≤ 2.500 px. Por debajo de 700 px el modelo de embeddings cobra un mínimo fijo, así que una imagen más pequeña cuesta lo mismo almacenarla. Redimensione antes de enviar; el error indica tanto el tamaño recibido como el tamaño requerido.

Cuántas vuelven

Por defecto 1, máximo 5 imágenes por respuesta. Cinco imágenes rondan los 20.000 tokens.

CampoQué hace
max_imagesDe 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.
Un pie de imagen en inglés mejora las consultas en inglés y empeora las consultas en los demás idiomas - 11,4 puntos de recall@5 de media en catorce idiomas. Guarde las imágenes sin pie de imagen si sus usuarios buscan en más de un idioma.
Desarrolladores

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ó.

Compatible con Tablet 2 y superiores. Pedirlo a un motor que no lo implementa se rechaza antes de emitir la llamada, de modo que nunca se le cobra una pasada que no hizo nada.

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)
const hits = await mem.search("what did I eat", undefined, 10, { verify: 3 });
// the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)
let hits = mem.search_opts("what did I eat", None, 10,
                            &SearchOpts { verify: Some(3), ..Default::default() }).await?;
// the SDKs hand back the memories; `verify_used` is on the HTTP response (curl tab)
curl -X POST https://api.wontopos.com/api/v1/memory/search \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","query":"what did I eat","verify":3}'
# → {"memories":[…], "verify_used":1}

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

LlamadaQué hace
verifyDe 0 a 3. Pasadas adicionales permitidas. Un valor fuera de rango se rechaza con un 400 en lugar de ajustarse en silencio.
verify_usedCuá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.

Las pasadas extra pueden reducir la precisión cuando la primera ya contenía la respuesta - las preguntas de tipo single-session-user en LongMemEval-S bajan 4,2 puntos. La ganancia crece con la frecuencia con la que una sola recuperación falla, así que es mayor en stores grandes.
Complemento · Beta

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-mcp

El 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.

El complemento en sí es gratis y está publicado en npm: solo pagas el precio de uso normal por las llamadas a la API que haga. Requiere Node 18+ y una clave de la consola.

Abrir la página de desarrollador

Complemento

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.json

Qué 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.

Complemento

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.txt

Ponlo 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.

Model Context Protocol · Beta

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.

MCP está en beta. Las nueve herramientas funcionan hoy y están probadas, pero la superficie puede cambiar mientras la terminamos. La API y los SDK por debajo son estables y versionados.

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-project
# ~/.cursor/mcp.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# .vscode/mcp.json
{ "servers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# ~/.codeium/windsurf/mcp_config.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
# Claude Desktop and any other MCP host
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }

Add to Cursor →  ·  Add to VS Code →

Env 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=1 no 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

yourecuerda que lanzamos los viernes

El agente llama a la herramienta remember. Queda guardado de forma duradera: que termine la sesión no cambia nada.

new session¿cuándo lanzamos?

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" → remember lo guarda; la siguiente sesión ya lo sabe.
  • "¿Qué formato de error acordamos la semana pasada?" → recall trae 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 update para 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_memories recorre 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 filtro speaker por persona — y filters para 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.

Corre en local por stdio (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.
Model Context Protocol · Beta

Claude Code

La vía principal: un comando en tu terminal y cada sesión empieza con memoria.

  1. Crea una clave de API en la consola. La clave lleva su workspace: una clave = un espacio de memoria.
  2. Registra el servidor. --scope user lo hace disponible en todos los proyectos; sin él, solo lo ve el proyecto actual.
  3. Compruébalo: ejecuta /mcp dentro de Claude Code; wontopos debe aparecer con nueve herramientas.
  4. 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-project
Model Context Protocol · Beta

Claude 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" }
    } } }
Model Context Protocol · Beta

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" }
    } } }

Add to Cursor →

Model Context Protocol · Beta

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" }
    } } }

Add to VS Code →

Model Context Protocol · Beta

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" }
    } } }
Model Context Protocol · Beta

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-Key

Mismo store, misma memoria: lo que ChatGPT guarda por la Action, Claude Code lo recuerda por MCP, y al revés.

Model Context Protocol · Beta

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" }
    } } }
SDK de Python

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()
Respuesta real
[{"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
Respuesta real
{"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")
Respuesta real
{"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")
response
[{"content": "Bob said the deadline moved to Tuesday", "speaker": "Bob", ...}]
Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.

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")
Respuesta real
{"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")
Respuesta real
{"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)
Respuesta real (cuerpo HTTP)
[{
   "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"
 }]
CampoSignificado
similaritySimilitud de embedding bruta con tu consulta (0–1).
is_supersededTrue si este hecho fue reemplazado por update().
search_msTiempo 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")
Respuesta real (forma - listas acortadas)
{"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")
Respuesta real (cuerpo HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

Conteos de memoria de un usuario.

mem.stats("alice")
Respuesta real
{"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-...")
response
{"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}
Respuesta real
{"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)
response
{"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-...")
Respuesta real
{"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")
Respuesta real
{"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
})
Claves: categories · event_from / event_to (cuándo OCURRIÓ el contenido - metadata.event_date) · time_from / time_to (cuándo se escribió) · min_importance. Las claves no listadas se descartan, no se rechazan, así que una errata amplía la búsqueda en silencio.

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}")
Deriva la clave de aquello que se almacena (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
SDK de TypeScript

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();
Respuesta real
[{"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
Respuesta real
{"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");
Respuesta real
{"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" });
Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.

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");
Respuesta real
{"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");
Respuesta real
{"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);
Respuesta real (cuerpo HTTP)
[{
   "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"
 }]
CampoSignificado
similaritySimilitud de embedding bruta con tu consulta (0–1).
is_supersededTrue si este hecho fue reemplazado por update().
search_msTiempo 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");
Respuesta real (forma - listas acortadas)
{"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");
Respuesta real (cuerpo HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

Conteos de memoria de un usuario.

await mem.stats("alice");
Respuesta real
{"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-...");
response
{"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 });
response
{"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-...");
Respuesta real
{"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");
Respuesta real
{"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
});
Claves: categories · event_from / event_to (cuándo OCURRIÓ el contenido - metadata.event_date) · time_from / time_to (cuándo se escribió) · min_importance. Las claves no listadas se descartan, no se rechazan, así que una errata amplía la búsqueda en silencio.

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}` });
Deriva la clave de aquello que se almacena (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
SDK de Rust

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?;
Respuesta real
[{"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
Respuesta real
{"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?;
Respuesta real
{"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?;
Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.

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?;
Respuesta real
{"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?;
Respuesta real
{"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?;
Respuesta real (cuerpo HTTP)
[{
   "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"
 }]
CampoSignificado
similaritySimilitud de embedding bruta con tu consulta (0–1).
is_supersededTrue si este hecho fue reemplazado por update().
search_msTiempo 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?;
Respuesta real (forma - listas acortadas)
{"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?;
Respuesta real (cuerpo HTTP)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

Conteos de memoria de un usuario.

mem.stats("alice").await?;
Respuesta real
{"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?;
response
{"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?;
response
{"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?;
Respuesta real
{"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?;
Respuesta real
{"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?;
Claves: categories · event_from / event_to (cuándo OCURRIÓ el contenido - metadata.event_date) · time_from / time_to (cuándo se escribió) · min_importance. Las claves no listadas se descartan, no se rechazan, así que una errata amplía la búsqueda en silencio.

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?;
Deriva la clave de aquello que se almacena (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

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"}'
Respuesta real
{"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!"}'
Respuesta real
{"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"}'
Los hablantes son explícitos, como los almacenes. Registra primero a la persona y luego guarda bajo su nombre: una errata nunca se convierte en silencio en una persona nueva. Un almacén registra hasta 50 personas para empezar (pensamos subirlo), y "me" nunca necesita registro ni cuenta.

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"}'
Respuesta real
{"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"}'
Respuesta real
{"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"}'
Deriva la clave de aquello que se almacena (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}'
Respuesta real
{"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"}}'
Claves: categories · event_from / event_to (cuándo OCURRIÓ el contenido - metadata.event_date) · time_from / time_to (cuándo se escribió) · min_importance. Las claves no listadas se descartan, no se rechazan, así que una errata amplía la búsqueda en silencio.

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
Respuesta real
{"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?"}'
Respuesta real (forma)
{"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
Respuesta real
{"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, collected

list_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.

Engramas

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.

Ya disponible. Los engramas generales de abajo son pipelines de recuperación sin LLM, así que corren en todos los niveles desde Tablet 1 en adelante. Memoir y Archive, un modo de modelo aparte, se cubren en su propia sección más abajo.

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.

Todos los engramas Engramas

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"
Recordado como una persona · una narrativa

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"
Conservado como registro · tiempo preciso

Devuelve las coincidencias como registros exactos - tiempo transcurrido preciso y anclas absolutas, estructurado para que un modelo lo lea de inmediato.

Renderiza memorias que ya almacenaste - no las crea. Cada memoria es una llamada 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 time
// the memoir form on search — and on recall, the LLM's one-call context
const s = await mem.withModel("scroll-1.2").search("what does Alice drink?", "alice", 10, { form: "memoir", tz: 9 });
const ctx = await mem.withModel("scroll-1.2").recall("what does Alice drink?", "alice", { form: "memoir", tz: 9 });
// form on search AND recall — the _with helpers merge extra fields into the body
let s = mem.with_model("scroll-1.2").search_with("what does Alice drink?", "alice", 10, json!({"form": "memoir", "tz": 9})).await?;
let ctx = mem.with_model("scroll-1.2").recall_with("what does Alice drink?", "alice", json!({"form": "memoir", "tz": 9})).await?;
# same X-WOS-Form header on /search, /recall, or /engram/run
curl -X POST https://api.wontopos.com/api/v1/memory/recall \
  -H "X-API-Key: wos-live-..." -H "X-WOS-Model: scroll-1.2" -H "X-WOS-Form: memoir" -H "X-WOS-Timezone: 9" \
  -d '{"user_id":"alice","query":"what does Alice drink?"}'
# every memory comes back with a "time" field; use X-WOS-Form: archive for exact time

tz 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:

Resultado · form: memoir
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "a couple weeks ago" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday afternoon" },
  { "content": "Alice moved to Brooklyn",          "time": "about half a year ago" }
] }
Resultado · form: archive
{ "count": 3, "memories": [
  { "content": "Alice prefers tea over coffee", "time": "2 weeks ago (Jun 09)" },
  { "content": "met Alice at the cafe downtown",  "time": "yesterday at 14:00" },
  { "content": "Alice moved to Brooklyn",          "time": "6 months ago (Dec 2025)" }
] }
TranscurridoMemoirArchive
3 mina few minutes ago3 minutes ago
14 minabout 15 minutes ago14 minutes ago
30 minhalf an hour ago30 minutes ago
50 minabout an hour ago50 minutes ago
2 ha couple hours ago2 hours ago, at 13:10
8 hthis morning8 hours ago, at 07:10
ayer p. m.yesterday afternoonyesterday at 14:00
anochelast night17 hours ago, at 22:00
2 díasa couple days ago2 days ago (Tue 15:10)
6 díasseveral days ago6 days ago (Fri 15:10)
9 díasabout a week agolast week (Jun 16)
16 díasa couple weeks ago2 weeks ago (Jun 09)
35 díasabout a month agolast month (May 21)
60 díasa couple months ago2 months ago (Apr 2026)
180 díasabout half a year ago6 months ago (Dec 2025)
380 díasabout a year agolast year (Jun 2025)
800 díasa couple years ago2 years ago (Apr 2024)
1500 díasabout 4 years ago4 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.

Memoir y Archive renderizan cada recall de la respuesta - una búsqueda simple, un recall o un engrama. El nivel del modelo (Tablet → Scroll → Book) define cuánto hace el motor; la forma (memoir / archive) define cómo escribe el tiempo. Disponible en Scroll 1.2 y superiores.
Todos los engramas Engramas

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")
const out = await mem.engram("deep_recall", "what should I know about Alice?", "alice");
let out = mem.engram("deep_recall", "what should I know about Alice?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"deep_recall","user_id":"alice","query":"what should I know about Alice?"}'
Respuesta
{ "engram": "deep_recall", "hops": 2, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 200, "output_tokens": 589 } }
Se mide por tokens - cada llamada devuelve 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 engramas Engramas

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")
const events = await mem.engram("timeline", "project milestones", "alice");
let events = mem.engram("timeline", "project milestones", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"timeline","user_id":"alice","query":"project milestones"}'
Respuesta
{ "engram": "timeline", "hops": 1, "count": 15,
  "memories": [ ... ],
  "usage": { "input_tokens": 100, "output_tokens": 736 } }
Se mide por tokens - cada llamada devuelve 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 engramas Engramas

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")
const related = await mem.engram("gather", "everything about Project Atlas", "alice");
let related = mem.engram("gather", "everything about Project Atlas", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"gather","user_id":"alice","query":"everything about Project Atlas"}'
Respuesta
{ "engram": "gather", "hops": 4, "count": 18,
  "memories": [ ... ],
  "usage": { "input_tokens": 400, "output_tokens": 637 } }
Se mide por tokens - cada llamada devuelve 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 engramas Engramas

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")
const wide = await mem.engram("equilibrium", "how have things been lately?", "alice");
let wide = mem.engram("equilibrium", "how have things been lately?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"equilibrium","user_id":"alice","query":"how have things been lately?"}'
Respuesta
{ "engram": "equilibrium", "hops": 3, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 300, "output_tokens": 293 } }
Se mide por tokens - cada llamada devuelve 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 engramas Engramas

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")
await mem.add("I keep answers short unless you ask for detail.", "alice", { speaker: "me" });
const mine = await mem.engram("tone_stabilizer", "how do I usually answer?", "alice");
mem.add("I keep answers short unless you ask for detail.", "alice", json!({"speaker": "me"})).await?;
let mine = mem.engram("tone_stabilizer", "how do I usually answer?", "alice").await?;
curl -X POST https://api.wontopos.com/api/v1/engram/run \
  -H "X-API-Key: wos-live-..." -H "Content-Type: application/json" \
  -d '{"name":"tone_stabilizer","user_id":"alice","query":"how do I usually answer?"}'
Respuesta
{ "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 } }
Se mide por tokens - cada llamada devuelve 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.
API HTTP

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

EncabezadoQué hace
X-API-KeyObligatorio en cada llamada. Tu clave, emitida en la consola.
X-WOS-ModelOpcional. 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-KeyOpcional, 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

EndpointPropósitoCampos del cuerpo
POST /api/v1/memory/collectioncrear un storeuser_id
GET /api/v1/memory/collectionslistar tus stores(ninguno)
DELETE /api/v1/memory/collectioneliminar un store + sus memoriasuser_id
/api/v1/memory/storealmacenar una memoriauser_id · content · metadata? (event_date · speaker) · image?
/api/v1/memory/store-turnalmacenar un turno de conversaciónuser_id · user_msg · assistant_msg
POST /api/v1/memory/speakersregistrar un hablante (explícito, hasta 50)user_id · speaker
GET /api/v1/memory/speakerslistar hablantes registrados + conteosuser_id
DELETE /api/v1/memory/speakersdar de baja un hablante (los recuerdos quedan)user_id · speaker
/api/v1/memory/by-speakerlo que dijo una persona, de lo más reciente («me» = el agente)user_id · speaker · limit? · before? · skip_ids?
POST /api/v1/memory/imagelos bytes originales de una memoria con imagenuser_id · memory_id
DELETE /api/v1/memory/imagequitar la imagen y conservar el textouser_id · memory_id · preview?
/api/v1/memory/imageslas imágenes de un store, de lo más reciente (+ el total)user_id · limit? · before? · skip_ids?
/api/v1/memory/lineagela cadena de ediciones de una memoria, de lo más antiguouser_id · memory_id
/api/v1/won/revisionscuánto de un store se ha reescrito. Gratisuser_id · include? · limit? · before? · skip_ids?
/api/v1/memory/revisionsla misma llamada bajo el plano memory. Gratisuser_id · include? · limit? · before? · skip_ids?
/api/v1/memory/bulk-storerellenar un bloque de textouser_id · content · category? · timestamp?
/api/v1/memory/searchbúsqueda semánticauser_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images?
/api/v1/memory/recallcorto + largo + contextouser_id · query · limit? · context_limit?
/api/v1/memory/getuna memoria por iduser_id · memory_id
/api/v1/memory/listrecorrer un almacén por páginasuser_id · limit? · cursor?
/api/v1/memory/historyturnos recientesuser_id
/api/v1/memory/statsconteos de memoriauser_id
/api/v1/memory/supersedereemplazar un hecho que cambióuser_id · old_memory_id · new_content
/api/v1/memory/forgeteliminar una (o todas)user_id · memory_id? (omitir = eliminar todo)
GET /api/v1/engramengramas que este modelo puede ejecutar(ninguno)
POST /api/v1/engram/runejecutar un engramaname · user_id · query · form? · tz?
GET /api/v1/modelsmodelos disponibles(ninguno)
Las escrituras aceptan una cabecera 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?"}'
Respuesta real - store
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Niveles de uso

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 usoCompra de créditoLí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 - EnterpriseHabla con nosotrosSin 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.

NivelSolicitudes por minuto
Tier 1150
Tier 2300
Tier 3600
Tier 41,500
Tier 53,000
Tier 6 - EnterprisePersonalizado

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.

El precio se basa en el uso: tokens más una tarifa fija de $0.0001 por solicitud. Tablet cuesta $2 por 1M de tokens de entrada, $3 por 1M de salida. El almacenamiento es gratis y sin topes. Mira por qué fijamos el precio así.
Errores & límites

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.

Respuesta real - clave inválida (HTTP 401)
{"type": "error", "error": {
   "type": "authentication_error",
   "message": "Invalid or revoked API key.",
   "request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
 }}
HTTPSignificadoQué hacer
400Cuerpo mal formado (campo faltante o de tipo incorrecto)El mensaje indica el campo exacto - corrige y reintenta.
401Clave de API inválida o revocadaVerifica la clave; emite una nueva en la consola.
402Saldo agotado, sin tarjeta registrada, o tope de nivel alcanzadoRecarga 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.
404No existe esa memoria, store o imagenVerifica el id. get_image también responde 404 cuando la memoria existe pero no lleva ninguna imagen.
409Ese nombre ya está en usoLos nombres de store y de workspace son únicos dentro de una cuenta - elige otro.
413Cuerpo de la solicitud por encima de 10MBBase64 ocupa alrededor de un 33% más que el archivo que codifica, así que redimensiona la imagen antes de codificarla.
429Límite de velocidad excedidoEl 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.
501El motor de este modelo no implementa ese endpointLas imágenes y el historial de revisiones necesitan un motor más reciente. GET /api/v1/models lista qué modelos sirven qué.
5xxProblema del lado del servidorReintenta 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á.

Seguridad de la clave. Tu clave se muestra una sola vez al crearla y de nuestro lado se guarda solo como hash. Mantenla en una variable de entorno; si se filtra, revócala en la consola - la revocación es inmediata.

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.

Desarrolladores

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.

Compatible con Tablet 2 y superiores. Solo lectura. A diferencia de 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"])
const { chain } = await mem.lineage(undefined, mid);
for (const step of chain) {
  console.log(step.changed_at, step.action, step.content);
}
let r = mem.lineage(None, mid).await?;
for step in r["chain"].as_array().unwrap_or(&vec![]) {
    println!("{} {} {}", step["changed_at"], step["action"], step["content"]);
}
curl -X POST https://api.wontopos.com/api/v1/memory/lineage \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice","memory_id":"m_9"}'
200
{ "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 }
  ] }
CampoQué hace
chainLas 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_atCuándo se reemplazó esta versión (RFC3339), o null mientras siga vigente.
actionQué ocurrió en este eslabón - cómo se relacionó el reemplazo con esta versión.
confidenceCuánta certeza tenía el motor sobre esa relación, de 0 a 1.
is_currentTrue para la única versión que sigue vigente. Exactamente una por cadena.
truncatedTrue 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

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.

LlamadaQué hace
POST /won/revisionsCuá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."
const r = await 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.";
}
let r = mem.revisions(None).await?;
let (rev, tot) = (r["revised"].as_f64().unwrap_or(0.0),
                r["total"].as_f64().unwrap_or(1.0));
if rev / tot > 0.1 { /* say so in the system prompt */ }
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" -H "Content-Type: application/json" \
  -d '{"user_id":"alice"}'

# → {"user_id":"alice","revised":3,"total":40,
#     "counts":"memories a transform has touched (supersede, update, retract, image removed)",
#     "excludes":"deletions — a deleted memory leaves nothing to count"}

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

ReglaValor
PrecioNinguno. Las llamadas gratuitas se saltan los controles de facturación - sin cargo por tokens, sin tarifa por solicitud y sin registro de uso.
Por minuto10 por minuto, por cuenta y por endpoint. Gastar el minuto de un endpoint no gasta el de otro.
Por hora300 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 pagoSeparados 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.

429 rate_limit_error
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." } }
La misma llamada responde también en /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.
Won · revisions

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.

Compatible con Tablet 2 y superiores. Se puede llamar por la API HTTP, desde los SDK de Python, TypeScript y Rust, y como herramienta MCP. Los motores anteriores responden 501 y nombran el modelo que no puede atenderlo.

Recuentos

Cuenta lo que tocó una transformación - reemplazado, actualizado, retractado y imágenes eliminadas.

mem.revisions()
# {"revised": 3, "unrevised": 37, "total": 40, …}
await mem.revisions();
mem.revisions(None).await?;
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" -d '{"user_id":"alice"}'
CampoQué significa
revisedMemorias que ha tocado una transformación.
unrevisedMemorias 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.
totalMemorias que hay en el store.
counts / excludesFrases 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"]
const page = await mem.revisions(undefined, { include: "revised", limit: 20 });
let page = mem.revisions_page(None, "revised", 20, None, None).await?;
curl -X POST https://api.wontopos.com/api/v1/won/revisions \
  -H "X-API-Key: $WOS_KEY" \
  -d '{"user_id":"alice","include":"revised","limit":20}'
CampoQué 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.
limitDe 5 a 20, por defecto 20. Un valor fuera de rango, o de tipo incorrecto, se rechaza en lugar de ajustarse.
matchedTotal de filas que hay detrás de esta página, no el tamaño de la página.
ordered_byEl servicio declara su propio orden: primero lo guardado más recientemente, no lo editado más recientemente.
next_beforeCursor para la página siguiente, junto con next_skip_ids. Devuelva los dos; los ids se acumulan de una página a otra.
La lista se ordena por cuándo se guardó una memoria, no por cuándo se modificó. Quien asuma "lo editado más recientemente primero" leerá mal la página, y por eso la respuesta indica cuál de los dos órdenes usa.
Las eliminaciones no se cuentan. Una memoria borrada no deja nada que contar, así que un store muy podado sigue reportando un revised bajo. Este número te dice cuánto se reescribió, no cuánto ha desaparecido.
Escala

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.

Privacidad

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.