@sublang/playbook 7.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 (61) hide show
  1. package/README.md +17 -4
  2. package/docs/cli.md +74 -29
  3. package/docs/configuration.md +209 -112
  4. package/docs/embedding.md +71 -25
  5. package/package.json +4 -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 +1078 -116
  11. package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
  12. package/reference/sdlc/code.playbook/bin/run.js +283 -298
  13. package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
  17. package/reference/sdlc/code.playbook/code.fsm.js +7 -11
  18. package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
  19. package/reference/sdlc/code.playbook/code.gears.md +1 -1
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
  21. package/reference/sdlc/code.playbook/code.playbook.js +12 -13
  22. package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
  24. package/reference/sdlc/code.playbook/code.registry.js +3 -10
  25. package/reference/sdlc/code.playbook/code.registry.ts +7 -32
  26. package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
  27. package/reference/sdlc/code.playbook/playbook-captain.js +970 -289
  28. package/reference/sdlc/code.playbook/playbook-captain.ts +1403 -396
  29. package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
  30. package/reference/sdlc/decide.md +4 -4
  31. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
  32. package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
  33. package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
  34. package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
  35. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
  36. package/reference/sdlc/decide.playbook/decide.playbook.js +171 -134
  37. package/reference/sdlc/decide.playbook/decide.playbook.ts +238 -162
  38. package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
  39. package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
  40. package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
  41. package/reference/sdlc/review.md +4 -5
  42. package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
  43. package/reference/sdlc/review.playbook/review.fsm.js +30 -24
  44. package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
  45. package/reference/sdlc/review.playbook/review.gears.md +6 -5
  46. package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
  47. package/reference/sdlc/review.playbook/review.playbook.js +16 -21
  48. package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
  49. package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
  50. package/reference/sdlc/review.playbook/review.registry.js +3 -16
  51. package/reference/sdlc/review.playbook/review.registry.ts +7 -38
  52. package/slc/gears2fsm.md +27 -23
  53. package/slc/link.md +113 -93
  54. package/slc/text2gears.md +19 -18
  55. package/src/runtime.d.ts +20 -16
  56. package/src/runtime.ts +19 -23
  57. package/src/xstate-playbook-runtime.d.ts +21 -17
  58. package/src/xstate-playbook-runtime.js +241 -149
  59. package/src/xstate-playbook-runtime.ts +331 -178
  60. package/src/xstate-runtime.js +63 -24
  61. package/src/xstate-runtime.ts +96 -28
package/slc/link.md CHANGED
@@ -59,13 +59,27 @@ interface PlaybookSession {
59
59
  parentSessionId?: string;
60
60
  parentCallId?: string;
61
61
  depth: number;
62
+ roleBindings?: Readonly<Record<string, PlaybookRoleBinding>>;
62
63
  playerSessions?: PlayerSessionStore;
63
64
  ports: PlaybookPorts;
64
65
  }
65
66
 
67
+ interface PlaybookRoleBinding {
68
+ readonly playerId: string;
69
+ readonly promptIdentity: string;
70
+ }
71
+
72
+ interface PlaybookPendingBossQuestion {
73
+ questionId: string;
74
+ asker: { kind: 'captain' } | { kind: 'role'; roleId: string };
75
+ question: string;
76
+ sourceItem?: string;
77
+ }
78
+
66
79
  interface PlayerSessionStore {
67
- select(playerId: string): string | false;
68
- update(playerId: string, resumeToken?: string): void;
80
+ select(roleId: string): string | false;
81
+ // Called only for a replacement token or an authorized ok-status clear.
82
+ update(roleId: string, resumeToken?: string): void;
69
83
  snapshot(): Readonly<Record<string, string>>;
70
84
  restore(tokens: Readonly<Record<string, string>>): void;
71
85
  }
@@ -103,6 +117,30 @@ interface PlaybookPendingCall {
103
117
  childSessionId: string;
104
118
  }
105
119
 
120
+ interface PlaybookSuspendedCall extends PlaybookPendingCall {
121
+ stateId: string;
122
+ text: string;
123
+ turnId?: number;
124
+ }
125
+
126
+ interface PlaybookRuntimeSnapshot {
127
+ schemaVersion: 3;
128
+ playbookId: string;
129
+ machine: JsonValue;
130
+ roleResumeTokens: { readonly [roleId: string]: string };
131
+ sequences: {
132
+ trace: number;
133
+ turn: number;
134
+ judgeCall: number;
135
+ playerCall: number;
136
+ playbookCall: number;
137
+ captainCall?: number;
138
+ };
139
+ state: PlaybookState;
140
+ pendingBossQuestions: readonly PlaybookPendingBossQuestion[];
141
+ suspendedCall?: PlaybookSuspendedCall;
142
+ }
143
+
106
144
  type PlaybookRunResult =
107
145
  | { outcome: 'quiescent' | 'no-action'; state: PlaybookState }
