writing

Axon is open source

Axon is the memory every Gagarin project carries. Your coding agent writes down decisions and gotchas once, and the next session reads them back in 1,500 tokens. The code is on GitHub now, under MIT.

You open a new session on a project you have worked on for two months. The agent reads the README, lists the directories, opens twelve files, and then suggests moving sessions into valkey. You moved them out of valkey in September, after the first restart logged every user out. The agent cannot know that. The reason lives in a Slack thread and in your head, and the code only shows where sessions ended up.

Axon is the fix we built for that, and as of this week its code is public at github.com/gagarin-cloud/axon, MIT licensed. It stores small, durable facts about a codebase: its overview, architecture, decisions, conventions and gotchas. A new session asks for a briefing before it reads any code and gets the facts that matter, packed into a token budget it chose.

Every Gagarin project has an axon memory behind it already, and your agent reaches it through Gagarin's MCP server.

Two costs of an agent that forgets

The first cost is tokens. A session that knows nothing starts by reading, because reading is the only way it can learn. We counted what a cold session would have to read to understand axon itself:

Tokens a session reads to learn axon
  • axon's source, tests and migrations105,624 tokens
  • axon's source code, src/ only45,736 tokens
  • the six documents that explain its design28,221 tokens
  • a briefing at its default budget1,500 tokens

Counted with o200k_base, the tokenizer axon counts its own budgets in. The six documents are axon's README and DEPLOY.md, the two decision logs behind it, and the two files in Gagarin's engine and infrastructure that place it.

A briefing doesn't replace reading the file you are about to change. It replaces the reading an agent does to learn the context around that file, which it repeats in every new session and forgets again when the session ends.

The second cost has no token count. Some facts never make it into the code. A diff shows that sessions live in Postgres; it can't show that valkey lost them once. A test pins a behaviour without saying which customer asked for it. Your agent proposes the fix you reverted last week because the revert left no trace it can read. Axon gives those facts somewhere to live, and the agent that learns one writes it down for the next.

What a briefing looks like

This is the briefing for an example project, at a budget of 500 tokens. It comes from the same rendering code the landing page uses, which follows axon's own line for line:

text
# saas — 16 memories, 20 links
gotcha 4 · architecture 3 · decision 3 · convention 2 · overview 1 · howto 1 · reference 1 · state 1

## #7 decision · Sessions stay in Postgres, not valkey
tags: sessions, auth · pinned · importance 5 · updated 2026-10-03
valkey keeps nothing across a restart: the first one logged every user out. Sessions live in pg’s sessions table; valkey may cache in front of it, never instead of it.
↔ #12 Login reads the sessions table on every request · #15 valkey restarts during deploys · #13 valkey caches rendered invoice PDFs, nothing else · #3 api, worker and web; pg is the only store of record

## #1 overview · saas: invoicing for small agencies, on Gagarin
tags: overview · importance 4 · updated 2026-09-28
Three services (api, web, worker) and one managed Postgres, pg. web is the only public service. Money moves through Stripe; everything else is ours.
↔ #3 api, worker and web; pg is the only store of record

More:
#3 architecture · api, worker and web; pg is the only store of record · 47t ↔6
#12 gotcha · Login reads the sessions table on every request · 37t ↔3
#4 architecture · worker drains the jobs table with SKIP LOCKED · 41t ↔4
#5 convention · Migrations are numbered and forward-only · 42t ↔3
#6 convention · A deploy restates every variable · 43t ↔2
#2 architecture · web is a Next.js app that only ever calls api · 39t ↔1
#15 gotcha · valkey restarts during deploys · 31t ↔2
#10 gotcha · A route to pg that isn’t declared hangs, it doesn’t fail · 55t ↔2
#16 gotcha · Stripe retries webhooks; handlers must be idempotent · 44t ↔2

(+5 more not shown — narrow with search or raise budget_tokens)

tags: pg(5) deploy(4) migrations(3) api(2) overview(2) sessions(2) stripe(2) valkey(2) worker(2) auth(1) invoices(1) performance(1) web(1)
Next: search · get ids · related id (follow ↔ links) · remember what you learn

