Spindle is an evidence-bearing lifecycle and control plane for agent skills.
Inspect and try exact skill bytes before adopting them. Borrow them temporarily. Compose the effective set for a project, verify it at harness startup, realize the smallest evaluated instructions for each parent or child agent, and retain the evidence needed to update, distill, roll back, or retire safely. See the vision, roadmap, and completion goal.
📖 Documentation site: https://lavallee.github.io/spindle/ — the lifecycle,
composition model, evaluation approach, and practical guide. Machine-readable
surface for agents: llms.txt and a
single-file bundle.md.
Agent "skills" — the reusable instruction files a coding agent loads to do a job well — multiply fast. The instinct is to install every useful skill globally so it's always available. That instinct is right; the result is not. A global pile is never vetted as a set: it spends context budget on skills the repo will never use, combines skills from different authors in ways no author tested (a "pidgin"), pushes each skill toward one-size-fits-all bloat, ignores that different surfaces want different skills phrased differently, and lets guardrails erode.
Spindle treats a skill as a behavioral dependency with a lifecycle, not as a directory to copy and forget. Composition remains central: Spindle resolves a tight subset per surface, checks it before anything lands, and realizes the smallest suitable instructions independently for every parent or child model. Around that, it adds temporary leases, exact ownership, startup reconciliation, bounded evaluation, update-as-trial, and reversible custody.
The lifecycle is:
inspect → try/borrow → compose → bootstrap → realize → activate → evaluate
│
adopt, update, distill, retire
See the rationale for the full argument, including why that extra step pays for itself.
The same Spindle plugin is available from lyra-forge/marketplace in both
harnesses. Their marketplace commands and manifests are similar, but not
interchangeable:
# Claude Code
claude plugin marketplace add lyra-forge/marketplace
claude plugin install spindle@lyra-forge
# Codex
codex plugin marketplace add lyra-forge/marketplace
codex plugin add spindle@lyra-forgeStart a new session after installation. The installed plugin is namespaced, so
its initial invocation is $spindle:spindle in Codex or
/spindle:spindle in Claude Code. Ask it to set up Spindle for the current
repository; it will inspect first and preview the owned operator and startup-hook
changes. After setup and one more new session, the project-local control surface
is the shorter $spindle or /spindle.
The plugin carries a deterministic launcher for the exact pinned Spindle source, so this path does not require a separate Python package installation. Python 3.11 or newer is required. See the public marketplace for catalog update and uninstall instructions.
uv sync --extra dev
# Preview and install the small project-local operator plus stable startup hooks.
spindle harness setup --harness codex --here --dry-run
spindle harness setup --harness codex --here
# Start a new session. In Codex, ask: $spindle inspect a review skill for this task
# In Claude Code, ask: /spindle inspect a review skill for this taskThe operator skill is only the control surface: it asks
spindle harness context --here --json for current state and exact command
contracts instead of carrying a copy of the CLI manual in model context. Setup
snapshots its exact bytes, projects it to .agents/skills/spindle for Codex or
.claude/skills/spindle for Claude Code, and structurally merges stable startup
hooks. It preserves foreign harness state and does not adopt any other skill.
The operator projection is a machine-local generated symlink: do not commit it.
Review the generated project hook configuration separately; commit it when it is
shared team policy, or ignore it when the setup is intentionally local-only.
Use spindle harness status --harness codex --here to distinguish a current
operator, configured hooks, and a hook that a trusted harness session actually
ran. configured-unverified, blocked, and restart-required are not reported
as observed success. A nested Claude session launched from Codex (or the reverse)
is routed from the nearest harness process rather than inherited environment
markers alone.
# Understand a local or pinned candidate without executing it.
spindle inspect ./candidate/review \
--for "review this migration" --harness codex --here --json
# Preview, then run one disposable read-only session over the current blend.
spindle try ./candidate/review --harness codex \
--task-file task.md --here --dry-run
spindle try ./candidate/review --harness codex \
--task-file task.md --here
# Borrow the exact snapshot for two hours without adopting it.
spindle borrow ./candidate/review --harness codex \
--until 2h --here --dry-run
# Verify exact desired state before starting the harness.
spindle bootstrap --harness codex --here --checkinspect is read-only. try and borrow operate on content-addressed snapshots
and create leases, not adoptions. Startup compares the surface lock with every
locally observable harness scope, repairs only receipt-owned drift, and preserves
foreign state. Promote a successful trial explicitly with spindle adopt.
If you author managed distributions, the composition workflow remains available:
spindle dist install examples/spindle-sample/distributions/spindle-sample
spindle dist activate spindle-sample
spindle bind /path/to/repo --harness claudebind classifies the repository, resolves its channels into a coherent blend,
renders through harness/model profiles, checks guardrail preservation, and
records exact ownership for the harness-native projection.
- The skill lifecycle — the complete inspect-to-retire control loop.
- Candidate inspection and leases — local and pinned sources, trust preflight, try, borrow, expiry, and quarantine.
- Startup integrity — desired versus effective state, stable native hooks, conflicts, repair, and restart semantics.
- Runtime realization — independent parent/child routing and activation/delegation receipts.
- Behavioral evaluation — paired and named-arm experiments, runtime gates, minimalism, and distillation.
- Adapter compatibility — build-specific Claude and Codex certification.
- Organization policy and state custody — authority constraints, migration, export/import, recovery, and GC.
A repository binding is shared, but its agent sessions need not use the same model.
A runtime-aware skill keeps its invariant core in SKILL.md, declares evaluated
session mappings in spindle-realization.toml, and stores minimal residues under
overlays/. Spindle resolves one immutable realization from the model actually
served to that agent:
spindle realize review \
--session-id review-agent-7 \
--harness codex \
--model gpt-5.6-sol \
--effort high \
--role reviewer \
--strictParents and subagents resolve independently; no command mutates the installed package or a workspace-global “current model” file. Unknown or mismatched runtime coordinates receive the conservative invariant core and an explicit unresolved receipt. See Runtime skill realization for the manifest, routing, adapter, and receipt contracts. The broader skill lifecycle UX documents intent-centered discovery, zero-install trials, temporary leases, effective inventory, adoption, maintenance, and retirement.
bind writes an exact surface lock plus immutable ownership receipts for the
links it creates. Package identity and the rendered skill identity are recorded
separately, so an editable source changing beneath the same version is visible as
drift. A same-target link without a receipt remains foreign state; Spindle will
not adopt, overwrite, or remove it by inference.
Inspect the complete locally observable Claude or Codex discovery view without changing it:
spindle inventory --effective --here --harness codex
spindle why review --effective --here --harness codex
spindle diff --effective --here --harness codex --strictinventory and why are read-only. doctor --startup is also read-only unless
--reconcile-owned is explicit; even then it can remove only a stale or broken
symlink whose current path and target match an immutable Spindle ownership
receipt. Use --dry-run to see that plan. Plugin and managed/built-in scopes that
cannot yet be observed from local files are reported as opaque, not assumed
clean.
spindle doctor --startup --here --harness codex --reconcile-owned --dry-run
spindle package snapshot sample-planning --dry-runExact package snapshots live under $SPINDLE_HOME/cache/packages; surface locks,
ownership receipts, and their current-path indexes live under
$SPINDLE_HOME/surface-locks, $SPINDLE_HOME/ownership-receipts, and
$SPINDLE_HOME/ownership.
Use the portable pre-discovery boundary to verify or ownership-safely repair the exact locked blend before launching a harness:
spindle bootstrap --harness claude --here --check
spindle bootstrap --harness codex --here --reconcile-owned
spindle launch claude --here -- --model sonnetStable native hook kits then attest session start, resume/compaction, and subagent start without embedding mutable package pins:
spindle hooks plan --harness claude --scope repo --here
spindle hooks install --harness claude --scope repo --here --dry-run
spindle hooks install --harness claude --scope repo --here
spindle hooks status --harness claude --scope repo --here --effective
spindle hooks remove --harness claude --scope repo --here --dry-runAn identical hook without a Spindle ownership receipt remains foreign. Hook
installation and removal structurally preserve unrelated configuration. A hook
heartbeat distinguishes “definition present” from “trusted and actually ran,”
and post-discovery drift reports restart-required.
Same-name foreign skills remain untouched and block until their collision is removed or an exact, content-bound decision is explicit:
spindle conflict allow review --path /absolute/foreign/review \
--reason "Intentional user-scoped alternate" --harness claude --here --dry-run
spindle conflict list --harness claude --hereSee Startup integrity and native hooks for decision semantics, trust gates, fast cache-only callbacks, receipts, and safe removal.
Inspecting a candidate is read-only: no candidate code runs, no cache or lock is written for local input, and package facts remain separate from contextual fit hints. Remote inspection may populate only the verified source/provenance cache; it never activates the candidate.
spindle inspect ./candidate/review \
--for "review this migration" --harness codex --here --json
spindle inspect \
'skills.sh:owner/repository@main#review' \
--harness codex --here --jsonBorrow an exact snapshot for a bounded time without creating adoption state, or run one disposable read-only harness session and clean it up automatically:
spindle borrow ./candidate/review \
--harness codex --here --until 2h --dry-run
spindle release sha256:... --harness codex --here --dry-run
spindle try ./candidate/review \
--harness claude --model sonnet --task-file task.md --here --dry-run
spindle try \
'github:owner/repository@0123456789abcdef0123456789abcdef01234567#review' \
--offline --harness codex --model gpt-5.6-sol \
--task-file task.md --here --dry-runLeases overlay the current binding in the surface lock, point only at immutable
cached bytes, and name the exact owned projection they authorize. Startup
receipts report active and expired leases separately. Pre-launch reconciliation
cleans an expired lease; a resume/compact hook reports restart-required
instead of changing an already-discovered session. See
Candidate inspection, pinned sources, trials, and expiring
leases. Static preflight requires explicit flags for
executable, network, credential, and declared-tool authority. spindle source quarantine|unquarantine|status controls future lease eligibility while
preserving historical receipts.
A zero-exit disposable trial can be promoted into durable repository ownership. The preview names the exact package and skill digests, source pin, owner, update channel, authority ceiling, prior adoption, surface-lock delta, and the run and activation receipts that justify the transition:
spindle adopt sha256:<lease-id> --scope repo --here --dry-run --json
spindle adopt sha256:<lease-id> --scope repo --here
spindle health --harness codex --herePinned commits default to a manual update channel; a trial of a mutable Git ref
retains that ref as its refresh channel. Refreshes never replace the incumbent
directly. update plan resolves and preflights candidate bytes without changing
desired state, while update try exposes them under a temporary side-by-side
name and cleans them up after the run. Promote that successful update lease with
the same adopt command:
spindle update plan --harness codex --here --json
spindle update try review --harness codex --task-file task.md --here
spindle adopt sha256:<update-lease-id> --here
spindle rollback review --harness codex --reason "regression" --hereEvaluation claims belong to exact bytes and are deliberately not inherited by
an update. health reports unassessed contribution plus runtime, source,
package, cache, and inventory drift. disable, enable, deprecate, and
retire retain adoption and decision history; retirement removes only the
projection named by Spindle's ownership records. vendor and distill expose
read-only custody-path plans when continued upstream adoption is the wrong fit.
Harness support is certified per build. The default capability matrix is fail-closed for runtime facts until a digest-verified observation proves the actual served models, tool/policy envelopes, and independent parent/child activations:
spindle adapter matrix --harness codex --build 0.146.0 \
--require model-identity
spindle adapter certify observation.json --dry-run --json
spindle adapter verify certification.jsonThe checked-in M7 evidence certifies Claude Code 2.1.220 with an Opus 5 parent
and Sonnet 5 child, and Codex CLI 0.146.0 with a GPT-5.6 Sol parent and Terra
child. These are exact adapter-contract coordinates, not evergreen harness or
model-quality claims. See Adapter compatibility and
certification.
Organizations can constrain source providers, authority ceilings, required hook
scopes and attestations, exact adapter builds, and certification requirements in
.spindle/policy.toml:
spindle policy show --here --json
spindle policy check --here --harness codex --harness-build 0.146.0 --jsonPolicy is inspected without mutation and is enforced again at lease/adoption boundaries. See Organization lifecycle policy.
Legacy installs are audited and preserved, never silently converted into adoptions. Immutable receipts can rebuild mutable indexes, and deterministic state bundles merge only absent or byte-identical files:
spindle migrate plan --harness codex --here --json
spindle state export spindle-state.tar.gz --dry-run --json
spindle state import spindle-state.tar.gz --destination /new/state --dry-run
spindle state recover --json
spindle state gc --jsonGC is a separate, exact-plan operation over unreferenced verified cache bytes; it does not erase adoption or evaluation history. See Migration, recovery, and state custody.
Most harnesses discover project skills repo-locally, so bind symlinks into a
directory inside the target repo:
| Harness | Materialization target |
|---|---|
claude |
<repo>/.claude/skills/ |
codex |
<repo>/.agents/skills/ |
pi |
<repo>/.pi/skills/ |
hermes |
~/.hermes/skills/spindle/ (global) |
Hermes only discovers skills by recursively walking its global
~/.hermes/skills/ tree (following symlinks); it does not read repo-local
.hermes/skills directories. spindle bind <repo> --harness hermes therefore
symlinks the blend into a dedicated, spindle-owned category directory —
~/.hermes/skills/spindle/ by default, overridable with
SPINDLE_HERMES_SKILLS_DIR — so hand-curated Hermes categories are never
touched. The repo argument still names the binding surface; only the target
directory is global, and reconcile/unbind semantics are identical to the other
harnesses (only spindle-owned symlinks are ever removed).
Caveat: because the target is one shared directory, skills from different
surfaces bound with --harness hermes share a namespace there. And there is no
hermes dialect profile yet — skills land in their claude-reference dialect;
rendering/profile transforms for hermes are future work.
| Term | Meaning |
|---|---|
| package | A pip-installable project that declares skills and capabilities in [tool.spindle.package]. |
| distribution | A pip-installable bundle that declares [tool.spindle.distribution] and depends on packages. |
| cache | Immutable package bytes available locally; no runtime or maintenance authority. |
| install | Durable compatibility path that installs a distribution and links its global skills; unlike a lease or adoption. |
| channel | A versioned (scope × harness) manifest listing the skills a surface should receive. |
| surface | A repo or agent that consumes a skill blend. |
| profile | A harness/model renderer such as identity, terse, or trim. |
| bind | Materialize one resolved surface blend and record exact ownership. |
| realization | An immutable invariant core plus at most one session-matched skill overlay. |
| activation | Evidence that one exact agent loaded a realization under the observed model, tools, and policy. |
| intent card | Read-only package facts plus separately labeled current-environment fit analysis. |
| lease | Content-addressed, expiring runtime authority that does not create adoption state. |
| evaluation | A bounded behavioral claim for exact artifact and runtime coordinates. |
| adoption | Durable maintenance custody promoted from a successful exact lease/trial. |
| retirement | Historical adoption retained while only its exactly owned runtime projection is removed. |
| blend | The resolved skill subset for a surface, plus the absolutes in force. |
| doctrine | The versioned first-principles set (preferences, absolutes, meta-principles) skills are checked against. |
Full walkthrough of the data model and the pipeline: How it works.
examples/spindle-sample/
packages/sample-planning/ # [tool.spindle.package]
distributions/spindle-sample/ # [tool.spindle.distribution]
channels/system/system/claude/ # sample channel
profiles/ # harness/model render profiles
doctrine/ # minimal doctrine used by lint/render
peers/ verdicts/ scout/ roster/ # optional learning/marketplace scaffolds
spindle package new my-tools --dest ./packages/my-tools \
--skill clarify --skill review --capabilities planning
spindle dist new my-dist --dest ./distributions/my-dist \
--source-dir ../../ \
--package my-tools==0.1.0Spindle discovers installed packages through [tool.spindle.package] metadata and
installed distributions through [tool.spindle.distribution]. The
guide covers authoring skills,
writing channels, and tuning per-surface profiles.
The OSS base includes local reference implementations; private infrastructure lives in separate adapter packages that depend on Spindle rather than being imported by Spindle core.
spindle ingestwrites a topo-ordered task queue toSPINDLE_TASK_QUEUEor$SPINDLE_HOME/tasks.jsonlunlessSPINDLE_TASK_URLis set.spindle gate filewrites decision gates toSPINDLE_GATE_QUEUEor$SPINDLE_HOME/gates.jsonl.spindle scoutmaterializes per-peer prompts and emits commands fromSPINDLE_SCOUT_COMMAND.
Rendering and binding prove that a skill is available, not that it improves an agent. Spindle evaluation manifests run the same cases with and without a skill, randomize pair order from a seed, and write a receipt whose promotion gate uses only held-out behavioral scores.
spindle eval validate examples/evaluation-sample/eval.toml
spindle eval run examples/evaluation-sample/eval.toml
spindle eval show examples/evaluation-sample/receipts/<receipt>.jsonThe runner is an argv contract rather than a built-in model client. A local test harness or isolated executor can implement it while Spindle owns the manifest, hashes, pairing, evidence validation, and promotion decision. See Behavioral Skill Evaluations and the evaluation overview.
The site under docs/ is an artoo
artifact (see artifact.toml). It is served by GitHub Pages
directly from the /docs folder on main — no build step — so
Settings → Pages → Source = Deploy from a branch, main / docs is all it
needs. Edit the pages in docs/ and push.
uv run --extra dev pytestMIT licensed. Contributions welcome; see CONTRIBUTING.md. Release process and versioning: RELEASING.md.