@sublang/playbook
v0.4.2
Published
Reference CODE playbook — XState v5 FSM, host-agnostic runtime, and tmux-play adapter for a coder/reviewer/committer loop driven by GEARS spec items.
Maintainers
Readme
playbook
Skills made reliable through state machines and visualization.
playbook is a compiler stack and reference implementation for turning a natural-language procedure into a runnable, inspectable state-machine agent — a playbook — that orchestrates other AI agents (players) per a spec written in plain prose. Three phases take prose to runtime:
- text → GEARS (slc/text2gears.md) — normative spec items, one per state behavior, partitioned by trigger and prompt content.
- GEARS → FSM (slc/gears2fsm.md) — an XState v5 finite state machine; each gear maps to one captain-invoking state with a typed Captain actor contract.
- FSM → runtime (slc/link.md) — a host-agnostic
module that drives Boss turns through ports the host wires up
(cligent's
tmux-playis one such host).
The repository is itself an end-to-end worked example: this
package — @sublang/playbook — is the SDLC coding workflow,
generated from reference/sdlc/code.md
as its prose source. The runtime drives a coder / reviewer /
committer loop end to end.
Getting started — the reference CODE playbook
The reference is the canonical worked example —
CODE source →
gears →
FSM → runtime — with
the runtime ported to cligent's tmux-play host out of the box.
The compiled artifacts live under
reference/sdlc/code.playbook/,
the slc pipeline's <basename>.<pipeline>/ output directory.
Install (users)
Install the package globally. @sublang/cligent, the Claude and
Codex adapter SDKs, and xstate are direct dependencies, so a single
install pulls in the reference CODE playbook and its host:
npm install -g @sublang/playbookThen launch the reference playbook in a tmux-play session:
playbook-codeFor a one-shot run without a global install, use the same command through npx:
npx playbook-codeOn first run, playbook-code creates a commented user config at
${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml
from the bundled template, prints that path to stderr, then checks the
declared adapters before launching. Later runs reuse that file and do
not overwrite it.
Known adapter readiness is intentionally light: claude is ready when
local Claude Code auth exists or ANTHROPIC_API_KEY is set; codex is
ready when local Codex CLI auth exists or OPENAI_API_KEY is set. If a
known adapter is not ready, playbook-code prints its own help text
with the config path, auth pointers, and agent-swap recipe, then exits
without launching. You can view the same recovery text at any time:
playbook-code --helpThe seed template runs each agent in cligent's protected auto mode
(permissions.mode: auto), suppressing routine approval prompts.
Configure agents
Edit the seeded user config when you want different coding agents:
$EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook-code.config.yaml"Both CODE players can use claude or codex; other adapter ids are
passed through to tmux-play with a warning because playbook-code
does not know how to preflight their auth. The safe tuning points are
captain.adapter, captain.model, each player's adapter, and each
player's model. Keep captain.from and the players[].id values
fixed; the runtime binds to those host-configuration invariants per
PBRT-4 and derives the
<coder-llm> / <reviewer-llm> substitution strings from each
player entry's model when pinned and adapter otherwise — so the
Committer's commit-message trailers can name the concrete model
(e.g. claude-opus-4-7) rather than the adapter family (claude).
For example, this swaps the Coder to Codex and the Reviewer to Claude:
captain:
from: "@sublang/playbook/code/tmux-play"
adapter: claude
model: claude-sonnet-4-6
reasoningEffort: high
permissions:
mode: auto
players:
- id: coder # must stay `coder` — see PBRT-4
adapter: codex
model: gpt-5.5
reasoningEffort: xhigh
permissions:
mode: auto
- id: reviewer # must stay `reviewer` — see PBRT-4
adapter: claude
model: claude-opus-4-7
reasoningEffort: xhigh
permissions:
mode: autoNormal playbook-code runs use the seeded path above. If you need a
separate config file for a one-off run, pass it explicitly; this bypasses
the seed and readiness gate and forwards the arguments to tmux-play
verbatim, as pinned in
PBCODE-1:
playbook-code --config ./playbook-code.config.yamlInstall (contributors / from source)
Clone, install, and run the suite locally:
git clone https://github.com/sublang-ai/playbook.git
cd playbook
pnpm install
pnpm build
pnpm testpnpm install here installs the @sublang/cligent version pinned
in the checked-in pnpm-lock.yaml — the same version CI installs
via --frozen-lockfile, so contributor checkouts and CI agree.
The published package.json declares @sublang/cligent as
latest, so an end-user install with no lockfile (e.g., npm
install -g @sublang/playbook) instead resolves whichever cligent
release currently carries the latest dist-tag at install time
(see RELEASE-14). To bump the
contributor pin to today's latest, run
pnpm update @sublang/cligent and commit the resulting
pnpm-lock.yaml change — a plain pnpm install won't refresh the
pin, since pnpm sees specifier: latest in the lockfile as
already matching package.json and skips re-resolving the tag.
No local link required for any of this. To point pnpm at a local
cligent checkout
instead, copy
pnpm-workspace.yaml.example
into place; the override is gitignored so it never leaks into a
production install.
Drive a Boss turn against the source tree (uses the developer
tmux-play.config.yaml
that imports the compiled adapter via relative path):
pnpm exec tmux-play --config reference/sdlc/code.playbook/tmux-play.config.yamlpnpm exec resolves tmux-play from the package's local
node_modules/.bin/, so this works whether or not @sublang/cligent is
installed globally.
Running a Boss turn
The Boss pane takes plain-language turns; the judge classifies each
into an FSM event (start a coding turn, continue or summarize an IR,
interrupt to a named state, or nothing) per
PBRT-1.
When a player surfaces a clarifying question
the FSM parks at awaitBossReply and the pane shows the question; your
next turn is normally classified as the reply, or a fresh directive
abandons it (PBRT-2).
The Captain pane streams the state machine with a four-glyph vocabulary —
◆ ▸ ⮕ ⤷ per PBRT-3 — while
player prompts ride their own panes.
Embedding the runtime in your own host
The runtime is host-agnostic; the tmux-play adapter is one host.
Construct the runtime against your own ports:
import createPlaybookRuntime, {
type PlaybookPorts,
} from '@sublang/playbook/code/playbook';
const ports: PlaybookPorts = {
callPlayer: async (playerId, prompt, signal) => { /* … */ },
callJudge: async (prompt, signal) => { /* … */ },
emitStatus: async (message, data) => { /* … */ },
emitTelemetry: async ({ topic, payload }) => { /* … */ },
};
const runtime = createPlaybookRuntime({
coderPlayer: 'claude',
reviewerPlayer: 'codex',
});
await runtime.init(ports);
await runtime.handleBossInput({
text: 'Start fixing the bug',
signal: new AbortController().signal,
});
await runtime.dispose();See
code.playbook.test.ts
for the full range of port shapes (classifier, judge, abort, interrupt,
status/telemetry) the runtime is contract-tested against.
Workflow
playbook is itself spec-driven: the compiler phases are specs in slc/,
and the reference playbook is regenerated from its prose source. The
loop:
- Edit source. For the reference, that's
reference/sdlc/code.md. - Recompile gears per
slc/text2gears.mdinto the package'scode.gears.md. - Recompile FSM per
slc/gears2fsm.mdinto the package'scode.fsm.ts. - Sync runtime, tests, and downstream specs so
pnpm teststays green and the introspect contract holds 1:1 between gear items and FSM captain-invoking states. - Commit with co-author trailers per
specs/dev/git.md.
The behavioral contract between gears and FSM
(PLAYBOOK-1..6) and the runtime contract that
ports satisfy (PBRT-5..16) are pinned
in specs/dev/ and verified by tests under the reference
package.
Requirements
- Node.js ≥ 20.6.0 (the
playbook-codeshim usesimport.meta.resolve, unflagged since this release) - pnpm 9 (for the reference package)
- A configured
tmux-playhost (for live Boss turns) — requirestmuxandglowonPATH; cligent 0.4+ usesglowto render Markdown pane output and fails fast without it
Contributing
We welcome contributions of all kinds.
- 🌟 Star our repo if you find playbook useful.
- Open an issue for bugs or feature requests.
- Open a PR for fixes or improvements.
- Discuss on Discord for support or new ideas.