Two memories arrive in full: the pinned decision, and the overview. Nine more arrive as index lines, each with its size in tokens (47t) and its link count (↔6), so the agent can decide what is worth a get. Five are left out, and the briefing says so instead of pretending the project has eleven memories.

The output is plain text. JSON would spend tokens on quotes and key names in every line. Ids are short numbers per project: #12 costs two tokens, and a UUID costs 35 each time it appears.

The graph below holds the same project. The buttons run the memory tools against it, and the graph lights up what each answer used:

memory · saas
  • overview
  • architecture
  • decision
  • convention
  • gotcha
  • howto
  • reference
  • state
  • ★ pinned
#1#2#3#4#5#6#7 ★#8#9#10#11#12#13#14#15#16
memory_get(saas, [7])
## #7 decision · Sessions stay in Postgres, not valkey
tags: sessions, auth · pinned · importance 5 · updated 2026-10-03
valkey keeps nothing across a restart: the first one logged every user out. Sessions live in pg’s sessions table; valkey may cache in front of it, never instead of it.
↔ #12 Login reads the sessions table on every request · #15 valkey restarts during deploys · #13 valkey caches rendered invoice PDFs, nothing else · #3 api, worker and web; pg is the only store of record

#7 is a decision. Its links are lit; thicker ones have been followed more.

16 memories · 20 links$0.0016 a month in use · reading is free

How it works

Axon is a TypeScript service over one Postgres database with pgvector. It speaks MCP (stdio and Streamable HTTP) and a REST API, and both call the same service object.

Every read has a budget, and never exceeds it

briefing, search, related and review all take budget_tokens. The packer fills full bodies in rank order until they reach 75% of the budget, lists the remaining memories as index lines, then spends whatever budget is left upgrading index lines back to full bodies. Its contract is that the measured output never exceeds the budget, and a property test throws 400 seeded random cases at it to check that, along with its other guarantees.

A briefing with a focus ("the Stripe webhook handler") or paths (the files you are about to touch) splits the budget by share. Pinned memories stop at 50%, and matches for the focus may fill up to 80%, so the matches get at least 30% of the briefing in full however many pinned memories the project holds.

The tool definitions get the same treatment. Clients see types, enums, defaults and descriptions; length limits and regex patterns are checked on the server instead of advertised. That cut axon's tool definitions from 1.7K to 1.1K tokens, which every session pays before it calls anything.

One query, two lanes

Search is hybrid, and it runs as a single SQL query. One lane is semantic: an HNSW index over the embeddings, with pgvector's iterative scan so a filter on kind or tag doesn't starve the results. The other lane is keyword: a tsvector with Postgres's simple configuration, which does no stemming, so STRIPE_WEBHOOK_SECRET matches itself exactly. Reciprocal rank fusion merges the two:

text
score = 1 / (60 + vector_rank) + 1 / (60 + keyword_rank)

A memory that ranks well in both lanes beats one that tops only one of them. Semantic-only hits below a similarity floor get dropped, so a vague query doesn't fill the budget with noise.

No language model on the write path

Axon embeds text with Qwen3-Embedding-0.6B, run on the server's own CPU as an 8-bit ONNX model through transformers.js. It needs no API key, and on a laptop CPU an embedding takes 40 to 80 ms. The Docker image bakes the model in, so a container starts offline.

That choice shapes the cost. Several hosted memory services run an LLM over each conversation to extract facts from it. Mem0's docs put the model cost at about $3.55 to $5.34 per 1,000 adds, paid to your LLM provider on top of Mem0's own bill. Axon asks the agent to write the fact itself, one fact per memory. The agent already understands what it learned, and asking a second model to guess at it costs money and adds a step that can get it wrong.

What axon does on a write is cheaper checks. remember refuses a near-duplicate (cosine similarity 0.85 or more) with a 409 that names the memory it collides with, suggests links to related memories, and warns about memories over 600 tokens.

Retrieval quality on the bundled golden set (25 memories, 35 queries):

Query typenRecall@1Recall@5MRR@10
Paraphrase1694%100%0.969
Exact identifier9100%100%1.000
Cross-lingual (uk, de, es, ja, fr against English memories)786%100%0.929

Links that strengthen with use

