README
ΒΆ
π Libretto Automata
The libretto is written first. The automaton performs it.
The panel β real terminal output, captured from the binary. tab switches where it acts.
An 18th-century automaton was a machine that played music by reading a score. A human wrote the notes; the machine executed them. It never improvised β it did exactly what the paper said. If the performance was wrong, the paper was wrong.
That is how this works. You write the spec. The agent performs it. When the output is bad, you fix the libretto, not the automaton.
What you get
Two things, and the first one is the point:
| The payload | an eight-phase spec-driven flow for Claude Code, as skills and commands |
| The CLI | a Go binary that symlinks the payload into ~/.claude |
flowchart LR
repo["this repository<br/>skills/ agents/ commands/"]
home["~/.claude<br/>one symlink per item"]
cc["Claude Code<br/>sessions"]
repo -->|"libretto install<br/>links, never copies"| home
home -->|reads| cc
What changes in practice: you say what you want, and before any code exists you get a contract β what "done" means, what is deliberately out of scope, and how each promise will be proven. You approve that, then a plan β how it gets built and which alternatives lost β with the checklist an agent cut from it without sitting through the argument. A fresh reviewer that saw none of the session checks the result, and the last question is whether to push.
flowchart LR
f1["1. find the work"] --> f2["2-4. write the spec,<br/>ask what is open"]
f2 --> s1{"stop: agree<br/>the spec"}
s1 --> f5["5. write the plan<br/><i>the how</i>"]
f5 --> c5["5β6. cut the tasks<br/><i>fresh agent</i>"]
c5 --> s2{"stop: agree the<br/>approach and the order"}
s2 --> f6["6. build and check"]
f6 --> f7["7. review, present"]
f7 --> f8["8. record"]
f8 --> s3{"stop:<br/>push?"}
Links are made per item, never per directory, so this coexists with anything else
installed into ~/.claude. Anything already there that this tool did not create is left
untouched and reported.
Install
Two commands, plus Go 1.26+.
go install github.com/pausf/libretto-automata/cmd/libretto@latest
libretto install # symlink every item into ~/.claude
That is the whole install. No tarball to fetch, no bootstrap step. Check it landed:
libretto status # every item's state, changes nothing
Updating
libretto update
Installs the newest version and relinks, so a release that adds a skill arrives linked.
The panel also tells you when a newer version exists, checked once a day and silent when it cannot check.
From a checkout instead
For working on the payload rather than with it:
git clone git@github.com:pausf/libretto-automata.git ~/gitrepos/libretto-automata
cd ~/gitrepos/libretto-automata
make build # stamps the version from git describe
make link # puts `libretto` on your PATH via ~/.local/bin
update pulls there instead of downloading, and the tree you are standing in is the
installation β why.
Your first run
The CLI is installed and you will not type it again for a while. The flow lives inside Claude Code β what follows are slash commands, not shell commands.
-
Say what you want.
/libretto-flow add a --json flag to statusIt reads the request, names the change, and writes it down before anything else. Hand it a tracker key instead β
/libretto-flow EUCAR-1234β and it reads the ticket. -
It stops at the spec. You get the contract: outcomes, what is out of scope, the constraints it found in your code, and the test that will prove each promise. This is the cheap place to disagree.
-
It writes the plan β how it gets built, and which alternatives lost. It does not wait.
-
It stops once the tasks are cut by a fresh agent that saw none of the conversation, which reports what the spec and the plan failed to answer. You agree both at once.
-
It builds, marking boxes as it goes, and leaves a proportionate test behind β one runnable check for real logic, none for a one-liner with no logic in it.
-
A fresh reviewer reads the result. It saw none of the session that wrote the code, which is the entire point, and it re-runs every proof the change touched.
-
It reports in the spec's own terms, including what it deliberately did not build and what would bring it back.
-
It asks once about pushing. That answer is always yours.
Those are the three places it waits, and each one is a question your terminal puts in front of you β not a line at the bottom of a report.
Nothing else to learn to start. The rest is knowing which door to use:
/libretto-status # what is in flight and what is queued β changes nothing
/libretto-flow # find the work and take it through the phases
/libretto-flow EUCAR-1234 # β¦starting from a ticket
/libretto-queue # capture ideas, one after another, and build none of them
/libretto-next # take the oldest queued idea into the flow
/libretto-review <pr-url> # review a PR/MR in a workspace that restores itself
/libretto-retro # spend the lessons ledger β fix what the flow got corrected on
/libretto-attacca # the same flow, without stopping β straight to a pushed PR
/libretto-attacca answers those stops in advance. Same phases, same spec, same plan
and tasks, same report β it just never waits, and it ends with the branch pushed and the
request open. A failing gate still stops it where it stands, and it never merges, tags or
releases; FLOW.md says why that boundary is where it is. attacca is what
a score writes to mean go on to the next movement without pausing.
The flow does not begin at a tracker. Phase 1 asks three sources in order β a change already in flight, a tracker key or URL, and what you said β and FLOW.md says why.
| Phase | Skill | |
|---|---|---|
| 1 | find the work β in flight, tracker, or asked for | find-work |
| 0Β·2Β·3 | does a spec even need to exist Β· the six pillars Β· one per subtask | write-spec |
| 5 | the plan β the approach, and the alternatives it beat | write-plan |
| 5β6 | the tasks β cut by a fresh agent, live state, one writer | write-tasks |
| 6 | build, with proportionate checks | build-and-check |
| 7 | present, including what was left out | present-work |
| 8 | commit, and make the spec true again | record-work |
Three rules hold at every phase, not one of them:
- ask β with a recommended option, real alternatives, and room to answer otherwise
- commit β per task, so a bisect lands somewhere meaningful
- evidence β nothing is true until it has been observed
Commands
libretto |
the panel β needs a terminal |
libretto status |
every item's state. Read-only, always. |
libretto install |
link everything. Idempotent; non-zero exit if anything was skipped. |
libretto uninstall |
show what this repo installed here β changes nothing |
libretto uninstall --yes |
take it back out |
libretto update |
install the newest version and relink β or pull, in a checkout |
libretto doctor |
what needs attention, plus what the payload expects here |
libretto prune |
show links whose source is gone β changes nothing |
libretto prune --yes |
remove them |
libretto preview |
print the panel once, no TUI |
libretto models |
which model each agent runs on, and how hard it thinks. Read-only. |
libretto models set <model> <agent>β¦ |
declare the model; --all for every agent |
libretto models effort <level> <agent>β¦ |
declare the effort; --all for every agent |
prune and uninstall are both dry by default β they change nothing until --yes. They
are not the same command: prune removes links whose item is gone, uninstall removes
links that are working.
Choosing a model per agent
Not every agent needs the same model. The review lenses that pattern-match over prose β design, tests β do the job on a cheap one; the lens looking for what an attacker can reach should not.
libretto models # who runs on what
libretto models set haiku review-lens-design review-lens-tests
libretto models set default work-reviewer # back to the session's model
And how hard it thinks
The model is the tier. Effort is the depth inside it β how often and how deeply the model reasons on each step. They are two keys, and either moves without the other: keeping an agent on Opus while its effort drops is the case the option exists for.
libretto models effort xhigh review-lens-security # deeper, on the lens that needs it
libretto models effort low review-lens-design # same model, cheaper reasoning
libretto models effort default work-reviewer # back to the session's effort
Haiku has no effort levels, so a level written there would do nothing. It is refused rather than written and ignored, and moving an agent onto Haiku clears any level it declared β the listing says so on the row it happened to.
And which levels an alias has depends on where your requests go. opus and sonnet
do not name the same model on every provider, so the alias is resolved from your
environment first and the listing names the provider it resolved for:
$ CLAUDE_CODE_USE_BEDROCK=1 libretto models
...
sonnet Sonnet 4.5 the everyday working model β no effort levels
opus Opus 5 everyday complex work; Max plans, metered on Pro
fable Fable 5 the hardest, longest-running work; priciest per token
resolved for Amazon Bedrock
An explicit ANTHROPIC_DEFAULT_SONNET_MODEL pin takes precedence over the provider
default, and a provider this build cannot resolve is treated as capable rather than
refused. Reading your environment is all it does β no request, and never a variable
holding a secret. Why, in DESIGN.md.
The panel has both behind its models row: mark agents with space, or all of them with
a, then m for the model and e for the effort.
models acts on the agents of the destination you name β ~/.claude/agents under
--global, <cwd>/.claude/agents under --project. Every agent there is listed and
editable, not just the ones this repository ships.
The values are aliases. The listing shows what each one resolves to, and when that was last checked:
models available (aliases; versions as of 2026-08):
default the session's model β whatever you are running
haiku Haiku 4.5 cheapest; fine for pattern-matching over prose β no effort levels
sonnet Sonnet 5 the everyday working model
opus Opus 5 everyday complex work; Max plans, metered on Pro
fable Fable 5 the hardest, longest-running work; priciest per token
resolved for the Anthropic API
effort available (weakest first; as of 2026-08; `models effort <level> <agent>β¦`):
default no key at all: whatever the session runs at
low short, scoped work that is not intelligence-sensitive
medium cost-sensitive work that can trade off some intelligence
high the balance point β and the host's own default
xhigh deeper reasoning at higher spend
max the deepest; prone to overthinking. Measure before adopting
Where it installs
Two axes, combinable β the tool and the scope:
| Tool | --global (default) |
--project |
|---|---|---|
--claude (default) |
~/.claude |
<this directory>/.claude |
--codex β skills only |
~/.agents |
<this directory>/.agents |
--opencode β all three |
~/.config/opencode |
<this directory>/.opencode |
So libretto install --codex --project links the skills into this directory's
.agents/, and plain libretto install still means what it always did. A
project-local install keeps a flow scoped to one repository without editing the
configuration every other project shares.
In the panel the two axes stay separate: the strip shows one row per tool under a
scope βΈ global label, tab switches the tool and s flips the scope for all
rows at once β the active row is gold.
Codex and OpenCode read the same Claude-compatible SKILL.md format, so the skills
link unchanged. OpenCode also reads commands β it looks for both command/ and
commands/, follows symlinks, and ignores frontmatter keys it does not know β so
commands/ links there too, unchanged and untransformed. Codex takes skills alone,
which is not an error.
OpenCode's agents are the one thing this tool writes rather than links. Its agent
frontmatter types tools as a map where ours is a comma-separated string, and it
rejects a file that disagrees rather than skipping it β so one symlinked agent would
break its config load. Each agent is therefore transformed on install: tools: and
model: are dropped, mode: subagent is added, and a x-libretto-source line records
which repo file it came from. That line is what makes the file provably ours, so
prune and uninstall can take it back out and will never touch an agent you wrote
yourself. Edit the agent in this repository and run libretto install again.
One detail worth knowing: OpenCode also reads ~/.claude/skills and ~/.agents/skills
directly, so a claude or codex install already reaches its skills β the --opencode
tool exists for setups that keep OpenCode's own directory as the source of truth, and
it is the only way to reach its commands.
Two flags on one axis is an error.
The five states
status reports one of these per item, per target:
| State | Meaning | Remedy |
|---|---|---|
linked |
our symlink, right destination | none |
missing |
in the repo, absent from the target | install |
wrong target |
our symlink, wrong destination | install repoints it |
conflict |
something foreign in the way | none β reported, never touched |
stale |
our symlink with no item behind it | prune |
Environment
CLAUDE_HOME |
Claude Code's root instead of ~/.claude. What makes the test suite safe. |
AGENTS_HOME |
the Codex destination's root instead of ~/.agents. Libretto-only; Codex does not read it. |
OPENCODE_HOME |
the OpenCode destination's root instead of ~/.config/opencode. Libretto-only. |
LIBRETTO_ROOT |
the repo location, instead of deriving it from the binary |
LIBRETTO_ASCII=safe |
swap the clef's quadrant glyphs for half blocks |
LIBRETTO_THEME |
dark or light, instead of detecting |
COLUMNS |
layout width when stdout is not a terminal |
There is no configuration file. A value that never varies is not configuration.
Learn more
| docs/FLOW.md | the eight phases, where the flow stops and why only there |
| docs/DESIGN.md | why it is built this way β symlinks, no --force, the palette |
.agents/specs/ |
the specification, one directory per capability |
| CONTRIBUTING.md | start here to contribute β the gates to run, and the release: label a merge needs |
| AGENTS.md | working on this repository: the gates, the commit rules, versioning |
| THIRD-PARTY.md | the vendored skills, their licences and versions |
Standing on other people's work
Seven skills ship with this repository, so the flow works on a machine that has nothing else installed. Copied unmodified, licence and version recorded in THIRD-PARTY.md:
- obra/superpowers β
writing-plans,test-driven-development,using-git-worktrees - DietrichGebert/ponytail β
ponytail,ponytail-debt. Decides how much gets built - JuliusBrussee/caveman β
caveman,caveman-commit. Decides how much gets said
Shipped is not required: nothing fails without them, and they prune like any other item. Why each is vendored rather than depended on, and why only the parts the flow calls by name, is in DESIGN.md.
Not managed here
CLAUDE.md and settings.json. Other tooling rewrites regions of those files, so they
stay hand-managed. Linking them would start a fight this tool cannot win.
Licence
MIT. Vendored items keep their own β see THIRD-PARTY.md.
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
libretto
command
Command lib is the Libretto Automata CLI.
|
Command lib is the Libretto Automata CLI. |
|
internal
|
|
|
agentmodel
Package agentmodel reads and writes the model each payload agent runs on.
|
Package agentmodel reads and writes the model each payload agent runs on. |
|
dist
Package dist finds the payload an installed copy links from, and moves it to a newer one.
|
Package dist finds the payload an installed copy links from, and moves it to a newer one. |
|
link
Package link enumerates repo items and reconciles them with a target.
|
Package link enumerates repo items and reconciles them with a target. |
|
repo
Package repo answers questions about the repository this tool lives in.
|
Package repo answers questions about the repository this tool lives in. |
|
target
Package target describes the agent installations that consume repo items.
|
Package target describes the agent installations that consume repo items. |
|
ui
Package ui renders the Libretto Automata panel.
|
Package ui renders the Libretto Automata panel. |