RohiRIK/OpenLtm

Long-Term Memory plugin for Claude Code — semantic search, context injection, session learning

TypeScript

26

122 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

OpenLTM — local SQLite long-term memory for coding agents (MIT) (r/SideProject)

Disclosure: I’m the author. I built OpenLTM because I wanted project memory that survives session resets without standing up a separate service. One SQLite file on disk: FTS5 then vector recall, decay so old noise fades (importance 5 stays permanent). Hosts: Claude Code (marketplace plugin),…

2

Sep 28, 2026

README

OpenLTM — Long-Term Memory for AI coding agents, now open source

OpenLTM

You explained your auth layer once. Why does Claude ask again tomorrow?

Long-Term Memory for AI coding agents — Claude Code, OpenCode, Pi, and OpenClaw

Version License Runtime Database Claude Code MCP

Persistent semantic memory that survives every session, every update, every compaction.


Now open source

OpenLTM began as a private memory layer for one agent. It is now MIT licensed, and the whole engine is on your disk to read, fork, and break.

  • One engine, four hosts. @rohirik/openltm-core holds the memory logic; each host gets a thin adapter.
  • You own the file. A local SQLite database. No account, no dashboard, no vendor copy.
  • Everything is hackable. Hooks, skills, janitor providers, the graph visualizer. All of it in the open.

Migrating from an earlier install? The marketplace is now RohiRIK/OpenLtm and the plugin is openltm. Your existing memory database carries over.


Read this before you store anything

The database is local. The embedding provider is not, by default.

Semantic search needs vectors, and the default embedding provider is Google Gemini (packages/openltm-core/src/embeddings.ts:47). Memory text is sent there to be turned into numbers. The janitor's LLM providers — Anthropic, Cohere, Gemini — receive memory content for the same reason.

There is no telemetry and no analytics in any configuration. That part is unconditional. But "no cloud" is not a claim this project makes, and an earlier version of this README made it.

Two environment variables put everything back on your machine:

export LTM_EMBED_PROVIDER=ollama     # embeddings stay local
export LTM_LLM_PROVIDER=ollama       # janitor summaries stay local

Full detail, including which write paths scrub secrets and which don't: Security notes.


The philosophy

Memory should be automatic. Hooks do the work. The session-end hook extracts patterns, the session-start hook injects them back. You shouldn't have to remember to remember.

Decay is a feature, not a bug. A gotcha from six months ago that you never revisited probably no longer applies. Set importance: 5 and a memory never ages out. Everything else fades at a rate set by how confident you were and how often it proved true (janitor/decay.ts).

Semantic over keyword. FTS5 full-text search runs first. If it returns nothing, vector embeddings kick in. You search by meaning — "how we handle async errors" finds the memory even if you never wrote those words.

Own your data. A file on your disk. When you leave the project, so does everything you taught it.


What you get

CapabilityWhat it actually doesWhere it lives
RecallFTS5 first, vector KNN as fallback — search by meaning, not exact wordsopenltm-core/src/recall/ · src/vec/index.ts
LearnStores a memory with category, importance, and confidence; scrubs secrets on this pathopenltm-core/src/db.ts:561
InjectRenders the top-ranked memories as context at session startopenltm-core/src/context.ts:49
DecayAges memories by importance × confidence; importance: 5 is permanentopenltm-core/src/janitor/decay.ts
GraphTraverses relations between memories and builds a reasoning chainopenltm-core/src/graph.ts:69
VisualizeA browser explorer over the live database, with janitor controlssrc/graph-server.ts · graph-app/
Extensionssqlite-vec and Honker loaded from disk, with a working fallback when absentopenltm-core/src/extensions.ts:160
DeduplicateMerges memories the janitor judges to be the same thingopenltm-core/src/janitor/dedup.ts

Four hosts, one database

The engine is one package. Each host gets an adapter, and all of them open the same openltm.db — so a gotcha learned in Claude Code is already there when you open OpenCode.

HostAdapterInstall
Claude Code.claude-plugin/claude plugin install openltm
OpenCode@rohirik/opencode-ltmbunx @rohirik/openltm-core --opencode
Pi@rohirik/pi-ltmbunx @rohirik/openltm-core --pi
OpenClaw@rohirik/openclaw-ltmsee docs/11-publishing.md

Plus a native Python plugin for Hermes — separate implementation, same database, same schema.


