📥 O adaptador do canal publica no bus
Nada no v3 fala diretamente com o Telegram, com o terminal ou com a porta HTTP. Cada canal tem um adaptador, e o trabalho dele é um só: traduzir o que chegou para um aviso no bus, chamado mensagem.recebida. Daí em diante, o resto do sistema não sabe nem se importa de onde veio.
🆕 Novo aqui?
- Bus (barramento): o correio interno do programa. Um pedaço publica um aviso, outro escuta. Serve para o canal não precisar conhecer o cérebro.
- Adaptador: o tradutor de um canal específico. O do Telegram usa a biblioteca grammy, lê texto e legenda de foto ou vídeo, e guarda quem falou.
- Ollama: o programa que roda modelos de IA dentro da sua própria máquina, sem internet e sem custo por uso.
- Chat autorizado: o Telegram do v3 só responde ao identificador em
ALLOWED_CHAT_ID. Qualquer outro chat vira uma linha no log, não uma resposta.
O que olhar: existem três saídas possíveis, e todas as três voltam a se juntar no mesmo ponto azul do fim. Nenhum texto sai para você sem passar pela guarda.
◆ Conceito principal
No Telegram existem dois tipos de chat. Os de TELEGRAM_CHATS_RESPONDER conversam com você. Os de TELEGRAM_CHATS_OBSERVAR só alimentam o cérebro: entram no conversation_log com agent_id = 'observado' e nunca recebem resposta. A cada 30 minutos o cron ingestao-observados transforma o que chegou em memória.
⚠️ Atenção
Grupo observado é conversa de outras pessoas, e elas não sabem que um bot está guardando o que dizem. Observe só onde isso for combinado.
⏱️ A janela de 2 segundos e os outros modos
Quando a mensagem não começa com barra, o orquestrador espera. O modo padrão se chama collect: mensagens seguidas dentro de uma janela de 2 segundos viram uma só. É isso que evita três respostas quando você escreve em três balões.
🆕 Novo aqui?
- Lane (faixa da fila): uma pista com quantos trabalhos podem correr juntos nela.
chataceita 2,agenteaceita 1,ollama1,cron1 eio4. - Modo de fila: o que fazer com uma mensagem que chega enquanto outra ainda está sendo respondida. Vale por chat e se troca com
/fila.
✓ Os quatro modos, honestamente
- ✓
collect: junta 2 segundos e responde uma vez. É o padrão. - ✓
followup: sem janela. Se estiver ocupado, entra na fila e responde depois. - ✓
steer: substitui o que estava na fila daquele chat pela mensagem nova, com prioridade alta. - ✓
interrupt: cancela fila e agentes daquele chat e responde à nova agora.
✗ O que esses modos não fazem
- ✗
steernão injeta a mensagem no agente que já está rodando: oclaude -pé um subprocesso, então a nova só entra quando o atual terminar. - ✗
interruptnão aborta uma resposta direta já em voo no Ollama: ela termina e é descartada por um contador de geração. - ✗Cancelar não é instantâneo: um
claude -pem execução morre na batida seguinte do worker, em até 30 segundos.
◆ Conceito principal
O modo escolhido fica gravado na tabela prefs, que é um par chave e valor por chat, com '*' valendo como global. Por isso ele sobrevive a um restart do serviço.
✦ Dica prática
Se você costuma pensar em voz alta e mandar cinco balões seguidos, collect é o seu modo. Se costuma corrigir o pedido no meio, experimente /fila steer naquele chat.
🧭 O roteador decide direto ou agente
Antes de gastar qualquer centavo, um modelo pequeno e local decide o que fazer. É o roteador: o papel roteador em config/ollama.yaml usa llama3.2, responde em cerca de 2 segundos, ocupa 4 GB e devolve um JSON com quatro campos, {rota, agente, tier, motivo}.
🆕 Novo aqui?
- Roteador: um classificador, não um conversador. Ele não responde a você, só diz por onde a mensagem deve seguir.
- Ollama: o programa que roda modelos de IA na sua máquina. O v3 conversa com ele só por HTTP, na porta 11434 do serviço systemd, que é o gerenciador de serviços do Linux, nunca subindo um processo paralelo.
- Tier: a faixa de custo escolhida para a chamada.
localcusta zero,baratoepremiumcustam dinheiro.
Rota direto
Conversa, pergunta curta, resumo, tradução. Segue para o gateway de custo no tier local e volta como resposta, sem passar pela fila de agentes.
Rota agente
Precisa de ferramenta: arquivo, shell, repositório, web, skill, calendário. Vira um job na lane agente, que roda um de cada vez.
Indicação de consultar
Até dois agentes só leitura, por exemplo o research, rodam antes e entregam as saídas deles ao agente lead. Só o lead escreve.
◆ Conceito principal
Decidir com um modelo local e grátis antes de chamar um modelo pago é a economia central do projeto. O campo motivo do JSON existe para que a decisão fique auditável depois, e não só executada.
⌨️ Copie e rode
Objetivo: ver o caminho da mensagem acontecendo em tempo real, do adaptador até a resposta.
journalctl --user -u openpcbotv3 -f -o cat
Como verificar: com o log aberto, mande uma mensagem ao bot no Telegram. As linhas em JSON aparecem na hora. Para sair, aperte Ctrl+C.
💬 Resposta direta: o prompt em camadas
Na rota direta, o que chega ao modelo não é só a sua frase. É um bolo montado em camadas: a identidade do bot, a persona escolhida para aquele chat, o USER.md do vault, o contexto de memória e o histórico recente da conversa.
🆕 Novo aqui?
- FTS5: a busca por palavra que já vem dentro do SQLite. Acha memórias que contêm as palavras da sua pergunta.
- Embedding (vetor): a mesma frase virada em uma lista de números que aproxima significados parecidos. O v3 usa o modelo
bge-m3, com 1024 números por memória, e compara por cosseno. - Salience (importância): uma nota de cada memória. Começa em 1.0, sobe 0,1 a cada uso e cai um pouco por dia sem uso.
- Vault: dois arquivos em Markdown que você controla,
MEMORY.mdeUSER.md. Nada é escrito neles sem a sua aprovação.
O que olhar: à esquerda, os três caminhos de busca que alimentam uma só caixa de memória. À direita, a pilha na ordem em que entra no prompt, com a sua frase por último.
◆ Conceito principal
O contexto de memória tem teto de 600 tokens e é montado assim: 3 memórias por palavra-chave, 3 por vetor, 3 mais importantes, 3 mais recentes, sem repetir, mais 3 insights. Teto existe porque cada token dessas camadas é pago em toda mensagem, mesmo quando a pergunta é curta.
✦ Dica prática
O comando /context mede quanto cada camada está ocupando, sem chamar modelo de chat. Ele roda o retrieval em modo somente leitura, então consultar não infla a importância das memórias.
🤖 Agente: um job na fila, um processo, um resultado
Quando a rota é agente, a mensagem não é respondida na hora: ela vira um job gravado na fila, na lane agente, que executa um por vez porque cada processo consome cerca de 500 MB de memória. O worker então roda claude -p --output-format json com o prompt em camadas.
🆕 Novo aqui?
- Job: uma linha na tabela da fila descrevendo um trabalho a fazer. Jobs nunca são apagados: eles são o histórico do sistema.
- Lease (aluguel): a marca de "este job é meu agora", renovada por um sinal de vida a cada 30 segundos. Se o processo morre, o aluguel vence e outro worker pode pegar o job de volta.
- Skill: uma receita em
skills/<id>/SKILL.md. Só o resumo entra no prompt, para não gastar token à toa. - Sessão retomada: o agente continua a conversa anterior em vez de começar do zero.
✓ O que a fila garante
- ✓Pegar um job é atômico, então dois workers nunca pegam o mesmo.
- ✓Falhou, tenta de novo com espera crescente entre as tentativas.
- ✓A mesma tarefa com a mesma
idem_keynão dispara duas vezes. - ✓Todo job com
chat_idavisa o chat ao terminar, dando certo ou errado. Se o envio falhar, uma varredura a cada 20 segundos reentrega.
✗ O que continua sendo limite
- ✗Um agente por vez: o segundo pedido espera o primeiro acabar.
- ✗Job enfileirado antes de um
/pararfalha ao ser pego, com o motivo, sem gastar token. - ✗Um job que passar de duas vezes o tempo limite de 20 minutos é tratado como zumbi e cancelado pelo heartbeat.
◆ Conceito principal
O padrão dos especialistas vale a pena entender: até dois agentes só leitura rodam em paralelo, o lead recebe as saídas deles no prompt, e só o lead escreve. Leitura em paralelo é barata e segura; escrita concorrente é que faz estrago.
⌨️ Copie e rode
Objetivo: mandar uma mensagem pela porta HTTP e ver o caminho inteiro sem abrir o Telegram.
curl -X POST http://127.0.0.1:3142/mensagem \
-H 'Content-Type: application/json' \
-d '{"texto":"resuma <o-assunto-que-voce-quiser> em tres linhas"}'
Como verificar: a mensagem aparece no journalctl do bloco anterior. Se o pedido virar trabalho de agente, /status no chat mostra o job e a lane em que ele caiu.
🧾 Depois da resposta: o que fica gravado
A mensagem respondida não acaba na resposta. Quatro coisas acontecem depois dela, e é esse rastro que faz o assistente melhorar em vez de esquecer tudo a cada conversa.
O turno é logado
Pergunta e resposta entram no conversation_log. É desse log que sai o histórico recente que volta ao prompt na próxima mensagem.
A memória é classificada em português
Toda mensagem sua com mais de 20 caracteres vira uma memória. Uma regex em PT-BR e inglês decide se é semantic, ou seja durável, quando aparecem marcas como "meu", "prefiro", "sempre", "nunca", "moro em", ou episodic. Pergunta nunca vira fato, e frases repetidas são descartadas por hash.
O vault pode receber uma proposta
Se o fato parece durável, o bot propõe uma entrada para o MEMORY.md ou o USER.md. Proposta não é escrita: nada entra sem /memoria aprovar <id>.
O custo é registrado
O gateway grava a chamada em chamadas_llm com latência e custo. Na rota de agente, o valor não é estimado: o custo real vem do próprio CLI claude. O /usage lê essa tabela por dia, por tier e por agente.
◆ Conceito principal
Antes de qualquer texto sair para você, ele passa pela guarda de exfiltração: uma varredura que procura tokens, chaves, JWT e valores do .env e troca o que achar por [REDIGIDO]. Vale para as três rotas, inclusive para o que um agente escreveu.
✦ Dica prática
De madrugada, às 4h, a consolidação junta memórias duplicadas, marca contradições com superseded_by em vez de apagar, e gera até três insights por chat. Roda no Ollama, então custa zero. Para rodar na hora, use /consolidar.
✅ Resumo do Módulo
Próximo módulo:
2.1 - Subir o v3