Por que o WOS

Memória de longo prazo para agentes de IA.

O WOS é uma API de memória. Você armazena as memórias de um usuário uma única vez e depois recupera apenas as relevantes para cada consulta, passando-as ao prompt do seu modelo.

A recuperação é puramente semântica, sem correspondência por palavras-chave nem BM25, então a qualidade do recall é idêntica em todos os idiomas. Cada consulta retorna um contexto pequeno e limitado, não importa quanto você tenha armazenado, e nenhum modelo é executado sobre as suas memórias armazenadas.

Operações principais

  • store - salva uma memória para um usuário.
  • recall - obtém as memórias relevantes para uma consulta. Esta é a chamada principal.
  • search - busca semântica bruta sobre as memórias armazenadas.
  • supersede - atualiza ou substitui uma memória desatualizada.
  • forget - exclui uma única memória ou um usuário inteiro (GDPR).
Escolha uma seção à esquerda para ver os detalhes de cada tópico.
Modelo

Três modelos, uma mesma linhagem.

Os modelos WOS recebem nomes das formas como as pessoas preservaram o conhecimento ao longo da história - Tablet, Scroll, Book. Pedra, pergaminho, livro encadernado: cada um faz mais pelo seu agente do que o anterior.

Tablet

Disponível
Gravado em pedra · store & recall

Uma forma enxuta, rápida e de baixo custo de inscrever e recuperar memória - a base sobre a qual todos os modelos são construídos.

Scroll

Disponível
Desenrolado · recall assistido por LLM

Adiciona um modelo de linguagem para ler sua pergunta com mais atenção e trazer de volta um contexto mais completo, de modo que evidências dispersas voltem reunidas, em vez de faltar uma peça.

Book

Em breve
Encadernado & indexado · roteamento próprio

Abre sozinho na página certa - escolhendo a memória e as ferramentas de que cada momento precisa, e ficando mais afiado quanto mais é usado.

O relatório completo de benchmark do Tablet 1 está na página de benchmarks.

Custo

Pague $2 para nós. Economize muitas vezes esse valor no seu LLM.

O WOS entrega ao seu LLM ~1,200 tokens por consulta - uma fatia limitada e relevante - em vez de enfiar o histórico completo em cada prompt. A diferença é enorme, e cresce junto com o seu histórico.

Custo do LLM por 1,000 consultas Com base no Tablet 1
Histórico do usuário100K
Consultas / mês1,000
Seu LLM
45× mais barato - você economiza $244/mês
Sem WOS$250.00
Com WOS$5.50

Cada $1 gasto no WOS economiza ~$98 no LLM. Histórico maior ou modelo mais caro → ROI maior.

De onde vem a economia

  • Sem o WOS, você enfia o histórico inteiro em cada prompt - 100K tokens × $2.50/1M = $0.25 por consulta, às tarifas de entrada do GPT-4o (cerca de 2× isso em modelos do nível do Opus).
  • Com o WOS, você ingere uma única vez ($2/1M) e cada consulta passa a ser uma pequena recuperação ($3/1M × 1,200) mais o seu LLM sobre apenas ~1,200 tokens.
  • Quanto menos tokens o seu LLM lê, menos você paga - e o WOS mantém esse número estável conforme a memória cresce.
Redução de contexto = histórico ÷ tokens entregues, não custo (a calculadora acima precifica cada recuperação).  25K → 21× · 100K → 83× · 200K → 167×.
Multilíngue

Todos os idiomas, a mesma precisão.

A recuperação é puramente semântica - apenas embeddings, zero correspondência por palavras-chave ou BM25. Então a qualidade do recall é idêntica, quer seus usuários escrevam em 日本語, 中文, Español ou inglês.

A correspondência lexical, como o BM25, é ajustada ao formato de um idioma específico - morfologia, espaçamento, escrita. Em um store multilíngue, isso significa que a qualidade da recuperação varia por idioma. O WOS não usa nenhuma correspondência lexical, então todos os idiomas passam pelo mesmo caminho.

Um store, três idiomas ao mesmo tempo

Você não escolhe um idioma por store - misture-os livremente. Abaixo, a memória de um único usuário contém japonês, inglês e espanhol ao mesmo tempo, e cada pergunta encontra a memória certa independentemente do idioma. Esta é uma interação real com a API em produção:

# 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 reais - cada pergunta cruza para um idioma diferente
"¿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

Sem etapa de tradução, sem detecção de idioma, sem configuração por idioma. Memórias e perguntas são posicionadas pelo significado, não pelo idioma - se o significado corresponde, o idioma não importa.

Três idiomas aqui é apenas o que cabe em uma página - não existe uma lista de idiomas suportados da qual fazer parte. O mesmo teste ao vivo também passa com memórias em 中文, Русский e العربية, todas verificadas contra a API de produção.

Por que banimos palavras-chave de propósito

A pontuação lexical, como o BM25, fortalece a recuperação para alguns idiomas mais do que para outros, o que atrapalha quando um store contém muitos idiomas. Por isso, nós a removemos completamente do motor e aplicamos essa regra na revisão de código: com qualquer pontuação lexical no caminho, a qualidade do recall variaria por idioma.

O LongMemEval é somente em inglês, então não mede recall multilíngue. A demonstração acima é como você pode verificar isso diretamente contra a API em produção.
Arquitetura

Nenhum modelo é executado sobre suas memórias.

O armazenamento é literal e o motor busca por embeddings - barato, rápido e determinístico. Um modelo nunca é executado sobre as suas memórias armazenadas. O Tablet não usa modelo algum; o Scroll e o Book adicionam um ao redor do motor para resultados mais fortes, mas ele só vê a sua consulta, nunca o que você armazenou.

  • Motor determinístico. O motor retorna as mesmas memórias para a mesma consulta, todas as vezes - e é por isso que a variância do nosso benchmark vem apenas do modelo leitor.
  • Barato em escala. Sem custo de geração para armazenar ou recuperar, então sua conta acompanha o armazenamento - não o uso de modelo - conforme a memória cresce.

Suas palavras, intocadas

Um design comum executa um modelo de linguagem no momento da escrita para extrair e reescrever "fatos" do texto. Esse design troca três coisas: custo de geração em cada escrita, latência adicional e o armazenamento da paráfrase de um modelo em vez das palavras originais. O WOS faz a troca oposta - armazena o que foi dito, sem alterações, e deixa que o seu LLM faça a interpretação no momento da leitura, com o texto original em mãos.

O que o WOS não é: não é um banco vetorial que você precisa operar, nem um framework de RAG que você precisa montar. Nenhum modelo é executado sobre seus dados armazenados - esse caminho é puramente embeddings. O Scroll e o Book usam, sim, um modelo de linguagem para resultados mais fortes, mas ele só vê a sua consulta, nunca suas memórias armazenadas - e ele nunca treina com seus dados nem os coleta.
Prova

67,5 %, medido e reproduzível.

67,5 % no BEAM 1M, média de 5 execuções independentes (σ 0,22 %, nenhuma escolhida a dedo), avaliado por gpt-4.1-mini com o prompt de julgamento do próprio benchmark.

No mesmo benchmark, as pontuações variam muito conforme o protocolo de avaliação: o juiz, o prompt e o que a camada de recuperação pode fazer. Avaliamos com o juiz que o próprio repositório dos autores traz, usamos o prompt de julgamento deles como está, não mudamos nada para caber no teste e publicamos o harness, o código de pontuação e o prompt do leitor, para que qualquer um reproduza exatamente os 67,5 %.

O protocolo, em uma única tabela

ItemO que fazemos
Conjunto de dadosBEAM 1M - 35 conversas, 74.630 turnos, 2,2 milhões de memórias, 700 perguntas
Juizgpt-4.1-mini com temperature 0, rodando o prompt de julgamento do próprio BEAM - o padrão no repositório dos autores, não um juiz escolhido por nós
Execuções5 execuções independentes, todas as pontuações publicadas, média reportada (σ 0,22 %)
LeitorModelo leitor e prompt fixos, publicados na íntegra

O que mantém a honestidade: um juiz terceiro, o prompt do leitor publicado sem alterações, recuperação puramente semântica e todas as execuções reportadas - não apenas a melhor. O motor de recuperação é determinístico - execute de novo e você obtém as mesmas memórias.

Escalamos benchmarks mais difíceis

Testamos no benchmark padrão mais difícil que ainda não conquistamos - e o número é a marca máxima entre todos os modelos WOS, reescrita sempre que um modelo melhor é lançado. Ao superar 94%, avançamos para um benchmark mais difícil.

BEAM 1MEm andamento
Tablet67.5%
juiz gpt-4.1-mini · média de cinco execuções94% para avançar
Benchmark anterior LongMemEval-S Superado
Tablet95.7%
Scroll92.3%
Juiz GPT-4o · melhor entre todos os modelos WOS94% para avançar
Veja o relatório completo
Preços

Duas tarifas de token por modelo,
mais $0.0001 por requisição.

Por milhão de tokens mais uma taxa fixa de $0.0001 por requisição, pagamento conforme o uso. Sem assinatura, sem aluguel de armazenamento, sem limites de memória. Você paga quando seu agente escreve ou lê - nunca pelo que ele lembra.

ModeloEntrada / 1MSaída / 1M
Tablet$2$3Disponível
Scroll$4$8Disponível
Book--A definir
  • $0.0001 por requisição. Uma taxa fixa em cada chamada de API, além do uso de tokens.
  • O armazenamento é gratuito. A ingestão paga uma única vez; manter os dados não custa nada. Sem limite de quantidade, sem limite de retenção.
  • Nós armazenamos. Nunca treinamos com os dados, os usamos ou olhamos para eles. A memória do seu agente é sua - nós apenas a organizamos para que você possa recuperá-la.
  • Por que o Tablet é tão barato: seu motor não executa modelo algum, então nosso custo é embeddings e disco - não GPUs. O Scroll e o Book adicionam um modelo, e é isso que o preço mais alto deles cobre.
Outros modelos de cobrança cobram mensalmente pelo volume armazenado ou limitam a quantidade de memórias por plano. O WOS não cobra nada pelos dados armazenados, independentemente do volume ou da idade.

Limites de requisições por nível de uso →

Para desenvolvedores

Três chamadas: armazenar, recuperar, responder.

