@sylphie-labs/memory-pkg
v0.10.0
Published
Long-term session memory for Claude Code. Captures every transcript, indexes events in TimescaleDB, and auto-injects relevant history into every new prompt. Multi-tier retrieval with a SQL-only fast path; rationale synthesis compresses turns into searchab
Maintainers
Readme
@sylphie-labs/memory-pkg
Long-term session memory for Claude Code.
Every Claude Code session leaves a transcript JSONL on disk. memory-pkg reads those transcripts, indexes them in TimescaleDB, and auto-injects relevant historical events into every new user prompt. The agent stops asking the same clarifying question twice; the developer stops being the agent's notebook.
Status: 0.3.0 — capture, ingestion, and the lexical fast path (trigram + entity) are production-shape. Embeddings are now computed at ingest and the semantic tier runs as a rescue pass when the fast path is weak. The dormant classifier and Neo4j knowledge-graph tiers were removed in this version. A starter test suite (vitest) covers the core pure logic, the transcript-capture collision fix, and the embedding skip/fallback path.
License
memory-pkg is source-available under the PolyForm Shield 1.0.0 license. In plain English:
- ✅ You can install, use, modify, and self-host it.
- ✅ You can use it inside commercial products you ship to your own customers.
- ❌ You cannot use it to build a competing memory-for-coding-agents product.
If you have a use case and want clarity on whether it's permitted, open an issue.
Install
Two install modes.
Global (recommended for solo dev / cross-repo use):
npm install -g @sylphie-labs/memory-pkgLocal (recommended for teams who want version pinning):
npm install --save-dev @sylphie-labs/memory-pkgYou'll also need TimescaleDB (with pgvector). init --docker writes a docker-compose.memory-pkg.yml for you, or use any TimescaleDB instance you already have.
Quickstart
# 1. Install (global)
npm install -g @sylphie-labs/memory-pkg
# 2. From your repo root
memory-pkg init --docker
# 3. Bring up TimescaleDB
docker compose -f docker-compose.memory-pkg.yml up -d
# 4. Merge the printed settings.json snippet into .claude/settings.json
# 5. Initialize the schema
memory-pkg schema
# 6. Start a Claude Code session — capture, ingest, and injection are now wiredinit installs the capture and injection hooks into .claude/hooks/, patches .mcp.json with the MCP server stanza, copies the temporal-recall skill template into .claude/skills/, and writes the install state to .memory-pkg/state.json so upgrade, status, and uninstall can operate later.
The memory-inject.cjs hook is rendered with the package's absolute path baked in as a fallback. The hook tries (1) MEMORY_PKG_CLI_PATH env var, (2) local node_modules, (3) the baked path — and fails open if none resolve. Re-run init --force after a global Node reinstall.
Lifecycle
memory-pkg init [--local] [--docker] [--force] [--dry-run]
memory-pkg upgrade [--plan] [--confirm] [--force]
memory-pkg status # show install state + drift
memory-pkg doctor [--no-network] # structural checks
memory-pkg uninstall --confirminit is one-time per repo. Writes a state file that subsequent commands read.
upgrade walks the migration graph from state.version to the currently-installed CLI version. Always shows the plan first; --confirm required to apply. Drifted files (modified since install) are skipped with a warning unless --force (which creates .bak.<timestamp> backups).
status is a quick drift report.
doctor runs six structural checks: state file present, version matches, managed files present, MCP stanza registered, hooks parse cleanly, TimescaleDB reachable.
uninstall removes every file recorded in state.json with --confirm. Modified files are backed up to .bak.<timestamp> unless --force.
setupis a deprecated alias forinitand will be removed before 1.0.
How it works
┌─ Claude Code session ────────────────────────────────┐
│ ~/.claude/projects/<sanitized-path>/<sess>.jsonl │
└──┬──────────────────────────────────────────┬───────┘
│ Stop hook reads tail via byte cursor │ UserPromptSubmit
▼ │ hook (fails open)
┌─ .claude/memory/ ──────────────────────┐ │
│ buffer.jsonl │ │
│ cursors/<sess>.json │ │
└──┬─────────────────────────────────────┘ │
│ Ingester (async after Stop) │
▼ │
┌─ TimescaleDB memory_events hypertable ──────┴───────┐
│ GIN trigram on search_text │
│ HNSW cosine on embedding vector(384) │
│ Partial index on (subsystem, ts) │
│ Unique on (session_id, transcript_uuid, ts) │
└──┬──────────────────────────────────────────────────┘
│ Multi-tier retrieval (trigram + entity fast path; embedding rescue)
▼
┌─ <memory-context> block (up to 4 KB) ────────┐
│ Up to 3 prior events ranked by similarity │
│ + recency, surfaced as additionalContext │
└──────────────────────────────────────────────┘The fast path is plain SQL trigram search against a Postgres GIN index, plus an entity tier that extracts identifiers from the prompt and the last 20 lines of the active transcript. For well-formed prompts this short-circuits the rest of the pipeline at score ≥ 0.7. When the fast path is weak, a semantic rescue tier embeds the query (bge-small-en-v1.5) and runs an HNSW cosine KNN against the embeddings computed at ingest — so the cold-start model load is paid only on hard prompts, not every turn. The tier registry lives in src/inject/tiers/index.ts if you fork and want to add your own.
MCP tools
| Tool | What it does |
|---|---|
| searchMemory(query, limit?, sessionId?, eventType?, since?) | Trigram fuzzy search ranked by similarity and recency |
| getMemoryContext(eventId, before?, after?) | Scale forward/backward in time around an event |
| unwindFromEvent(eventId, limit?) | Replay every event in the session from start up to the anchor |
| getSessionTimeline(sessionId, eventType?, limit?) | Full chronological dump of one session |
Rationale synthesis
Rationale synthesis compresses each turn into a 2–3 sentence "why" event, so future fuzzy searches for "why did we change X?" match the reasoning instead of the actions. The installed Stop hook runs it automatically — chained after ingest, in the background — so it costs one Haiku call per new turn, is idempotent, and is amortized over every future retrieval. Uses the local claude CLI under Max OAuth; no API key consumed in the default setup.
You can also run it on demand:
npx memory-pkg rationale --limit 50CLI reference
# Lifecycle
memory-pkg init [--local] [--docker] [--force] [--dry-run]
memory-pkg upgrade [--plan] [--confirm] [--force] [--verbose]
memory-pkg status
memory-pkg doctor [--no-network]
memory-pkg uninstall --confirm [--force] [--dry-run]
# Memory operations
memory-pkg schema # create/update the hypertable + indexes
memory-pkg ingest # flush buffer.jsonl to TimescaleDB
memory-pkg search "<query>" # fuzzy search the memory store
memory-pkg context <eventId> # scale around an event
memory-pkg unwind <eventId> # replay session up to an event
memory-pkg timeline <sessionId> # full session dump
memory-pkg rationale # synthesize turn rationales
memory-pkg backfill-subsystems # re-derive subsystem tags
memory-pkg backfill-embeddings # compute embeddings for legacy rows
memory-pkg inject "<prompt>" # dry-run the injection pipeline
memory-pkg tune # summarize the rationale-log telemetrymemory-pkg-mcp runs the MCP server directly (Claude Code launches it for you via .mcp.json).
Configuration
| Variable | Default | Purpose |
|---|---|---|
| MEMORY_PKG_PG_HOST | localhost | Postgres host |
| MEMORY_PKG_PG_PORT | 5432 | Postgres port |
| MEMORY_PKG_PG_USER | memory-pkg | DB user |
| MEMORY_PKG_PG_PASSWORD | memory-pkg-local | DB password |
| MEMORY_PKG_PG_DATABASE | memory | DB name |
| MEMORY_PKG_REPO_ANCHOR | (auto via git rev-parse --show-toplevel) | Absolute path of your repo root for subsystem derivation |
| MEMORY_PKG_HOOK_TIMEOUT_MS | 30000 | Injection-hook timeout (always fails open on overrun) |
| MEMORY_PKG_EMBED_MODEL | Xenova/bge-small-en-v1.5 | Embedding model used by the embedding tier |
| MEMORY_PKG_RATIONALE_MODEL | claude-haiku-4-5-20251001 | Model for rationale synthesis |
Legacy DRIFT_MEMORY_* env vars are still recognized for retrieval-tier toggles; see source for the full list.
What this doesn't do (yet)
- Cross-project memory federation
- Continuous aggregates / compression on old TimescaleDB chunks
- Embedding backfill for rows ingested before 0.2.0 (new events embed at ingest;
backfill-embeddingsfills legacy rows on demand) - Full test coverage (a starter vitest suite exists — run
npm test; the.cjscapture hook and merger remain uncovered)
See also
@sylphie-labs/codebase-pkg— companion package addressing the structural side of agent forgetting (codebase knowledge graph).memory-pkghandles the episodic side (the work itself).
