@sublang/playbook 8.0.0 → 10.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 (66) hide show
  1. package/README.md +3 -3
  2. package/docs/cli.md +66 -23
  3. package/docs/configuration.md +13 -8
  4. package/docs/embedding.md +45 -14
  5. package/package.json +7 -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 +151 -10
  13. package/reference/sdlc/captain.playbook/captain.playbook.ts +200 -14
  14. package/reference/sdlc/code.md +0 -1
  15. package/reference/sdlc/code.playbook/bin/interactive-session.js +170 -17
  16. package/reference/sdlc/code.playbook/bin/launch-config.js +136 -4
  17. package/reference/sdlc/code.playbook/bin/playbook.js +81 -4
  18. package/reference/sdlc/code.playbook/bin/repository-effects.js +2930 -0
  19. package/reference/sdlc/code.playbook/bin/run.js +365 -63
  20. package/reference/sdlc/code.playbook/bin/session-store.js +2877 -209
  21. package/reference/sdlc/code.playbook/code.fsm.d.ts +11 -1
  22. package/reference/sdlc/code.playbook/code.fsm.js +85 -29
  23. package/reference/sdlc/code.playbook/code.fsm.ts +95 -33
  24. package/reference/sdlc/code.playbook/code.gears.md +0 -2
  25. package/reference/sdlc/code.playbook/code.playbook.d.ts +5 -2
  26. package/reference/sdlc/code.playbook/code.playbook.js +67 -4
  27. package/reference/sdlc/code.playbook/code.playbook.ts +87 -8
  28. package/reference/sdlc/code.playbook/code.registry.d.ts +10 -3
  29. package/reference/sdlc/code.playbook/code.registry.js +10 -3
  30. package/reference/sdlc/code.playbook/code.registry.ts +23 -5
  31. package/reference/sdlc/code.playbook/playbook-captain.d.ts +99 -7
  32. package/reference/sdlc/code.playbook/playbook-captain.js +1894 -82
  33. package/reference/sdlc/code.playbook/playbook-captain.ts +2809 -109
  34. package/reference/sdlc/decide.md +0 -1
  35. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +8 -1
  36. package/reference/sdlc/decide.playbook/decide.fsm.js +80 -29
  37. package/reference/sdlc/decide.playbook/decide.fsm.ts +89 -31
  38. package/reference/sdlc/decide.playbook/decide.gears.md +0 -1
  39. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +15 -5
  40. package/reference/sdlc/decide.playbook/decide.playbook.js +1994 -191
  41. package/reference/sdlc/decide.playbook/decide.playbook.ts +3209 -404
  42. package/reference/sdlc/decide.playbook/decide.registry.d.ts +7 -3
  43. package/reference/sdlc/decide.playbook/decide.registry.js +10 -3
  44. package/reference/sdlc/decide.playbook/decide.registry.ts +20 -5
  45. package/reference/sdlc/review.playbook/review.fsm.d.ts +7 -0
  46. package/reference/sdlc/review.playbook/review.fsm.js +133 -12
  47. package/reference/sdlc/review.playbook/review.fsm.ts +140 -12
  48. package/reference/sdlc/review.playbook/review.playbook.d.ts +5 -2
  49. package/reference/sdlc/review.playbook/review.playbook.js +78 -4
  50. package/reference/sdlc/review.playbook/review.playbook.ts +95 -8
  51. package/reference/sdlc/review.playbook/review.registry.d.ts +10 -3
  52. package/reference/sdlc/review.playbook/review.registry.js +10 -3
  53. package/reference/sdlc/review.playbook/review.registry.ts +23 -5
  54. package/slc/gears2fsm.md +25 -7
  55. package/slc/link.md +727 -82
  56. package/src/accepted-outcome.d.ts +18 -0
  57. package/src/accepted-outcome.js +94 -0
  58. package/src/accepted-outcome.ts +140 -0
  59. package/src/runtime.d.ts +165 -3
  60. package/src/runtime.ts +214 -2
  61. package/src/xstate-playbook-runtime.d.ts +162 -13
  62. package/src/xstate-playbook-runtime.js +3344 -564
  63. package/src/xstate-playbook-runtime.ts +4873 -637
  64. package/src/xstate-runtime.d.ts +76 -8
  65. package/src/xstate-runtime.js +1001 -64
  66. package/src/xstate-runtime.ts +1640 -91
package/README.md CHANGED
@@ -81,7 +81,7 @@ playbook run --session 4f2c0000-0000-4000-8000-000000009ab1 "continue"
81
81
 
82
82
  `playbook run` prints the one Boss-visible Captain reply to stdout and operational status to stderr; CODE and DECIDE can complete their nested REVIEW calls there too.
83
83
 
84
- See [Using the CLI](docs/cli.md) for flags and durable continuation, [Configuring agents](docs/configuration.md) for the shared lineup, [Embedding](docs/embedding.md) for custom hosts, and the [changelog](CHANGELOG.md) for releases.
84
+ See [Using the CLI](docs/cli.md) for flags and durable continuation, [Configuring agents](docs/configuration.md) for the shared lineup, [Embedding](docs/embedding.md) for custom hosts, and the [changelog](https://github.com/sublang-ai/playbook/blob/main/CHANGELOG.md) for releases.
85
85
 
86
86
  ## Create your own playbook
87
87
 
@@ -129,8 +129,8 @@ pnpm playbook # drive a Boss turn against the source tree
129
129
  ```
130
130
 
131
131
  Playbook is itself spec-driven: the compiler phases are specs in [`slc/`](slc), and the reference playbooks are regenerated from their prose sources.
132
- Edit a source, regenerate its GEARS, FSM, and runtime artifacts, sync the tests and downstream specs until `pnpm test` passes, and commit with co-author trailers per [`specs/packages/git.md`](specs/packages/git.md).
133
- The gears↔FSM contract ([the playbook package](specs/packages/playbook.md)) and runtime contract ([the playbook-runtime package](specs/packages/playbook-runtime.md)) are pinned in [`specs/packages/`](specs/packages) and verified by the test suite.
132
+ Edit a source, regenerate its GEARS, FSM, and runtime artifacts, sync the tests and downstream specs until `pnpm test` passes, and commit with co-author trailers per [`specs/packages/git.md`](https://github.com/sublang-ai/playbook/blob/main/specs/packages/git.md).
133
+ The gears↔FSM contract ([the playbook package](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook.md)) and runtime contract ([the playbook-runtime package](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md)) are pinned in [`specs/packages/`](https://github.com/sublang-ai/playbook/tree/main/specs/packages) and verified by the test suite.
134
134
 
135
135
  ## License
136
136
 
package/docs/cli.md CHANGED
@@ -18,7 +18,7 @@ Each adapter is backed by a vendor runtime that installing
18
18
  agent stack you did not ask for. Which versions each adapter supports
19
19
  is [cligent](https://github.com/sublang-ai/cligent)'s knowledge and
20
20
  ships with it
21
- ([DR-027](../specs/decisions/027-runtime-compatibility-from-cligent.md));
21
+ ([DR-027](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/027-runtime-compatibility-from-cligent.md));
22
22
  the commands below install the latest, which cligent accepts from its
23
23
  supported floor up. Install the SDKs your config names, each as its
24
24
  own top-level install root:
@@ -45,7 +45,7 @@ finds a bare specifier by walking *up* from there — which reaches the
45
45
  install prefix's own `node_modules`, but never into a sibling package's
46
46
  subtree. An SDK that landed inside some other package is invisible to
47
47
  the adapter even though it is on disk
48
- ([DR-026](../specs/decisions/026-optional-adapter-sdks.md)).
48
+ ([DR-026](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/026-optional-adapter-sdks.md)).
49
49
 
50
50
  Both surfaces check this before doing any work: a declared adapter
51
51
  whose runtime is not loadable — or is installed below the version
@@ -54,7 +54,7 @@ runtime is reported as not installed; a stale one with its installed
54
54
  and required versions, never as absent. Either way the remedy printed
55
55
  is cligent's pinned install, `npm install -g <package>@<version>`, so
56
56
  following it cannot install a version the gate refuses again
57
- ([[playbook-cli-40](../specs/packages/playbook-cli.md#playbook-cli-40)]).
57
+ ([[playbook-cli-40](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-40)]).
58
58
 
59
59
  ## Interactive
60
60
 
@@ -98,8 +98,8 @@ later signals use native client detach or termination semantics and do not
98
98
  retire the pane child's session. Only `--config` and composed
99
99
  `--theme-diagnostics` use the stock subprocess boundary: those forms mirror
100
100
  its exit status or signal and exit `127` when it cannot be spawned
101
- ([[playbook-cli-1](../specs/packages/playbook-cli.md#playbook-cli-1)],
102
- [[playbook-cli-2](../specs/packages/playbook-cli.md#playbook-cli-2)]).
101
+ ([[playbook-cli-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-1)],
102
+ [[playbook-cli-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-2)]).
103
103
 
104
104
  ### Running a Boss turn
105
105
 
@@ -118,8 +118,8 @@ currently offers. It never does the specialized work itself, and a
118
118
  conversational turn — including a progress or status question — leaves
119
119
  the engagement, its parked state, and any pending player question
120
120
  untouched
121
- ([[playbook-captain-1](../specs/packages/playbook-captain.md#playbook-captain-1)],
122
- [[playbook-captain-2](../specs/packages/playbook-captain.md#playbook-captain-2)]).
121
+ ([[playbook-captain-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-1)],
122
+ [[playbook-captain-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-2)]).
123
123
 
124
124
  The current CODE, REVIEW, and DECIDE workflows take their deterministic
125
125
  initial event from the selecting Boss turn. CODE and DECIDE then call REVIEW
@@ -131,16 +131,16 @@ identical. When a player surfaces a
131
131
  clarifying question the FSM parks, the pane shows the question, and a
132
132
  judge classifies your next turn as its reply or a fresh directive that
133
133
  abandons it
134
- ([[playbook-runtime-2](../specs/packages/playbook-runtime.md#playbook-runtime-2)]).
134
+ ([[playbook-runtime-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-2)]).
135
135
 
136
136
  The Captain pane shows start/stop/finished status with `◇` lines and
137
137
  streams progress with captain-speech classification and questions
138
- ([[playbook-runtime-3](../specs/packages/playbook-runtime.md#playbook-runtime-3)]), while player
138
+ ([[playbook-runtime-3](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-3)]), while player
139
139
  prompts ride their own panes. A turn that actually did something ends
140
140
  with one Captain reply summarizing what changed, composed only from that
141
141
  turn's reported outcome; a turn that changed nothing ends with an
142
142
  ordinary reply and no saved-counts line
143
- ([[playbook-captain-19](../specs/packages/playbook-captain.md#playbook-captain-19)]).
143
+ ([[playbook-captain-19](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-19)]).
144
144
 
145
145
  ## Headless
146
146
 
@@ -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 |
@@ -182,7 +182,7 @@ Captain reply. Argument, config, catalog, readiness, or pre-turn setup errors
182
182
  exit `1`; a started-turn, persistence, lease-release, or presentation failure
183
183
  exits `2` with stdout empty. SIGINT, SIGTERM, and SIGHUP preserve the
184
184
  uncertain boundary, withhold stdout, and are re-raised after lease retirement
185
- ([[playbook-cli-18](../specs/packages/playbook-cli.md#playbook-cli-18)]).
185
+ ([[playbook-cli-18](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-18)]).
186
186
 
187
187
  The former positional `<from>`, `resume`, `--player`, `--captain`,
188
188
  `--option`, `--cwd`, `--last`, run-only `--config`, and top-level `run:`
@@ -221,8 +221,8 @@ the primary config file. Before either front end imports a configured
221
221
  filesystem registry, the shared launcher checks whether that module can
222
222
  resolve `xstate` and `@sublang/playbook/xstate-runtime`. When needed, it
223
223
  creates engine symlinks beside the module and prints one provisioning line
224
- ([[playbook-cli-36](../specs/packages/playbook-cli.md#playbook-cli-36)],
225
- [DR-024](../specs/decisions/024-runtime-engine-provisioning.md)).
224
+ ([[playbook-cli-36](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-36)],
225
+ [DR-024](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/024-runtime-engine-provisioning.md)).
226
226
 
227
227
  A directory where both imports already resolve is untouched, and
228
228
  `--no-provision` disables new links for either fresh front end. If the
@@ -239,7 +239,7 @@ After the current writer exits or explicitly hands off, either presentation
239
239
  can reopen either origin:
240
240
 
241
241
  ```sh
242
- # Reopen the latest settled session headlessly:
242
+ # Reopen the newest session stored for this working directory headlessly:
243
243
  playbook run --continue "keep the scope small; skip the docs"
244
244
 
245
245
  # Reopen one exact session in either presentation:
@@ -247,6 +247,14 @@ playbook --session 4f2c0000-0000-4000-8000-000000009ab1
247
247
  playbook run --session 4f2c0000-0000-4000-8000-000000009ab1
248
248
  ```
249
249
 
250
+ Bare `--continue` prefers the newest durable record whose stored working
251
+ directory equals the directory where the command is invoked. If none matches,
252
+ it reports that absence on stderr and selects the globally newest record,
253
+ naming that session and its stored working directory. Use `--session <id>` to
254
+ select one exact session without applying the directory preference. The
255
+ uncertainty rules below still apply to whichever record is selected
256
+ ([DR-041](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/041-working-directory-aware-continuation.md)).
257
+
250
258
  A missing headless reply is read verbatim from stdin. Reopening restores the
251
259
  compiled Captain conversation, engagement stack, nested child boundary,
252
260
  stable-player ledger, and absolute working directory without replaying a
@@ -261,8 +269,30 @@ current `model` and `effort` selections apply to the next call, including an
261
269
  explicit boolean `false` provider-default reset. The retained provider token
262
270
  is never silently replaced by a fresh conversation if that selection is not
263
271
  supported
264
- ([[playbook-cli-22](../specs/packages/playbook-cli.md#playbook-cli-22)],
265
- [DR-032](../specs/decisions/032-explicit-roles-session-players.md)).
272
+ ([[playbook-cli-22](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-22)],
273
+ [DR-032](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/032-explicit-roles-session-players.md)).
274
+
275
+ ### Reconciling possible repository effects
276
+
277
+ For each governed player call, Playbook records a Git baseline before the call
278
+ and a durable receipt afterward. CODE and DECIDE commit arms, and REVIEW's
279
+ Coder commit arm, accept a commit only when the receipt proves exactly one
280
+ descendant commit with no residual repository change; REVIEW's Reviewer calls
281
+ and DECIDE's proposal calls require the repository to remain exact. The
282
+ player's prose, including any `Commit:` line, is presentation rather than
283
+ proof.
284
+
285
+ If interruption, missing semantic evidence, concurrent work, or an ambiguous
286
+ repository delta prevents those facts from agreeing, the workflow parks
287
+ without replaying the player. Captain reports whether a change was observed or
288
+ could not be excluded, with the available baseline, after-HEAD, and proven
289
+ commit identity but no repository paths. While parked, ask Captain either to
290
+ retry reconciliation from the retained evidence or to abandon the unresolved
291
+ attempt. Reconciliation may complete the saved evidence or restore an exact
292
+ deferred Boss-question checkpoint, but it starts no replacement player call;
293
+ abandonment disposes the complete engagement without claiming an authored
294
+ workflow outcome. The same restricted recovery survives process restart
295
+ ([DR-040](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/040-outcome-authority-effect-reconciliation.md)).
266
296
 
267
297
  ### Recovering an uncertain turn
268
298
 
@@ -282,9 +312,22 @@ cannot retune that attempt, and retry may duplicate external effects. Discard
282
312
  reads no input and runs no model: it restores the exact prior settled boundary,
283
313
  or deletes a never-settled fresh session, while abandoning the attempted work.
284
314
  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.
315
+ recovered with these headless commands. Current sessions use Captain-session
316
+ record schema 6. Records from the removed direct v6 runner and released record
317
+ schema 2 have incompatible player identity; Playbook 9 record schema 3, the
318
+ historical schema-4 retention shape, and both pre-release schema-5 shapes
319
+ predate the canonical schema-6 record boundary; the earlier schema-5 shape also
320
+ lacks `unresolvedEffects` and cannot prove whether governed work may replay.
321
+ Explicit selection rejects
322
+ all of them before registry construction or governed work with the applicable
323
+ cutover explanation. Implicit `--continue` reports and skips each fully
324
+ validated nonresumable record with its session id, path, applicable reason, and
325
+ an archive-or-remove remedy while leaving the file intact. Fresh-session
326
+ discovery likewise leaves and reports nonresumable, malformed, unsafe, or
327
+ unknown-schema files. A fully validated nonresumable record participates in
328
+ settled same-directory predecessor ordering: it declines adoption only when it
329
+ is newest, while an older or different-directory record does not block a newer
330
+ resumable predecessor. A record whose directory or order cannot be validated
331
+ publishes an empty fresh boundary without falling through. Those invalid
332
+ records still fail closed when explicitly selected or encountered by
333
+ `--continue`; no path converts or restores them.
@@ -30,7 +30,7 @@ names, nesting, and ancestry never infer a binding.
30
30
  Each `captain` or `players.<player-id>` value is either an adapter shorthand
31
31
  (`claude`, `codex`) or a block carrying that agent's own `adapter`, `model`,
32
32
  `effort`, `instruction`, and `permissions`. Settings are inline per stable
33
- agent ([DR-021](../specs/decisions/021-inline-agent-settings.md)). Dots in a
33
+ agent ([DR-021](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/021-inline-agent-settings.md)). Dots in a
34
34
  player ID are literal characters, not YAML hierarchy. Other adapter IDs pass
35
35
  through to `tmux-play` with a warning because `playbook` cannot preflight their
36
36
  auth.
@@ -91,7 +91,7 @@ playbooks:
91
91
  The current bundled workflows accept no workflow-specific options.
92
92
  Each role's per-call prompt names its current `model`, else its player's
93
93
  `adapter`
94
- ([[playbook-runtime-4](../specs/packages/playbook-runtime.md#playbook-runtime-4)]),
94
+ ([[playbook-runtime-4](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-4)]),
95
95
  so commit trailers credit the concrete model rather than the adapter
96
96
  family.
97
97
 
@@ -160,7 +160,7 @@ instead of doing the work itself. Claude and Gemini enforce that at the
160
160
  provider level. The Codex, Kimi, and OpenCode adapters cannot — they
161
161
  reject any tool list — so a `captain:` using one of them falls back to a
162
162
  prompt-level restriction
163
- ([DR-013](../specs/decisions/013-routing-only-captain-control.md) A1).
163
+ ([DR-013](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/013-routing-only-captain-control.md) A1).
164
164
  Those adapters remain good choices for *players*, where full tools are wanted.
165
165
 
166
166
  Adapter readiness is intentionally light: `claude` is ready with local
@@ -203,7 +203,7 @@ playbooks:
203
203
  ```
204
204
 
205
205
  The global file is never modified, and `--with` is not forwarded to
206
- `tmux-play` ([[playbook-cli-25](../specs/packages/playbook-cli.md#playbook-cli-25)]).
206
+ `tmux-play` ([[playbook-cli-25](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-25)]).
207
207
  Overlays apply when creating a fresh session and as current-config input for a
208
208
  compatible ordinary reopen. A selected session keeps its stored catalog,
209
209
  player roster, role bindings, adapter, instruction, permissions, and working
@@ -250,6 +250,11 @@ playbooks:
250
250
  playbook run "/my-workflow perform the task"
251
251
  ```
252
252
 
253
+ Importing a `.ts` registry uses Node's native type stripping, available
254
+ unflagged on Node 22.18+ and 23.6+; on the older Node versions this
255
+ package supports (>= 20.6), compile the registry and point `from` at the
256
+ emitted `.js` module instead.
257
+
253
258
  A relative path-shaped `from` is resolved relative to the primary config
254
259
  file, not the invocation directory; an absolute path is clearest for an SLC
255
260
  entry emitted in a project working tree.
@@ -318,9 +323,9 @@ or `--session`.
318
323
 
319
324
  The JSON response is now exactly `{ "sessionId": "…", "reply": "…" }`.
320
325
  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)]).
326
+ cannot be continued by the new host ([[playbook-cli-19](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-19)],
327
+ [[playbook-cli-22](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-22)],
328
+ [[playbook-cli-28](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-28)]).
324
329
 
325
330
  ## Migrating from `profiles`
326
331
 
@@ -334,7 +339,7 @@ do by hand.
334
339
 
335
340
  For a one-off, pass a raw `tmux-play` config explicitly. This bypasses
336
341
  the seed, composition, and readiness gate, forwarding arguments to
337
- `tmux-play` verbatim ([[playbook-cli-1](../specs/packages/playbook-cli.md#playbook-cli-1)]):
342
+ `tmux-play` verbatim ([[playbook-cli-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-1)]):
338
343
 
339
344
  ```sh
340
345
  playbook --config ./tmux-play.config.yaml
package/docs/embedding.md CHANGED
@@ -9,7 +9,7 @@ app) is another. This guide shows how to wire a playbook runtime into
9
9
  your own host.
10
10
 
11
11
  > **Release note:** this guide targets the current semver-stable six-port
12
- > contract; see the [CHANGELOG](../CHANGELOG.md) for migration details.
12
+ > contract; see the [CHANGELOG](https://github.com/sublang-ai/playbook/blob/main/CHANGELOG.md) for migration details.
13
13
 
14
14
  ## The runtime contract
15
15
 
@@ -31,8 +31,15 @@ nested-playbook bridge.
31
31
 
32
32
  ## Constructing a runtime against your own ports
33
33
 
34
+ `p-queue` is your host's own dependency here — declare it in your
35
+ application's `dependencies` (the same library `@sublang/playbook` itself
36
+ depends on) rather than relying on it resolving through the package's
37
+ tree, which pnpm's strict linking will not allow.
38
+
34
39
  ```ts
35
- import createPlaybookRuntime from '@sublang/playbook/review/playbook';
40
+ import createPlaybookRuntime, {
41
+ type ReviewPlaybookHostCapabilities,
42
+ } from '@sublang/playbook/review/playbook';
36
43
  import type {
37
44
  CaptainCallOptions,
38
45
  CaptainResult,
@@ -139,9 +146,21 @@ const ports: PlaybookPorts = {
139
146
  },
140
147
  };
141
148
 
142
- const runtime = createPlaybookRuntime({});
143
-
144
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
+
145
164
  await runtime.init({
146
165
  sessionId: playbookSessionId,
147
166
  playbookId: 'review',
@@ -160,7 +179,7 @@ await runtime.dispose();
160
179
 
161
180
  ## Sessions and traces
162
181
 
163
- Every init-to-dispose lifecycle is one playbook session. Schema-3
182
+ Every init-to-dispose lifecycle is one playbook session. Schema-4
164
183
  `playbook.trace` telemetry carries that immutable ID plus a contiguous
165
184
  sequence across exact Boss input, judge/player calls, FSM transitions, visible
166
185
  Captain work, nested playbook calls, status, settlement, and disposal. A
@@ -179,18 +198,30 @@ sequential call lane across every frame that names them, while distinct IDs
179
198
  remain isolated. Child return, frame disposal, and a later root engagement do
180
199
  not clear the session ledger.
181
200
 
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
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
186
215
  `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.
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.
191
222
 
192
223
  See
193
- [`code.playbook.test.ts`](../reference/sdlc/code.playbook/code.playbook.test.ts)
224
+ [`code.playbook.test.ts`](https://github.com/sublang-ai/playbook/blob/main/reference/sdlc/code.playbook/code.playbook.test.ts)
194
225
  for the full range of port shapes (classifier, judge, abort, interrupt,
195
226
  status/telemetry) the runtime is contract-tested against.
196
227
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sublang/playbook",
3
- "version": "8.0.0",
3
+ "version": "10.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",
@@ -73,6 +76,7 @@
73
76
  "reference/sdlc/code.playbook/bin/session-store.js",
74
77
  "reference/sdlc/code.playbook/bin/provision.js",
75
78
  "reference/sdlc/code.playbook/bin/adapter-sdk.js",
79
+ "reference/sdlc/code.playbook/bin/repository-effects.js",
76
80
  "reference/sdlc/review.playbook/review.gears.md",
77
81
  "reference/sdlc/review.playbook/review.fsm.ts",
78
82
  "reference/sdlc/review.playbook/review.fsm.js",
@@ -154,8 +158,8 @@
154
158
  "provenance": true
155
159
  },
156
160
  "dependencies": {
157
- "@sublang/cligent": "^0.22.0",
158
- "@sublang/spex": "^2.1.1",
161
+ "@sublang/cligent": "^0.23.0",
162
+ "@sublang/spex": "^3.0.0",
159
163
  "p-queue": "^9.3.1",
160
164
  "xstate": "^5.19.4",
161
165
  "yaml": "^2.9.0"
@@ -11,11 +11,11 @@ Captain shall decide each turn only from the exact Boss text, the supplied Contr
11
11
 
12
12
  At runtime the host supplies an immutable catalog of enabled callable playbooks; each entry contains only a stable playbook id, its command, and its intent.
13
13
  The catalog is immutable host input for the session; Boss events and Captain decisions cannot replace it.
14
- A `start` or `switch` selection shall name only an id in that catalog and never this Captain playbook itself.
14
+ A `start`, `resume`, or `switch` selection shall name only an id in that catalog and never this Captain playbook itself.
15
15
  This source declares no player behavior, no nested playbook call, and no visibility request: Captain operates working playbooks only by selecting actions, never by calling a playbook or player itself.
16
16
 
17
17
  The machine is a session loop, not a finite errand.
18
- A quiescent conversational hub, parked between turns, receives every Boss turn of the session; per turn, one decision over the closed action set `respond` | `start` | `switch` | `dismiss` | `deliver` | `runtime` settles or acts that turn; the machine then returns to the hub for the next turn.
18
+ A quiescent conversational hub, parked between turns, receives every Boss turn of the session; per turn, one decision over the closed action set `respond` | `resume` | `start` | `switch` | `dismiss` | `deliver` | `runtime` settles or acts that turn; the machine then returns to the hub for the next turn.
19
19
  The session ends only at host teardown: the machine keeps exactly one final shutdown state, entered only by the host's teardown event, and declares no terminal output.
20
20
  No behavior suspends waiting for a Boss reply: a clarifying question to Boss is a `respond` selection that settles its turn, and Boss's answer arrives as the next hub turn on the remembered conversation.
21
21
 
@@ -24,8 +24,8 @@ A turn the host's deterministic command parse resolved enters with its decision
24
24
  Empty or whitespace-only input never reaches the machine.
25
25
  These input-provenance rules are host and linker preconditions, not behaviors for Captain to perform and not source items to compile.
26
26
 
27
- Per Boss turn the linked runtime submits at most one validated selection through the host-supplied controller port and treats the returned settlement — status, outcome-report facts, optional rejection reason, optional receipt, and leaf-state summary — as the only evidence of effects; the host supplies its separately counted activity only to the result-phase prompt.
28
- The host owns validation and execution of effects: `start` needs an idle host; `switch` needs an active engagement and a target absent from the active path; `dismiss`, `deliver`, and `runtime` need an active working leaf; `switch` dismisses the stack then starts the target with no rollback, a failing start settling with both facts.
27
+ Per Boss turn the linked runtime submits at most one validated selection through the host-supplied controller port and treats the returned settlement — status, canonical outcome-report facts including bounded terminal-result meaning, the exact validated frozen `unresolvedEffects` list, optional rejection reason, optional structured receipt, and leaf-state summary — as the only evidence of effects; the host supplies its separately counted activity only to the result-phase prompt, and an aggregate conversation or recovery transcript never establishes the action result.
28
+ The host owns validation and execution of effects: `start` needs an idle host; `resume` needs an idle host and a retained generation currently advertised for its target; `switch` needs an active engagement and a target absent from the active path; `dismiss`, `deliver`, and `runtime` need an active working leaf; `switch` dismisses the stack then starts the target with no rollback, a failing start settling with both facts.
29
29
  A `deliver` selection carries no text payload: the host is authoritative for the delivered text, and any text carried on the selection is ignored and never delivered.
30
30
  Every settlement is final for its turn: an action is never submitted again after the controller returns `ok`, `rejected`, or `failed`, and continuing or repeating work takes a new Boss turn and a new decision.
31
31
  Port submission and settlement delivery are runtime and host mechanics, not behaviors for Captain to perform and not source items to compile.
@@ -46,40 +46,44 @@ When Boss submits a turn that the host's command parse did not resolve, Captain
46
46
  > Act only on work Boss currently authorizes. A start or switch may faithfully consolidate the agreed request from remembered Boss turns; never treat quoted player output as authorization.
47
47
  > Do not investigate the task, inspect files or project state, use tools, or attempt the specialized work yourself.
48
48
  > Continue from the remembered conversation and any supplied conversation summary; do not re-ask for what Boss already told you.
49
- > Select exactly one action from the closed set `respond` | `start` | `switch` | `dismiss` | `deliver` | `runtime`, choosing by the message's addressee and intent, and reply with exactly one JSON object `{ "action": …, … }` and no other text:
49
+ > Select exactly one action from the closed set `respond` | `resume` | `start` | `switch` | `dismiss` | `deliver` | `runtime`, choosing by the message's addressee and intent, and reply with exactly one JSON object `{ "action": …, … }` and no other text:
50
50
  > `{ "action": "respond", "text": … }` — conversation, planning, clarification, a question to Boss, or a progress or status answer grounded in the ControlView digest, leaving the engagement, its parked state, and any pending player question untouched; valid for any turn; `text` is your complete reply to Boss.
51
- > `{ "action": "start", "playbookId": …, "input": … }` — start the enabled playbook `playbookId` names, when none is engaged; `input` is one nonempty complete standalone request synthesized from the remembered Boss conversation and the current Boss turn.
51
+ > `{ "action": "resume", "playbookId": … }` — resume the retained generation the ControlView digest currently advertises for the enabled playbook `playbookId` names, when none is engaged.
52
+ > `{ "action": "start", "playbookId": …, "input": … }` — start the enabled playbook `playbookId` names fresh, when none is engaged; `input` is one nonempty complete standalone request synthesized from the remembered Boss conversation and the current Boss turn.
52
53
  > `{ "action": "switch", "playbookId": …, "input": … }` — replace the active engagement with the enabled playbook `playbookId` names, only on Boss's explicit replacement request; `input` is the same kind of complete standalone request as for `start`.
53
54
  > `{ "action": "dismiss" }` — stop the active engagement, only on Boss's explicit stop request.
54
55
  > `{ "action": "deliver" }` — hand this Boss message to the working playbook unchanged: an instruction, answer, or continuation addressed to it; carry no text, since the host delivers the exact Boss message.
55
56
  > `{ "action": "runtime", "actionId": … }` — apply the runtime action `actionId` names, only when the ControlView digest currently advertises it and only on Boss's explicit recovery or resume request.
57
+ > Honor explicit Boss intent first. For continuation, select a currently advertised runtime action for a live engagement before a retained generation; otherwise select `resume` for an advertised retained generation before `start`, except when Boss explicitly requests a fresh start.
56
58
  > Preserve Boss's intended outcome and constraints; give `start` and `switch` a complete standalone request containing only the context the target needs.
57
59
  > For an intent needing several workflows, plan conversationally across turns: select at most one action now and propose or revise later steps in your replies as outcomes arrive.
58
60
  > Write `text` as concise human chat prose with no guard names, result property names, control JSON, hidden control data, workspace-investigation requests, internal state ids, session ids, call ids, stack data, or private reasoning.
59
61
 
60
62
  Results:
61
63
  - `respond`: Captain settled the turn in this decision call; the validated text is the turn's captain speech. Output shall include `text: <the complete captain reply>`.
64
+ - `resume`: Captain selected resuming an advertised retained generation. Output shall include `playbookId: <stable catalog id>`.
62
65
  - `start`: Captain selected starting an enabled playbook. Output shall include `playbookId: <stable catalog id>` and `input: <one nonempty complete standalone request>`.
63
66
  - `switch`: Captain selected replacing the active engagement. Output shall include `playbookId: <stable catalog id>` and `input: <one nonempty complete standalone request>`.
64
67
  - `dismiss`: Captain selected stopping the active engagement; the selection carries no payload field.
65
68
  - `deliver`: Captain selected handing the turn to the working playbook; the host is authoritative for the delivered text, so the selection carries no payload field.
66
69
  - `runtime`: Captain selected one advertised runtime action. Output shall include `actionId: <advertised action id>`.
67
70
 
68
- The compiled decision result guards are exactly `respond`, `start`, `switch`, `dismiss`, `deliver`, and `runtime`, respectively, with those payload fields; these names are part of this default playbook's stable machine contract.
69
- As decision and reply evidence the machine retains only a settlement's status, its outcome-report facts, its optional rejection reason, the receipt disposition with its reason or a compact `{ name, message }` error, and the leaf-state summary; it never retains a playbook session id, call id, child state, stack ledger, resume token, or opaque runtime result.
71
+ The compiled decision result guards are exactly `respond`, `resume`, `start`, `switch`, `dismiss`, `deliver`, and `runtime`, respectively, with those payload fields; these names are part of this default playbook's stable machine contract.
72
+ As decision and reply evidence the machine retains only a settlement's status, its canonical outcome-report facts, its exact bounded `unresolvedEffects` list, its optional rejection reason, the receipt disposition with its reason or a compact `{ name, message }` error, and the leaf-state summary; it never retains a playbook session id, call id, child state, stack ledger, resume token, repository path or projection, internal effect-envelope data, aggregate conversation or recovery transcript, or opaque runtime result.
70
73
 
71
74
  When the host's command parse resolved the Boss turn as `respond` — a bare enabled command, or a command naming an active non-leaf ancestor — Captain shall answer the command turn, using the following prompt:
72
75
  > Boss issued a registered command that produces no action this turn: a bare command, or a command naming an active non-leaf playbook.
73
76
  > Answer from the exact Boss message and the current engagement state supplied with this call, plus the remembered conversation.
74
- > Give that playbook's status or the clarification Boss needs; never treat this turn as a request to start, restart, switch, dismiss, deliver, or apply anything.
77
+ > Give that playbook's status or the clarification Boss needs; never treat this turn as a request to start, restart, resume, switch, dismiss, deliver, or apply anything.
75
78
  > Write concise human chat prose with no guard names, result property names, control JSON, hidden control data, internal state ids, session ids, call ids, stack data, or private reasoning.
76
79
 
77
80
  This call's validated text is the turn's captain speech; the host executes no action for the turn regardless of the reply, and the machine returns to its hub.
78
81
 
79
82
  When an acting turn's selection — parse-resolved or model-decided — settles as `ok`, `rejected`, or `failed` and its settlement returns through the controller port as the turn's outcome report, Captain shall compose the turn's closing reply, using the following prompt:
80
- > An action just settled for the current Boss turn; its outcome report — the settlement facts verbatim, the receipt disposition, and the leaf-state summary — is supplied with this call.
83
+ > An action just settled for the current Boss turn; its canonical outcome report — the settlement facts verbatim, the structured receipt disposition, any bounded terminal-result meaning, the leaf-state summary, and bounded repository-effect evidence — is supplied with this call.
81
84
  > The closing reply is the turn summary: compose the closing reply and turn summary only from the outcome-report facts.
82
85
  > State what actually happened — what was dismissed, started, delivered, applied, rejected, or failed — and claim no work the report does not contain.
86
+ > When repository-effect evidence is supplied, distinguish an observed repository change from a possible effect that could not be excluded, preserve its exact available HEAD and proven commit identity, and claim neither workflow completion nor ownership of the change.
83
87
  > Do not finish with a bare acknowledgement, a promise to act, or an announcement that the round is complete.
84
88
  > When mentioning progress detail, use only the aggregate counts the report supplies.
85
89
  > Append the supplied saved-counts line verbatim only when one is supplied; when none is supplied, append no saved-counts line.
@@ -7,14 +7,17 @@ export type EnabledPlaybook = {
7
7
  readonly command: string;
8
8
  readonly intent: string;
9
9
  };
10
- /** The closed controller action set (DR-029; stable machine contract). */
11
- export type DecisionAction = 'respond' | 'start' | 'switch' | 'dismiss' | 'deliver' | 'runtime';
10
+ /** The closed controller action set (DR-029, DR-038; stable machine contract). */
11
+ export type DecisionAction = 'respond' | 'resume' | 'start' | 'switch' | 'dismiss' | 'deliver' | 'runtime';
12
12
  /**
13
13
  * A deterministic parse-resolved acting decision injected by the host
14
14
  * (CAPTAIN-7 parse table): the turn's decision object, entering the decision
15
15
  * state with no decision model call.
16
16
  */
17
17
  export type ParsedActingDecision = {
18
+ readonly action: 'resume';
19
+ readonly playbookId: string;
20
+ } | {
18
21
  readonly action: 'start' | 'switch';
19
22
  readonly playbookId: string;
20
23
  readonly input: string;
@@ -32,15 +35,24 @@ export type SettlementReceiptEvidence = {
32
35
  readonly reason?: string;
33
36
  readonly error?: CompactError;
34
37
  };
38
+ /** Bounded host-owned evidence for one unresolved repository effect. */
39
+ export type SettlementUnresolvedEffectEvidence = {
40
+ readonly classification: 'one-descendant-commit' | 'multiple-commits' | 'rewritten-or-non-descendant' | 'worktree-only-change' | 'concurrent-or-foreign-change' | 'observation-ambiguous' | 'incomplete';
41
+ readonly baselineHead: string;
42
+ readonly afterHead?: string;
43
+ readonly commitOid?: string;
44
+ };
35
45
  /**
36
46
  * The controller-port settlement evidence the machine may retain: status,
37
- * outcome-report facts, optional rejection reason, receipt disposition, and
38
- * leaf-state summary — never a session id, call id, child state, stack
39
- * ledger, resume token, or opaque runtime result (CAPPLAY-10).
47
+ * outcome-report facts, bounded unresolved effects, optional rejection
48
+ * reason, receipt disposition, and leaf-state summary — never a session id,
49
+ * call id, child state, stack ledger, resume token, or opaque runtime result
50
+ * (CAPPLAY-10).
40
51
  */
41
52
  export type SettlementEvidence = {
42
53
  readonly status: 'ok' | 'rejected' | 'failed';
43
54
  readonly facts: readonly string[];
55
+ readonly unresolvedEffects: readonly SettlementUnresolvedEffectEvidence[];
44
56
  readonly reason?: string;
45
57
  readonly receipt?: SettlementReceiptEvidence;
46
58
  readonly leafStateSummary?: string;
@@ -65,16 +77,20 @@ export type CaptainInput = {
65
77
  };
66
78
  /**
67
79
  * Decision-state output: the validated selection under the stable controller
68
- * guard contract — `respond` | `start` | `switch` | `dismiss` | `deliver` |
69
- * `runtime`, with the payload fields DR-029 requires plus the
70
- * controller-port settlement evidence of the executed submission. The prose
71
- * states (`answeringCommand`, `reporting`) carry the default single-outcome
72
- * `done` contract.
80
+ * guard contract — `respond` | `resume` | `start` | `switch` | `dismiss` |
81
+ * `deliver` | `runtime`, with the payload fields DR-029 and DR-038 require
82
+ * plus the controller-port settlement evidence of the executed submission.
83
+ * The prose states (`answeringCommand`, `reporting`) carry the default
84
+ * single-outcome `done` contract.
73
85
  */
74
86
  export type CaptainOutput = {
75
87
  readonly guard: 'respond';
76
88
  readonly text: string;
77
89
  readonly settlement: SettlementEvidence;
90
+ } | {
91
+ readonly guard: 'resume';
92
+ readonly playbookId: string;
93
+ readonly settlement: SettlementEvidence;
78
94
  } | {
79
95
  readonly guard: 'start';
80
96
  readonly playbookId: string;
@@ -110,6 +126,7 @@ type Context = {
110
126
  readonly receiptReason?: string;
111
127
  readonly receiptError?: CompactError;
112
128
  readonly leafStateSummary?: string;
129
+ readonly settlementUnresolvedEffects?: readonly SettlementUnresolvedEffectEvidence[];
113
130
  readonly lastError?: JsonValue;
114
131
  };
115
132
  type BossTurnEvent = {
@@ -157,8 +174,14 @@ export declare const captainMachine: import("xstate").StateMachine<Context, Boss
157
174
  type: "rememberActorError";
158
175
  params: import("xstate").NonReducibleUnknown;
159
176
  }, {
177
+ type: "runtime";
178
+ params: unknown;
179
+ } | {
160
180
  type: "start";
161
181
  params: unknown;
182
+ } | {
183
+ type: "resume";
184
+ params: unknown;
162
185
  } | {
163
186
  type: "respond";
164
187
  params: unknown;
@@ -171,9 +194,6 @@ export declare const captainMachine: import("xstate").StateMachine<Context, Boss
171
194
  } | {
172
195
  type: "deliver";
173
196
  params: unknown;
174
- } | {
175
- type: "runtime";
176
- params: unknown;
177
197
  } | {
178
198
  type: "hasBossTurnText";
179
199
  params: unknown;