@sublang/playbook 9.0.0 → 11.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 (71) hide show
  1. package/README.md +1 -1
  2. package/docs/cli.md +109 -21
  3. package/docs/configuration.md +89 -40
  4. package/docs/embedding.md +126 -12
  5. package/package.json +14 -3
  6. package/reference/sdlc/captain.md +14 -10
  7. package/reference/sdlc/captain.playbook/captain.fsm.d.ts +33 -13
  8. package/reference/sdlc/captain.playbook/captain.fsm.js +80 -9
  9. package/reference/sdlc/captain.playbook/captain.fsm.ts +137 -18
  10. package/reference/sdlc/captain.playbook/captain.gears.md +10 -6
  11. package/reference/sdlc/captain.playbook/captain.playbook.d.ts +5 -1
  12. package/reference/sdlc/captain.playbook/captain.playbook.js +140 -10
  13. package/reference/sdlc/captain.playbook/captain.playbook.ts +188 -16
  14. package/reference/sdlc/code.md +35 -16
  15. package/reference/sdlc/code.playbook/bin/interactive-session.js +228 -23
  16. package/reference/sdlc/code.playbook/bin/launch-config.js +611 -221
  17. package/reference/sdlc/code.playbook/bin/playbook.js +304 -178
  18. package/reference/sdlc/code.playbook/bin/replay-observer.js +221 -0
  19. package/reference/sdlc/code.playbook/bin/repository-effects.js +2930 -0
  20. package/reference/sdlc/code.playbook/bin/run.js +669 -215
  21. package/reference/sdlc/code.playbook/bin/session-store.js +4546 -502
  22. package/reference/sdlc/code.playbook/code.fsm.d.ts +7 -0
  23. package/reference/sdlc/code.playbook/code.fsm.js +74 -25
  24. package/reference/sdlc/code.playbook/code.fsm.ts +83 -29
  25. package/reference/sdlc/code.playbook/code.gears.md +0 -2
  26. package/reference/sdlc/code.playbook/code.playbook.d.ts +5 -2
  27. package/reference/sdlc/code.playbook/code.playbook.js +54 -2
  28. package/reference/sdlc/code.playbook/code.playbook.ts +75 -6
  29. package/reference/sdlc/code.playbook/code.registry.d.ts +10 -3
  30. package/reference/sdlc/code.playbook/code.registry.js +10 -3
  31. package/reference/sdlc/code.playbook/code.registry.ts +23 -5
  32. package/reference/sdlc/code.playbook/playbook-captain.d.ts +103 -8
  33. package/reference/sdlc/code.playbook/playbook-captain.js +1871 -75
  34. package/reference/sdlc/code.playbook/playbook-captain.ts +2801 -102
  35. package/reference/sdlc/code.playbook/playbook.config.template.yaml +14 -10
  36. package/reference/sdlc/code.playbook/session-store.d.ts +82 -0
  37. package/reference/sdlc/code.playbook/session-store.js +113 -0
  38. package/reference/sdlc/decide.md +24 -16
  39. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +7 -0
  40. package/reference/sdlc/decide.playbook/decide.fsm.js +80 -29
  41. package/reference/sdlc/decide.playbook/decide.fsm.ts +89 -31
  42. package/reference/sdlc/decide.playbook/decide.gears.md +0 -1
  43. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +13 -5
  44. package/reference/sdlc/decide.playbook/decide.playbook.js +1712 -91
  45. package/reference/sdlc/decide.playbook/decide.playbook.ts +2677 -136
  46. package/reference/sdlc/decide.playbook/decide.registry.d.ts +7 -3
  47. package/reference/sdlc/decide.playbook/decide.registry.js +10 -3
  48. package/reference/sdlc/decide.playbook/decide.registry.ts +20 -5
  49. package/reference/sdlc/review.md +36 -18
  50. package/reference/sdlc/review.playbook/review.fsm.d.ts +7 -0
  51. package/reference/sdlc/review.playbook/review.fsm.js +133 -12
  52. package/reference/sdlc/review.playbook/review.fsm.ts +140 -12
  53. package/reference/sdlc/review.playbook/review.playbook.d.ts +5 -2
  54. package/reference/sdlc/review.playbook/review.playbook.js +65 -2
  55. package/reference/sdlc/review.playbook/review.playbook.ts +83 -6
  56. package/reference/sdlc/review.playbook/review.registry.d.ts +10 -3
  57. package/reference/sdlc/review.playbook/review.registry.js +10 -3
  58. package/reference/sdlc/review.playbook/review.registry.ts +23 -5
  59. package/slc/gears2fsm.md +6 -5
  60. package/slc/link.md +544 -41
  61. package/src/accepted-outcome.d.ts +18 -0
  62. package/src/accepted-outcome.js +94 -0
  63. package/src/accepted-outcome.ts +140 -0
  64. package/src/runtime.d.ts +164 -3
  65. package/src/runtime.ts +213 -2
  66. package/src/xstate-playbook-runtime.d.ts +149 -10
  67. package/src/xstate-playbook-runtime.js +2569 -270
  68. package/src/xstate-playbook-runtime.ts +4133 -490
  69. package/src/xstate-runtime.d.ts +59 -1
  70. package/src/xstate-runtime.js +866 -7
  71. package/src/xstate-runtime.ts +1397 -7
