Skip to main content
Glama
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

MIT