Memories link to each other, undirected, at most 32 links each. The database enforces a < b on every pair, so self-links and duplicate pairs can't exist, and composite foreign keys keep links inside one project. related walks them breadth-first with a recursive CTE, up to three hops.

The weights are Hebbian. Each time an agent follows a link with related, or reads both ends in one get, the link gains 0.25, up to 5. The extra weight decays back toward 1 with a 60-day half-life:

text
strength = 1 + (weight − 1) × 0.5 ^ (days since last traversed / 60)

Postgres computes the decay at read time, in a SQL function, so no background job has to tick the weights down. The paths your agents use come first, and the ones they stop using fade.

Facts go stale, so axon tracks that

A note that says the deploy needs a flag outlives the flag. An agent that trusts it is worse off than one with no memory at all. So every memory records when someone last confirmed it true, and the briefing scales its rank by how long ago that was:

KindTrust halves everyMarked unconfirmed after
state30 days60 days
gotcha180 days180 days
howto, reference180 days
overview, architecture, decision, convention365 days

Rank never drops below 40% of its fresh value, so an old fact sinks without vanishing. An agent that doubts a memory sends update dispute=true; the memory then renders ⚠ disputed, sorts below every undisputed one, and each briefing names it until someone confirms, fixes or archives it.

A fact that changes in place gets a key, such as deploy/current. A later remember with the same key replaces the holder and moves its links to the successor, with no search first. A service check under the project lock gives the readable error, and a partial unique index makes the rule hold under a race.

review finds what needs tending: likely duplicates, orphans, oversized memories, stale state, memories nobody has read in 90 days, and disputes.

Encryption, and what it leaves readable

With AXON_ENCRYPTION_KEY set, axon encrypts each memory's title, body and source with AES-256-GCM. The associated data binds each ciphertext to its project and field, so nobody can copy a row into another project or column and have it decrypt. axon rotate-key re-encrypts every row in one transaction and never writes the text back as plaintext.

The embeddings stay readable, since vector search needs them, and an embedding gives away the topic of a memory. Kind, tags, paths, keys and timestamps stay readable too. Postgres can't index ciphertext, so with encryption on, the keyword lane matches only tags and paths.

On Gagarin, every project has one

Gagarin's MCP server exposes axon as nine tools, memory_briefing and remember among them, and its instructions tell every connected agent to call memory_briefing before reading code. You don't add a resource or deploy anything. Create a project and its memory exists.

Axon has no authentication of its own, and on Gagarin it stays that way. The engine is its only caller:

how it fits together
your agent (Claude Code, Codex, Cursor)
   │  MCP over HTTPS, with your OAuth token
   ▼
mcp.gagarin.cloud ──── holds no credential, decides nothing
   │  the same token
   ▼
api.gagarin.cloud ──── may this caller read or write this project?
   │  cluster network; the project id is axon's slug
   ▼
axon ───────────────── no Ingress, no DNS name; a NetworkPolicy
   │                   admits the engine and its jobs, nothing else
   ▼
Postgres + pgvector ── its own database and role

The engine decides access to memory the way it decides access to a service: by your role on the project. Axon keys memories by the project's id, never its name, because two accounts can both own a project called api. When you destroy a project, the engine deletes its memory, and a nightly job sweeps up anything a failed delete left behind.

A static token between the engine and axon would stop only an attacker already inside the platform namespace, who could read the Secret holding it. So we left it out and wrote the NetworkPolicy down as the boundary. On the live cluster, we probed axon from a tenant pod, from the MCP server's pod and from the admin page's pod, and all three timed out.

Memory costs $0.10 a month per 1,000 memories in use, billed by the minute like a disk. Reads cost nothing. A project at the default cap of 5,000 memories costs $0.50 a month. We charge for it because an agent gets real use from memory without ever deploying, and a free line with value of its own is a free tier, which Gagarin doesn't have. A new account's $5 credit covers memory for a long time.

What other memory services charge

These are the cheapest paid plans we could find on each vendor's own pricing page on 3 November 2026. What each plan includes differs, so read this as the entry price, not a like-for-like quote:

