🧠 O que o cérebro guarda
O cérebro do v3 mora em src/cerebro/ e no banco store/openpcbotv3.db. Ele não é uma coisa só: são quatro depósitos com regras diferentes. A tabela memories guarda fatos; o vetor bge-m3 guarda o significado desses fatos; o conversation_log guarda os turnos da conversa; e os insights guardam o que a consolidação percebeu sozinha. Entender qual é qual é o que evita procurar no lugar errado.
Legenda: as setas roxas são a escrita, que acontece a cada mensagem sua. As setas cianas tracejadas são a leitura, que acontece no começo da resposta seguinte. Nada disso sai da máquina.
| Depósito | O que entra | Quem escreve | Quando some |
|---|---|---|---|
memories setor semantic | frase durável: "meu", "prefiro", "sempre", "nunca", "lembra", "moro em", "meu nome" | cada mensagem sua com mais de 20 caracteres; /memoria salvar; a ingestão | decai 0,5 % por dia sem uso |
memories setor episodic | o resto da conversa que não bateu na regra de durável | o mesmo classificador por regex, em PT-BR e inglês | decai 2 % por dia sem uso |
Vetor bge-m3 | 1024 floats por memória, num BLOB | o cron indexar-memoria, a cada 15 min | junto com a memória |
conversation_log | turnos do seu chat e dos chats observados (agent_id = 'observado') | o orquestrador, a cada turno | /novo apaga os do chat |
| Insights | até 3 por chat, texto curto | a consolidação das 4h, no Ollama | gerados na consolidação das 4h |
Legenda: pergunta nunca vira fato. E frase repetida não duplica: existe dedupe por hash da frase normalizada.
▦ Novo aqui? Saliência
Saliência é o "peso" de uma memória. Toda memória nasce com 1.0. Cada vez que ela é usada numa resposta, ganha +0.1. Cada dia sem uso, perde: memória semântica 0,5 %, episódica 2 %. Quando cai abaixo de 0.05 e não é acessada há 30 dias, o cron decaimento (3h30) apaga. O efeito prático: o que você repete fica; o que foi só do momento se dissolve sozinho, sem você limpar nada.
✓ O que vira memória boa
- ✓"Prefiro reunião de manhã, nunca depois das 17h." Frase afirmativa, durável, curta.
- ✓"Meu servidor de casa chama
<nome-da-sua-maquina>e roda Ubuntu." Dado de fato, com nome. - ✓
/memoria salvar <texto>quando você quer garantir: entra comosemantic, origemmanual.
✗ O que não vira (ou vira ruim)
- ✗Pergunta. "Qual meu horário preferido?" nunca vira fato, por desenho.
- ✗Mensagem com 20 caracteres ou menos: passa direto, não é registrada como memória.
- ✗Parágrafo de dez linhas: a memória guarda a frase inteira, então o trecho inteiro vai junto (tópico 6).
🔎 /memoria e o retrieval em 3 camadas
O comando /memoria é a porta de entrada do cérebro. Sem subcomando, ele assume lista. Cada subcomando tem uma forma de resposta fixa, e conhecer essa forma poupa tempo: é nela que está o #id que você usa para esquecer, aprovar ou descartar.
| Subcomando | O que faz | Forma da resposta |
|---|---|---|
/memoria lista | as mais recentes deste chat | cabeçalho com total e quantas são semânticas, depois até 15 linhas #id [setor·saliência] texto, texto cortado em 90 caracteres |
/memoria buscar <termo> | busca no cérebro deste chat | até 8 linhas #id texto cortado em 120 caracteres, ou "Nada encontrado." |
/memoria salvar <texto> | grava à mão como semantic, origem manual | "Guardado." e o convite para levar também ao vault, com o id da proposta |
/memoria esquecer <id> | apaga uma memória | confirma o id apagado, ou "Não encontrei." |
/memoria propostas|aprovar <id>|descartar <id> | fila do vault | tópico 3 |
Legenda: o setor aparece pela primeira letra (s de semântica, e de episódica) e a saliência com duas casas. Um [s·1.00] é memória durável nova; um [e·0.31] é conversa antiga a caminho do decaimento.
Legenda: o #id da esquerda é o que vai em /memoria esquecer <id>. A saliência acima de 1.00 mostra memória que já foi usada em resposta.
O contexto em 3 camadas é o que monta o pedaço de memória que entra no prompt de toda conversa, com teto de 600 tokens. O README descreve o conteúdo assim: 3 por palavra-chave, 3 por vetor, 3 mais importantes, 3 mais recentes, sem repetir, mais 3 insights.
| Bloco | Quantos | Como é escolhido |
|---|---|---|
| Palavra-chave | 3 | FTS5 sobre o texto da memória, com os termos da sua mensagem |
| Vetor | 3 | cosseno entre o vetor bge-m3 da sua mensagem e o das memórias |
| Mais importantes | 3 | maior saliência |
| Mais recentes | 3 | ordem de chegada |
| Insights | 3 | o que a consolidação das 4h escreveu para este chat |
Legenda: os blocos se sobrepõem de propósito. Um item que apareceria em dois blocos entra uma vez só. Por isso a conta raramente fecha em 15 itens.
⌘ Copiar e rodar: ensinar um fato e recuperar
Objetivo: gravar um fato seu, achar de novo pela busca e ver a memória aparecer na contagem, tudo no chat do Telegram com o bot.
/memoria salvar meu servidor de casa chama <nome-da-sua-maquina> e roda Ubuntu
/memoria buscar <nome-da-sua-maquina>
/memoria lista
Como verificar: a primeira resposta confirma que guardou e oferece o vault com um id de proposta. A busca precisa devolver uma linha com #id e o seu texto. Na lista, ela aparece no topo com [s·1.00]. Se a busca não achar, confira se a frase tem mais de 20 caracteres e se você usou salvar e não buscar.
✦ Dica prática
Memória recém-criada ainda não tem vetor: o cron indexar-memoria só passa a cada 15 minutos. Nesse intervalo ela é achável por palavra-chave, não por significado. Se você acabou de ensinar um fato e a busca por sinônimo não acha, não é bug: espere o próximo ciclo ou busque pelas palavras exatas. Para medir quanto do prompt é memória, use /context detail (Trilha 4), que lê sem inflar a saliência.
📓 Vault: MEMORY.md e USER.md
O banco é automático: enche sozinho e decai sozinho. O vault é o contrário: dois arquivos de texto em ~/vault/ que só mudam quando você aprova. MEMORY.md é o caderno do que importa; USER.md é quem você é, e entra no prompt de toda conversa. Nada é escrito sem aprovação, nem pelo bot, nem por um agente.
Banco (memories) | Vault (~/vault/*.md) | |
|---|---|---|
| Como entra | sozinho, a cada mensagem sua | o bot propõe, você aprova com /memoria aprovar <id> |
| Formato | linha por fato, com setor e saliência | markdown que você pode abrir e editar no editor |
| Decai | sim, por dia sem uso | não, fica até você tirar |
| No prompt | pelo retrieval, dentro dos 600 tokens | USER.md inteiro, em toda conversa |
| Onde aparece | /memoria lista | os primeiros 500 caracteres de MEMORY.md vão no /daily |
Legenda: use o banco para o volume e o vault para o que você não aceita perder. O vault é pequeno de propósito: ele custa tokens em toda mensagem.
✓ O que merece o vault
- ✓Seu nome, seu fuso, como você quer ser tratado: isso é
USER.md. - ✓Regras que valem sempre, do tipo "publicar é commit e push, nunca mexer no painel do provedor".
- ✓Rever a fila com
/memoria propostasuma vez por semana e decidir tudo de uma vez. - ✓Abrir o arquivo no editor quando quiser reorganizar: é markdown comum, seu.
✗ O que não põe no vault
- ✗Fato de um dia só ("hoje o deploy saiu às 14h"): isso é episódico, deixa decair.
- ✗Senha, token ou chave. A guarda de exfiltração redige na saída, mas o arquivo em disco não é lugar de segredo.
- ✗Aprovar toda proposta que aparece:
USER.mdinflado entra em cada prompt e come contexto de graça. - ✗Esperar que
/memoria esquecerlimpe o vault: ele age no banco. Vault se edita no arquivo ou se descarta antes de aprovar.
⌘ Copiar e rodar: aprovar uma entrada no vault
Objetivo: ver a fila de propostas, aprovar uma e confirmar que o arquivo mudou.
/memoria propostas
/memoria aprovar <id-da-proposta>
/daily
Como verificar: as propostas vêm no formato #id → arquivo: texto. Ao aprovar, o bot confirma em qual arquivo gravou. Fora do chat, cat ~/vault/MEMORY.md mostra a linha nova, e o /daily passa a trazer o começo do arquivo no fim do resumo. Para recusar, /memoria descartar <id>.
🌙 Consolidação noturna
Memória que só cresce vira lixo. Toda madrugada o v3 passa três vezes pelo cérebro, em ordem, e sempre no Ollama local: custo zero. O resultado é um banco menor, mais coerente e com o que é novo por cima do que ficou velho.
3h30: decaimento
Reduz a saliência de tudo que passou o dia sem ser usado: 0,5 % para semântica, 2 % para episódica. O que ficou abaixo de 0.05 e não é acessado há 30 dias é apagado. É a poda antes da limpeza.
4h: consolidacao-noturna
Funde duplicatas e fica com a mais recente. Detecta contradições por data: a memória antiga recebe superseded_by apontando para a nova e nunca é apagada, então dá para auditar o que mudou. Por fim escreve até 3 insights por chat.
4h15: backup-noturno
Copia o banco já consolidado com VACUUM INTO, cifra com age (ou comprime com gzip quando não há destinatário) e guarda em store/backups/, com retenção de 14. Backup sempre depois da limpeza, nunca antes.
Quando você não quer esperar
/consolidar enfileira a consolidação agora, como job na lane ollama, com uma tentativa só. O bot devolve o número do job; /status <id> acompanha. Como a lane ollama roda um por vez, ela espera a indexação ou a ingestão que estiver em curso.
⌘ Copiar e rodar: forçar a consolidação e conferir
Objetivo: rodar a consolidação fora do horário e ver o efeito na contagem de memórias.
/memoria lista
/consolidar
/status <numero-do-job-que-o-bot-devolveu>
/memoria lista
Como verificar: anote o total do cabeçalho da primeira lista. O /consolidar responde com o job enfileirado. Quando o /status mostrar done, a segunda lista precisa ter total igual ou menor, nunca maior. Se o job falhar, quase sempre é Ollama fora de ar: confira com /health e /ollama status.
✦ Dica prática
Contradição não é erro: é como você corrige o bot sem comando nenhum. Se a memória diz "moro em Porto Alegre" e você escreve "mudei para Florianópolis", a consolidação marca a antiga com superseded_by e a nova passa a valer. O caminho rápido de correção é esse, e o caminho cirúrgico é /memoria esquecer <id> (tópico 6).
🔌 Conectores: Gmail, agendas e Telegram observado
Conector é o que liga o bot às suas fontes de informação. A documentação separa de propósito três coisas que costumam ser confundidas, e a diferença entre elas é a diferença entre um bot que lê e um bot que aprende.
| O que é | Como se liga | |
|---|---|---|
| Acesso | o bot consegue ler a fonte quando você pede | token OAuth por conta (Google) ou o próprio bot (Telegram) |
| Observação | o bot vê o que passa, sem responder | lista de chats observados no .env |
| Ingestão | o que passou vira fato na memória | cron que lê o novo e extrai fatos no Ollama, custo zero |
Legenda: acesso sem ingestão é o comportamento do v2: o bot lê seu e-mail quando você manda, e esquece depois. A ingestão é o que o v3 acrescenta.
Legenda: nada nesse caminho sai da máquina. A extração roda no modelo local, e por isso um grupo movimentado ou uma caixa de entrada cheia não geram custo nenhum.
Google, uma credencial para todas as contas. Você cria um projeto no Google Cloud, ativa Gmail API e Google Calendar API, configura a tela de consentimento como External com todos os seus e-mails em Test users, gera um OAuth client ID do tipo Desktop app e salva o JSON como ~/.config/google/credentials.json com permissão 600. Essa credencial serve todas as contas. Cada conta ganha um apelido em ~/.config/google/contas.json e um token próprio, criado uma vez com auth. Os scripts moram no repo, em conectores/google/; os segredos nunca entram no repo.
| Passo | Comando ou arquivo | Resultado |
|---|---|---|
| Credencial, uma vez | ~/.config/google/credentials.json (chmod 600) | o "app" que serve todas as contas |
| Declarar as contas | ~/.config/google/contas.json: apelido para e-mail | a primeira do arquivo é a padrão; GOOGLE_CONTA_PADRAO fixa outra |
| Autenticar cada uma | python3 gmail.py --conta <apelido> auth | ~/.config/google/token_gmail_<apelido>.json, renovado sozinho |
| Agenda | python3 gcal.py --conta <apelido> auth | mesmo arquivo de contas, consentimento separado porque o escopo é outro |
| Usar | --conta aceita um apelido, vários por vírgula, ou todas | saída sempre em JSON; conta com token quebrado avisa e não derruba as outras |
| Ligar a ingestão | /cron on gmail-ingestao · /cron on agenda-ingestao | e-mail a cada 2 h (últimas 3 h); agenda às 7h05 (próximos 14 dias) |
Legenda: agenda compartilhada entra sozinha, sem apelido novo: o token da conta enxerga tudo que aquela conta enxerga no Google Calendar. Memória vinda do e-mail fica com origem gmail:<apelido>; da agenda, gcal:<apelido>.
Telegram observado. Um bot vê o que acontece na frente dele, dali para frente. Ele lê mensagens dos chats onde está, lê todo o texto de um grupo se a privacidade estiver desligada, lê legenda de foto e vídeo e recebe encaminhadas. Ele não lê suas conversas privadas com outras pessoas, não lê histórico anterior à entrada dele, não lista seus contatos e não lê canal onde não é membro. Para grupo funcionar, desligue Group Privacy no BotFather e remova e adicione o bot de volta em cada grupo: a configuração só vale a partir da nova entrada.
Variável no .env | O que define | Regra |
|---|---|---|
TELEGRAM_CHATS_RESPONDER | chats onde o bot conversa | padrão: só o ALLOWED_CHAT_ID |
TELEGRAM_CHATS_OBSERVAR | chats que ele só observa: grava e nunca responde | aceita lista, ou todos para qualquer chat em que ele for adicionado |
| Chat nas duas listas | vale como chat de conversa | |
| Chat fora das duas | ignorado, nem grava | |
| As duas vazias | o bot responde a quem falar com ele |
Legenda: para descobrir o id, mande /chatid no chat ou olhe o log, onde um chat desconhecido aparece uma vez justamente para isso. Grupo tem id negativo; conversa pessoal tem id positivo. Depois de editar o .env, aplique com bash scripts/instalar-servico.sh e confirme com /fontes.
O que acontece com uma mensagem observada: ela entra no conversation_log com agent_id = 'observado', o autor no início do texto e o nome do grupo junto. Nenhuma resposta, nenhum modelo, nenhum custo nesse momento. A cada 30 minutos o cron ingestao-observados pega o que chegou desde a última passagem, monta um bloco por grupo e faz uma única chamada no Ollama residente pedindo os fatos duráveis: decisão, compromisso, prazo, preferência, dado de pessoa, valor combinado. Cada fato vira memória com origem telegram:<chatId>, entra no FTS5 e ganha vetor no ciclo seguinte. O marcador ingestao_estado garante que rodar duas vezes não reprocessa; se o Ollama estiver fora, o bloco não é marcado e volta na próxima passagem.
⌘ Copiar e rodar: conferir as fontes e forçar a ingestão
Objetivo: ver quais chats o bot observa, quanto de cada fonte já virou memória e rodar a ingestão sem esperar o cron.
/fontes
/fontes ingerir
/status <numero-do-job-que-o-bot-devolveu>
/memoria buscar <palavra-que-apareceu-no-grupo>
Como verificar: o /fontes mostra em quais chats o bot responde, quais observa e, por fonte, quantos itens já foram ingeridos e quando foi o último. O /fontes ingerir devolve o job; quando ele fica done, a busca precisa achar o fato. Sem argumento, ingerir roda só os chats observados; /fontes ingerir gmail e /fontes ingerir agenda fazem o mesmo para os conectores Google. Ingestão que devolve zero fato costuma ser conversa sem nada durável, e isso é o esperado.
✦ Dica prática
Grupo observado é conversa de outras pessoas, e elas não sabem que um bot está guardando fatos. Em grupo de trabalho isso costuma ser aceitável; em grupo de família ou de terceiros, pergunte antes. Vale lembrar o limite técnico do outro lado: importar o seu Telegram inteiro, com DMs e histórico, exigiria uma sessão de usuário via MTProto, que não está implementada no v3 e cujo arquivo de sessão equivale à senha da sua conta. A documentação descreve o desenho e diz de forma explícita que isso só é ligado se você pedir.
🧹 O que a memória ainda não faz
O README é direto sobre o limite principal: a memória guarda a frase inteira, não um fato extraído. Quando você conversa com o bot, a sua mensagem é classificada e armazenada como veio. Não há um passo que a reduza a "usuário prefere reunião de manhã". Repare no contraste com o tópico anterior: na ingestão de e-mail, agenda e grupo observado existe extração, feita em lote no Ollama. Na sua conversa, não.
| Caminho | Passa por extração | O que fica gravado |
|---|---|---|
| Você conversa com o bot | não | a frase inteira, classificada em semântica ou episódica |
| Chat observado do Telegram | sim, a cada 30 min | fatos duráveis extraídos do bloco do grupo |
| Gmail e Google Calendar | sim, no cron de cada um | fatos duráveis, com a conta na origem |
Legenda: a consequência prática é simples. Frase longa vira memória longa, come parte dos 600 tokens do contexto e pode trazer junto um detalhe que já não vale.
✓ Como corrigir uma memória errada
- ✓
/memoria buscar <termo-errado>para achar o#id, depois/memoria esquecer <id>. É o corte cirúrgico. - ✓Afirmar o fato novo e deixar a consolidação das 4h marcar o antigo com
superseded_by. O histórico fica auditável. - ✓
/memoria salvar <frase-curta-e-correta>: escrever você mesmo a versão enxuta que queria que a extração tivesse feito. - ✓Para o que não pode errar nunca, levar ao vault com
/memoria aprovar: lá não decai e não se contradiz sozinho.
✗ O que não resolve
- ✗Pedir em prosa "esquece aquilo": a frase vira mais uma memória, e a errada continua lá.
- ✗
/novo: apaga sessão e histórico do chat, não toca emmemoriesnem no vault. - ✗Esperar
/retryou/undo: estão anotados como fase 10, não existem hoje. - ✗Apagar o banco para "limpar": a tabela
jobsnunca é purgada e cresce cerca de 1,5 mil linhas por dia, mas isso é histórico de fila, não memória.
▦ Novo aqui? Apagar uma fonte inteira
Esquecer um fato é /memoria esquecer <id>. Desfazer uma fonte inteira, por exemplo tudo que veio de um grupo do Telegram, é um DELETE FROM memories WHERE origem = 'telegram:<id-do-grupo>' no banco, com o serviço parado. Essa não tem comando de chat, e é assim de propósito: apagar em lote não deve ser fácil de fazer sem querer.
✦ Dica prática
Duas proteções que valem saber antes de ligar tudo. Os escopos do Gmail hoje são leitura, modificação e envio, para o bot poder marcar como lida e responder; se você quer só leitura, tire gmail.send e gmail.modify de SCOPES_GMAIL em conectores/google/comum.py e refaça o auth de cada conta. E toda resposta que sai passa pela guarda de exfiltração: token, chave de API, JWT e os valores das variáveis sensíveis do .env viram [REDIGIDO], inclusive quando vieram de um e-mail ingerido.
✅ Resumo do módulo
memories com setor e saliência, vetor bge-m3, conversation_log e insights; pergunta nunca vira fatolista traz 15, buscar traz 8, salvar grava e propõe o vault, esquecer apaga pelo #idMEMORY.md e USER.md em ~/vault/; USER.md entra em toda conversasuperseded_by, insights), 4h15 copia; /consolidar antecipa/fontes; sua conversa guarda a frase inteira, não o fatoPróxima trilha:
Trilha 4 - Configurar e estender: interruptores, modos de fila, persona, custo do prompt, agentes e skills com a sua cara.