Shelves
A shelf is a content-addressed working-tree checkpoint. Unlike a git stash — a delta against HEAD stored as a hidden commit — a Muse shelf is a full snapshot of every tracked file at save time, written into the object store alongside a rich metadata envelope: intent type, resumability flag, agent provenance, domain-specific state, and optional expiry. Shelves are the canonical primitive for handing interrupted work between agents.
Overview
muse shelf save does three things atomically:
- Writes every modified tracked file into the object store as content-addressed blobs.
- Records a
ShelfEntryin.muse/shelf/as a per-entry msgpack file. - Restores the working tree to HEAD — leaving a clean slate for the next task.
Entries are named: either explicitly (muse shelf save my-wip) or
auto-generated as {branch}/{N:03d} (e.g. feat/auth/000).
The entry id is a sha256: of the entry content — two
shelves with the same files and metadata always produce the same ID.
Name resolution for all subcommands follows a consistent rule: no argument → index 0 (most recent); integer string → 0-based index; any other string → exact name lookup.
ShelfEntry
Every shelf entry is a TypedDict with the following fields:
| Field | Type | Description |
|---|---|---|
id | str (sha256:…) | Content-addressed ID derived from all other fields. Identical content always produces the same ID. |
name | str | Unique per repo. Auto-generated as {branch}/{N:03d} if not specified. |
snapshot | dict[path, object_id] | Complete path → sha256: object ID map of the shelved working tree. Full snapshot, not a delta. |
deleted | list[str] | Paths present in HEAD at shelf time that were absent from the working tree. Restored as deletions on apply. |
snapshot_id | str (sha256:…) | Content-addressed ID of the snapshot manifest. Used by GC for reachability. |
parent_commit | str (sha256:…) | HEAD commit at shelf time. Used to compute the correct diff on restore. |
branch | str | Branch active when the shelf was saved. |
created_at | str (ISO 8601) | Save timestamp in UTC. |
created_by | str | Agent ID (e.g. "claude-code") or "human". |
intent_type | str | One of interrupt, checkpoint, handoff, experiment, draft. Defaults to "checkpoint". |
intent | str | null | Free-text description of what was being worked on. Searchable by successor agents. |
resumable | bool | Whether another agent is allowed to apply and continue this work. Defaults to false. |
tags | list[str] | Arbitrary labels. Filter with muse shelf list --tags. |
expires_at | str | null (ISO 8601) | After this timestamp the entry is eligible for GC. null = never expires. |
domain_state | dict[str, any] | Per-domain plugin metadata. Populated by the active domain's shelf_state() hook. Empty dict if the domain has no shelf hook. |
JSON shape
# muse shelf read feat/billing/000 --json { "id": "sha256:4a7c3f...", "name": "feat/billing/000", "snapshot_id": "sha256:9e21b8...", "parent_commit": "sha256:d3f1a2...", "branch": "feat/billing", "created_at": "2026-04-30T14:22:11Z", "created_by": "claude-code", "intent_type": "interrupt", "intent": "renamed compute_total in 3 of 7 files", "resumable": true, "tags": ["billing", "refactor"], "expires_at": null, "deleted": [], "snapshot": { "src/billing.py": "sha256:c1d2e3...", "src/invoice.py": "sha256:f4a5b6...", "tests/test_billing.py": "sha256:77aa1c..." }, "domain_state": { "code": { "active_symbol": "src/billing.py::compute_total", "pending_lint_errors": 2, "index_dirty": true } } }
Intent types
intent_type tells any future agent — or human — why this
shelf exists. It shapes how entries appear in listings and how coordination tools
treat them.
| Value | Meaning | Typical use |
|---|---|---|
"interrupt" |
Work was interrupted mid-task (context limit hit, process killed, user paused) | Agent saves state before shutdown; a successor agent picks it up |
"checkpoint" |
Periodic progress snapshot during a long task | Agent saves every N commits so work can be rewound without losing all progress. Default when --intent-type is omitted. |
"handoff" |
Intentional transfer to another agent or human | Orchestrator explicitly routes work; --resumable is required for the successor to discover it |
"experiment" |
Speculative work — may be discarded | Agent tries an approach; saves before committing in case it needs to revert and try a different path |
"draft" |
Work-in-progress not yet ready for review | Human or agent saves a partial implementation to share or revisit later |
Saving a shelf
muse shelf save snapshots the current working tree, writes the
ShelfEntry to the object store, then restores HEAD — leaving a clean
working tree. No staged changes remain after save.
# minimal — auto-named, intent_type defaults to "checkpoint" muse shelf save --json
{
"id": "sha256:4a7c3f...",
"name": "feat/billing/000",
"intent_type": "checkpoint",
"resumable": false,
"files": 3
}
# named with full metadata — handoff to a successor muse shelf save refactor-wip \ --intent-type handoff \ --resumable \ -m "billing module half done — continue from Invoice.compute_total" \ --tags billing refactor \ --expires-in 86400 \ --json # periodic checkpoint mid-task muse shelf save --intent-type checkpoint \ -m "renamed 47/120 symbols" \ --json
Flags
| Flag | Type | Description |
|---|---|---|
--intent-type TYPE | str | One of the five intent types. Defaults to checkpoint. |
-m / --message MSG | str | Free-text description stored in intent. Shown in listings and used by agents to decide whether to resume. |
--resumable | flag | Mark this entry as safe for another agent to pick up via shelf list --resumable. |
--tags TAG… | str… | One or more labels. Filter with shelf list --tags. |
--expires-in SECONDS | int | Set expires_at to now + SECONDS. GC will remove the entry after this time. |
Inspecting shelves
List all entries
muse shelf list --json
[
{
"name": "refactor-wip",
"id": "sha256:4a7c3f...",
"branch": "feat/billing",
"intent_type": "handoff",
"intent": "billing module half done — continue from Invoice.compute_total",
"resumable": true,
"created_at": "2026-04-30T14:22:11Z",
"created_by": "claude-code",
"tags": ["billing", "refactor"],
"expires_at": "2026-05-01T14:22:11Z",
"files": 3
},
{
"name": "feat/billing/000",
"intent_type": "checkpoint",
"resumable": false,
"created_at": "2026-04-30T13:10:04Z",
"files": 3
}
]
Filter for resumable entries
muse shelf list --resumable --json muse shelf list --tags billing --json muse shelf list --resumable --tags security --json
Read a single entry (full detail)
# by name muse shelf read refactor-wip --json # by index (0 = most recent) muse shelf read 0 --json
shelf read returns the complete ShelfEntry including the
full snapshot manifest (all tracked paths and their object IDs) and
domain_state. Use shelf list for summary views.
Diff — preview before restoring
muse shelf diff refactor-wip --json
{
"restored": ["src/billing.py", "src/invoice.py"],
"already_current": ["tests/test_billing.py"],
"deleted": []
}
Always run shelf diff before shelf pop when resuming
work on a different machine or after intervening commits — the diff tells you
exactly which files will change before you commit to it.
Restoring a shelf
Three commands restore shelf state, differing only in whether the entry is kept:
| Command | Restores files | Removes entry | Use when |
|---|---|---|---|
muse shelf apply [NAME] | Yes | No | Restore and keep — non-destructive; safe for inspection loops |
muse shelf pop [NAME] | Yes | Yes | Standard resume path — restore and discard the entry |
muse shelf drop [NAME] | No | Yes | Abandon the work — remove the entry without touching the working tree |
Apply semantics
When applying, each path in the shelf's snapshot map is compared
to the current HEAD manifest:
- restored — file differs from HEAD; written from the object store to disk
- already_current — file at shelf version matches HEAD; no write needed
- deleted — path appears in
entry.deleted; removed from disk and staged
# inspect first, then pop muse shelf diff refactor-wip --json muse shelf pop refactor-wip --json
{
"restored": ["src/billing.py", "src/invoice.py"],
"already_current": ["tests/test_billing.py"],
"deleted": [],
"entry_removed": true
}
# address by index when you don't know the name muse shelf pop 0 --json # most recent muse shelf pop 2 --json # third most recent
Handoff patterns
Shelves are the canonical mechanism for transferring interrupted work between agents.
The resumable flag is the key signal: only entries with
resumable: true appear in muse shelf list --resumable,
so a successor agent can discover exactly what work is waiting for it.
Pattern 1 — Interrupted agent → successor agent
The most common pattern. Agent A hits a context limit mid-task, saves state, and exits. Agent B starts cold, discovers the shelf, and resumes from exactly where A left off.
## Agent A: interrupted (context limit hit) muse shelf save \ --intent-type interrupt \ --resumable \ -m "renamed compute_total → compute_invoice_total in 3 of 7 files; 4 remain" \ --tags billing \ --json
{
"id": "sha256:4a7c3f...",
"name": "feat/billing/000",
"intent_type": "interrupt",
"resumable": true,
"files": 3
}
## Agent B: discover what work is waiting muse shelf list --resumable --json
[
{
"name": "feat/billing/000",
"intent_type": "interrupt",
"intent": "renamed compute_total → compute_invoice_total in 3 of 7 files; 4 remain",
"branch": "feat/billing",
"created_by": "claude-code",
"resumable": true,
"files": 3
}
]
## Agent B: inspect what changed, then resume muse shelf diff feat/billing/000 --json muse shelf pop feat/billing/000 --json # working tree restored — Agent B continues from exactly where A stopped
Pattern 2 — Orchestrator → specialist handoff
An orchestrator prepares a focused context (checking out the right branch, staging relevant files, running impact analysis) then shelves it for a specialist agent to pick up with zero setup overhead.
## Orchestrator: set up context and hand off muse shelf save security-review \ --intent-type handoff \ --resumable \ -m "auth module staged for security audit — focus on validate_token" \ --tags security audit \ --expires-in 3600 \ --json ## Security agent: find work tagged for it muse shelf list --resumable --tags security --json muse shelf pop security-review --json
Branches and resumability
Shelves and branches each carry a resumable flag independently.
They serve different discovery purposes:
| Signal | What it means | How to query |
|---|---|---|
shelf entry: resumable=true |
This working-tree snapshot is safe for another agent to apply and continue. Files are staged and ready. | muse shelf list --resumable --json |
branch: resumable=true |
This branch was created for a task that may be incomplete. Another agent can check it out and continue committing. | muse branch --resumable --json |
A fully resumable handoff sets both: the branch carries intent about the overall task, and the shelf carries the in-flight working-tree state. A successor agent checks out the branch first, then pops the shelf:
## Agent A: save state and mark the branch resumable muse branch feat/billing \ --intent "compute_total rename — 4 files remain" \ --resumable muse shelf save \ --intent-type interrupt \ --resumable \ -m "renamed 3 of 7 files" ## Agent B: full discovery — find branches with in-flight shelves muse branch --resumable --json # [{"name": "feat/billing", "intent": "compute_total rename — 4 files remain", "resumable": true}] muse checkout feat/billing muse shelf list --resumable --json muse shelf pop 0 --json
Domain state
The domain_state field is populated by the active domain plugin when
muse shelf save runs. It preserves ephemeral context that cannot be
reconstructed from the file snapshot alone — things like a MIDI playback cursor,
an active symbol in the code editor, or pending lint errors.
Plugin interface
Domain plugins implement two optional hooks. If a plugin omits them,
domain_state is an empty dict.
class MyDomainPlugin(MuseDomainPlugin): def shelf_state(self) -> dict[str, object]: """Called by `muse shelf save`. Return any state the plugin needs to resume.""" return { "active_symbol": self.cursor.symbol_address, "pending_errors": len(self.lint.errors), "index_dirty": self.index.is_dirty, } def restore_shelf_state(self, state: dict[str, object]) -> None: """Called by `muse shelf apply/pop`. Restore from the saved state dict.""" if addr := state.get("active_symbol"): self.cursor.seek(addr) if state.get("index_dirty"): self.index.invalidate()
Shipped domain state examples
// code domain — active symbol, pending errors, index freshness "domain_state": { "code": { "active_symbol": "src/billing.py::compute_total", "pending_lint_errors": 2, "index_dirty": true } } // midi domain — playback cursor, loop region, active track "domain_state": { "midi": { "cursor_tick": 3840, "loop_start": 0, "loop_end": 7680, "active_track": "drums" } }
Storage
Shelf entries use a per-entry msgpack layout mirroring how commits and snapshots are stored:
.muse/
shelf/
sha256/
4a7c3f...ce91.msgpack # ShelfEntry for "feat/billing/000"
9b2d1a...f803.msgpack # ShelfEntry for "refactor-wip"
The path segment (sha256) is derived from the entry's
content-addressed ID prefix, making the layout forward-compatible with
future hash algorithms. Each file is capped at 64 MiB (the global msgpack
object size limit) and fsynced on write.
File blobs referenced by snapshot live in the same object store
as commit objects (.muse/objects/). A file already committed at
the same content costs zero additional storage in the shelf — deduplication
is automatic and transparent.
GC respects shelf reachability: any object referenced by a shelf entry's
snapshot is treated as a GC root and will not be pruned while
the entry exists. Run muse shelf drop NAME before
muse gc if you want those objects collected.
muse bundle create —
the bundle carries all shelf-referenced objects. The domain_state
metadata is embedded in each .msgpack entry and travels with the bundle.
CLI reference
All commands accept --json. Name arguments resolve as:
no argument → index 0 (most recent); integer string → 0-based index;
any other string → exact name match.
| Task | Command |
|---|---|
| Save working tree (auto-named checkpoint) | muse shelf save --json |
| Save with name, intent, and expiry | muse shelf save NAME --intent-type handoff --resumable -m "desc" --expires-in 3600 --json |
| Save with tags | muse shelf save --tags security audit --json |
| List all entries (summary) | muse shelf list --json |
| List resumable entries only | muse shelf list --resumable --json |
| Filter by tag | muse shelf list --tags security --json |
| Inspect one entry (full detail) | muse shelf read NAME --json |
| Preview what apply/pop would change | muse shelf diff NAME --json |
| Restore working tree (keep entry) | muse shelf apply NAME --json |
| Restore and remove entry | muse shelf pop NAME --json |
| Discard entry without restoring | muse shelf drop NAME --json |