enhx-memory
by cbuntingde
README.md
# enhx-memory
A persistent, project-scoped memory layer for AI coding agents. Exposes a
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server with
**45 tools** for capturing, searching, deduplicating, relating, and
maintaining memory records across long-running coding sessions.
**Enterprise-grade by default.** The server is **100% automatic** โ
- ๐ **Auto-session**: opens a session against the cwd (or
`INITIAL_PROJECT_PATH`) at boot. You never need to call `start_session`.
- ๐ **Cwd watcher**: polls `process.cwd()` and auto-rebinds to a new
project when the agent changes directories (worktree, monorepo, etc).
- ๐งน **Auto-curate on session boot**: scans the project roots listed in
`AUTO_CURATE_ROOTS` (README, CHANGELOG, plan files, decisions, agents,
package.json) and runs the extractors to persist typed memories
(`command`, `error`, `decision`, `reference`). Idempotent; only re-runs
when an mtime changes.
- ๐ฅ **Auto-ingest**: user prompts that flow through the MCP transport
are persisted as memories with the `auto_ingest` tag, debounced per
project. Three independent ingest paths (transport frame, per-tool
args wrapper, POSIX `SIGUSR1` debug signal) share a single debounce
map, so a payload that arrives twice lands once.
- ๐ **Auto-recall**: every project-scoped tool call returns an
`auto_recalled` array of the most relevant pinned / type-weighted /
recency-decayed memories โ the agent never has to remember to call
`recall_memories`.
- ๐ง **Memory Manager** enforces dedup, near-duplicate merging, project
conventions, and importance scoring.
This is the **TypeScript rewrite** of the original Python/FastMCP
`enhx-memory` v0.1.0. The two servers share the same SQLite schema
(`migrations/0001_initial.sql` is byte-identical), so existing databases
work without modification.
---
## Install
### A. `npx` (recommended โ zero install)
```bash
npx enhx-memory
```
### B. Global npm
```bash
npm install -g enhx-memory
enhx-memory
```
### C. Single binary
Download a binary for your platform from the GitHub release page. The
binary bundles Node + the server + the migrations.
---
## What's new in 1.1.0
Six phases of content-aware work landed between 1.0.0 and 1.1.0. If you
already have a 1.0.x database, the schema is unchanged โ just upgrade.
- **Phase 3 โ Content ingest.** Four new tools (`ingest_file`,
`ingest_files`, `extract_commands`, `extract_errors`) and a
`FileIngester` that respects `FILE_INGEST_MAX_BYTES` and a binary
sniff gate.
- **Phase 4 โ Auto-curate on session boot.** When a session opens,
`AutoSessionManager` runs `curate_session` against the configured
roots. The synthesized session brief is pinned as a memory, so the
next `open_session_brief` returns it instantly.
- **Phase 5 โ Robust transport ingest hook.** Every `server.tool(...)`
call is wrapped so free-text args are inspected for ingestible content.
Also wired a POSIX `SIGUSR1` handler that reads stdin and ingests it โ
a power-user escape hatch for dumping debug payloads into memory
without going through the MCP client.
- **Phase 6 โ Recaller v2.** `recall_memories` now applies type
weights (`RECALL_TYPE_WEIGHTS`) and a per-day recency decay
(`RECALL_RECENCY_DECAY`) on top of the FTS5 score. Pinned memories
always come first.
- **Phase 7 โ `open_session_brief` + `export_project_doc`.** Two new
tools that surface the pinned session brief + top-5 recall, and that
compose a single Markdown document summarising the active project
(capped to `FILE_INGEST_MAX_BYTES`).
The full test count went from 208 โ 323.
---
## MCP client configuration
### Claude Desktop / Cursor / Cline
Common โ the three knobs most people tweak: log verbosity, how many
memories get auto-attached per tool call (the recall cap), and whether the
auto-cleanup scheduler runs. Everything else falls back to its default.
```json
{
"mcpServers": {
"enhx-memory": {
"command": "npx",
"args": ["-y", "enhx-memory"],
"env": {
"DATA_DIR": "/path/to/your/data",
"LOG_LEVEL": "INFO",
"AUTO_RECALL_LIMIT": "5",
"ENABLE_AUTO_CLEANUP": "true"
}
}
}
}
```
> **Note**: there is no `MAX_MEMORY_ITEMS` โ the closest knob is
> `AUTO_RECALL_LIMIT`, which caps how many memories are auto-attached to
> every tool result. To bound total storage, use `ENABLE_AUTO_CLEANUP` with
> `CLEANUP_INTERVAL_HOURS` and `SOFT_DELETE_GRACE_DAYS` (below).
Fully-configured โ every variable the server reads from `env`, with
defaults inline. Drop or change the ones you want to override; everything
else is optional.
```json
{
"mcpServers": {
"enhx-memory": {
"command": "npx",
"args": ["-y", "enhx-memory"],
"env": {
"DATA_DIR": "/path/to/your/data",
"DB_FILENAME": "enhx_memory.db",
"LOG_LEVEL": "INFO",
"DEDUP_POLICY": "reject",
"DEDUP_THRESHOLD": "0.8",
"ENABLE_AUTO_CLEANUP": "true",
"CLEANUP_INTERVAL_HOURS": "24",
"SOFT_DELETE_GRACE_DAYS": "7",
"ENABLE_AUTO_SESSION": "true",
"INITIAL_PROJECT_NAME": "",
"INITIAL_PROJECT_PATH": "",
"INITIAL_CLIENT": "auto",
"ENABLE_AUTO_INGEST": "true",
"AUTO_INGEST_DEBOUNCE_SECONDS": "30",
"AUTO_INGEST_MIN_LENGTH": "20",
"ENABLE_AUTO_RECALL": "true",
"AUTO_RECALL_LIMIT": "5",
"AUTO_RECALL_MIN_SCORE": "0",
"AUTO_DETECT_CONVENTIONS": "true",
"ENABLE_CWD_WATCHER": "true",
"CWD_CHECK_INTERVAL_SECONDS": "5",
"ENABLE_FILE_INGEST": "true",
"FILE_INGEST_MAX_BYTES": "262144",
"FILE_INGEST_BINARY_SNIFF": "true",
"AUTO_CURATE_SESSION": "true",
"AUTO_CURATE_ROOTS": "README.md,CHANGELOG.md,plan*.md,plan/*.md,decisions.md,agents.md,package.json,.github/agents.md",
"CURATE_PIN_BRIEF": "true",
"RECALL_TYPE_WEIGHTS": "error:.5,decision:.3,pattern:.25,code:.1,reference:.05,note:0,conversation:0",
"RECALL_RECENCY_DECAY": "0.95"
}
}
}
}
```
Globally-installed npm (the `env` block is optional โ only override what you
need):
```json
{
"mcpServers": {
"enhx-memory": {
"command": "enhx-memory",
"env": {
"LOG_LEVEL": "INFO",
"AUTO_RECALL_LIMIT": "5",
"ENABLE_AUTO_CLEANUP": "true"
}
}
}
}
```
Single binary (same โ `env` block optional):
```json
{
"mcpServers": {
"enhx-memory": {
"command": "/usr/local/bin/enhx-memory",
"env": {
"LOG_LEVEL": "INFO",
"AUTO_RECALL_LIMIT": "5",
"ENABLE_AUTO_CLEANUP": "true"
}
}
}
}
```
---
## CLI flags
| Flag | Behavior |
|---|---|
| `--data-dir` | Print the data directory path and exit |
| `--install-path` | Print the install path |
| `--reset --yes` | Wipe the database and logs (refuses without marker file) |
| `--uninstall --yes` | Remove the install + data dir |
| `--upgrade` | Print the `npm install -g enhx-memory@latest` command |
| `--version` | Print version |
| `--help` | Print help |
Without flags, the server starts on stdio.
---
## Environment variables
| Variable | Default | Description |
|---|---|---|
| `DATA_DIR` | `~/enhx-memory` | Where the DB and logs live |
| `DB_FILENAME` | `enhx_memory.db` | Database filename |
| `LOG_LEVEL` | `INFO` | `DEBUG` \| `INFO` \| `WARNING` \| `ERROR` |
| `DEDUP_POLICY` | `reject` | `reject` \| `merge` \| `warn` \| `off` |
| `DEDUP_THRESHOLD` | `0.8` | Jaccard similarity cutoff for near-duplicates |
| `ENABLE_AUTO_CLEANUP` | `true` | Run the cleanup scheduler |
| `CLEANUP_INTERVAL_HOURS` | `24` | Cleanup cycle period |
| `SOFT_DELETE_GRACE_DAYS` | `7` | Days to keep soft-deleted rows before purging |
| `ENABLE_AUTO_SESSION` | `true` | Open a session at boot against cwd / `INITIAL_PROJECT_PATH` |
| `INITIAL_PROJECT_NAME` | _(none)_ | Force a specific project name when auto-sessions boot |
| `INITIAL_PROJECT_PATH` | _(cwd)_ | Force a specific project root path when auto-sessions boot |
| `INITIAL_CLIENT` | `auto` | Client name recorded on the auto-opened session |
| `ENABLE_AUTO_INGEST` | `true` | Persist user prompts as memories (auto-debounced) |
| `AUTO_INGEST_DEBOUNCE_SECONDS` | `30` | Debounce window per content hash |
| `AUTO_INGEST_MIN_LENGTH` | `20` | Minimum text length to ingest |
| `ENABLE_AUTO_RECALL` | `true` | Attach `auto_recalled` to every project-scoped tool result |
| `AUTO_RECALL_LIMIT` | `5` | Max memories attached per tool call |
| `AUTO_RECALL_MIN_SCORE` | `0` | Minimum score for non-pinned recall hits |
| `AUTO_DETECT_CONVENTIONS` | `true` | Detect project conventions on `start_session` |
| `ENABLE_CWD_WATCHER` | `true` | Auto-rebind to a new project when `process.cwd()` changes |
| `CWD_CHECK_INTERVAL_SECONDS` | `5` | Polling interval for the cwd watcher |
| `ENABLE_FILE_INGEST` | `true` | Enable the `ingest_file(s)` / `extract_*` tools |
| `FILE_INGEST_MAX_BYTES` | `262144` | Hard size cap (bytes) for an ingested file |
| `FILE_INGEST_BINARY_SNIFF` | `true` | Reject binary files before they hit the ingester |
| `AUTO_CURATE_SESSION` | `true` | Run `curate_session` against `AUTO_CURATE_ROOTS` on session start |
| `AUTO_CURATE_ROOTS` | _8 glob patterns_ | Comma-separated glob list of files to curate automatically |
| `CURATE_PIN_BRIEF` | `true` | Pin the synthesized session brief as a memory |
| `RECALL_TYPE_WEIGHTS` | _see below_ | `type:weight` pairs for the type-weighted recall score |
| `RECALL_RECENCY_DECAY` | `0.95` | Per-day retention factor in the recency-decay formula |
> **Note**: `ENABLE_AUTO_INGEST`, `ENABLE_AUTO_RECALL`, and
> `ENABLE_AUTO_SESSION` all default to `true` so the server runs as a fully
> automatic memory layer out of the box. Set any of them to `false` to opt
> out of that specific behavior.
---
## The 45 tools
### Lifecycle (5)
- `start_session` โ open a session against a project (auto-creates if needed).
When called with no arguments, delegates to `AutoSessionManager` and
returns an `auto_recalled` array of relevant memories. Sessions opened
via the auto-curate hook also attach a freshly-pinned session brief
(see `open_session_brief`).
- `get_project_summary` โ counts + recent memories for the active project,
plus `auto_recalled`.
- `list_projects` โ all known projects.
- `notify_project_change` โ explicit signal that the agent has switched
projects (e.g. into a git worktree, multi-repo monorepo, or any case the
cwd watcher can't see). Takes `{cwd?, project_name?, root_path?, reason?}`;
rebinds the active project, opens a fresh session, and returns the new
active project + an `auto_recalled` array.
- `get_active_project` โ return the currently active project and session
(auto-recovers a session if none is active).
### Conventions (4)
- `detect_project_type` โ sniff files at a root path
- `detect_and_save_project_conventions` โ persist conventions on the project row
- `remember_project_pattern` โ save a project-specific pattern as a memory
- `get_project_conventions` โ return saved conventions
### Auto-ingest (2)
- `set_auto_ingest` โ toggle + configure the auto-ingest middleware
- `get_auto_ingest_status` โ current config + counters
### Recall & ingest (2)
- `recall_memories` โ free-text recall. Type-weighted + recency-decayed
(Recall v2): pinned memories always come first, then FTS hits scored by
`RECALL_TYPE_WEIGHTS` and `RECALL_RECENCY_DECAY`. Returns
`RecalledMemory[]` with score and reason.
- `add_user_message` โ convenience tool for the agent to persist every user
prompt as a `conversation`-type memory tagged `user_prompt`. Auto-recall
is also attached.
### Memories (8)
Every project-scoped tool here returns an `auto_recalled` array of relevant
memories, so the agent never needs to issue a separate `recall_memories`
call.
- `add_memory` โ insert with optional type/tags/importance/file paths
- `get_memory` โ fetch a single row (bumps access count)
- `list_memories` โ paginated list with filters
- `search_memories` โ FTS5 + LIKE search
- `update_memory` โ patch fields on an existing memory
- `delete_memory` โ soft-delete (preserved until cleanup)
- `count_memories` โ fast count with filters
- `bulk_add_memories` โ insert many in one call
### Dedup (3)
- `find_duplicates` โ scan project for near-duplicates
- `merge_memories` โ combine two memories (rewires relations, hard-deletes source)
- `set_dedup_policy` โ `reject` / `merge` / `warn` / `off` + threshold
### Relations (5)
- `add_relation` โ directed edge between two memories
- `remove_relation` โ drop edges (optionally filtered by type)
- `get_relations` โ incoming + outgoing edges for a memory
- `get_related_memories` โ BFS up to N hops
- `list_relation_types` โ distinct types currently used
### Tasks (4)
- `create_task` โ new task in the active project
- `list_tasks` โ paginated with status/priority filters
- `update_task_status` โ `pending` / `in_progress` / `done` / `cancelled`
- `delete_task` โ remove by id
### Cleanup (3)
- `cleanup_old_data` โ purge soft-deleted rows + VACUUM/ANALYZE
- `optimize_memories` โ drop orphans + canonicalize JSON
- `set_cleanup_policy` โ adjust interval / grace / enable
### System (3)
- `health_check` โ DB, FTS, active project, scheduler, auto-ingest status
- `get_database_stats` โ table row counts + size
- `get_performance_stats` โ per-tool call counts, latencies, error rates
### Content ingest (4)
Tools for slurping project files into typed memories. Designed to be
called by an agent that wants to "remember" what a plan, an error log,
or a decisions file says without re-reading it on every turn.
- `ingest_file` โ read a single file (binary sniff + size cap), run the
extractors, persist typed memories. Idempotent via content-hash.
- `ingest_files` โ batch over a list of paths in one call.
- `extract_commands` โ pull shell-command lines out of free text and
return them as typed `command` memories (no DB write).
- `extract_errors` โ pull error / stack-trace blocks out of free text
and return them as typed `error` memories (no DB write).
### Session & export (2)
- `open_session_brief` โ return the pinned session brief for the active
project plus recent memory sections and the top 5 v2-recall hits.
Cheap to call; meant to be the first thing the agent reads when it
wakes up in a new session.
- `export_project_doc` โ compose a single Markdown document summarising
the active project (header, brief, conventions, recent memories,
pending tasks, recent errors, frequent commands, recent decisions).
Capped to `FILE_INGEST_MAX_BYTES`. Useful as an `AGENTS.md` source.
---
## Development
```bash
git clone https://github.com/enhx/enhx-memory.git
cd enhx-memory
npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # vitest run (323 tests)
npm run build # tsc โ dist/
npm start # node bin/enhx-memory.mjs (uses dist/)
npm run dev # tsx src/index.ts (no build needed)
```
Test coverage is configured to fail under 80 % line coverage (`vitest --coverage`).
---
## Architecture
```
src/
index.ts # entry โ boots DB, AppContext, McpServer, transport
config.ts # ServerConfig (zod-validated, env-driven)
logging.ts # pino + rotating file
context.ts # AppContext (DI bag)
perf/tracker.ts # per-tool latency tracking
errors.ts # ToolError / DatabaseError / ValidationError
version.ts # reads version from package.json at runtime
util/ # csv, json, signals, time
db/ # DatabaseManager + per-table CRUD (better-sqlite3)
dedup/ # normalize + MinHash + Jaccard
domain/ # MemoryManager, RelationsManager, TasksManager,
# ProjectConventionLearner, AutoSessionManager,
# AutoIngestMiddleware, CleanupScheduler,
# FileIngester, MemoryCurator, Recall v2,
# extractors (commands/errors/decisions/refs)
tools/ # 45 MCP tools across 13 register files
# (lifecycle, conventions, auto-ingest, recall,
# memories, dedup, relations, tasks, cleanup,
# system, ingest, session-brief, export-project)
# + transport wrapper + helpers
migrations/ # 0001_initial.sql (verbatim from Python v0.1.0)
bin/enhx-memory.mjs # CLI launcher
tests/ # vitest suite (323 tests across helpers, db,
# domain, dedup, unit, integration, tools)
```
The full rewrite plan is in [`TS_REWRITE_PLAN.md`](./TS_REWRITE_PLAN.md).
---
## License
MITThis server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing