@sublang/playbook 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -151
- package/package.json +46 -6
- package/reference/sdlc/captain.md +102 -0
- package/reference/sdlc/captain.playbook/captain.fsm.d.ts +227 -0
- package/reference/sdlc/captain.playbook/captain.fsm.js +628 -0
- package/reference/sdlc/captain.playbook/captain.fsm.ts +851 -0
- package/reference/sdlc/captain.playbook/captain.gears.md +60 -0
- package/reference/sdlc/captain.playbook/captain.playbook.d.ts +23 -0
- package/reference/sdlc/captain.playbook/captain.playbook.js +1053 -0
- package/reference/sdlc/captain.playbook/captain.playbook.ts +1144 -0
- package/reference/sdlc/code.playbook/bin/playbook.js +152 -10
- package/reference/sdlc/code.playbook/bin/run.js +893 -0
- package/reference/sdlc/code.playbook/code.fsm.d.ts +11 -4
- package/reference/sdlc/code.playbook/code.fsm.introspect.d.ts +2 -2
- package/reference/sdlc/code.playbook/code.fsm.introspect.js +1 -1
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +6 -6
- package/reference/sdlc/code.playbook/code.fsm.js +334 -102
- package/reference/sdlc/code.playbook/code.fsm.ts +467 -180
- package/reference/sdlc/code.playbook/code.gears.md +11 -10
- package/reference/sdlc/code.playbook/code.playbook.d.ts +18 -9
- package/reference/sdlc/code.playbook/code.playbook.js +1095 -200
- package/reference/sdlc/code.playbook/code.playbook.ts +1437 -256
- package/reference/sdlc/code.playbook/code.registry.d.ts +0 -3
- package/reference/sdlc/code.playbook/code.registry.js +0 -3
- package/reference/sdlc/code.playbook/code.registry.ts +0 -6
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +9 -4
- package/reference/sdlc/code.playbook/playbook-captain.js +889 -210
- package/reference/sdlc/code.playbook/playbook-captain.ts +1136 -257
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +10 -0
- package/reference/sdlc/discuss.playbook/discuss.fsm.d.ts +396 -0
- package/reference/sdlc/discuss.playbook/discuss.fsm.js +2066 -0
- package/reference/sdlc/discuss.playbook/discuss.fsm.ts +2464 -0
- package/reference/sdlc/discuss.playbook/discuss.gears.md +251 -0
- package/reference/sdlc/discuss.playbook/discuss.playbook.d.ts +113 -0
- package/reference/sdlc/discuss.playbook/discuss.playbook.js +1514 -0
- package/reference/sdlc/discuss.playbook/discuss.playbook.ts +1926 -0
- package/reference/sdlc/discuss.playbook/discuss.registry.d.ts +58 -0
- package/reference/sdlc/discuss.playbook/discuss.registry.js +97 -0
- package/reference/sdlc/discuss.playbook/discuss.registry.ts +153 -0
- package/slc/gears2fsm.md +557 -57
- package/slc/link.md +1097 -80
- package/slc/optimize.md +88 -0
- package/slc/text2gears.md +247 -5
- package/src/runtime.d.ts +145 -3
- package/src/runtime.ts +200 -2
- package/src/xstate-runtime.d.ts +94 -0
- package/src/xstate-runtime.js +1247 -0
- package/src/xstate-runtime.ts +1802 -0
package/slc/gears2fsm.md
CHANGED
|
@@ -13,29 +13,185 @@ Target is an object artifact only: it defines the machine, actor contracts, and
|
|
|
13
13
|
|
|
14
14
|
## Formats
|
|
15
15
|
|
|
16
|
-
| Role
|
|
17
|
-
|
|
|
18
|
-
| source | gears
|
|
19
|
-
| target | fsm
|
|
16
|
+
| Role | Format | Extension |
|
|
17
|
+
| ------ | ------ | --------- |
|
|
18
|
+
| source | gears | .md |
|
|
19
|
+
| target | fsm | .ts |
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
23
|
The artifact shall use XState v5's `setup(...)` then `.createMachine(...)` [[10]].
|
|
24
|
-
The
|
|
25
|
-
|
|
24
|
+
The artifact shall restrict itself to erasable TypeScript syntax — type annotations that strip cleanly, no constructor parameter properties, `enum`s, or namespaces — so a host running under type stripping loads it directly.
|
|
25
|
+
It shall also pass the repository's strict `noUnusedLocals` and
|
|
26
|
+
`noUnusedParameters` checks. Helper signatures and XState callbacks shall omit
|
|
27
|
+
values they do not read; for example, a fresh-context helper that uses only
|
|
28
|
+
`bossIntent` shall not also accept an unused `context`, and an assign callback
|
|
29
|
+
that reads only `event` shall destructure only `event`.
|
|
30
|
+
The `types` block shall declare only `context`, `events`, machine `input`, and
|
|
31
|
+
machine `output`. XState v5's `SetupTypes` has no `actors` property; emitting
|
|
32
|
+
`types: { actors: ... }` is invalid and prevents registered action and actor
|
|
33
|
+
names from type-checking.
|
|
34
|
+
Declare a distinct typed actor contract in `setup(...)`'s top-level `actors`
|
|
35
|
+
map for every actor kind the GEARS artifact uses, using typed actor logic such
|
|
36
|
+
as `fromPromise<Output, Input>(...)` [[11]]:
|
|
37
|
+
|
|
38
|
+
- `captain` for direct work performed by Captain;
|
|
39
|
+
- `player` for work Captain delegates to a named player;
|
|
40
|
+
- `playbook` for a nested playbook call; and
|
|
41
|
+
- `script` for a deterministic shell script an
|
|
42
|
+
[optimizer-introduced script item](text2gears.md#script-behaviors-optimizer-introduced)
|
|
43
|
+
runs without any agent.
|
|
44
|
+
|
|
45
|
+
Do not declare, register, export, or import an actor kind the GEARS artifact
|
|
46
|
+
does not use. A playbook with direct Captain work and nested calls but no
|
|
47
|
+
delegated player therefore has `captain` and `playbook` contracts only.
|
|
48
|
+
|
|
49
|
+
XState may expose output from heterogeneous invoked actors as `unknown` in
|
|
50
|
+
shared guards and actions. Generated helpers shall accept an unknown event and
|
|
51
|
+
narrow its `output` or `error` structurally to the declared actor contract
|
|
52
|
+
before reading fields; they shall not rely on unchecked `event.output`
|
|
53
|
+
inference. Helpers that construct transition arrays shall preserve guard,
|
|
54
|
+
action, and target literals with `as const`, `satisfies`, or typed action/guard
|
|
55
|
+
functions rather than widening registered names to plain `string`.
|
|
56
|
+
|
|
57
|
+
The artifact shall not import a runner or bake in concrete actor
|
|
58
|
+
implementations. Each actor placeholder shall fail explicitly (for example,
|
|
59
|
+
throw `'captain actor must be provided by the runner'`).
|
|
60
|
+
Where the Source artifact begins with an SPDX comment block, the generated
|
|
61
|
+
artifact shall preserve its license and copyright text before the imports
|
|
62
|
+
using valid TypeScript line comments. It shall never copy Markdown HTML
|
|
63
|
+
comment delimiters into a TypeScript target.
|
|
26
64
|
|
|
27
65
|
`CaptainInput` shall be a typed object with at least:
|
|
28
66
|
|
|
67
|
+
- `stateId`: the stable id of the invoking working leaf;
|
|
68
|
+
- `sourceItem`: the GEARS item ID this state realizes;
|
|
69
|
+
- `prompt`: the source item's full final prompt, verbatim;
|
|
70
|
+
- `result`: a record whose keys are the valid guard names this invocation may
|
|
71
|
+
return.
|
|
72
|
+
|
|
73
|
+
`PlayerInput` shall be a typed object with at least:
|
|
74
|
+
|
|
75
|
+
- `stateId`: the stable id of the invoking working leaf;
|
|
29
76
|
- `player`: the [player](text2gears.md#players) Captain is to invoke;
|
|
30
77
|
- `sourceItem`: the GEARS item ID this state realizes;
|
|
31
78
|
- `prompt`: the source item's full final prompt, verbatim;
|
|
32
79
|
- `result`: a record whose keys are the valid guard names this invocation may return.
|
|
33
80
|
|
|
34
|
-
`
|
|
81
|
+
`ScriptInput` shall be a typed object with at least:
|
|
82
|
+
|
|
83
|
+
- `stateId`: the stable id of the invoking working leaf;
|
|
84
|
+
- `sourceItem`: the GEARS item ID this state realizes;
|
|
85
|
+
- `command`: the script item's blockquote text, verbatim after Markdown
|
|
86
|
+
unescaping;
|
|
87
|
+
- `result`: a record whose keys are the item's two declared guard names, first
|
|
88
|
+
the zero-exit guard, then the nonzero-exit guard.
|
|
89
|
+
|
|
90
|
+
`ScriptOutput` shall be a discriminated union with one literal `guard` member
|
|
91
|
+
per declared result key and a required `exitStatus: number` property.
|
|
92
|
+
The script contract carries no prose output: downstream prompts shall not
|
|
93
|
+
depend on text a script produces.
|
|
94
|
+
|
|
95
|
+
`CaptainOutput` and `PlayerOutput` shall each be a discriminated union with one
|
|
96
|
+
literal `guard` member per authored result key and every payload field required
|
|
97
|
+
by that result as a required property. A catch-all `guard: string` interface
|
|
98
|
+
with optional look-alike fields is not a discriminated contract and is
|
|
99
|
+
malformed.
|
|
100
|
+
The artifact shall export the machine input plus every Captain, player, and
|
|
101
|
+
playbook actor input/output type that the linker must provide. The linked
|
|
102
|
+
module imports those exact types; it shall not redeclare near-duplicates that
|
|
103
|
+
can drift in optional fields, dynamic-call metadata, question ids, or child
|
|
104
|
+
result shapes.
|
|
105
|
+
Any recursive JSON value type in the artifact shall exactly preserve the
|
|
106
|
+
shared boundary's readonly variance:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
type JsonValue =
|
|
110
|
+
| null
|
|
111
|
+
| boolean
|
|
112
|
+
| number
|
|
113
|
+
| string
|
|
114
|
+
| readonly JsonValue[]
|
|
115
|
+
| { readonly [key: string]: JsonValue };
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Nested-playbook output, completed-result evidence, plans, context, and machine
|
|
119
|
+
output shall use that readonly type rather than a mutable array/record
|
|
120
|
+
near-duplicate. The linker shall not cast or copy around a variance mismatch.
|
|
121
|
+
|
|
122
|
+
Every runtime-value placeholder established by Source in a direct-Captain or
|
|
123
|
+
delegated-player prompt shall be backed by a typed actor-input field populated
|
|
124
|
+
from typed machine context, so the linker can substitute it with the exact
|
|
125
|
+
runtime value. Angle-bracketed metavariables quoted inside domain instructions
|
|
126
|
+
(for example the literal `<model>` in a commit-message format) remain ordinary
|
|
127
|
+
prompt text and are not runtime-value placeholders. For the generic Captain
|
|
128
|
+
forms, wire `<boss-intent>` from `bossIntent`,
|
|
129
|
+
`<enabled-playbooks>` from `enabledPlaybooks`, `<remaining-plan>` from
|
|
130
|
+
`remainingPlan`, and `<completed-call-results>` from
|
|
131
|
+
`completedCallResults`. Other placeholders shall retain the semantic typed
|
|
132
|
+
field established by Source (for example `<#>` from `irNumber`). Leaving a
|
|
133
|
+
placeholder literal, replacing it with an empty default because its field was
|
|
134
|
+
omitted, or making the linker recover it from untyped context is malformed.
|
|
135
|
+
The sole blockquote placeholder of a dynamic nested-playbook item is instead
|
|
136
|
+
the child `textContext` field specified in §Nested playbook calls.
|
|
137
|
+
|
|
138
|
+
The generic Captain's `enabledPlaybooks` field shall be an immutable array of
|
|
139
|
+
exact entries `{ id: string, command: string, intent: string }`, not an array
|
|
140
|
+
of ids or an open record. Its dynamic-call guard checks `entry.id`, while the
|
|
141
|
+
linked runtime validates, snapshots, and deterministically renders all three
|
|
142
|
+
fields.
|
|
35
143
|
|
|
36
144
|
Guard names shall be specified and interpreted **per state**, not as a global union.
|
|
37
145
|
A global union encourages name reuse with divergent semantics and couples unrelated states.
|
|
38
146
|
Shared helpers may accept `string`, but each state's `invoke.input.result` is the authoritative local contract.
|
|
147
|
+
For an acting GEARS item, the compiler shall derive that contract only from
|
|
148
|
+
the ordered bullets under the item's out-of-blockquote `Results:` label.
|
|
149
|
+
Every declared guard name shall match `[A-Za-z_$][A-Za-z0-9_$]*`.
|
|
150
|
+
It shall preserve every guard name, order, and description verbatim, reject a
|
|
151
|
+
missing, duplicate, blank, or malformed declaration, and shall not infer a
|
|
152
|
+
result contract from acting-prompt prose or transition implementation.
|
|
153
|
+
|
|
154
|
+
An acting item that declares no `Results:` label has exactly one outcome
|
|
155
|
+
(text2gears emits result contracts only for behaviors with more than one
|
|
156
|
+
outcome, or whose output a later item consumes).
|
|
157
|
+
The compiler shall give such a state the **default single-outcome contract**:
|
|
158
|
+
one result `done` with the fixed description
|
|
159
|
+
`The acting agent completed the behavior.`, plus the universal
|
|
160
|
+
`needsBossReply` below.
|
|
161
|
+
A single-outcome item may instead carry exactly one authored `Results:`
|
|
162
|
+
bullet when a later prompt consumes its output; the compiler derives the
|
|
163
|
+
one-guard contract from that bullet as usual.
|
|
164
|
+
The `done` transition is self-driving per §Transitions: it targets the next
|
|
165
|
+
workflow obligation, or a `final` state when the item is the last one.
|
|
166
|
+
The default never applies to an item carrying a `Results:` label, and it does
|
|
167
|
+
not license inferring any richer contract from prose.
|
|
168
|
+
The item's blockquote alone becomes `invoke.input.prompt`; the `Results:`
|
|
169
|
+
label and bullets shall never enter that prompt.
|
|
170
|
+
Each result description shall name every additional output field its accepting
|
|
171
|
+
guard requires, using the exact case-sensitive property names. For example, a
|
|
172
|
+
delegation or continuing-call description whose guard reads the planned child
|
|
173
|
+
call shall say that output includes `remainingPlan`, `nextPlaybookId`, and
|
|
174
|
+
`nextPlaybookInput`; a direct or final response shall name `response`; and an
|
|
175
|
+
authored question shall name `question`. A vague description such as
|
|
176
|
+
"selected the next call" is malformed when its guard also requires structured
|
|
177
|
+
fields. Deterministic verification synthesizes valid actor output from this
|
|
178
|
+
local result contract and shall not infer hidden guard payloads from guard
|
|
179
|
+
source text.
|
|
180
|
+
|
|
181
|
+
For the default generic Captain decide-call-observe pattern, the local guard
|
|
182
|
+
discriminants are a stable compiler contract, not names the compiler may
|
|
183
|
+
invent:
|
|
184
|
+
|
|
185
|
+
- initial routing uses `question` with required `question` and `delegation`
|
|
186
|
+
with required `remainingPlan`, `nextPlaybookId`, and
|
|
187
|
+
`nextPlaybookInput`; it has no direct or terminal result;
|
|
188
|
+
- post-child reassessment uses `final` with required `response`,
|
|
189
|
+
`followUpQuestion` with required `question`, and `continuing` with required
|
|
190
|
+
`remainingPlan`, `nextPlaybookId`, and `nextPlaybookInput`.
|
|
191
|
+
|
|
192
|
+
Both direct-Captain states additionally receive the universal
|
|
193
|
+
`needsBossReply` result. Their guards and actions shall use those exact
|
|
194
|
+
case-sensitive names so the compiled adjudication contract remains stable.
|
|
39
195
|
|
|
40
196
|
## States
|
|
41
197
|
|
|
@@ -44,20 +200,41 @@ Each state shall declare:
|
|
|
44
200
|
- a stable `id` (for `#id` targeting and Boss interrupts);
|
|
45
201
|
- an intuitive state key (the property name under `states: { ... }`);
|
|
46
202
|
- a one-line `description` (for inspector tools and documentation);
|
|
47
|
-
-
|
|
203
|
+
- JSON-safe `meta: { playbook: { stateId, description } }` repeating its
|
|
204
|
+
stable id and description so linked runtimes can discover active public
|
|
205
|
+
identities through `snapshot.getMeta()` without private XState nodes;
|
|
206
|
+
- if it invokes the direct `captain` actor: `invoke.input` carrying
|
|
207
|
+
`sourceItem`, `prompt`, and `result` (per [Setup](#setup));
|
|
208
|
+
- if it invokes the delegated `player` actor: `invoke.input` additionally
|
|
209
|
+
carrying `player`;
|
|
210
|
+
- if it invokes the `script` actor: `invoke.input` carrying `stateId`,
|
|
211
|
+
`sourceItem`, `command`, and `result` (per [Setup](#setup)) — no `prompt`
|
|
212
|
+
and no `player`.
|
|
48
213
|
|
|
49
214
|
The source item ID shall live in `invoke.input.sourceItem`, not in a comment — this keeps the GEARS-to-state mapping machine-readable.
|
|
50
|
-
A state's `invoke.input.player` shall match its source item's
|
|
215
|
+
A delegated state's `invoke.input.player` shall match its source item's named
|
|
216
|
+
player. A direct Captain state shall not invent a `Captain` player binding.
|
|
51
217
|
|
|
52
|
-
|
|
218
|
+
Every invoking working leaf — sequential or parallel, whatever its actor
|
|
219
|
+
kind — shall carry the tag `playbook.busy`: the shared quiescence helper
|
|
220
|
+
derives busyness strictly from active-state tags, so an untagged working leaf
|
|
221
|
+
reads as quiescent while its call is still in flight.
|
|
222
|
+
|
|
223
|
+
The machine's initial state shall be a quiescent idle hub (no `invoke`) — typically `ready` — that accepts the Boss entry events and carries the `playbook.parked` tag because it can return control to Boss.
|
|
224
|
+
Captain- and player-invoking work begins only on a Boss-originated event, so
|
|
225
|
+
constructing and starting the machine performs no agent call.
|
|
226
|
+
|
|
227
|
+
Each direct Captain or delegated player actor returns a discriminated result
|
|
228
|
+
with `guard` set to one of `input.result`'s keys [[4]].
|
|
53
229
|
Guards [[5]] on `onDone` transitions inspect `event.output.guard` to route.
|
|
54
230
|
|
|
55
|
-
|
|
231
|
+
Delegated-player example:
|
|
56
232
|
|
|
57
233
|
```typescript
|
|
58
234
|
invoke: {
|
|
59
|
-
src: '
|
|
60
|
-
input: ({ context }):
|
|
235
|
+
src: 'player',
|
|
236
|
+
input: ({ context }): PlayerInput => ({
|
|
237
|
+
stateId: '<stable-state-id>',
|
|
61
238
|
player: 'Reviewer',
|
|
62
239
|
sourceItem: '<ITEM-A>',
|
|
63
240
|
prompt: [
|
|
@@ -74,25 +251,268 @@ invoke: {
|
|
|
74
251
|
}
|
|
75
252
|
```
|
|
76
253
|
|
|
254
|
+
For an item in which Captain acts directly, the corresponding invocation uses
|
|
255
|
+
`src: 'captain'` and a `CaptainInput` with the same static mapping fields but no
|
|
256
|
+
`player` field.
|
|
257
|
+
|
|
77
258
|
## Mapping
|
|
78
259
|
|
|
79
260
|
Each Source spec item shall map to exactly one state in Target.
|
|
80
261
|
A state's `invoke.input.sourceItem` shall be that item's ID, and `invoke.input.prompt` shall carry the item's prompt verbatim.
|
|
81
262
|
|
|
263
|
+
An item written as direct Captain work shall map to exactly one `captain`
|
|
264
|
+
invocation. An item that prompts or relays to a named player shall map to
|
|
265
|
+
exactly one `player` invocation. A nested-call item shall map to exactly one
|
|
266
|
+
`playbook` invocation. A script item (`Captain shall run:`) shall map to
|
|
267
|
+
exactly one `script` invocation whose `input.command` carries the blockquote
|
|
268
|
+
verbatim and whose `result` preserves the item's two guards in declared order.
|
|
269
|
+
The compiler shall not infer one actor kind from a
|
|
270
|
+
runtime player name or encode Captain as a player.
|
|
271
|
+
A script state is not agent-invoking: the compiler shall not add
|
|
272
|
+
`needsBossReply` to a script state's result map and shall not register it with
|
|
273
|
+
`resumableStates(ids)`.
|
|
274
|
+
A script state's success guard shall target the next workflow step and its
|
|
275
|
+
failure guard shall route to `failed` unless the source items define a
|
|
276
|
+
different recovery.
|
|
277
|
+
|
|
82
278
|
Per text2gears [composition](text2gears.md#composition), each spec item already carries the full final prompt for one state behavior, with no duplicate lines.
|
|
83
279
|
The FSM compiler shall not concatenate prompts across items, re-compose them, or silently dedupe.
|
|
84
280
|
A spec item that still contains duplicate prompt lines is malformed; the compiler shall reject or flag it rather than silently propagate the duplication into `invoke.input.prompt`.
|
|
85
281
|
|
|
282
|
+
## Parallel groups
|
|
283
|
+
|
|
284
|
+
Items carrying the same `Parallel group: <id>` metadata shall compile into one
|
|
285
|
+
compound state with `type: 'parallel'` and one region per item [[12]].
|
|
286
|
+
Each member shall be a delegated-player item; a direct-Captain or nested-call
|
|
287
|
+
member is malformed because those actor kinds share one Captain control lane or one
|
|
288
|
+
pending-child slot. Each region shall contain a delegated-player working leaf
|
|
289
|
+
and a local final state; the working leaf retains the item's stable state id,
|
|
290
|
+
`sourceItem`, player, prompt, and result contract.
|
|
291
|
+
The parallel parent shall use `onDone` as the join, which XState takes only
|
|
292
|
+
after every region reaches final.
|
|
293
|
+
|
|
294
|
+
Each branch shall assign only its own staged result.
|
|
295
|
+
The join shall promote all staged results atomically before later work begins,
|
|
296
|
+
so branch completion order cannot change downstream inputs.
|
|
297
|
+
Transitions between sibling regions are forbidden.
|
|
298
|
+
|
|
299
|
+
Working leaves shall carry tag `playbook.busy`.
|
|
300
|
+
A branch that supports Boss-reply suspension shall use a local waiting leaf
|
|
301
|
+
tagged `playbook.parked` rather than exit the parallel parent; `BOSS_REPLY`
|
|
302
|
+
shall identify and reenter only the waiting branch.
|
|
303
|
+
If several branch questions are pending, the event shall carry a stable
|
|
304
|
+
question id and the classifier shall not guess among them.
|
|
305
|
+
A fresh entry event or root interrupt may exit the complete parallel parent and
|
|
306
|
+
shall clear its staged results and branch questions.
|
|
307
|
+
Treat a fixed parallel parent as one jumpable unit. Generate a stable id and
|
|
308
|
+
root `BOSS_INTERRUPT` target for the parallel parent, not for any working leaf
|
|
309
|
+
inside its regions. Branch working ids remain valid internal resume targets for
|
|
310
|
+
their branch-local `BOSS_REPLY`; they shall not appear in the interrupt target
|
|
311
|
+
union or classifier catalog. This prevents a nominal one-branch jump from
|
|
312
|
+
implicitly entering or restarting the parallel parent's other regions.
|
|
313
|
+
An invoke error shall exit to the root failure state, allowing XState to stop
|
|
314
|
+
the sibling invocations automatically.
|
|
315
|
+
|
|
316
|
+
## Nested playbook calls
|
|
317
|
+
|
|
318
|
+
An item whose behavior is a literal or dynamic
|
|
319
|
+
`Captain shall call playbook ...:` shall compile to a state that invokes a
|
|
320
|
+
typed `playbook` actor, not the `captain` or `player` actor.
|
|
321
|
+
The setup types shall declare `PlaybookInput` with stable `stateId`, target
|
|
322
|
+
`playbookId`, composed `text`, and optional `sourceItem`. The playbook actor's
|
|
323
|
+
successful output is the child's JSON-safe machine output itself (or
|
|
324
|
+
`undefined`), not a second wrapper carrying a synthetic status or `output`
|
|
325
|
+
field. `invoke.onDone` shall therefore record `event.output` as the successful
|
|
326
|
+
child output. Aborted and error call results reject the actor and reach
|
|
327
|
+
`invoke.onError`.
|
|
328
|
+
The artifact shall supply a failing placeholder for `playbook`, just as it does
|
|
329
|
+
for `captain` and `player`; the linked runtime provides the actor
|
|
330
|
+
implementation.
|
|
331
|
+
|
|
332
|
+
A literal call shall retain the existing representation: `playbookId` is the
|
|
333
|
+
literal target and `text` is the composed GEARS blockquote.
|
|
334
|
+
|
|
335
|
+
A dynamic call written
|
|
336
|
+
``Captain shall call playbook selected by `<target-field>`:`` shall declare the
|
|
337
|
+
named target field and the blockquote's text field as typed string fields in
|
|
338
|
+
FSM context. The dynamic `PlaybookInput` variant shall require string-valued
|
|
339
|
+
`playbookIdContext` and `textContext` metadata fields. Its `invoke.input` shall
|
|
340
|
+
read the runtime values from those exact context fields and shall also carry
|
|
341
|
+
the following static metadata:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
{
|
|
345
|
+
stateId: '<stable-state-id>',
|
|
346
|
+
sourceItem: '<ITEM-A>',
|
|
347
|
+
playbookId: context.nextPlaybookId,
|
|
348
|
+
text: context.nextPlaybookInput,
|
|
349
|
+
playbookIdContext: 'nextPlaybookId',
|
|
350
|
+
textContext: 'nextPlaybookInput',
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`playbookIdContext` and `textContext` name context fields; they never contain
|
|
355
|
+
runtime target or text values. The compiler shall emit them as explicit string
|
|
356
|
+
literals so conformance tools can verify context wiring without evaluating or
|
|
357
|
+
parsing the `invoke.input` function's source. The evaluated `playbookId` and
|
|
358
|
+
`text` shall each be strings and shall come from the context field named by its
|
|
359
|
+
corresponding metadata property. Literal calls need not carry these dynamic
|
|
360
|
+
metadata properties and retain their existing behavior.
|
|
361
|
+
|
|
362
|
+
The call state shall carry tag `playbook.suspended` and shall route
|
|
363
|
+
`invoke.onDone` from child output and `invoke.onError` from child failure.
|
|
364
|
+
The child call shall remain state-scoped: leaving the call state stops the
|
|
365
|
+
invoked actor and aborts the host call through XState's invocation signal
|
|
366
|
+
[[2]].
|
|
367
|
+
The FSM shall not allocate runtime call ids, construct child sessions, retain
|
|
368
|
+
runtime promises, or route Boss text to the child.
|
|
369
|
+
|
|
370
|
+
When Source explicitly continues one downstream behavior after a child
|
|
371
|
+
success, abort, or failure, both `invoke.onDone` and `invoke.onError` shall
|
|
372
|
+
record the corresponding JSON-safe child result and target that downstream
|
|
373
|
+
behavior. The generic `failed` state is the default only when Source declares
|
|
374
|
+
no recovery or reassessment path for a rejected child.
|
|
375
|
+
That recovering `onError` shall be an ordered transition array. Its first arm
|
|
376
|
+
shall use a typed structural guard that accepts only an `Error` carrying a
|
|
377
|
+
validated public child `result` with `status: 'aborted' | 'error'`; only that
|
|
378
|
+
arm appends sanitized child evidence and continues. A fallback arm shall retain
|
|
379
|
+
the control error normalized as JSON-safe `{ name, message, stack? }` in
|
|
380
|
+
`lastError` and route to `failed` without appending a completed child result;
|
|
381
|
+
the linked runtime alone retains the original error in its out-of-machine
|
|
382
|
+
latch. Non-abort port rejection, malformed port data, JSON, identity, bridge,
|
|
383
|
+
and other control-plane errors are not authored child outcomes even though
|
|
384
|
+
XState delivers both kinds through `invoke.onError`.
|
|
385
|
+
Where the rejected error structurally carries the runtime's normalized child
|
|
386
|
+
result, the error action shall inspect whether its status was `aborted` or
|
|
387
|
+
`error`; it shall not collapse both into an invented success/failure enum. The
|
|
388
|
+
FSM may inspect that public structural data without importing the runner or
|
|
389
|
+
constructing runtime call identities.
|
|
390
|
+
For a workflow that reassesses child results, use a typed JSON-safe record such
|
|
391
|
+
as `{ playbookId, status: 'ok', output }` on `onDone` and
|
|
392
|
+
`{ playbookId, status: 'aborted' | 'error', error }` on `onError`. Because the
|
|
393
|
+
runtime rejection is an `Error` with a public `result` property, normalization
|
|
394
|
+
shall inspect `result.status` and `result.error` before applying a generic
|
|
395
|
+
`Error` normalizer. It shall persist only the current context target id, the
|
|
396
|
+
status, and a compact `{ name, message }` error; it shall never persist the
|
|
397
|
+
whole runtime result, child session id, child state, call identity, or stack.
|
|
398
|
+
An abort without an error gets a compact generic abort description. The current
|
|
399
|
+
target id remains available in typed context until the sanitized record has
|
|
400
|
+
been created. On success, persist only `event.output`, which is the actual child
|
|
401
|
+
machine output returned by the bridge, not a runtime call-result envelope.
|
|
402
|
+
When that optional output is absent, omit the `output` property from the
|
|
403
|
+
completed-result record rather than storing `undefined`.
|
|
404
|
+
The outer trusted error is an actual `Error` instance and therefore is not a
|
|
405
|
+
plain JSON object. The structural guard shall inspect its public `.result`
|
|
406
|
+
property directly, then validate only that nested result before sanitizing it;
|
|
407
|
+
it shall not require the outer error itself to pass a plain-object/JSON guard.
|
|
408
|
+
Validation of that nested public result includes its status-specific required
|
|
409
|
+
members and target identity: `playbookId` shall equal the current selected
|
|
410
|
+
target, an `error` result shall carry a normalized error, and every optional
|
|
411
|
+
member that is present shall have the public contract's declared shape. A
|
|
412
|
+
look-alike such as `{ status: 'error' }` is malformed control data, not an
|
|
413
|
+
authored child failure, and shall take the fallback `failed` arm without
|
|
414
|
+
appending evidence. The guard shall not fabricate missing identity or error
|
|
415
|
+
members merely because the status string happens to be recognized.
|
|
416
|
+
The public result's declared optional `childSessionId` and `state` members are
|
|
417
|
+
valid when their shapes satisfy the shared contract; validate and then discard
|
|
418
|
+
them when building compact Captain evidence. They are not undeclared extras.
|
|
419
|
+
Likewise, the public normalized error may carry its declared optional string
|
|
420
|
+
`stack`; validate it and omit it from the compact `{ name, message }` evidence
|
|
421
|
+
rather than rejecting an otherwise valid authored child result.
|
|
422
|
+
Apply the public union exactly: an `aborted` or `error` result shall reject an
|
|
423
|
+
`output` member; `childSessionId`, when present, shall be non-empty; `error`
|
|
424
|
+
shall contain only non-empty `name`, string `message`, and optional string
|
|
425
|
+
`stack`; and `state`, when present, shall validate every declared
|
|
426
|
+
`PlaybookState` member and reject unknown or missing members. Treating an
|
|
427
|
+
arbitrary JSON-safe object as a valid `state`, or checking only that these
|
|
428
|
+
members have broad string/object types, is not complete public-result
|
|
429
|
+
validation.
|
|
430
|
+
In other words, the guard validates the complete public result it received,
|
|
431
|
+
while the action retains only the current selected playbook id, status, and
|
|
432
|
+
compact error. Do not implement evidence minimization by accepting only the
|
|
433
|
+
three keys that survive that projection.
|
|
434
|
+
|
|
435
|
+
Before entering a dynamic call, the machine shall reject an empty target and
|
|
436
|
+
empty input text, any target equal to `selfPlaybookId`, and any target that
|
|
437
|
+
Source requires to belong to an input catalog but that catalog does not
|
|
438
|
+
contain. Rejection shall occur before invoking the `playbook` actor; the host
|
|
439
|
+
remains responsible for its independent registry validation.
|
|
440
|
+
Where Source forbids repeating an equivalent completed or failed call without
|
|
441
|
+
new information, the machine shall also keep a private deterministic history
|
|
442
|
+
of target-and-input signatures and reject a continuation whose target and
|
|
443
|
+
complete input exactly match a prior call. Encode each signature as the
|
|
444
|
+
collision-free `JSON.stringify([playbookId, text])` tuple of exact JavaScript
|
|
445
|
+
strings, not delimiter concatenation, and append it before invocation so
|
|
446
|
+
success, abort, and authored failure all count. That history shall not be
|
|
447
|
+
included in a Captain or player prompt; a revised input containing new
|
|
448
|
+
information is a different call. The exact machine check is a safety floor;
|
|
449
|
+
the acting Captain remains responsible for Source's broader semantic
|
|
450
|
+
equivalence policy.
|
|
451
|
+
That validation belongs on the guarded transition into the call state. The
|
|
452
|
+
call state's `invoke.input` mapper shall be a pure read of the already-validated
|
|
453
|
+
typed context fields; it shall not call an assertion helper or throw while
|
|
454
|
+
XState resolves actor input. This keeps state restoration, inspection, and
|
|
455
|
+
scripted coverage from crashing outside the invocation's `onError` boundary.
|
|
456
|
+
For the default Captain decide-call-observe loop, the delegation and
|
|
457
|
+
continuing `onDone` arms shall transition directly into the invoking call
|
|
458
|
+
state. Each arm's single guard validates its applicable actor-output and
|
|
459
|
+
context constraints: both validate JSON shape, catalog membership,
|
|
460
|
+
self-target, and duplicate history, while strict plan shrink applies only to
|
|
461
|
+
`continuing`. Its actions store the selected target/input and append the
|
|
462
|
+
signature before state entry. Do not interpose an eventless preparation or
|
|
463
|
+
validation state between the Captain actor and the call state: it obscures the
|
|
464
|
+
authored Captain entry edge from deterministic coverage and adds no XState
|
|
465
|
+
safety beyond the guarded direct transition.
|
|
466
|
+
|
|
86
467
|
## Context and prompts
|
|
87
468
|
|
|
88
469
|
Context fields used to drive guards or compose prompts shall be **typed and named**.
|
|
89
470
|
The compiler shall not branch on untyped properties of `lastResult`; persistent routing decisions belong in typed context fields. (`lastResult` is for inspection only.)
|
|
471
|
+
Where Source declares a finite ordered plan, represent it as a typed readonly
|
|
472
|
+
JSON-safe array and validate that shape on the actor-output transition; an
|
|
473
|
+
unconstrained `JsonValue` does not establish that a plan is ordered or finite.
|
|
474
|
+
Where a decide-call-observe loop carries the calls after the selected next call
|
|
475
|
+
as `remainingPlan`, its continuing-call guard shall additionally require the
|
|
476
|
+
new plan to be strictly shorter than the current plan. The Captain may revise
|
|
477
|
+
or remove remaining entries as evidence arrives, but it cannot grow or retain
|
|
478
|
+
the same-length plan indefinitely; the initial finite array therefore bounds
|
|
479
|
+
the number of sequential child calls without an arbitrary runtime call limit.
|
|
90
480
|
|
|
91
481
|
Prompts shall pass only the **specific extracted fields** the player needs.
|
|
92
482
|
The compiler shall not dump `JSON.stringify(lastResult)` or any opaque blob: it leaks internal `guard` strings, wastes tokens, and confuses the LLM.
|
|
93
483
|
|
|
94
484
|
Player bindings and per-run parameters shall flow in via the machine's `input` and be copied into context at start-up.
|
|
95
485
|
The artifact shall not bake in player bindings, model names, or per-run values.
|
|
486
|
+
Host-owned configuration such as an enabled-playbook catalog shall remain
|
|
487
|
+
immutable machine input/context for the session. Boss events and actor outputs
|
|
488
|
+
shall not carry, replace, append to, or otherwise overwrite that catalog.
|
|
489
|
+
Every machine with a dynamic call shall receive its own registered or authored
|
|
490
|
+
playbook id as immutable machine input/context named `selfPlaybookId`, and its
|
|
491
|
+
dynamic-call guard shall reject that target. The leaf-level `stateId` name is
|
|
492
|
+
reserved for actor invocation identity and shall not be reused for a playbook
|
|
493
|
+
id.
|
|
494
|
+
JSON-safe context and output records shall omit absent optional members instead
|
|
495
|
+
of creating own properties whose value is `undefined`.
|
|
496
|
+
JSON validation shall accept only null, booleans, finite numbers, strings,
|
|
497
|
+
arrays, and plain own enumerable data-property objects. It shall reject cycles,
|
|
498
|
+
non-plain instances (`Error`, `Date`, `Map`, and class instances), accessors,
|
|
499
|
+
symbol keys, sparse/undefined values, `NaN`, and infinities rather than silently
|
|
500
|
+
changing them during serialization.
|
|
501
|
+
An accepted array shall have prototype exactly `Array.prototype`, no holes,
|
|
502
|
+
symbols, accessors, or extra own string properties, and enumerable own data
|
|
503
|
+
descriptors for every canonical index; its standard non-enumerable `length`
|
|
504
|
+
descriptor is the sole exception. That data descriptor shall be
|
|
505
|
+
non-configurable and carry the exact array length, but its `writable` flag may
|
|
506
|
+
be either `true` on an ordinary array or `false` after the shared runtime
|
|
507
|
+
recursively freezes a validated boundary value. `Reflect.ownKeys(array)` shall
|
|
508
|
+
contain exactly `length + 1` keys: the `length` property and every canonical
|
|
509
|
+
index from `0` through `length - 1`. A digit string whose numeric value is not
|
|
510
|
+
less than `length` is an extra property, not an array index. An accepted record
|
|
511
|
+
shall have prototype exactly `Object.prototype` or `null`, and every key returned by
|
|
512
|
+
`Reflect.ownKeys` shall be a string whose own descriptor is enumerable and a
|
|
513
|
+
data descriptor. Cycle detection shall track only the active recursion path
|
|
514
|
+
and remove a container on unwind, so a shared acyclic array or record is valid
|
|
515
|
+
while an actual back-edge is rejected.
|
|
96
516
|
|
|
97
517
|
## Transitions
|
|
98
518
|
|
|
@@ -123,87 +543,167 @@ Phases may set typed routing fields so terminal outcomes return to the originati
|
|
|
123
543
|
|
|
124
544
|
### Boss interrupts
|
|
125
545
|
|
|
126
|
-
Boss may interrupt any active state
|
|
546
|
+
Boss may interrupt any active state that can itself receive a Boss turn. Every
|
|
547
|
+
jumpable state shall have a stable `id` [[9]]. A final state is not jumpable.
|
|
548
|
+
A `playbook.suspended` call state with an outstanding child is also not a Boss
|
|
549
|
+
interrupt target: the host routes Boss input to the active child leaf and
|
|
550
|
+
resumes the parent only from the matching child result.
|
|
127
551
|
The runtime sends `{ type: 'BOSS_INTERRUPT', targetId: '<id>' }`; the root machine handles it with one guarded transition per jumpable state targeting `#<id>` with `reenter: true` [[7]][[8]][[9]], so invoked actors restart cleanly.
|
|
128
552
|
The compiler shall emit a `bossInterrupts(ids)` helper rather than hand-writing one transition per state.
|
|
553
|
+
Each generated arm shall guard both the selected `targetId` and every typed
|
|
554
|
+
context precondition required to enter that target safely. It shall not jump
|
|
555
|
+
into a working or reassessment state with missing intent, prior result, plan,
|
|
556
|
+
or other required context and shall not invent defaults merely to make an
|
|
557
|
+
interrupt target executable.
|
|
129
558
|
XState automatically stops the current state's invoked actor on transition [[2]].
|
|
559
|
+
Where the default Captain's routing state accepts a fresh intent while another
|
|
560
|
+
state or Boss-reply wait is active, its `BOSS_INTERRUPT` event shall carry a
|
|
561
|
+
required non-empty `bossIntent`. The guarded routing arm shall copy that value,
|
|
562
|
+
clear the prior plan, child evidence, exact-call history, selected call,
|
|
563
|
+
response, error, and consumed question/reply context, then reenter routing.
|
|
564
|
+
It shall not restart the old intent or retain a stale pending question.
|
|
565
|
+
For this default Captain, `routing` is the sole `BOSS_INTERRUPT` target; a
|
|
566
|
+
fresh directive always returns to routing and shall not jump directly into
|
|
567
|
+
reassessment or the Boss-reply wait. The typed event union and classifier
|
|
568
|
+
contract shall require exactly `targetId: 'routing'` plus the fresh
|
|
569
|
+
`bossIntent`.
|
|
130
570
|
|
|
131
571
|
### Boss entry events vs. BOSS_INTERRUPT
|
|
132
572
|
|
|
133
573
|
`BOSS_INTERRUPT` jumps into an **active** machine, pre-empting whichever state is running.
|
|
134
574
|
**Boss entry events** start or resume from idle or recoverable states when Boss-supplied parameters can't be inferred from machine state alone.
|
|
135
575
|
Entry events shall be typed alongside `BOSS_INTERRUPT` and populate context via a dedicated action.
|
|
136
|
-
|
|
576
|
+
An entry event's copy action shall not clear per-run parameters the event omits: an absent optional field falls back to the existing (input-seeded) context value.
|
|
577
|
+
The two surfaces shall not be collapsed. `BOSS_INTERRUPT` always carries its
|
|
578
|
+
target id and may additionally carry typed Boss-supplied fields such as an
|
|
579
|
+
intent or IR number only where Source requires the pre-empted target to consume
|
|
580
|
+
them; a parameterless entry event may collapse to interrupt-style routing only
|
|
581
|
+
when state-jump and context-update semantics are identical.
|
|
137
582
|
Entry events shall not be root-level transitions from every active state unless the workflow supports pre-emption; they belong on idle and recoverable states (e.g., `failed`).
|
|
138
583
|
|
|
139
584
|
### Boss-reply suspension
|
|
140
585
|
|
|
141
|
-
When a captain-invoking state needs a Boss decision the
|
|
586
|
+
When a captain- or player-invoking state needs a Boss decision the acting agent
|
|
587
|
+
cannot supply alone, the machine shall suspend that task in a quiescent wait
|
|
588
|
+
state and resume the same task with the Q+A in the next prompt.
|
|
142
589
|
This is a third Boss surface alongside `BOSS_INTERRUPT` and Boss entry events.
|
|
143
590
|
|
|
144
|
-
Every captain-invoking state supports this path.
|
|
591
|
+
Every captain- and player-invoking state supports this path.
|
|
145
592
|
There is no source-level opt-in annotation and no `needsBossReply` result metadata in GEARS output.
|
|
146
593
|
The FSM compiler shall preserve the GEARS blockquote as the state's domain `prompt` body and shall not inject any Boss-question instruction into `invoke.input.prompt`.
|
|
147
594
|
|
|
148
|
-
For every captain-invoking state, the compiler shall add
|
|
595
|
+
For every captain- and player-invoking state, the compiler shall add
|
|
596
|
+
`needsBossReply` to the state's `invoke.input.result` map.
|
|
149
597
|
The description shall be the standard adjudicator-facing text:
|
|
150
598
|
|
|
151
599
|
```text
|
|
152
|
-
The
|
|
600
|
+
The acting agent's prose surfaces a clarifying question for Boss that the agent cannot answer alone. Output shall include `question: <verbatim question text from the acting agent's prose>`.
|
|
153
601
|
```
|
|
154
602
|
|
|
155
|
-
|
|
603
|
+
The standard annotated backtick form names the exact `question` property; the
|
|
604
|
+
linker's required-field extractor shall interpret only the identifier before
|
|
605
|
+
the colon as the JSON field name.
|
|
156
606
|
The linked runtime composes player prompts per [link.md "Player prompt composition"](link.md#player-prompt-composition), without adding a player-visible Boss-question instruction.
|
|
157
607
|
|
|
158
|
-
The
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- `
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
A
|
|
608
|
+
The question record shall be
|
|
609
|
+
`{ questionId, resumeStateId, sourceItem, player, question }`.
|
|
610
|
+
`questionId` and `resumeStateId` shall both equal the stable working-leaf
|
|
611
|
+
`stateId`.
|
|
612
|
+
`questionId`, `resumeStateId`, and `sourceItem` shall come from the suspended
|
|
613
|
+
working leaf's stable invocation metadata. `player` shall come from a delegated
|
|
614
|
+
`PlayerInput`, or be the literal `Captain` for a direct-Captain state. Only
|
|
615
|
+
`question` shall come from adjudicated actor output.
|
|
616
|
+
|
|
617
|
+
A machine with at most one active Captain or player task may use the scalar
|
|
618
|
+
form:
|
|
619
|
+
|
|
620
|
+
- An `awaitBossReply` state with stable `id: 'awaitBossReply'`, tag
|
|
621
|
+
`playbook.parked`, and description
|
|
622
|
+
`Waiting for Boss to answer the acting agent's question.`.
|
|
623
|
+
- A `BOSS_REPLY` event carrying `{ answer: string; questionId?: string }`.
|
|
624
|
+
- Context fields `pendingBossQuestion?: PendingBossQuestion` and
|
|
625
|
+
`bossReply?: string`.
|
|
626
|
+
- `resumableStates(ids)`, `setPendingBossQuestion`, and
|
|
627
|
+
`clearBossReplyContext` helpers with the existing single-question behavior.
|
|
628
|
+
|
|
629
|
+
A machine with parallel delegated-player tasks shall use the keyed form:
|
|
630
|
+
|
|
631
|
+
- One local waiting leaf per branch, tagged `playbook.parked`.
|
|
632
|
+
- A `BOSS_REPLY` event carrying `{ questionId: string; answer: string }`.
|
|
633
|
+
- Context fields
|
|
634
|
+
`pendingBossQuestions: Partial<Record<ResumableStateId, PendingBossQuestion>>`
|
|
635
|
+
and `bossReplies: Partial<Record<ResumableStateId, string>>`.
|
|
636
|
+
- Helpers that set, answer, and clear only the named branch record; exiting the
|
|
637
|
+
complete parallel group for a fresh directive or interrupt clears every
|
|
638
|
+
record owned by that group.
|
|
639
|
+
|
|
640
|
+
Where exactly one question is pending, a linked runtime may accept a classifier
|
|
641
|
+
reply that omits `questionId` and fill that sole id.
|
|
642
|
+
Where several questions are pending, the classifier prompt and event shall
|
|
643
|
+
require `questionId` and shall reject an omitted or unknown id without moving
|
|
644
|
+
the FSM.
|
|
645
|
+
|
|
646
|
+
The scalar `awaitBossReply` state and every local branch wait are quiescent for
|
|
647
|
+
the runtime drive boundary.
|
|
648
|
+
They shall allow a fresh root entry event or interrupt to abandon the relevant
|
|
649
|
+
pending question data before starting new work.
|
|
650
|
+
The wait state or branch-wait leaf itself shall not be an interrupt target:
|
|
651
|
+
re-entering it after the interrupt clears its pending question would create an
|
|
652
|
+
unresumable parked state. Its recorded working leaf remains the sole
|
|
653
|
+
`BOSS_REPLY` resume destination.
|
|
654
|
+
|
|
655
|
+
A captain- or player-invoking state's `invoke.input` function shall carry the
|
|
656
|
+
pending question and reply selected for that working leaf as singular
|
|
657
|
+
`pendingBossQuestion` and `bossReply` fields, regardless of the scalar or keyed
|
|
658
|
+
context representation, so prompt composition has one stable contract.
|
|
180
659
|
When both fields are present, the linked runtime shall compose the continuation preamble and labelled Q&A blocks per [link.md "Player prompt composition"](link.md#player-prompt-composition).
|
|
181
660
|
The FSM artifact shall not bake the continuation preamble into the GEARS-derived `prompt` body.
|
|
182
661
|
|
|
183
662
|
The following malformed states shall route to `failed` per [Errors and termination](#errors-and-termination):
|
|
184
663
|
|
|
185
|
-
- Captain output has `guard: 'needsBossReply'` but no `question`
|
|
186
|
-
|
|
664
|
+
- Captain or player output has `guard: 'needsBossReply'` but no `question`
|
|
665
|
+
field.
|
|
666
|
+
- Captain or player output declares `needsBossReply` from a state without a
|
|
667
|
+
registered scalar or branch-local resume route.
|
|
187
668
|
- `BOSS_REPLY` fired with empty or whitespace-only `answer`.
|
|
669
|
+
- A keyed `BOSS_REPLY` names no pending question.
|
|
188
670
|
|
|
189
671
|
## Errors and termination
|
|
190
672
|
|
|
191
|
-
Every `invoke` shall declare an `onError` handler
|
|
192
|
-
`failed`
|
|
673
|
+
Every `invoke` shall declare an `onError` handler with a fallback routing to a
|
|
674
|
+
dedicated `failed` state and capturing the error in `context.lastError` for
|
|
675
|
+
inspection. A nested playbook invoke may place its validated authored-child
|
|
676
|
+
recovery arm before that fallback as described in §Nested playbook calls.
|
|
677
|
+
`failed` is not `final`: it shall carry tag `playbook.parked`, retain enough
|
|
678
|
+
typed context for Boss recovery, and accept the workflow's recovery entry or
|
|
679
|
+
interrupt surface. The parked tag distinguishes a recoverable failure from a
|
|
680
|
+
busy state so the host retains the session instead of treating the outcome as
|
|
681
|
+
an unhandled runtime error.
|
|
193
682
|
|
|
194
683
|
Every machine shall declare at least one `type: 'final'` state (typically `done`) reachable on completion.
|
|
195
684
|
A never-terminating machine is a defect: the runner has no completion signal.
|
|
196
685
|
|
|
686
|
+
Where Source declares a JSON-safe terminal result, the setup types shall
|
|
687
|
+
declare that output and the root machine shall derive it from typed context
|
|
688
|
+
through XState's machine `output` function. A final-state transition alone does
|
|
689
|
+
not satisfy a declared output contract.
|
|
690
|
+
Fields that Source requires in every terminal output shall be required in the
|
|
691
|
+
TypeScript output type. In particular, a declared `{ response }` result shall
|
|
692
|
+
compile as `{ response: string }`, not `{ response?: string }`; reaching the
|
|
693
|
+
final state without a non-empty response shall be guarded out before the
|
|
694
|
+
machine output is constructed.
|
|
695
|
+
|
|
197
696
|
## References
|
|
198
697
|
|
|
199
|
-
[1]: https://stately.ai/docs/xstate
|
|
200
|
-
[2]: https://stately.ai/docs/invoke
|
|
201
|
-
[3]: https://stately.ai/docs/input
|
|
202
|
-
[4]: https://stately.ai/docs/output
|
|
203
|
-
[5]: https://stately.ai/docs/guards
|
|
204
|
-
[6]: https://stately.ai/docs/context
|
|
205
|
-
[7]: https://stately.ai/docs/transitions
|
|
206
|
-
[8]: https://stately.ai/docs/parent-states
|
|
207
|
-
[9]: https://stately.ai/docs/finite-states
|
|
208
|
-
[10]: https://stately.ai/docs/setup
|
|
209
|
-
[11]: https://stately.ai/docs/actors
|
|
698
|
+
[1]: https://stately.ai/docs/xstate 'XState Official Documentation'
|
|
699
|
+
[2]: https://stately.ai/docs/invoke 'Invoke — invoking actors from states'
|
|
700
|
+
[3]: https://stately.ai/docs/input 'Input — passing data to invoked actors'
|
|
701
|
+
[4]: https://stately.ai/docs/output 'Output — receiving actor results via onDone'
|
|
702
|
+
[5]: https://stately.ai/docs/guards 'Guards — synchronous transition conditions'
|
|
703
|
+
[6]: https://stately.ai/docs/context 'Context — persistent state and assign'
|
|
704
|
+
[7]: https://stately.ai/docs/transitions 'Transitions — reenter, root-level routing'
|
|
705
|
+
[8]: https://stately.ai/docs/parent-states 'Parent states — root-level event handling'
|
|
706
|
+
[9]: https://stately.ai/docs/finite-states 'Finite states — state IDs'
|
|
707
|
+
[10]: https://stately.ai/docs/setup 'Setup — typed machine setup'
|
|
708
|
+
[11]: https://stately.ai/docs/actors 'Actors — typed actor contracts'
|
|
709
|
+
[12]: https://stately.ai/docs/parallel-states 'Parallel states — concurrent regions and onDone joins'
|