Install

claude plugin marketplace add https://github.com/RohiRIK/OpenLtm
claude plugin install openltm

Restart Claude Code. Five hooks auto-wire, six commands load, seven skills activate, and your openltm.db migrates or creates itself.

bunx (no clone)

bunx @rohirik/openltm-core                        # auto-detect installed hosts
bunx @rohirik/openltm-core --pi                   # experimental Pi adapter
bunx @rohirik/openltm-core --dry-run --claude     # show me everything you'd write, write nothing

Dev / git clone

git clone https://github.com/RohiRIK/OpenLtm ~/Projects/OpenLtm
cd ~/Projects/OpenLtm && bash install.sh

What that installer does outside this repo. It writes ~/.claude.json, creates ~/.claude/settings.json if it isn't there, and adds mcp__plugin_openltm_memory to the permissions.allow list so the memory tools stop prompting for approval. That's a deliberate widening of your agent's tool permissions, and it's the kind of thing an installer should tell you about. Read scripts/install-wiring.ts first if you'd rather look before running.


Quick start

Start a new session. Context is injected at the top automatically.

/openltm:memory recall auth       — what do we know about auth in this project?
/openltm:memory learn <insight>   — save something worth keeping
/openltm:health                   — memory health + decay summary
/openltm:project init             — set a goal for the current project

For headless agents

Slash commands and the ltm_* tools only exist inside an agent TUI. From a plain shell — scripts, cron, CI:

bunx @rohirik/openltm-core memory learn --text "Docker Hub rate limits unauthenticated pulls" \
  --category gotcha --importance 4 --project homelab --json
bunx @rohirik/openltm-core memory recall --query "docker rate limit" --json
bunx @rohirik/openltm-core memory forget --id 42 --reason "outdated"
bunx @rohirik/openltm-core memory context --project homelab

Any MCP-capable host can run the full server directly:

bunx @rohirik/openltm-core mcp-serve

Security notes

Three things worth knowing before this holds anything you care about.

Secret scrubbing is not applied on every write path. learn() scrubs before storing (packages/openltm-core/src/db.ts:561). The janitor's promote and dedup paths, and context capture, write to memories and context_items without it. The scrubber also fails open — on an internal error it returns the original text unchanged (secretsScrubber.ts:101) — and it is a pattern denylist, not a guarantee. Stored memory is not the same as redacted memory.

LTM_SQLITE_LIB and LTM_HONKER_EXT load native code. Both are filesystem paths handed to a SQLite extension loader (extensions.ts:132,144). There is no allowlist and no signature check. Set them only to paths you trust, or turn the loaders off with LTM_DISABLE_VEC=1 and LTM_DISABLE_HONKER=1.

npm publishing is tokenless. Releases authenticate through GitHub OIDC trusted publishing with provenance. No NPM_TOKEN is stored in this repository.


Go deeper

I want to…Read
Get running in five minutesQuickstart
See every install optionInstallation
Use every command and its flagsCommands
Tune decay, injection, embedding behaviorConfiguration
See how it works under the hoodHow It Works · Architecture
Understand the schema and data modelDB Spec
See all hooks, skills, and MCP toolsHooks · Skills · MCP Tools
Publish a releasePublishing
Fix a problemTroubleshooting
See where it's goingPRD · Roadmap
Contribute a changeContributing
Check what changedChangelog

Environment variables

VariablePurpose
LTM_DB_PATHWhere the SQLite file lives. Overrides the default location.
LTM_EMBED_PROVIDEREmbedding provider. gemini by default; set ollama to stay local.
LTM_LLM_PROVIDERProvider for janitor summaries.
LTM_DISABLE_VECTurn off the sqlite-vec loader; falls back to JS-cosine.
LTM_DISABLE_HONKERTurn off the Honker loader.
LTM_SQLITE_LIBExplicit path to a SQLite library. Loads native code.
LTM_HONKER_EXTExplicit path to the Honker extension. Loads native code.
LTM_CHANNELHonker pub-sub channel.
LTM_BACKUP_RETENTIONHow many rotated backups to keep.

License

MIT — RohiRIK


Built for agents that forget, and shouldn't.

ai
claude-code
long-term-memory
mcp
memory
plugin

RohiRIK/OpenLtm

Long-Term Memory plugin for Claude Code — semantic search, context injection, session learning

