Developer Docs Shelves
PHASE 08

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.

Not a stash. Git stash collapses state into a diff and loses domain-specific metadata. A shelf preserves the full working-tree snapshot alongside intent, agent provenance, branch context, and any domain plugin state needed to resume without re-deriving it. Two shelves with identical file content produce the same content-addressed ID — deduplication is automatic.

Overview

muse shelf save does three things atomically:

  1. Writes every modified tracked file into the object store as content-addressed blobs.
  2. Records a ShelfEntry in .muse/shelf/ as a per-entry msgpack file.
  3. 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:

FieldTypeDescription
idstr (sha256:…)Content-addressed ID derived from all other fields. Identical content always produces the same ID.
namestrUnique per repo. Auto-generated as {branch}/{N:03d} if not specified.
snapshotdict[path, object_id]Complete path → sha256: object ID map of the shelved working tree. Full snapshot, not a delta.
deletedlist[str]Paths present in HEAD at shelf time that were absent from the working tree. Restored as deletions on apply.
snapshot_idstr (sha256:…)Content-addressed ID of the snapshot manifest. Used by GC for reachability.
parent_commitstr (sha256:…)HEAD commit at shelf time. Used to compute the correct diff on restore.
branchstrBranch active when the shelf was saved.
created_atstr (ISO 8601)Save timestamp in UTC.
created_bystrAgent ID (e.g. "claude-code") or "human".
intent_typestrOne of interrupt, checkpoint, handoff, experiment, draft. Defaults to "checkpoint".
intentstr | nullFree-text description of what was being worked on. Searchable by successor agents.
resumableboolWhether another agent is allowed to apply and continue this work. Defaults to false.
tagslist[str]Arbitrary labels. Filter with muse shelf list --tags.
expires_atstr | null (ISO 8601)After this timestamp the entry is eligible for GC. null = never expires.
domain_statedict[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.

ValueMeaningTypical 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

FlagTypeDescription
--intent-type TYPEstrOne of the five intent types. Defaults to checkpoint.
-m / --message MSGstrFree-text description stored in intent. Shown in listings and used by agents to decide whether to resume.
--resumableflagMark 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 SECONDSintSet 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:

CommandRestores filesRemoves entryUse when
muse shelf apply [NAME]YesNoRestore and keep — non-destructive; safe for inspection loops
muse shelf pop [NAME]YesYesStandard resume path — restore and discard the entry
muse shelf drop [NAME]NoYesAbandon 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
Pair with coordination reservations. Reserve the symbol addresses the shelved work touches before saving, so no other agent modifies them while the shelf is waiting to be popped. See Phase 06 — Reservations.

Branches and resumability

Shelves and branches each carry a resumable flag independently. They serve different discovery purposes:

SignalWhat it meansHow 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.

Shelves are local-only. There is no remote shelf API on MuseHub yet. To share a shelf across machines, use 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.

TaskCommand
Save working tree (auto-named checkpoint)muse shelf save --json
Save with name, intent, and expirymuse shelf save NAME --intent-type handoff --resumable -m "desc" --expires-in 3600 --json
Save with tagsmuse shelf save --tags security audit --json
List all entries (summary)muse shelf list --json
List resumable entries onlymuse shelf list --resumable --json
Filter by tagmuse shelf list --tags security --json
Inspect one entry (full detail)muse shelf read NAME --json
Preview what apply/pop would changemuse shelf diff NAME --json
Restore working tree (keep entry)muse shelf apply NAME --json
Restore and remove entrymuse shelf pop NAME --json
Discard entry without restoringmuse shelf drop NAME --json