Uma única API. A chamada recall() retorna contexto de curto prazo, de longo prazo e do entorno em uma única ida e volta, pronto para inserir no seu prompt.

1

Armazenar

add() guarda fatos e trocas: as palavras do seu usuário, as do próprio assistente (speaker "me") ou as de uma pessoa nomeada. Incorporado na entrada, sem chamada de LLM.

2

Recuperar

recall() retorna curto prazo + longo prazo + contexto em uma única chamada - um contexto limitado e de tamanho fixo.

3

Responder

Entregue esse contexto limitado ao seu LLM - qualquer provedor, sua chave.

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

Memórias carregam um falante. O padrão são as palavras do seu usuário, speaker "me" guarda o que o próprio assistente disse, e um nome como "Bob" lembra quem disse, permitindo lembrar por pessoa.

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.
Início rápido

Seu primeiro recall em 5 minutos.

Uma chave, uma linha de instalação, três chamadas - seu agente tem memória. Cada trecho de código nesta página foi realmente executado; as respostas são mostradas na íntegra.

1

Obtenha uma chave de API

Crie uma no console. Uma chave de 155 caracteres que começa com wos-live- é exibida uma única vez. Guarde-a em uma variável de ambiente - nunca no código.

2

Instale

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

Crie um store, depois armazene & recupere

Um store é o user_id sob o qual você lê e escreve. Stores são explícitos: crie um primeiro (a chamada abaixo), depois armazene e recupere sob ele. Armazenar - embeddings gerados na entrada, sem chamada de LLM. Recuperar - curto prazo + longo prazo + contexto em uma única ida e volta.

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?"}'
Resposta real - create_store()
{"user_id": "alice", "status": "created"}
Resposta real - add()
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Defina o store uma vez. Passe user_id ao cliente e todas as chamadas o usam - sem precisar repeti-lo; sobrescreva uma chamada específica passando user_id a ela. Stores são explícitos: armazenar em um store que não existe, ou recuperar dele, retorna 404 - crie-o primeiro. Toda conta começa com um store default, então sem nenhum user_id o caminho de configuração zero simplesmente funciona. Veja Stores para listá-los e gerenciá-los.

recall() retorna quatro blocos - short_term (turnos recentes), long_term (memórias relevantes), context (o que cercava a melhor correspondência) e uma instruction dizendo ao LLM como usá-los. Insira tudo isso no seu prompt.

Funciona em qualquer idioma. Armazene em inglês, pergunte em coreano, japonês ou chinês - a mesma memória volta. Busca por embeddings, não correspondência por palavras-chave.

Todos os métodos, por linguagem →

Um cliente, configurações diferentes

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 - criar, listar, excluir.

Um store é o user_id sob o qual você lê e escreve - um espaço de memória isolado por usuário final, agente ou tópico. Stores são explícitos: crie um antes de armazenar nele ou recuperar dele, ou a chamada retorna 404. Toda conta começa com um store default, então você pode começar sem uma chamada de criação.

Como o isolamento se aninha. Uma conta possui workspaces; cada workspace isola sua própria memória, suas chaves de API e seu uso (a cobrança é compartilhada no nível da conta). Um store vive dentro de um workspace: chaves do mesmo workspace compartilham seus stores, e workspaces diferentes nunca veem a memória uns dos outros. account → workspace → store (user_id) → memories.
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"}'
Resposta real - create
{ "user_id": "alice", "status": "created" }   // "exists" if it already did
Resposta 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 em um store que não 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." } }
Use um store por usuário final ("alice", "user_42") para manter a memória de cada pessoa separada, ou um único store default para um agente pessoal. Você também pode criar e navegar pelos stores no console (Memory ids → Issue) sem escrever código. Excluir um store é permanente - remove todas as memórias sob ele. Os ids de armazém são dobrados antes de serem guardados: passam a minúsculas e tudo fora de [a-z0-9_] torna-se _, pelo que Alice.Smith e alice-smith designam o mesmo armazém. Um segundo id que dobre sobre um existente é recusado com 409 em vez de partilhado em silêncio. O id tem também de corresponder a [A-Za-z0-9][A-Za-z0-9._-]{0,63}, pelo que um endereço de e-mail ou um nome não latino não pode ser um id de armazém - use um identificador interno.

Listar e apagar 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"}'
Cache de recall

Recalls repetidos, por um décimo do preço.

Ative por solicitação e o WOS armazena em cache o resultado da busca sob o texto da consulta, com as mesmas regras de prefixo do prompt caching dos LLMs. Enquanto o cache está quente, uma consulta repetida ou estendida reutiliza o resultado anterior, e a parte em cache é cobrada a 10% da tarifa normal por token.

Apenas Tablet e Scroll. O cache funciona em todos os modelos Tablet e Scroll, atuais e futuros. O Book não o suporta: o Book raciocina sobre suas memórias e aprende entre as chamadas, então a mesma pergunta pode legitimamente voltar com uma resposta diferente, e um resultado em cache seria errado por design. Enviar cache_control ao Book retorna um 403 claro.

Uma conversa, três turnos

É isto que realmente acontece quando um agente continua conversando com sua memória. Cada turno envia a conversa acumulada como consulta, com cache_control ligado.

writeTurno 1 - “Alice: Eu me mudei para Lisboa na primavera passada.”

A consulta inteira é buscada e armazenada em cache: entrada a 2x (TTL de 5 minutos).

extendTurno 2 - o mesmo texto mais “Bob: Como está o clima aí?”

Só a frase do Bob é transformada em embeddings e buscada. A parte antiga custa 0,1x, a frase nova 2x, e o cache agora termina nela.

hitTurno 3 - exatamente a mesma consulta de novo (um retry, um refresh)

Nenhuma chamada ao motor. Tudo a 0,1x: o desconto de 90%.

As tarifas

OperaçãoCobrança de tokensO que significa
Escrita de cache - TTL 5 minutosA primeira solicitação. Seu resultado é mantido por 5 minutos, e cada leitura desliza a janela para frente.
Escrita de cache - TTL 1 horaA primeira solicitação, mantida por uma hora inteira.
Leitura de cache - acerto ou acerto de prefixo0.1×Toda solicitação após a escrita: a parte em cache custa um décimo da tarifa normal por token.

Quanto isso economiza

Um exemplo concreto: seu agente envia uma conversa de 3.000 tokens como consulta e a repete ou continua 10 vezes em cinco minutos. Sem cache, são 30.000 tokens de entrada a preço cheio. Com um cache de 5 minutos, são 6.000 pela primeira escrita (2x) mais cerca de 2.700 pelas nove leituras em cache: 8.700 tokens cobrados, 71% menos. Quanto mais longa a conversa, maior a economia.

A regra do prefixo

A correspondência é feita no início da consulta. Se o início permanece idêntico e apenas texto novo é acrescentado ao final, a parte em cache é reutilizada e apenas a parte nova é buscada. Se algo muda antes do fim do texto em cache, nada pode ser reutilizado.

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

acerto - o início não mudou, E é a única parte nova
erro - o início mudou, então a consulta inteira é buscada e armazenada em cache novamente

Três regras para lembrar

  • Estender rearmazena até a nova cauda. Depois de [A B C D E F G] + E, o cache agora termina em E: a cauda é cobrada uma vez na tarifa de escrita, e o próximo turno pode casar todo o A..E como prefixo de novo.
  • Um único prefixo contíguo por solicitação. Uma consulta não pode ser dividida em dois segmentos de cache; apenas o seu início pode casar.
  • Escritas invalidam na hora. Qualquer store, store-turn, bulk-store, forget, supersede ou exclusão do store descarta o cache dele, então uma resposta em cache nunca pode ficar obsoleta.

Como ativar

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"
Resposta - o objeto cache informa o que aconteceu
{ "memories": [ ... ],
  "cache": { "status": "hit",              // "write" | "hit" | "extend"
             "ttl": "5m",
             "cache_read_input_tokens": 412,
             "cache_creation_input_tokens": 0 } }

Você não precisa de um SDK para nada disso. O caching é um campo em uma chamada HTTP, então funciona a partir de qualquer linguagem de programação. A aba curl é a receita universal, e os SDKs de Python, TypeScript e Rust são apenas invólucros de conveniência sobre exatamente a mesma chamada.

O cache é isolado por store e por modelo dentro do seu workspace, e vem desligado por padrão: sem cache_control, nada muda nas suas solicitações.
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"}'
Desenvolvedores

Imagens

Uma memória pode carregar uma imagem. O motor indexa a imagem, então uma consulta em texto em qualquer idioma a encontra mesmo quando o registro não tem legenda, título nem texto alternativo.

Compatível com o Tablet 2 e modelos mais novos. Um motor que não implementa imagens informa isso pelo nome em vez de responder um 404 seco, o que permite distinguir um recurso ausente de uma memória ausente. JPEG, PNG, GIF e WebP.

Armazenar uma

Passe um objeto image para a chamada comum de add. content pode ficar vazio; nesse caso a imagem é pesquisável sozinha.

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 é obrigatório. O prefixo data:image/jpeg;base64, e as quebras de linha adicionadas por base64 e openssl são removidos para você.

CampoO que faz
dataBase64 da imagem. Obrigatório. O teto de tamanho é uma configuração do servidor, não uma constante do SDK - /health o informa como memory.images.max_bytes.
referenceOnde fica a sua própria cópia do original. Armazenado como string e nunca acessado por nós.
taken_atRFC3339, normalmente vindo do EXIF. Preenche event_date quando esse campo está vazio, então a memória é ordenada por quando a imagem foi tirada, não por quando foi enviada.

Encontrar uma

Não existe uma busca separada para imagens. search e recall retornam imagens junto com texto, classificadas em conjunto.

Trabalhar com as que já existem

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}'
ChamadaO que faz
get_imageOs bytes originais, como (bytes, content_type). O tipo é detectado a partir dos bytes, não do nome com que o upload foi enviado. Uma memória sem imagem lança um erro em vez de retornar algo vazio.
list_imagesUma página, da mais recente para a mais antiga, mais count - o total do store, não o tamanho da página. A paginação é por cursor: devolva next_before e next_skip_ids. Os dois são necessários porque imagens podem compartilhar o mesmo timestamp.
forget_imageRemove a imagem e mantém o texto. Uma imagem armazenada sem legenda é a memória, então nesse caso a memória também é apagada.

