README.md
Rama main · réplica de lectura del repositorio original.
# SynaptoMind
[](https://github.com/zumik3-del/synaptomind/actions/workflows/ci.yml)
[](https://github.com/zumik3-del/synaptomind/actions/workflows/codeql.yml)
[](https://github.com/zumik3-del/synaptomind/actions/workflows/security.yml)
[](https://coveralls.io/github/zumik3-del/synaptomind?branch=main)
[](https://github.com/zumik3-del/synaptomind/releases)
[](LICENSE)
[](https://ghcr.io/zumik3-del/synaptomind)
[](https://bun.sh)
**Local persistent memory for AI agents.** Your agent remembers decisions, goals, and context across sessions — over MCP (stdio or HTTP). Data stays on your machine. The HTTP listener on port 3005 is an ops plane (health and graph repair), not a second agent API.
<p align="center">
<img src="docs/images/synaptomind-hero.png" alt="SynaptoMind" width="100%" />
</p>
**Works with** Claude Desktop · Cursor · OpenCode · Codex · any MCP client
---
## Quick Start
```bash
curl -fsSL https://raw.githubusercontent.com/zumik3-del/synaptomind/main/deploy/install.sh | bash
```
A piped script has no sibling files, so the shared helpers and config are fetched
from the published base by default — no environment variables are needed.
`DEPLOY_RAW_URL` overrides that base (forks/mirrors); an explicit `APP_ENV_URL`
or `LIB_RAW_URL` still wins. The installer downloads and extracts the release
tarball (`DIST=binary`); the server starts on `http://127.0.0.1:3105` (HTTP ops) and
`http://127.0.0.1:3106/mcp` (MCP). Config lives in `/var/lib/synaptomind/config.json`,
reached via the `/opt/synaptomind/config.json` symlink. The installer seeds
ports 3105/3106; the client-config and endpoint examples in this README use the
built-in defaults (3005 HTTP ops / 3006 MCP).
For the full install & update guide (flags, channels, rollback, uninstall), see
[docs/DEPLOY.md](docs/DEPLOY.md). For the deployment convention shared with
ziptask and subagentix — canonical layout, unit policy, the two distribution
modes and the per-app `app.env` — see
[docs/DEPLOY-LAYOUT.md](docs/DEPLOY-LAYOUT.md).
Connect your client — add to Claude Desktop config (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"synaptomind": {
"url": "http://127.0.0.1:3006/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
```
The installer generates `SYNAPTOMIND_SECRET` into `/opt/synaptomind/.env`; read
it with `sudo grep SYNAPTOMIND_SECRET /opt/synaptomind/.env`. That's it — your
agent now has persistent memory.
---
## Use Cases
- **Coding agent memory** — architecture decisions, bug root causes, TODOs persist between sessions
- **Project journal** — decisions and goals as a connected graph, not scattered notes
- **Idea graph** — capture thoughts, let semantic search find connections you missed
- **MCP memory backend** — plug persistent memory into any MCP-compatible agent
---
## Example: Auth Decision Across Sessions
```
You: "Remember: we chose JWT for auth, refresh tokens in httpOnly cookies"
Agent: [memory_store action=create — tags: auth, jwt, security]
--- new session ---
You: "What did we decide about auth?"
Agent: [memory_recall action=search "auth decision" → finds JWT thought with full context]
You: "What should I do next?"
Agent: [memory_status action=frontier → "Implement refresh token rotation" ranked #1]
```
Core loop: **capture → link → retrieve → act**. No manual organization — the graph connects related thoughts automatically.
```mermaid
sequenceDiagram
participant U as You
participant A as Agent
participant S as SynaptoMind
U->>A: "Remember JWT for auth"
A->>S: memory_store(action=create, content, tags)
S-->>A: thought_id
Note over U,A: --- new session ---
U->>A: "What about auth?"
A->>S: memory_recall(action=search, "auth")
S-->>A: JWT thought + context chain
A->>S: memory_status(action=frontier)
S-->>A: ranked next actions
```
## Features
<details>
<summary><strong>Core concepts — what makes this more than a note app</strong></summary>
| Concept | What it does |
|---------|-------------|
| **Thoughts** | Individual notes/ideas. Each gets an embedding for semantic search and links to other thoughts. |
| **Slots** | Context windows summarizing your state: pending items, project context, active goals and architecture decisions. Agents read these on startup. |
| **Frontier** | Ranks "what to do next" over `directive`/`todo`/`pending` thoughts. Deferred pending items join once their `wake_days` delay has elapsed. |
| **Primer** | Compact project summary for quick context injection. Promotes the most relevant thoughts into one document. |
| **Clusters** | Group related thoughts; inspect and maintain their graph membership. |
</details>
<details>
<summary><strong>Technical capabilities</strong></summary>
- **Graph storage** — thoughts, edges, projects, tags in SQLite
- **Hybrid search** — vector (vec0) + BM25 (FTS5) via Reciprocal Rank Fusion
- **Local embeddings** — `@huggingface/transformers`, no API keys
- **MCP server** — stdio + HTTP transport
- **Auto-clustering** — batch grouping by embedding proximity
- **Background jobs** — decay, proposal retention
</details>
---
## Architecture
SynaptoMind runs as a single Bun process: the HTTP server on port 3005 (health and
health-check ops endpoints), the MCP server on port 3006 (stdio or HTTP), and a child
process that serves local embeddings over IPC. Every thought operation is served over MCP;
the HTTP listener is an ops plane, documented in [docs/API.md](docs/API.md). Storage is one
SQLite database using the `vec0` extension for vector search, FTS5 for full-text search, and
an edges table for the graph.
```mermaid
graph LR
A[AI Agent] -->|MCP| B[SynaptoMind]
O[Ops / monitoring] -->|HTTP health| B
B --> C[(SQLite)]
C --> D[vec0 — vector search]
C --> E[FTS5 — full-text search]
C --> F[Graph — edges & links]
```
---
## Connecting MCP Clients
<details>
<summary><strong>Claude Desktop</strong></summary>
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"synaptomind": {
"url": "http://127.0.0.1:3006/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
```
</details>
<details>
<summary><strong>Cursor</strong></summary>
Add to `.cursor/mcp.json` in your project or global config:
```json
{
"mcpServers": {
"synaptomind": {
"url": "http://127.0.0.1:3006/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
```
</details>
<details>
<summary><strong>OpenCode</strong></summary>
Add to `~/.config/opencode/opencode.json`:
```json
{
"mcp": {
"synaptomind": {
"type": "remote",
"url": "http://127.0.0.1:3006/mcp",
"headers": { "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
```
</details>
<details>
<summary><strong>Stdio transport</strong></summary>
For clients that prefer stdio:
```json
{
"command": "bun",
"args": ["run", "/path/to/synaptomind/src/index.ts", "--stdio"],
"env": { "SYNAPTOMIND_SECRET": "your-token" }
}
```
By default the stdio process is a **single-owner client**: it opens an MCP
session against the shared database but does **not** start its own embedder
child process or the decay / proposal-retention jobs at startup. The standalone
HTTP server owns the scheduled jobs. An embedding request can still start a
local child on demand; stdio does not reuse the HTTP server's embedder process.
If you run stdio with no separate server, opt into local ownership with the
`--stdio-standalone` flag (`args: ["run", "...", "--stdio", "--stdio-standalone"]`)
or `SYNAPTOMIND_MCP_STDIO_STANDALONE=true` / `"mcp": { "stdioStandalone": true }`
in `config.json`.
</details>
<details>
<summary><strong>Codex (OpenAI)</strong></summary>
See [docs/codex-plugin.md](docs/codex-plugin.md) for installation and usage.
</details>
<details>
<summary><strong>ChatGPT (OpenAI Secure MCP Tunnel)</strong></summary>
Connect web ChatGPT to a private SynaptoMind MCP server without exposing an
inbound network port. See
[docs/secure-mcp-tunnel.md](docs/secure-mcp-tunnel.md) for setup, validation,
and the security boundary.
</details>
---
## Configuration
All settings in `config.json`. Priority: env vars > config.json > defaults.
The HTTP listener port resolves in that order too: `SYNAPTOMIND_PORT` overrides
`config.json` `server.port`, which overrides the built-in `3005`
(default `src/config.ts:58`, env mapping `src/config.ts:138`, applied in
`applyEnvOverrides()` `src/config.ts:283-301`).
| Setting | Default | Description |
|---------|---------|-------------|
| `server.port` | 3005 | HTTP ops port (health, health-check) |
| `server.host` | 127.0.0.1 | Bind address |
| `mcp.httpPort` | 3006 | MCP HTTP transport port |
| `mcp.stdioStandalone` | false | Let a `--stdio` process own the embedder + background jobs (default: shared HTTP server owns them) |
| `embedder.model` | Xenova/multilingual-e5-small | HuggingFace embedding model |
| `db.path` | ./data/synaptomind.db | SQLite database path |
Auth tokens via env vars only:
| Env var | Purpose |
|---------|---------|
| `SYNAPTOMIND_SECRET` | Bearer token in single-user mode (`users: []`, the default) |
| `SYNAPTOMIND_USER_TOKEN_<ID>` | Per-user bearer token when `config.json` has a `users` list; `SYNAPTOMIND_SECRET` is then inert |
In multi-user mode every HTTP request must present one of the configured users'
tokens and is served that user's database. Without a token the server fails
closed: authenticated API and MCP requests are rejected with `401` and a warning
is logged at startup. For local development only, `SYNAPTOMIND_ALLOW_INSECURE=true`
disables auth entirely in single-user mode.
Multi-user: `config.json` may carry a `users` list — one SQLite database and one
bearer token per user, all served by the same process. Adding, rotating and
removing users, and switching an existing install over, is in
[CONFIG.md § Users](docs/CONFIG.md#users-multi-user).
See `config.json.example` for all options. Full reference: [docs/CONFIG.md](docs/CONFIG.md). Custom MCP instructions: [docs below](#custom-instructions).
---
<details>
<summary><strong>Docker</strong></summary>
### From source (development)
```bash
docker compose up -d --build
```
Volumes mount `./data` and `./config.json`.
### Published image (production)
The compose file resolves the image via the `SYNAPTOMIND_IMAGE` variable (default `:local`, built from source):
```bash
SYNAPTOMIND_IMAGE=ghcr.io/zumik3-del/synaptomind:latest docker compose pull && docker compose up -d
```
Tagged releases publish `ghcr.io/zumik3-del/synaptomind:<version>` (and `:latest`
for non-prereleases) via the release workflow; the `deploy/` framework does not
manage containers.
### Container user
The container runs as a non-root user (uid/gid `10001`). Make sure the mounted paths are writable/readable by that uid:
```bash
sudo chown -R 10001:10001 data # required once when upgrading from older (root-run) images
chmod 644 config.json # config.json must be readable by uid 10001
```
### Auth
```bash
echo "SYNAPTOMIND_SECRET=your-secret-token" > .env
```
See `.env.example` for all variables. Without a token (`SYNAPTOMIND_SECRET` in
single-user mode, or a configured user's token when `config.json` has a `users`
list) the server fails closed — authenticated requests are rejected with `401`.
Use `SYNAPTOMIND_ALLOW_INSECURE=true` for local development only (single-user
mode).
For full Docker guide (updating, backup, troubleshooting), see [docs/DOCKER.md](docs/DOCKER.md).
</details>
<details>
<summary><strong>Storing and recalling: MCP examples</strong></summary>
Every thought operation is an MCP tool call (stdio or `POST /mcp` on port 3006). The
HTTP listener serves the ops endpoints only — `/health` and the two `/api/health-check`
routes — and answers `404` for everything else; see [docs/API.md](docs/API.md).
```text
Agent: [memory_store action=create
content="First session went well" tags=["retrospective"]
cwd="/path/to/project"]
Agent: [memory_recall action=search query="session retrospective" limit=5
cwd="/path/to/project"]
```
Arguments, envelopes and per-action examples: [docs/mcp-tools.md](docs/mcp-tools.md).
### Health check
```bash
curl http://127.0.0.1:3005/health
```
### Graph health report
```bash
curl "http://127.0.0.1:3005/api/health-check?severity=warning" \
-H "Authorization: Bearer $SYNAPTOMIND_SECRET"
```
</details>
<details>
<summary><strong>MCP tools</strong></summary>
| Category | Tools |
|----------|-------|
| Recall | `memory_recall` (search, get, context, chain, clusters, list) |
| Store | `memory_store` (create, update, link, link_batch, unlink, unlink_batch, retype, bulk_create) |
| Supersede | `memory_supersede` (archive, archive_batch, merge) |
| Status | `memory_status` (slots, frontier, config, health, propose) |
| Projects | `memory_manage` (list, create, update, delete, resolve) |
| Consolidate | `memory_crystallize` (graph, cluster, auto_cluster) |
| Reflect | `memory_reflect` (reflect, timeline) |
| Primers | `memory_primers` (list, delete) |
| Review | `edge_review` (enqueue, list, apply, apply_batch, rollback, reject) |
| Retention | `retention_review` (list, protect, delete, compact) |
| Guide | `memory_guide` |
Every action answers in one response envelope — `{ items, meta }` for collections, `{ item, meta }`
for single objects — and the project-scoped actions take an optional `cwd` that narrows an answer
to one project: without it a read answers across every project and says so in `meta`, while a
created thought lands in the default project. `memory_status action=propose` is the exception
among the readers: its plan stays pinned to the project of the thought it addresses. For
action-level detail (parameters, the dry-run/confirm pattern, examples), see
[docs/mcp-tools.md](docs/mcp-tools.md).
**Breaking 0.x changes:** this release prunes several 0.x subsystems from the MCP tools,
the HTTP surface and the configuration surface. The 0.20 release shrinks HTTP to the three
ops endpoints: every `/api/*` thought route is gone and answers `404`, with the route → MCP
tool map and the dropped capabilities in [docs/API.md](docs/API.md#removed-routes-020).
[CHANGELOG.md](CHANGELOG.md) records every removal per release; the pages linked here
document only the surface that still exists, and a configuration file that still carries
retired keys loads unchanged and ignores them.
</details>
---
## Custom Instructions
The MCP server sends instructions to the AI agent on startup. To customize:
1. Create a markdown file (e.g., `instructions.md`)
2. Set the path in `config.json`:
```json
"mcp": {
"instructionsFile": "./instructions.md"
}
```
Or via env var:
```bash
export SYNAPTOMIND_MCP_INSTRUCTIONS_FILE=./instructions.md
```
Falls back to default instructions if file not found.
---
## Development
```bash
bun run test # canonical suite (src/), sharded across parallel processes
bun run test:unit # fast lane: everything except the entry-point/child-boot tests
bun run test:integration # slow lane: those entry-point/child-boot tests only
bun run test:serial # single-process oracle (`bun test src`), same file set
bun run test:all # full suite (src/ + deploy/ + eval/ + scripts/)
bun run test:deploy # deploy shell integration (deploy/)
bun run test:eval # retrieval quality gates (eval/)
bun run test:perf # perf scale (SYNAPTOMIND_PERF_SUITE=1)
bun run test:isolated # subprocess-heavy embedder IPC + http transport lifecycle
bun run eval # memory evaluation harness (recall@k, MRR, baselines)
bunx biome check src/ eval/ # lint (advisory)
bun run format # apply Biome formatting (src eval scripts deploy)
bun run format:check # verify formatting — blocking CI gate
```
`bun test` (without a script) still runs the full suite; the canonical developer
gate is `bun run test`, which runs exactly the same `src/**/*.test.ts` set as
`bun test src` but splits the files across several `bun test` processes — Bun
runs test files sequentially in one process, so sharding is what uses the extra
cores. `bun run test:serial` is the single-process fallback for bisecting a
failure, and `--shards N` (or a `--` passthrough, e.g. `bun run test -- --bail`)
is honoured by the runner. Lanes and shard weights live in `src/test/lanes.ts`.
`vec0.so` is installed by `bun install` into the checkout that ran it, so a fresh
git worktree has no copy of its own. `bunfig.toml` preloads `src/test/preload.ts`
for tests only: when the running checkout has no `vec0.so`, it points the test
process at the main checkout's copy and prints one line naming the file it used. A
local copy always wins (and `SYNAPTOMIND_VEC0_PATH` overrides everything), so a
genuinely broken extension still fails loudly.
For step-by-step usage scenarios, see [docs/SCENARIOS.md](docs/SCENARIOS.md).
For performance benchmarks (search latency, write throughput, embedding speed), see [docs/BENCHMARK.md](docs/BENCHMARK.md).
For the memory evaluation harness (metrics, datasets, thresholds), see [docs/EVAL.md](docs/EVAL.md).
### Releasing
Releases are created by CI on `main`; the version is set explicitly, not derived
from commit types.
1. From a clean `dev` tree, prepare the bump:
`bash scripts/release.sh <version>` (or `bun run release <version>`) — bumps
`version` in `package.json` and regenerates the pending `CHANGELOG.md`
section. It does not create a tag, commit, or push.
2. Commit the bump + changelog, push, and open a PR `dev` → `main`.
3. Merging the PR triggers `.github/workflows/release.yml` on `main`: it
verifies the tree (`tsc` + `bun test`), creates the annotated tag
`v<version>` plus the GitHub Release (prerelease when the version contains
`-`), and separately publishes the Docker image.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
[MIT](LICENSE)
---
## Topics
`mcp` `mcp-server` `ai-memory` `agent-memory` `llm-memory` `persistent-memory` `self-hosted` `local-first` `knowledge-graph` `semantic-search` `ai-agents` `sqlite` `rag`