Memória de longo prazo para agentes de IA, feita do que você já escreveu. O Cortex transforma o seu cofre do Obsidian num índice que o agente consulta antes de responder, e tudo roda na sua máquina.
Agentes de IA esquecem tudo entre uma sessão e outra, e as memórias que vêm prontas nas ferramentas guardam o que o agente decidiu anotar, num formato que você não lê nem edita. Enquanto isso, o que você sabe de verdade já está escrito no Obsidian.
O Cortex liga as duas pontas.
- Usa o cofre que você já tem. Notas, PDFs e
[[links]]viram um índice de busca. O cofre continua sendo a fonte da verdade: o índice se reconstrói a partir dele a qualquer momento. - Tudo fica na sua máquina. Vetores pelo Ollama local, banco em Docker, portas só em 127.0.0.1, explorer sem nada carregado de fora e números de documento pessoais mascarados nas respostas e no que os agentes gravam (CPF e CNS com dígito verificador válido). Serve para cofres que você nunca mandaria para a nuvem.
- Busca que se mede. Vetor, palavras e uma ficha por nota, fundidos por RRF e expandidos pelo grafo de links. Cada mudança na busca passa por perguntas de avaliação antes de entrar.
- O agente sabe quando não sabe. Se o cofre provavelmente não tem a resposta, a busca avisa, e o agente não inventa a partir de trechos soltos.
- Memória que o agente também escreve. Notas novas e decisões técnicas viram arquivos
.mdno seu cofre, com a versão anterior guardada. Um relatório de faxina aponta duplicatas, órfãs, links quebrados e perguntas sem resposta. - Um núcleo, várias áreas. Siglas, pastas, fatos fixos e perguntas de avaliação ficam num perfil por área. O código é o mesmo para trabalho, estudo ou vida pessoal.
- Busca híbrida com três vias fundidas por RRF: vetor dos trechos, busca textual em português (FTS) e uma ficha por
nota (título, pasta, seções e começo do texto). Depois expande pelos
[[wiki-links]]. - Aviso de baixa confiança quando o cofre provavelmente não tem a resposta, para o agente não inventar.
- Servidor MCP com 9 ferramentas:
brain_search,brain_recall,brain_graph,brain_list,brain_store,brain_record_decision,brain_historico,brain_feedbackebrain_consolidar. - Memória que se mantém:
brain_storeguarda a versão anterior da nota (.cortex_historico/),brain_feedbackregistra se a busca resolveu ebrain_consolidardevolve um relatório de faxina (só leitura). - API REST:
/health,/search,/recall/{titulo},/graph/traverse,/graph/pathe/consolidar. - Explorer web em
/explorer: busca com o diagnóstico de cada via e o grafo de links. - Indexação incremental: só o que mudou volta para o Ollama, e trecho com texto igual reaproveita o vetor.
Precisa de Docker com Compose v2.24 ou mais novo, uns 8 GB de RAM livres para o Docker e 6 GB de disco (o modelo de embeddings tem cerca de 2,5 GB). GPU NVIDIA é opcional.
git clone https://github.com/weesrnp/cortex.git
cd cortex
cp .env.example .env
docker compose up -d --buildNa primeira subida o Ollama baixa o qwen3-embedding:4b e o Cortex indexa o cofre de exemplo. Em conexão lenta o
download leva alguns minutos; acompanhe com docker compose logs -f brain-api. Depois:
- API: http://127.0.0.1:8900/health
- Busca: http://127.0.0.1:8900/search?q=prazo+de+resposta+do+suporte
- Explorer: http://127.0.0.1:8900/explorer
- MCP (HTTP streamable): http://127.0.0.1:8900/mcp
Com GPU NVIDIA (NVIDIA Container Toolkit; no Windows, Docker Desktop com WSL2):
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --buildO Cortex grava notas no cofre montado, então o container roda com o uid do dono da pasta. Se o seu usuário não for o
1000, ponha no .env o resultado de id -u e id -g em CORTEX_UID e CORTEX_GID. Com SELinux (Fedora, RHEL),
acrescente :z ao volume do cofre no docker-compose.yml.
No macOS isso é o recomendado, porque o Docker no Mac não usa a GPU. No .env:
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_PORT=11435 # a 11434 já é do seu OllamaNo Linux, o Ollama do host precisa escutar fora do 127.0.0.1 (OLLAMA_HOST=0.0.0.0). O serviço ollama do compose
continua subindo, mas fica sem modelo carregado.
Qualquer cliente MCP com transporte HTTP serve. A configuração costuma ser assim:
{
"mcpServers": {
"cortex": { "type": "http", "url": "http://127.0.0.1:8900/mcp" }
}
}Ao conectar, o agente recebe as instruções do perfil: como buscar, quais pastas existem e quais fatos não pode contradizer.
- No
.env, aponteVAULT_HOST_PATHpara a pasta do cofre (no Windows, use barras normais:C:/Users/voce/Notas). Use um cofre fora da pasta do repositório; o cofre de exemplo serve só para testar. - Copie
perfis/exemploparaperfis/minha_areae ponhaPERFIL=minha_areano.env. - Ajuste os arquivos do perfil (veja abaixo) e rode
docker compose up -d. - Escreva perguntas de avaliação e meça (seção "Medir antes de mudar").
O cofre é montado com escrita porque brain_store e brain_record_decision criam notas. Pastas e arquivos que começam
com ponto ficam fora do índice, como no Obsidian.
O código em brain-api/src é o mesmo para qualquer assunto. O que é da sua área fica em perfis/<nome>/, e todos os
arquivos são opcionais:
| Arquivo | Para quê |
|---|---|
perfil.yaml |
Nome do servidor MCP e título, descrição do cofre, domínio, exemplo de pergunta, regras extras, instrução do modelo de embedding, regex de metadados (viram etiquetas no explorer), pastas oficiais, limiares e chaves de vigência do frontmatter |
siglas.yaml |
Glossário. A pergunta ganha "(SIGLA: extenso)" no fim antes de virar vetor, nos dois sentidos |
instrucoes.md |
Texto que entra nas instruções do MCP: mapa das pastas, fatos fixos |
perguntas.yaml |
Perguntas de avaliação (principais, conferencia, pdfs) e perguntas que o cofre não responde (fora) |
Com PERFIL= vazio no .env, o Cortex roda neutro: sem siglas, sem metadados e com instruções genéricas. Um perfil
que não existe impede a subida, para você não rodar sem as regras da área sem perceber.
Uma instância por área funciona melhor que um índice com tudo misturado: notas de assuntos diferentes competem na
busca, e dados sensíveis de uma área não precisam dividir banco com as outras. Para isso, rode uma cópia por área com
COMPOSE_PROJECT_NAME, API_PORT e POSTGRES_PORT diferentes e um só Ollama para todas (OLLAMA_BASE_URL), ou
também OLLAMA_PORT diferente.
Mudança na busca só vale se medir melhor. Os scripts em brain-api/tests usam as perguntas do perfil:
docker compose exec brain-api python tests/avaliar_busca.py # hit@1, hit@5 e MRR por modo do FTS
docker compose exec brain-api python tests/avaliar_busca.py and --conjunto conferencia
docker compose exec brain-api python tests/calibrar_confianca.py # limiar de baixa confiança
docker compose exec brain-api python tests/diagnosticar_busca.py # por que a nota certa não apareceu
docker compose exec brain-api python tests/sortear_notas.py # notas sorteadas para escrever perguntas novas
docker compose exec brain-api python tests/avaliar_feedback.py # perguntas reais que os agentes marcaram como úteisEscreva as perguntas antes de mexer na busca e não ajuste as da conferencia depois de medir: elas servem para
conferir se um ganho nas principais não é ajuste às próprias perguntas. Num cofre real com cerca de 550 notas e
6.700 trechos, as 46 perguntas principais dão MRR 0,700, com a nota certa no top-5 em 38 delas.
docker compose exec brain-api python -m pytest tests -qOs testes de banco usam o Postgres do próprio Cortex, dentro de uma transação desfeita no fim, e não chamam o Ollama.
Fora do Docker, os testes de banco são pulados se DATABASE_URL não estiver definida. O CI do GitHub roda tudo contra
um Postgres com pgvector a cada push.
git pull
docker compose up -d --build
docker compose exec -T postgres psql -U cortex -d cortex -v ON_ERROR_STOP=1 < db/init.sqlO db/init.sql cria o schema na primeira subida e pode ser rodado de novo sem perder nada: é assim que uma versão nova
traz tabelas, colunas e funções. O CHANGELOG avisa quando isso é necessário. Usuário e banco
diferentes de cortex: troque no comando. No PowerShell, a última linha fica
Get-Content db/init.sql | docker compose exec -T postgres psql -U cortex -d cortex -v ON_ERROR_STOP=1.
cofre (.md e .pdf) ──► parser (trechos por cabeçalho, ficha da nota, metadados do perfil)
│
▼
Ollama (qwen3-embedding:4b, vetor de 1024 dimensões)
│
▼
PostgreSQL + pgvector: notas, trechos, links, fichas, log de buscas e feedback
│
▼
busca: vetor exato + FTS português + ficha ──► RRF ──► expansão por [[links]]
│
REST · MCP (/mcp) · explorer (/explorer)
- Os trechos seguem os cabeçalhos de nível 1 a 4 (
Nota > Seção), com o texto antes do primeiro cabeçalho num trecho próprio. Seção longa é cortada por parágrafo; nota sem cabeçalho vira um trecho só. - O vetor da pergunta é comparado com todos os trechos (busca exata). Até alguns milhares de trechos isso é rápido e não perde resultado como o índice HNSW, que é aproximado.
- PDFs entram como notas, com o número da página no trecho. Ficam de fora PDFs com mais de 300 páginas, os sem texto
(digitalizados), as cópias com texto igual (fica uma) e os de caminho que casa com
PDF_EXCLUDE_PATTERN. - Nota que não vale mais continua no cofre e no índice. Com
revogada_em: 2026-03-15(ou sósubstituida_por) no frontmatter, a busca multiplica o RRF dela porREVOGADA_PESO, e o MCP, a API e o explorer avisam que ela foi revogada e qual nota vale (substituida_por: "[[Nota nova]]"). Comvigente_desdeno futuro, o aviso diz desde quando ela vale, sem rebaixar. As chaves em inglês também valem (revoked_at,superseded_by,valid_from), e operfil.yamlpode trocar os nomes (vigencia:). A nota é lida de novo quando o arquivo muda, então basta editar o frontmatter. - As buscas pela API e pelo MCP vão para
search_log, com a pergunta já mascarada. É a base dobrain_consolidare doavaliar_feedback.py. O log e o feedback não vêm do cofre: apagar o volume do banco apaga os dois.
As variáveis ficam no .env (veja .env.example) e chegam ao Cortex pelo compose. Os padrões estão em
brain-api/src/config.py. As mais usadas:
| Variável | Padrão | O que faz |
|---|---|---|
VAULT_HOST_PATH |
./exemplo-vault |
Pasta do cofre no host |
PERFIL |
exemplo |
Pasta em perfis/; vazio = sem perfil |
CORTEX_UID / CORTEX_GID |
1000 |
Usuário do container (Linux: o dono do cofre) |
EMBEDDING_MODEL |
qwen3-embedding:4b |
Modelo do Ollama |
OLLAMA_BASE_URL |
serviço ollama do compose |
Ollama a usar |
API_PORT / POSTGRES_PORT / OLLAMA_PORT |
8900 / 5433 / 11434 | Portas no host, só em 127.0.0.1 |
INDEX_PDF |
true |
PDFs do cofre no índice |
PDF_EXCLUDE_PATTERN |
(?i)confidencial|sigiloso|nominal |
Regex do caminho de PDF que fica fora do índice |
EXPAND_SIGLAS |
true |
Expansão de siglas na pergunta |
LOW_CONFIDENCE_THRESHOLD / AUTO_LINK_THRESHOLD |
0,62 / 0,70 | Limiares (o perfil também define; a variável vence) |
REVOGADA_PESO |
0,5 | Multiplica o RRF de nota revogada (0 a 1); 1 só avisa, sem mudar a ordem |
FUSO |
vazio (UTC no Docker) | Fuso do "hoje" da vigência, ex.: America/Sao_Paulo |
A senha do Postgres vale quando o volume é criado. Para trocar depois, use ALTER USER no banco ou apague o volume.
Trocar de modelo de embedding não reindexa sozinho: apague o volume do banco (docker compose down -v, que também
apaga o log de buscas) e suba de novo, depois recalibre os limiares com calibrar_confianca.py. Outra dimensão de
vetor exige também mudar VECTOR(1024) no db/init.sql.
Para desenvolver, docker-compose.dev.yml monta o código e os testes por cima da imagem:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d.
O explorer usa htmx 2.0.11 (BSD Zero Clause) e vis-network 10.1.2 (Apache-2.0 ou MIT), servidos localmente. Algumas ideias de memória vieram de outros projetos abertos (Cognee, ai-memory, AgentMemoryRepo, Hindsight, OpenMemory), citados no código onde entram.
MIT. Veja LICENSE. Falhas de segurança: SECURITY.md.
