🗺️ Arquitetura da memória
A memória do OpenHuman não é um banco — é uma cadeia de domínios Rust que cooperam. Cada etapa tem uma responsabilidade nítida e pode ser depurada em isolamento.
Domínios envolvidos
memory ← coordenação memory_queue ← fila de ingest memory_tree ← chunks + summaries hierárquicos memory_store ← persistência memory_entities ← canonização (pessoa, projeto, etc.) memory_graph ← relações entre entidades memory_sync ← reconciliação memory_archivist← housekeeping embeddings ← vetores tokenjuice ← compressão pré-LLM
🔄 Pipeline em uma frase
Mensagem chega → TokenJuice comprime → tree summarizer cria chunks e nós de resumo → embeddings indexam → store persiste → entities ligam o conteúdo a "Maria", "Projeto X" → fica pronto para recall.
💤 Subconscious — o loop de fundo
O subconscious é o motor que faz o agente continuar pensando depois que você fechou o chat. A cada tick (mínimo 5 min), ele carrega tarefas pendentes, avalia uma a uma com o modelo local, e decide: skip, act ou escalate.
Heartbeat dispara
Timer roda; se o tick anterior ainda está em execução, o novo assume e cancela o antigo. Ticks nunca empilham.
Engine monta situação
Combina memória + estado do workspace dentro do context budget e passa para o modelo local avaliar.
Decisão por tarefa
Skip = log "nada novo". Act = executa (local ou cloud). Escalate = handoff para agente cloud (com gate de aprovação se write inesperado).
Activity log registra
Cada avaliação aparece em Intelligence → Subconscious com cor (azul, verde, cinza, âmbar, coral) e status.
✓ Quando aprovação NÃO é precisa
- ✓Tarefa write-intent ("envie digest no Slack") — você já pediu
- ✓Tarefa read-only que só lê e reporta
- ✓Skip — não faz nada
✗ Quando aprovação É precisa
- ✗Tarefa read-only descobre algo e quer escrever sem você pedir
- ✗Escalation para cloud com write não solicitado
- ✗Caso vire card amber em "Approval Needed"
💡 Dica prática
Estenda as system tasks listando uma por linha em HEARTBEAT.md no workspace. Não precisa redeploy.
🌳 Tree summarizer
O memory tree resolve um problema clássico: como recuperar contexto sem despejar tudo no prompt. A solução é uma árvore de resumos — folhas com chunks brutos, nós intermediários com resumos, raiz com visão global.
Estrutura conceitual
[Root summary]
│
├── [Source: Slack]
│ ├── [Channel: #produto]
│ │ ├── chunk (mensagem bruta)
│ │ └── chunk
│ └── [Channel: #engenharia]
│ └── chunk
└── [Source: Gmail]
├── [Thread: lançamento Q1]
│ ├── chunk
│ └── chunk
└── [Thread: contrato cliente Y]
└── chunk
📊 Por que árvore (e não lista plana)
- Granularidade adaptativa: o agente puxa só o galho necessário
- Recall top-down: resume → desce → resume → desce
- Custo controlado: não embed-a o universo inteiro toda hora
- Atualização local: mensagem nova invalida só o ramo dela
🧮 Embeddings e busca semântica
Cada chunk vira um vetor. Quando o agente quer "tudo sobre o lançamento de Q1", ele compara o vetor da pergunta contra os vetores dos chunks e ranqueia por similaridade. Isso é recall semântico. Já search é keyword pura.
Tools expostas via MCP / JSON-RPC
memory.search → keyword (FTS-style) memory.recall → semântico (vetor) tree.read_chunk → ler 1 chunk pelo id tree.browse → listar com filtros (source, entity, tempo) tree.top_entities → entidades mais referenciadas tree.list_sources → fontes ingeridas + contagens
✓ Use recall quando
- ✓Conceito difuso ("clima do time esta semana")
- ✓Idiomas misturados, sinônimos importam
- ✓Pergunta longa em linguagem natural
✗ Recall NÃO é mágica
- ✗ID, número de pedido, código exato → use search
- ✗Filtro temporal duro → use browse com
since_ms - ✗Top-k grande não compensa qualidade ruim
💬 Threads vs conversations
Pós-unificação /chat, a unidade canônica de diálogo é thread. Conversations legadas, agentes separados, accounts em rotas próprias — tudo foi absorvido em uma única superfície.
Modelo de uma thread
- • Mensagens ordenadas
- • Participantes (humanos + agente)
- • Canal de origem (Slack, e-mail, web)
- • Contexto vinculado (chunks de memória)
- • Estado (ativa, arquivada)
O que NÃO existe mais
- • Rota
/conversations - • Rota
/accounts(virou parte de/chat) - • Rota
/agents - • Rota
/loginou/mnemonic
💡 Dica prática
Se você ainda vê referências a "conversation" no código frontend, é dívida. O store novo é thread (em app/src/store/).
🧃 Token compression (TokenJuice)
TokenJuice é o que torna economicamente viável processar 6 meses de e-mail. Roda no caminho de execução de tools, comprime a saída antes de entrar no LLM, e usa regras JSON sobrepostas em 3 camadas.
Camadas de regra (later wins)
1. Builtin ← shipped com o binário (git, npm, cargo, docker, kubectl) 2. User ← ~/.config/tokenjuice/rules/ (overrides pessoais) 3. Project ← .tokenjuice/rules/ (overrides do repo)
📊 Estratégias de redução
- truncate — cortar após N linhas / N tokens
- dedup lines — eliminar repetições
- fold whitespace — colapsar espaços/quebras
- drop regex — remover linhas que casam pattern
- summarize sections — agrupar e resumir blocos
Debug — ver o que está casando
RUST_LOG=openhuman_core::openhuman::tokenjuice=debug \ ./target/debug/openhuman-core serve
Mostra qual regra matchou cada tool call e quanto da saída foi cortado.
💡 Dica prática
Quando um git status em monorepo enche o contexto, escreva uma regra de projeto cortando paths irrelevantes. ROI imediato em $ e em qualidade da resposta.
🧠 Resumo do módulo
/chat, esqueça /conversations.Próximo módulo:
3.3 — 🧩 Skills e tools