@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.
Files changed (63) hide show
  1. package/README.md +28 -11
  2. package/docs/cli.md +158 -68
  3. package/docs/configuration.md +246 -108
  4. package/docs/embedding.md +71 -25
  5. package/package.json +6 -3
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +3 -3
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +3 -3
  8. package/reference/sdlc/code.md +1 -1
  9. package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
  10. package/reference/sdlc/code.playbook/bin/launch-config.js +1900 -0
  11. package/reference/sdlc/code.playbook/bin/playbook.js +573 -535
  12. package/reference/sdlc/code.playbook/bin/provision.js +84 -38
  13. package/reference/sdlc/code.playbook/bin/run.js +1164 -991
  14. package/reference/sdlc/code.playbook/bin/session-store.js +1961 -0
  15. package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
  17. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
  18. package/reference/sdlc/code.playbook/code.fsm.js +7 -11
  19. package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
  20. package/reference/sdlc/code.playbook/code.gears.md +1 -1
  21. package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
  22. package/reference/sdlc/code.playbook/code.playbook.js +12 -13
  23. package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
  24. package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
  25. package/reference/sdlc/code.playbook/code.registry.js +3 -10
  26. package/reference/sdlc/code.playbook/code.registry.ts +7 -32
  27. package/reference/sdlc/code.playbook/playbook-captain.d.ts +101 -9
  28. package/reference/sdlc/code.playbook/playbook-captain.js +1690 -213
  29. package/reference/sdlc/code.playbook/playbook-captain.ts +2492 -253
  30. package/reference/sdlc/code.playbook/playbook.config.template.yaml +44 -62
  31. package/reference/sdlc/decide.md +4 -4
  32. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
  33. package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
  34. package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
  35. package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
  36. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
  37. package/reference/sdlc/decide.playbook/decide.playbook.js +244 -143
  38. package/reference/sdlc/decide.playbook/decide.playbook.ts +326 -171
  39. package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
  40. package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
  41. package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
  42. package/reference/sdlc/review.md +4 -5
  43. package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
  44. package/reference/sdlc/review.playbook/review.fsm.js +30 -24
  45. package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
  46. package/reference/sdlc/review.playbook/review.gears.md +6 -5
  47. package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
  48. package/reference/sdlc/review.playbook/review.playbook.js +16 -21
  49. package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
  50. package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
  51. package/reference/sdlc/review.playbook/review.registry.js +3 -16
  52. package/reference/sdlc/review.playbook/review.registry.ts +7 -38
  53. package/slc/gears2fsm.md +27 -23
  54. package/slc/link.md +140 -97
  55. package/slc/text2gears.md +19 -18
  56. package/src/runtime.d.ts +24 -8
  57. package/src/runtime.ts +29 -13
  58. package/src/xstate-playbook-runtime.d.ts +21 -17
  59. package/src/xstate-playbook-runtime.js +301 -159
  60. package/src/xstate-playbook-runtime.ts +405 -186
  61. package/src/xstate-runtime.d.ts +19 -2
  62. package/src/xstate-runtime.js +403 -62
  63. package/src/xstate-runtime.ts +566 -78
@@ -3,7 +3,7 @@
3
3
 
4
4
  # Configuring agents
5
5
 
