Memoria que sabe quién lo dijo.
Las personas recuerdan por persona: qué prometió Bob, qué dijiste que harías. Etiqueta cada recuerdo con un hablante y tu agente hará lo mismo, en todos los modelos Tablet y Scroll.
Un equipo, tres recuerdos
Un almacén mantiene separadas muchas voces. Registra a una persona una vez, guarda cada comentario con su hablante y luego pregunta por persona.
El almacén ya conoce a Bob. El límite de 50 se cuenta aquí, al registrar; las llamadas de guardado nunca devuelven un error de límite.
El recuerdo ahora es de Bob: cada búsqueda que lo devuelve lo indica.
Lo que dice el asistente también se recuerda, y "me" nunca cuenta para el límite.
Solo vuelven las palabras de Bob. Las palabras de una persona nunca vuelven como las de otra.
Tres reglas para recordar
- "me" es el propio asistente. Nunca se registra ni cuenta. Reservado y en minúsculas:
speaker: "Me"o"ME"devuelve400 invalid_request_erroren lugar de convertirse en silencio. - El límite se cuenta al registrar: 50 por almacén para empezar. Registrar por encima devuelve
400 invalid_request_errorconspeaker_limit: 50en el cuerpo del error. Guardar con un nombre sin registrar también devuelve400y no guarda nada. Filtrar la búsqueda por un nombre sin registrar devuelve404 not_found_error. Ramifica por el código y los campos, no por el texto del mensaje; planeamos subir el límite. - Las etiquetas viven en cada lectura. Los resultados de búsqueda, el contexto de largo plazo de recall y los resultados de engram llevan su hablante, así que el modelo siempre sabe de quién son las palabras. Pasa speaker en una búsqueda para obtener solo las de una persona. Un supersede conserva el hablante; forget lo elimina.
- Los nombres son Unicode: cualquier idioma funciona. さくら, Иван y 하늘 son hablantes válidos, y la atribución se comporta igual en todos los idiomas. La coincidencia es exacta tras recortar y normalizar Unicode, así que
Bobybobson dos personas distintas. Los nombres llegan hasta 80 caracteres.
# POST /speakers past the limit { "type": "error", "error": { "type": "invalid_request_error", "message": "This store already has 50 registered speakers, ...", "speaker_limit": 50 } } # store with an unregistered name → 400, nothing stored { "type": "error", "error": { "type": "invalid_request_error", "message": "speaker 'Bob' is not registered in this store. Register it first: ...", "speaker": "Bob" } } # search filtered by an unregistered name → 404 { "type": "error", "error": { "type": "not_found_error", "message": "speaker 'Bob' is not registered in this store.", "speaker": "Bob" } }
Dos notas de alcance. speaker va en add / store: add_turn recuerda el intercambio completo, y las etiquetas por persona y el filtro vienen de recuerdos con speaker explícito. Y los pasajes de sesión (expand) son compuestos de varios recuerdos, así que no llevan etiqueta; un filtro speaker siempre devuelve recuerdos atómicos y etiquetados. Y una escritura cuyo significado se acerca lo suficiente a una memoria ya guardada se descarta: la coincidencia es semántica, no textual. Ese store devuelve status "duplicate" con una nota explícita, no guarda nada y no adjunta hablante. Un hecho genuinamente nuevo que solo varía en un detalle de uno existente ("alergia al marisco" tras "alergia a los cacahuetes") cae bajo la misma regla, así que lea status en lugar de suponer que la escritura se realizó.
Lo probamos de la forma difícil: recuerdos guardados sin nombres en el texto, recuperados por persona. La atribución viene del registro de hablantes, no de coincidencias de palabras, así que se comporta igual en todos los idiomas.
Cómo usarlo
mem.add_speaker("Bob", user_id="alice") # once per person; "me" needs no registration
mem.add("Bob said the deadline moved to Tuesday", user_id="alice", speaker="Bob")
mem.add("I promised the summary by Friday", user_id="alice", speaker="me")
hits = mem.search("what did Bob say about the deadline?", user_id="alice", speaker="Bob")
mem.list_speakers(user_id="alice")
mem.remove_speaker("Bob", user_id="alice") # memories stay, the tag goes{ "memories": [
{ "content": "Bob said the deadline moved to Tuesday",
"speaker": "Bob", ... } ] }{ "user_id": "alice",
"speakers": [ { "speaker": "Bob", "memories": 2, "created_at": "2026-07-10T04:20:39Z" } ],
"count": 1, "limit": 50 }La lista muestra a quién conoce el almacén con conteos por persona frente al límite. Quitar solo borra el registro: sus recuerdos quedan, solo se va la etiqueta.
Leer las memorias de una sola persona
by_speaker devuelve lo que dijo una persona, las más recientes primero, sin consulta. "me" devuelve las palabras del propio asistente. La misma paginación por cursor que las imágenes: devuelva next_before y next_skip_ids en la llamada siguiente.
page = mem.by_speaker("Bob", limit=50)
page["memories"], page["chunks"]| Campo | Qué hace |
|---|---|
| memories | Las memorias, las más recientes primero. La misma forma que devuelve una búsqueda. |
| chunks | Fragmentos a nivel de frase que hay detrás de esas memorias - lo que una eliminación borraría realmente. Suele ser mayor que el número de memorias. Muéstrelo antes de que alguien confirme una eliminación. También se informa como points_to_delete. |
| next_before | Cursor para la página siguiente, junto con next_skip_ids. Hacen falta los dos porque varias memorias pueden compartir la misma marca de tiempo. |
speaker aquí es la etiqueta escrita al guardar, no una búsqueda sobre el texto. Una memoria guardada sin hablante es accesible por búsqueda, pero nunca por by_speaker, tampoco bajo "me".Listar hablantes, recorrerlos y darlos de baja
mem.list_speakers() # who is registered
mem.by_speaker("Bob") # what Bob said, newest first
mem.remove_speaker("Bob") # unregister; the memories stay