Passe preview=True para forget_image para obter memory_kept sem alterar nada. iter_images pagina para você.

Quanto custa uma imagem

Uma imagem é cobrada em tokens, a mesma unidade do texto. Tokens = área em pixels / 556,7. Acima de 1.568 px no lado maior a contagem é feita a 1.568 px, então uma imagem de 2.500 px custa o mesmo que uma de 1.568 px.

ImagemContabilizada emTokens
700 × 700as sent881
1000 × 1000as sent1,797
1568 × 1568as sent4,417
1920 × 10801568 × 8822,485
2500 × 18751568 × 11763,313
2500 × 25001568 × 15684,417

Teto: 4.417 tokens por imagem. Reservamos esse teto do seu saldo antes da chamada e cobramos depois o valor medido, que nunca é maior.

Imagens grandes demais ou pequenas demais são recusadas com um 400. Não as redimensionamos por você. Os dois lados devem ser ≥ 700 px e o lado maior ≤ 2.500 px. Abaixo de 700 px o modelo de embedding cobra um mínimo fixo, então uma imagem menor custa o mesmo para armazenar. Redimensione antes de enviar; o erro informa o tamanho recebido e o tamanho exigido.

Quantas retornam

Padrão 1, máximo de 5 imagens por resposta. Cinco imagens ficam perto de 20.000 tokens.

CampoO que faz
max_imagesDe 0 a 5. Imagens que uma única resposta pode carregar. Padrão 1. 0 retorna apenas texto. Fora do intervalo é recusado, não ajustado ao limite.
Uma legenda em inglês na imagem melhora as consultas em inglês e piora as consultas nos outros idiomas - 11,4 pontos de recall@5 em média em quatorze idiomas. Armazene imagens sem legenda se os seus usuários pesquisam em mais de um idioma.
Desenvolvedores

verify

verify permite que uma busca execute passadas adicionais. Cada passada exclui o que as passadas anteriores retornaram, então uma segunda passada alcança memórias que a primeira não alcançou.

Compatível com o Tablet 2 e modelos mais novos. Pedir isso a um motor que não o implementa é recusado antes de a chamada ser feita, então você nunca é cobrado por uma passada que não fez nada em silêncio.

Um inteiro de 0 a 3 em search e recall. É o número de passadas adicionais, então 3 permite quatro recuperações. Padrão 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}

Nenhum modelo de linguagem é executado nesse laço

As passadas carregam os ids já retornados; o motor os exclui e busca além deles. A consulta não é reformulada, então os resultados são determinísticos para uma dada requisição e nenhuma credencial de modelo está envolvida. O seu código decide se gasta outra passada.

O que você envia e o que volta

ChamadaO que faz
verifyDe 0 a 3. Passadas adicionais permitidas. Fora do intervalo é rejeitado com um 400, não ajustado ao limite em silêncio.
verify_usedQuantas passadas adicionais foram de fato executadas. Pode ser menor do que o solicitado.

As passadas param assim que uma delas não retorna nada novo, e as passadas não usadas não são cobradas. Se uma passada posterior falhar, os resultados reunidos até então são retornados.

Passadas extras podem reduzir a precisão quando a primeira passada já continha a resposta - as perguntas single-session-user do LongMemEval-S caem 4,2 pontos. O ganho é proporcional à frequência com que uma única recuperação erra, portanto é maior em stores grandes.
Complemento · Beta

MCP - memória para ferramentas de IA

O núcleo do WOS é a API e os SDKs. O servidor MCP é um complemento sobre eles: a mesma memória, plugada em ferramentas que você não construiu - Claude Code, Claude Desktop, Cursor.

Uma linha de instalação dá ao agente nove ferramentas de memória que ele usa sozinho. E como a memória vive na sua conta, o que uma ferramenta escreve, todas as outras lembram - inclusive agentes que você constrói com o SDK.

O que dá para fazer com isso

  • Um Claude Code que lembra do seu projeto. Decisões, correções, preferências - lembrados na próxima sessão sem reexplicar nada.
  • Comece no ChatGPT, continue no Claude. Mesmo store, mesma memória - a conversa atravessa ferramentas em vez de recomeçar.
  • Seu próprio agente continua no circuito. O que o Claude Code aprende, um agente do SDK lembra - e o que seu agente guarda, o Claude Code lembra de volta.

Funciona no Claude Code, Claude Desktop, Cursor, Windsurf e qualquer host MCP. O ChatGPT alcança a mesma memória via Actions mais a spec OpenAPI.

Instalação

claude mcp add wontopos --env WONTOPOS_API_KEY=wos-live-... -- npx -y wontopos-mcp

O agente recebe nove ferramentas - recall · remember · search · update · forget · list_memories · engram · stats · create_store - cada uma descrita para que ele saiba sozinho quando usá-las.

O complemento em si é grátis e publicado no npm - você paga só o preço de uso normal pelas chamadas de API que ele faz. Requer Node 18+ e uma chave criada no console.

Abrir a página de desenvolvedor

Complemento

Spec OpenAPI

O mapa completo e legível por máquina da API - cada endpoint, requisição, resposta e erro.

OpenAPI é o formato padrão da indústria para descrever uma API HTTP em um arquivo legível por máquinas.

https://api.wontopos.com/openapi.json

O que dá para fazer com isso

Postman: File → Import → cole a URL e todos os endpoints viram uma coleção clicável. ChatGPT: crie um GPT, adicione uma Action, cole a mesma URL. Codegen: openapi-generator -i .../openapi.json -g go gera um cliente em uma linguagem que não publicamos.

Importe no Postman, gere um cliente em uma linguagem que não publicamos, conecte ChatGPT Actions ou rode checks de contrato no CI. Um teste a fixa às rotas reais: não há como desviar.

Complemento

llms.txt

Toda a API em uma página de texto que uma IA consegue ler.

llms.txt é uma convenção da web: uma página de texto puro na raiz do site que conta a uma IA tudo o que ela precisa sobre um produto.

https://wontopos.com/llms.txt

Coloque na sua IDE ou agente de código e ele sabe como construir sobre o WOS - auth, endpoints, padrões, erros. Atualizado a cada release.

Os mesmos fatos da spec OpenAPI, público diferente: a spec é estrutura precisa para ferramentas; este arquivo é prosa que uma IA (ou uma pessoa) lê de uma vez. Ambos atualizam a cada release.

Model Context Protocol · Beta

Sua memória dentro de cada ferramenta de IA

Um único comando dá ao Claude Code, Claude Desktop, Cursor ou qualquer host MCP uma memória de longo prazo baseada na sua conta WOS. Sem código de integração: o agente recebe nove ferramentas de memória e decide quando usá-las.

MCP está em beta. As nove ferramentas funcionam hoje e são testadas, mas a superfície ainda pode mudar enquanto a finalizamos. A API e os SDKs por baixo são estáveis e versionados.

Instalação

Claude Code, uma linha (crie antes uma chave no console):

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 define o store padrão, WONTOPOS_MODEL o motor, WONTOPOS_BASE_URL uma implantação self-hosted. WONTOPOS_READ_ONLY=1 muda para somente leitura (só recall/busca/listagem).

Antes de compartilhar um store

  • Use uma chave dedicada. Chaves carregam seu workspace: uma chave só para MCP delimita o que as ferramentas conectadas podem tocar - e você pode rotacioná-la no console sem mexer nas chaves do app.
  • Modo somente leitura. WONTOPOS_READ_ONLY=1 não registra nenhuma ferramenta de escrita: o agente pode lembrar, buscar, listar memórias, executar engramas e ler estatísticas, mas não guardar, atualizar, esquecer ou apagar. Ideal para agentes que devem consultar a memória, não possuí-la.
  • Mantenha a confirmação de ferramentas ligada. Hosts MCP perguntam antes de rodar ferramentas por padrão - deixe ligada principalmente para forget, pois exclusões valem para todas as ferramentas do store.
  • Tudo o que for guardado pode ser lembrado por qualquer ferramenta com a chave. Nunca guarde segredos - chaves de API, senhas - como memórias.
  • Memórias lembradas são dados, não instruções. As descrições das ferramentas dizem isso explicitamente ao agente. Ainda assim, não guarde texto de terceiros não confiável em um store que um agente autônomo obedece.
  • Exclusões também são compartilhadas. Um forget ou delete_all de uma ferramenta apaga para todas.
  • "me" é o agente que escreve no store. Se vários agentes compartilham um store, as vozes "me" se misturam. Dê a cada agente seu próprio store (WONTOPOS_USER_ID) para identidades separadas.
  • Uma conta paga. Todas as ferramentas conectadas consomem o mesmo saldo e limite de taxa.

Depois, é só conversar

youlembre que lançamos às sextas

O agente chama a ferramenta remember. Fica guardado de forma durável: o fim da sessão não muda nada.

new sessionquando lançamos?

Uma sessão nova não tem histórico. O agente chama recall e responde de memória: às sextas.

Coisas para dizer

  • "Este repo usa pnpm, lembre disso" → remember guarda; a próxima sessão já sabe.
  • "Qual formato de erro combinamos semana passada?" → recall traz a decisão de volta ao contexto.
  • "Na verdade, o prazo passou para sexta" → o agente vê que contradiz o que lembrou e chama update para corrigir essa memória no lugar.
  • "Isso está errado, esqueça" → o agente acha o id e chama forget - seu host pede confirmação antes.
  • "O que você lembra sobre mim?" → list_memories percorre tudo o que está guardado, para o agente responder ou organizar.

Nada de especial para formular: são frases comuns, não comandos. O agente lê a descrição de cada ferramenta e escolhe sozinho.

As nove ferramentas

  • recall - Contexto em uma chamada: turnos recentes mais memórias relevantes. A descrição instrui o agente a chamá-la primeiro sempre que o contexto passado importar.
  • remember - Guarda um fato ou decisão durável. speaker: "me" marca as palavras do próprio agente; um nome registrado, quem disse.
  • search - Busca semântica, com um filtro speaker por pessoa — e filters para limitá-la por DATA ou tema ("o que decidimos em junho?"), o único eixo que o significado sozinho não consegue estreitar.
  • update - Substitui uma memória cujo fato mudou, mantendo o rastro em vez de apagá-lo.
  • forget - Apaga uma memória pelo id.
  • list_memories - Percorre tudo o que está armazenado, para responder "o que você lembra de mim?" ou fazer limpeza.
  • engram - Executa um pipeline multi-salto embutido (deep_recall, timeline, gather) quando uma única busca não basta.
  • stats - Quanto há em um repositório - útil antes de uma limpeza e para confirmar que uma escrita chegou.
  • create_store - Stores são explícitos: um por usuário final, projeto ou agente.

