by Martin Monperrus

TLDR: the best memory for a coding agent is its own past sessions, and the right interface to that memory is a filesystem, not an API.

Every coding agent I run — Claude Code, Codex, Copilot CLI, opencode — writes a full trajectory of each session to disk: my instructions, its tool calls, the outputs, the token counts. That data sits in some ~/.dotfolders, one bespoke format per agent. It is the single richest record of how a project was actually built, and it is write-only in practice: nothing ever reads it back.

Trajectory memory

The core concept I propose is trajectory memory: the agent’s own past sessions, normalized to a single format, and mounted as a read-only filesystem inside the project they belong to.

Four properties define it:

How you use it

One command, in the project you are working on:

$ trajectoriz-cli memory
Mounting trajectory memory for /home/martin/workspace/prototypes/trajectoriz at
  /home/martin/workspace/prototypes/trajectoriz/memory (daemonized).
Unmount with: trajectoriz-cli memory --unmount memory

From there the usage needs no new verbs. “Why is this function written this way?” becomes a grep over past sessions. “What did we try before this?” becomes reading the session from the week the file was last touched. And because the mount is a directory, the agent does this with the tools it already has — its own Read, Grep and Glob — with no integration on my part.

The directory explains itself, which matters when the reader is an agent that just found it: memory/README.md says what the files are, what an ATIF payload contains, and how to locate a session without grepping.

The idiom I use most is find, then read. trajectoriz-cli search locates the session — matching first messages, IDs and agent names, so it parses nothing — and the mount serves it:

$ trajectoriz-cli search "tokens per trajectory" --local
## Search: `tokens per trajectory` — 1 result(s)

| ID | Agent | Date | First message |
|---|---|---|---|
| `cl-eb1c6529` | claude | 2026-06-10 | add support for computing the number of tokens per traje… |

$ ls memory | grep cl-eb1c6529
2026-06-10_claude_cl-eb1c6529.atif.json

$ jq -r '.steps[0].message' memory/2026-06-10_claude_cl-eb1c6529.atif.json
add support for computing the number of tokens per trajectory, inspired from code in @../agent-reports-codex/

$ jq '.final_metrics' memory/2026-06-10_claude_cl-eb1c6529.atif.json
{ "total_steps": 90, "total_tool_calls": 49, ... }

Search IDs and filenames carry the same identifier, so the two compose without glue. Add --content to look inside every step instead of only the first message: exhaustive, and slower, because it parses each session it touches.

Why it works

The filesystem is the protocol. And it is the most agent-native protocol there is: every coding agent ships with file tools, so the API is already implemented on both sides. An MCP server must be installed, configured, kept running, and — the part people forget — its tool definitions must occupy the context window before the agent can use them. A directory costs none of that: no server lifecycle, no schema negotiation, no version handshake, no tokens. The agent does not need to be told that a memory API exists; it needs a directory that answers ls.

The cost is paid on read. An index would have to be built and refreshed; a service would have to be running. A mount is inert until something touches it, which is what makes it safe to leave mounted in every project.

Agents are already good at this. Grepping a directory and reading the interesting file is the single behaviour every coding agent has been trained hardest on. Trajectory memory needs no new agent capability — it reuses the strongest one.

The implementation

It is a FUSE filesystem, pip install trajectoriz[fuse], in which every past session of the current project is one JSON file — generated when read, never stored:

$ ls memory | head -3
1780222476_opencode_oc-b83cd02d.atif.json
2026-05-31_claude_cl-261c8a5c.atif.json
2026-05-31_claude_cl-c2f9679f.atif.json

$ ls memory | wc -l
30

$ grep -rl "pytest" memory | wc -l
26

The filenames carry the date, the agent and the session ID, because those are the three things you sort and filter by. Everything else is inside.

Each file is the session translated to ATIF v1.7, the Agent Trajectory Interchange Format: one envelope with session_id, the agent and model, the ordered steps (messages, tool calls, observations), and final_metrics.

Why translate at all? Because every agent encodes the same three concepts — a message, a tool call, its result — in a different vocabulary. Claude Code stores a tool result inside the next user message, keyed by tool_use_id. Codex emits a function_call_output carrying a call_id. Copilot CLI emits a tool.execution_complete event with a toolCallId. Same event, three encodings, and that is before the ones that keep sessions in SQLite instead of JSONL. Without translation, one-file-per-session would only relocate the problem: the reader would still need to know which of eight stores it is looking at. With it, .steps[3].tool_calls[0].function_name means the same thing everywhere, and a jq filter written today keeps working when I install tomorrow’s agent.

See also