TypeScript

26

122 commits

updated Sep 28, 2026

See the code

See what people are saying

SourceMessageScoreDate

OpenLTM — local SQLite long-term memory for coding agents (MIT) (r/SideProject)

Disclosure: I’m the author. I built OpenLTM because I wanted project memory that survives session resets without standing up a separate service. One SQLite file on disk: FTS5 then vector recall, decay so old noise fades (importance 5 stays permanent). Hosts: Claude Code (marketplace plugin),…

2

Sep 28, 2026

README

OpenLTM — Long-Term Memory for AI coding agents, now open source

OpenLTM

You explained your auth layer once. Why does Claude ask again tomorrow?

Long-Term Memory for AI coding agents — Claude Code, OpenCode, Pi, and OpenClaw

Version License Runtime Database Claude Code MCP

Persistent semantic memory that survives every session, every update, every compaction.


Now open source

OpenLTM began as a private memory layer for one agent. It is now MIT licensed, and the whole engine is on your disk to read, fork, and break.

  • One engine, four hosts. @rohirik/openltm-core holds the memory logic; each host gets a thin adapter.
  • You own the file. A local SQLite database. No account, no dashboard, no vendor copy.
  • Everything is hackable. Hooks, skills, janitor providers, the graph visualizer. All of it in the open.

Migrating from an earlier install? The marketplace is now RohiRIK/OpenLtm and the plugin is openltm. Your existing memory database carries over.


Read this before you store anything

The database is local. The embedding provider is not, by default.

Semantic search needs vectors, and the default embedding provider is Google Gemini (packages/openltm-core/src/embeddings.ts:47). Memory text is sent there to be turned into numbers. The janitor's LLM providers — Anthropic, Cohere, Gemini — receive memory content for the same reason.

There is no telemetry and no analytics in any configuration. That part is unconditional. But "no cloud" is not a claim this project makes, and an earlier version of this README made it.

Two environment variables put everything back on your machine:

export LTM_EMBED_PROVIDER=ollama     # embeddings stay local
export LTM_LLM_PROVIDER=ollama       # janitor summaries stay local

Full detail, including which write paths scrub secrets and which don't: Security notes.


The philosophy

Memory should be automatic. Hooks do the work. The session-end hook extracts patterns, the session-start hook injects them back. You shouldn't have to remember to remember.

Decay is a feature, not a bug. A gotcha from six months ago that you never revisited probably no longer applies. Set importance: 5 and a memory never ages out. Everything else fades at a rate set by how confident you were and how often it proved true (janitor/decay.ts).

Semantic over keyword. FTS5 full-text search runs first. If it returns nothing, vector embeddings kick in. You search by meaning — "how we handle async errors" finds the memory even if you never wrote those words.

Own your data. A file on your disk. When you leave the project, so does everything you taught it.


What you get

CapabilityWhat it actually doesWhere it lives
RecallFTS5 first, vector KNN as fallback — search by meaning, not exact wordsopenltm-core/src/recall/ · src/vec/index.ts
LearnStores a memory with category, importance, and confidence; scrubs secrets on this pathopenltm-core/src/db.ts:561
InjectRenders the top-ranked memories as context at session startopenltm-core/src/context.ts:49
DecayAges memories by importance × confidence; importance: 5 is permanentopenltm-core/src/janitor/decay.ts
GraphTraverses relations between memories and builds a reasoning chainopenltm-core/src/graph.ts:69
VisualizeA browser explorer over the live database, with janitor controlssrc/graph-server.ts · graph-app/
Extensionssqlite-vec and Honker loaded from disk, with a working fallback when absentopenltm-core/src/extensions.ts:160
DeduplicateMerges memories the janitor judges to be the same thingopenltm-core/src/janitor/dedup.ts

Four hosts, one database

The engine is one package. Each host gets an adapter, and all of them open the same openltm.db — so a gotcha learned in Claude Code is already there when you open OpenCode.

HostAdapterInstall
Claude Code.claude-plugin/claude plugin install openltm
OpenCode@rohirik/opencode-ltmbunx @rohirik/openltm-core --opencode
Pi@rohirik/pi-ltmbunx @rohirik/openltm-core --pi
OpenClaw@rohirik/openclaw-ltmsee docs/11-publishing.md

Plus a native Python plugin for Hermes — separate implementation, same database, same schema.