SDK ou MCP?

  • O SDK vai dentro de um app que você escreve. Seu código decide exatamente quando guardar e o que lembrar - determinístico, tipado, versionado. Construindo um produto? SDK.
  • O MCP se pluga em uma ferramenta de IA que você não escreveu. O agente decide quando usar a memória, guiado pelas descrições - zero código. Para Claude Code, Claude Desktop, Cursor ou dar memória a um assistente pronto.

Por baixo, a mesma API e os mesmos stores - um app feito no SDK e uma sessão do Claude Code via MCP compartilham uma memória. Escolha por superfície, não um ou outro.

Uma memória através de todas as ferramentas

A memória pertence à conta, não à ferramenta. O mesmo store escrito do ChatGPT (Actions mais a spec OpenAPI) é lembrado no Claude Code e nos seus próprios agentes, e vice-versa: uma conversa iniciada em uma ferramenta continua em outra.

E como é um único store, você pode sair do Claude Code e continuar a conversa onde constrói: um agente do SDK com a mesma chave e store lembra tudo o que o Claude Code acabou de aprender - e o que seu agente guarda, o Claude Code lembra na próxima sessão.

Roda localmente via stdio (npx wontopos-mcp): com este método a sua chave fica no seu ambiente e nunca nos é enviada como parte de uma sessão MCP. Envolve o SDK de TypeScript, pelo que as repetições automáticas, a recusa de redirecionamentos e o mascaramento da chave se aplicam tal e qual.
Model Context Protocol · Beta

Claude Code

O caminho principal: um comando no terminal e toda sessão começa com memória.

  1. Crie uma chave de API no console. A chave carrega seu workspace: uma chave = um espaço de memória.
  2. Registre o servidor. --scope user o disponibiliza em todos os projetos; sem ele, só o projeto atual o vê.
  3. Confira: rode /mcp dentro do Claude Code - wontopos deve aparecer com nove ferramentas.
  4. Torne automático: uma linha no seu CLAUDE.md - "quando o contexto passado importar, chame wontopos recall primeiro" - e toda sessão começa com memória sem pedir.
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

Adicione o bloco abaixo ao claude_desktop_config.json (Configurações → Developer → Edit Config), reinicie o app e as nove ferramentas aparecem. Nota: o claude.ai na web e no celular precisa de um servidor MCP remoto, que o WOS ainda não oferece - o app de desktop é o caminho suportado.

# 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

Adicione o bloco abaixo ao ~/.cursor/mcp.json - ou aperte o botão de um clique - e reinicie o Cursor. O agente pega as nove ferramentas.

# ~/.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

O VS Code (modo agente do Copilot) lê servidores MCP de .vscode/mcp.json do projeto - adicione o bloco abaixo ou aperte o botão de um clique.

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

O Windsurf (Cascade) lê ~/.codeium/windsurf/mcp_config.json: adicione o bloco abaixo e recarregue - as mesmas nove ferramentas aparecem.

# ~/.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

Os conectores MCP do ChatGPT só aceitam servidores remotos, então o caminho suportado hoje é um GPT personalizado com uma Action: crie um GPT, adicione uma Action, cole a URL da spec OpenAPI abaixo e defina sua chave como header de auth. Esse GPT chamará a mesma memória das suas outras ferramentas.

# GPT → Configure → Actions → Import from URL
https://api.wontopos.com/openapi.json
# Authentication: API Key · Header name: X-API-Key

Mesmo store, mesma memória: o que o ChatGPT guarda pela Action, o Claude Code lembra pelo MCP - e vice-versa.

Model Context Protocol · Beta

Gemini CLI

O Gemini CLI lê servidores MCP de ~/.gemini/settings.json: adicione o bloco abaixo, reinicie a CLI e as mesmas nove ferramentas aparecem lá também.

# ~/.gemini/settings.json
{ "mcpServers": {
    "wontopos": {
      "command": "npx",
      "args": ["-y", "wontopos-mcp"],
      "env": { "WONTOPOS_API_KEY": "wos-live-...",
               "WONTOPOS_USER_ID": "my-project" }
    } } }
SDK Python

Python - todos os métodos, três grupos.

Escrever, ler, excluir. Todos os exemplos abaixo foram executados contra a API em produção em 2026-08-01; as respostas estão na íntegra.

pip install wontopos
from wontopos import Client

mem = Client(api_key="wos-live-...")  # or read from an env var

Escolha um modelo

A chave de API escolhe qual memória (sua conta); o modelo escolhe qual motor a lê. Todos os modelos compartilham uma mesma memória, então você pode armazenar com um e recuperar com outro. Defina um padrão no cliente; sobrescreva uma chamada específica passando 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

O catálogo - os ids que você pode passar em model e se cada um está disponível. Modelos com memory: "shared" leem o mesmo store; "isolated" mantém o seu próprio. Não requer chave de API.

mem.list_models()
Resposta 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

Confirme a conexão e que sua chave de API funciona: uma verificação de uma linha.

mem.ping()   # True, or raises AuthenticationError / PaymentRequiredError

O catálogo acima sempre reflete os modelos disponíveis neste momento - passe qualquer outro id e você recebe um erro claro. Novos modelos aparecem nele automaticamente quando são lançados.

Escrever

add

Armazena uma memória. Embeddings gerados na entrada - sem chamada de LLM, você paga apenas 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
Resposta real
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn

Armazena um turno de conversa (usuário + assistente) na memória de curto e de longo prazo de uma só vez.

mem.add_turn("hi", "hello!", user_id="alice")
Resposta real
{"status": "ok"}

speaker

Cada memória pode carregar quem a disse. Registre uma pessoa uma vez e depois passe o nome como speaker; "me" (as palavras do próprio assistente) nunca precisa de registro. A busca também aceita speaker, para lembrar só as palavras de uma pessoa.

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", ...}]
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.

add_bulk

Faz backfill de um grande bloco de texto. Fragmentado e convertido em embeddings no servidor - ideal para importar um histórico existente.

mem.add_bulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", user_id="alice")
Resposta real
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

Um fato mudou. A memória antiga é marcada como substituída (mantida para histórico); a nova toma o lugar dela no recall.

mem.update("576700aa-...", "she switched to coffee this year", user_id="alice")
Resposta real
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Ler

search

Busca semântica, os mais relevantes primeiro. Puramente embeddings - sem correspondência por palavras-chave, então qualquer idioma encontra qualquer memória. O SDK retorna o array de memórias diretamente; o corpo HTTP bruto é mostrado abaixo. Num modelo com faixa própria (Scroll 1.2+) o serviço responde em duas faixas e o SDK devolve ambas fundidas, pelo que o array pode conter MAIS do que max_results. Dimensione a janela do prompt pelo que recebe, não pelo número pedido.