6
- `playbook` reads one config at
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 (it
18
- runs the session Captain's hidden control calls, the hidden judge calls,
19
- and the replies you see in the Captain pane), optional
20
- `layout` / `notifications` / `theme`, and a `playbooks` map of enabled
21
- playbooks.
22
-
23
- Each `captain` or `players.<role>` value is either an adapter shorthand
24
- (`claude`, `codex`) or a block carrying that agent's own `adapter`,
25
- `model`, `effort`, and `permissions`. Settings are inline per agent, so
26
- tuning one player never changes another
27
- ([DR-021](../specs/decisions/021-inline-agent-settings.md)). Other
28
- adapter ids pass through to `tmux-play` with a warning, because
29
- `playbook` cannot preflight their auth.
30
-
31
- Within a `playbooks.<id>` block, `from` (the registry module), `command`
32
- (an optional slash-command override), and `players` are launcher-owned;
33
- every other key is that playbook's option slice. The launcher injects
34
- the rest you do not write host wiring by hand.
35
-
36
- The seeded config runs each Coder on Claude Opus 4.8 1m and each
37
- Reviewer on GPT-5.5:
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
- players:
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
- players:
61
- coder:
62
- adapter: claude
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
- players:
79
- coder:
80
- adapter: claude
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-run prompt names its pinned `model`, else its `adapter`
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
- ## Nested roles and sessions
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
- The launcher creates a namespaced fallback player for every configured
104
- playbook role, but a nested call maps an exact same-name role to the
105
- nearest ancestor's effective player and backend conversation. CODE's
106
- nested REVIEW therefore continues CODE's `coder` and uses REVIEW's
107
- configured `reviewer`; DECIDE's nested REVIEW continues both of
108
- DECIDE's roles. A standalone REVIEW starts with REVIEW's own configured
109
- players, and every new root engagement starts fresh
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
- The separate fallback entries are still required because tmux creates
113
- its roster at launch time. The host changes which existing panes are
114
- visible as the active nested leaf changes; it does not create a new
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 enforces
122
- that at the provider level. The Codex adapter cannot — it rejects any
123
- tool list — so a `captain:` on `codex` falls back to a prompt-level
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
- Codex remains a good choice for *players*, where full tools are wanted.
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 — swap the Coder for one run; nothing is written back.
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
- players:
199
+ roles:
148
200
  coder:
149
- adapter: codex
150
- model: gpt-5.5
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
- ## Defaults for `playbook run`
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
- An optional top-level `run` block supplies the non-interactive host's
172
- lineup so you stop retyping flags — `run.captain`, `run.players.<role>`,
173
- and a `run.player` catch-all for any other required role, each an
174
- `<adapter>[:<model>][@<effort>]` string. Flags win per role, and
175
- `resume` always keeps the lineup stored with the parked session
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
- run:
181
- captain: claude:claude-opus-4-8@high
182
- players:
183
- coder: claude:claude-opus-4-8[1m]@xhigh
184
- reviewer: codex:gpt-5.5@xhigh
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`, `PlayerCallOptions`,
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 (playerId, prompt, signal, { resume }) => {
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
- return { status: 'ok', finalText: 'done', resumeToken: 'next-token' };
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. Its
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
- visible Captain work, nested playbook calls, status, settlement, and
132
- disposal. Without `PlaybookSession.playerSessions`, a standalone runtime
133
- starts each player fresh and privately retains the latest opaque
134
- `resumeToken` its adapter returned.
135
-
136
- A composing host can instead supply a frame-local `PlayerSessionStore`
137
- view over one root-owned continuation map. The host maps each local role
138
- to its effective binding, so a nested exact same-name role selects and
139
- updates the ancestor conversation while an unmatched child role gets
140
- its own binding. Child return or disposal does not clear the root's
141
- token, and a new root engagement starts fresh. Trace data and tokens
142
- never enter Boss-visible status text. Because trace observers do receive
143
- opaque resume tokens, persisted traces should be protected as sensitive
144
- data.
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": "6.0.0",
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.19.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: 1, runtimeAbi: 1 }
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: 1, runtimeAbi: RUNTIME_ABI },
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
- playerStates: {},
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: 1, runtimeAbi: 1 }
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: 1, runtimeAbi: RUNTIME_ABI },
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
- playerStates: {},
813
+ roleStates: {},
814
814
  classificationStatus: () => undefined,
815
815
  captainStrategy: controllerCaptainStrategy,
816
816
  // CAPPLAY-10 / PBRT-52: the Captain's own ControlView context projection —
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Code
6
6
 
7
- Players:
7
+ Roles:
8
8
 
9
9
  - Coder
10
10