Hundreds of skills available. None of them in the way.
The catalog lists skill sources pinned to exact commits. Nothing in it is installed. An agent or a job runner asks for help with a task and gets back the few skills a judge thinks would matter, as files to read.
Sources and pins
[[source]]
slug = "ours"
path = "../packages" # a local directory, read in place
[[source]]
slug = "upstream"
ref = "github:owner/skills@<40-character commit>"
track = "main"
include = ["plugins/upstream/skills/*"] # optional: index only these
exclude = ["plugins/upstream/skills/setup-*"]
notes = "Shown to agents beside each skill from this source."
$ spindle catalog sync # export pinned sources, rebuild the index
$ spindle catalog sync upstream # or one source
$ spindle catalog list
$ spindle catalog update # what would a newer upstream commit change?
$ spindle catalog update upstream --apply
A Git source must pin a full commit. update compares the pin
with the tracked branch and lists the skills added, changed, and removed,
with any new static indicators. --apply rewrites the pin, and you
review it as an ordinary diff to the house. include and
exclude take globs over each skill's directory, relative to the
source root, so one plugin can be taken from a repository of many and its
harness-specific setup skills left out.
For every skill the index records its digest, the commit it came from, whether it hard-codes its own install location (and so cannot be used from a snapshot), and static indicators of network, credential, or shell use.
Find, use, report
$ spindle find "agents keep making the same mistake in this repo"
* 0.80 1.00 upstream/correct Find the mistakes agents keep repeating and make each impossible
0.45 0.92 other/retro Conduct a retrospective on a coding session
$ spindle use upstream/correct # the SKILL.md path; --print for the text
$ spindle report --outcome accepted --helped upstream/correct
$ spindle usage # counts and the prior per skill
The two numbers are the judge's probability and the text-match score
relative to the best match. use hands over a path, which works in
any harness that can read files and needs no install, lease, or reload. Use a
lease (try or borrow) only when a skill has to
appear in a harness's native skill list.
How a choice is made
Expand
An optional command rewrites the task as phrases a skill description would use.
[sourcing] expandRecall
Full-text search ranks the catalog per phrase and for the task, then fuses the rankings.
BM25 + RRFWeigh
Ranks are weighted by each skill's record. Kernel skills are left out.
usage priorJudge
Would this skill materially help? At or above 0.6 is selected. None is a fine answer.
--judge auto|jev|noneReceipt
The selection is saved with its candidates, scores, and skill digests.
~/.spindle/selectionsThe expander is any executable named in
house.toml that reads the task on stdin and prints short search
phrases, one per line. It is usually a small model call. A missing command,
a non-zero exit, a timeout, or empty output means no expansion.
--expand none skips it.
The judge is the Jev decision model: one call, a
calibrated probability per candidate. Enable it with
TYPESAFE_API_KEY, or name a key file with
SPINDLE_JEV_KEY_FILE or jev_key_file in
~/.spindle/house.toml. It is advisory and fail-soft: a missing
key, a timeout, or a malformed answer falls back to the text-match shortlist,
marked lexical, and says so.
Feedback
Every step appends to ~/.spindle/usage.jsonl: what was offered
and selected, what was read, and how the work was judged.
report --helped and --hurt are direct verdicts. An
outcome on a run that merely had a skill loaded counts as weak evidence. The
resulting prior lifts skills that keep helping and lowers skills that keep
being rejected, and spindle usage shows both.
For job runtimes
A runner that dispatches agent jobs needs one call before dispatch and one after grading:
$ spindle brief --json --task-file - --repo "$WORKTREE" < commission.md
$ spindle report --selection "$SELECTION" --job "$JOB_ID" \
--outcome accepted --loaded upstream/systematic-debugging
brief --json field | What the runtime does with it |
|---|---|
prompt_block | Append to the agent's prompt. It lists each offered skill with the path to read, so the agent pays for a skill only if it uses it. Empty means nothing was offered. |
skills | Store beside the job's own receipt: selection id, how it was chosen, delivery mode, and each skill's ref, digest, and judged probability. A reviewer then sees what guidance the producer had. |
selection | Pass to report after grading. |
read_roots | Add to the agent's read allow-list in a sandbox, so it can open an offered skill, or one it finds itself, by path. |
- No judge, no offer. A shortlist built from a whole
commission is mostly noise, so without a judge
briefreturns a short "skills on demand" section that tells the agent how to runfindonce it knows what it is about to do.--unjudged shortlist|nonechanges that. - Inline only when you must.
--inlineembeds skill text for sandboxes that cannot read outside their workspace, at most two skills and only ones scored 0.7 or higher. Embedded text is paid for whether or not it is used. - Attribution without plumbing. Set
SPINDLE_JOBin the agent's environment and everyfindanduseit runs is attributed to the job, soreport --jobjoins them. - Rules for bare runtimes.
--rulesreturns the house rules in the same answer, for runtimes that load no harness instruction file. - Never a gate. Treat any failure like an empty
prompt_block. Skill sourcing should never stop a job.
What Spindle will not do
- Run a source
- Sources are exported through the same
non-executing snapshot path as
spindle inspect: no hooks, no install scripts, no package managers. - Float a version
- A Git source without a full commit is
refused. Upstream work arrives only through
catalog update --apply. - Let a skill outrank the task
- The handover tells the agent a skill is guidance. It never overrides the task, its acceptance criteria, or the house rules.
- Hide what was offered
- Every selection has a receipt, and every offered skill a digest, so an evaluator can pin the exact bytes in play.