chatmem
Historial de chat de LLM local servido a través de MCP. Postgres embebido + pgvector, memoria entre herramientas, todo permanece en tu máquina.
Documentación
chatmem
Historial de chat de LLM local, servido a través de MCP.
chatmem es una utilidad de un solo binario que captura tus conversaciones de LLM a través de herramientas de Model Context Protocol que cualquier cliente puede llamar (Claude Code, Cursor, aider, SDKs personalizados), almacena todo en una base de datos Postgres 18 integrada con pgvector en tu máquina, y sirve contexto relevante del pasado a cualquier LLM bajo demanda.
Solo la telemetría anónima (ID de instalación, contadores agregados, informes de fallos opcionales) sale de tu máquina — el contenido de los mensajes nunca lo hace.
Tabla de contenidos
- Estado
- Inicio rápido
- Referencia de herramientas MCP
- Comandos
- Rutas de datos y configuración
- Telemetría
- Arquitectura
- Estructura del repositorio
- Compilación desde el código fuente
- Pruebas
- Flujo de trabajo de desarrollo
- Solución de problemas
- Desinstalación
- Hoja de ruta
- Licencia
Estado
Pre-alfa (v0.0.1-dev). Plataformas totalmente funcionales:
| Plataforma | pgvector | Distribución | Verificado |
|---|---|---|---|
darwin/arm64 | 0.8.5 | Homebrew tap | Uso en vivo |
linux/amd64 | 0.8.3 | RPM (zypper/dnf) + DEB + tarball | Contenedor Debian 12 + contenedor openSUSE Leap 15.6 |
linux/arm64 | 0.8.3 | RPM (zypper/dnf) + DEB + tarball | Contenedor Debian 12 + contenedor openSUSE Leap 15.6 |
darwin/amd64 | — | — | Necesita un pgvector.dylib para Intel-Mac |
windows/amd64 | — | — | Necesita un pgvector.dll + historia de servicio |
La diferencia de versiones entre macOS y Linux es intencional (Homebrew incluye 0.8.5, el apt oficial de Postgres incluye 0.8.3 para PG18). Ambos son compatibles a nivel de protocolo para nuestro uso: mismos operadores, mismo soporte HNSW.
Funcionando hoy:
| Comando | Propósito |
|---|---|
chatmem init | Aprovisiona la base de datos local, aplica el esquema, imprime la configuración del cliente MCP. |
chatmem mcp | Servidor MCP stdio autocontenido (inicia y gestiona Postgres integrado). |
chatmem daemon | Proceso Postgres de larga duración. Normalmente no se invoca directamente: chatmem install lo envuelve en un servicio launchd/systemd para que se inicie al iniciar sesión y se mantenga activo. |
chatmem install / uninstall | Instala / elimina el servicio en segundo plano a nivel de usuario (launchd en macOS, systemd --user en Linux) que ejecuta chatmem daemon. Mantiene Postgres integrado activo para que los clientes MCP obtengan un inicio instantáneo en lugar del arranque en frío de 6-8 s. |
chatmem start / stop / restart | Inicia / detiene / reinicia el servicio en segundo plano. |
chatmem status | Muestra si el servicio está instalado y si PG está escuchando. |
chatmem doctor | Imprime un informe de autodiagnóstico: HOME, EUID, rutas de datos/caché, disponibilidad de puertos, estado de telemetría, alcance de ingesta, estado de Notion. Ejecuta esto primero si algo es extraño. |
chatmem telemetry {enable,disable,status,dump} | Gestiona la telemetría anónima; respeta CHATMEM_TELEMETRY=0. |
chatmem notion {connect,status,disconnect,list,resync,sample} | Gestiona la integración con Notion para auto-sintetizar conversaciones en páginas de estudio/depuración. Ver Síntesis de Notion a continuación. |
chatmem import | Carga en bloque una transcripción de chat existente (JSONL o matriz JSON) en chatmem. Ideal para rellenar conversaciones que ocurrieron fuera de un cliente conectado a chatmem. Ver Importar un chat existente a continuación. |
Inicio 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
Aplicación de Claude Desktop — fusiona en ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"chatmem": {
"command": "/opt/homebrew/bin/chatmem",
"args": ["mcp"]
}
}
}
Sal por completo y relanza la aplicación después de editar.
Codex — fusiona en ~/.codex/config.toml:
[mcp_servers.chatmem]
command = "/opt/homebrew/bin/chatmem"
args = ["mcp"]
startup_timeout_sec = 60
Cursor / Windsurf / otros clientes MCP — el mismo bloque JSON que Claude Desktop pero en su archivo de configuración MCP. Consulta la documentación del cliente para la ruta.
Referencia de herramientas MCP
Las tres herramientas están registradas por internal/mcp/server.go. Cada escritura transmite model/provider/client_id explícitamente — el daemon nunca las infiere — para que múltiples clientes LLM que escriben en la misma base de datos se atribuyan limpiamente.
record_message
Almacena un único mensaje de chat. Abre una nueva conversación cuando conversation_id está vacío (entonces model/provider/client_id son obligatorios).
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
conversation_id | UUID string | si continúa una conversación | Omitir para abrir una nueva |
role | user | assistant | system | tool | sí | |
content | string | sí | Texto del mensaje |
model | string | al abrir una nueva conversación | p. ej. claude-opus-4-7 |
provider | string | al abrir una nueva conversación | p. ej. anthropic, openai |
client_id | string | al abrir una nueva conversación | p. ej. claude-code, cursor, aider |
token_count | int | no | Metadatos opcionales |
Devuelve:
{ "message_id": "<uuid>", "conversation_id": "<uuid>" }
search_history
Busca en el historial de chat almacenado. El MVP usa clasificación de texto completo de Postgres (to_tsvector + plainto_tsquery); el reordenamiento semántico mediante embeddings ONNX es el próximo hito. Devuelve un resultado por conversación (MMR-lite), empaquetado a token_budget.
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
query | string | sí | Consulta de texto libre |
top_k | int | no | Predeterminado 10, máximo 100 |
token_budget | int | no | Total de tokens de fragmento (predeterminado 4000) |
model | string | no | Filtrar por ID de modelo |
client_id | string | no | Filtrar por ID de cliente |
since | RFC3339 string | no | Límite inferior en created_at |
until | RFC3339 string | no | Límite superior en created_at |
conversation_ids | array of UUIDs | no | Restringir a estas conversaciones |
Devuelve tanto un bloque de texto renderizado (visible para cualquier cliente MCP) como una carga útil estructurada 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…
Estructurado (StructuredContent):
{
"hits": [
{ "message_id": "<uuid>", "conversation_id": "<uuid>", "role": "user",
"snippet": "...", "score": 0.147, "created_at": "2026-07-20T10:15:32Z" }
]
}
(Antes de v0.0.2 Content era un recuento de coincidencias simple — Windsurf/Cascade y cualquier cliente que ignore StructuredContent no mostraría texto de fragmento.)
get_conversation
Obtiene una conversación y sus mensajes, ordenados por created_at ascendente. Paginado por cursor mediante after.
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
conversation_id | UUID string | sí | |
limit | int | no | Predeterminado 100, máximo 500 |
after | RFC3339 string | no | Devuelve mensajes estrictamente después de esta marca de tiempo |
Devuelve tanto un bloque de texto renderizado como una carga útil estructurada.
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
Estructurado (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íntesis de Notion (v0.3.0 — multipaso con garantía de cobertura)
Después de cada conversación, chatmem publica una página de Notion estructurada y organizada por conceptos — una página en modo estudio para conversaciones de aprendizaje, o una página en modo depuración para sesiones de resolución de problemas. Las páginas están optimizadas para la revisión: TL;DR en la parte superior, diagramas Mermaid para cualquier cosa con estructura o flujo, citas a los mensajes originales, transcripción completa plegada al final.
Garantías de calidad v0.3.0: el LLM extrae primero hechos atómicos de cada mensaje, luego compone el Resumen a partir de ese inventario. Chatmem se niega a escribir en Notion a menos que el Resumen cite ≥ 95% de los mensajes que contienen hechos no triviales. Resultado: ningún mensaje se descarta silenciosamente, incluso en una sesión de 4 horas y 200 mensajes.
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 │
└───────────────────────────────────────────────────┘
Configuración (una vez)
- Crea una integración interna de Notion en https://www.notion.so/my-integrations. Copia el token secreto (comienza con
ntn_osecret_). - Crea una página de Notion que contendrá tus páginas de chatmem. Compártela con la integración (⋯ → Agregar conexiones → tu integración).
- En tu máquina:
chatmem notion connect ntn_XXXXX --parent <page-url-or-id> chatmem notion status # verify token + parent
Reinicia tu cliente MCP (Windsurf/Cursor/Claude Code). La auto-síntesis se activa cuando una conversación supera los 20 mensajes, o la regla del lado del cliente se dispara en suggest_synthesize=true en respuestas record_message.
Regla de prompt del sistema para tu cliente MCP
Pega esto en tus reglas de Windsurf / Cursor / Claude-Code para que el disparo automático funcione:
Siempre que una respuesta de herramienta
record_messageincluyasuggest_synthesize: true, inmediatamente (a) llama aget_synthesis_promptpara elconversation_idreferenciado, (b) compone el objeto Resumen según el esquema en ese prompt, (c) llama asynthesize_to_notioncon{conversation_id, summary}. Haz esto una vez por conversación por disparador, antes de continuar con el turno del usuario.
Tipos de página
- Modo estudio (conversaciones con muchos conceptos): TL;DR · Prerrequisitos · Conceptos principales (llamada de definición + cuerpo + ejemplo + por qué importa + citas) · Diagramas Mermaid · Ideas clave · Código · Estudio adicional · Referencias · Transcripción completa.
- Modo depuración (arreglar algo roto): TL;DR · Llamada de estado (resuelto / parcial / no resuelto) · Diagrama Mermaid de línea de tiempo · Lo que intenté (intentos) · Causa raíz · Resolución · Prevención · Transcripción completa.
El tipo de sesión se clasifica automáticamente por conversación. Los diagramas son obligatorios (validación aplicada) — Mermaid timeline para depuración, tipo de diagrama apropiado para estudio cuando los conceptos lo justifiquen.
Previsualiza las plantillas sin tocar Notion
chatmem notion sample --type=study # prints Summary JSON + rendered blocks JSON
chatmem notion sample --type=debug
Inspecciona la cobertura
chatmem notion coverage <conversation_id>
# → total messages, messages with facts, category breakdown,
# msgs without any fact yet
Fiabilidad
Las escrituras fallidas de Notion se persisten en ~/.local/share/chatmem/notion-pending/ y se reintentan automáticamente en el próximo inicio de chatmem mcp, o manualmente con chatmem notion resync.
Desconectar
chatmem notion disconnect # removes notion.json; published pages are untouched
Importar un chat existente (v0.2.1)
¿Tienes una transcripción de un chat que ocurrió fuera de chatmem — exportación web de ChatGPT, una conversación de Claude.ai que guardaste, un registro de aider, etc.? Cárgala en chatmem con chatmem import. Luego, o el LLM la auto-sintetiza a Notion (si supera el umbral) o la impulsas manualmente.
Formatos de entrada
Se aceptan dos formatos; se detectan automáticamente por el primer carácter que no sea espacio en blanco:
JSONL (un objeto JSON por línea):
{"role":"user","content":"what is hnsw"}
{"role":"assistant","content":"a graph-based ANN index"}
{"role":"user","content":"pgvector defaults?"}
Matriz JSON:
[
{"role":"user","content":"what is hnsw"},
{"role":"assistant","content":"a graph-based ANN index"}
]
Los campos adicionales en cada mensaje (marcas de tiempo, IDs de origen, tool_calls) se ignoran silenciosamente — chatmem genera sus propios IDs y marcas de tiempo.
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 se conecta al Postgres de una instancia de chatmem en ejecución si hay una activa (sesión de Claude Code / Windsurf / etc.). De lo contrario, inicia su propio PG integrado brevemente. De cualquier manera, es seguro ejecutarlo.
En caso de éxito, imprime el UUID de la nueva conversación + una sugerencia para sintetizarla en tu 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"
Conversión desde exportaciones de chat reales
- Exportación de ChatGPT
.zip→ JSONL: recetajq(ajusta según tu formato de exportación):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 - Exportación de Claude.ai → JSONL:
jq '.[] | {role: .sender, content: .text}' < conversation.json - Cualquier cosa basada en texto: convierte cada turno en
{"role":"...","content":"..."}y listo.
Comandos
Cada subcomando tiene --help. Ejecuta chatmem sin argumentos para ver la lista de nivel superior.
chatmem init
Aprovisiona el directorio de datos persistente, extrae Postgres integrado, instala pgvector en el runtime y aplica el esquema. Al finalizar, imprime un fragmento JSON MCP listo para pegar con la ruta real del binario (os.Executable()).
Seguro de volver a ejecutar: todos los pasos de aprovisionamiento son idempotentes.
chatmem mcp [--port <n>]
Ejecuta un servidor MCP stdio para un solo cliente. Inicia Postgres integrado durante la vida del proceso y lo detiene limpiamente al cerrar stdin o SIGTERM.
Los clientes MCP concurrentes no pueden compartir un único Postgres embebido en el MVP — cada proceso chatmem mcp abre su propio PG en el puerto suministrado. Consulta Roadmap para la arquitectura daemon+shim.
chatmem daemon [--port <n>]
Ejecuta la base de Postgres de larga duración para la futura arquitectura daemon+shim. No es necesario para el uso del MVP. Termina limpiamente con SIGINT/SIGTERM.
chatmem telemetry {enable|disable|status}
Gestiona la configuración de telemetría anónima. status imprime el estado efectivo, la fuente de precedencia (env | config | default) y el ID de instalación.
Rutas de datos y configuración
| Tipo | macOS | Linux | Anulación por entorno |
|---|---|---|---|
| Datos | ~/.local/share/chatmem/ | ~/.local/share/chatmem/ | CHATMEM_HOME |
| Caché | ~/Library/Caches/chatmem/ | ~/.cache/chatmem/ | CHATMEM_CACHE |
| ID de instalación | <data>/install_id | <data>/install_id | — |
| Config. de telemetría | <data>/telemetry.json | <data>/telemetry.json | — |
| Datos de Postgres | <data>/pgdata/ | <data>/pgdata/ | — |
| Runtime de Postgres | <cache>/pg-runtime/ | <cache>/pg-runtime/ | — |
Telemetría
chatmem puede enviar pings de uso anónimos para ayudar a rastrear la adopción. El contenido de los mensajes, las cadenas de consulta y los nombres de archivo nunca se envían, jamás. Lo que sí se envía cuando está habilitado:
- UUID de instalación (generado localmente en la primera ejecución, en
<data>/install_id) - versión de
chatmem - contadores para la ventana de vaciado (5 minutos por defecto): capturas, búsquedas, obtenciones, errores
- distribuciones de modelo + cliente — p. ej.
{"claude-opus-4-7": 12, "gpt-5": 4},{"windsurf": 8, "cursor": 8} - percentiles de latencia por operación — p50/p95/p99 en ms
Modos
El cliente opera en tres modos según la configuración:
| Modo | Comportamiento |
|---|---|
| Deshabilitado (entorno o configuración) | No se acumula nada. Aggregator.Record* sigue ejecutándose pero Flush es una operación nula. |
| Habilitado, sin URL de ingesta | Acumular + vaciado periódico → slog.Info("telemetry flush (local-only, no ingest URL set)", ...). Solo observabilidad local. |
| Habilitado, URL de ingesta establecida | Acumular + vaciar → POST a <URL>/v1/ping con 3 intentos de retroceso exponencial. Los envíos fallidos persisten en <data>/pending/*.json y se drenan en el siguiente vaciado exitoso (TTL de 24 h). Los binarios de lanzamiento tienen esta URL incorporada (apunta al Worker del mantenedor); CHATMEM_TELEMETRY_URL la anula, y chatmem telemetry status imprime el valor efectivo. |
Precedencia (gana la más alta)
CHATMEM_TELEMETRY=0(tambiénfalse,off) — desactivación forzosa<data>/telemetry.json({"enabled": true|false}) — elección persistentechatmem telemetry {enable|disable}— escribe lo anterior- Predeterminado: habilitado
chatmem init solicita confirmación en la primera ejecución (solo TTY interactivo) y escribe la configuración. Las inicializaciones sin TTY imprimen un aviso y dejan el valor predeterminado; opta por no participar de forma no interactiva con chatmem telemetry disable o la variable de entorno.
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)
Configurar tu propia ingesta
El cliente publica en cualquier endpoint que hable POST /v1/ping con la carga útil documentada en internal/telemetry/client.go:Payload. Un Cloudflare Worker + D1 listo para implementar vive en server/telemetry-worker/ — un wrangler deploy y tienes un endpoint. Consulta ese README para la configuración de 5 comandos.
Arquitectura
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)
- Lenguaje: Go 1.26, binario estático único (CGO desactivado).
- Almacenamiento:
fergusstrange/embedded-postgresv1.34 que impulsa Postgres 18.3 en un directorio de datos por usuario. Arranque en frío ~600 ms en M-series (~7 s en la primera ejecución debido ainitdb). - Columna vectorial:
vector(384)en la tablachunkscon un índice HNSW de coseno (m=16, ef_construction=64), preparada para búsqueda semántica. Los valores son vectores cero hasta que llegue el integrador ONNX. - Búsqueda (MVP): texto completo de Postgres —
to_tsvector('english', content)+plainto_tsquery, índice GIN (chunks_tsv_idx). Clasificada conts_rank_cd. - MCP:
modelcontextprotocol/go-sdkoficial v1.6.1. Transporte Stdio. - CLI:
spf13/cobrav1.10.
El .dylib de pgvector para darwin_arm64 está comprometido bajo internal/pg/assets/darwin_arm64/ y se envía dentro del binario Go mediante //go:embed. En el primer Start(), internal/pg copia la dylib en <runtimeDir>/lib/postgresql/ y los archivos de control/SQL en <runtimeDir>/share/postgresql/extension/ — luego CREATE EXTENSION vector funciona de inmediato.
Estructura del repositorio
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
Compilar desde el código fuente
Requiere Go 1.26+ y, por ahora, pgvector 0.8.5 de Homebrew (solo si quieres actualizar la dylib incluida — la copia comprometida es suficiente para compilar).
git clone https://github.com/sid077/chatmem
cd chatmem
go build ./...
Para actualizar los artefactos de pgvector incluidos:
# 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
El .so de Linux se compila contra glibc 2.31 de Debian 11 para máxima compatibilidad en tiempo de ejecución — cualquier cosa con glibc ≥ 2.31 funciona (Debian 11+, Ubuntu 22.04+, RHEL 9+, Alpine con libc6-compat, etc.).
Pruebas
Cada prueba que toca almacenamiento inicia un Postgres embebido real — espera ~7–10 s por paquete de prueba en caché fría.
# 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
Las pruebas usan puertos fijos distintos (54334, 54335, 54336) — ejecuta un paquete de prueba a la vez si tienes un daemon de chatmem real ejecutándose en 54329.
Flujo de trabajo de desarrollo
- Editar — el código vive bajo
cmd/chatmemyinternal/. - Probar —
go test ./...después de cada cambio; añade una prueba junto a cualquier nuevo comportamiento de store/MCP. - Actualiza los documentos en cada cambio funcional:
README.mdpara cambios visibles para el usuario (nueva herramienta, nuevo comando, valores predeterminados cambiados).CLAUDE.mdpara cambios orientados al desarrollador (nuevo paquete, nueva invariante, nueva trampa).~/.claude/skills/chatmem/SKILL.mdpara contexto entre sesiones (mantenido en sincronía automáticamente).
- Confirmar — un cambio enfocado por confirmación, línea de asunto imperativa.
go mod tidysi las dependencias cambiaron.
Solución de problemas
En macOS: diálogo "Apple no pudo verificar… puede ser malware" — el binario de lanzamiento no está firmado ni notarizado con Apple Developer ID todavía. Solución temporal en dos pasos:
# 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 ejecuta el paso 2 automáticamente a partir de v0.3.2. La solución real (Developer ID + notarización) está conectada al flujo de trabajo de lanzamiento; se activa para futuros lanzamientos una vez que se añadan los secretos MACOS_CERT_P12 + APP_STORE_CONNECT_KEY. Consulta docs/release-signing-setup.md.
En Linux: advertencia "Package chatmem is not signed! Continue anyway?" de zypper/dnf — los RPM de lanzamiento no están firmados con GPG todavía. Misma historia que macOS — la infraestructura llega en v0.3.2, se activa cuando se añade el secreto GPG_PRIVATE_KEY. Hasta entonces es seguro aceptar la advertencia (--allow-unsigned-rpm).
¿Algo raro? Ejecuta chatmem doctor primero — imprime HOME, EUID, rutas efectivas de datos + caché, disponibilidad de puertos, estado de telemetría y accesibilidad de ingesta, con una verificación verde/roja para cada uno. La mayoría de los problemas de instalación aparecen aquí en una pantalla.
$HOME (…) is owned by uid X but you are uid Y — looks like sudo -E preserved a different HOME — ejecutaste sudo -E chatmem …, que mantuvo HOME=/root pero cambió a un uid no root. Haz una de estas opciones:
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 — estás en un entorno reducido (unidad de systemd sin Environment=HOME=…, env -i, etc.). Establece HOME explícitamente al directorio de inicio de tu usuario de inicio de sesión.
chatmem cannot run as root — en Linux, Postgres se niega a ejecutarse bajo uid 0 y chatmem ahora también se niega, de antemano. Vuelve a ejecutar como usuario sin privilegios: su - <username> -c 'chatmem init' (o sudo -u <username> chatmem init).
El cliente chatmem mcp ve "invalid character 'T' looking for beginning of value" — el protocolo MCP se ejecuta sobre stdout; algo está escribiendo no-JSON allí. Lo más probable es que embedded-postgres se haya configurado con logger: os.Stdout en lugar de os.Stderr (el valor predeterminado aquí es os.Stderr; verifica internal/pg/embedded.go si personalizaste).
no embedded pgvector assets for <goos>/<goarch> — estás ejecutando en una plataforma no compatible. Copia un pgvector precompilado que coincida en internal/pg/assets/<goos>_<goarch>/ (consulta Compilar desde el código fuente para la estructura de archivos) y recompila.
El primer chatmem init tarda ~15–20 s — eso es initdb para un directorio de datos completamente nuevo más la extracción del binario de Postgres. Las ejecuciones posteriores (cachés cálidas) son ~1 s.
El puerto 54329 ya está en uso — o hay otro proceso de chatmem daemon/mcp ejecutándose (mátalo) o algo más tomó el puerto. Usa --port para anular.
CREATE EXTENSION vector falla con could not access file "$libdir/vector" — el .dylib de pgvector no se copió en <runtimeDir>/lib/postgresql/. Verifica la salida de internal/pg/embedded.go de installPgvector; generalmente un directorio de runtime borrado a mitad de ejecución.
Desinstalación
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
Roadmap
Próximamente (aún en el alcance del MVP):
- Integrador ONNX MiniLM int8 → actualizar
search_historyde palabras clave a semántico. - Plataformas restantes:
darwin/amd64(Mac Intel) ywindows/amd64. - Endpoint de ingesta de Cloudflare Worker para pings de telemetría reales.
chatmem daemonHTTP MCP +chatmem mcpshim stdio-a-HTTP para que múltiples clientes MCP puedan compartir un Postgres.
Post-MVP (v1.0):
- Sincronización cifrada E2E opcional (alojada, open-core).
- Notarización de macOS +
.pkgfirmado. - Servicio de Windows, inicio automático launchd/systemd.
- Envoltorios de SDK de Python + TypeScript alrededor de las herramientas MCP.
- Distribución a apt, dnf/COPR, zypper/OBS, AUR, winget, scoop.
- Sitio de documentación.
Publicar un lanzamiento
Disparado por etiqueta — el flujo de trabajo de GitHub Actions release hace todo.
git tag -a v0.0.1 -m "v0.0.1"
git push origin v0.0.1
Al empujar la etiqueta, el flujo de trabajo:
- Ejecuta
goreleaser release --clean— compila de forma cruzada darwin/arm64 + linux/amd64 + linux/arm64, empaqueta.tar.gz+.rpm+.deb, sube los archivos al lanzamiento de GitHub y empuja la fórmula actualizada de Homebrew asid077/homebrew-chatmem. - Ejecuta
createrepo_cen los.rpmpara construir un árbol de repositorio compatible con zypper/dnf. - Empuja el árbol del repositorio a la rama
gh-pagesde este repositorio.
Los usuarios reciben actualizaciones mediante zypper refresh / brew upgrade / dnf update sin ninguna acción adicional tuya.
Para una prueba en seco local antes de etiquetar:
goreleaser release --snapshot --clean --skip=publish # produces dist/*
scripts/build-rpm-repo.sh # produces dist/rpm-repo/ (needs Docker for createrepo_c)
Verifica con un contenedor de 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'
Firma GPG (recomendada para producción)
El MVP se envía sin firmar (gpgcheck=0 en el archivo .repo). Para firmar:
- Genera una clave GPG:
gpg --gen-key. - Exporta la clave pública:
gpg --armor --export you@example.com > chatmem.gpg. - En
.goreleaser.yaml, añadesigns:para los rpms con tu ID de clave. - Copia
chatmem.gpgjunto al archivo.repoendist/rpm-repo/y cambiagpgcheck=1+gpgkey=<URL>/chatmem.gpg.
Licencia
Apache-2.0. Consulta LICENSE.