Using Spindle.
Start with a read-only intent card, try exact bytes in one disposable session, borrow only when useful, and adopt only after the runtime evidence is sound. Composition and authoring are deeper paths, not prerequisites for trying a skill.
Drive it from Codex or Claude Code
Install Spindle from the public marketplace, then let its namespaced operator inspect and set up one small project-local operator as the ordinary control surface. The catalog name is shared, but each harness has its own marketplace manifest and commands:
# Codex: install once, then start a new session
$ codex plugin marketplace add lyra-forge/marketplace
$ codex plugin add spindle@lyra-forge
› $spindle:spindle set up Spindle for this repository
# Claude Code: install once, then start a new session
$ claude plugin marketplace add lyra-forge/marketplace
$ claude plugin install spindle@lyra-forge
› /spindle:spindle set up Spindle for this repository
The plugin includes the exact pinned Spindle source and a deterministic
launcher, so it needs no separate Python package install. Python 3.11 or newer
is required. Marketplace skills are namespaced to avoid conflicts; after
project setup and a new session, use the shorter $spindle in
Codex or /spindle in Claude Code.
The project-local operator reads live lifecycle state and exact command contracts from the CLI, so its prose stays small while Spindle evolves:
$ spindle harness setup --harness codex --here --dry-run
$ spindle harness setup --harness codex --here
# after starting a new Codex session
› $spindle show me which skills are active and why
# Claude Code uses its native slash invocation
› /spindle try this review skill once; do not adopt it
harness setup snapshots the operator's exact bytes into the
Spindle cache, projects an owned symlink into .agents/skills/spindle
or .claude/skills/spindle, and merges stable project startup hooks.
It can bootstrap even when unrelated ambient conflicts already block strict
startup; the operator then reports those conflicts rather than hiding or
deleting them. The operator link points to a machine-local immutable cache and
should not be committed. Review the hook configuration separately: commit it
as shared project policy, or ignore it for a local-only setup.
harness status reports operator version/custody, hook definition,
and observed hook heartbeat separately. For Codex, trust the project layer
and exact command through /hooks; definition presence alone is
configured-unverified. Run harness remove --dry-run
to preview removal of only Spindle-owned state.
Inspect, try, and borrow
Local inspection imports nothing, runs nothing, and writes no candidate state. A remote inspection may populate only a verified provenance/source cache; it does not activate the candidate.
$ spindle inspect ./candidate/review \
--for "review this migration" --harness codex --here --json
# preview, then run one disposable read-only session
$ spindle try ./candidate/review --harness codex \
--task-file task.md --here --dry-run
$ spindle try ./candidate/review --harness codex \
--task-file task.md --here
# keep the exact snapshot temporarily without adopting it
$ spindle borrow ./candidate/review --harness codex \
--until 2h --here --dry-run
$ spindle release sha256:<lease-id> --harness codex --here --dry-run
try retains startup, activation, run, and cleanup receipts but no
adoption. borrow adds one exact, expiring lease over the current
blend. Executable resources, network, credentials, and tool expansion are
separate grants; an inspection result is never permission to execute them.
Verify startup
Use the portable pre-discovery boundary even when native hooks are available. It compares the exact surface lock with the effective harness inventory and reconciles only receipt-owned projections:
$ spindle inventory --effective --harness codex --here
$ spindle bootstrap --harness codex --here --check
$ spindle bootstrap --harness codex --here --reconcile-owned
$ spindle launch codex --here --
# stable definitions; mutable pins remain in Spindle state
$ spindle hooks plan --harness codex --scope repo --here
$ spindle hooks install --harness codex --scope repo --here --dry-run
$ spindle hooks status --harness codex --scope repo --here --effective
Foreign state is inventoried and preserved. A same-name collision blocks
strict startup unless an exact path-and-content decision exists. A mismatch
found after discovery reports restart-required.
Adopt, update, and retire
A successful zero-exit lease can be promoted into durable maintenance custody. Updates are tested beside the incumbent and never inherit its evaluation claims:
$ spindle adopt sha256:<lease-id> --scope repo --here --dry-run --json
$ spindle health --harness codex --here
$ spindle update plan --harness codex --here --json
$ spindle update try review --harness codex --task-file task.md --here
$ spindle rollback review --harness codex --reason regression --here
$ spindle retire review --harness codex --reason obsolete --here --dry-run
Rollback and status changes append decisions rather than rewrite history.
Retirement removes only the current projection proven by an ownership receipt.
Use vendor or distill to inspect alternate custody paths
without changing adoption.
Managed distributions
Spindle also supports authored, pip-installable distributions carrying packages, doctrine, channels, and profiles. This is the composition path for a managed skill set, not a prerequisite for candidate trials:
$ uv sync --extra dev
# install the reference distribution and its packages, then activate it
$ spindle dist install examples/spindle-sample/distributions/spindle-sample
$ spindle dist activate spindle-sample
# confirm what's live
$ spindle dist list
$ spindle doctrine show
$ spindle skill list
$ spindle capability list
dist install is the durable compatibility path: it installs the
distribution, links its skills into Spindle's global skill surface, and
records the event and ownership. Activation selects the doctrine, channels,
and profiles used for managed composition. Use inspect,
try, or borrow when you do not want a durable global
install.
Bind a managed blend
bind classifies a repository, resolves its active distribution
channels, renders the selected skills for the harness and model, checks the
blend, and materializes it with exact ownership:
$ spindle bind /path/to/repo --harness claude
# target a model tier (changes rendering density), or preview first
$ spindle bind /path/to/repo --harness claude --model frontier
$ spindle bind /path/to/repo --harness claude --dry-run
# see how Spindle classifies a repo without binding
$ spindle appclass /path/to/repo
# remove exactly the skills Spindle materialized
$ spindle unbind /path/to/repo --harness claude
The materialized skills land in the harness's native directory —
.claude/skills/ for Claude — so the agent discovers them with no
extra configuration. Useful flags: --dry-run (compute, don't write),
--no-render (select only, skip profiles), and --force
(materialize despite lint problems — use sparingly; it's the escape hatch past
the fail-closed gate).
Work ahead: advance & liaison
Because a bind is deterministic and cached, you can do it before
anyone needs it. The advance team binds every configured surface up
front — from an advance/surfaces.toml or a project registry — so
the common case is a warm cache, and the "extra step" is already paid.
# precompute blends for every known surface
$ spindle advance run --from-registry
# capture an ad-hoc need as a demand signal (optionally bind it now)
$ spindle liaison request /path/to/repo --intent "audit accessibility" --bind
$ spindle liaison log repo
The liaison records what a surface asked for, so a recurring one-off can graduate into a precomputed skill instead of being re-derived every time.
Build your own
A package is an ordinary pip project with a [tool.spindle.package]
table; a distribution is one with [tool.spindle.distribution] that
depends on packages. Scaffold both:
$ 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.0
Author a skill
A skill is a directory with a SKILL.md: YAML frontmatter plus a
short, canonical body. Keep it small; put guardrails as
ALWAYS/NEVER lines so rendering will protect them:
# packages/my-tools/my_tools/skills/design/SKILL.md
---
name: design
description: Convert a clarified request into an implementation design.
---
# design
Use after the request has a clear outcome and acceptance criteria.
1. Read the relevant code and existing conventions first.
2. Propose the smallest coherent change that satisfies the outcome.
3. Name the affected files, data contracts, and tests.
ALWAYS prefer existing local patterns over new abstractions.
Declare the package
# packages/my-tools/pyproject.toml
[tool.spindle.package]
name = "my-tools"
version = "0.1.0"
distribution = "my-dist"
skills = ["clarify", "design", "review"]
capabilities = ["technical-design", "plan-review"]
[[tool.spindle.package.sources]] # provenance, optional
peer = "upstream-lib"
url = "https://github.com/org/upstream-lib"
transposed_at = "2026-07-11"
Write a channel
A channel decides which of your installed skills a surface receives at a
given scope and harness. Place it under the distribution's
source_dir at
channels/<scope>/<name>/<harness>/channel.toml. The
system channel is the broad default; add cluster channels for app-types and a
repo channel to override a single surface.
# channels/system/system/claude/channel.toml
version = "0.1.0"
absolutes = ["A1", "A2"]
skills = ["clarify", "design", "review"]
[tiers]
review = "judgment"
Tune per surface with profiles
Add a harness profile (dialect) and a model profile (density) to change how skills render without touching their source. See Rendering for the transforms.
# profiles/codex/profile.toml
version = "1"
transform = "terse"
# profiles/models/frontier/profile.toml
version = "1"
transform = "trim"
tier = "frontier"
Evaluate a skill
Before you trust a new or edited skill, prove it helps. Write an
eval.toml, point it at a runner, and gate on held-out 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>.json
The full contract — the runner's env vars, the result JSON, and the promotion gate — is on the Evaluation page. Named-arm matrices add no-skill, invariant-core, candidate-overlay, incumbent, and ablation comparisons with explicit runtime gates:
$ spindle eval matrix validate matrix.toml
$ spindle eval matrix run matrix.toml
$ spindle eval distill classify ./candidate/review
$ spindle eval distill plan ./candidate/review --json
Use spindle optimize for the older paired-edit path. Use
eval distill stage for one bounded deletion proposal; staging never
changes adoption.
Policy, certification, and recovery
Harness claims are tied to one build. Organization policy can restrict source providers, authority ceilings, required hooks and attestations, exact adapter builds, and certification requirements:
$ spindle adapter matrix --harness codex --build 0.146.0
$ spindle adapter verify certification.json
$ spindle policy check --harness codex \
--harness-build 0.146.0 --here --json
# deterministic custody and crash recovery
$ spindle state export spindle-state.tar.gz --dry-run --json
$ spindle state recover --json
$ spindle state gc --json
$ spindle migrate plan --harness codex --here --json
Import merges only absent or byte-identical files. Recovery rebuilds mutable indexes from immutable receipts. GC applies an exact rechecked plan over unreferenced verified cache bytes. Migration audits legacy state without adopting or deleting it.
Extending Spindle
Spindle is open-core: the compose-render-materialize-evaluate machinery and
the contracts are public; the infrastructure behind them is meant to be your own.
The core ships local reference sinks (JSONL files under
$SPINDLE_HOME) and swaps them out through environment variables and
injectable callables. Private adapters depend on Spindle — never the reverse.
| Seam | Env var | Default |
|---|---|---|
| State root | SPINDLE_HOME | ~/.spindle |
| Active distribution | SPINDLE_ACTIVE_DIST_NAME / _DIR | the activated dist |
| Task sink | SPINDLE_TASK_QUEUE / SPINDLE_TASK_URL | local JSONL queue |
| Gate sink | SPINDLE_GATE_QUEUE | local JSONL queue |
| Scout runner | SPINDLE_SCOUT_COMMAND | illustrative default |
| LLM render / propose | ANTHROPIC_API_KEY | absent → those steps skip, not fail |
Around the core sits an optional learning & marketplace layer —
scout, peers, verdict,
roster, broker, gate,
ingest, fleet. Treat it as a set of extension seams:
discovery passes, a judgment store, a marketplace facade, decision-gate and
task queues, and cross-machine ledger sync. The public base wires the contracts
and offline defaults; the live providers are yours to supply.
CLI reference
The command surface, grouped by what it touches:
| Area | Commands |
|---|---|
| Distributions | dist list · show · install · uninstall · activate · new |
| Packages / skills | package list · show · new · skill list · show · capability list · show |
| Inspect / lease | inspect · try · borrow · release · source |
| Harness operator | harness setup · status · context · remove |
| Compose / activate | bind · bootstrap · launch · hooks · realize · inventory · why · diff |
| Maintain | adopt · update plan · try · health · rollback · disable · enable · deprecate · retire |
| Doctrine | doctrine show · validate |
| Evaluate / tune | eval validate · run · show · matrix · distill · optimize · rate |
| Adapter / policy | adapter matrix · certify · verify · policy show · check |
| Learn / market | peers · verdict · roster · broker · acquisitions · scout |
| Infra sinks | ingest · gate file · from-result · fleet status · sync |
| Custody | state show · rebuild · export · import · recover · gc · migrate plan · apply |
Run any command with --help for its flags, or read the
bundle for the whole story in one file.