r = mem.search("what does she drink?", user_id="alice", limit=1)
Resposta real (corpo 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
similaritySimilaridade bruta de embedding com a sua consulta (0–1).
is_supersededVerdadeiro se este fato foi substituído por update().
search_msTempo de recuperação no servidor.

recall

Uma única ida e volta retorna tudo o que o seu LLM precisa - cole o resultado direto no seu prompt: um contexto limitado e de tamanho fixo, não importa quanto você tenha armazenado.

ctx = mem.recall("what does she drink?", user_id="alice")
Resposta real (formato - listas encurtadas)
{"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 recentes da conversa (memória de curto prazo), do mais antigo para o mais recente.

turns = mem.history("alice")
Resposta real (corpo 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

Contagens de memórias de um usuário.

mem.stats("alice")
Resposta real
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

Busca uma memória pelo id - o id que add ou list_memories retornou. Apenas o texto original armazenado e seus metadados, nunca o vetor. Um id de outro store, ou uma memória apagada ou invalidada, retorna 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}
Resposta 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 as memórias de um armazenamento - apenas o texto original que você guardou e seus metadados, nunca o vetor. Paginado por cursor: reenvie o next_cursor retornado para a próxima página.

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

Percorra todas as memórias sem gerenciar o cursor, ou traga o armazenamento inteiro de uma 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

Excluir

delete

Exclui uma única memória pelo id.

mem.delete("alice", memory_id="576700aa-...")
Resposta real
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all

Apaga tudo de um usuário - uma única chamada, pronta para o GDPR.

mem.delete_all("alice")
Resposta real
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erros e confiabilidade

Cada falha é um erro tipado - capture o específico (limite de taxa, autenticação, pagamento) ou todos com o 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

Leia a cota restante após qualquer chamada e desacelere antes de bater no limite.

mem.search("...", user_id="alice")
rl = mem.rate_limit   # {"limit": 150, "remaining": 3, "reset": ...}

search_self

As duas trilhas em uma única chamada num modelo de memória própria (Scroll 1.2+): o que os outros disseram e as palavras do PRÓPRIO agente - separadas, para que quem lê nunca confunda quem falou.

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

Pergunte ao serviço quais engramas e formas de entrega o modelo selecionado consegue executar, em vez de fixar nomes que ficam desatualizados assim que um novo é lançado.

cat = mem.list_engrams()
[e["name"] for e in cat["engrams"]]   # ask, never hard-code

filters

Restringe a busca a parte do repositório. Aplicado antes da ordenação, então você recebe as melhores correspondências dentro do filtro - não um top-N filtrado depois.

mem.search("what did we decide", user_id="alice", filters={
    "categories": ["work"],
    "event_from": "2026-01-01",   # when it HAPPENED
})
Chaves: categories · event_from / event_to (quando o conteúdo ACONTECEU - metadata.event_date) · time_from / time_to (quando foi escrito) · min_importance. Chaves não listadas são descartadas, não rejeitadas, então um erro de digitação amplia a busca em silêncio.

idempotency_key

Torna seguro repetir uma escrita. Use quando a repetição é sua - um job que caiu e foi reexecutado, uma fila que reentrega.

mem.add("she prefers tea", "alice", idempotency_key=f"import:{row.id}")
Derive a chave do que está sendo armazenado (import:row-42), nunca uma constante: uma chave reutilizada em duas escritas diferentes repete a primeira e a segunda se perde em silêncio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].

with_timeout / with_retries

Ajuste um único ponto de chamada sem mexer no cliente já construído: um clone com timeout maior para um backfill grande, ou sem retentativas dentro do seu próprio laço de retry.

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 TypeScript

TypeScript - todos os métodos, três grupos.

Escrever, ler, excluir. Todos os exemplos abaixo foram executados contra a API em produção em 2026-08-01; as respostas estão na íntegra.

npm install wontopos
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-..." });

Escolha um modelo

A chave de API escolhe qual memória (sua conta); o modelo escolhe qual motor a lê. Todos os modelos compartilham uma mesma memória, então você pode armazenar com um e recuperar com outro. Defina um padrão no construtor; sobrescreva uma chamada específica com 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

O catálogo - os ids que você pode passar em model e se cada um está disponível. Modelos com memory: "shared" leem o mesmo store; "isolated" mantém o seu próprio. Não requer chave de API.

await mem.listModels();
Resposta 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

Confirme a conexão e que sua chave de API funciona: uma verificação de uma linha.

await mem.ping();   // true, or throws AuthenticationError / PaymentRequiredError

O catálogo acima sempre reflete os modelos disponíveis neste momento - passe qualquer outro id e você recebe um erro claro. Novos modelos aparecem nele automaticamente quando são lançados.

Escrever

add

Armazena uma memória. Embeddings gerados na entrada - sem chamada de LLM, você paga apenas 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
Resposta real
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

addTurn

Armazena um turno de conversa (usuário + assistente) na memória de curto e de longo prazo de uma só vez.

await mem.addTurn("hi", "hello!", "alice");
Resposta real
{"status": "ok"}

speaker

Cada memória pode carregar quem a disse. Registre uma pessoa uma vez e depois passe o nome como speaker; "me" (as palavras do próprio assistente) nunca precisa de registro. A busca também aceita speaker, para lembrar só as palavras de uma pessoa.

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" });
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.

addBulk

Faz backfill de um grande bloco de texto. Fragmentado e convertido em embeddings no servidor - ideal para importar um histórico existente.

await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
Resposta real
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

Um fato mudou. A memória antiga é marcada como substituída (mantida para histórico); a nova toma o lugar dela no recall.

await mem.update("576700aa-...", "she switched to coffee this year", "alice");
Resposta real
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Ler

search

Busca semântica, os mais relevantes primeiro. Puramente embeddings - sem correspondência por palavras-chave, então qualquer idioma encontra qualquer memória. O SDK retorna o array de memórias diretamente; o corpo HTTP bruto é mostrado abaixo. Num modelo com faixa própria (Scroll 1.2+) o serviço responde em duas faixas e o SDK devolve ambas fundidas, pelo que o array pode conter MAIS do que max_results. Dimensione a janela do prompt pelo que recebe, não pelo número pedido.

const r = await mem.search("what does she drink?", "alice", 1);
Resposta real (corpo 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
similaritySimilaridade bruta de embedding com a sua consulta (0–1).
is_supersededVerdadeiro se este fato foi substituído por update().
search_msTempo de recuperação no servidor.

recall

Uma única ida e volta retorna tudo o que o seu LLM precisa - cole o resultado direto no seu prompt: um contexto limitado e de tamanho fixo, não importa quanto você tenha armazenado.

const ctx = await mem.recall("what does she drink?", "alice");
Resposta real (formato - listas encurtadas)
{"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 recentes da conversa (memória de curto prazo), do mais antigo para o mais recente.

const turns = await mem.history("alice");
Resposta real (corpo 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

Contagens de memórias de um usuário.

await mem.stats("alice");
Resposta real
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

Busca uma memória pelo id - o id que add ou list_memories retornou. Apenas o texto original armazenado e seus metadados, nunca o vetor. Um id de outro store, ou uma memória apagada ou invalidada, retorna 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 as memórias de um armazenamento - apenas o texto original que você guardou e seus metadados, nunca o vetor. Paginado por cursor: reenvie o next_cursor retornado para a próxima página.

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

Percorra todas as memórias sem gerenciar o cursor, ou traga o armazenamento inteiro de uma vez.

for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content);
const everything = await mem.exportMemories("alice");

Excluir

delete

Exclui uma única memória pelo id.

await mem.delete("alice", "576700aa-...");
Resposta real
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

deleteAll

Apaga tudo de um usuário - uma única chamada, pronta para o GDPR.

await mem.deleteAll("alice");
Resposta real
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erros e confiabilidade

Cada falha é um erro tipado - capture o específico (limite de taxa, autenticação, pagamento) ou todos com o 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

Leia a cota restante após qualquer chamada e desacelere antes de bater no limite.

await mem.search("...", "alice");
const rl = mem.rateLimit;   // { limit: 150, remaining: 3, reset: ... }

searchSelf

As duas trilhas em uma única chamada num modelo de memória própria (Scroll 1.2+): o que os outros disseram e as palavras do PRÓPRIO agente - separadas, para que quem lê nunca confunda quem falou.

const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice");
// memories = what others said · self_memories = the agent's OWN words

listEngrams

Pergunte ao serviço quais engramas e formas de entrega o modelo selecionado consegue executar, em vez de fixar nomes que ficam desatualizados assim que um novo é lançado.

const { engrams, forms } = await mem.listEngrams();  // ask, never hard-code

filters

Restringe a busca a parte do repositório. Aplicado antes da ordenação, então você recebe as melhores correspondências dentro do filtro - não um top-N filtrado depois.

await mem.search("what did we decide", "alice", 10, {
  filters: { categories: ["work"], event_from: "2026-01-01" },  // when it HAPPENED
});
Chaves: categories · event_from / event_to (quando o conteúdo ACONTECEU - metadata.event_date) · time_from / time_to (quando foi escrito) · min_importance. Chaves não listadas são descartadas, não rejeitadas, então um erro de digitação amplia a busca em silêncio.

idempotencyKey

Torna seguro repetir uma escrita. Use quando a repetição é sua - um job que caiu e foi reexecutado, uma fila que reentrega.

await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
Derive a chave do que está sendo armazenado (import:row-42), nunca uma constante: uma chave reutilizada em duas escritas diferentes repete a primeira e a segunda se perde em silêncio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].

withTimeout / withRetries

Ajuste um único ponto de chamada sem mexer no cliente já construído: um clone com timeout maior para um backfill grande, ou sem retentativas dentro do seu próprio laço de retry.

await mem.withTimeout(120_000).addBulk(bigBlob, "alice");  // this slow call only
await mem.withRetries(0).add("...", "alice");            // you retry, not the SDK
SDK Rust

Rust - todos os métodos, três grupos.

Escrever, ler, excluir. Todos os exemplos abaixo foram executados contra a API em produção em 2026-08-01; as respostas estão na íntegra.

cargo add wontopos
use wontopos::Client;

let mem = Client::new("wos-live-...");

Escolha um modelo

A chave de API escolhe qual memória (sua conta); o modelo escolhe qual motor a lê. Todos os modelos compartilham uma mesma memória, então você pode armazenar com um e recuperar com outro. Defina um padrão com with_model(); encadeie-o novamente para sobrescrever uma chamada específica.

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

O catálogo - os ids que você pode passar em with_model e se cada um está disponível. Modelos com memory: "shared" leem o mesmo store; "isolated" mantém o seu próprio. Não requer chave de API.

mem.list_models().await?;
Resposta 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

Confirme a conexão e que sua chave de API funciona: uma verificação de uma linha.

mem.ping().await?;   // Ok(true), or Err whose .kind() is Auth / PaymentRequired

O catálogo acima sempre reflete os modelos disponíveis neste momento - passe qualquer outro id e você recebe um erro claro. Novos modelos aparecem nele automaticamente quando são lançados.

Escrever

add

Armazena uma memória. Embeddings gerados na entrada - sem chamada de LLM, você paga apenas 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
Resposta real
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

add_turn

Armazena um turno de conversa (usuário + assistente) na memória de curto e de longo prazo de uma só vez.

mem.add_turn("hi", "hello!", "alice").await?;
Resposta real
{"status": "ok"}

speaker

Cada memória pode carregar quem a disse. Registre uma pessoa uma vez e depois passe o nome como speaker; "me" (as palavras do próprio assistente) nunca precisa de registro. A busca também aceita speaker, para lembrar só as palavras de uma pessoa.

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?;
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.

add_bulk

Faz backfill de um grande bloco de texto. Fragmentado e convertido em embeddings no servidor - ideal para importar um histórico existente.

mem.add_bulk("Alice moved to Brooklyn in March...", "alice", "general").await?;
Resposta real
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

Um fato mudou. A memória antiga é marcada como substituída (mantida para histórico); a nova toma o lugar dela no recall.

mem.update("576700aa-...", "she switched to coffee this year", "alice").await?;
Resposta real
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

Ler

search

Busca semântica, os mais relevantes primeiro. Puramente embeddings - sem correspondência por palavras-chave, então qualquer idioma encontra qualquer memória. O SDK retorna o array de memórias diretamente; o corpo HTTP bruto é mostrado abaixo. Num modelo com faixa própria (Scroll 1.2+) o serviço responde em duas faixas e o SDK devolve ambas fundidas, pelo que o array pode conter MAIS do que max_results. Dimensione a janela do prompt pelo que recebe, não pelo número pedido.

let r = mem.search("what does she drink?", "alice", 1).await?;
Resposta real (corpo 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
similaritySimilaridade bruta de embedding com a sua consulta (0–1).
is_supersededVerdadeiro se este fato foi substituído por update().
search_msTempo de recuperação no servidor.

recall

Uma única ida e volta retorna tudo o que o seu LLM precisa - cole o resultado direto no seu prompt: um contexto limitado e de tamanho fixo, não importa quanto você tenha armazenado.

let ctx = mem.recall("what does she drink?", "alice").await?;
Resposta real (formato - listas encurtadas)
{"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 recentes da conversa (memória de curto prazo), do mais antigo para o mais recente.

let turns = mem.history("alice").await?;
Resposta real (corpo 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

Contagens de memórias de um usuário.

mem.stats("alice").await?;
Resposta real
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

Busca uma memória pelo id - o id que add ou list_memories retornou. Apenas o texto original armazenado e seus metadados, nunca o vetor. Um id de outro store, ou uma memória apagada ou invalidada, retorna 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 as memórias de um armazenamento - apenas o texto original que você guardou e seus metadados, nunca o vetor. Paginado por cursor: reenvie o next_cursor retornado para a próxima página.

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

Percorra todas as memórias sem gerenciar o cursor, ou traga o armazenamento inteiro de uma vez.

let all = mem.list_all_memories("alice").await?;   // every page, collected

Excluir

delete

Exclui uma única memória pelo id.

mem.delete("alice", "576700aa-...").await?;
Resposta real
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

delete_all

Apaga tudo de um usuário - uma única chamada, pronta para o GDPR.

mem.delete_all("alice").await?;
Resposta real
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

Erros e confiabilidade

Cada falha é um erro tipado - capture o específico (limite de taxa, autenticação, pagamento) ou todos com o 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

Leia a cota restante após qualquer chamada e desacelere antes de bater no limite.

mem.search("...", "alice", 10).await?;
let rl = mem.rate_limit();   // Some(RateLimit { remaining: Some(3), .. })

search_self

As duas trilhas em uma única chamada num modelo de memória própria (Scroll 1.2+): o que os outros disseram e as palavras do PRÓPRIO agente - separadas, para que quem lê nunca confunda quem falou.

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

Pergunte ao serviço quais engramas e formas de entrega o modelo selecionado consegue executar, em vez de fixar nomes que ficam desatualizados assim que um novo é lançado.

let cat = mem.list_engrams().await?;  // ask, never hard-code

filters

Restringe a busca a parte do repositório. Aplicado antes da ordenação, então você recebe as melhores correspondências dentro do filtro - não um top-N filtrado depois.

mem.search_with("what did we decide", "alice", 10, json!({"filters": {
    "categories": ["work"], "event_from": "2026-01-01"   // when it HAPPENED
}})).await?;
Chaves: categories · event_from / event_to (quando o conteúdo ACONTECEU - metadata.event_date) · time_from / time_to (quando foi escrito) · min_importance. Chaves não listadas são descartadas, não rejeitadas, então um erro de digitação amplia a busca em silêncio.

add_idempotent

Torna seguro repetir uma escrita. Use quando a repetição é sua - um job que caiu e foi reexecutado, uma fila que reentrega.

mem.add_idempotent("she prefers tea", "alice", json!({}), &format!("import:{}", row.id)).await?;
Derive a chave do que está sendo armazenado (import:row-42), nunca uma constante: uma chave reutilizada em duas escritas diferentes repete a primeira e a segunda se perde em silêncio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].

with_timeout / with_retries

Ajuste um único ponto de chamada sem mexer no cliente já construído: um clone com timeout maior para um backfill grande, ou sem retentativas dentro do seu próprio laço de retry.

mem.with_timeout(120).add_bulk(big_blob, "alice", "general").await?;
mem.with_retries(0).add("...", "alice", json!({})).await?;
curl

curl - sem instalação, os mesmos métodos.

Nenhum SDK para instalar - qualquer cliente HTTP funciona. Defina sua chave uma vez e chame os mesmos endpoints que os SDKs encapsulam. URL base https://api.wontopos.com, autenticação via X-API-Key, JSON na entrada e na saída.

# set your key once (never hard-code it)
export WOS_API_KEY="wos-live-..."

Escrever

store

Armazena uma memória. Embeddings gerados na entrada - sem chamada de 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"}'
Resposta real
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

store-turn

Armazena um turno de conversa (usuário + assistente) na memória de curto e de longo prazo de uma só 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!"}'
Resposta real
{"status": "ok"}

speaker

Cada memória pode carregar quem a disse. Registre uma pessoa uma vez e depois passe o nome como speaker; "me" (as palavras do próprio assistente) nunca precisa de registro. A busca também aceita speaker, para lembrar só as palavras de uma pessoa.

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

supersede

Um fato mudou - a memória antiga é marcada como substituída, a nova toma o lugar dela no 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"}'
Resposta real
{"new_memory_id": "07e94433-...", "old_memory_id": "576700aa-...", "status": "superseded"}

bulk-store

Carrega um histórico longo em uma chamada - fatiado e incorporado no 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"}'
Resposta real
{"elapsed_secs": 0.138589761, "status": "ok", "stored": 1, "total_chunks": 1}

Idempotency-Key

Torna seguro repetir uma escrita. Use quando a repetição é sua - um job que caiu e foi reexecutado, uma fila 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"}'
Derive a chave do que está sendo armazenado (import:row-42), nunca uma constante: uma chave reutilizada em duas escritas diferentes repete a primeira e a segunda se perde em silêncio. Formato: 1-128 caracteres de [A-Za-z0-9._:-].

Ler

search

Busca semântica, os mais relevantes primeiro. Puramente embeddings - qualquer idioma encontra qualquer memória.

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}'
Resposta real
{"memories": [{"id": "576700aa-...", "content": "she prefers tea over coffee",
   "similarity": 0.63, "is_superseded": false}], "search_ms": 315, "total_found": 1}

search + filters

Restringe a busca a parte do repositório. Aplicado antes da ordenação, então você recebe as melhores correspondências dentro do filtro - não um top-N filtrado depois.

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"}}'
Chaves: categories · event_from / event_to (quando o conteúdo ACONTECEU - metadata.event_date) · time_from / time_to (quando foi escrito) · min_importance. Chaves não listadas são descartadas, não rejeitadas, então um erro de digitação amplia a busca em silêncio.

get

Lê uma memória pelo id que store ou list devolveu - texto original e metadados, sem vetores.

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

Percorre tudo em um repositório, cursor a cursor. Serve para navegar ou 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
Resposta 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

Uma única ida e volta retorna curto prazo + longo prazo + contexto + uma instrução. Cole direto no seu 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?"}'
Resposta real (formato)
{"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..."}

Excluir

forget

Exclui uma memória pelo id, ou omita-o para excluir tudo de um usuário (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
Resposta real
{"memories_deleted": 1, "status": "deleted", "user_id": "alice"}

Todos os endpoints + campos do corpo →

Variantes exclusivas do Rust

Python e TypeScript recebem esses valores como argumentos opcionais. O Rust estável não tem argumentos padrão nem nomeados, então cada um é um método próprio, e não um builder que você precisa finalizar.

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 também é exportado como iter_images, o mesmo nome usado pelos outros dois SDKs - quem chega daquela documentação digita esse nome primeiro.

Engrams

Engrams

Ferramentas de recall invocáveis pelo seu modelo - cada uma é uma estratégia de recuperação diferente sobre a mesma memória. Use uma, ou execute várias ao mesmo tempo.

Já disponível. Os engrams gerais abaixo são pipelines de recuperação sem LLM, então rodam em todos os níveis a partir do Tablet 1. Memoir e Archive, um modo de modelo separado, são cobertos em sua própria seção abaixo.

Novos engrams são lançados regularmente - esta lista cresce.

Memoir & Archive Scroll 1.2+

Esta é uma forma de entrega, não uma ferramenta invocável. No Scroll 1.2 e superiores, escolha por chamada - form: "memoir" ou form: "archive" - e esse recall, incluindo uma busca simples, volta com o tempo escrito dessa forma.

Todos os engrams Engrams

Time_awareness Scroll 1.2+

Uma forma de entrega - escolhida por chamada. Passe form - memoir ou archive - em qualquer chamada de um modelo compatível (Scroll 1.2 e superiores), e a resposta volta renderizada dessa forma: uma busca simples, um recall ou qualquer engram. Nos SDKs é um campo form, como tz; via HTTP é o cabeçalho X-WOS-Form. Um Memoir se lê do jeito que uma pessoa lembra; um Archive mantém um registro exato - a diferença aparece principalmente em como cada um escreve o tempo.

Memoir

form: "memoir"
Lembrado como uma pessoa · uma narrativa

Conta o que aconteceu e como um momento levou ao seguinte, com a noção suave de tempo que uma pessoa recorda - lido como experiência, não como uma lista.

Archive

form: "archive"
Mantido como registro · tempo preciso

Retorna as correspondências como registros exatos - tempo decorrido preciso e âncoras absolutas, estruturados para um modelo ler diretamente.

Ele renderiza memórias que você já armazenou - não as cria. Cada memória é uma chamada store / add sob um user_id (esse user_id é o store daquela pessoa). Armazene primeiro; depois qualquer recall - incluindo a busca simples abaixo - volta com marcação de tempo. Veja o Início rápido para armazenar.
# 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 é o deslocamento UTC de quem chama, em horas - assim, "esta manhã" e o limite de dia às 4h caem no horário local deles. Omita para UTC; via HTTP, é o cabeçalho X-WOS-Timezone. Aproximadamente, por região: Leste dos EUA -5, Centro dos EUA -6, Oeste dos EUA -8 · Reino Unido / Lisboa 0 · Europa Central +1 · Europa Oriental +2 · Índia +5.5 · China / Cingapura +8 · Coreia / Japão +9 · Sydney +10. (Horário padrão - o horário de verão desloca algumas regiões em +1; passe o que os seus usuários estiverem realmente usando.)

A mesma busca, duas formas - as memórias são idênticas, apenas o time muda:

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)" }
] }
DecorridoMemoirArchive
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
ontem à tardeyesterday afternoonyesterday at 14:00
ontem à noitelast night17 hours ago, at 22:00
2 diasa couple days ago2 days ago (Tue 15:10)
6 diasseveral days ago6 days ago (Fri 15:10)
9 diasabout a week agolast week (Jun 16)
16 diasa couple weeks ago2 weeks ago (Jun 09)
35 diasabout a month agolast month (May 21)
60 diasa couple months ago2 months ago (Apr 2026)
180 diasabout half a year ago6 months ago (Dec 2025)
380 diasabout a year agolast year (Jun 2025)
800 diasa couple years ago2 years ago (Apr 2024)
1500 diasabout 4 years ago4 years ago (May 2022)

Todos os valores acima são a saída real do renderizador. Observe as duas linhas de "ontem": um Memoir separa a tarde da noite anterior - um dia é um sono - enquanto um Archive escreve um único horário de relógio e não traça linha entre dia e noite.

Como cada modo lê o tempo

Memoir - do jeito que as pessoas realmente falam. Momentos recentes permanecem razoavelmente nítidos (uns 15 minutos, meia hora), e depois a formulação vai se alargando quanto mais para trás se vai - algumas semanas, cerca de meio ano, uns dois anos - do mesmo jeito que a própria memória se afrouxa com a distância. Dentro de um dia, ele troca o relógio por um marco: esta manhã, ontem à noite, ontem à tarde. E um dia é um sono, não um tique de calendário: o limite fica por volta das 4h no horário local, então uma madrugada ainda é lida como a mesma noite, não como o dia seguinte.

Archive - preciso, sempre com uma âncora. Cada linha traz o tempo decorrido exato mais uma referência absoluta a partir da qual um modelo pode calcular, e a âncora fica mais precisa conforme se aproxima do presente: um horário de relógio para hoje (8 horas atrás, às 07:10), um dia da semana e horário nesta semana (2 dias atrás (ter 15:10)), uma data neste mês (semana passada (16 jun)), um mês e ano além disso (6 meses atrás (dez 2025)). Nunca vago, nunca errado.

Memoir e Archive renderizam todo recall da resposta - uma busca simples, um recall ou um engram. O nível do modelo (Tablet → Scroll → Book) define quanto o motor faz; a forma (memoir / archive) define como ele escreve o tempo. Disponível no Scroll 1.2 e superiores.
Todos os engrams Engrams

deep_recall

Recall multi-hop. Busca a sua consulta, depois pega a melhor correspondência e busca de novo sobre o conteúdo dela - trazendo contexto vinculado que uma busca única deixaria passar. Ideal quando as memórias referenciam umas às outras (uma pessoa → seus projetos → detalhes). Retorna até ~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?"}'
Resposta
{ "engram": "deep_recall", "hops": 2, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 200, "output_tokens": 589 } }
Medido em tokens - toda chamada retorna usage (entrada + saída), contado pelo mesmo tokenizador do restante da API; sem taxa oculta por engram. Precisa de vários de uma vez? Chame-os simultaneamente - cada engram é uma requisição independente.
Todos os engrams Engrams

