OpenLynx — persistent semantic memory for Claude Code, Codex CLI and other coding agents. Auto-saves and recalls conversation history across sessions.
Project description
lynx-memory
Persistent, semantic, long-term memory for Claude Code. Conversations are auto-saved across sessions and the most relevant snippets are injected into context whenever you start a new prompt — no special syntax, no "remember this" phrasing required.
You : What can I do tomorrow if the weather's nice — maybe walk the dog?
Claude : Since you've got Dandan (your golden Border Collie) who needs a lot
of exercise, try a long walk, frisbee, or a bike ride with him
tagging along… 🐶
(you never mentioned Dandan or owning a dog — memory recalled it
from a past chat)
How it works
Three Claude Code hooks plus a small Python service:
| Hook | What it does |
|---|---|
UserPromptSubmit |
Embeds your prompt and injects the top-K most similar prior turns (a turn's summary is injected instead of raw prose when one exists). |
Stop |
Persists the current turn into SQLite + Chroma, then spawns a detached summarizer that calls the configured API (OpenAI / DeepSeek / Qwen) to extract long-term memories. |
SessionEnd |
Calls the configured API to produce a coarse memory summary of the whole session. |
Storage:
- SQLite — source of truth for raw turns, per-turn summaries, and session summaries.
- Chroma — local vector index over turns + summaries.
- Voyage AI (
voyage-3.5) — embeddings. - OpenAI (
gpt-4o-mini, default), DeepSeek (deepseek-chat), or Qwen (qwen-turbo) — per-turn and session summarization. Any one ofOPENAI_API_KEY,DEEPSEEK_API_KEY, orQWEN_API_KEYis enough.
Install
pip install openlynx
lynx-memory init
init will:
- Create the shared OpenLynx home at
~/.openlynx/. - Prompt for your
VOYAGE_API_KEY(get one free at https://www.voyageai.com/). - Write the default
.env(MIN_SCORE=0.7,SUMMARY_ENABLED=1,SUMMARY_BACKEND=auto) — setOPENAI_API_KEY,DEEPSEEK_API_KEY, orQWEN_API_KEYto enable per-turn summarization (configurable later via the Web UI ⚙ Settings panel). - Back up your existing
~/.claude/settings.jsonand add the three hooks. - Link shared commands and the OpenLynx skill into supported host directories.
- Print verification steps.
If a legacy ~/.claude/lynx-memory/ store exists and ~/.openlynx/ does not,
init migrates it to ~/.openlynx/ before installing hooks. If both exist,
OpenLynx uses ~/.openlynx/ and leaves the legacy directory untouched.
The shared home is host-neutral:
~/.openlynx/
.env
db/
commands/
skills/
Claude Code and Codex keep their own hook configuration files, but reusable
OpenLynx files are linked from this shared home into host directories such as
~/.claude/commands/, ~/.claude/skills/, ~/.codex/commands/, and
~/.codex/skills/.
Then open a fresh Claude Code session, chat for a few turns, and run:
lynx-memory status
You should see turns and chroma_turns counters going up.
Codex CLI (cross-host memory)
Same memory store, also wired into Codex CLI:
lynx-memory init --target codex # or --target all to install both
This writes ~/.codex/hooks.json, sets [features] hooks = true in
~/.codex/config.toml, and registers three hooks (UserPromptSubmit →
inject, Stop → persist, SessionStart → summarize the previous session,
since Codex has no SessionEnd event).
Codex's additionalContext field is fully respected, so retrieved memory is
injected exactly like in Claude Code. Restart any running codex process for
hooks to take effect — they're loaded at session start.
A turn typed in Claude Code can be recalled inside Codex (and vice versa)
because both write to the same SQLite + Chroma store at ~/.openlynx/.
CLI
| Command | What it does |
|---|---|
lynx-memory init |
Install hooks, slash commands, and skill links (--goal "…" sets a global goal). |
lynx-memory init-project |
Create a .lynx-memory/ marker in cwd to enable project-level storage (--goal "…"). |
lynx-memory status |
Show data dir, hook registration, DB stats, and the active goal. |
lynx-memory goal |
View/set/clear the per-scope goal (goal show | set "…" | clear, with --scope). |
lynx-memory daily |
Summarize a project's turns for today, optionally push to your phone (--notify, --all, --project, --since-hours). |
lynx-memory doctor |
Verify Python, deps, API key, and settings.json. |
lynx-memory merge |
Merge memory between the project and global stores (--from / --to, with --dry-run). |
lynx-memory delete |
Permanently delete memory for a scope (--scope project|global|both, double confirm). |
lynx-memory uninstall |
Remove hooks, slash commands, and skill links (keeps your data). |
Goals
A goal is an optional, per-scope (project or global) statement of what
you're working toward. Set one at install time (lynx-memory init /
init-project prompt for it, or pass --goal "…") or any time afterwards:
lynx-memory goal set "Ship the v2 billing API and migrate existing customers"
lynx-memory goal show # project + global goals, gating state
lynx-memory goal clear # active scope, asks to confirm
When a goal is set, OpenLynx changes two behaviors for that store:
- Storage gating — before a turn is persisted, the configured summarization
LLM (OpenAI / DeepSeek / Qwen) judges whether it is relevant to the goal.
Turns judged irrelevant never enter the database. The judgement is strict
by default and fails open: if no LLM key is configured or the call errors
out, the turn is stored, so memory is never lost on a hiccup. Every dropped
turn is logged to
db/hook.log. - Goal-focused summaries — per-turn and per-session summaries are steered to prioritise information that advances the goal.
With no goal set, memory behaves exactly as before (every turn stored and
summarized). Changing a goal does not delete already-stored turns — it only
affects which future turns are kept. Tunables (.env):
| Variable | Default | Purpose |
|---|---|---|
GOAL_GATING_ENABLED |
1 |
Set 0 to keep storing every turn even with a goal |
GOAL_STRICTNESS |
strict |
loose | balanced | strict |
GOAL_JUDGE_TIMEOUT |
8 |
Seconds for the relevance LLM call (0 = none) |
Daily digest
lynx-memory daily summarizes a project's turns for the day (since local
midnight, or --since-hours N) into a short "what I did today" recap — reusing
each turn's existing summary when present, and steering toward the goal if one
is set. It prints by default; --notify pushes it to your phone.
lynx-memory daily # print today's recap for cwd's project
lynx-memory daily --project ~/code/app --notify # build + push for one project
lynx-memory daily --all --notify # aggregate EVERY store on the machine
--all scans for every store on the machine (the global store + each project's
.lynx-memory/) and produces one cross-project recap grouped by project — so
you see everything you did today, not just one repo. The scan walks $HOME
(override roots with LYNX_SCAN_ROOTS, depth with LYNX_SCAN_DEPTH), skipping
big/noisy directories.
Notifier backends (auto-detected from env, or forced via DAILY_NOTIFY_BACKEND):
| Backend | Env | Notes |
|---|---|---|
serverchan |
SERVERCHAN_SENDKEY |
WeChat push via ServerChan (Server酱) |
webhook |
DAILY_WEBHOOK_URL |
Generic JSON POST {"title","body"} |
To run it automatically every night, schedule it with your OS scheduler. On
macOS, a launchd LaunchAgent with StartCalendarInterval (e.g. Hour 21)
running lynx-memory daily --all --notify is the most reliable option; wrap it
in caffeinate -i and add a pmset repeat wakeorpoweron a couple of minutes
earlier so it fires even while the Mac sleeps. On Linux, a cron entry works.
Slash commands
lynx-memory init also installs six global slash commands into
~/.openlynx/commands/ and links them into host command directories such as
~/.claude/commands/ and ~/.codex/commands/:
| Command | What it does |
|---|---|
/lynx-memory-status |
Show current scope (project vs global) with stats for both. |
/lynx-memory-goal |
View or set the per-scope goal (gates storage, focuses summaries). |
/lynx-memory-pull-global |
Merge global memory into the current project (global → project). |
/lynx-memory-push-global |
Merge current project memory into global (project → global). |
/lynx-memory-delete |
Delete memory with mandatory double confirm (DELETE + y). |
/lynx-memory-history |
Open a local Web UI to browse, search, tag, and delete turns. |
Each of these runs lynx-memory status / merge --dry-run first and asks for
your approval before any write or destructive action.
Skills
lynx-memory init installs the bundled OpenLynx skill into
~/.openlynx/skills/openlynx/ and links it into supported host skill
directories such as ~/.claude/skills/openlynx and ~/.codex/skills/openlynx.
Web UI
Type /lynx-memory-history in Claude Code (or run lynx-memory web) to launch
a local FastAPI + React UI on 127.0.0.1. The page opens automatically in your
browser and lets you:
- Switch between project and global scopes.
- Page through every saved turn.
- Search by keyword (SQL
LIKE) or semantic similarity (Voyage embeddings). - Tag turns (e.g.
#work,#personal) and filter by tag. - Delete a single turn (also clears its embedding from Chroma).
- See the per-turn summary above each turn, with a one-click button to (re)generate it on demand.
- Click the ⚙ gear icon (top-right) to open the Settings panel and configure everything in-browser: API keys, summary backend (OpenAI / DeepSeek / Qwen), model, Top-K, min score, and retrieval scope — saved directly to
~/.openlynx/.env.
Usage
# default — listens on http://127.0.0.1:9527 and opens your browser
lynx-memory web
# pick a different port
lynx-memory web --port 8080
# or let the OS assign a free port
lynx-memory web --port 0
# don't auto-open the browser (useful in headless / SSH sessions)
lynx-memory web --no-open
| Action | What happens on disk |
|---|---|
| Delete a turn | Row removed from SQLite turns and turn_tags; embedding removed from Chroma. |
| Add a tag | Inserted into SQLite tags (created on demand) and turn_tags. |
| Remove a tag | Row removed from turn_tags; orphaned tag is GC'd from tags. |
| Search (keyword) | SQL LIKE over user_msg and assistant_msg — no embedding call. |
| Search (semantic) | One Voyage embedding per query, then top-K from Chroma. |
| Regenerate summary | One API call (per SUMMARY_BACKEND); writes summary / summary_model / summary_ts back into the turns row. |
The server only binds to 127.0.0.1. Press Ctrl+C to stop it.
Project-level vs global
Memory is global by default. Run this in a project root:
cd ~/code/my-project
lynx-memory init-project
It creates a .lynx-memory/ marker. As long as your cwd is inside that project,
memory transparently switches to the project-level store at
<project>/.lynx-memory/db/, isolated from the global one at ~/.openlynx/.
Use /lynx-memory-status to inspect the active scope, and
/lynx-memory-pull-global / /lynx-memory-push-global to move history between
the two layers.
Configuration
All optional, set in ~/.openlynx/.env:
| Variable | Default | Purpose |
|---|---|---|
VOYAGE_API_KEY |
— | Required for embeddings |
TOP_K |
5 |
Max memories injected per prompt |
MIN_SCORE |
0.7 |
Cosine similarity floor (0–1) |
SUMMARY_ENABLED |
1 |
Set 0/false to disable per-turn summarization |
SUMMARY_BACKEND |
auto |
auto → first provider with a key (OpenAI → DeepSeek → Qwen); force with openai, deepseek, or qwen |
OPENAI_API_KEY |
— | Required when SUMMARY_BACKEND=openai |
OPENAI_MODEL |
gpt-4o-mini |
OpenAI model used for summarization |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
Override for OpenAI-compatible endpoints |
DEEPSEEK_API_KEY |
— | Required when SUMMARY_BACKEND=deepseek |
DEEPSEEK_MODEL |
deepseek-chat |
DeepSeek model used for summarization |
DEEPSEEK_BASE_URL |
https://api.deepseek.com/v1 |
Override for the DeepSeek endpoint |
QWEN_API_KEY |
— | Required when SUMMARY_BACKEND=qwen (DASHSCOPE_API_KEY also accepted) |
QWEN_MODEL |
qwen-turbo |
Qwen model used for summarization |
QWEN_BASE_URL |
DashScope compatible-mode URL | Override for the Qwen/DashScope endpoint |
LYNX_MEMORY_DIR |
~/.openlynx |
Where SQLite + Chroma live |
See Goals and Daily digest for their own env vars.
Optional: MCP server
You can also expose memory as MCP tools for Claude Code (search_memory,
list_recent, stats, forget). Add to ~/.claude.json or .mcp.json:
{
"mcpServers": {
"lynx-memory": {
"command": "lynx-memory-mcp"
}
}
}
Uninstall
lynx-memory uninstall # remove hooks + slash commands + skill links
lynx-memory delete --scope global # delete the global store (confirms)
# or
rm -rf ~/.openlynx # nuke directly (irreversible)
Privacy
- All data stays on your machine in
~/.openlynx/. - Outbound calls: Voyage AI for embeddings (your prompt text); OpenAI, DeepSeek, or Qwen for per-turn and session summaries (requires an API key — set via
.envor the Web UI ⚙ Settings panel). - Set
SUMMARY_ENABLED=0if you don't want per-turn summaries to leave the box. - Set
LYNX_MEMORY_DIRto encrypt at rest with whatever filesystem-level encryption your OS provides.
Roadmap
-
Project-level / global dual-layer storage Global by default; auto-switches to project-level when a
.lynx-memory/marker is found by walking up from cwd, so histories from different projects don't bleed into each other. Runlynx-memory init-projectin a project root to create the marker. Search supportsscope=auto|project|global|merged(hooks viaLYNX_MEMORY_SCOPEenv; MCP tools accept ascopeargument). -
Codex CLI — same hooks + shared store; use
lynx-memory init --target codex(or--target all). See Codex CLI above. -
Local Web UI memory browser A local FastAPI + React UI with paging, keyword / semantic search, single-turn deletion, and tagging (e.g.
#work,#personal). Launch with/lynx-memory-history(orlynx-memory web); the page exposes both project-level and global histories with a one-click scope toggle. -
Goals & daily digest — set a per-scope goal to gate storage and focus summaries;
lynx-memory dailyrecaps your day (per project or--all) and can push it to your phone. -
Other CLIs (Cursor, Gemini CLI, …) — not integrated yet. Cursor: blocked until a stable hooks surface ships (we plan to adopt it once available); meanwhile MCP-only workflows remain possible where applicable.
-
Unified multi-client installer A future
lynx-memory install --client <name>to write MCP configs in one shot, with rules templates for consistent recall across clients that support them. -
Import / export & cross-device sync
lynx-memory export/importfor JSONL backup and restore; placedb/in iCloud / Dropbox / a Git repo, or use a built-inlynx-memory syncsubcommand to share memory across machines. -
Richer automatic tagging (precise vs associative) Stronger auto-labeling for turns, with a switchable precise mode (tight, literal, auditable tags) vs associative mode (broader links and softer clusters to improve semantic recall).
-
Recall modes & tunable ranking Let users steer what gets injected beyond raw similarity — combine signals such as retrieval / hit count, relevance score, and recency, with presets or manual weighting so priority matches your workflow.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openlynx-0.5.0.tar.gz.
File metadata
- Download URL: openlynx-0.5.0.tar.gz
- Upload date:
- Size: 182.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a64b0c3373aa60d2a8d811dd53c9a9d7948a21d04e4fefe40c683de0578e25b8
|
|
| MD5 |
a60d7a3ae11b1baec809ee419425d2cd
|
|
| BLAKE2b-256 |
3f87fc4583e190bd79d037ce90aae42485045f6860e794f786a720c01b83daf3
|
File details
Details for the file openlynx-0.5.0-py3-none-any.whl.
File metadata
- Download URL: openlynx-0.5.0-py3-none-any.whl
- Upload date:
- Size: 189.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a794999fa9e5deb77412a737367702aa06a7407b47f452a87646598e363e1706
|
|
| MD5 |
5007ac19020192e09f9aa2332357b0ce
|
|
| BLAKE2b-256 |
108a962f99196094929715c6213c5bc398a501209198815d5ec55a56025a8f77
|