Three lines of code to give your AI agents persistent memory. Reduce 90% token consumption while also maintaining quality.
# Add to your Claude Code skills
git clone https://github.com/angelnicolasc/graymatterThree lines of code to give your AI agents persistent memory and cut token usage by 90%. One binary. Drop it in. Run it. No Docker, no databases, no config files, no cloud accounts, no bullshit. General-purpose MCP server. Zero vendor lock-in. Works with Claude Code, Cursor, Codex, OpenCode, Antigravity — and any MCP-compatible client. Also a plain Go library if you don't use MCP. Free. Offline. No account required.
ctx := context.Background()
mem := graymatter.New(".graymatter")
mem.Remember(ctx, "agent", "user prefers bullet points, hates long intros")
facts, _ := mem.Recall(ctx, "agent", "how should I format this response?")
// ["user prefers bullet points, hates long intros"]
Every AI agent is stateless by default. Each run re-injects the full conversation history — and that history grows linearly. Two prompts in and you've already burned half of your daily quota.
That's not just a memory problem. That's a money and performance problem.
Mem0, Zep, Supermemory solve this — but they're Python/TypeScript-only and require a running server. The Go ecosystem has no production-ready, embeddable, zero-dependency memory layer for agents.
That gap is GrayMatter.
You can't improve what you can't see.
graymatter tui opens a live terminal dashboard with everything your
agent memory is doing — no extra setup required.
What you get at a glance:
No comments yet. Be the first to share your thoughts!
The dashboard auto-refreshes every 5 seconds. Press 1–4 to switch tabs,
r to force refresh, q to quit.
Binary (recommended):
# macOS (Apple Silicon)
curl -sSL -o graymatter.tar.gz https://github.com/angelnicolasc/graymatter/releases/download/v0.5.1/graymatter_0.5.1_darwin_arm64.tar.gz
tar -xzf graymatter.tar.gz
sudo mv graymatter /usr/local/bin/
# Windows (PowerShell)
iwr https://github.com/angelnicolasc/graymatter/releases/download/v0.5.1/graymatter_0.5.1_windows_amd64.zip -OutFile graymatter.zip
Expand-Archive graymatter.zip -DestinationPath .\graymatter_cli
Go install:
go install github.com/angelnicolasc/graymatter/cmd/graymatter@latest
Library:
go get github.com/angelnicolasc/graymatter
graymatter init
One command auto-wires GrayMatter into every supported client at once. Existing entries from other MCP servers are merged, not overwritten — safe to run in any repo.
| Client | Config file auto-wired | Scope |
|--------|------------------------|-------|
| Claude Code | .mcp.json | project |
| Cursor | .cursor/mcp.json | project |
| Codex (OpenAI) | ~/.codex/config.toml | home |
| OpenCode | opencode.jsonc | project |
| Antigravity (Google) | mcp_config.json | project (opt-in: --with-antigravity) |
Narrow down what gets wired:
graymatter init --only claudecode,cursor # whitelist
graymatter init --skip-codex --skip-opencode # blacklist
graymatter init --with-antigravity # include opt-in clients
Then restart your editor (or toggle the MCP server off/on in its settings). Five tools become available:
| Tool | What it does |
|------|-------------|
| memory_search | Recall facts for a query |
| memory_add | Store a new fact |
| checkpoint_save | Snapshot current session |
| checkpoint_resume | Restore last checkpoint |
| memory_reflect | Add / update / forget / link memories (agent self-edit) |
GrayMatter speaks plain MCP. If your client isn't on the table above, point it at the binary:
graymatter mcp serve # stdio transport
graymatter mcp serve --http :8080 # HTTP transport
The schema is identical to every other MCP server — command +
args: ["mcp", "serve"]. No proprietary glue.
If you'd rather not run graymatter init in every repo, drop the same
JSON into the editor's global config — ~/.cursor/mcp.json for Cursor,
~/.claude/mcp.json for Claude Code:
{
"mcpServers": {
"graymatter": {
"command": "graymatter",
"args": ["mcp", "serve"]
}
}
}
graymatter must be on PATH. The init command handles this
automatically on Windows via the User PATH registry; on macOS / Linux
the recommended install path /usr/local/bin is already on PATH.
There are four ways a fact ends up in the store. You don't have to pick one — they compose:
| Path | Who calls it | When to use |
|------|--------------|-------------|
| mem.Remember(ctx, agent, text) | Your code, explicitly | You already know the exact string worth keeping. |
| mem.RememberExtracted(ctx, agent, llmResponse) | Your code, on raw LLM output | You want GrayMatter to pull atomic facts out of a full response for you (LLM-assisted; falls back to storing the raw text if no API key is set). |
| memory_reflect (MCP tool) | The LLM itself, mid-session | Claude Code / Cursor agents self-curate: add, update, forget, or link memories when they notice a contradiction, finish a task, or learn a preference. |
| Consolidate (async, on by default) | Background goroutine | Summarises, decays, and prunes over time. Runs automatically after writes once ConsolidateThreshold is hit. |
Forgetting a single Remember call is not fatal. memory_reflect lets the
agent fix its own memory as it works, and Consolidate curates the store
over time. That's why long interactive sessions in Claude Code Desktop
and Cursor are a sweet spot for GrayMatter — not only 24/7 autonomous
agents. The LLM maintains its own memory through MCP.
Three functions cover 95% of use cases. All methods accept context.Context as the first argument so timeouts and cancellation propagate end-to-end — no wrappers needed.
import "github.com/angelnicolasc/graymatter"
ctx := context.Background()
// Open (or create) a memory store in the given directory.
mem := graymatter.New(".graymatter")
defer mem.Close()
// Always check health in production — New() never panics, but it may degrade
// to no-op mode if the data dir is unwritable or bbolt fails to open.
if !mem.Healthy() {
log.Fatalf("graymatter: %v", mem.Status().InitError)
}
// Store an observation.
mem.Remember(ctx, "sales-closer", "Maria didn't reply Wednesday. Third touchpoint due Friday.")
// Retrieve relevant context for a query.
facts, _ := mem.Recall(ctx, "sales-closer", "follow up Maria")
// ["Maria didn't reply Wednesday. Third touchpoint due Friday."]
Context propagates everywhere — timeouts and traces work as expected:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := mem.Remember(ctx, "agent", "observation"); err != nil { ... }
results, err := mem.Recall(ctx, "agent", "query")
ctx := context.Background()
mem := graymatter.New(project.Root + "/.graymatter")
defer mem.Close()
if !mem.Healthy() {
log.Fatalf("graymatter: %v", mem.Status().InitError)
}
// 1. Recall before calling the LLM.
memCtx, _ := mem.Recall(ctx, skill.Name, task.Description)
messages := []anthropic.MessageParam{
{Role: "system", Content: skill.Identity + "\n\n## Memory\n" + strings.Join(memCtx, "\n")},
{Role: "user", Content: task.Description},
}
// 2. Call your LLM.
response, _ := client.Messages.New(ctx, anthropic.MessageNewParams{...})
// 3a. If you already have a clean string worth keeping, store it directly.
mem.Re