timeline

Recall em ordem temporal. Retorna memórias ordenadas do mais recente para o mais antigo por quando o evento aconteceu, não por relevância. Para perguntas de "quando foi X", histórico e sequência. Retorna até 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"}'
Resposta
{ "engram": "timeline", "hops": 1, "count": 15,
  "memories": [ ... ],
  "usage": { "input_tokens": 100, "output_tokens": 736 } }
Medido em tokens - toda chamada retorna usage (entrada + saída), contado pelo mesmo tokenizador do restante da API; sem taxa oculta por engram. Precisa de vários de uma vez? Chame-os simultaneamente - cada engram é uma requisição independente.
Todos os engrams Engrams

gather

Coleta ampla. Busca e depois expande ao redor das três melhores correspondências - uma rede mais ampla que o deep_recall. Use-o para trazer tudo relacionado a uma pessoa, projeto ou tópico em uma única chamada. Retorna até ~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"}'
Resposta
{ "engram": "gather", "hops": 4, "count": 18,
  "memories": [ ... ],
  "usage": { "input_tokens": 400, "output_tokens": 637 } }
Medido em tokens - toda chamada retorna usage (entrada + saída), contado pelo mesmo tokenizador do restante da API; sem taxa oculta por engram. Precisa de vários de uma vez? Chame-os simultaneamente - cada engram é uma requisição independente.
Todos os engrams Engrams

