@sksoftofficial/mindroot
v1.0.6
Published
Long-term memory for AI agents: hybrid-searched memories over human-editable markdown notes. 100% local — offline ONNX embeddings, SQLite FTS5, MCP server, REST API, CLI, dashboard. No cloud, no API keys.
Maintainers
Readme
Mindroot
AI agents that never forget.

Mindroot is a tiny Node.js service that gives your AI agents a permanent, project-scoped memory: hybrid-searched memories over human-editable markdown notes, exposed through a local HTTP service with an MCP interface, a REST API, and a built-in dashboard. Everything runs on your machine — offline embeddings, SQLite, plain markdown files you own. No cloud, no API keys, no per-query cost.
- Website: https://skbilisim.com/en/projects/mindroot
- npm: https://www.npmjs.com/package/@sksoftofficial/mindroot
How it works
- Notes (Layer 2): human-editable markdown files under
~/.mindroot/notes/<project>/, parsed into heading-addressable sections such asOverview::API. The unit of storage. - Memories (Layer 1): short, retrieval-optimized texts that stand alone or point into a note or section via
target_path. The unit of retrieval. - Search: memories are the semantic index — hybrid ranking of cosine similarity (0.75) over local embeddings plus BM25 keyword search (0.25) through SQLite FTS5. Note sections use literal case-insensitive string matching. All content search is strictly project-scoped; project discovery uses its own fuzzy slug search with slug embeddings and token overlap.
Features
- Two-layer memory — markdown notes for depth, short memories for recall, linked via heading paths.
- Local embeddings — 768-dimensional q8 embeddings from
onnx-community/embeddinggemma-300m-ONNXvia transformers.js, computed fully offline and cached under~/.mindroot/models. - Hybrid search — semantic + BM25 ranking over memories, literal string matching over note sections, fuzzy project-name matching across projects.
- MCP server — JSON-RPC endpoint at
/mcpwith 12 project/memory/note tools for agents. - REST API — the same operations over
POST/GET/PUT/DELETE /api/*for scripts and integrations. - Dashboard — web UI at
/for browsing notes and memories, editing markdown, and searching. - CLI — init, start/stop/restart/status, and human-readable browsing commands.
- Background service —
mindroot startdaemonizes, writes a pidfile, and waits until healthy;stopandrestartmanage it. - Self-healing reads — note reads verify a stored content hash and silently re-index files edited externally.
- Surgical edits —
update_sectionsplices only the targeted heading's line range, leaving the rest of the file byte-identical. - Link cleanup — deleting a note clears memories linked to its exact path or one of its heading paths.
- Auto-created projects — any write to a new project slug creates it; no cwd detection, no disk scanning, no cross-project content search.
- Local-first storage — one SQLite database (
node:sqlite, WAL mode, FTS5) plus plain markdown files you own. - Security — constant-time Bearer API-key auth for every
/apiand/mcprequest, server-side path-traversal and slug validation, localhost-only by default.
Install
Requires Node.js >= 22.
npm install -g @sksoftofficial/mindroot # or: npm link from a clone
mindroot init # creates store, schema, api key; caches the embedding model (~316MB)
mindroot start # daemonizes and waits until healthyService management
mindroot start # start in the background, wait for /health
mindroot start --foreground # run attached for debugging
mindroot stop # stop the pidfile process (escalates when needed)
mindroot restart # stop + start
mindroot status # pidfile, health endpoint, and key consistency check
mindroot dashboard # open the dashboard in your browserThe default address is 127.0.0.1:7620, configurable in ~/.mindroot/config.json. There is no crash auto-restart.
Quick start (MCP)
Point your MCP client at the service:
{
"mcpServers": {
"mindroot": {
"type": "remote",
"url": "http://127.0.0.1:7620/mcp",
"headers": { "Authorization": "Bearer <apiKey>" }
}
}
}The API key is generated by mindroot init and stored in ~/.mindroot/config.json (never printed). Tool failures return isError: true content instead of crashing the call.
Agent memory policy
Add this policy to your agent's instructions:
<!-- mindroot:start -->
# Agent memory policy (mindroot)
Use mindroot MCP tools as persistent memory for durable project facts. It's a project index, not a scratchpad.
- EVERY task — not once per session; a single session can contain multiple unrelated tasks, so run this per task: resolve the project slug (`search_projects` with the repo name, or `list_projects`), `search_memories` with keywords from the task, then `read_note` the notes those memory hits link to. Always choose reading the facts from mindroot over exploring the codebase. Exploring source is the second option, ONLY when mindroot has no relevant notes or the notes don't match reality — then source is the truth (and correct the stale note).
- Every content tool needs a project slug. A project is created automatically on first write.
## Notes vs memories
Notes are storage; memories are the retrieval index into them. They work as a pair:
- **Memories** (`save_memory`, `delete_memory`) — short, standalone atomic facts ("where email delivery lives", "never migrate during business hours"), optionally linked via `target_path` ("note.md::Heading"). The semantic search layer: `search_memories` ranks hybrid (embeddings + keywords). No listing tool; hits carry the ids for `delete_memory`. When in doubt, save a memory.
- **Notes** (`save_note`, `update_section`, `read_note`, `delete_note`) — structured markdown docs parsed into heading-addressable sections. `read_note` without args returns full content plus the section list; with `section` ("Audit logging::API") returns only that section's body — prefer section reads when you don't need the whole note. `update_section` takes the same heading paths; run `read_note`/`list_notes` first and copy exact paths. `search_notes` is a literal case-insensitive string search over section text — a fallback for exact keywords/identifiers, not semantic search.
After writing or updating a notable note section, also save 1–3 memories pointing at it (`target_path`) so future searches surface it — one per key fact a future agent would search for. If a memory stands alone (no note worth writing), that's fine too. Dedupe by searching first, then deleting stale hits — never stack near-copies.
Notes read like compact index cards: one sentence on what the feature is, then the files, entry points, and data flow needed to work on it later — enough to answer "where is this implemented?" without reading source. Keep one feature per note under keyword-rich headings (`## Model`, `## API`, `## Gotchas`, …), plus standard cross-cutting notes: `overview.md`, `conventions.md` (patterns shared across features), `commands.md`, `gotchas.md`.
## What to save
Facts that improve future navigation, implementation, debugging, or verification: structure and key files, config values, frameworks/services, build/test/deploy commands, conventions, recurring bugs and gotchas.
Never save: task history, reasoning trails, rejected alternatives, dated recaps, secrets, logs, or speculation.
Write bullets as standalone present-tense facts with file anchors (`models/AuditLog.jsx` defines model `AuditLog` in collection `auditlog`). Not "we decided X today".
After work that changes a durable fact, update the smallest relevant note section — plus linked memories for its key facts — automatically before your final response, no confirmation needed. Notes stay compact: merge overlapping bullets, drop stale ones.
<!-- mindroot:end -->CLI
CLI output is human-readable; agents should use MCP tools.
| Command | Description |
|---|---|
| mindroot init | Initialize store + model cache (idempotent) |
| mindroot start [--foreground] | Daemonize the service (or run foreground for debugging) |
| mindroot stop | Stop the running service |
| mindroot restart | Restart the service (stop + start) |
| mindroot status | Show whether the service is running |
| mindroot dashboard | Open the dashboard in your browser |
| mindroot projects | List projects with counts |
| mindroot notes --project <slug> [path] | List notes, or print one |
| mindroot memories --project <slug> | List memories |
| mindroot save-memory "<text>" --project <slug> [--link "note.md::Heading"] | Save a memory |
| mindroot search "<query>" --project <slug> [--limit N] | Search memories + notes |
MCP tools
All tools are prefixed mindroot_.
| Tool | Params | Purpose |
|---|---|---|
| list_projects | — | List projects with note/memory counts |
| search_projects | query | Fuzzy-find a project by name (semantic + token overlap) |
| rename_project | project*, new_slug* | Rename a project; moves notes and preserves memories and indexes |
| search_memories | project*, query, limit? | Hybrid search over memories (hits carry ids) |
| search_notes | project*, query, limit? | Literal string search over note sections (fallback to memories' semantic search) |
| save_memory | project*, text, target_path? | Save a memory, optionally linked to a note/section |
| delete_memory | project*, id | Delete a memory by id |
| list_notes | project* | List notes with section paths |
| read_note | project*, path, section? | Full note content, or one section's body via section (hash-verified, auto-reindexed) |
| save_note | project*, path, content | Create/overwrite a note |
| update_section | project*, path, heading_path, content | Replace one section body, or append the section when missing |
| delete_note | project*, path | Delete a note (clears its linked memories) |
REST API
Same operations as MCP, for scripts and integrations. All /api/* routes (and /mcp) require Authorization: Bearer <apiKey>; /health does not.
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness probe |
| GET | /api/projects | List projects |
| POST | /api/projects | Create project { slug } |
| GET | /api/projects/:slug | Project detail with docs and memories |
| DELETE | /api/projects/:slug | Delete a project |
| GET | /api/projects/:slug/docs | List notes |
| GET/PUT/DELETE | /api/projects/:slug/doc?path= | Read (optionally §ion=), write, or delete one note |
| POST | /api/projects/:slug/sections | Update one section { path, heading_path, content } |
| GET/POST | /api/projects/:slug/memories | List or add memories { text, target_path? } |
| DELETE | /api/projects/:slug/memories/:id | Delete a memory |
| POST | /api/search | Search { project, query, kind?: "memories"\|"notes", limit? } |
| POST | /mcp | JSON-RPC MCP endpoint |
Dashboard
The dashboard at http://127.0.0.1:7620/ stores its key in localStorage and offers:
- Notes view with a markdown editor in Write / Split / Read modes and rendered preview.
- Memories view for browsing and adding memories with optional note links.
- Search view across notes and memories.
Configuration
Everything lives under ~/.mindroot/:
config.json—port(default 7620) andapiKeynotes/<project>/— your markdown notesmodels/— cached ONNX embedding modelmindroot.db— SQLite index (WAL mode)mindroot.pid/mindroot.log— written bystart/stop
Set MINDROOT_DIR to relocate the whole store.
Development
node --test # runs the test suite in test/License
ISC
