spool
Skill0 starsChanged 2 months agoMITView on GitHub
Close a big-context session and reopen it in a fresh one through a lightweight, verified pointer — cheaper and safer than /compact or /clear. `spool` is a store-and-forward context carrier at the campaign tier (a meta-brief governing N sequential specs): it re-derives live state from the repo (git head, branch, working-tree deltas, completed vs ongoing specs, open beads, gate commands, any project-specific head/version anchor), diffs that against what the chat believed, and at a SINGLE human-in-the-loop checkpoint surfaces every unverifiable claim and every conflict (repo contradicts a chat-held belief) for you to adjudicate — it never asserts past a conflict. It then writes a self-contained launcher to an out-of-repo, ID-keyed, TTL-swept store (`~/.substrate/spool/`), so producing a spool commits nothing. `/substrate:spool --resume <id>` unspools into a fresh session: it re-verifies the volatile anchors (which may have advanced since capture), confirms the reconstructed understanding, then deletes the spool (`--keep` to retain). `/substrate:spool --list` shows what's on the spool. Sits one tier ABOVE /substrate:synthesize-session: synthesize captures per-spec LEARNING into doctrine + beads; spool carries campaign POSITION across specs. Degrades gracefully to a single-session snapshot when there's no campaign above it.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- iamvicliu/AgentHubtheir own version, first seen Nov 1, 2026
- spool-lab/spooltheir own version, first seen Oct 8, 2026
- anneheartrecord/charles-skilltheir own version, first seen Aug 6, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- spool-lab/spool★ 592same name
- iamvicliu/AgentHub★ 80same name
- anneheartrecord/charles-skill★ 3same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository14 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- add-doctrineSkill
- adoptSkill
- architect-specSkill
- deploySkill
- diagnoseSkill
- executeSkill
- graph-specSkill
- initSkill
- migrateSkill
- orchestrateSkill
- quick-specSkill
- serve-beadSkill
- synthesize-sessionSkill
The file
We split this skill into the rules it gives an agent
10 rules. 0 are common, 3 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 80
If the user proceeds, record context: compacted in the spool frontmatter so the resuming session knows.
- Distinctive: line 87
Single session (degraded): no campaign above this session — spool whatever position exists (current branch of work, open loose ends).
- Very specific: line 89
If ambiguous, ask which — end the question with [type 'default' to let me decide sensible defaults].
- Distinctive: line 120
Do not interrupt per-fact.
- Distinctive: line 185
Write the launcher to the store, run gc, and report:
- Very specific: line 212
Repo-match: If the spool's repo: ≠ the current git remote get-url origin (or pwd) → warn: you're resuming another project's spool.
- Very specific: line 225
Run sp_gc, then sp_list, and render a table: id · captured · repo · title.
- Very specific: line 230
MUST: treat a conflict (repo contradicts a chat belief) as higher-priority than a missing fact, and MUST surface both values at the HIL …
- Very specific: line 232
MUST: write the spool to an out-of-repo store (~/.substrate/spool/, or $SUBSTRATE_SPOOL_DIR).
- Very specific: line 235
MUST: delete the spool after a successful resume unless --keep is passed.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
/substrate:spool
Store-and-forward context carrier. Close a session that's accumulated a large, expensive context and reopen the position in a fresh session through a small, hand-verified pointer — not a lossy in-memory copy.
The thesis: durable state already lives on disk (specs in docs/tasks/, work in the bead tracker, decisions in docs/doctrine/, gates in substrate.yaml). /compact tries to keep a summary of that state in the context window; spool keeps state out of the window and ships a launcher into the record. That's the whole performance win — and the grounding step (re-derive facts from the repo, never from chat memory) is what makes it strictly safer than compact, which faithfully carries forward whatever the chat got wrong.
Position in the lifecycle
Tier 0 meta-architect over an N-phase brief (N sequential specs) ← /substrate:spool (campaign POSITION)
Tier 1 architect-spec scopes each phase → one spec
Tier 2 execute runs that spec → /substrate:synthesize-session (per-spec LEARNING)spool and synthesize-session never compete — they operate at different tiers. synthesize writes learning permanently into the repo (doctrine fixes, beads) at the leaf. spool carries campaign position across specs at the top, and its output is a throwaway launcher, not landed work. In a mature campaign, spool is nearly trivial: verify the live heads, list the open beads, point at the source of truth. It only gets heavy when state is still trapped in the chat.
Modes
| Invocation | Mode |
/substrate:spool | produce — capture verified position → print an ID |
/substrate:spool --resume <id> | resume — unspool into a fresh session → re-verify → confirm → delete |
/substrate:spool --resume (no id) | list available spools, then ask which to resume |
/substrate:spool --list | show what's on the spool |
--keep (with --resume) | resume without deleting |
The store
Out of every repo — so producing a spool commits nothing and no stale handoff can rot in git history.
SPOOL_DIR="${SUBSTRATE_SPOOL_DIR:-$HOME/.substrate/spool}" # override for tests- One file per spool:
$SPOOL_DIR/<slug>-<4hex>.md, where<slug>is the campaign/epic slug (kebab-case) and<4hex>is a random suffix (collision-safe, typeable). - Each file carries
repo:+captured:frontmatter so a global store safely holds spools from many projects, and--resumecan warn on a repo mismatch. - TTL sweep: on every
produceandlist, delete spools older than 14 days. Abandoned spools GC themselves.
Store operations (inline bash — spool does NOT depend on a scripts/ file, because scripts/ is not copied into target repos; this mirrors synthesize-session's inline-tempfile precedent):
SPOOL_DIR="${SUBSTRATE_SPOOL_DIR:-$HOME/.substrate/spool}"
sp_gc() { find "$SPOOL_DIR" -name '*.md' -type f -mtime +14 -delete 2>/dev/null || true; }
sp_new() { mkdir -p "$SPOOL_DIR"; id="$1-$(od -An -N2 -tx1 /dev/urandom | tr -d ' \n')"; printf '%s' "$id"; } # then write "$SPOOL_DIR/$id.md"
sp_list() { for f in "$SPOOL_DIR"/*.md; do [ -e "$f" ] || continue
id=$(basename "$f" .md)
printf '%s\t%s\t%s\t%s\n' "$id" \
"$(sed -n 's/^captured: //p' "$f" | head -1)" \
"$(sed -n 's/^repo: //p' "$f" | head -1)" \
"$(sed -n 's/^title: //p' "$f" | head -1)"; done; }
sp_get() { f="$SPOOL_DIR/$1.md"; [ -e "$f" ] || { echo "no spool: $1" >&2; return 1; }; cat "$f"; }
sp_del() { rm -f "$SPOOL_DIR/$1.md"; }When to REFUSE
| Signal | Redirect |
Not a git repo (git rev-parse fails) | spool grounds every fact against the repo — there's nothing to verify against. Abort with that explanation. |
--resume <id> and the spool file doesn't exist | Print the id + --list output so the user can pick a real one. Abort — do NOT fabricate a resume from memory. |
Workflow — PRODUCE (/substrate:spool)
Step 0 — Compaction self-check
spool's input is the model's own context window. If it has been auto-compacted, the campaign narrative is already degraded and the spool will inherit that. Self-assess: over the session's commits (git log --oneline <session-base>..HEAD), do you recall why each was made? If recall is sparse on >50%, print:
⚠ Context appears compacted. This spool will be degraded.
A spool is only as good as the context it distills.
Recommended: reconstruct from the source-of-truth files instead of spooling a thin context.
Proceed anyway? (y / n)If the user proceeds, record context: compacted in the spool frontmatter so the resuming session knows.
Step 1 — Establish scope
Identify what you're spooling:
- Campaign (default target): a Tier-0 meta-brief governing multiple sequential specs (e.g. an N-slice MVP). The
<slug>is the campaign/epic slug. - Single session (degraded): no campaign above this session — spool whatever position exists (current branch of work, open loose ends). Note it as a session snapshot, not a campaign.
If ambiguous, ask which — end the question with [type 'default' to let me decide sensible defaults].
Step 2 — Ground every fact (the anti-compact core)
Re-derive the durable anchors from the repo, never from chat memory. Run in parallel:
git rev-parse --abbrev-ref HEAD # branch
git rev-parse --short HEAD # tip
git status --porcelain # uncommitted working-tree deltas
git log --oneline -15 # recent history
ls docs/tasks/completed/ 2>/dev/null # shipped specs
ls docs/tasks/ongoing/ 2>/dev/null # in-flight specs
test -f substrate.yaml && cat substrate.yaml # gate commands (compile/test/lint)Beads (tracker-aware, same detection as synthesize-session): if .tbd/config.yml exists and a tbd binary is callable (command -v tbd or npx --no-install get-tbd --version), run tbd list (or the epic view bash docs/scripts/bead-graph.sh --epic <slug> when present) to enumerate open + carried-forward beads. Else scan docs/tasks/ongoing/<*>/bead.md.
Project-specific anchors: a campaign often tracks a head the generic probes don't cover — a migration/schema revision, a deploy tag, a version pin. Detect or ask; never hardcode and never assert from chat memory. If the chat believed a specific value (e.g. "migration head is X"), that belief goes into Step 3's diff, it does not go straight into the launcher.
Step 3 — Diff chat-belief vs repo
Build two lists:
- Unverifiable — a fact the chat asserts that the repo can't confirm.
- Conflict — the repo contradicts a belief the chat held. This is the dangerous case
/compactsilently propagates: the chat has an answer, and it's wrong (e.g. chat thought a slice was unbuilt;docs/tasks/completed/shows it shipped). Surface both the chat value and the repo value.
Empty lists are the happy path — say so.
Step 4 — Single batched HIL checkpoint
Do not interrupt per-fact. Gather everything and present one checkpoint:
Before I write the spool, resolve these:
CONFLICTS (repo disagrees with the session):
1. <fact> — chat believed «X», repo shows «Y». Keep: repo / chat / other?
...
UNVERIFIABLE (session asserted, repo can't confirm):
a. <claim> — verify, drop, or mark ‹unverified›?
...
[type 'default' to let me decide sensible defaults]Apply the resolutions. A conflict resolved toward the repo overwrites the chat belief; toward the chat requires the user to say why (recorded inline). Nothing enters the launcher unresolved — never assert past a conflict.
Step 5 — Compose the launcher
Pointers-first: the spool points into the source of truth, it does not copy it. Only verified live anchors go inline. Template:
---
id: <slug>-<4hex>
title: <one-line campaign position, e.g. "KEYLARK MVP — 4/8 slices shipped, slice 5 next">
repo: <git remote origin url, or pwd>
captured: <ISO8601 UTC>
context: full | compacted
---
# <Campaign> — session context handoff (as of <date>)
You're in <repo path>. Read <SoT files — e.g. CLAUDE.md, the campaign brief> FIRST —
they are the single source of truth; everything below is a verified pointer, not a copy.
## What this is
<2–3 sentences: what the project/campaign is, pointing at the SoT rather than restating it.>
## Position (VERIFIED against the repo this session)
- Branch / tip: <branch> @ <short-sha>
- Shipped: <specs in docs/tasks/completed/ — the campaign steps done>
- Next: <the next step, and where its brief/spec is or that it's unwritten>
- Gates: <compile / test / lint from substrate.yaml>
- <project anchor>: <migration head / deploy tag / version — verified, not recalled>
- Working tree: <clean, or the specific uncommitted deltas>
## Carried-forward loose ends
- <open bead id — one line> (inspect: <tracker command>)
- <parked question / follow-up>
- <‹unverified› items the user chose to keep, clearly flagged>
## How work gets built here
<point at the pipeline: architect-spec → graph-spec → execute|orchestrate → synthesize-session.
Name the rule-of-thumb the campaign has learned, if any.>
## Non-negotiable gotchas
<point at CLAUDE.md / doctrines for detail; inline ONLY the few that bite silently — commit/branch
policy, the gate commands, the append-only/verified anchors.>
## Likely next action
<the single most probable next step, and an explicit "confirm intent with the user before starting".>Step 6 — Persist + hand off
Write the launcher to the store, run gc, and report:
SPOOL_DIR="${SUBSTRATE_SPOOL_DIR:-$HOME/.substrate/spool}"; mkdir -p "$SPOOL_DIR"
id="<slug>-$(od -An -N2 -tx1 /dev/urandom | tr -d ' \n')"
# write the composed launcher (with the id filled into its frontmatter) to "$SPOOL_DIR/$id.md"
find "$SPOOL_DIR" -name '*.md' -type f -mtime +14 -delete 2>/dev/null || true # TTL gcPrint:
✔ Spooled. id: <id>
Nothing was committed to the repo — the spool lives at ~/.substrate/spool/<id>.md (out of the tree).
In a fresh session, resume with:
/substrate:spool --resume <id>
(--keep to retain it after resuming; /substrate:spool --list to see all spools.)Then you're clear to /clear and reopen fresh.
Workflow — RESUME (/substrate:spool --resume <id>)
- Fetch.
sp_get <id>. If the file doesn't exist → REFUSE (print--list). - Repo-match. If the spool's
repo:≠ the currentgit remote get-url origin(or pwd) → warn: you're resuming another project's spool. Ask before proceeding. - Re-verify the volatile anchors. A spool is a launcher, not gospel — the repo may have advanced since
captured:. Re-run the Step-2 probes for the fast-moving anchors (branch/tip,docs/tasks/completed|ongoing, open beads, the project head) and diff against the spool's claims. Report any drift explicitly:
Trust the repo, not the file, on every drifted anchor.⚠ Drift since this spool was captured (<captured>): - tip: spool said <a>, repo now at <b> - <bead x> is now closed - Confirm understanding. Print a short reconstruction back to the user: what this campaign is, verified position as of now (post-drift), the likely next action. This is the moment the user catches a bad spool before acting on it.
- Delete the spool (
sp_del <id>) — default. With--keep, retain it (e.g. to fan two fresh sessions off one capture).
Workflow — LIST (/substrate:spool --list)
Run sp_gc, then sp_list, and render a table: id · captured · repo · title. If empty, say so.
Constraints
- MUST ground every anchor against the repo in Step 2. A fact that can't be verified is either resolved at the HIL checkpoint or written
‹unverified›— never asserted with false confidence. This is the one property that makes spool safer than/compact. - MUST treat a conflict (repo contradicts a chat belief) as higher-priority than a missing fact, and MUST surface both values at the HIL checkpoint. Never silently prefer the chat's stale belief — that's the exact failure
/compactpropagates. - MUST batch the HIL into a single checkpoint (Step 4), not per-fact interrupts.
- MUST write the spool to an out-of-repo store (
~/.substrate/spool/, or$SUBSTRATE_SPOOL_DIR). MUST NOT commit anything to the target repo — producing or resuming a spool leaves the working tree untouched. (This is the inverse ofsynthesize-session, which commits.) - MUST stamp
repo:+captured:frontmatter so a global store is safe across projects, and MUST warn on a repo mismatch at resume. - MUST re-verify volatile anchors at resume (Step 3 of RESUME) and report drift. A spool goes stale the moment the repo advances; the mitigation is re-grounding at resume, not trusting the file.
- MUST delete the spool after a successful resume unless
--keepis passed. TTL-sweep (14d) on produce + list so abandoned spools self-GC. - MUST compose the launcher pointers-first: point at the SoT files (
CLAUDE.md, the brief, doctrines, the tracker); inline only verified live anchors and the few silently-biting gotchas. Restating durable content the repo already holds defeats the purpose. - MUST record
context: compactedif Step 0 detected compaction and the user proceeded. - MUST offer the
[type 'default' to let me decide sensible defaults]suffix on Socratic questions (scope choice, HIL adjudication). Binary confirmations (y/n, proceed-anyway) are exempt. - SHOULD stay light in a mature campaign — verify + point, don't re-narrate. Heaviness is a smell that state wasn't externalized (and that
synthesize-sessionshould run first).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

