Persistent sessions — explanation
What is this about?
A session is one conversation: the system prompt, the questions, the answers and the tool calls in between. Until 2026-09-16 it lived in memory and died with the process; an editor that restarted mm could only open a new thread, which is what Zed's "Loading or resuming sessions is not supported by this agent" said. Persistent sessions (sessions.enabled) write each conversation to a file and let both front ends get it back.
Why files, per project
Sessions live under the project, in .mm/sessions/, one JSON file each, rewritten whole after every turn. Three reasons. A conversation is about a project — its files, its commands — and belongs next to them, not in a home-wide database where projects mix. Files are inspectable: jq answers "what did the model read?" without a tool. And a whole-file rewrite is what compression needs: /compact replaces old turns by a summary, and the file must say the same as the memory, not the memory plus everything it used to hold.
The id is the creation time plus a random tail (20260916-101530-4f2a9c1e), so a listing sorts by time and two front ends never collide.
Why a start is a new session
When mm starts, it starts a new session and says how to get the old ones back, rather than resuming the latest on its own. This mirrors docker agent (cagent): its run command never resumes by itself — --session -1 or the TUI's /sessions browser do — and it is the safer default. The last conversation may be about something else; resuming it silently would hand the model a stale context, and the user would only notice when the answers went wrong. An explicit /resume costs one line and keeps the surprise out.
The terminal writes the file on the first question, not at start: a launch that asked nothing leaves no trace, and /sessions lists conversations, not launches.
Why session/load and a full replay
The Agent Client Protocol offers two ways back into a session. session/load is in the stable spec: the agent replays the conversation as session/update notifications and the editor rebuilds the thread from them. session/list and session/resume are unstable extensions: the agent only re-registers the session, and the editor keeps whatever it was showing. docker agent chose the second (loadSession: false, Resume capability, SQLite store in ~/.cagent/session.db). mini-me chose the first, because it is the stable one and because a replay puts the thread back even in an editor that kept nothing.
"Replay" means re-sending records. Each tool call comes back as a single, already-completed tool_call carrying the input the model gave and the output the tool returned at the time. Nothing is executed again: reloading a thread that once ran rm runs nothing.
A reloaded or resumed session continues under the current system prompt, not the one stored with it: the prompt is the agent's, and a change to agent.yaml must reach old conversations too.
/new in the two front ends
In the terminal /new simply leaves the old file alone and lets the next question open a new one. Over ACP the editor owns the session id and keeps using it, so the id's file must become the fresh session; the conversation so far is moved to a new id first. Either way nothing is lost on disk, and a later reload of the thread gives the current conversation, not the one /new closed.
Rejected alternatives
- Resume the latest session automatically at start. Rejected for the stale-context reason above, and because docker agent does not do it either.
- Append-only log per session. Rejected: compression shrinks the history, and a log would keep saying what the memory no longer holds.
- SQLite, like docker agent. Rejected for a minimal agent: one more dependency, opaque files, and a per-user database where mini-me wants per-project files.
session/resume(unstable) instead ofsession/load. Rejected: not in the stable spec, and it relies on the editor having kept the thread.- Replaying only text messages. Rejected by the user: the tool calls are the part of a coding session worth seeing again.
How it relates to the rest
internal/store is the file layer, used by internal/agent (a keeper that saves after each turn and serves /sessions and /resume) and by internal/acp (a file per session, session/load, the replay in replay.go). internal/session.WithSystem swaps the stored prompt for the current one. Compression (internal/compact) is unaware of persistence: the front ends save after it runs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|