package/README.md CHANGED
@@ -63,7 +63,7 @@ playbook
63
63
  Type a task, enter `/code <task>` for implementation, or enter
64
64
  `/decide <question>` for an independently proposed and reviewed decision.
65
65
 
66
- On first launch, Playbook writes its config to `${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml`.
66
+ On first launch, Playbook writes its config to `${SPEX_HOME:-$HOME/.spex}/playbook/playbook.config.yaml`. A config left at the former `${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml` is moved there on the next launch, unless a relative primary locator would change targets; that case is rejected unchanged with absolute replacements.
67
67
 
68
68
  The same config, compiled Captain, enabled playbooks, stable players, and
69
69
  nested calls power headless turns. Both front ends create the same durable
package/docs/cli.md CHANGED
@@ -169,7 +169,7 @@ stderr, and `--verbose` adds only telemetry topic names to stderr.
169
169
  | `--no-provision` | do not create missing engine links for configured filesystem registries |
170
170
  | `--json` | print exactly one `sessionId` / `reply` object |
171
171
  | `--verbose` | add Captain telemetry topic names to stderr |
172
- | `--continue` | continue the latest durable Captain session |
172
+ | `--continue` | continue the newest Captain session stored for this working directory, or the reported global fallback |
173
173
  | `--session <id>` | continue one durable Captain session explicitly |
174
174
  | `--retry-uncertain` | with `--session`, retry its exact recorded uncertain input |
175
175
  | `--discard-uncertain` | with `--session`, abandon its uncertain attempt |
@@ -188,7 +188,7 @@ The former positional `<from>`, `resume`, `--player`, `--captain`,
188
188
  `--option`, `--cwd`, `--last`, run-only `--config`, and top-level `run:`
189
189
  config are removed from `playbook run`. Enable a registry under `playbooks`,
190
190
  declare provider agents once under top-level `players`, bind every local role
191
- under `playbooks.<id>.roles`, tune compatible model and effort in a `--with`
191
+ under `playbooks.<id>.roles`, tune compatible model, effort, and fast mode in a `--with`
192
192
  overlay, invoke the effective `/command`, and run from the working directory
193
193
  you want agents to use. Legacy `playbooks.<id>.players` blocks are rejected and
194
194
  are not auto-migrated because choosing equal or distinct new player IDs chooses
@@ -231,15 +231,16 @@ module's directory is a git repository, add `node_modules/` to its
231
231
 
232
232
  ### Continuing a Captain session
233
233
 