equilibrium

Correção de desvio. A busca semântica estreita-se ao longo de uma sessão: a consulta carrega o estado atual, por isso traz memórias do mesmo estado e o turno seguinte inclina-se ainda mais para esse lado. Este engrama alarga o resultado por três eixos que a consulta não controla: a dispersão no tempo, a associação que se afasta da consulta e a parte substancial do armazém. Use quando as respostas começarem a repetir-se ou a ficar planas. Para um facto específico, deep_recall ou gather servem melhor, porque ficam mais perto da consulta. Devolve até 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?"}'
Resposta
{ "engram": "equilibrium", "hops": 3, "count": 12,
  "memories": [ ... ],
  "usage": { "input_tokens": 300, "output_tokens": 293 } }
Medido em tokens - toda chamada retorna usage (entrada + saída), contado pelo mesmo tokenizador do restante da API; sem taxa oculta por engram. Precisa de vários de uma vez? Chame-os simultaneamente - cada engram é uma requisição independente.
Todos os engrams Engrams

tone_stabilizer

A própria voz. Sessões longas afastam um assistente do seu registo: as respostas alongam-se, viram relatório ou assumem o humor do último trecho. A auto-recordação comum agrava isso, porque acompanha o estado atual e devolve as falas mais recentes como se fossem o carácter. Este engrama devolve, em vez disso, as próprias palavras de antes desse trecho. Precisa de turnos guardados com speaker me; sem nenhum, devolve vazio em vez de adivinhar. Devolve até 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?"}'
Resposta
{ "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 } }
Medido em tokens - toda chamada retorna usage (entrada + saída), contado pelo mesmo tokenizador do restante da API; sem taxa oculta por engram. Precisa de vários de uma vez? Chame-os simultaneamente - cada engram é uma requisição independente.
API HTTP

Todos os endpoints, uma única URL base.

Nenhum SDK necessário - qualquer cliente HTTP funciona. URL base https://api.wontopos.com, autenticação via o cabeçalho X-API-Key, JSON na entrada e na saída. As operações de memória são POST; gerenciar stores usa POST / GET / DELETE em /collection. Um store precisa existir primeiro (veja Stores), ou as operações dentro do store retornam 404.

Cabeçalhos

CabeçalhoO que faz
X-API-KeyObrigatório em toda chamada. Sua chave, emitida no console.
X-WOS-ModelOpcional. Qual motor responde. Omita e o padrão da conta é usado. GET /api/v1/models lista os modelos que a sua chave pode selecionar; um endpoint que um motor mais antigo não consegue atender responde 501 e informa esse modelo.
Idempotency-KeyOpcional, nas escritas. A mesma chave com o mesmo corpo repete a primeira resposta em vez de armazenar de novo - veja a nota abaixo.

Endpoint

EndpointFinalidadeCampos do corpo
POST /api/v1/memory/collectioncriar um storeuser_id
GET /api/v1/memory/collectionslistar seus stores(nenhum)
DELETE /api/v1/memory/collectionexcluir um store + suas memóriasuser_id
/api/v1/memory/storearmazenar uma memóriauser_id · content · metadata? (event_date · speaker) · image?
/api/v1/memory/store-turnarmazenar um turno de conversauser_id · user_msg · assistant_msg
POST /api/v1/memory/speakersregistrar um falante (explícito, até 50)user_id · speaker
GET /api/v1/memory/speakerslistar falantes registrados + contagensuser_id
DELETE /api/v1/memory/speakersremover um falante (memórias ficam)user_id · speaker
/api/v1/memory/by-speakero que uma pessoa disse, do mais recente ("me" = o agente)user_id · speaker · limit? · before? · skip_ids?
POST /api/v1/memory/imageos bytes originais de uma memória com imagemuser_id · memory_id
DELETE /api/v1/memory/imageremover a imagem e manter a legendauser_id · memory_id · preview?
/api/v1/memory/imagesas imagens de um store, do mais recente (+ o total)user_id · limit? · before? · skip_ids?
/api/v1/memory/lineagea cadeia de edições de uma memória, do mais antigouser_id · memory_id
/api/v1/won/revisionsquanto de um store foi reescrito. Grátisuser_id · include? · limit? · before? · skip_ids?
/api/v1/memory/revisionsa mesma chamada sob o plano memory. Grátisuser_id · include? · limit? · before? · skip_ids?
/api/v1/memory/bulk-storebackfill de um bloco de textouser_id · content · category? · timestamp?
/api/v1/memory/searchbusca semânticauser_id · query · max_results? · speaker? · cache_control? · filters? · verify? · max_images?
/api/v1/memory/recallcurto + longo + contextouser_id · query · limit? · context_limit?
/api/v1/memory/getuma memória por iduser_id · memory_id
/api/v1/memory/listpercorrer um repositório por páginasuser_id · limit? · cursor?
/api/v1/memory/historyturnos recentesuser_id
/api/v1/memory/statscontagens de memóriasuser_id
/api/v1/memory/supersedesubstituir um fato que mudouuser_id · old_memory_id · new_content
/api/v1/memory/forgetexcluir uma (ou todas)user_id · memory_id? (omitir = excluir tudo)
GET /api/v1/engramengramas que este modelo pode executar(nenhum)
POST /api/v1/engram/runexecutar um engramaname · user_id · query · form? · tz?
GET /api/v1/modelsmodelos disponíveis(nenhum)
As escritas aceitam o cabeçalho Idempotency-Key. A mesma chave com o mesmo corpo repete a primeira resposta em vez de armazenar de novo (10 minutos); a mesma chave com corpo diferente responde 422. Apenas 2xx são cacheados, portanto uma chamada que falhou pode ser repetida na hora.
# 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?"}'
Resposta real - store
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}
Níveis de uso

Os mesmos recursos para todos.
Os níveis só aumentam seus limites.

Todo nível executa o motor completo - mesma qualidade de recall, mesmos idiomas, todos os métodos. Os níveis avançam automaticamente até o Nível 5 conforme suas compras acumuladas de créditos crescem, sem solicitação nem contato com vendas. Enterprise (Nível 6) é a única exceção.

Limites de gasto

Cada nível limita quanto você pode gastar por mês-calendário. Você avança imediatamente quando suas compras acumuladas de créditos atingem o próximo patamar.

Nível de usoCompra de créditosLimite mensal de gasto
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 - EnterpriseFale conoscoSem limite

Limites de requisições

Os limites de requisições são por conta - todas as chaves de API de uma conta compartilham um mesmo limite, que escala com o seu nível. Excedê-lo retorna um 429 com um cabeçalho retry-after; recue (1s → 2s → 4s) e tente novamente. Todos os endpoints são compatíveis com idempotência, então repetir requisições é seguro.

NívelRequisições por minuto
Tier 1150
Tier 2300
Tier 3600
Tier 41,500
Tier 53,000
Tier 6 - EnterprisePersonalizado

O Enterprise (Nível 6) recebe limites de requisições personalizados, um SLA, suporte dedicado e uma licença opcional de self-host - fale conosco.

Chamadas gratuitas