Install

claude plugin marketplace add https://github.com/RohiRIK/OpenLtm
claude plugin install openltm

Restart Claude Code. Five hooks auto-wire, six commands load, seven skills activate, and your openltm.db migrates or creates itself.

bunx (no clone)

bunx @rohirik/openltm-core                        # auto-detect installed hosts
bunx @rohirik/openltm-core --pi                   # experimental Pi adapter
bunx @rohirik/openltm-core --dry-run --claude     # show me everything you'd write, write nothing

Dev / git clone

git clone https://github.com/RohiRIK/OpenLtm ~/Projects/OpenLtm
cd ~/Projects/OpenLtm && bash install.sh

What that installer does outside this repo. It writes ~/.claude.json, creates ~/.claude/settings.json if it isn't there, and adds mcp__plugin_openltm_memory to the permissions.allow list so the memory tools stop prompting for approval. That's a deliberate widening of your agent's tool permissions, and it's the kind of thing an installer should tell you about. Read scripts/install-wiring.ts first if you'd rather look before running.


Quick start

Start a new session. Context is injected at the top automatically.

/openltm:memory recall auth       — what do we know about auth in this project?
/openltm:memory learn <insight>   — save something worth keeping
/openltm:health                   — memory health + decay summary
/openltm:project init             — set a goal for the current project

For headless agents

Slash commands and the ltm_* tools only exist inside an agent TUI. From a plain shell — scripts, cron, CI:

bunx @rohirik/openltm-core memory learn --text "Docker Hub rate limits unauthenticated pulls" \
  --category gotcha --importance 4 --project homelab --json
bunx @rohirik/openltm-core memory recall --query "docker rate limit" --json
bunx @rohirik/openltm-core memory forget --id 42 --reason "outdated"
bunx @rohirik/openltm-core memory context --project homelab

Any MCP-capable host can run the full server directly:

bunx @rohirik/openltm-core mcp-serve

Security notes

Three things worth knowing before this holds anything you care about.

Secret scrubbing is not applied on every write path. learn() scrubs before storing (packages/openltm-core/src/db.ts:561). The janitor's promote and dedup paths, and context capture, write to memories and context_items without it. The scrubber also fails open — on an internal error it returns the original text unchanged (secretsScrubber.ts:101) — and it is a pattern denylist, not a guarantee. Stored memory is not the same as redacted memory.

LTM_SQLITE_LIB and LTM_HONKER_EXT load native code. Both are filesystem paths handed to a SQLite extension loader (extensions.ts:132,144). There is no allowlist and no signature check. Set them only to paths you trust, or turn the loaders off with LTM_DISABLE_VEC=1 and LTM_DISABLE_HONKER=1.

npm publishing is tokenless. Releases authenticate through GitHub OIDC trusted publishing with provenance. No NPM_TOKEN is stored in this repository.


Go deeper

I want to…Read
Get running in five minutesQuickstart
See every install optionInstallation
Use every command and its flagsCommands
Tune decay, injection, embedding behaviorConfiguration
See how it works under the hoodHow It Works · Architecture
Understand the schema and data modelDB Spec
See all hooks, skills, and MCP toolsHooks · Skills · MCP Tools
Publish a releasePublishing
Fix a problemTroubleshooting
See where it's goingPRD · Roadmap
Contribute a changeContributing
Check what changedChangelog

Environment variables

VariablePurpose
LTM_DB_PATHWhere the SQLite file lives. Overrides the default location.
LTM_EMBED_PROVIDEREmbedding provider. gemini by default; set ollama to stay local.
LTM_LLM_PROVIDERProvider for janitor summaries.
LTM_DISABLE_VECTurn off the sqlite-vec loader; falls back to JS-cosine.
LTM_DISABLE_HONKERTurn off the Honker loader.
LTM_SQLITE_LIBExplicit path to a SQLite library. Loads native code.
LTM_HONKER_EXTExplicit path to the Honker extension. Loads native code.
LTM_CHANNELHonker pub-sub channel.
LTM_BACKUP_RETENTIONHow many rotated backups to keep.

License

MIT — RohiRIK


Built for agents that forget, and shouldn't.

ai
claude-code
long-term-memory
mcp
memory
plugin

Languages

TypeScript

77.9%

Python

12.7%

JavaScript

8.8%