Using Spindle.
The daily workflow is one command. Everything else — building your own skills, tuning per surface, and wiring Spindle into your own infrastructure — is optional depth you reach for when you need it.
Install & activate
Spindle is a Python toolchain; skills arrive as pip-installable distributions. Install one, then mark it active so every later command reads its doctrine, channels, and profiles:
$ 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
Installing a distribution links its skills into your global skills directory
and records the event to Spindle's ledger. Activation writes the active
distribution name into $SPINDLE_HOME; with a single distribution
installed, Spindle will use it even before you activate.
Bind a repo — the daily step
This is the one command that stands between "skills installed" and "skills in use." It classifies the repo, resolves its blend, renders it for the harness and model, lints it, and materializes it:
$ 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. To improve a
skill's prose against that same gate, use spindle optimize.
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 |
| Compose | appclass · bind · unbind · advance run · liaison request · log |
| Doctrine | doctrine show · validate |
| Evaluate / tune | eval validate · run · show · optimize · rate |
| Learn / market | peers · verdict · roster · broker · acquisitions · scout |
| Infra sinks | ingest · gate file · from-result · fleet status · sync |
| Housekeeping | status · preempt · unpreempt · state show · rebuild |
Run any command with --help for its flags, or read the
bundle for the whole story in one file.