chatmem
Histórico de chat de LLM local servido via MCP. Postgres embutido + pgvector, memória entre ferramentas, tudo permanece na sua máquina.
Documentação
chatmem
Histórico de chat LLM local, servido via MCP.
chatmem é um utilitário de binário único que captura suas conversas com LLMs por meio de ferramentas do Model Context Protocol que qualquer cliente pode chamar (Claude Code, Cursor, aider, SDKs personalizados), armazena tudo em um banco de dados Postgres 18 embutido com pgvector na sua máquina e fornece contexto passado relevante de volta para qualquer LLM sob demanda.
Apenas telemetria anônima (ID de instalação, contadores agregados, relatórios de falha opcionais) sai da sua máquina — o conteúdo das mensagens nunca sai.
Sumário
- Status
- Início rápido
- Referência de ferramentas MCP
- Comandos
- Caminhos de dados e configuração
- Telemetria
- Arquitetura
- Estrutura do repositório
- Compilação a partir do código-fonte
- Testes
- Fluxo de trabalho de desenvolvimento
- Solução de problemas
- Desinstalação
- Roadmap
- Licença
Status
Pré-alfa (v0.0.1-dev). Plataformas totalmente funcionais:
| Plataforma | pgvector | Distribuição | Verificado |
|---|---|---|---|
darwin/arm64 | 0.8.5 | Homebrew tap | Uso real |
linux/amd64 | 0.8.3 | RPM (zypper/dnf) + DEB + tarball | Contêiner Debian 12 + contêiner openSUSE Leap 15.6 |
linux/arm64 | 0.8.3 | RPM (zypper/dnf) + DEB + tarball | Contêiner Debian 12 + contêiner openSUSE Leap 15.6 |
darwin/amd64 | — | — | Precisa de um pgvector.dylib para Intel-Mac |
windows/amd64 | — | — | Precisa de uma pgvector.dll + estratégia de serviço |
A diferença de versão entre macOS e Linux é intencional (Homebrew fornece 0.8.5, o apt oficial do Postgres fornece 0.8.3 para PG18). Ambos são compatíveis em nível de wire para o nosso uso — mesmos operadores, mesmo suporte a HNSW.
Funcionando hoje:
| Comando | Finalidade |
|---|---|
chatmem init | Provisiona o banco de dados local, aplica o schema e imprime a configuração do cliente MCP. |
chatmem mcp | Servidor MCP stdio autônomo (inicia e gerencia o Postgres embutido). |
chatmem daemon | Processo Postgres de longa duração. Normalmente não é invocado diretamente — o chatmem install envolve isso em um serviço launchd/systemd para que inicie no login e permaneça ativo. |
chatmem install / uninstall | Instala / remove o serviço em segundo plano no nível do usuário (launchd no macOS, systemd --user no Linux) que executa o chatmem daemon. Mantém o Postgres embutido ativo para que os clientes MCP tenham inicialização instantânea em vez do boot a frio de 6 a 8 segundos. |
chatmem start / stop / restart | Inicia / para / reinicia o serviço em segundo plano. |
chatmem status | Mostra se o serviço está instalado e se o PG está escutando. |
chatmem doctor | Imprime um relatório de autodiagnóstico — HOME, EUID, caminhos de dados/cache, disponibilidade de porta, estado da telemetria, alcance da ingestão, status do Notion. Execute isto primeiro se algo estiver estranho. |
chatmem telemetry {enable,disable,status,dump} | Gerencia telemetria anônima; respeita o CHATMEM_TELEMETRY=0. |
chatmem notion {connect,status,disconnect,list,resync,sample} | Gerencia a integração com o Notion para sintetizar automaticamente conversas em páginas de estudo/depuração. Consulte Síntese Notion abaixo. |
chatmem import | Carrega em massa uma transcrição de chat existente (JSONL ou array JSON) no chatmem. Ótimo para preencher conversas que aconteceram fora de um cliente conectado ao chatmem. Consulte Importar um chat existente abaixo. |
Início rápido
# --- macOS or Linuxbrew (Homebrew tap — ships as a cask) ---
brew tap sid077/chatmem
brew install --cask chatmem
# --- openSUSE / SUSE (zypper self-hosted repo) ---
sudo zypper ar https://sid077.github.io/chatmem/chatmem.repo
sudo zypper --gpg-auto-import-keys refresh
sudo zypper in chatmem
# --- Fedora / RHEL (dnf, same repo) ---
sudo dnf config-manager --add-repo https://sid077.github.io/chatmem/chatmem.repo
sudo dnf install chatmem
# --- Debian / Ubuntu (direct .deb download; APT repo TBD) ---
# curl -sSLo /tmp/chatmem.deb https://github.com/sid077/chatmem/releases/latest/download/chatmem_<ver>_<arch>.deb
# sudo apt install /tmp/chatmem.deb
# --- direct download (any Linux) ---
# curl -sSL https://github.com/sid077/chatmem/releases/latest/download/chatmem_Linux_x86_64.tar.gz \
# | tar -xz && sudo mv chatmem /usr/local/bin/
# 2) bootstrap the local database — prints the JSON snippet to paste into your MCP client
chatmem init
# 3) paste into ~/.claude/mcp.json (or ~/.cursor/mcp.json), restart the client
# 4) verify tools appear
# In Claude Code, ask: "list your MCP tools" — you should see
# record_message, search_history, get_conversation from the 'chatmem' server.
Claude Code (terminal claude, 2.1.x+):
claude mcp add --scope user chatmem /opt/homebrew/bin/chatmem mcp
claude mcp list # should show: chatmem ✓ Connected
Aplicativo Claude Desktop — mescle em ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"chatmem": {
"command": "/opt/homebrew/bin/chatmem",
"args": ["mcp"]
}
}
}
Saia e reabra o aplicativo completamente após editar.
Codex — mescle em ~/.codex/config.toml:
[mcp_servers.chatmem]
command = "/opt/homebrew/bin/chatmem"
args = ["mcp"]
startup_timeout_sec = 60
Cursor / Windsurf / outros clientes MCP — mesmo bloco JSON do Claude Desktop, mas no arquivo de configuração MCP deles. Consulte a documentação do cliente para o caminho.
Referência de ferramentas MCP
As três ferramentas são registradas por internal/mcp/server.go. Cada escrita transmite model/provider/client_id explicitamente — o daemon nunca os infere — para que vários clientes LLM gravando no mesmo banco de dados permaneçam claramente atribuídos.
record_message
Armazena uma única mensagem de chat. Abre uma nova conversa quando conversation_id está vazio (então model/provider/client_id são obrigatórios).
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
conversation_id | UUID string | se continuando uma conversa | Omita para abrir uma nova |
role | user | assistant | system | tool | sim | |
content | string | sim | Texto da mensagem |
model | string | ao abrir uma nova conversa | ex.: claude-opus-4-7 |
provider | string | ao abrir uma nova conversa | ex.: anthropic, openai |
client_id | string | ao abrir uma nova conversa | ex.: claude-code, cursor, aider |
token_count | int | não | Metadados opcionais |
Retorna:
{ "message_id": "<uuid>", "conversation_id": "<uuid>" }
search_history
Pesquisa no histórico de chat armazenado. O MVP usa classificação full-text do Postgres (to_tsvector + plainto_tsquery); a reclassificação semântica via embeddings ONNX é o próximo marco. Retorna um resultado por conversa (MMR-lite), condensado em token_budget.
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
query | string | sim | Consulta em texto livre |
top_k | int | não | Padrão 10, máximo 100 |
token_budget | int | não | Total de tokens de trecho (padrão 4000) |
model | string | não | Filtrar por ID do modelo |
client_id | string | não | Filtrar por ID do cliente |
since | RFC3339 string | não | Limite inferior em created_at |
until | RFC3339 string | não | Limite superior em created_at |
conversation_ids | array de UUIDs | não | Restringir a estas conversas |
Retorna tanto um bloco de texto renderizado (visível para qualquer cliente MCP) quanto um payload estruturado para uso programático:
Texto renderizado (Content):
2 hit(s) for "kafka retention"
── hit 1 ──
role: user
conversation: 8a2f…
message: def6…
created: 2026-07-20T10:15:32Z
score: 0.2341
snippet:
kafka retention is set at 7 days for the ingest topic…
Estruturado (StructuredContent):
{
"hits": [
{ "message_id": "<uuid>", "conversation_id": "<uuid>", "role": "user",
"snippet": "...", "score": 0.147, "created_at": "2026-07-20T10:15:32Z" }
]
}
(Antes da v0.0.2, Content era uma contagem de resultados simples — Windsurf/Cascade e qualquer cliente que ignore StructuredContent não mostraria texto de trecho.)
get_conversation
Busca uma conversa e suas mensagens, ordenadas por created_at ascendente. Paginação por cursor via after.
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
conversation_id | UUID string | sim | |
limit | int | não | Padrão 100, máximo 500 |
after | RFC3339 string | não | Retorna mensagens estritamente após este timestamp |
Retorna tanto um bloco de texto renderizado quanto um payload estruturado.
Texto renderizado (Content):
conversation 8a2f…
model: anthropic / claude-opus-4-7
client: claude-code
started: 2026-07-20T10:15:32Z
updated: 2026-07-20T10:20:15Z
messages: 3
── user @ 2026-07-20T10:15:32Z ──
hello from chatmem
── assistant @ 2026-07-20T10:15:35Z ──
hi back
Estruturado (StructuredContent):
{
"conversation": {
"id": "<uuid>", "client_id": "...", "model": "...", "provider": "...",
"title": null, "started_at": "...", "updated_at": "..."
},
"messages": [
{ "id": "<uuid>", "role": "user", "content": "...", "token_count": 0, "created_at": "..." }
],
"next_after": null
}
Síntese Notion (v0.3.0 — múltiplas passagens com garantia de cobertura)
Após cada conversa, o chatmem publica uma página Notion estruturada e organizada por conceitos — uma página em modo estudo para conversas de aprendizado, ou uma página em modo depuração para sessões de solução de problemas. As páginas são otimizadas para revisão: TL;DR no topo, diagramas Mermaid para qualquer coisa com estrutura ou fluxo, citações de volta às mensagens originais e transcrição completa recolhida no rodapé.
Garantias de qualidade v0.3.0: o LLM extrai fatos atômicos de cada mensagem primeiro e depois compõe o Resumo a partir desse inventário. O chatmem se recusa a gravar no Notion a menos que o Resumo cite ≥ 95% das mensagens que contêm fatos não triviais. Resultado: nenhuma mensagem é descartada silenciosamente, mesmo em uma sessão de 4 horas com 200 mensagens.
suggest_synthesize=true triggers the client-side LLM to run:
┌─ Phase 1: extract facts ──────────────────────────┐
│ get_extraction_prompt → chunk of unextracted │
│ record_facts → store, return remaining │
│ loop until extraction_complete=true │
└──────────────────────┬────────────────────────────┘
▼
┌─ Phase 2: compose Summary ────────────────────────┐
│ get_synthesis_prompt → transcript + facts + │
│ schema + quality rules │
└──────────────────────┬────────────────────────────┘
▼
┌─ Phase 3: publish (coverage-gated) ───────────────┐
│ synthesize_to_notion → validates + writes to │
│ Notion, OR returns list │
│ of missed msg uuids for │
│ the LLM to fix and retry │
└───────────────────────────────────────────────────┘
Configuração (uma vez)
- Crie uma integração interna do Notion em https://www.notion.so/my-integrations. Copie o token secreto (começa com
ntn_ousecret_). - Crie uma página do Notion que armazenará suas páginas do chatmem. Compartilhe-a com a integração (⋯ → Add connections → sua integração).
- Na sua máquina:
chatmem notion connect ntn_XXXXX --parent <page-url-or-id> chatmem notion status # verify token + parent
Reinicie seu cliente MCP (Windsurf/Cursor/Claude Code). A auto-síntese é acionada quando uma conversa ultrapassa 20 mensagens, ou a regra do lado do cliente é acionada em suggest_synthesize=true em respostas record_message.
Regra de prompt de sistema para seu cliente MCP
Cole isto nas suas regras do Windsurf / Cursor / Claude-Code para que o disparo automático funcione:
Sempre que uma resposta da ferramenta
record_messageincluirsuggest_synthesize: true, imediatamente (a) chameget_synthesis_promptpara oconversation_idreferenciado, (b) componha o objeto Summary de acordo com o schema nesse prompt, (c) chamesynthesize_to_notioncom{conversation_id, summary}. Faça isso uma vez por conversa por gatilho, antes de continuar o turno do usuário.
Tipos de página
- Modo estudo (conversas com muitos conceitos): TL;DR · Pré-requisitos · Conceitos principais (callout de definição + corpo + exemplo + por que é importante + citações) · Diagramas Mermaid · Principais Insights · Código · Estudo adicional · Referências · Transcrição completa.
- Modo depuração (consertando algo quebrado): TL;DR · Callout de status (resolvido / parcial / não resolvido) · Diagrama Mermaid de linha do tempo · O Que Eu Tentei (tentativas) · Causa Raiz · Resolução · Prevenção · Transcrição completa.
O tipo de sessão é classificado automaticamente por conversa. Diagramas são obrigatórios (validação aplicada) — Mermaid timeline para depuração, tipo de diagrama apropriado para estudo quando os conceitos justificarem um.
Visualizar os modelos sem tocar no Notion
chatmem notion sample --type=study # prints Summary JSON + rendered blocks JSON
chatmem notion sample --type=debug
Inspecionar a cobertura
chatmem notion coverage <conversation_id>
# → total messages, messages with facts, category breakdown,
# msgs without any fact yet
Confiabilidade
Falhas de gravação no Notion são persistidas em ~/.local/share/chatmem/notion-pending/ e repetidas automaticamente na próxima inicialização do chatmem mcp, ou manualmente com chatmem notion resync.
Desconectar
chatmem notion disconnect # removes notion.json; published pages are untouched
Importar um chat existente (v0.2.1)
Tem uma transcrição de um chat que aconteceu fora do chatmem — exportação web do ChatGPT, uma conversa do Claude.ai que você salvou, um log do aider, etc.? Carregue-a no chatmem com chatmem import. Depois, ou o LLM a sintetiza automaticamente para o Notion (se ultrapassar o limite) ou você a aciona manualmente.
Formatos de entrada
Dois formatos aceitos; detectados automaticamente pelo primeiro caractere não espaço em branco:
JSONL (um objeto JSON por linha):
{"role":"user","content":"what is hnsw"}
{"role":"assistant","content":"a graph-based ANN index"}
{"role":"user","content":"pgvector defaults?"}
Array JSON:
[
{"role":"user","content":"what is hnsw"},
{"role":"assistant","content":"a graph-based ANN index"}
]
Campos extras em cada mensagem (timestamps, IDs de origem, tool_calls) são silenciosamente ignorados — o chatmem gera seus próprios IDs e timestamps.
Comandos
# From a file, opening a new conversation:
chatmem import -f ./chatgpt-export.jsonl \
--model gpt-5 --provider openai --client-id chatgpt-web
# Piping from stdin:
cat ./transcript.jsonl | chatmem import --stdin \
--model claude-opus-4-7 --provider anthropic --client-id claude-web
# Appending to an existing chatmem conversation (e.g. resume a partial capture):
chatmem import -f ./followup.jsonl \
--conversation-id 8a2f... \
--model claude-opus-4-7 --provider anthropic --client-id claude-code
chatmem import conecta-se ao Postgres de uma instância do chatmem em execução se houver uma ativa (sessão do Claude Code / Windsurf / etc. ao vivo). Caso contrário, inicia seu próprio PG embutido brevemente. De qualquer forma, é seguro executar.
Em caso de sucesso, imprime o novo UUID da conversa + uma dica para sintetizá-la no seu cliente LLM:
imported 47 messages into conversation 8a2f...
Next steps:
chatmem notion status
# From an LLM session with chatmem:
# "call get_synthesis_prompt for conversation 8a2f..., then synthesize_to_notion"
Convertendo de exportações reais de chat
- Exportação ChatGPT
.zip→ JSONL: receitajq(ajuste para o seu formato de exportação):jq -c '.[0].mapping | to_entries | map(select(.value.message != null)) | sort_by(.value.message.create_time) | .[] | {role: .value.message.author.role, content: (.value.message.content.parts | join("\n"))}' \ < conversations.json > out.jsonl - Exportação Claude.ai → JSONL:
jq '.[] | {role: .sender, content: .text}' < conversation.json - Qualquer coisa baseada em texto: transforme cada turno em
{"role":"...","content":"..."}e pronto.
Comandos
Cada subcomando tem --help. Execute chatmem sem argumentos para ver a lista de nível superior.
chatmem init
Provisiona o diretório de dados persistente, extrai o Postgres embutido, instala o pgvector no runtime e aplica o schema. Ao concluir, imprime um trecho JSON MCP pronto para colar com o caminho real do binário (os.Executable()).
Seguro re-executar — todas as etapas de provisionamento são idempotentes.
chatmem mcp [--port <n>]
Executa um servidor MCP stdio para um único cliente. Inicia o Postgres embutido durante o ciclo de vida do processo e o encerra corretamente no fechamento do stdin ou no SIGTERM.
Clientes MCP simultâneos não podem compartilhar um Postgres embutido no MVP — cada processo chatmem mcp abre seu próprio PG na porta fornecida. Veja Roteiro para a arquitetura daemon+shim.
chatmem daemon [--port <n>]
Executa a base Postgres de longa duração para a futura arquitetura daemon+shim. Não é necessário para o uso do MVP. Encerra de forma limpa em SIGINT/SIGTERM.
chatmem telemetry {enable|disable|status}
Gerencie a configuração de telemetria anônima. status imprime o estado efetivo, a fonte de precedência (env | config | default) e o ID de instalação.
Caminhos de dados e configuração
| Tipo | macOS | Linux | Substituição por env |
|---|---|---|---|
| Dados | ~/.local/share/chatmem/ | ~/.local/share/chatmem/ | CHATMEM_HOME |
| Cache | ~/Library/Caches/chatmem/ | ~/.cache/chatmem/ | CHATMEM_CACHE |
| ID de instalação | <data>/install_id | <data>/install_id | — |
| Config. de telemetria | <data>/telemetry.json | <data>/telemetry.json | — |
| Dados do Postgres | <data>/pgdata/ | <data>/pgdata/ | — |
| Runtime do Postgres | <cache>/pg-runtime/ | <cache>/pg-runtime/ | — |
Telemetria
chatmem pode enviar pings de uso anônimos para ajudar a acompanhar a adoção. O conteúdo das mensagens, strings de consulta e nomes de arquivos nunca são enviados, jamais. O que é enviado quando habilitado:
- UUID de instalação (gerado localmente na primeira execução, em
<data>/install_id) - Versão do
chatmem - Contadores para a janela de descarga (padrão: 5 minutos): capturas, buscas, gets, erros
- Distribuições de modelo e cliente — ex.:
{"claude-opus-4-7": 12, "gpt-5": 4},{"windsurf": 8, "cursor": 8} - Percentis de latência por operação — p50/p95/p99 em ms
Modos
O cliente opera em três modos dependendo da configuração:
| Modo | Comportamento |
|---|---|
| Desabilitado (env ou config) | Nada é acumulado. Aggregator.Record* ainda é executado, mas Flush é um no-op. |
| Habilitado, sem URL de ingestão | Acumula + descarga periódica → slog.Info("telemetry flush (local-only, no ingest URL set)", ...). Apenas observabilidade local. |
| Habilitado, com URL de ingestão definida | Acumula + descarga → POST para <URL>/v1/ping com 3 tentativas de recuo exponencial. Envios com falha persistem em <data>/pending/*.json e são drenados na próxima descarga bem-sucedida (TTL de 24h). Binários de lançamento têm essa URL embutida (aponta para o Worker do mantenedor); CHATMEM_TELEMETRY_URL a sobrepõe, e chatmem telemetry status imprime o valor efetivo. |
Precedência (maior vence)
CHATMEM_TELEMETRY=0(tambémfalse,off) — desligamento forçado<data>/telemetry.json({"enabled": true|false}) — escolha persistentechatmem telemetry {enable|disable}— grava o acima- Padrão: habilitado
chatmem init pergunta na primeira execução (somente TTY interativo) e grava a configuração. Inicializações não-TTY imprimem um aviso e mantêm o padrão; opte por desativar de forma não interativa com chatmem telemetry disable ou a variável de ambiente.
Comandos
chatmem telemetry status # current state + source + ingest URL
chatmem telemetry enable # persist enabled = true
chatmem telemetry disable # persist enabled = false
chatmem telemetry dump # list <data>/pending/*.json (unshipped pings)
Configurando sua própria ingestão
O cliente envia para qualquer endpoint que fale POST /v1/ping com o payload documentado em internal/telemetry/client.go:Payload. Um Cloudflare Worker + D1 pronto para implantar vive em server/telemetry-worker/ — um wrangler deploy e você tem um endpoint. Veja esse README para a configuração de 5 comandos.
Arquitetura
LLM client (Claude Code, Cursor, aider, custom)
│
▼ MCP over stdio
chatmem mcp
│
▼
embedded Postgres 18 + pgvector 0.8.5
│
▼
chunks (with vector(384) column, HNSW cosine index)
messages (btree on conv_id + created_at)
conversations (append-only event log ready for future sync)
- Linguagem: Go 1.26, binário estático único (CGO desativado).
- Armazenamento:
fergusstrange/embedded-postgresv1.34 dirigindo Postgres 18.3 em um diretório de dados por usuário. Início a frio ~600 ms em M-series (~7 s na primeira execução devido ainitdb). - Coluna vetorial:
vector(384)na tabelachunkscom um índice HNSW cosseno (m=16, ef_construction=64), preparado para busca semântica. Valores são vetores zero até o embedder ONNX chegar. - Busca (MVP): texto completo do Postgres —
to_tsvector('english', content)+plainto_tsquery, índice GIN (chunks_tsv_idx). Classificado comts_rank_cd. - MCP: oficial
modelcontextprotocol/go-sdkv1.6.1. Transporte Stdio. - CLI:
spf13/cobrav1.10.
O .dylib do pgvector para darwin_arm64 está commitado em internal/pg/assets/darwin_arm64/ e enviado dentro do binário Go via //go:embed. Na primeira Start(), internal/pg copia o dylib para <runtimeDir>/lib/postgresql/ e os arquivos de controle/SQL para <runtimeDir>/share/postgresql/extension/ — então CREATE EXTENSION vector funciona imediatamente.
Layout do repositório
chatmem/
├── cmd/chatmem/ # cobra CLI entrypoint + subcommands
│ ├── main.go
│ ├── init.go # chatmem init
│ ├── daemon.go # chatmem daemon (+ dataHome / cacheHome helpers)
│ ├── mcp.go # chatmem mcp (stdio MCP server)
│ ├── telemetry.go # chatmem telemetry {enable,disable,status}
│ └── mcp_e2e_test.go # spawns built binary, drives stdio MCP as a real client
├── internal/
│ ├── pg/ # embedded-postgres wrapper + pgvector install
│ │ ├── embedded.go
│ │ └── assets/
│ │ ├── darwin_arm64/vector.dylib + extension/{vector.control,vector--0.8.5.sql}
│ │ ├── linux_amd64/vector.so + extension/{vector.control,vector--0.8.3.sql}
│ │ └── linux_arm64/vector.so + extension/{vector.control,vector--0.8.3.sql}
│ ├── telemetry/
│ │ ├── telemetry.go # State/Config, install_id, opt-out precedence
│ │ ├── aggregator.go # Thread-safe counters + latency reservoir + percentiles
│ │ └── client.go # Flush loop, HTTP POST with retry, local pending dir
│ ├── store/ # schema + pgx-backed data access
│ │ ├── schema.sql
│ │ ├── store.go # EnsureSchema, RecordMessage, SearchHistory, GetConversation
│ │ └── store_test.go
│ ├── mcp/ # MCP tool registration
│ │ ├── server.go # NewServer, register{RecordMessage,SearchHistory,GetConversation}
│ │ └── server_test.go # in-process MCP round-trip
│ └── telemetry/ # install_id + opt-out gate
│ └── telemetry.go
├── server/telemetry-worker/ # Cloudflare Worker + D1 for the telemetry ingest
├── docs/
│ └── marketplace-submissions.md # Playbook: awesome-mcp / Smithery / PulseMCP / Glama
├── smithery.yaml # Smithery registry config (stdio start command)
├── .goreleaser.yaml # cross-platform build + Homebrew tap + rpm/deb via nfpm
├── .github/workflows/release.yml # tag push → goreleaser + gh-pages RPM repo publish
├── scripts/build-rpm-repo.sh # assemble zypper/dnf repo tree locally (uses createrepo_c via docker)
├── README.md
├── CLAUDE.md # in-repo dev docs, auto-loaded by Claude Code
└── LICENSE # Apache-2.0
Compilando a partir do código-fonte
Requer Go 1.26+ e, por enquanto, o pgvector 0.8.5 do Homebrew (somente se você quiser atualizar o dylib vendido — a cópia commitada é suficiente para compilar).
git clone https://github.com/sid077/chatmem
cd chatmem
go build ./...
Para atualizar os artefatos do pgvector vendido:
# darwin (from Homebrew) — refreshes internal/pg/assets/darwin_arm64/
brew install pgvector
cp /opt/homebrew/Cellar/pgvector/0.8.5/lib/postgresql@18/vector.dylib \
internal/pg/assets/darwin_arm64/vector.dylib
cp /opt/homebrew/Cellar/pgvector/0.8.5/share/postgresql@18/extension/{vector.control,vector--0.8.5.sql} \
internal/pg/assets/darwin_arm64/extension/
# linux amd64/arm64 (from official PostgreSQL apt, pgdg11+1 for glibc 2.31 baseline)
for arch in amd64 arm64; do
curl -sSLo /tmp/pgv-$arch.deb \
"https://apt.postgresql.org/pub/repos/apt/pool/main/p/pgvector/postgresql-18-pgvector_0.8.3-1.pgdg11+1_${arch}.deb"
tmp=$(mktemp -d); cd "$tmp"; ar x /tmp/pgv-$arch.deb; tar -xf data.tar.xz
cp "$tmp/usr/lib/postgresql/18/lib/vector.so" internal/pg/assets/linux_${arch}/vector.so
cp "$tmp/usr/share/postgresql/18/extension/"{vector.control,vector--0.8.3.sql} \
internal/pg/assets/linux_${arch}/extension/
done
O .so Linux é compilado contra o glibc 2.31 do Debian 11 para máxima compatibilidade de runtime — qualquer coisa com glibc ≥ 2.31 funciona (Debian 11+, Ubuntu 22.04+, RHEL 9+, Alpine com libc6-compat, etc.).
Testes
Todo teste que toca armazenamento inicia um Postgres embutido real — espere ~7–10 s por pacote de teste em cache frio.
# unit + integration
go test ./... -count=1 -timeout=240s
# just the storage layer round-trip
go test ./internal/store -count=1 -v
# just the in-process MCP round-trip
go test ./internal/mcp -count=1 -v
# end-to-end: spawn the built binary as a subprocess, drive stdio MCP
go test ./cmd/chatmem -run TestBinaryStdioMCP -count=1 -v
Os testes usam portas fixas distintas (54334, 54335, 54336) — execute um pacote de teste por vez se você tiver um daemon chatmem real rodando em 54329.
Fluxo de desenvolvimento
- Edite — o código vive em
cmd/chatmemeinternal/. - Teste —
go test ./...após cada mudança; adicione um teste junto a qualquer novo comportamento de store/MCP. - Atualize a documentação a cada mudança funcional:
README.mdpara mudanças voltadas ao usuário (nova ferramenta, novo comando, padrões alterados).CLAUDE.mdpara mudanças voltadas ao desenvolvedor (novo pacote, novo invariante, nova pegadinha).~/.claude/skills/chatmem/SKILL.mdpara contexto entre sessões (mantido em sincronia automaticamente).
- Commit — uma mudança focada por commit, linha de assunto imperativa.
go mod tidyse dependências mudaram.
Solução de problemas
No macOS: diálogo "A Apple não pôde verificar … pode ser malware" — o binário de lançamento ainda não está assinado com Apple Developer ID nem notarizado. Solução alternativa em duas etapas por enquanto:
# 1. Strip the "downloaded from internet" flag (needed once per install):
xattr -d com.apple.quarantine "$(brew --prefix)/bin/chatmem"
# 2. Ad-hoc-sign the binary so launchd doesn't re-prompt on every daemon start:
codesign --force --sign - "$(brew --prefix)/bin/chatmem"
chatmem install executa a etapa 2 automaticamente a partir da v0.3.2. A correção real (Developer ID + notarização) está conectada ao fluxo de lançamento; ativa para lançamentos futuros quando os segredos MACOS_CERT_P12 + APP_STORE_CONNECT_KEY forem adicionados. Veja docs/release-signing-setup.md.
No Linux: aviso "Package chatmem is not signed! Continue anyway?" do zypper/dnf — os RPMs de lançamento ainda não são assinados com GPG. Mesma história que no macOS — o encanamento chega na v0.3.2, ativa quando o segredo GPG_PRIVATE_KEY for adicionado. Até lá, é seguro aceitar o aviso (--allow-unsigned-rpm).
Algo estranho? Execute chatmem doctor primeiro — imprime HOME, EUID, caminhos efetivos de dados/cache, disponibilidade de porta, estado de telemetria e alcance de ingestão, com um check verde/vermelho para cada. A maioria dos problemas de instalação aparece aqui em uma tela.
$HOME (…) is owned by uid X but you are uid Y — looks like sudo -E preserved a different HOME — você executou sudo -E chatmem …, que manteve HOME=/root, mas caiu para um uid não-root. Faça um destes:
sudo -H -u <user> chatmem init # -H rewrites HOME
su - <user> -c "chatmem init" # login shell resets HOME
chatmem init # or just don't sudo — chatmem must run as your normal user
$HOME is not set — você está em um ambiente mínimo (unidade systemd sem Environment=HOME=…, env -i, etc.). Defina HOME explicitamente para o diretório home do seu usuário de login.
chatmem cannot run as root — no Linux, o Postgres se recusa a executar sob uid 0, e o chatmem agora também recusa, de antemão. Reexecute como um usuário sem privilégios: su - <username> -c 'chatmem init' (ou sudo -u <username> chatmem init).
chatmem mcp cliente vê "invalid character 'T' looking for beginning of value" — o protocolo MCP roda sobre stdout; algo está escrevendo não-JSON ali. Mais provável que embedded-postgres foi configurado com logger: os.Stdout em vez de os.Stderr (o padrão aqui é os.Stderr; verifique internal/pg/embedded.go se você personalizou).
no embedded pgvector assets for <goos>/<goarch> — você está em uma plataforma não suportada. Copie um pgvector pré-compilado correspondente para internal/pg/assets/<goos>_<goarch>/ (veja Compilando a partir do código-fonte para o layout de arquivos) e recompile.
Primeira chatmem init leva ~15–20 s — isso é initdb para um diretório de dados novo, além da extração do binário do Postgres. Execuções subsequentes (cache quente) são ~1 s.
Porta 54329 já em uso — ou outro processo chatmem daemon/mcp está rodando (mate-o) ou outra coisa pegou a porta. Use --port para sobrepor.
CREATE EXTENSION vector falha com could not access file "$libdir/vector" — o .dylib do pgvector não foi copiado para <runtimeDir>/lib/postgresql/. Verifique a saída de internal/pg/embedded.go de installPgvector; geralmente é um diretório de runtime apagado no meio da execução.
Desinstalação
brew uninstall chatmem # or delete the binary you built
rm -rf ~/.local/share/chatmem # data (delete only if you're sure)
rm -rf ~/Library/Caches/chatmem # cache (macOS) — safe to delete anytime
rm -rf ~/.cache/chatmem # cache (linux) — safe to delete anytime
rm -f ~/.claude/mcp.json # or hand-remove the chatmem entry
Roteiro
Próximos (ainda no escopo do MVP):
- Embedder ONNX MiniLM int8 → upgrade do
search_historyde palavra-chave para semântico. - Plataformas restantes:
darwin/amd64(Intel Mac) ewindows/amd64. - Endpoint de ingestão Cloudflare Worker para pings de telemetria reais.
chatmem daemonHTTP MCP +chatmem mcpshim stdio-to-HTTP para que múltiplos clientes MCP possam compartilhar um Postgres.
Pós-MVP (v1.0):
- Sincronização criptografada E2E opcional (hospedada, open-core).
- Notarização macOS +
.pkgassinado. - Windows Service, autostart launchd/systemd.
- Wrappers SDK Python e TypeScript em torno das ferramentas MCP.
- Distribuição para apt, dnf/COPR, zypper/OBS, AUR, winget, scoop.
- Site de documentação.
Publicando um lançamento
Acionado por tag — o workflow release do GitHub Actions faz tudo.
git tag -a v0.0.1 -m "v0.0.1"
git push origin v0.0.1
No push de tag, o workflow:
- Executa
goreleaser release --clean— cross-compila darwin/arm64 + linux/amd64 + linux/arm64, empacota.tar.gz+.rpm+.deb, envia os arquivos para o GitHub Release e envia a fórmula Homebrew atualizada parasid077/homebrew-chatmem. - Executa
createrepo_cnos.rpmpara construir uma árvore de repositório compatível com zypper/dnf. - Envia a árvore do repositório para o branch
gh-pagesdeste repositório.
Os usuários então recebem atualizações via zypper refresh / brew upgrade / dnf update sem nenhuma ação adicional sua.
Para um teste seco local antes de criar a tag:
goreleaser release --snapshot --clean --skip=publish # produces dist/*
scripts/build-rpm-repo.sh # produces dist/rpm-repo/ (needs Docker for createrepo_c)
Verifique com um contêiner openSUSE:
cd dist/rpm-repo && python3 -m http.server 8765 &
docker run --rm --platform linux/arm64 --add-host=host.docker.internal:host-gateway \
opensuse/leap:15.6 bash -c '
echo -e "[chatmem]\nbaseurl=http://host.docker.internal:8765/\$basearch/\nenabled=1\ngpgcheck=0" > /etc/zypp/repos.d/chatmem.repo
zypper --non-interactive refresh chatmem
zypper --non-interactive install chatmem
chatmem --version'
Assinatura GPG (recomendado para produção)
O MVP é lançado sem assinatura (gpgcheck=0 no arquivo .repo). Para assinar:
- Gere uma chave GPG:
gpg --gen-key. - Exporte a chave pública:
gpg --armor --export you@example.com > chatmem.gpg. - Em
.goreleaser.yaml, adicionesigns:para os rpms com o seu ID de chave. - Copie
chatmem.gpgjunto ao arquivo.repoemdist/rpm-repo/e alteregpgcheck=1+gpgkey=<URL>/chatmem.gpg.
Licença
Apache-2.0. Veja Licença.