Alguns endpoints não têm cobrança nenhuma - estão reunidos sob Won. No lugar de um preço, eles têm dois limites.

  • 10 requisições por minuto, por endpoint. Cada endpoint gratuito mantém o próprio bucket, então consumir um não consome o outro.
  • 300 requisições por hora, compartilhadas. Todos os endpoints gratuitos consomem uma única cota horária por conta.

Nenhum dos dois é atingido em uso comum, e nenhum deles afeta os limites pagos acima.

O preço é baseado no uso: tokens mais uma taxa fixa de $0.0001 por requisição. O Tablet custa $2 por 1M de tokens de entrada, $3 por 1M de saída. O armazenamento é gratuito e sem limites. Veja por que precificamos desta forma.
Erros & limites

Quando algo dá errado.

Os erros retornam como um envelope JSON com um type estável, uma mensagem legível e um request_id que você pode nos enviar ao relatar um problema.

Resposta real - chave inválida (HTTP 401)
{"type": "error", "error": {
   "type": "authentication_error",
   "message": "Invalid or revoked API key.",
   "request_id": "063f8b83-eee2-4383-a5cf-11e4bcd29d7c"
 }}
HTTPSignificadoO que fazer
400Corpo malformado (campo ausente ou de tipo errado)A mensagem indica o campo exato - corrija e tente novamente.
401Chave de API inválida ou revogadaVerifique a chave; emita uma nova no console.
402Saldo esgotado, nenhum cartão cadastrado, ou limite do nível atingidoAdicione créditos ou cadastre um cartão no console. A resposta traz balance_cents e floor_cents, então você consegue saber qual dos dois barrou a chamada.
404Memória, store ou imagem inexistenteVerifique o id. get_image também responde 404 quando a memória existe mas não carrega nenhuma imagem.
409Esse nome já está em usoOs nomes de store e de workspace são únicos dentro de uma conta - escolha outro.
413Corpo da requisição acima de 10MBO Base64 fica cerca de 33% maior que o arquivo que codifica, então redimensione a imagem antes de codificá-la.
429Limite de requisições excedidoO SDK já tenta novamente por você, com recuo e jitter, respeitando o Retry-After. Receber um deles significa que as tentativas se esgotaram - reduza a sua concorrência em vez de envolver tudo em um laço próprio.
501O motor deste modelo não implementa esse endpointImagens e histórico de revisões exigem um motor mais recente. GET /api/v1/models lista quais modelos atendem o quê.
5xxProblema no servidorTente novamente com recuo, mas não às cegas. O SDK não repete automaticamente um 5xx aqui, porque toda chamada desta API é um POST e o servidor pode já ter armazenado a sua solicitação. Reenvie com uma chave de idempotência para que uma repetição não possa gravar duas vezes, e inclua o request_id se entrar em contato conosco.

Todo erro é um WosError, e cada status também tem uma classe própria - BadRequestError, AuthenticationError, PaymentRequiredError, NotFoundError, ConflictError, RateLimitError, ServerError, APIConnectionError. Capture aquela que você pretende tratar em 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

Alguns erros nunca chegam até nós. A chave de API, o id do store, a chave de idempotência e a imagem são todos verificados antes de a requisição sair, e nesses casos o que é levantado é ValueError ou TypeError - não WosError. Um except WosError sozinho não vai capturá-los.

Segurança da chave. Sua chave é exibida uma única vez na criação e armazenada apenas como hash do nosso lado. Guarde-a em uma variável de ambiente; se vazar, revogue-a no console - a revogação é imediata.

Os limites de requisições são por conta, compartilhados entre todas as suas chaves, e escalam com o seu nível - veja Níveis de uso. O uso da sua conta é exibido no console.

Desenvolvedores

lineage

A cadeia de edições por trás de uma memória, da mais antiga para a mais recente. revisions diz quanto um store mudou; esta chamada diz o que aconteceu com um fato.

Compatível com o Tablet 2 e modelos mais novos. Somente leitura. Diferente de revisions, esta é uma chamada cobrada normalmente, porque retorna conteúdo de memória.

Passe o id de qualquer memória da cadeia. As versões substituídas são mantidas em vez de apagadas, então uma busca que retorna apenas o fato atual ainda pode ser rastreada até a origem.

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 }
  ] }
CampoO que faz
chainAs versões, da mais antiga para a mais recente. Cada uma carrega os mesmos campos de uma memória, mais os quatro abaixo.
changed_atQuando esta versão foi substituída (RFC3339), ou null enquanto ela ainda está em vigor.
actionO que aconteceu neste elo - como a substituição se relacionou com esta versão.
confidenceQuanto o motor estava seguro dessa relação, de 0 a 1.
is_currentTrue para a única versão ainda em vigor. Exatamente uma por cadeia.
truncatedTrue quando a cadeia era mais longa do que o serviço percorre. Os passos retornados continuam sendo os mais antigos.

Para que serve

Dois usos. Depuração: por que uma memória está redigida como está hoje. E permitir que um assistente veja o próprio histórico - um fato corrigido três vezes é um tipo de fato diferente de um escrito uma única vez, e só a cadeia mostra isso.

Won

Won é para quem lê a memória.

A maior parte desta API responde com memória. Won responde sobre ela: quanto de um store foi reescrito e até onde dá para confiar nele. É voltado ao lado que lê, normalmente o assistente que você está construindo, e não à pessoa de quem as memórias falam.

Wontopos é Won + Topos, um único lugar onde a memória mora. Won é a parte desse lugar que informa sobre a memória em vez de devolvê-la. Estas chamadas são gratuitas, somente leitura, e nunca tocam na recuperação: perguntar não custa nada ao seu usuário e não muda nada do que está guardado.

O que está disponível hoje

Por enquanto, uma chamada.

ChamadaO que faz
POST /won/revisionsQuanto deste store foi alterado desde que foi escrito. Dois números e duas frases que os explicam.

Um exemplo prático

Use a proporção, não a contagem bruta. 3 de 40 e 30 de 40 pedem tratamentos diferentes.

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 e excludes são retornados como frases, não como flags, já que quem chama costuma ser um modelo. As exclusões não são contadas.

Preço e limites

RegraValor
PreçoNenhum. As chamadas gratuitas ignoram os controles de cobrança - sem cobrança de tokens, sem taxa por requisição e sem registro de uso.
Por minuto10 por minuto, por conta e por endpoint. Consumir o minuto de um endpoint não consome o de outro.
Por hora300 por hora, por conta, compartilhadas por todas as chamadas gratuitas. Esse limite ignora o caminho, então adicionar endpoints gratuitos não aumenta o total que uma conta pode gastar.
Em relação ao tráfego pagoSeparados nas duas direções. Estas chamadas não podem deixar as suas buscas mais lentas e as suas buscas não podem esgotar estas chamadas. As chaves de uma mesma conta compartilham os buckets, então ter mais chaves não multiplica a cota.

Os dois tetos respondem 429 com Retry-After em segundos e uma mensagem indicando qual deles foi atingido.

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." } }
A mesma chamada também responde em /api/v1/memory/revisions, para clientes publicados antes de a superfície Won existir. É o mesmo handler e o mesmo orçamento, não uma segunda cota. Código novo deve usar o endereço Won.
Won · revisions

Quanto de um store foi reescrito

revisions responde revised de total: quantas memórias de um store foram alteradas depois de escritas. Vale a pena perguntar antes de se apoiar na memória para algo que importa, ou quando um fato lembrado não bate com o que o usuário está dizendo agora. Um store em que três de cada dez fatos foram substituídos merece menos confiança do que um que ninguém editou.

Suportado no Tablet 2 e superiores. Pode ser chamado pela API HTTP, pelos SDKs de Python, TypeScript e Rust, e como ferramenta MCP. Motores mais antigos respondem 501 e informam o modelo que não consegue atender.

Contagens

Conta o que uma transformação tocou - substituído, atualizado, revogado e imagens removidas.

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"}'
CampoO que significa
revisedMemórias que uma transformação tocou.
unrevisedMemórias que nada tocou desde que foram escritas. revised + unrevised é sempre igual a total - é um valor derivado, não contado à parte, então uma escrita concorrente não pode fazer os três discordarem.
totalMemórias que existem no store.
counts / excludesFrases simples, não flags, explicitando o que os números cobrem. Quem chama costuma ser um modelo.

Ler a lista

Passe include para obter as próprias memórias, não apenas a quantidade. Omita e você recebe só as contagens, que é a chamada 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}'
CampoO que faz
include"revised" ou "unrevised". Qualquer outro valor é recusado com um 400 em vez de voltar para as contagens - um erro de digitação que descarta a lista em silêncio fica idêntico a um store vazio.
limitDe 5 a 20, padrão 20. Fora do intervalo, ou com o tipo errado, é recusado, não ajustado ao limite.
matchedTotal de linhas por trás desta página, não o tamanho da página.
ordered_byO serviço declara a própria ordenação: primeiro as armazenadas mais recentemente, não as editadas mais recentemente.
next_beforeCursor para a próxima página, junto com next_skip_ids. Devolva os dois; os ids se acumulam entre as páginas.
A lista é ordenada por quando uma memória foi armazenada, não por quando foi alterada. Quem assume "editadas mais recentemente primeiro" lê a página de forma errada, e por isso a resposta informa qual é a ordem.
As exclusões não são contadas. Uma memória apagada não deixa nada para contar, então um store bastante podado continua reportando um revised baixo. Este número diz quanto foi reescrito, não quanto desapareceu.
Escala

Além da janela de contexto.

O WOS recupera de históricos de 1.4M tokens - muito maiores que qualquer janela de contexto de LLM - e ainda assim devolve uma fatia enxuta de ~1,470 tokens.

A memória do seu agente não é limitada pelo que cabe em um prompt. Ela guarda tudo e recupera apenas o que importa, não importa o quanto o histórico cresça.

Privacidade

Privado, e seu.

Seus dados permanecem no seu store. Nunca treinamos com eles, os visualizamos ou os reutilizamos - apenas os organizamos para que você possa recuperá-los.

  • BYOK. Sua chave de LLM é enviada a cada requisição e nunca armazenada.
  • Isolado. As memórias têm escopo por conta e, depois, por user_id.
  • Exclusão GDPR & self-host. Uma única chamada apaga um usuário; execute o motor no seu próprio ambiente, se preferir.