Repo: 👉 github.com/bayuh85/atmo-mem (MIT, pure Go, no CGO)
The problem
Every new coding-agent session starts with amnesia. You spent yesterday's session with Claude Code working out why the auth refresh was flaky, decided on an approach, and agreed on a few project conventions. Today you open a fresh session and the agent has forgotten all of it. Either you explain it again, or you keep a hand-maintained notes file and hope the agent reads it.
What I wanted was boring: a local place where the agent can write down decisions and fixes as it works, and look them up at the start of the next session. No hosted service, no Docker, no Python environment. Just one binary.
What atmo-mem does
atmo-mem is persistent cross-session memory for coding agents, shipped as one static binary.
- It speaks the Model Context Protocol over stdio, so Claude Code, Cursor, Cline, and other MCP clients can use it.
- The agent saves decisions, bugfixes, and facts while it works.
- A later session searches and recalls them from a local SQLite database with an FTS5 full-text index.
- At the end of a session, atmo-mem can compress the notes into a summary with any OpenAI-compatible model (default:
zai/glm-5.3).
Privacy-wise, the important part is: save, search, and recall stay on disk. Only session_summarize calls the network.
SQLite is compiled in through modernc.org/sqlite (pure Go, FTS5 included), so you don't need CGO or a system SQLite library.
Quickstart
You need a Go toolchain. Clone and build:
git clone https://github.com/bayuh85/atmo-mem.git
cd atmo-mem
CGO_ENABLED=0 go build -o atmo-mem ./cmd/atmo-mem
Move the atmo-mem binary somewhere on your PATH, then check it:
atmo-mem version
The README also describes a
go installcommand and GoReleaser archives for Linux, macOS, and Windows (amd64andarm64) for tagged releases. When I wrote this the repo had no published releases yet, so building from a checkout is the reliable route.
If you want end-of-session summaries, configure an API key:
cp .env.example .env
# edit ATMO_MEM_API_KEY
You can try it without an agent first. The CLI uses the same database:
atmo-mem save --project my-app --kind decision \
--title "Use FTS5" \
--content "Memory search is SQLite FTS5." \
--tags sqlite,search
atmo-mem search --project my-app fts
atmo-mem recall --project my-app memory search
atmo-mem summarize --project my-app \
--notes "We stored memories in SQLite and exposed them over MCP."
Flags go before positional arguments. Add --json to get JSON output from save, search, recall, list, get, and summarize. atmo-mem help lists every command.
Setting it up with your agent
Running atmo-mem with no arguments (or atmo-mem mcp) reads JSON-RPC from stdin and writes JSON-RPC to stdout, with logs on stderr. In an interactive terminal it prints help instead; when stdin is a pipe, which is how editors launch it, it starts the MCP server. Don't wrap it in a script that prints a banner, because that breaks the protocol.
Two tips from the README:
- Set
ATMO_MEM_PROJECTto the repo name so memories from different projects don't mix. - Pass
ATMO_MEM_API_KEYonly if you want end-of-session summaries.
Claude Code
Project .mcp.json:
{
"mcpServers": {
"atmo-mem": {
"command": "atmo-mem",
"args": [],
"env": {
"ATMO_MEM_API_KEY": "your-key",
"ATMO_MEM_PROJECT": "my-app"
}
}
}
}
Or with the CLI (flags vary slightly across Claude Code versions; the JSON above is the stable form):
claude mcp add --transport stdio --scope project \
--env ATMO_MEM_API_KEY=your-key \
--env ATMO_MEM_PROJECT=my-app \
atmo-mem -- atmo-mem
Cursor
Put the same block in .cursor/mcp.json in your project (or ~/.cursor/mcp.json for a global config), then reload MCP servers from Cursor settings. atmo-mem has to be on the PATH Cursor sees, or you can set command to the absolute path.
Cline
Open MCP Servers → Configure (cline_mcp_settings.json) and add:
{
"mcpServers": {
"atmo-mem": {
"command": "atmo-mem",
"args": [],
"env": {
"ATMO_MEM_API_KEY": "your-key",
"ATMO_MEM_PROJECT": "my-app"
},
"disabled": false,
"autoApprove": ["memory_recall", "memory_search", "memory_list", "memory_get"]
}
}
}
The auto-approve list here is a good default: read-only tools run without prompting, while memory_delete (destructive) and session_summarize (a billed network call) still ask for approval.
Teach the agent the workflow
The tools only help if the agent calls them. The README suggests this loop, which works well in CLAUDE.md, Cursor rules, or Cline custom instructions:
- At the start of a task, call
memory_recallwith the task and the project. - When a decision, fix, or preference lands, call
memory_save. - At the end of the session, call
session_summarizewith notes, or with asession_idyou used along the way.
The tools
| Tool | What it does |
|---|---|
memory_save |
Store title, content, optional kind, tags, project, session_id. |
memory_search |
AND keyword search. Words of 3+ characters are prefix-matched (auth finds authentication). |
memory_recall |
Rank memories for a task. An empty query returns recent project memory. The newest summary is pinned first. |
memory_list |
Recent memories, newest first. |
memory_get |
One memory by id. |
memory_delete |
Permanently delete one memory by id. |
session_summarize |
Compress a transcript and that session's memories with the LLM, then store a summary. |
Memory kinds: observation (default), decision, bugfix, preference, fact, summary, task.
How it works
Search vs. recall
The two lookups deliberately behave differently:
- Search is strict. A memory is returned only when every query word matches. Results are ordered by FTS5 bm25, with the title weighted above the body and tags.
-
Recall is wide. Any word can match, and a small recency bonus breaks near ties. The latest
summaryfor the project goes to the front even if it shares no words with the query. A much stronger text match still outranks a memory that's only newer.
Common English filler words (the, and, for, …) are dropped when the query has other words. Matching is case-insensitive, words shorter than two characters are ignored, and two-character words such as go match exactly.
Storage
Everything lives in one SQLite file, by default ~/.atmo-mem/atmo-mem.db. The directory is created with mode 0700, and the file is set to 0600 where the OS allows it. Triggers keep an FTS5 index (unicode61 tokenizer) in sync on insert and delete.
There's no update call: to change a memory, delete the old id and save a new one. Sessions are just bookkeeping rows. What the next session actually reads is a normal memory with kind summary.
Summarization
session_summarize sends the transcript or notes, plus memories saved under that session_id, to POST {ATMO_MEM_BASE_URL}/chat/completions with a standard body (model, messages, temperature, max_tokens) and stores the result as a summary memory. Because recall pins the newest summary, the next session starts with it.
Configuration
Settings are read from flags first, then environment variables, then a .env file in the working directory; the first non-empty value wins. ATMO_MEM_* takes precedence over the OPENAI_* fallbacks.
| Variable | Default | Purpose |
|---|---|---|
ATMO_MEM_API_KEY |
empty | Bearer token for summarization (falls back to OPENAI_API_KEY). |
ATMO_MEM_BASE_URL |
https://atmorouter.dev/v1 |
OpenAI-compatible base URL, including /v1 (falls back to OPENAI_BASE_URL). |
ATMO_MEM_MODEL |
zai/glm-5.3 |
Model id (falls back to OPENAI_MODEL). |
ATMO_MEM_DB |
~/.atmo-mem/atmo-mem.db |
SQLite file. |
ATMO_MEM_PROJECT |
empty | Project used when a call omits project. |
ATMO_MEM_TIMEOUT |
60s |
Summarization timeout. |
Credit
atmo-mem is inspired by claude-mem and engram, two projects that have explored persistent agent memory further than this one. atmo-mem is not affiliated with either. Its angle is narrow: one dependency-free binary, local SQLite + FTS5, and an optional summary step that works with any OpenAI-compatible endpoint. If you want something richer, look at those projects too.
About AtmoRouter: a cheap AI API gateway and OpenRouter alternative
Disclosure: by default, session_summarize sends requests to AtmoRouter using zai/glm-5.3; summaries don't need the model you code with. Any OpenAI-compatible provider works. AtmoRouter is a cheap AI API gateway that speaks both the OpenAI Chat Completions API and the Anthropic Messages API, so one key reaches 100+ models from Cursor, Cline, Claude Code, or any compatible client. It is pay-per-token with no subscription, accepts QRIS and USDC, and prices sit well below official rates: AtmoRouter advertises an average of 83.7% below official prices, which makes it an affordable LLM API and a practical OpenRouter alternative for agent workloads.
Try it
If you're tired of re-explaining your project to a fresh agent session every morning:
👉 github.com/bayuh85/atmo-mem
Issues and PRs are welcome. Real-world reports on how recall ranking holds up across a long-running project would be especially useful.
Top comments (0)