108
146
  | {
@@ -151,7 +189,7 @@ exactly one `pendingCall` is active.
151
189
  Control-plane exceptions reject the runtime method rather than masquerade as a
152
190
  recoverable workflow `failed` result.
153
191
 
154
- `PlaybookRuntimeOptions` is host-agnostic and carries only _per-run_ knobs such as identity strings (e.g., model names a playbook substitutes into prompt placeholders), strategy overrides the linker exposes, and — where the compiled playbook's policy needs a host seam — host-supplied port-shaped callbacks the linker exposes as option members whose types the artifact itself declares, so the six-member `PlaybookPorts` contract and the shared contract module stay free of host types.
192
+ `PlaybookRuntimeOptions` is host-agnostic and carries only _per-run_ workflow knobs, strategy overrides the linker exposes, and — where the compiled playbook's policy needs a host seam — host-supplied port-shaped callbacks the linker exposes as option members whose types the artifact itself declares, so the six-member `PlaybookPorts` contract and the shared contract module stay free of host types.
155
193
  The link compiler emits a typed options interface per playbook based on the FSM's `CodingInput` (or equivalent).
156
194
  The CLI's absence of `--link-option` values does not mean that
157
195
  `PlaybookRuntimeOptions` is empty. CLI link options are compile-time inputs;
@@ -162,15 +200,16 @@ remain a required readonly runtime option passed through to machine input; the
162
200
  linker shall neither invent an empty catalog nor require it to be baked into a
163
201
  CLI link option.
164
202
 
165
- Player binding is a _linker-time_ input baked into the emitted runtime by default.
166
- A linker may also expose it via `PlaybookRuntimeOptions` for per-run remapping; the contract requires only that the runtime ship with a deterministic binding it applies at every `callPlayer` site.
203
+ Concrete player binding and prompt identity are host policy and shall not enter `PlaybookRuntimeOptions`, machine input, or the emitted artifact.
204
+ For a shell-hosted runtime, `PlaybookSession.roleBindings` shall carry exactly the runtime's local roles, map each to its resolved player id and current prompt identity, and be the sole source for call targeting, player-facing prompt identity, concurrency keys, and trace player ids.
205
+ The host shall derive `promptIdentity` from the current effective model when present and the established adapter otherwise; a standalone runtime may omit the map and retain only its local role identity.
167
206
 
168
207
  ## PlaybookPorts contract
169
208
 
170
209
  ```typescript
171
210
  interface PlaybookPorts {
172
211
  callPlayer(
173
- playerId: string,
212
+ roleId: string,
174
213
  prompt: string,
175
214
  signal: AbortSignal,
176
215
  options: PlayerCallOptions,
@@ -289,7 +328,7 @@ emission drain. Absent such a control-plane failure, if the combined signal
289
328
  has aborted, ordinary abort settlement remains authoritative after the actor
290
329
  reaches its error path.
291
330
 
292
- Every linked runtime owns a map from resolved player id to its latest non-empty `resumeToken`.
331
+ Every standalone linked runtime owns a private continuation map keyed by resolved player id when `roleBindings` is supplied and by local role id otherwise.
293
332
  Before reading a resolved direct-Captain or delegated-player result, the
294
333
  runtime shall validate, detach, and freeze it through the shared
295
334
  `validateCaptainResult` or `validatePlayerResult` helper. The accepted object
@@ -297,17 +336,18 @@ shape is exact: only the declared status and optional string fields are
297
336
  allowed, JSON-unsafe members reject, and caller mutation after resolution
298
337
  cannot change trace evidence or player continuity. Validation happens before
299
338
  adopting a resume token or reading final text.
300
- The first call to each player in a playbook session shall pass `{ resume: false }`; later calls shall pass the exact stored token.
301
- After a resolved call, the runtime shall replace the token when the result carries one or clear it when absent before interpreting `status`; a rejected call with no result leaves the prior token unchanged.
339
+ The first call to each private continuation key in a standalone playbook session shall pass `{ resume: false }`; later calls shall pass the exact stored token, so two sequential roles explicitly bound to one player id share one token even without a supplied `PlayerSessionStore`.
340
+ After a validated resolved call, the runtime shall replace the token when the result carries one, clear it only for an `ok` result that omits one, and preserve it for an `aborted` or `error` result that omits one; a rejected call with no result likewise leaves the prior token unchanged.
302
341
  After awaiting a host Captain or player promise, the runtime shall re-check the
303
342
  combined invocation/public-boundary signal before validating the result,
304
343
  adopting a resume token, or emitting a successful finish. A host promise that
305
344
  ignores cancellation and resolves late shall be paired as aborted and shall
306
345
  not mutate continuity or masquerade as success.
307
346
  The map survives actor reconstruction inside the same runtime and is discarded at `dispose`.
308
- The runtime shall keep an in-flight set keyed by resolved player id and reject
309
- a second concurrent call to the same id before crossing the host port. Calls
310
- to distinct resolved player ids may overlap.
347
+ The runtime shall keep an in-flight set keyed by resolved player id when the
348
+ host supplies binding metadata, otherwise by local role id, and reject a
349
+ second concurrent call to the same key before crossing the host port.
350
+ Calls to distinct keys may overlap.
311
351
 
312
352
  `callJudge` returns free-form text.
313
353
  The runtime parses it per the state's adjudication strategy (§Captain adjudication).
@@ -346,7 +386,7 @@ The runtime never speaks to LLMs directly and never touches host types beyond `P
346
386
  ## Playbook trace
347
387
 
348
388
  Every linked runtime shall emit a boundary-complete, ordered trace through `emitTelemetry` topic `playbook.trace`.
349
- Each payload shall carry `schemaVersion: 2`, the immutable session identity and
389
+ Each payload shall carry `schemaVersion: 3`, the immutable session identity and
350
390
  causality, a contiguous one-based `sequence`, a Unix-millisecond `timestamp`, a
351
391
  trace `type`, event `payload`, and the runtime-local `turnId` / paired `callId`
352
392
  where applicable.
@@ -371,7 +411,7 @@ type PlaybookTraceType =
371
411
  | 'session.disposed';
372
412
 
373
413
  interface PlaybookTraceEvent {
374
- schemaVersion: 2;
414
+ schemaVersion: 3;
375
415
  sessionId: string;
376
416
  playbookId: string;
377
417
  rootSessionId: string;
@@ -387,7 +427,7 @@ interface PlaybookTraceEvent {
387
427
  }
388
428
  ```
389
429
 
390
- A composing host may supply `playerSessions` as a frame-local view of player continuation owned by the root engagement tree.
430
+ A composing host may supply `playerSessions` as a frame-local view of player continuation owned by the logical Captain session.
391
431
  The runtime shall select through that view before allocating or tracing a player call and shall update or clear it from a validated player result before emitting the matching finish trace.
392
432
  Snapshot export and restore shall use the same view, failed restore shall leave its prior contents unchanged, and child disposal shall not clear host-owned continuation.
393
433
  A host that omits the view retains the runtime's private per-session continuation behavior.
@@ -401,7 +441,7 @@ The trace types are `session.started`, `boss.input.received`,
401
441
  `boss.input.settled`, and `session.disposed`.
402
442
  Call pairs carry exact prompts and replies, normalized failures, actor and state
403
443
  identity, and their boundary-specific options.
404
- `apply.started` and `apply.finished` are the paired schema-2 boundary of an
444
+ `apply.started` and `apply.finished` are the paired apply boundary of a
405
445
  executed `apply()` call on a runtime implementing the optional control surface
406
446
  (§Control surface): both carry the action id and idempotency `key` (plus the
407
447
  singular `stateId` on start when one exists), the pair shares one
@@ -423,7 +463,8 @@ described top-level `state` and its singular `stateId` when present, as well as
423
463
  its message and optional data; consumers shall not have to recover state
424
464
  identity from a nested ad hoc object.
425
465
  Judge results use `reply`; player start and finish payloads both carry the
426
- selected `resume`; Captain start and finish payloads both carry
466
+ local `roleId`, the resolved `playerId` when host binding metadata is
467
+ available, and the selected `resume`; Captain start and finish payloads both carry
427
468
  the exact composed prompt, the boundary's selected `visibility`, the direct
428
469
  invocation's `stateId` and `sourceItem`, and no player resume selection or
429
470
  resume token — a visible workflow call carries its runtime-owned
@@ -498,51 +539,36 @@ Trace payloads never become Boss-visible status or prompt text.
498
539
  The link compiler shall accept:
499
540
 
500
541
  - The FSM artifact (path to a `.fsm.ts`).
501
- - A **player binding** mapping GEARS players (declared in the
502
- [text2gears](text2gears.md#players) source) to opaque player-identifier
503
- strings.
504
- Where no binding is supplied, the linker shall apply the default
505
- binding — each player to its lowercased name (e.g. `Coder` → `coder`)
506
- — and record the applied binding in the emitted header.
507
542
  - An **adjudication strategy** (default: LLM-judge per state) and a
508
543
  **Boss-event mapping** (default: free-text judge classification).
509
544
  Both strategies are host-agnostic.
510
545
 
511
546
  The host's identity does not enter compilation; the linked module runs unchanged under any host that implements `PlaybookPorts`.
512
547
 
513
- ## Player binding
548
+ ## Role identity
514
549
 
515
- Each delegated GEARS state names exactly one player
516
- (`player` actor `invoke.input.player`).
517
- The linker shall map every named player to a `playerId` string used in
518
- `PlaybookPorts.callPlayer(playerId, …)`.
519
- The host adapter routes that opaque string to its concrete primitive.
550
+ Each delegated GEARS state names exactly one canonical local role id (`player` actor `invoke.input.role`).
551
+ The linker shall retain that id in `PlaybookPorts.callPlayer(roleId, …)` without selecting a concrete player.
552
+ The host shall bind that local role id explicitly when it constructs the runtime.
520
553
  Every direct-Captain and delegated-player invocation shall also carry its
521
554
  working leaf's explicit
522
555
  `stateId`; a linked runtime shall use that field for call identity and shall
523
556
  not infer one leaf from a structured root snapshot.
524
- Direct `captain` actor states bypass player binding and call
525
- `PlaybookPorts.callCaptain`; the linker shall not synthesize a player id named
526
- `captain` for them.
527
-
528
- For composite players declared with aliases (e.g., `Committer = Coder | Reviewer`), the linker shall resolve the alias **per source item**.
529
- Resolution inspects the `PlayerInput` fields populated at that state:
530
-
531
- - If only one `<playerName>Player` field is present, bind to that player.
532
- - If multiple are present, prefer the first-listed alternative in the alias declaration order.
533
- - If none are present, fall back to the alias's first alternative.
534
-
535
- Resolution shall be deterministic and recorded in the emitted module so future maintainers can audit it without re-running the linker.
536
-
537
- The linker shall not invent player identifiers beyond the recorded default
538
- binding, and shall not silently collapse aliases at the FSM level — composite
539
- players keep their `player: 'Committer'` value on `PlayerInput`; resolution
540
- decides only the `callPlayer` invocation.
557
+ Direct `captain` actor states call `PlaybookPorts.callCaptain`; the linker shall not synthesize a local role or concrete player id named `captain` for them.
558
+ The linker shall reject an alias-shaped role declaration rather than choose a runtime identity.
541
559
 
542
560
  ## Player prompt composition
543
561
 
544
562
  The runtime shall compose the actual player prompt from the state's
545
563
  `PlayerInput`.
564
+ The shared-factory `composePlayerPrompt` seam shall receive an
565
+ invocation-scoped `promptIdentity(roleId)` lookup as its second argument.
566
+ The lookup shall return the current detached session binding's prompt identity,
567
+ or the canonical local role id when bindings are absent, and shall reject an
568
+ undeclared role.
569
+ It shall expose neither the resolved player id nor the binding map, and the
570
+ runtime shall not place the lookup or any value read through it in options,
571
+ machine input, FSM context, or a persisted snapshot.
546
572
  `input.prompt` is the GEARS-derived domain prompt body and shall not be mutated, re-flowed, or treated as a place to store framework control instructions.
547
573
  A leading `>` inside that body is authored quoted-context content and shall reach the player unchanged.
548
574
 
@@ -687,7 +713,7 @@ classification is recoverable control input, not a public boundary rejection.
687
713
  If a recovered `BOSS_REPLY` names no question that is currently pending, it is
688
714
  such a malformed classification: emit the one recovery status, send no event,
689
715
  leave the actor unchanged, and return `no-action` after emissions drain.
690
- Host-owned runtime options, player bindings, and enabled-playbook catalogs are
716
+ Host-owned runtime options, role-to-player bindings, and enabled-playbook catalogs are
691
717
  not Boss-event payload. The classifier schema and parser shall not invite or
692
718
  accept them, and classified prose shall never overwrite their machine context.
693
719
  Every recovered classifier object shall have exactly `type` plus the declared
@@ -705,14 +731,14 @@ aborted while the classifier finish emission was pending, return and trace the
705
731
  same structured `aborted` result against the unchanged actor.
706
732
  When the FSM supports a Boss-reply suspension state, the prompt shall inspect
707
733
  the actor snapshot context and include each exact pending Boss question,
708
- question id, and asking player so the judge can distinguish a reply from a
709
- fresh directive. With one pending question, a classified `BOSS_REPLY` that
710
- omits its optional id shall be filled with that sole id. With several pending
711
- questions, the classifier shall require a known id. A reply shall re-enter only
734
+ question id, and discriminated Captain-or-role asker so the judge can distinguish a reply from a fresh directive.
735
+ With one pending question, a classified `BOSS_REPLY` that omits its optional id shall be filled with that sole id.
736
+ With several pending questions, the classifier shall require a known id.
737
+ A reply shall re-enter only
712
738
  its recorded resume state and preserve the original intent, plan, prior child
713
739
  results, and Q+A continuation context.
714
- The classifier-facing pending-question block contains only `questionId`,
715
- `player`, and `question`. Internal `resumeStateId`, source-item identity, and
740
+ The classifier-facing pending-question block contains only `questionId`, `asker`, and `question`.
741
+ Internal `resumeStateId`, source-item identity, and
716
742
  other machine-routing fields remain authoritative in snapshot context and
717
743
  shall not be serialized into the judge prompt.
718
744
  The allowed fresh directives while parked include every applicable root entry
@@ -1191,23 +1217,20 @@ The `PlaybookRuntime` shall:
1191
1217
  failure cannot skip the parent disposal boundary or leave the runtime bound.
1192
1218
 
1193
1219
  The actor's `lastError` field shall be surfaced via `emitStatus` when the machine enters its `failed` state.
1194
- Presence of linker-emitted `playerStates` selects the canonical
1195
- factory-backed status profile. That profile shall emit the selected Boss event type
1220
+ Presence of linker-emitted `roleStates` selects the canonical factory-backed status profile.
1221
+ That profile shall emit the selected Boss event type
1196
1222
  before sending that event, exactly `→ <guard>` (with no payload-count or tally
1197
1223
  rider) when a settling actor output carries a guard, and
1198
- `⤷ <Player>: <label>` only when the entered state appears in the
1199
- linked module's `playerStates` metadata. It shall emit no raw state-id fallback
1200
- for any other state. `playerStates` shall be a complete map of the FSM states
1201
- that invoke the typed `player` actor; each value carries the exact player from
1202
- that state's source-derived `meta.playbook.player` and the state's exact FSM
1203
- description as `{ player, label }`. The factory shall reject an incomplete
1204
- entry, a non-player state, or a player or label that differs from the FSM
1205
- metadata. A schema-1 legacy module that predates this metadata
1206
- shall preserve the prior factory status defaults, including no classification
1207
- line, `Entered <stateId>.` for ordinary state entry, the single question line,
1208
- and the unglyphed failure line. The new profile is opt-in so adding the seam
1209
- does not silently reinterpret an already-linked artifact without an ABI or
1210
- artifact-schema bump.
1224
+ `⤷ <Role>: <label>` only when the entered state appears in the linked module's `roleStates` metadata.
1225
+ It shall emit no raw state-id fallback for any other state.
1226
+ `roleStates` shall be a complete map of the FSM states
1227
+ that invoke the typed `player` actor; each schema-2 value carries the exact
1228
+ local role from that state's source-derived `meta.playbook.role` and the state's exact FSM description as `{ role, label }`.
1229
+ The factory shall reject an
1230
+ incomplete entry, a non-player state, or a role or label that differs from the FSM metadata.
1231
+ Artifact schema `1` and a missing compatibility declaration
1232
+ shall reject before interpretation because their legacy `player` values may
1233
+ encode bindings or aliases rather than canonical local roles.
1211
1234
  For the default Captain runtime, an initial `ready` state and a terminal `done`
1212
1235
  state shall not emit human status. The terminal response is already visible
1213
1236
  Captain prose; a synthetic “entered done” message would present it twice.
@@ -1245,22 +1268,22 @@ quiescent state with actor status `active`.
1245
1268
  At a safe capture point it shall return a JSON-safe
1246
1269
  `PlaybookRuntimeSnapshot` carrying:
1247
1270
 
1248
- - `schemaVersion`: literal `2`.
1271
+ - `schemaVersion`: literal `3`.
1249
1272
  - `playbookId`: the bound session's playbook id.
1250
1273
  - `machine`: the root actor's `getPersistedSnapshot()` result, passed
1251
1274
  through the shared JSON detachment with any raw `Error` context value
1252
1275
  (for example FSM `lastError`) normalized to `{ name, message, stack? }`
1253
1276
  first. The value is opaque to hosts.
1254
- - `playerResumeTokens`: the resume-token map as a plain object
1277
+ - `roleResumeTokens`: the local-role resume-token projection as a plain object
1255
1278
  (§PlaybookPorts contract).
1256
1279
  - `sequences`: the live `trace`, `turn`, `judgeCall`, `playerCall`, and
1257
1280
  `playbookCall` counters, plus `captainCall` when the runtime supports direct
1258
- Captain calls. A direct-Captain-capable runtime shall persist it in current
1259
- schema-version-2 exports; it remains optional in legacy schema-version-1
1260
- input, where restore uses `trace` as its collision-safe floor.
1281
+ Captain calls.
1282
+ A direct-Captain-capable runtime shall persist it in every schema-version-3 export.
1261
1283
  - `state`: the current normalized state descriptor.
1262
1284
  - `pendingBossQuestions`: the pending Boss question(s) from FSM context as
1263
- a list of `{ questionId, player, question, sourceItem? }`, empty when the
1285
+ a list of `{ questionId, asker, question, sourceItem? }`, where `asker` is
1286
+ `{ kind: 'captain' }` or `{ kind: 'role', roleId }`, empty when the
1264
1287
  parked state awaits no reply. This list exists so hosts can surface the
1265
1288
  question without parsing status lines or telemetry.
1266
1289
  - `suspendedCall`: omitted when no nested call is pending; otherwise the
@@ -1278,19 +1301,16 @@ unsafe and returns `undefined`.
1278
1301
  `restore(session, snapshot)` is an alternative to `init` under the same
1279
1302
  lifecycle guards (§Session lifecycle): it shall reject when already
1280
1303
  initialized, disposing, or disposed, and shall validate
1281
- schema version `1` or `2` and that `snapshot.playbookId` equals
1282
- `session.playbookId` before touching state. Schema version `1` remains a
1283
- descriptor-free legacy input; schema version `2` may carry the suspended-call
1284
- descriptor above.
1304
+ schema version `3` and that `snapshot.playbookId` equals `session.playbookId` before touching state.
1305
+ Runtime snapshot schemas `1` and `2` shall reject before state binding because their token and pending-question fields conflate local roles, concrete players, and Captain identity.
1285
1306
  The host supplies the same immutable `PlaybookSession` identity the
1286
1307
  snapshot was exported under and recreates the runtime through the same
1287
1308
  factory with equivalent options; the runtime does not diff options, and
1288
1309
  module identity — that the factory constructing this runtime still
1289
1310
  belongs to the snapshot's playbook — is likewise the host's check to
1290
1311
  make before calling `restore`.
1291
- `restore` shall bind the session, restore the resume-token map, the
1292
- sequence counters (using the persisted global `trace` counter as a
1293
- collision-safe floor for an absent legacy `captainCall`), and the
1312
+ `restore` shall bind the session and its current detached role bindings, restore the local-role token projection, the
1313
+ sequence counters, and the
1294
1314
  prior-state descriptor from the snapshot,
1295
1315
  prepare the shared nested bridge with the suspended-call descriptor or its
1296
1316
  explicit absence, restore a descriptor's call-to-turn map entry, construct
@@ -1599,11 +1619,10 @@ The thin emitted module:
1599
1619
  under TypeScript erasure; `placeholderFields` only for authored token/field
1600
1620
  exceptions not covered by the canonical kebab-token-to-camel-field mapping
1601
1621
  and the canonical `<#>` → `irNumber` special case; the
1602
- transition-event payload fields the FSM's Boss union declares; a
1603
- non-default player binding where the linker inputs supplied one; the
1604
- complete `playerStates` status map derived from every FSM state that invokes
1605
- the typed `player` actor, with each `player` copied from that state's
1606
- source-derived `meta.playbook.player` (an empty map when there is no such
1622
+ transition-event payload fields the FSM's Boss union declares; the
1623
+ complete `roleStates` status map derived from every FSM state that invokes
1624
+ the typed `player` actor, with each `role` copied from that state's
1625
+ source-derived `meta.playbook.role` (an empty map when there is no such
1607
1626
  state); the
1608
1627
  `verbatimPayloadFields` set derived from annotated result fields above; the
1609
1628
  `controlContextFields` projection of §Control surface; and any
@@ -1641,13 +1660,13 @@ The thin emitted module:
1641
1660
  fields?: Readonly<Record<string, XStateBossEventFieldSpec>>;
1642
1661
  }
1643
1662
 
1644
- interface XStatePlayerStateStatus {
1645
- player: string;
1663
+ interface XStateRoleStateStatus {
1664
+ role: string;
1646
1665
  label: string;
1647
1666
  }
1648
1667
 
1649
1668
  bossEvents?: readonly XStateBossEventSpec[];
1650
- playerStates?: Readonly<Record<string, XStatePlayerStateStatus>>;
1669
+ roleStates?: Readonly<Record<string, XStateRoleStateStatus>>;
1651
1670
  placeholderFields?: Readonly<Record<string, string>>;
1652
1671
  ```
1653
1672
 
@@ -1663,8 +1682,8 @@ The thin emitted module:
1663
1682
  runtime-owned arm to have lost payload detail under erasure shall report
1664
1683
  that gap rather than emit the entry.
1665
1684
  - Supplies `spec.compat` with the compatibility values current at link time:
1666
- `{ artifactSchema, runtimeAbi }`, where `artifactSchema` is `1` — the
1667
- schema number of the thin-module format this §Output defines — and
1685
+ `{ artifactSchema, runtimeAbi }`, where `artifactSchema` is `2` — the
1686
+ schema number of the local-role thin-module format this §Output defines — and
1668
1687
  `runtimeAbi` is the installed shared engine's `RUNTIME_ABI` self-report.
1669
1688
  The linker shall verify that the installed engine lists the emitted
1670
1689
  schema in `SUPPORTED_ARTIFACT_SCHEMAS` and treat its absence as a
@@ -1675,7 +1694,8 @@ The thin emitted module:
1675
1694
  declaration against the engine instance that actually loads the emitted
1676
1695
  module and fails construction on a mismatch, so an artifact linked under
1677
1696
  one engine cannot run silently skewed under another. Modules emitted
1678
- before this contract carry no `compat` member and remain loadable.
1697
+ before this contract carry no `compat` member and shall reject before interpretation.
1698
+ - Requires the containing public registry manifest to advertise the same `artifactSchema: 2`; the Captain host shall reject a missing or disagreeing registry value before constructing this runtime, and a bespoke runtime profile shall advertise the same schema without claiming this shared factory's `runtimeAbi`.
1679
1699
  - Default-exports the factory call as `createPlaybookRuntime`, typed
1680
1700
  `PlaybookRuntimeFactory<PlaybookRuntimeOptions>`.
1681
1701
  - Exposes, under an `_internal` export, the pure helpers verification
@@ -1693,7 +1713,7 @@ The thin emitted module:
1693
1713
  speaks only `PlaybookPorts` for every agent and host concern; the
1694
1714
  `node:child_process` dependency of §Script execution lives in the shared
1695
1715
  factory, not in the emitted module.
1696
- - Records the linker inputs (FSM path, player binding, strategies) in a
1716
+ - Records the linker inputs (FSM path and strategies) in a
1697
1717
  top-of-file header comment so the file is reproducible from the same
1698
1718
  inputs.
1699
1719
  - Sources the contract types (`PlayerResult`, `PlayerCallOptions`,
package/slc/text2gears.md CHANGED
@@ -18,24 +18,24 @@ The second phase (spec items → state machine) is out of scope.
18
18
  | source | text | .md |
19
19
  | target | gears | .md |
20
20
 
21
- ## Players
21
+ ## Roles
22
22
 
23
- Players name AI agents and the user.
23
+ Roles name playbook-local delegated work functions.
24
24
 
25
- Two default players:
25
+ Two fixed actors remain outside the role list:
26
26
 
27
27
  - Boss: the human user
28
28
  - Captain: the coordinating agent
29
29
 
30
- Source may declare additional players in an opening `Players:` section.
31
- A player may alias other players with `=` and `|`; Boss picks one at runtime.
30
+ Source may declare delegated roles in an opening `Roles:` section.
31
+ Each role shall be unique and shall not alias another role; concrete player selection and sharing belong to explicit host configuration.
32
+ Role names shall also be unique after canonical lowercase-id derivation, so declarations such as `Coder` and `coder` reject rather than collapse to one manifest role.
32
33
  E.g.:
33
34
 
34
35
  - Coder
35
36
  - Reviewer
36
- - Committer = Coder | Reviewer
37
37
 
38
- Capitalize English player names (e.g., `Writer`); quote non-English names (e.g., `作者`) when needed to distinguish from prose.
38
+ Capitalize English role names (e.g., `Writer`); quote non-English names (e.g., `作者`) when needed to distinguish from prose.
39
39
 
40
40
  ## Behaviors
41
41
 
@@ -47,15 +47,15 @@ will not be visible to downstream compilers or verification.
47
47
  The behavior kind shall be one of:
48
48
 
49
49
  - direct Captain work, written `Captain shall <behavior>:` without naming a
50
- delegated player;
51
- - delegated player work, written `Captain shall prompt <Player>:` or the
52
- existing `Captain shall relay ... to <Player> ...:` form; or
50
+ delegated role;
51
+ - delegated-role work, written `Captain shall prompt <Role>:` or the
52
+ existing `Captain shall relay ... to <Role> ...:` form; or
53
53
  - a literal or dynamic nested playbook call as defined below.
54
54
 
55
55
  Direct Captain work means the coordinating Captain performs the behavior
56
56
  itself. It shall not be rewritten as `Captain shall prompt Captain`, because
57
- Captain is a distinct runtime actor rather than a player binding.
58
- Delegated work shall name the declared player that receives the prompt.
57
+ Captain is a distinct runtime actor rather than a role binding.
58
+ Delegated work shall name the declared role that receives the prompt.
59
59
  Prompts shall be blockquoted, one point per line.
60
60
  When Source already supplies the complete blockquoted acting prompt for a
61
61
  behavior, text2gears shall preserve those prompt lines exactly (apart from the
@@ -210,10 +210,11 @@ heading.
210
210
  Every item in one parallel group shall receive the same completed-prior-group
211
211
  inputs; no item prompt may depend on another member's result from the current
212
212
  group.
213
- Every member shall delegate to a named player, and the source shall permit
214
- those members to resolve to distinct players. Direct-Captain work shares one
213
+ Every member shall delegate to a distinct named role; a group that repeats one canonical role is malformed because one role resolves to one player.
214
+ Direct-Captain work shares one
215
215
  Captain session and nested calls share one pending-child stack slot, so neither
216
- kind may receive parallel-group metadata. If Source explicitly requires either
216
+ kind may receive parallel-group metadata.
217
+ If Source explicitly requires either
217
218
  unsupported kind to run concurrently, text2gears shall report that the source
218
219
  cannot be represented rather than silently serialize it or emit metadata the
219
220
  next phase cannot compile.
@@ -311,15 +312,15 @@ prose, acting prompts, and result descriptions follow the Source language,
311
312
  read per the matching localization of the GEARS definition [[1]].
312
313
  The four `Captain shall` acting-clause forms defined above (direct,
313
314
  delegated, nested playbook call, and script), guard names, and the
314
- `Players:` and `Results:` labels are fixed machine syntax and stay in this
315
+ `Roles:` and `Results:` labels are fixed machine syntax and stay in this
315
316
  exact English form regardless of Source language.
316
317
 
317
318
  ## Transformation-spec sources
318
319
 
319
320
  A Source may itself be the normative specification of a transformation — e.g., a compiler phase definition, as when a meta pipeline compiles this file.
320
- Such a Source declares no players and prompts none; its implied procedure is that Captain performs the specified transformation on request.
321
+ Such a Source declares no roles and prompts none; its implied procedure is that Captain performs the specified transformation on request.
321
322
  Compose Captain-acting spec items for it: when a transformation request names the specification's source and target, Captain shall carry out the transformation as specified.
322
- Prompts shall carry the specification's normative requirements as instructions to Captain — deduplicated, one point per line — without inventing players, triggers, or requirements the specification does not state.
323
+ Prompts shall carry the specification's normative requirements as instructions to Captain — deduplicated, one point per line — without inventing roles, triggers, or requirements the specification does not state.
323
324
 
324
325
  ## Composition
325
326
 
package/src/runtime.d.ts CHANGED
@@ -8,11 +8,15 @@ export interface PlayerCallOptions {
8
8
  resume: string | false;
9
9
  }
10
10
  export interface PlayerSessionStore {
11
- select(playerId: string): string | false;
12
- update(playerId: string, resumeToken?: string): void;
11
+ select(roleId: string): string | false;
12
+ update(roleId: string, resumeToken?: string): void;
13
13
  snapshot(): Readonly<Record<string, string>>;
14
14
  restore(tokens: Readonly<Record<string, string>>): void;
15
15
  }
16
+ export interface PlaybookRoleBinding {
17
+ readonly playerId: string;
18
+ readonly promptIdentity: string;
19
+ }
16
20
  export interface CaptainCallOptions {
17
21
  visibility: 'visible' | 'hidden';
18
22
  resume: string | false;
@@ -100,7 +104,7 @@ export type PlaybookRunResult = {
100
104
  pendingCall: PlaybookPendingCall;
101
105
  };
102
106
  export interface PlaybookPorts {
103
- callPlayer(playerId: string, prompt: string, signal: AbortSignal, options: PlayerCallOptions): Promise<PlayerResult>;
107
+ callPlayer(roleId: string, prompt: string, signal: AbortSignal, options: PlayerCallOptions): Promise<PlayerResult>;
104
108
  callCaptain(prompt: string, signal: AbortSignal, options: CaptainCallOptions): Promise<CaptainResult>;
105
109
  callJudge(prompt: string, signal: AbortSignal): Promise<string>;
106
110
  callPlaybook(request: PlaybookCallRequest, signal: AbortSignal): Promise<PlaybookCallStart>;
@@ -117,12 +121,13 @@ export interface PlaybookSession {
117
121
  parentSessionId?: string;
118
122
  parentCallId?: string;
119
123
  depth: number;
124
+ roleBindings?: Readonly<Record<string, PlaybookRoleBinding>>;
120
125
  playerSessions?: PlayerSessionStore;
121
126
  ports: PlaybookPorts;
122
127
  }
123
128
  export type PlaybookTraceType = 'session.started' | 'boss.input.received' | 'judge.call.started' | 'judge.call.finished' | 'player.call.started' | 'player.call.finished' | 'captain.call.started' | 'captain.call.finished' | 'playbook.call.started' | 'playbook.call.finished' | 'apply.started' | 'apply.finished' | 'fsm.transition' | 'status.emitted' | 'boss.input.settled' | 'session.disposed';
124
129
  export interface PlaybookTraceEvent {
125
- schemaVersion: 2;
130
+ schemaVersion: 3;
126
131
  sessionId: string;
127
132
  playbookId: string;
128
133
  rootSessionId: string;
@@ -138,15 +143,21 @@ export interface PlaybookTraceEvent {
138
143
  }
139
144
  export interface PlaybookPendingBossQuestion {
140
145
  questionId: string;
141
- player: string;
146
+ asker: {
147
+ kind: 'captain';
148
+ } | {
149
+ kind: 'role';
150
+ roleId: string;
151
+ };
142
152
  question: string;
143
153
  sourceItem?: string;
144
154
  }
145
- interface PlaybookRuntimeSnapshotFields {
155
+ export interface PlaybookRuntimeSnapshot {
156
+ schemaVersion: 3;
146
157
  playbookId: string;
147
158
  machine: JsonValue;
148
- playerResumeTokens: {
149
- readonly [playerId: string]: string;
159
+ roleResumeTokens: {
160
+ readonly [roleId: string]: string;
150
161
  };
151
162
  sequences: {
152
163
  trace: number;
@@ -158,14 +169,8 @@ interface PlaybookRuntimeSnapshotFields {
158
169
  };
159
170
  state: PlaybookState;
160
171
  pendingBossQuestions: readonly PlaybookPendingBossQuestion[];
161
- }
162
- export type PlaybookRuntimeSnapshot = PlaybookRuntimeSnapshotFields & ({
163
- schemaVersion: 1;
164
- suspendedCall?: never;
165
- } | {
166
- schemaVersion: 2;
167
172
  suspendedCall?: PlaybookSuspendedCall;
168
- });
173
+ }
169
174
  export interface PlaybookControlAction {
170
175
  id: string;
171
176
  label: string;
@@ -210,4 +215,3 @@ export interface PlaybookRuntime {
210
215
  dispose(): Promise<void>;
211
216
  }
212
217
  export type PlaybookRuntimeFactory<Options = unknown> = (options: Options) => PlaybookRuntime;
213
- export {};