Quem disse

Memória que sabe quem disse.

As pessoas lembram por pessoa: o que o Bob prometeu, o que você disse que faria. Marque cada memória com um falante e seu agente faz o mesmo, em todos os modelos Tablet e Scroll.

Falantes são explícitos, como armazenamentos. Registre a pessoa primeiro e depois salve sob o nome dela: um erro de digitação nunca vira silenciosamente uma pessoa nova. Um armazenamento registra até 50 pessoas para começar (planejamos aumentar), e "me" nunca precisa de registro nem conta.

Um time, três memórias

Um armazenamento mantém muitas vozes separadas. Registre uma pessoa uma vez, salve cada fala com seu falante e depois pergunte por pessoa.

addRegistre o Bob uma vez: POST /speakers, ou add_speaker("Bob") nos SDKs.

O armazenamento agora conhece o Bob. O limite de 50 é contado aqui, no registro; chamadas de salvamento nunca retornam erro de limite.

BobBob diz que o prazo mudou para terça. Salve com speaker "Bob".

A memória agora é do Bob: toda busca que a devolve indica isso.

meSeu assistente promete o resumo até sexta. Salve as próprias palavras com speaker "me".

A fala do próprio assistente também vira memória, e "me" nunca conta para o limite.

askDepois: "o que o Bob disse sobre o prazo?" Busque com speaker "Bob".

Só as palavras do Bob voltam. As palavras de uma pessoa nunca voltam como as de outra.

Três regras para lembrar

  • "me" é o próprio assistente. Nunca registrado, nunca contado. Reservado e minúsculo: speaker: "Me" ou "ME" retorna 400 invalid_request_error em vez de ser convertido em silêncio.
  • O limite é contado no registro: 50 por armazenamento para começar. Registrar além disso retorna 400 invalid_request_error com speaker_limit: 50 no corpo do erro. Salvar com um nome não registrado também retorna 400 e nada é salvo. Filtrar a busca por um nome não registrado retorna 404 not_found_error. Ramifique pelo status e pelos campos, não pelo texto da mensagem; planejamos aumentar o limite.
  • Rótulos vivem em toda leitura. Resultados de busca, o contexto de longo prazo do recall e resultados de engram carregam seu falante — o modelo sempre sabe de quem são as palavras. Passe speaker numa busca para obter só as de uma pessoa. Um supersede mantém o falante; forget o remove.
  • Nomes são Unicode: qualquer idioma funciona. さくら, Иван e 하늘 são falantes válidos, e a atribuição se comporta igual em todos os idiomas. A correspondência é exata após trim e normalização Unicode, então Bob e bob são duas pessoas. Nomes vão até 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" } }

Duas notas de escopo. speaker acompanha add / store: add_turn lembra a troca inteira, e rótulos por pessoa e o filtro vêm de memórias com speaker explícito. E passagens de sessão (expand) são compostos de várias memórias, então não carregam rótulo; um filtro speaker sempre devolve memórias atômicas e rotuladas. E uma escrita cujo significado esteja próximo o suficiente de uma memória já guardada é descartada: a correspondência é semântica, não textual. Esse store devolve status "duplicate" com uma nota explícita, não guarda nada e não anexa falante. Um facto genuinamente novo que varie apenas num detalhe de um existente ("alergia a marisco" depois de "alergia a amendoim") cai na mesma regra, por isso leia status em vez de assumir que a escrita foi concluída.

Testamos do jeito difícil: memórias sem nomes no texto, lembradas por pessoa. A atribuição vem do registro de falantes, não de casamento de palavras, então funciona igual em qualquer idioma.

Como usar

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 }

A lista mostra quem o armazenamento conhece, com contagens por pessoa contra o limite. Remover apaga só o registro: as memórias ficam, apenas o rótulo do nome vai embora.

Ler as memórias de uma pessoa

by_speaker retorna o que uma pessoa disse, da mais recente para a mais antiga, sem consulta. "me" devolve as próprias palavras do assistente. A paginação por cursor é a mesma das imagens: devolva next_before e next_skip_ids.

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}'
CampoO que faz
memoriesAs memórias, da mais recente para a mais antiga. O mesmo formato que uma busca retorna.
chunksOs fragmentos em nível de frase por trás dessas memórias - o que uma exclusão de fato removeria. Normalmente maior que o número de memórias; exiba-o antes de alguém confirmar uma exclusão. Também informado como points_to_delete.
next_beforeCursor para a próxima página, junto com next_skip_ids. Os dois são necessários porque memórias podem compartilhar o mesmo timestamp.
speaker aqui é a etiqueta gravada no momento do armazenamento, não uma busca sobre o texto. Uma memória armazenada sem falante é alcançável por busca, mas nunca por by_speaker, inclusive sob "me".

Listar, navegar e remover falantes

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