Cheapest paid plan, per month
  • Zep, Flex$125/month
  • axon on Gagarin, your own instance$42.38/month
  • Letta, Pro$20/month
  • Supermemory, Pro$19/mo
  • Basic Memory, Cloud$19/month
  • Gagarin project memory, 5,000 memories$0.50/month

Mem0 now sells new accounts a Free plan and a Custom one, with no listed price for the second, so it has no bar. Cognee bills by tokens processed rather than by plan. Both are in the table below.

ServiceWhat it bills onEntry paid planOpen source
Mem0adds and retrievalsCustom (Free for new accounts), plus your LLM at $3.55 to $5.34 per 1,000 addsApache-2.0
Zepcredits: 1 per 350-byte episode, retrieval free$125/month for 50,000 credits, then $25 per 10,000Graphiti, its graph engine, is Apache-2.0
Supermemorydollar credits per token, query and operation$19/mo with $20 of credits; Max is $100/moMIT
Basic Memorycredits at $0.01 each$19/month; Pro is $99/monthAGPL-3.0
Lettaa base fee, then agents and tool time$20/monthApache-2.0
Cogneetokens processed$1.00 per 1M tokens, $5 per extra workspaceApache-2.0
Gagarinmemories in use$0.10 per 1,000 a month; reads freeaxon is MIT

The gap comes from the write path. A service that runs an LLM over each write has to price in that model, and its bill grows with the volume of text you send. Axon's write costs one local embedding on a CPU, so the price can follow what you store.

Where a hosted memory service is the better pick

You want memory extracted for you. Mem0 and Zep read conversations and pull facts out of them without the agent deciding to write anything. If your agent can't be relied on to call remember, or your source is chat transcripts, that extraction is the product.

Your memory is about people. Zep's temporal knowledge graph tracks how facts about an entity change over time, and Mem0 scopes memory per end user, with no limit on how many users you have. Axon is per project and built for codebases. It has no notion of a user.

You need to ingest documents and tools. Cognee connects to Slack, Notion, Linear and Google Drive, and Supermemory takes rich media. Axon stores what an agent writes, and nothing else gets in.

You need compliance paperwork. Zep's Enterprise plan lists SOC 2 Type II and a HIPAA BAA. Gagarin has neither today.

Running your own

Axon runs on a laptop for free:

bash
git clone https://github.com/gagarin-cloud/axon && cd axon
docker compose up -d db        # Postgres 17 + pgvector
npm install && cp .env.example .env
npm run axon -- serve          # REST on :7717/v1, MCP on /mcp
claude mcp add --transport http axon http://127.0.0.1:7717/mcp

The first start downloads the embedding model, about 600 MB, and later starts load it in about a second.

Run your own instance on Gagarin when you want to hold the encryption key yourself, since on the built-in memory we hold it. Other reasons: a different embedding model, no cap, or one memory shared by agents that sign in with a bearer token instead of Gagarin's OAuth. The repo's DEPLOY.md walks through it, from published images with signed provenance attestations:

PieceSizeA month
axon server (the model needs most of a gigabyte)m, $0.042 an hour$30.66
Postgres with pgvectors, $0.014 an hour$10.22
10 GB of disk$0.15 per GB$1.50
Private, reachable from your own services$42.38
a bearer-token gateway with a public addresss$10.22
Public, behind the gateway$52.60

Either one holds as many projects as you like. For most teams the built-in memory is cheaper by two orders of magnitude, and you get the same code either way.

What's in the repo

The server, the MCP and REST transports, migrations, the eval harness and its golden set, the bearer-token gateway, and more than 200 tests that run against real Postgres with pgvector and a deterministic fake embedder. The tests try to break the properties that matter: one project's memories never show up in another's search, link walk, review or export, no budgeted read exceeds its budget, a failed write leaves nothing behind, and an encrypted row holds no plaintext.

Issues and pull requests are open at github.com/gagarin-cloud/axon. If you run a Gagarin project, your agent already has a memory: ask it for a briefing.

Sources, read on 3 November 2026

  1. Gagarin pricing
  2. Mem0 pricing (docs)
  3. Zep pricing
  4. Supermemory pricing
  5. Basic Memory pricing
  6. Letta pricing
  7. Cognee pricing