What Does Muse Store Locally? Agent Session JSONL Explained
Muse keeps one JSONL per agent under ~/agents — with session_header/item/compaction_checkpoint records, thinking as a first-class citizen, and a source field on every item naming the runtime subsystem that wrote it.
Claude Code keeps sessions under ~/.claude/. Cursor spreads them across SQLite and JSONL. Grok Bot keeps them on a cloud box. Muse is simpler than all three: one JSONL per agent, on your own disk — ~/agents/<agent-id>/sessions/<agent-id>.jsonl, append-only, no database. The twist is that the runtime signs every line it writes: each item record carries a source field like runtime.feed, runtime.self_improvement, or scheduler.cron, naming the subsystem that produced it.
Try it: npx vibe-replay@latest -p muse. Or open the full dashboard with npx vibe-replay@latest -d.
Here’s where the files live, how the schema works, and what vibe-replay rewrites so the replay matches the session you actually lived. All numbers below come from a real census: 471 agent sessions, 11,667 lines, zero malformed.
The practical mental model is:
~/agents/<agentId>/sessions/<agentId>.jsonl ← one file per agent, append-only
~/agents/<agentId>/sessions/sessions.json ← runtime index (best-effort)

vibe-replay discovers those JSONL files, pairs tool calls with their outputs, promotes thinking blocks, filters runtime noise, and renders the result as the same replay format used for other providers.
Where the files live
Sessions default to:
~/agents/<agentId>/sessions/<agentId>.jsonl
Layout rules that matter for tooling:
| Path piece | Meaning |
|---|---|
<agentId>/ |
One folder per agent; the filename repeats the agent id |
<agentId>.jsonl |
Append-only conversation log, one JSON object per line |
sibling sessions.json |
Runtime index: created_at/updated_at, item_count, compaction_count, context_window_usage.model_id |
Point discovery at a different root with one env var:
MUSE_AGENTS_DIR=/path/to/agents npx vibe-replay@latest -p muse
The JSONL shape
Three record types at the top level:
| Record | Share of 11,667 lines | Meaning |
|---|---|---|
item |
11,182 | Everything: messages, thinking, tool calls and outputs |
session_header |
471 | One per file: session_id, agent_id, created_at |
compaction_checkpoint |
15 | Context compaction marker (metadata only) |
Inside item records, the type field tells you what you’re looking at:
function_call(3,275) /function_call_output(3,274) — tool calls and their results, paired bycall_idthinking(2,783) — the model’s private reasoning, a first-class recordmessage(1,283) — user / assistant / developer turnscommentary_text(537) — short status narration between tool callsmessage_parts(30) — multi-part message fragments
Roles are the familiar three — user (820), assistant (392), developer (101) — but the role alone doesn’t tell you who wrote a line. The source field does.
Source provenance: the runtime signs its work
This is the most unusual thing about the Muse format, and the reason the provider was fun to build. Every item record carries source, a dotted name for the runtime subsystem that emitted it. From the census:
| Source | Lines | What it is |
|---|---|---|
runtime.feed |
4,898 | Background feed-writer agent (proactive Feed posts) |
runtime |
3,525 | Interactive agent loop (user chat + subagent delegations) |
runtime.self_improvement |
2,503 | Background self-improvement passes |
scheduler.cron |
167 | Scheduled cron runs |
runtime.monitoring |
19 | Monitoring injections |
runtime.injected_context_change |
16 | Context updates pushed mid-session |
runtime.tool_guidance |
8 | Tool-use guidance |
runtime.skill_invalidation |
14 | Skill cache invalidations |
runtime.subagent_progress / runtime.subagent_monitor |
14 | Subagent lifecycle |
runtime.dev_notice, runtime.onboarding, runtime.merge_notice, runtime.todo_snapshot, runtime.background_exec |
~20 | Notices, onboarding, todos, background work |
vibe-replay uses record.source — not text sniffing — to decide what the runtime injected versus what the conversation actually was. A parser that only looks at roles would show you developer instructions and hidden wakes as if they were chat. The source field makes the distinction structural.
Thinking is a first-class citizen
2,783 thinking blocks out of 11,667 lines — nearly one in four lines is the model reasoning with itself. Muse doesn’t hide its scratch work in an undocumented field; thinking is a top-level item type. vibe-replay renders these as thinking scenes in the replay, so you can watch the agent change its mind.
What gets filtered
Two things never make the replay:
developermessages (101) — runtime instructions with sources likeruntime.dev_noticeandruntime.onboarding. They’re addressed to the model, not part of your conversation.- Compaction summaries — the 15
compaction_checkpointrecords are kept as metadata (timestamp and trigger), but the summary blob itself is not replayed. It’s a compressed memory of everything before the checkpoint — replaying it would surface distilled context the session itself never showed.
One thing is only downplayed: the runtime injects a huge [Subagent Context] blob as the first user message of delegated sessions. Discovery skips it when picking titles and first prompts, but the parser still renders it — so you’ll see it as the opening user message in a subagent’s replay.
Tool names need a map
On disk, tools show up as lowercase implementation names: exec, read, write, edit, browser_search, context_fetch, memory_search. The replay viewer expects canonical names (Bash, Read, Write, Edit, …) to build shell and diff scenes, so the provider normalizes names and remaps arguments — exec → Bash, read → Read, and so on. Unrecognized names (future builtins, MCP tools) pass through unchanged. Each function_call is paired with its function_call_output by call_id into a single tool-use block with result/error flags.
The most-called tools in the census read like a week of real agent life: exec (875), read (465), finish_step (313), write (181), browser_search (168), browser_open (160), edit (147).
Try it
npx vibe-replay@latest -p muse
Or open the full dashboard:
npx vibe-replay@latest -d
Muse is a good reminder that the interesting part of a transcript format isn’t the container — JSONL is JSONL — it’s the provenance model. One file per agent, no SQLite, no cloud round-trip, and every item stamped with the name of the subsystem that wrote it. Once you model the source field, thinking blocks, and the call/result pairing, the replay matches the session instead of the raw log.
For comparison, see the cloud-box JSONL of Grok Bot and Hermes’s profile-aware state.db.
Related
- PR #630 — native
provider-muse - vibe-replay.com
- Earlier in this series: Claude Code, Cursor, Codex, Cowork, Pi, OpenCode, Hermes, Grok Bot