234
- Interactive and headless commands write the same logical-session records under
235
- `${XDG_STATE_HOME:-$HOME/.local/state}/playbook/sessions/`. A fresh interactive
236
- child persists turn zero before printing `playbook: session <id>` and opening
237
- Boss input; a fresh headless turn returns the same kind of ID in `--json`.
238
- After the current writer exits or explicitly hands off, either presentation
239
- can reopen either origin:
234
+ Interactive and headless commands write the same logical-session records in
235
+ the configured [`sessions` directory](configuration.md#session-storage), whose
236
+ default is `${XDG_STATE_HOME:-$HOME/.local/state}/playbook/sessions/`. A fresh
237
+ interactive child persists turn zero before printing `playbook: session <id>`
238
+ and opening Boss input; a fresh headless turn returns the same kind of ID in
239
+ `--json`. After the current writer exits or explicitly hands off, either
240
+ presentation can reopen either origin:
240
241
 
241
242
  ```sh
242
- # Reopen the latest settled session headlessly:
243
+ # Reopen the newest session stored for this working directory headlessly:
243
244
  playbook run --continue "keep the scope small; skip the docs"
244
245
 
245
246
  # Reopen one exact session in either presentation:
@@ -247,6 +248,14 @@ playbook --session 4f2c0000-0000-4000-8000-000000009ab1
247
248
  playbook run --session 4f2c0000-0000-4000-8000-000000009ab1
248
249
  ```
249
250
 
251
+ Bare `--continue` prefers the newest durable record whose stored working
252
+ directory equals the directory where the command is invoked. If none matches,
253
+ it reports that absence on stderr and selects the globally newest record,
254
+ naming that session and its stored working directory. Use `--session <id>` to
255
+ select one exact session without applying the directory preference. The
256
+ uncertainty rules below still apply to whichever record is selected
257
+ ([DR-041](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/041-working-directory-aware-continuation.md)).
258
+
250
259
  A missing headless reply is read verbatim from stdin. Reopening restores the
251
260
  compiled Captain conversation, engagement stack, nested child boundary,
252
261
  stable-player ledger, and absolute working directory without replaying a
@@ -257,13 +266,78 @@ competing front end fails closed instead of forking the history.
257
266
  An ordinary reopen reads current config and any opening `--with` fragments,
258
267
  projects them to the stored catalog and player roster, and requires the stored
259
268
  role bindings plus every structural setting to remain exact. Compatible
260
- current `model` and `effort` selections apply to the next call, including an
261
- explicit boolean `false` provider-default reset. The retained provider token
262
- is never silently replaced by a fresh conversation if that selection is not
263
- supported
269
+ current `model`, `effort`, and optional `fastMode` settings apply to the next
270
+ call. Boolean `false` explicitly resets model or effort to the provider
271
+ default, but `fastMode: false` is a literal disabled request; omission selects
272
+ the fast-mode provider default or inherits the player value at a role binding.
273
+ The retained provider token is never silently replaced by a fresh conversation
274
+ if a requested setting is not supported
264
275
  ([[playbook-cli-22](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-22)],
265
276
  [DR-032](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/032-explicit-roles-session-players.md)).
266
277
 
278
+ ### Following replay history
279
+
280
+ Beside each `<session-id>.json` manifest, both front ends tee every observed
281
+ host record in order to `<session-id>.records.jsonl`. Each complete line is a
282
+ version-1 envelope with a writer-assigned sequence contiguous from `1`, the
283
+ opaque record, and an optional local playbook role where the trace establishes
284
+ one unambiguously. The stream is a mode-`0600` regular non-symlink file inside
285
+ the private sessions directory. Readers consume only the complete
286
+ newline-terminated prefix; a final partial line is not presented as a record.
287
+
288
+ The replay projection recursively removes provider `resumeToken` fields and
289
+ string-valued `resume` selections while preserving `resume: false`. It is
290
+ therefore useful for following and presentation, but it cannot resume an agent
291
+ conversation and does not replace the private canonical manifest. External
292
+ hosts can follow this stream without taking the writer lease through the
293
+ published [`@sublang/playbook/session-store`
294
+ facade](embedding.md#sharing-the-cli-session-store).
295
+
296
+ Streams are unbounded in this version: neither front end prunes them, and
297
+ retention remains tied to the deferred session-deletion policy. Removing
298
+ provider credentials does not make the remaining replay content non-sensitive;
299
+ protect the prompts, replies, tool calls, events, and timestamps it contains as
300
+ sensitive session data.
301
+
302
+ Replay recording is fail-soft after the canonical session lease is valid. If
303
+ initialization, sanitization, append, repair, publication synchronization, or a
304
+ checkpoint fails, recording stops for that lease without changing the agent
305
+ turn, durable session settlement, reply, or exit outcome. The headless command
306
+ attempts this warning once on stderr:
307
+
308
+ ```text
309
+ playbook run: warning: replay history for session "<session-id>" may be incomplete; recording has stopped
310
+ ```
311
+
312
+ The interactive child instead presents one Captain status with the same
313
+ `warning: replay history ...` text and writes no raw stderr diagnostic. A later
314
+ lease gets its own warning only if its replay writer independently becomes
315
+ incomplete. The public facade emits no warning; an embedding host decides how
316
+ to present the `incomplete` status it receives
317
+ ([[playbook-cli-84](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-84)]).
318
+
319
+ ### Reconciling possible repository effects
320
+
321
+ For each governed player call, Playbook records a Git baseline before the call
322
+ and a durable receipt afterward. CODE and DECIDE commit arms, and REVIEW's
323
+ Coder commit arm, accept a commit only when the receipt proves exactly one
324
+ descendant commit with no residual repository change; REVIEW's Reviewer calls
325
+ and DECIDE's proposal calls require the repository to remain exact. The
326
+ player's prose, including any `Commit:` line, is presentation rather than
327
+ proof.
328
+
329
+ If interruption, missing semantic evidence, concurrent work, or an ambiguous
330
+ repository delta prevents those facts from agreeing, the workflow parks
331
+ without replaying the player. Captain reports whether a change was observed or
332
+ could not be excluded, with the available baseline, after-HEAD, and proven
333
+ commit identity but no repository paths. While parked, ask Captain either to
334
+ retry reconciliation from the retained evidence or to abandon the unresolved
335
+ attempt. Reconciliation may complete the saved evidence or restore an exact
336
+ deferred Boss-question checkpoint, but it starts no replacement player call;
337
+ abandonment disposes the complete engagement without claiming an authored
338
+ workflow outcome. The same restricted recovery survives process restart
339
+ ([DR-040](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/040-outcome-authority-effect-reconciliation.md)).
340
+
267
341
  ### Recovering an uncertain turn
268
342
 
269
343
  Before model work, the runner takes one exclusive session lease and writes an
@@ -277,14 +351,28 @@ playbook run --session 4f2c0000-0000-4000-8000-000000009ab1 --discard-uncertain
277
351
  ```
278
352
 
279
353
  Retry reads no input and reuses the byte-exact recorded turn and its exact
280
- attempted Captain, player, and per-role model/effort selections; current config
281
- cannot retune that attempt, and retry may duplicate external effects. Discard
354
+ attempted Captain, player, and per-role model, effort, and fast-mode settings;
355
+ current config cannot retune that attempt, and retry may duplicate external
356
+ effects. Discard
282
357
  reads no input and runs no model: it restores the exact prior settled boundary,
283
358
  or deletes a never-settled fresh session, while abandoning the attempted work.
284
359
  An interrupted interactive turn uses the same uncertain record and is
285
- recovered with these headless commands. Session files written by the removed
286
- direct v6 runner and legacy record schemas are not shared schema-3 Captain
287
- sessions and cannot be continued. Explicit selection rejects them. Implicit
288
- `--continue` reports and skips released schema-2 Captain records, naming each
289
- session and path; move them outside the sessions directory or remove them to
290
- silence the warning. Malformed records and unknown schemas still fail closed.
360
+ recovered with these headless commands. Current sessions use Captain-session
361
+ record schema 6. Records from the removed direct v6 runner and released record
362
+ schema 2 have incompatible player identity; Playbook 9 record schema 3, the
363
+ historical schema-4 retention shape, and both pre-release schema-5 shapes
364
+ predate the canonical schema-6 record boundary; the earlier schema-5 shape also
365
+ lacks `unresolvedEffects` and cannot prove whether governed work may replay.
366
+ Explicit selection rejects
367
+ all of them before registry construction or governed work with the applicable
368
+ cutover explanation. Implicit `--continue` reports and skips each fully
369
+ validated nonresumable record with its session id, path, applicable reason, and
370
+ an archive-or-remove remedy while leaving the file intact. Fresh-session
371
+ discovery likewise leaves and reports nonresumable, malformed, unsafe, or
372
+ unknown-schema files. A fully validated nonresumable record participates in
373
+ settled same-directory predecessor ordering: it declines adoption only when it
374
+ is newest, while an older or different-directory record does not block a newer
375
+ resumable predecessor. A record whose directory or order cannot be validated
376
+ publishes an empty fresh boundary without falling through. Those invalid
377
+ records still fail closed when explicitly selected or encountered by
378
+ `--continue`; no path converts or restores them.
@@ -4,22 +4,35 @@
4
4
  # Configuring agents
5
5
 
6
6
  Fresh launches and ordinary reopens read one config at
7
- `${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml`. The
7
+ `${SPEX_HOME:-$HOME/.spex}/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.
10
10
 
11
+ On the first launching command after upgrading Playbook, it moves a config from the
12
+ former `${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml` path
13
+ when the canonical path is absent. The one-time move preserves bytes and
14
+ permissions and leaves no compatibility alias, so running an older Spex host
15
+ afterward could seed a second file at the former path.
16
+
17
+ The current guard rejects relocation when a legacy relative `sessions` value
18
+ or relative filesystem `playbooks.<id>.from` would resolve differently below
19
+ the new directory. It leaves the former file unchanged and names every
20
+ target-preserving absolute replacement. Apply those replacements and retry;
21
+ Playbook does not rewrite the user-authored file.
22
+
11
23
  ```sh
12
- $EDITOR "${XDG_CONFIG_HOME:-$HOME/.config}/playbook/playbook.config.yaml"
24
+ $EDITOR "${SPEX_HOME:-$HOME/.spex}/playbook/playbook.config.yaml"
13
25
  ```
14
26
 
15
27
  ## Anatomy
16
28
 
17
29
  The config is top-level (no `config:` wrapper): a `captain` agent, one flat
18
30
  `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.
31
+ workflows and their explicit role bindings, an optional `sessions` storage
32
+ locator, and optional `layout` / `notifications` / `theme`. The Captain runs
33
+ hidden control and judge calls and writes the replies you see in the Captain
34
+ pane or on headless stdout. The three presentation fields apply only to
35
+ interactive tmux; headless runs ignore them.
23
36
 
24
37
  A **role** is local to a playbook artifact: CODE's `coder` and REVIEW's `coder`
25
38
  have the same semantic name but remain separate declarations. A **player** is
@@ -29,11 +42,12 @@ names, nesting, and ancestry never infer a binding.
29
42
 
30
43
  Each `captain` or `players.<player-id>` value is either an adapter shorthand
31
44
  (`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](https://github.com/sublang-ai/playbook/blob/main/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.
45
+ `effort`, `fastMode`, `instruction`, and `permissions`. Settings are inline per
46
+ stable agent
47
+ ([DR-021](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/021-inline-agent-settings.md)).
48
+ Dots in a player ID are literal characters, not YAML hierarchy. Other adapter
49
+ IDs pass through to `tmux-play` with a warning because `playbook` cannot
50
+ preflight their auth.
37
51
 
38
52
  Within a `playbooks.<id>` block, `from` (the registry module), `command` (an
39
53
  optional slash-command override), and `roles` are launcher-owned; every other
@@ -41,34 +55,35 @@ key is that playbook's option slice. Every manifest role must be present
41
55
  exactly once. The launcher injects the rest — you do not write host wiring by
42
56
  hand.
43
57
 
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:
58
+ The seeded config runs the stable Coder player on GPT-5.6 Sol with fast mode
59
+ enabled and the stable Reviewer player on Claude Opus 5:
46
60
 
47
61
  ```yaml
48
62
  captain:
49
63
  adapter: claude
50
- model: claude-opus-4-8
64
+ model: claude-opus-5
51
65
  effort: high
52
66
  permissions:
53
67
  mode: auto # protected auto mode for the Claude Captain
54
68
 
55
69
  players:
56
70
  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
71
  adapter: codex
65
- model: gpt-5.5
66
- effort: xhigh
72
+ model: gpt-5.6-sol
73
+ effort: ultra
74
+ fastMode: true
67
75
  permissions:
68
76
  mode: auto
69
77
  writablePaths:
70
78
  - .git # allow git metadata writes under Codex auto mode
71
79
 
80
+ dev.reviewer:
81
+ adapter: claude
82
+ model: claude-opus-5
83
+ effort: xhigh
84
+ permissions:
85
+ mode: auto # protected auto mode for the Claude Reviewer
86
+
72
87
  playbooks:
73
88
  code:
74
89
  from: '@sublang/playbook/code/registry'
@@ -104,21 +119,26 @@ roles:
104
119
  coder: dev.coder
105
120
  ```
106
121
 
107
- Use a block to override only that role invocation's model or effort:
122
+ Use a block to override only that role invocation's model, effort, or fast
123
+ mode:
108
124
 
109
125
  ```yaml
110
126
  roles:
111
127
  coder:
112
128
  player: dev.coder
113
- model: claude-opus-4-8[1m]
129
+ model: gpt-5.6-sol
114
130
  effort: false # explicitly reset to this provider's default
131
+ fastMode: false # literal disabled request, not a default sentinel
115
132
  ```
116
133
 
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.
134
+ Omitting any override inherits that player's top-level default. For `model`
135
+ and `effort`, boolean `false` selects the provider default explicitly, so a
136
+ resumed conversation cannot accidentally retain an earlier selection. For
137
+ `fastMode`, `false` is a literal request to disable fast mode; omitting the
138
+ top-level setting selects the provider default. A present fast-mode boolean is
139
+ accepted only for adapters Cligent reports as supporting it. A role binding
140
+ cannot override adapter, instruction, permissions, workspace, or tool posture;
141
+ those define the stable player envelope.
122
142
 
123
143
  ## Sharing, isolation, and concurrency
124
144
 
@@ -136,9 +156,9 @@ second top-level player and change only its binding:
136
156
  ```yaml
137
157
  players:
138
158
  review.coder:
139
- adapter: claude
140
- model: claude-opus-4-8[1m]
141
- effort: xhigh
159
+ adapter: codex
160
+ model: gpt-5.6-sol
161
+ effort: ultra
142
162
 
143
163
  playbooks:
144
164
  review:
@@ -183,8 +203,9 @@ playbook run --with fast-lineup.yaml "/code implement the approved change"
183
203
  # fast-lineup.yaml — retune the shared Coder; nothing is written back.
184
204
  players:
185
205
  dev.coder:
186
- model: claude-opus-4-8
206
+ model: gpt-5.6-sol
187
207
  effort: medium
208
+ fastMode: false
188
209
  ```
189
210
 
190
211
  Fragments merge into the agent block rather than replacing it, so
@@ -207,9 +228,37 @@ The global file is never modified, and `--with` is not forwarded to
207
228
  Overlays apply when creating a fresh session and as current-config input for a
208
229
  compatible ordinary reopen. A selected session keeps its stored catalog,
209
230
  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.
231
+ directory; only model, effort, and fast mode may change. The next call reapplies
232
+ both complete model and effort selections and the optional effective fast-mode
233
+ boolean. An uncertain retry accepts no tuning overlay and uses the exact
234
+ attempted settings already stored with that turn.
235
+
236
+ ## Session storage
237
+
238
+ Both front ends select canonical session manifests and write replay streams in
239
+ one directory, where external hosts may keep their own sidecars too. Set the
240
+ optional top-level `sessions` key to move that shared store:
241
+
242
+ ```yaml
243
+ sessions: ./state/playbook-sessions
244
+ ```
245
+
246
+ The value must be a nonempty filesystem path. When the key is absent, the
247
+ directory is
248
+ `${XDG_STATE_HOME:-$HOME/.local/state}/playbook/sessions`. An absolute path is
249
+ used as given; `~` and `~/...` expand from the home directory, while `~user`
250
+ is rejected. Every other value, including a bare relative path such as the one
251
+ above, resolves against the primary config file's directory rather than the
252
+ invocation directory. A `sessions` value in a `--with` overlay replaces the
253
+ primary value, with later overlays winning.
254
+
255
+ Launch validates that the resolved path can serve as the mode-`0700`, real,
256
+ non-symlink session store before selecting a record or starting agent work and
257
+ fails closed when it cannot. The non-launching `playbook --list` command still
258
+ validates the locator's syntax but does not inspect that directory's filesystem
259
+ usability. The resolved locator is launch configuration only: it never enters
260
+ a persisted structural or execution projection
261
+ ([[playbook-cli-78](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-78)]).
213
262
 
214
263
  ## Durable shared configuration
215
264
 
@@ -223,9 +272,9 @@ public UUID. Presentation-only fields are inert headlessly.
223
272
  An ordinary reopen reads current config and opening overlays, but first
224
273
  projects them to the stored playbooks and referenced players. An unrelated new
225
274
  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.
275
+ compatible model, effort, or fast-mode changes apply on the next provider
276
+ call. Legacy record, shell, runtime-snapshot, and trace schemas are rejected
277
+ rather than having role or player identity guessed.
229
278
 
230
279
  ## External playbooks
231
280
 
package/docs/embedding.md CHANGED
@@ -37,7 +37,9 @@ depends on) rather than relying on it resolving through the package's
37
37
  tree, which pnpm's strict linking will not allow.
38
38
 
39
39
  ```ts
40
- import createPlaybookRuntime from '@sublang/playbook/review/playbook';
40
+ import createPlaybookRuntime, {
41
+ type ReviewPlaybookHostCapabilities,
42
+ } from '@sublang/playbook/review/playbook';
41
43
  import type {
42
44
  CaptainCallOptions,
43
45
  CaptainResult,
@@ -144,9 +146,21 @@ const ports: PlaybookPorts = {
144
146
  },
145
147
  };
146
148
 
147
- const runtime = createPlaybookRuntime({});
148
-
149
149
  const playbookSessionId = randomUUID();
150
+
151
+ // Schema-3 artifacts keep persisted configured options separate from live
152
+ // current-host authority. Build these capabilities only after acquiring the
153
+ // session lease and resolving the canonical Git worktree. Their authority
154
+ // must name this playbook/session/working directory and their repository and
155
+ // effect-ledger operations must stay live; never put them in configuration,
156
+ // machine input, or a persisted snapshot.
157
+ declare const hostCapabilities: ReviewPlaybookHostCapabilities;
158
+
159
+ const runtime = createPlaybookRuntime({
160
+ configuredOptions: {},
161
+ hostCapabilities,
162
+ });
163
+
150
164
  await runtime.init({
151
165
  sessionId: playbookSessionId,
152
166
  playbookId: 'review',
@@ -165,7 +179,7 @@ await runtime.dispose();
165
179
 
166
180
  ## Sessions and traces
167
181
 
168
- Every init-to-dispose lifecycle is one playbook session. Schema-3
182
+ Every init-to-dispose lifecycle is one playbook session. Schema-4
169
183
  `playbook.trace` telemetry carries that immutable ID plus a contiguous
170
184
  sequence across exact Boss input, judge/player calls, FSM transitions, visible
171
185
  Captain work, nested playbook calls, status, settlement, and disposal. A
@@ -184,15 +198,115 @@ sequential call lane across every frame that names them, while distinct IDs
184
198
  remain isolated. Child return, frame disposal, and a later root engagement do
185
199
  not clear the session ledger.
186
200
 
187
- Runtime snapshots are schema 3. Their `roleResumeTokens` projection remains
188
- role-local, while the composing shell's own schema-3 snapshot persists the
189
- stable player ledger and every frame's exact role bindings. Do not restore
190
- schema 1 or 2 by guessing identity. On a compatible restore, rebuild
201
+ Runtime and complete shell snapshots are schema 4. Their role-resume
202
+ projection remains role-local, while the composing shell persists the stable
203
+ player ledger, every frame's exact role bindings, and the host's authoritative
204
+ effect-ledger mirror. The bundled CLI wraps that shell state in Captain
205
+ session-record schema 6. Do not restore an earlier snapshot or session record
206
+ by guessing identity or effect evidence. This includes both pre-release
207
+ schema-5 record shapes; the earlier one predates required `unresolvedEffects`,
208
+ and neither is a canonical schema-6 boundary. Explicit selection rejects them,
209
+ while fresh discovery reports and skips them. A fully validated nonresumable
210
+ record participates in settled same-directory ordering and declines adoption
211
+ only when it is newest; an older or different-directory record does not block a
212
+ newer resumable predecessor. An unvalidatable record leaves ordering unproved,
213
+ so discovery publishes an empty target without falling through. On a compatible
214
+ restore, rebuild
191
215
  `promptIdentity` from the current model selection (or adapter for an explicit
192
- provider-default selection) so the next prompt and trace describe the current
193
- invocation rather than stale machine state. Trace data and tokens never enter
194
- Boss-visible status text. Because trace observers do receive opaque resume
195
- tokens, persisted traces should be protected as sensitive data.
216
+ provider-default selection) and rebuild live host capabilities under the
217
+ current lease, so neither invocation identity nor repository authority comes
218
+ from stale machine state. Trace data, tokens, repository projections, and
219
+ capability functions never enter Boss-visible status text or configured
220
+ options. Because trace observers do receive opaque resume tokens, persisted
221
+ traces should be protected as sensitive data.
222
+
223
+ ## Sharing the CLI session store
224
+
225
+ An external host that needs the CLI's canonical session validation and
226
+ token-free replay format can use the narrow, semver-stable
227
+ `@sublang/playbook/session-store` facade. It shares the CLI's private store and
228
+ validators by construction but exposes no canonical manifest, snapshot,
229
+ provider credential, effect ledger, recovery operation, or turn-lifecycle
230
+ operation:
231
+
232
+ ```ts
233
+ import {
234
+ defaultSessionsDir,
235
+ openSessionStore,
236
+ } from '@sublang/playbook/session-store';
237
+
238
+ const store = openSessionStore(defaultSessionsDir());
239
+ const { sessions, skipped } = await store.list();
240
+
241
+ const sessionId = sessions[0]?.sessionId;
242
+ if (sessionId !== undefined) {
243
+ const summary = await store.read(sessionId);
244
+ console.log(summary.sessionId, summary.state, summary.cwd);
245
+
246
+ const first = await store.readStream(sessionId);
247
+ const next = await store.readStream(sessionId, {
248
+ afterSeq: first.lastReadableSeq,
249
+ });
250
+ console.log(next.entries, skipped);
251
+ }
252
+ ```
253
+
254
+ `list()` reports valid summaries and separately reports skipped canonical
255
+ manifests with their validation or cutover reason. A summary has exactly
256
+ `schemaVersion`, `sessionId`, `state`, `cwd`, and `updatedAt`. A lease-free
257
+ `readStream()` returns complete envelopes and `lastReadableSeq` only: it makes
258
+ no claim that another process has durably checkpointed the observed bytes or
259
+ that its live writer remains complete. An absent stream reads as empty, and
260
+ acquiring its lease does not require a manifest; only `read()` requires a
261
+ canonical session summary. Pass an absolute path to `openSessionStore()` when
262
+ using a directory other than the environment-derived default.
263
+
264
+ Writing requires the one exclusive session lease. The writer assigns envelope
265
+ version and sequence, serializes overlapping appends in invocation order, and
266
+ strips provider resume credentials from every accepted record:
267
+
268
+ ```ts
269
+ const lease = await store.acquire(
270
+ '4f2c0000-0000-4000-8000-000000009ab1',
271
+ );
272
+ try {
273
+ await lease.append({ type: 'host_notice', message: 'attached' });
274
+ const status = lease.streamStatus();
275
+ console.log(status);
276
+ if (status.lastReadableSeq !== null) {
277
+ const replay = await lease.readStream();
278
+ console.log(replay.lastReadableSeq, replay.lastDurableSeq);
279
+ }
280
+ } finally {
281
+ const finalStatus = await lease.release();
282
+ console.log(finalStatus);
283
+ }
284
+ ```
285
+
286
+ `streamStatus()` synchronously returns the current live status. If initialization
287
+ could not establish a trustworthy whole-stream boundary, it returns
288
+ `{ lastReadableSeq: null, lastDurableSeq: null, incomplete: true }`, and
289
+ `lease.readStream()` rejects rather than return partial history.
290
+ An `append()` suppressed before release by either unavailable initialization or
291
+ a numeric incomplete latch resolves `undefined` without recording the supplied
292
+ record, so fulfillment alone does not prove persistence.
293
+
294
+ Always release a successfully acquired lease. `release()` closes append
295
+ admission, drains earlier appends, attempts the final checkpoint, retires the
296
+ lease, and returns the final `lastReadableSeq`, `lastDurableSeq`, and
297
+ `incomplete` status. A replay initialization failure reports unavailable null
298
+ boundaries; a later sanitization, repair, or persistence failure latches
299
+ numeric `incomplete` status. Either state suppresses later replay work on that
300
+ lease and remains isolated from canonical session lifecycle work. The facade
301
+ writes no warning to stdout or stderr; the embedding host owns any presentation
302
+ of that status.
303
+
304
+ For control flow, a missing canonical manifest from `read()` uses
305
+ `Error.code === 'PLAYBOOK_SESSION_NOT_FOUND'`, and a competing live or foreign
306
+ lease uses `Error.code === 'PLAYBOOK_SESSION_LEASE_ACTIVE'`. Do not match error
307
+ messages or assume those codes for malformed input, unsafe storage, an
308
+ indeterminate owner probe, or another storage failure
309
+ ([[playbook-cli-73](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-73)]).
196
310
 
197
311
  See
198
312
  [`code.playbook.test.ts`](https://github.com/sublang-ai/playbook/blob/main/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": "9.0.0",
3
+ "version": "11.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",
@@ -28,6 +28,9 @@
28
28
  "src/runtime.ts",
29
29
  "src/runtime.js",
30
30
  "src/runtime.d.ts",
31
+ "src/accepted-outcome.ts",
32
+ "src/accepted-outcome.js",
33
+ "src/accepted-outcome.d.ts",
31
34
  "src/xstate-runtime.ts",
32
35
  "src/xstate-runtime.js",
33
36
  "src/xstate-runtime.d.ts",
@@ -64,15 +67,19 @@
64
67
  "reference/sdlc/code.playbook/playbook-captain.ts",
65
68
  "reference/sdlc/code.playbook/playbook-captain.js",
66
69
  "reference/sdlc/code.playbook/playbook-captain.d.ts",
70
+ "reference/sdlc/code.playbook/session-store.js",
71
+ "reference/sdlc/code.playbook/session-store.d.ts",
67
72
  "reference/sdlc/code.playbook/code.gears.md",
68
73
  "reference/sdlc/code.playbook/playbook.config.template.yaml",
69
74
  "reference/sdlc/code.playbook/bin/playbook.js",
70
75
  "reference/sdlc/code.playbook/bin/launch-config.js",
71
76
  "reference/sdlc/code.playbook/bin/run.js",
72
77
  "reference/sdlc/code.playbook/bin/interactive-session.js",
78
+ "reference/sdlc/code.playbook/bin/replay-observer.js",
73
79
  "reference/sdlc/code.playbook/bin/session-store.js",
74
80
  "reference/sdlc/code.playbook/bin/provision.js",
75
81
  "reference/sdlc/code.playbook/bin/adapter-sdk.js",
82
+ "reference/sdlc/code.playbook/bin/repository-effects.js",
76
83
  "reference/sdlc/review.playbook/review.gears.md",
77
84
  "reference/sdlc/review.playbook/review.fsm.ts",
78
85
  "reference/sdlc/review.playbook/review.fsm.js",
@@ -123,6 +130,10 @@
123
130
  "types": "./reference/sdlc/code.playbook/playbook-captain.d.ts",
124
131
  "default": "./reference/sdlc/code.playbook/playbook-captain.js"
125
132
  },
133
+ "./session-store": {
134
+ "types": "./reference/sdlc/code.playbook/session-store.d.ts",
135
+ "default": "./reference/sdlc/code.playbook/session-store.js"
136
+ },
126
137
  "./slc/*": "./slc/*",
127
138
  "./review/playbook": {
128
139
  "types": "./reference/sdlc/review.playbook/review.playbook.d.ts",
@@ -154,8 +165,8 @@
154
165
  "provenance": true
155
166
  },
156
167
  "dependencies": {
157
- "@sublang/cligent": "^0.22.0",
158
- "@sublang/spex": "^2.1.1",
168
+ "@sublang/cligent": "^0.24.0",
169
+ "@sublang/spex": "^3.0.0",
159
170
  "p-queue": "^9.3.1",
160
171
  "xstate": "^5.19.4",
161
172
  "yaml": "^2.9.0"