@sublang/playbook 6.0.0 → 8.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.
- package/README.md +28 -11
- package/docs/cli.md +158 -68
- package/docs/configuration.md +246 -108
- package/docs/embedding.md +71 -25
- package/package.json +6 -3
- package/reference/sdlc/captain.playbook/captain.playbook.js +3 -3
- package/reference/sdlc/captain.playbook/captain.playbook.ts +3 -3
- package/reference/sdlc/code.md +1 -1
- package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
- package/reference/sdlc/code.playbook/bin/launch-config.js +1900 -0
- package/reference/sdlc/code.playbook/bin/playbook.js +573 -535
- package/reference/sdlc/code.playbook/bin/provision.js +84 -38
- package/reference/sdlc/code.playbook/bin/run.js +1164 -991
- package/reference/sdlc/code.playbook/bin/session-store.js +1961 -0
- package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
- package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
- package/reference/sdlc/code.playbook/code.fsm.js +7 -11
- package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
- package/reference/sdlc/code.playbook/code.gears.md +1 -1
- package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
- package/reference/sdlc/code.playbook/code.playbook.js +12 -13
- package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
- package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
- package/reference/sdlc/code.playbook/code.registry.js +3 -10
- package/reference/sdlc/code.playbook/code.registry.ts +7 -32
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +101 -9
- package/reference/sdlc/code.playbook/playbook-captain.js +1690 -213
- package/reference/sdlc/code.playbook/playbook-captain.ts +2492 -253
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +44 -62
- package/reference/sdlc/decide.md +4 -4
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
- package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
- package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
- package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
- package/reference/sdlc/decide.playbook/decide.playbook.js +244 -143
- package/reference/sdlc/decide.playbook/decide.playbook.ts +326 -171
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
- package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
- package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
- package/reference/sdlc/review.md +4 -5
- package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
- package/reference/sdlc/review.playbook/review.fsm.js +30 -24
- package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
- package/reference/sdlc/review.playbook/review.gears.md +6 -5
- package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
- package/reference/sdlc/review.playbook/review.playbook.js +16 -21
- package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
- package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
- package/reference/sdlc/review.playbook/review.registry.js +3 -16
- package/reference/sdlc/review.playbook/review.registry.ts +7 -38
- package/slc/gears2fsm.md +27 -23
- package/slc/link.md +140 -97
- package/slc/text2gears.md +19 -18
- package/src/runtime.d.ts +24 -8
- package/src/runtime.ts +29 -13
- package/src/xstate-playbook-runtime.d.ts +21 -17
- package/src/xstate-playbook-runtime.js +301 -159
- package/src/xstate-playbook-runtime.ts +405 -186
- package/src/xstate-runtime.d.ts +19 -2
- package/src/xstate-runtime.js +403 -62
- package/src/xstate-runtime.ts +566 -78
package/docs/configuration.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# Configuring agents
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Fresh launches and ordinary reopens read one config at
|
|
7
7
|
`${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml`. The
|
|
8
8
|
first launch seeds it from the bundled starter and prints the path;
|
|
9
9
|
later launches reuse it untouched.
|
|
@@ -14,27 +14,35 @@ $EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml"
|
|
|
14
14
|
|
|
15
15
|
## Anatomy
|
|
16
16
|
|
|
17
|
-
The config is top-level (no `config:` wrapper): a `captain` agent
|
|
18
|
-
|
|
19
|
-
and
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
The config is top-level (no `config:` wrapper): a `captain` agent, one flat
|
|
18
|
+
`players` map of stable Captain-session agents, a `playbooks` map of enabled
|
|
19
|
+
workflows and their explicit role bindings, and optional `layout` /
|
|
20
|
+
`notifications` / `theme`. The Captain runs hidden control and judge calls and
|
|
21
|
+
writes the replies you see in the Captain pane or on headless stdout. The three
|
|
22
|
+
presentation fields apply only to interactive tmux; headless runs ignore them.
|
|
23
|
+
|
|
24
|
+
A **role** is local to a playbook artifact: CODE's `coder` and REVIEW's `coder`
|
|
25
|
+
have the same semantic name but remain separate declarations. A **player** is
|
|
26
|
+
a stable session-wide provider conversation with an exact ID such as
|
|
27
|
+
`dev.coder`. A role uses only the player named by its binding; matching role
|
|
28
|
+
names, nesting, and ancestry never infer a binding.
|
|
29
|
+
|
|
30
|
+
Each `captain` or `players.<player-id>` value is either an adapter shorthand
|
|
31
|
+
(`claude`, `codex`) or a block carrying that agent's own `adapter`, `model`,
|
|
32
|
+
`effort`, `instruction`, and `permissions`. Settings are inline per stable
|
|
33
|
+
agent ([DR-021](../specs/decisions/021-inline-agent-settings.md)). Dots in a
|
|
34
|
+
player ID are literal characters, not YAML hierarchy. Other adapter IDs pass
|
|
35
|
+
through to `tmux-play` with a warning because `playbook` cannot preflight their
|
|
36
|
+
auth.
|
|
37
|
+
|
|
38
|
+
Within a `playbooks.<id>` block, `from` (the registry module), `command` (an
|
|
39
|
+
optional slash-command override), and `roles` are launcher-owned; every other
|
|
40
|
+
key is that playbook's option slice. Every manifest role must be present
|
|
41
|
+
exactly once. The launcher injects the rest — you do not write host wiring by
|
|
42
|
+
hand.
|
|
43
|
+
|
|
44
|
+
The seeded config runs the stable Coder player on Claude Opus 4.8 1m and the
|
|
45
|
+
stable Reviewer player on GPT-5.5:
|
|
38
46
|
|
|
39
47
|
```yaml
|
|
40
48
|
captain:
|
|
@@ -44,86 +52,116 @@ captain:
|
|
|
44
52
|
permissions:
|
|
45
53
|
mode: auto # protected auto mode for the Claude Captain
|
|
46
54
|
|
|
55
|
+
players:
|
|
56
|
+
dev.coder:
|
|
57
|
+
adapter: claude
|
|
58
|
+
model: claude-opus-4-8[1m]
|
|
59
|
+
effort: xhigh
|
|
60
|
+
permissions:
|
|
61
|
+
mode: auto # protected auto mode for the Claude Coder
|
|
62
|
+
|
|
63
|
+
dev.reviewer:
|
|
64
|
+
adapter: codex
|
|
65
|
+
model: gpt-5.5
|
|
66
|
+
effort: xhigh
|
|
67
|
+
permissions:
|
|
68
|
+
mode: auto
|
|
69
|
+
writablePaths:
|
|
70
|
+
- .git # allow git metadata writes under Codex auto mode
|
|
71
|
+
|
|
47
72
|
playbooks:
|
|
48
73
|
code:
|
|
49
74
|
from: '@sublang/playbook/code/registry'
|
|
50
|
-
|
|
51
|
-
coder:
|
|
52
|
-
adapter: claude
|
|
53
|
-
model: claude-opus-4-8[1m]
|
|
54
|
-
effort: xhigh
|
|
55
|
-
permissions:
|
|
56
|
-
mode: auto # protected auto mode for the Claude Coder
|
|
75
|
+
roles:
|
|
76
|
+
coder: dev.coder
|
|
57
77
|
|
|
58
78
|
review:
|
|
59
79
|
from: '@sublang/playbook/review/registry'
|
|
60
|
-
|
|
61
|
-
coder:
|
|
62
|
-
|
|
63
|
-
model: claude-opus-4-8[1m]
|
|
64
|
-
effort: xhigh
|
|
65
|
-
permissions:
|
|
66
|
-
mode: auto
|
|
67
|
-
reviewer:
|
|
68
|
-
adapter: codex
|
|
69
|
-
model: gpt-5.5
|
|
70
|
-
effort: xhigh
|
|
71
|
-
permissions:
|
|
72
|
-
mode: auto
|
|
73
|
-
writablePaths:
|
|
74
|
-
- .git # allow git metadata writes under Codex auto mode
|
|
80
|
+
roles:
|
|
81
|
+
coder: dev.coder
|
|
82
|
+
reviewer: dev.reviewer
|
|
75
83
|
|
|
76
84
|
decide:
|
|
77
85
|
from: '@sublang/playbook/decide/registry'
|
|
78
|
-
|
|
79
|
-
coder:
|
|
80
|
-
|
|
81
|
-
model: claude-opus-4-8[1m]
|
|
82
|
-
effort: xhigh
|
|
83
|
-
permissions:
|
|
84
|
-
mode: auto
|
|
85
|
-
reviewer:
|
|
86
|
-
adapter: codex
|
|
87
|
-
model: gpt-5.5
|
|
88
|
-
effort: xhigh
|
|
89
|
-
permissions:
|
|
90
|
-
mode: auto
|
|
91
|
-
writablePaths:
|
|
92
|
-
- .git
|
|
86
|
+
roles:
|
|
87
|
+
coder: dev.coder
|
|
88
|
+
reviewer: dev.reviewer
|
|
93
89
|
```
|
|
94
90
|
|
|
95
91
|
The current bundled workflows accept no workflow-specific options.
|
|
96
|
-
Each role's per-
|
|
92
|
+
Each role's per-call prompt names its current `model`, else its player's
|
|
93
|
+
`adapter`
|
|
97
94
|
([[playbook-runtime-4](../specs/packages/playbook-runtime.md#playbook-runtime-4)]),
|
|
98
95
|
so commit trailers credit the concrete model rather than the adapter
|
|
99
96
|
family.
|
|
100
97
|
|
|
101
|
-
##
|
|
98
|
+
## Role binding forms
|
|
99
|
+
|
|
100
|
+
The shortest binding is a scalar stable player ID:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
roles:
|
|
104
|
+
coder: dev.coder
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use a block to override only that role invocation's model or effort:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
roles:
|
|
111
|
+
coder:
|
|
112
|
+
player: dev.coder
|
|
113
|
+
model: claude-opus-4-8[1m]
|
|
114
|
+
effort: false # explicitly reset to this provider's default
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Omitting `model` or `effort` inherits that player's top-level default. The
|
|
118
|
+
boolean `false` is different: it selects the provider default explicitly, so a
|
|
119
|
+
resumed conversation cannot accidentally retain an earlier selection. A role
|
|
120
|
+
binding cannot override adapter, instruction, permissions, workspace, or tool
|
|
121
|
+
posture; those define the stable player envelope.
|
|
122
|
+
|
|
123
|
+
## Sharing, isolation, and concurrency
|
|
124
|
+
|
|
125
|
+
Two bindings that name the same player ID deliberately share one sequential
|
|
126
|
+
provider conversation throughout the logical Captain session — across nested
|
|
127
|
+
calls, returns, and later root engagements. CODE's and REVIEW's `coder` roles
|
|
128
|
+
therefore share `dev.coder` in the starter, and DECIDE and its nested REVIEW
|
|
129
|
+
share both starter players. Disposal of one playbook frame does not clear that
|
|
130
|
+
session ledger.
|
|
131
|
+
|
|
132
|
+
Two distinct player IDs stay isolated even when their agent blocks are
|
|
133
|
+
byte-for-byte equal. To give standalone REVIEW an independent Coder, define a
|
|
134
|
+
second top-level player and change only its binding:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
players:
|
|
138
|
+
review.coder:
|
|
139
|
+
adapter: claude
|
|
140
|
+
model: claude-opus-4-8[1m]
|
|
141
|
+
effort: xhigh
|
|
102
142
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
([DR-030](../specs/decisions/030-shared-mapped-player-continuity.md)).
|
|
143
|
+
playbooks:
|
|
144
|
+
review:
|
|
145
|
+
from: '@sublang/playbook/review/registry'
|
|
146
|
+
roles:
|
|
147
|
+
coder: review.coder
|
|
148
|
+
reviewer: dev.reviewer
|
|
149
|
+
```
|
|
111
150
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
host player or backend agent session for a mapped role.
|
|
151
|
+
Roles a manifest may run concurrently must bind to distinct IDs. DECIDE's
|
|
152
|
+
`coder` and `reviewer` are concurrent, so aliasing both to one player rejects
|
|
153
|
+
before registry import, host creation, or agent work.
|
|
116
154
|
|
|
117
155
|
## Choosing the Captain agent
|
|
118
156
|
|
|
119
157
|
Every session-Captain call and adjudication call is hidden and runs
|
|
120
158
|
tool-free, which is what keeps the Captain deciding and reporting
|
|
121
|
-
instead of doing the work itself. Claude
|
|
122
|
-
|
|
123
|
-
tool list — so a `captain:`
|
|
124
|
-
restriction
|
|
159
|
+
instead of doing the work itself. Claude and Gemini enforce that at the
|
|
160
|
+
provider level. The Codex, Kimi, and OpenCode adapters cannot — they
|
|
161
|
+
reject any tool list — so a `captain:` using one of them falls back to a
|
|
162
|
+
prompt-level restriction
|
|
125
163
|
([DR-013](../specs/decisions/013-routing-only-captain-control.md) A1).
|
|
126
|
-
|
|
164
|
+
Those adapters remain good choices for *players*, where full tools are wanted.
|
|
127
165
|
|
|
128
166
|
Adapter readiness is intentionally light: `claude` is ready with local
|
|
129
167
|
Claude Code auth or `ANTHROPIC_API_KEY`; `codex` with local Codex CLI
|
|
@@ -138,52 +176,152 @@ recursively, other values replace):
|
|
|
138
176
|
|
|
139
177
|
```sh
|
|
140
178
|
playbook --with fast-lineup.yaml
|
|
179
|
+
playbook run --with fast-lineup.yaml "/code implement the approved change"
|
|
141
180
|
```
|
|
142
181
|
|
|
143
182
|
```yaml
|
|
144
|
-
# fast-lineup.yaml —
|
|
183
|
+
# fast-lineup.yaml — retune the shared Coder; nothing is written back.
|
|
184
|
+
players:
|
|
185
|
+
dev.coder:
|
|
186
|
+
model: claude-opus-4-8
|
|
187
|
+
effort: medium
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Fragments merge into the agent block rather than replacing it, so
|
|
191
|
+
settings the base defines and the fragment omits — here the adapter,
|
|
192
|
+
instruction, and permissions — survive. Retuning a top-level player affects
|
|
193
|
+
every bound role that does not override that field. To retune only one role,
|
|
194
|
+
overlay its binding instead:
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
145
197
|
playbooks:
|
|
146
198
|
code:
|
|
147
|
-
|
|
199
|
+
roles:
|
|
148
200
|
coder:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
effort: medium
|
|
152
|
-
permissions:
|
|
153
|
-
mode: auto
|
|
154
|
-
# CODE's Coder commits, so Codex needs the `.git` grant to write
|
|
155
|
-
# repository metadata.
|
|
156
|
-
writablePaths:
|
|
157
|
-
- .git
|
|
201
|
+
player: dev.coder
|
|
202
|
+
effort: low
|
|
158
203
|
```
|
|
159
204
|
|
|
160
|
-
Fragments merge into the agent block rather than replacing it, so
|
|
161
|
-
settings the base defines and the fragment omits — here `mode: auto` —
|
|
162
|
-
survive. Anything the adapter itself requires must still be stated: a
|
|
163
|
-
role switched to `codex` needs its own `writablePaths` grant, because
|
|
164
|
-
the base Claude block had no reason to carry one.
|
|
165
|
-
|
|
166
205
|
The global file is never modified, and `--with` is not forwarded to
|
|
167
206
|
`tmux-play` ([[playbook-cli-25](../specs/packages/playbook-cli.md#playbook-cli-25)]).
|
|
207
|
+
Overlays apply when creating a fresh session and as current-config input for a
|
|
208
|
+
compatible ordinary reopen. A selected session keeps its stored catalog,
|
|
209
|
+
player roster, role bindings, adapter, instruction, permissions, and working
|
|
210
|
+
directory; only model and effort may change. The next call reapplies both
|
|
211
|
+
complete selections. An uncertain retry accepts no tuning overlay and uses the
|
|
212
|
+
exact attempted selections already stored with that turn.
|
|
213
|
+
|
|
214
|
+
## Durable shared configuration
|
|
215
|
+
|
|
216
|
+
Fresh interactive and headless sessions use the same top-level Captain,
|
|
217
|
+
players, role bindings, and playbooks. Both persist the same logical-session
|
|
218
|
+
record, shell snapshot, player ledger, normalized catalog, structural agent
|
|
219
|
+
envelopes, last-applied tuning, and absolute working directory. A session
|
|
220
|
+
created by either front end can reopen through either front end with the same
|
|
221
|
+
public UUID. Presentation-only fields are inert headlessly.
|
|
222
|
+
|
|
223
|
+
An ordinary reopen reads current config and opening overlays, but first
|
|
224
|
+
projects them to the stored playbooks and referenced players. An unrelated new
|
|
225
|
+
entry cannot enter or invalidate the session. Structural drift fails closed;
|
|
226
|
+
compatible model or effort changes apply on the next provider call. Legacy
|
|
227
|
+
record, shell, runtime-snapshot, and trace schemas are rejected rather than
|
|
228
|
+
having role or player identity guessed.
|
|
229
|
+
|
|
230
|
+
## External playbooks
|
|
231
|
+
|
|
232
|
+
`slc playbook my-workflow.md` emits `my-workflow.ts` beside its artifact
|
|
233
|
+
directory. That file already default-exports the registry manifest Playbook
|
|
234
|
+
requires: `id`, `command`, `intent`, `requiredRoleIds`, `validateOptions`,
|
|
235
|
+
and `createRuntime`. Enable it under `playbooks`, bind every role listed in
|
|
236
|
+
its `requiredRoleIds`, and invoke its effective slash command through Captain:
|
|
237
|
+
|
|
238
|
+
```yaml
|
|
239
|
+
players:
|
|
240
|
+
my.worker: claude
|
|
241
|
+
|
|
242
|
+
playbooks:
|
|
243
|
+
my-workflow:
|
|
244
|
+
from: /absolute/path/to/my-workflow.ts
|
|
245
|
+
roles:
|
|
246
|
+
worker: my.worker
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
playbook run "/my-workflow perform the task"
|
|
251
|
+
```
|
|
168
252
|
|
|
169
|
-
|
|
253
|
+
A relative path-shaped `from` is resolved relative to the primary config
|
|
254
|
+
file, not the invocation directory; an absolute path is clearest for an SLC
|
|
255
|
+
entry emitted in a project working tree.
|
|
256
|
+
Before either front end imports a filesystem registry, the shared launcher
|
|
257
|
+
checks and, unless `--no-provision` is set, provisions its runtime engine
|
|
258
|
+
links as described in [Using the CLI](cli.md#external-playbooks-and-engine-provisioning).
|
|
170
259
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
([[playbook-cli-28](../specs/packages/playbook-cli.md#playbook-cli-28)],
|
|
177
|
-
[DR-017](../specs/decisions/017-run-defaults-config.md)).
|
|
260
|
+
## Migrating per-playbook players
|
|
261
|
+
|
|
262
|
+
The former `playbooks.<id>.players` shape made agent configuration and local
|
|
263
|
+
workflow roles the same thing. It is removed. For example, this legacy config
|
|
264
|
+
gave CODE and REVIEW two separately configured `coder` entries:
|
|
178
265
|
|
|
179
266
|
```yaml
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
267
|
+
playbooks:
|
|
268
|
+
code:
|
|
269
|
+
from: '@sublang/playbook/code/registry'
|
|
270
|
+
players:
|
|
271
|
+
coder: { adapter: claude, model: claude-opus-4-8[1m] }
|
|
272
|
+
review:
|
|
273
|
+
from: '@sublang/playbook/review/registry'
|
|
274
|
+
players:
|
|
275
|
+
coder: { adapter: claude, model: claude-opus-4-8[1m] }
|
|
276
|
+
reviewer: { adapter: codex, model: gpt-5.5 }
|
|
185
277
|
```
|
|
186
278
|
|
|
279
|
+
Move each provider agent into the flat top-level map, choose stable IDs, and
|
|
280
|
+
bind the local roles explicitly:
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
players:
|
|
284
|
+
dev.coder: { adapter: claude, model: claude-opus-4-8[1m] }
|
|
285
|
+
dev.reviewer: { adapter: codex, model: gpt-5.5 }
|
|
286
|
+
|
|
287
|
+
playbooks:
|
|
288
|
+
code:
|
|
289
|
+
from: '@sublang/playbook/code/registry'
|
|
290
|
+
roles: { coder: dev.coder }
|
|
291
|
+
review:
|
|
292
|
+
from: '@sublang/playbook/review/registry'
|
|
293
|
+
roles:
|
|
294
|
+
coder: dev.coder
|
|
295
|
+
reviewer: dev.reviewer
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The launcher intentionally does **not** perform this migration for you. It
|
|
299
|
+
cannot know whether the two old `coder` blocks were meant to share one
|
|
300
|
+
conversation or remain isolated. Reusing `dev.coder` above chooses sharing;
|
|
301
|
+
using `code.coder` and `review.coder` would choose isolation. A surviving
|
|
302
|
+
per-playbook `players` block therefore rejects before profile migration,
|
|
303
|
+
registry preparation, or agent work.
|
|
304
|
+
|
|
305
|
+
## Migrating direct runs from 6.x
|
|
306
|
+
|
|
307
|
+
The top-level `run:` block is deliberately rejected rather than silently
|
|
308
|
+
ignored or rewritten, because doing otherwise could change the agents after
|
|
309
|
+
an upgrade. Re-express `run.captain`, `run.players`, and former `--player`
|
|
310
|
+
bindings as top-level stable player blocks and explicit role bindings above;
|
|
311
|
+
the old `run.player` catch-all has no shared equivalent, so configure every
|
|
312
|
+
required role at `playbooks.<id>.roles.<role>`. Use a `--with` fragment for
|
|
313
|
+
temporary compatible tuning changes. Move former `--option` values into their `playbooks.<id>`
|
|
314
|
+
block, run from the desired directory instead of passing `--cwd`, enable a
|
|
315
|
+
former positional `<from>` as a configured registry, and quote or pipe one
|
|
316
|
+
`/command task` Boss message. Replace `resume` and `--last` with `--continue`
|
|
317
|
+
or `--session`.
|
|
318
|
+
|
|
319
|
+
The JSON response is now exactly `{ "sessionId": "…", "reply": "…" }`.
|
|
320
|
+
Released direct-run session records are not complete Captain sessions and
|
|
321
|
+
cannot be continued by the new host ([[playbook-cli-19](../specs/packages/playbook-cli.md#playbook-cli-19)],
|
|
322
|
+
[[playbook-cli-22](../specs/packages/playbook-cli.md#playbook-cli-22)],
|
|
323
|
+
[[playbook-cli-28](../specs/packages/playbook-cli.md#playbook-cli-28)]).
|
|
324
|
+
|
|
187
325
|
## Migrating from `profiles`
|
|
188
326
|
|
|
189
327
|
Configs written before 3.0.0 carried a top-level `profiles` map. The
|
package/docs/embedding.md
CHANGED
|
@@ -16,8 +16,8 @@ your own host.
|
|
|
16
16
|
The port and runtime contracts live in the type-only module
|
|
17
17
|
[`@sublang/playbook/runtime`](../src/runtime.ts) — a public,
|
|
18
18
|
semver-stable surface (`PlayerResult`, `PlaybookPorts`,
|
|
19
|
-
`PlaybookRuntime`, `PlaybookSession`, `
|
|
20
|
-
`PlayerSessionStore`, `CaptainCallOptions`, `CaptainResult`,
|
|
19
|
+
`PlaybookRuntime`, `PlaybookSession`, `PlaybookRoleBinding`,
|
|
20
|
+
`PlayerCallOptions`, `PlayerSessionStore`, `CaptainCallOptions`, `CaptainResult`,
|
|
21
21
|
`PlaybookTraceEvent`, and `PlaybookRuntimeFactory`) that imports no CODE
|
|
22
22
|
or FSM types, so a host satisfies it once and inherits every playbook.
|
|
23
23
|
The generated CODE, REVIEW, and DECIDE modules re-export their shared
|
|
@@ -37,6 +37,9 @@ import type {
|
|
|
37
37
|
CaptainCallOptions,
|
|
38
38
|
CaptainResult,
|
|
39
39
|
PlaybookPorts,
|
|
40
|
+
PlaybookRoleBinding,
|
|
41
|
+
PlayerResult,
|
|
42
|
+
PlayerSessionStore,
|
|
40
43
|
} from '@sublang/playbook/runtime';
|
|
41
44
|
import { randomUUID } from 'node:crypto';
|
|
42
45
|
import PQueue from 'p-queue';
|
|
@@ -53,6 +56,33 @@ declare const captainAdapter: {
|
|
|
53
56
|
): Promise<CaptainResult>;
|
|
54
57
|
};
|
|
55
58
|
|
|
59
|
+
declare const playerAdapter: {
|
|
60
|
+
run(
|
|
61
|
+
playerId: string,
|
|
62
|
+
prompt: string,
|
|
63
|
+
options: { signal: AbortSignal; resume: string | false },
|
|
64
|
+
): Promise<PlayerResult>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Roles are local workflow identities. Players are stable provider
|
|
68
|
+
// conversations owned by the logical Captain session. `promptIdentity` is
|
|
69
|
+
// the current model name, or the player's adapter when provider-default is
|
|
70
|
+
// selected; rebuild it from current compatible tuning on restore.
|
|
71
|
+
const roleBindings = {
|
|
72
|
+
coder: {
|
|
73
|
+
playerId: 'team.coder',
|
|
74
|
+
promptIdentity: 'claude-opus-4-8[1m]',
|
|
75
|
+
},
|
|
76
|
+
reviewer: {
|
|
77
|
+
playerId: 'team.reviewer',
|
|
78
|
+
promptIdentity: 'gpt-5.5',
|
|
79
|
+
},
|
|
80
|
+
} satisfies Readonly<Record<string, PlaybookRoleBinding>>;
|
|
81
|
+
|
|
82
|
+
// Supply a frame-local role view over your session-wide player ledger.
|
|
83
|
+
// Equal player IDs must select/update the same token; distinct IDs must not.
|
|
84
|
+
declare const playerSessions: PlayerSessionStore;
|
|
85
|
+
|
|
56
86
|
// Construct one host-wide lane and reuse it for every runtime. Passing each
|
|
57
87
|
// call's signal to both the lane and adapter cancels queued and active work.
|
|
58
88
|
const captainLane = new PQueue({ concurrency: 1 });
|
|
@@ -69,10 +99,16 @@ async function runCaptain(
|
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
const ports: PlaybookPorts = {
|
|
72
|
-
callPlayer: async (
|
|
102
|
+
callPlayer: async (roleId, prompt, signal, { resume }) => {
|
|
103
|
+
const binding = roleBindings[roleId as keyof typeof roleBindings];
|
|
104
|
+
if (binding === undefined) throw new Error(`Unknown role: ${roleId}`);
|
|
73
105
|
// `resume === false` starts fresh; a string selects that player's
|
|
74
|
-
// prior backend conversation. Return the adapter's next token
|
|
75
|
-
|
|
106
|
+
// prior backend conversation. Return the adapter's next token; the
|
|
107
|
+
// runtime updates `playerSessions` only after validating this result.
|
|
108
|
+
return await playerAdapter.run(binding.playerId, prompt, {
|
|
109
|
+
signal,
|
|
110
|
+
resume,
|
|
111
|
+
});
|
|
76
112
|
},
|
|
77
113
|
callCaptain: async (prompt, signal, options) => {
|
|
78
114
|
// Forward every option exactly: omission preserves configured tools, while
|
|
@@ -103,10 +139,7 @@ const ports: PlaybookPorts = {
|
|
|
103
139
|
},
|
|
104
140
|
};
|
|
105
141
|
|
|
106
|
-
const runtime = createPlaybookRuntime({
|
|
107
|
-
coderLlm: 'claude-opus-4-8[1m]',
|
|
108
|
-
reviewerLlm: 'gpt-5.5',
|
|
109
|
-
});
|
|
142
|
+
const runtime = createPlaybookRuntime({});
|
|
110
143
|
|
|
111
144
|
const playbookSessionId = randomUUID();
|
|
112
145
|
await runtime.init({
|
|
@@ -114,6 +147,8 @@ await runtime.init({
|
|
|
114
147
|
playbookId: 'review',
|
|
115
148
|
rootSessionId: playbookSessionId,
|
|
116
149
|
depth: 0,
|
|
150
|
+
roleBindings,
|
|
151
|
+
playerSessions,
|
|
117
152
|
ports,
|
|
118
153
|
});
|
|
119
154
|
await runtime.handleBossInput({
|
|
@@ -125,23 +160,34 @@ await runtime.dispose();
|
|
|
125
160
|
|
|
126
161
|
## Sessions and traces
|
|
127
162
|
|
|
128
|
-
Every init-to-dispose lifecycle is one playbook session.
|
|
163
|
+
Every init-to-dispose lifecycle is one playbook session. Schema-3
|
|
129
164
|
`playbook.trace` telemetry carries that immutable ID plus a contiguous
|
|
130
|
-
sequence across exact Boss input, judge/player calls, FSM transitions,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
sequence across exact Boss input, judge/player calls, FSM transitions, visible
|
|
166
|
+
Captain work, nested playbook calls, status, settlement, and disposal. A
|
|
167
|
+
shell-hosted player boundary keeps both identities: `roleId` says which local
|
|
168
|
+
workflow job made the call, while `playerId` says which stable session
|
|
169
|
+
conversation owned it. A standalone runtime retains the role without
|
|
170
|
+
inventing host player identity.
|
|
171
|
+
|
|
172
|
+
Without `PlaybookSession.playerSessions`, a standalone runtime starts each
|
|
173
|
+
local role fresh and privately retains the latest opaque `resumeToken` its
|
|
174
|
+
adapter returned. A composing host instead supplies a frame-local
|
|
175
|
+
`PlayerSessionStore` view over one Captain-session ledger and explicit
|
|
176
|
+
`roleBindings`. The store's methods receive local role IDs; the view resolves
|
|
177
|
+
them to the configured stable player IDs. Equal IDs share one token and
|
|
178
|
+
sequential call lane across every frame that names them, while distinct IDs
|
|
179
|
+
remain isolated. Child return, frame disposal, and a later root engagement do
|
|
180
|
+
not clear the session ledger.
|
|
181
|
+
|
|
182
|
+
Runtime snapshots are schema 3. Their `roleResumeTokens` projection remains
|
|
183
|
+
role-local, while the composing shell's own schema-3 snapshot persists the
|
|
184
|
+
stable player ledger and every frame's exact role bindings. Do not restore
|
|
185
|
+
schema 1 or 2 by guessing identity. On a compatible restore, rebuild
|
|
186
|
+
`promptIdentity` from the current model selection (or adapter for an explicit
|
|
187
|
+
provider-default selection) so the next prompt and trace describe the current
|
|
188
|
+
invocation rather than stale machine state. Trace data and tokens never enter
|
|
189
|
+
Boss-visible status text. Because trace observers do receive opaque resume
|
|
190
|
+
tokens, persisted traces should be protected as sensitive data.
|
|
145
191
|
|
|
146
192
|
See
|
|
147
193
|
[`code.playbook.test.ts`](../reference/sdlc/code.playbook/code.playbook.test.ts)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sublang/playbook",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, and DECIDE workflows driven by GEARS specs.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -67,7 +67,10 @@
|
|
|
67
67
|
"reference/sdlc/code.playbook/code.gears.md",
|
|
68
68
|
"reference/sdlc/code.playbook/playbook.config.template.yaml",
|
|
69
69
|
"reference/sdlc/code.playbook/bin/playbook.js",
|
|
70
|
+
"reference/sdlc/code.playbook/bin/launch-config.js",
|
|
70
71
|
"reference/sdlc/code.playbook/bin/run.js",
|
|
72
|
+
"reference/sdlc/code.playbook/bin/interactive-session.js",
|
|
73
|
+
"reference/sdlc/code.playbook/bin/session-store.js",
|
|
71
74
|
"reference/sdlc/code.playbook/bin/provision.js",
|
|
72
75
|
"reference/sdlc/code.playbook/bin/adapter-sdk.js",
|
|
73
76
|
"reference/sdlc/review.playbook/review.gears.md",
|
|
@@ -140,7 +143,7 @@
|
|
|
140
143
|
},
|
|
141
144
|
"scripts": {
|
|
142
145
|
"build": "tsc",
|
|
143
|
-
"test": "spex lint && vitest run",
|
|
146
|
+
"test": "spex lint && vitest run --exclude src/cligent-release-capabilities.test.ts && vitest run src/cligent-release-capabilities.test.ts",
|
|
144
147
|
"test:acceptance": "vitest run --config vitest.acceptance.config.ts",
|
|
145
148
|
"smoke:release": "node scripts/release-smoke.mjs",
|
|
146
149
|
"check:links": "node scripts/check-links.mjs",
|
|
@@ -151,7 +154,7 @@
|
|
|
151
154
|
"provenance": true
|
|
152
155
|
},
|
|
153
156
|
"dependencies": {
|
|
154
|
-
"@sublang/cligent": "^0.
|
|
157
|
+
"@sublang/cligent": "^0.22.0",
|
|
155
158
|
"@sublang/spex": "^2.1.1",
|
|
156
159
|
"p-queue": "^9.3.1",
|
|
157
160
|
"xstate": "^5.19.4",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// deterministic entry mapping, the controller captain-call
|
|
32
32
|
// strategy with its single corrective re-ask (CAPPLAY-18),
|
|
33
33
|
// controller-port submission, and status formatting.
|
|
34
|
-
// Compat: spec.compat = { artifactSchema:
|
|
34
|
+
// Compat: spec.compat = { artifactSchema: 2, runtimeAbi: 1 }
|
|
35
35
|
// (DR-022; checked at construction by the loading engine).
|
|
36
36
|
import { createXStatePlaybookRuntime, defaultComposeCaptainPrompt, normalizeError, normalizeErrorCompact, parseJudgeJson, snapshotJsonValue, RUNTIME_ABI, } from '../../../src/xstate-runtime.js';
|
|
37
37
|
import { captainMachine, } from './captain.fsm.js';
|
|
@@ -516,11 +516,11 @@ export const _internal = {
|
|
|
516
516
|
// describe/apply control surface — lives in @sublang/playbook/xstate-runtime.
|
|
517
517
|
const runtimeSpec = {
|
|
518
518
|
label: 'CAPTAIN',
|
|
519
|
-
compat: { artifactSchema:
|
|
519
|
+
compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
|
|
520
520
|
snapshotOptions: snapshotCaptainOptions,
|
|
521
521
|
machineInput: (options) => ({ enabledPlaybooks: options.enabledPlaybooks }),
|
|
522
522
|
classifyBossText: (text, ports, signal, snapshotOrState, boundary, options) => classifyControllerTurn(text, ports, signal, snapshotOrState, boundary, options),
|
|
523
|
-
|
|
523
|
+
roleStates: {},
|
|
524
524
|
classificationStatus: () => undefined,
|
|
525
525
|
captainStrategy: controllerCaptainStrategy,
|
|
526
526
|
// CAPPLAY-10 / PBRT-52: the Captain's own ControlView context projection —
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// deterministic entry mapping, the controller captain-call
|
|
32
32
|
// strategy with its single corrective re-ask (CAPPLAY-18),
|
|
33
33
|
// controller-port submission, and status formatting.
|
|
34
|
-
// Compat: spec.compat = { artifactSchema:
|
|
34
|
+
// Compat: spec.compat = { artifactSchema: 2, runtimeAbi: 1 }
|
|
35
35
|
// (DR-022; checked at construction by the loading engine).
|
|
36
36
|
|
|
37
37
|
import {
|
|
@@ -798,7 +798,7 @@ export const _internal = {
|
|
|
798
798
|
// describe/apply control surface — lives in @sublang/playbook/xstate-runtime.
|
|
799
799
|
const runtimeSpec: XStatePlaybookRuntimeSpec<ValidatedCaptainOptions> = {
|
|
800
800
|
label: 'CAPTAIN',
|
|
801
|
-
compat: { artifactSchema:
|
|
801
|
+
compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
|
|
802
802
|
snapshotOptions: snapshotCaptainOptions,
|
|
803
803
|
machineInput: (options) => ({ enabledPlaybooks: options.enabledPlaybooks }),
|
|
804
804
|
classifyBossText: (text, ports, signal, snapshotOrState, boundary, options) =>
|
|
@@ -810,7 +810,7 @@ const runtimeSpec: XStatePlaybookRuntimeSpec<ValidatedCaptainOptions> = {
|
|
|
810
810
|
boundary,
|
|
811
811
|
options,
|
|
812
812
|
) as Promise<import('xstate').EventObject | undefined>,
|
|
813
|
-
|
|
813
|
+
roleStates: {},
|
|
814
814
|
classificationStatus: () => undefined,
|
|
815
815
|
captainStrategy: controllerCaptainStrategy,
|
|
816
816
|
// CAPPLAY-10 / PBRT-52: the Captain's own ControlView context projection —
|