@sublang/playbook 1.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->
3
+
4
+ # Configuring agents
5
+
6
+ `playbook` reads one config at
7
+ `${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml`. The
8
+ first launch seeds it from the bundled starter and prints the path;
9
+ later launches reuse it untouched.
10
+
11
+ ```sh
12
+ $EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml"
13
+ ```
14
+
15
+ ## Anatomy
16
+
17
+ The config is top-level (no `config:` wrapper): a `captain` agent (it
18
+ runs both visible Captain work and hidden judge calls), optional
19
+ `layout` / `notifications` / `theme`, and a `playbooks` map of enabled
20
+ playbooks.
21
+
22
+ Each `captain` or `players.<role>` value is either an adapter shorthand
23
+ (`claude`, `codex`) or a block carrying that agent's own `adapter`,
24
+ `model`, `effort`, and `permissions`. Settings are inline per agent, so
25
+ tuning one player never changes another
26
+ ([DR-021](../specs/decisions/021-inline-agent-settings.md)). Other
27
+ adapter ids pass through to `tmux-play` with a warning, because
28
+ `playbook` cannot preflight their auth.
29
+
30
+ Within a `playbooks.<id>` block, `from` (the registry module), `command`
31
+ (an optional slash-command override), and `players` are launcher-owned;
32
+ every other key is that playbook's option slice. The launcher injects
33
+ the rest — you do not write host wiring by hand.
34
+
35
+ The seeded config runs the Coder on Claude Opus 4.8 1m and the Reviewer
36
+ on GPT-5.5:
37
+
38
+ ```yaml
39
+ captain:
40
+ adapter: claude
41
+ model: claude-opus-4-8
42
+ effort: high
43
+ permissions:
44
+ mode: auto # protected auto mode for the Claude Captain
45
+
46
+ playbooks:
47
+ code:
48
+ from: '@sublang/playbook/code/registry'
49
+ players:
50
+ coder:
51
+ adapter: claude
52
+ model: claude-opus-4-8[1m]
53
+ effort: xhigh
54
+ permissions:
55
+ mode: auto # protected auto mode for the Claude Coder
56
+ reviewer:
57
+ adapter: codex
58
+ model: gpt-5.5
59
+ effort: xhigh
60
+ permissions:
61
+ mode: auto
62
+ writablePaths:
63
+ - .git # allow git metadata writes under Codex auto mode
64
+ committer: coder # which role commits — `coder` or `reviewer`
65
+ ```
66
+
67
+ `committer` is CODE's one option: an alias naming which role runs the
68
+ commit turn (fallback semantics:
69
+ [PBRT-8](../specs/dev/playbook-runtime.md#pbrt-8)). Each role's per-run
70
+ prompt names its pinned `model`, else its `adapter`
71
+ ([PBRT-4](../specs/user/playbook-runtime.md#pbrt-4)), so commit trailers
72
+ credit the concrete model rather than the adapter family.
73
+
74
+ ## Choosing the Captain agent
75
+
76
+ Hidden routing and adjudication calls run tool-free, which is what keeps
77
+ the Captain routing instead of doing the work itself. Claude enforces
78
+ that at the provider level. The Codex adapter cannot — it rejects any
79
+ tool list — so a `captain:` on `codex` falls back to a prompt-level
80
+ restriction
81
+ ([DR-013 A1](../specs/decisions/013-routing-only-captain-control.md#addendum-a1-prompt-level-isolation-for-adapters-without-tool-enforcement)).
82
+ Codex remains a good choice for *players*, where full tools are wanted.
83
+
84
+ Adapter readiness is intentionally light: `claude` is ready with local
85
+ Claude Code auth or `ANTHROPIC_API_KEY`; `codex` with local Codex CLI
86
+ auth or `OPENAI_API_KEY`. A known adapter that is not ready blocks the
87
+ launch and prints the help text.
88
+
89
+ ## Per-launch overlays
90
+
91
+ To retune one launch without editing the file, overlay a fragment in
92
+ the same format with `--with` (repeatable, later files win; maps merge
93
+ recursively, other values replace):
94
+
95
+ ```sh
96
+ playbook --with fast-lineup.yaml
97
+ ```
98
+
99
+ ```yaml
100
+ # fast-lineup.yaml — swap the Coder for one run; nothing is written back.
101
+ playbooks:
102
+ code:
103
+ players:
104
+ coder:
105
+ adapter: codex
106
+ model: gpt-5.5
107
+ effort: medium
108
+ permissions:
109
+ mode: auto
110
+ # The seeded `committer` is `coder`, so this role runs the commit
111
+ # turn; a Codex agent needs the `.git` grant to write git metadata.
112
+ writablePaths:
113
+ - .git
114
+ ```
115
+
116
+ Fragments merge into the agent block rather than replacing it, so
117
+ settings the base defines and the fragment omits — here `mode: auto` —
118
+ survive. Anything the adapter itself requires must still be stated: a
119
+ role switched to `codex` needs its own `writablePaths` grant, because
120
+ the base Claude block had no reason to carry one.
121
+
122
+ The global file is never modified, and `--with` is not forwarded to
123
+ `tmux-play` ([PBCLI-25](../specs/user/playbook-cli.md#pbcli-25)).
124
+
125
+ ## Defaults for `playbook run`
126
+
127
+ An optional top-level `run` block supplies the non-interactive host's
128
+ lineup so you stop retyping flags — `run.captain`, `run.players.<role>`,
129
+ and a `run.player` catch-all for any other required role, each an
130
+ `<adapter>[:<model>][@<effort>]` string. Flags win per role, and
131
+ `resume` always keeps the lineup stored with the parked session
132
+ ([PBCLI-28](../specs/user/playbook-cli.md#pbcli-28),
133
+ [DR-017](../specs/decisions/017-run-defaults-config.md)).
134
+
135
+ ```yaml
136
+ run:
137
+ captain: claude:claude-opus-4-8@high
138
+ players:
139
+ coder: claude:claude-opus-4-8[1m]@xhigh
140
+ ```
141
+
142
+ ## Migrating from `profiles`
143
+
144
+ Configs written before 3.0.0 carried a top-level `profiles` map. The
145
+ launcher rewrites such a config on the next launch — inlining each
146
+ profile's settings into the agent that named it, keeping your comments,
147
+ and saving the original as `<config>.bak` — then continues. Nothing to
148
+ do by hand.
149
+
150
+ ## Using a raw tmux-play config
151
+
152
+ For a one-off, pass a raw `tmux-play` config explicitly. This bypasses
153
+ the seed, composition, and readiness gate, forwarding arguments to
154
+ `tmux-play` verbatim ([PBCLI-1](../specs/user/playbook-cli.md#pbcli-1)):
155
+
156
+ ```sh
157
+ playbook --config ./tmux-play.config.yaml
158
+ ```
@@ -0,0 +1,161 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->
3
+
4
+ # Embedding the runtime in your own host
5
+
6
+ The playbook runtime is host-agnostic; cligent's `tmux-play` adapter is
7
+ one host, and [spex](https://github.com/sublang-ai/spex) (the desktop
8
+ app) is another. This guide shows how to wire a playbook runtime into
9
+ your own host.
10
+
11
+ > **Release note:** this guide targets the semver-stable 1.0 six-port
12
+ > contract; see the [CHANGELOG](../CHANGELOG.md) for migration details.
13
+
14
+ ## The runtime contract
15
+
16
+ The port and runtime contracts live in the type-only module
17
+ [`@sublang/playbook/runtime`](../src/runtime.ts) — a public,
18
+ semver-stable surface (`PlayerResult`, `PlaybookPorts`,
19
+ `PlaybookRuntime`, `PlaybookSession`, `PlayerCallOptions`,
20
+ `CaptainCallOptions`, `CaptainResult`, `PlaybookTraceEvent`, and
21
+ `PlaybookRuntimeFactory`) that imports no CODE or FSM types, so a host
22
+ satisfies it once and inherits every playbook. The CODE runtime
23
+ re-exports `PlayerResult`, `PlaybookPorts`, `PlaybookSession`, and
24
+ `PlaybookRuntime` from `@sublang/playbook/code/playbook`;
25
+ `PlaybookRuntimeFactory` is available from `@sublang/playbook/runtime`.
26
+
27
+ Generated linked runtimes reuse the XState integration engine exposed
28
+ as `@sublang/playbook/xstate-runtime`, including strict JSON
29
+ validation, normalized snapshots, quiescence waiting, and the
30
+ nested-playbook bridge.
31
+
32
+ ## Constructing a runtime against your own ports
33
+
34
+ ```ts
35
+ import createPlaybookRuntime from '@sublang/playbook/code/playbook';
36
+ import type {
37
+ CaptainCallOptions,
38
+ CaptainResult,
39
+ PlaybookPorts,
40
+ } from '@sublang/playbook/runtime';
41
+ import { randomUUID } from 'node:crypto';
42
+ import PQueue from 'p-queue';
43
+
44
+ declare const captainAdapter: {
45
+ run(
46
+ prompt: string,
47
+ options: {
48
+ signal: AbortSignal;
49
+ visibility: 'visible' | 'hidden';
50
+ resume: string | false;
51
+ allowedTools?: readonly string[];
52
+ },
53
+ ): Promise<CaptainResult>;
54
+ };
55
+
56
+ // Construct one host-wide lane and reuse it for every runtime. Passing each
57
+ // call's signal to both the lane and adapter cancels queued and active work.
58
+ const captainLane = new PQueue({ concurrency: 1 });
59
+
60
+ async function runCaptain(
61
+ prompt: string,
62
+ signal: AbortSignal,
63
+ options: CaptainCallOptions,
64
+ ): Promise<CaptainResult> {
65
+ return await captainLane.add(
66
+ () => captainAdapter.run(prompt, { signal, ...options }),
67
+ { signal },
68
+ );
69
+ }
70
+
71
+ const ports: PlaybookPorts = {
72
+ callPlayer: async (playerId, prompt, signal, { resume }) => {
73
+ // `resume === false` starts fresh; a string selects that player's
74
+ // prior backend conversation. Return the adapter's next token.
75
+ return { status: 'ok', finalText: 'done', resumeToken: 'next-token' };
76
+ },
77
+ callCaptain: async (prompt, signal, options) => {
78
+ // Forward every option exactly: omission preserves configured tools, while
79
+ // an explicit empty allowlist requests a tool-free call and must fail closed
80
+ // when the adapter cannot enforce it.
81
+ return await runCaptain(prompt, signal, options);
82
+ },
83
+ callJudge: async (prompt, signal) => {
84
+ // Judge work is hidden control work: run it fresh and tool-free.
85
+ const result = await runCaptain(prompt, signal, {
86
+ visibility: 'hidden',
87
+ resume: false,
88
+ allowedTools: [],
89
+ });
90
+ if (result.status !== 'ok' || result.finalText === undefined) {
91
+ throw new Error(result.error ?? 'Judge call failed');
92
+ }
93
+ return result.finalText;
94
+ },
95
+ callPlaybook: async (request, signal) => {
96
+ throw new Error('No nested playbook host configured');
97
+ },
98
+ emitStatus: async (message, data) => {
99
+ /* … */
100
+ },
101
+ emitTelemetry: async ({ topic, payload }) => {
102
+ /* … */
103
+ },
104
+ };
105
+
106
+ const runtime = createPlaybookRuntime({
107
+ coderPlayer: 'claude',
108
+ reviewerPlayer: 'codex',
109
+ });
110
+
111
+ const playbookSessionId = randomUUID();
112
+ await runtime.init({
113
+ sessionId: playbookSessionId,
114
+ playbookId: 'code',
115
+ rootSessionId: playbookSessionId,
116
+ depth: 0,
117
+ ports,
118
+ });
119
+ await runtime.handleBossInput({
120
+ text: 'Start fixing the bug',
121
+ signal: new AbortController().signal,
122
+ });
123
+ await runtime.dispose();
124
+ ```
125
+
126
+ ## Sessions and traces
127
+
128
+ Every init-to-dispose lifecycle is one playbook session. Its
129
+ `playbook.trace` telemetry carries that immutable ID plus a contiguous
130
+ sequence across exact Boss input, judge/player calls, FSM transitions,
131
+ visible Captain work, nested playbook calls, status, settlement, and
132
+ disposal. Each resolved player starts fresh in a new playbook session
133
+ and then resumes only from the latest opaque `resumeToken` its adapter
134
+ returned; trace data and tokens never enter Boss-visible status text.
135
+ Because trace observers do receive opaque resume tokens, persisted
136
+ traces should be protected as sensitive data.
137
+
138
+ See
139
+ [`code.playbook.test.ts`](../reference/sdlc/code.playbook/code.playbook.test.ts)
140
+ for the full range of port shapes (classifier, judge, abort, interrupt,
141
+ status/telemetry) the runtime is contract-tested against.
142
+
143
+ ## Reading the published spec contracts
144
+
145
+ The authored compiler-phase specs ship in the package and are exposed
146
+ as a public, semver-stable surface under `@sublang/playbook/slc/*`.
147
+ Resolve and read one with `import.meta.resolve` plus `fs`:
148
+
149
+ ```ts
150
+ import { readFile } from 'node:fs/promises';
151
+ import { fileURLToPath } from 'node:url';
152
+
153
+ const url = import.meta.resolve('@sublang/playbook/slc/link.md');
154
+ const link = await readFile(fileURLToPath(url), 'utf8');
155
+ ```
156
+
157
+ The four specs are [`slc/text2gears.md`](../slc/text2gears.md),
158
+ [`slc/gears2fsm.md`](../slc/gears2fsm.md),
159
+ [`slc/link.md`](../slc/link.md) — the FSM-to-runtime contract that
160
+ `@sublang/playbook/runtime` projects into TypeScript — and
161
+ [`slc/optimize.md`](../slc/optimize.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sublang/playbook",
3
- "version": "1.3.0",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "Composable XState v5 playbook runtime with compiled Captain, CODE, and DISCUSS workflows driven by GEARS specs.",
6
6
  "license": "Apache-2.0",
@@ -39,6 +39,8 @@
39
39
  "slc/text2gears.md",
40
40
  "slc/optimize.md",
41
41
  "reference/sdlc/captain.md",
42
+ "reference/sdlc/code.md",
43
+ "reference/sdlc/discuss.md",
42
44
  "reference/sdlc/captain.playbook/captain.gears.md",
43
45
  "reference/sdlc/captain.playbook/captain.fsm.ts",
44
46
  "reference/sdlc/captain.playbook/captain.fsm.js",
@@ -74,7 +76,8 @@
74
76
  "reference/sdlc/discuss.playbook/discuss.playbook.d.ts",
75
77
  "reference/sdlc/discuss.playbook/discuss.registry.ts",
76
78
  "reference/sdlc/discuss.playbook/discuss.registry.js",
77
- "reference/sdlc/discuss.playbook/discuss.registry.d.ts"
79
+ "reference/sdlc/discuss.playbook/discuss.registry.d.ts",
80
+ "docs"
78
81
  ],
79
82
  "bin": {
80
83
  "playbook": "reference/sdlc/code.playbook/bin/playbook.js"
@@ -117,6 +120,7 @@
117
120
  "scripts": {
118
121
  "build": "tsc",
119
122
  "test": "vitest run",
123
+ "test:acceptance": "vitest run --config vitest.acceptance.config.ts",
120
124
  "playbook": "node reference/sdlc/code.playbook/bin/playbook.js"
121
125
  },
122
126
  "publishConfig": {
@@ -126,7 +130,7 @@
126
130
  "dependencies": {
127
131
  "@anthropic-ai/claude-agent-sdk": "^0.3.154",
128
132
  "@openai/codex-sdk": "^0.139.0",
129
- "@sublang/cligent": "^0.15.0",
133
+ "@sublang/cligent": "^0.16.0",
130
134
  "@sublang/spex": "^0.3.0",
131
135
  "p-queue": "^9.3.1",
132
136
  "xstate": "^5.19.4",
@@ -0,0 +1,105 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai> -->
3
+
4
+ # Code
5
+
6
+ Players:
7
+
8
+ - Coder
9
+ - Reviewer
10
+ - Committer = Coder | Reviewer
11
+
12
+ ## Coder
13
+
14
+ When Boss gives a coding intent, Captain shall relay it to Coder along with the following prompt:
15
+ > Assess whether this can be completed in a single commit, following best practices.
16
+ > If yes, implement and test, updating both code and specs; otherwise, decompose into tasks as a new IR under @specs/intents (or @specs/iterations in older scaffolds) and stop without implementing any IR task.
17
+ > For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.
18
+ > Ensure @specs/map.md reflects the changes.
19
+ > Do not commit.
20
+ The resulting changes are regarded as Initial Changes.
21
+
22
+ When Reviewer raises any findings, Captain shall relay them to Coder along with the following prompt:
23
+ > For each review item below for the above changes, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.
24
+ > Stage all current changes that belong in the repo before making any edits, and leave your edits unstaged/untracked.
25
+
26
+ When a new IR or IR task passes review and is committed, Captain shall prompt Coder:
27
+ > Continue to implement IR-<#> if not all deliverables and tasks are done.
28
+ > Implement one task at a time (including corresponding tests if any).
29
+ > Stop after each task for review — do not commit yet.
30
+ > If relevant, mark progress in the IR.
31
+ The resulting changes are regarded as Initial Changes.
32
+
33
+ When an IR is done, Captain shall prompt Coder:
34
+ > Read IR-<#> and corresponding commits.
35
+ > According to @specs/meta.md, add or update spec items to fully capture:
36
+ >
37
+ > - the external behavior users rely on,
38
+ > - the internal system behavior, and
39
+ > - the integration/system test cases.
40
+ >
41
+ > The spec items should be the *minimal* set needed to reimplement code without the IR.
42
+ > The set should be complete and coherent.
43
+ > Avoid implementation specifics.
44
+ > Avoid redundant spec items.
45
+ > Ensure @specs/map.md reflects the changes.
46
+
47
+ ## Reviewer
48
+
49
+ For each finding in a review round, Coder either addresses it with changes or challenges it with a rebuttal.
50
+ Any code change to address findings starts a new round of review, even if some findings are also rebutted.
51
+ Rounds continue until Reviewer raises no findings.
52
+
53
+ Spec item files are the files under @specs/ that hold spec items — @specs/packages/ and @specs/compositions/ in the current layout, or @specs/user/, @specs/dev/, and @specs/test/ in the legacy one; decision and intent records (iteration records in older scaffolds), @specs/map.md, and @specs/meta.md are not spec item files.
54
+
55
+ When Reviewer begins the first review round on changes from a Boss coding intent, Captain shall relay the intent to Reviewer.
56
+
57
+ When Reviewer begins the first review round on changes from an IR task, Captain shall relay the IR's task description to Reviewer.
58
+
59
+ When Committer commits Initial Changes, Captain shall prompt Reviewer to begin a review round:
60
+ > Review the latest commit.
61
+ > Refer to the commit message.
62
+
63
+ When any changes are made by Coder but not reviewed (outside of any Initial Changes), Captain shall prompt Reviewer to begin a review round:
64
+ > Review the unstaged and untracked changes in the context of the staged changes.
65
+ > Understand the intent.
66
+
67
+ When Reviewer begins a review round involving spec item files, Captain shall prompt Reviewer:
68
+ > Verify any affected spec items are:
69
+ >
70
+ > - Complete & coherent: sufficient for you to reimplement code.
71
+ > - Right level: external behavior users rely on or internal system behavior (organized per @specs/meta.md), not implementation specifics; integration/system testing, not unit testing.
72
+ > - Minimal: essential and concise; every item earns its place; also check with other items.
73
+ > - Well organized: spec packages are finely scoped, with high cohesion and low coupling.
74
+ >
75
+ > Flag anything missing, redundant, over-specified, or under-specified.
76
+
77
+ When Reviewer begins a review round involving any changes outside spec item files, Captain shall prompt Reviewer:
78
+ > Flag any issues or improvements (numbered; no duplication).
79
+ > Think thoroughly — don't just approve or reject.
80
+
81
+ When Reviewer begins any review round, Captain shall prompt Reviewer:
82
+ > For context discovery, @specs/map.md indexes all spec files and @specs/meta.md describes the spec format.
83
+ > Verify @specs/map.md reflects the changes.
84
+ > If the change is ready to commit or push, don't raise nitpicks.
85
+ > Do not edit files or commit; report findings only.
86
+
87
+ When Coder raises any rebuttals, Captain shall relay them to Reviewer along with the following prompt:
88
+ > For each rebuttal below, challenge or accept it, with strong reasoning, solid evidence, and comprehensive thinking.
89
+ > Do not edit files or commit; report findings only.
90
+
91
+ ## Committer
92
+
93
+ When Coder makes any Initial Changes or Reviewer raises no findings on uncommitted changes, Captain shall prompt Committer:
94
+ > Make a commit of the changes that belong in the repo, following @specs/packages/git.md (reread if necessary).
95
+ > If that spec is absent, follow the legacy @specs/dev/git.md; if neither exists, follow the repository's existing commit conventions and do not search elsewhere.
96
+ > Write the commit message concisely.
97
+
98
+ When Captain prompts Committer and only Coder has played since the last commit, Captain shall also append:
99
+ > Coder is \<coder-llm\>.
100
+
101
+ When Captain prompts Committer and both Coder and Reviewer have played since the last commit, Captain shall also append:
102
+ > Coder is \<coder-llm\>; Reviewer is \<reviewer-llm\>.
103
+
104
+ When Captain prompts Committer, Captain shall also append:
105
+ > Format the `Co-authored-by` `<model>` token as the conventional human form of the substituted id (e.g., `claude-opus-4-7` → `Claude-Opus-4.7`, `gpt-5.5` → `GPT-5.5`).