@orkestrel/workflow 0.0.1
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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/src/browser/index.d.ts +279 -0
- package/dist/src/browser/index.js +399 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/core/index.cjs +2805 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +3179 -0
- package/dist/src/core/index.d.ts +3179 -0
- package/dist/src/core/index.js +2734 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/server/index.cjs +129 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +93 -0
- package/dist/src/server/index.d.ts +93 -0
- package/dist/src/server/index.js +127 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +111 -0
|
@@ -0,0 +1,3179 @@
|
|
|
1
|
+
import { AbortInterface } from '@orkestrel/abort';
|
|
2
|
+
import { AgentInterface } from '@orkestrel/agent';
|
|
3
|
+
import { ArrayShape } from '@orkestrel/contract';
|
|
4
|
+
import { BudgetInterface } from '@orkestrel/budget';
|
|
5
|
+
import { ContractInterface } from '@orkestrel/contract';
|
|
6
|
+
import { DriverInterface } from '@orkestrel/database';
|
|
7
|
+
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
8
|
+
import { EmitterHooks } from '@orkestrel/emitter';
|
|
9
|
+
import { EmitterInterface } from '@orkestrel/emitter';
|
|
10
|
+
import { LiteralShape } from '@orkestrel/contract';
|
|
11
|
+
import { NumberShape } from '@orkestrel/contract';
|
|
12
|
+
import { ObjectShape } from '@orkestrel/contract';
|
|
13
|
+
import { OptionalShape } from '@orkestrel/contract';
|
|
14
|
+
import { Result } from '@orkestrel/contract';
|
|
15
|
+
import { StringShape } from '@orkestrel/contract';
|
|
16
|
+
import { TableInterface } from '@orkestrel/database';
|
|
17
|
+
import { TokenUsage } from '@orkestrel/budget';
|
|
18
|
+
import { ToolInterface } from '@orkestrel/agent';
|
|
19
|
+
import { ToolManagerInterface } from '@orkestrel/agent';
|
|
20
|
+
import { UnionShape } from '@orkestrel/contract';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The ancestry identifier of an agent in a run chain — `agent:<name>`.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* The agent counterpart of {@link workflowTag}: the runner adds one when it dispatches an
|
|
27
|
+
* `agent` task, and rejects the task (a typed `DEPTH` `task.fail`) when the same tag is
|
|
28
|
+
* already in the ancestry (a re-entry cycle). The `agent:` namespace keeps it distinct
|
|
29
|
+
* from a same-string workflow id.
|
|
30
|
+
*
|
|
31
|
+
* @param name - The agent's registry name (the `agent`-form's `name`)
|
|
32
|
+
* @returns The namespaced ancestry tag (`agent:<name>`)
|
|
33
|
+
*/
|
|
34
|
+
export declare function agentTag(name: string): string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Assert that a {@link WorkflowSnapshot} carries a `boolean` `bail` — at the workflow tier AND
|
|
38
|
+
* on every phase — and that its every node's status (and its `override`, when present) is drawn
|
|
39
|
+
* from the lifecycle vocabulary, throwing a `RESTORE` {@link WorkflowError} otherwise.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* The boundary-narrowing guard (AGENTS §14) for {@link restoreWorkflow}: a snapshot is
|
|
43
|
+
* untrusted JSON, so a status (or an override) outside
|
|
44
|
+
* {@link import('./constants.js').WORKFLOW_STATUSES} /
|
|
45
|
+
* {@link import('./constants.js').PHASE_STATUSES} / {@link import('./constants.js').TASK_STATUSES},
|
|
46
|
+
* or a non-boolean `bail` (the workflow's OR any phase's — both are REQUIRED persisted policy),
|
|
47
|
+
* is rejected loudly (naming the offending node) rather than silently producing a broken tree.
|
|
48
|
+
* The `override` is optional, so it is only checked WHEN present. Structural shape beyond these
|
|
49
|
+
* fields is the contract's concern; this guards exactly the fields the live state machine reads back.
|
|
50
|
+
*
|
|
51
|
+
* @param snapshot - The snapshot to validate
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertSnapshot(snapshot: WorkflowSnapshot): void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a {@link PhaseContext} — a phase's own identity plus a back-reference to its
|
|
57
|
+
* workflow — from the parent {@link WorkflowContext} and the phase node's identity.
|
|
58
|
+
*
|
|
59
|
+
* @param workflow - The parent workflow context (the lineage pointer UP the tree)
|
|
60
|
+
* @param node - The phase's identity (`id` / `name` / optional `description`)
|
|
61
|
+
* @returns The {@link PhaseContext}
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildPhaseContext(workflow: WorkflowContext, node: WorkflowContext): PhaseContext;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build a {@link TaskContext} — a task's own identity plus a back-reference to its phase
|
|
67
|
+
* (and, transitively, its workflow) — from the parent {@link PhaseContext} and the task
|
|
68
|
+
* node's identity.
|
|
69
|
+
*
|
|
70
|
+
* @param phase - The parent phase context (carrying the full lineage UP the tree)
|
|
71
|
+
* @param node - The task's identity (`id` / `name` / optional `description`)
|
|
72
|
+
* @returns The {@link TaskContext}
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildTaskContext(phase: PhaseContext, node: WorkflowContext): TaskContext;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build a {@link WorkflowContext} — the identity every level inherits — from a node's
|
|
78
|
+
* `id` / `name` / optional `description`.
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* The root of the context chain a live {@link import('./Workflow.js').Workflow} exposes;
|
|
82
|
+
* {@link buildPhaseContext} / {@link buildTaskContext} extend it down the tree. Accepts a
|
|
83
|
+
* structural node (a definition or a snapshot node — both carry the three identity fields).
|
|
84
|
+
*
|
|
85
|
+
* @param node - The node's identity (`id` / `name` / optional `description`)
|
|
86
|
+
* @returns The {@link WorkflowContext}
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildWorkflowContext(node: WorkflowContext): WorkflowContext;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Test whether the live W-b task state machine may move directly from one
|
|
92
|
+
* {@link TaskStatus} to another — the legal-transition guard.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Reads the {@link import('./constants.js').TASK_TRANSITIONS} graph: `true` only when
|
|
96
|
+
* `to` is listed under `from`. A settled (terminal) `from` has no legal targets, so any
|
|
97
|
+
* transition off it is `false`. The W-b `Task` consults this before every transition and
|
|
98
|
+
* throws a `TRANSITION` {@link import('./errors.js').WorkflowError} when it returns `false`.
|
|
99
|
+
*
|
|
100
|
+
* @param from - The task's current status
|
|
101
|
+
* @param to - The status the transition would move it to
|
|
102
|
+
* @returns `true` when the move is legal
|
|
103
|
+
*/
|
|
104
|
+
export declare function canTransitionTask(from: TaskStatus, to: TaskStatus): boolean;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Flatten a nested list of per-phase {@link TaskResult} lists into one positional list
|
|
108
|
+
* — the workflow tier of the result tree, built from each phase's `results()`.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Pure and order-preserving: phases in order, each phase's task results in order. The
|
|
112
|
+
* W-b `Workflow.results()` calls this over its phases' `results()`; a phase's own
|
|
113
|
+
* `results()` is the per-phase list this consumes.
|
|
114
|
+
*
|
|
115
|
+
* @param phases - The per-phase result lists, in phase order
|
|
116
|
+
* @returns One flattened {@link TaskResult} list, in positional order
|
|
117
|
+
*/
|
|
118
|
+
export declare function collectResults(phases: readonly (readonly TaskResult[])[]): readonly TaskResult[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize
|
|
122
|
+
* any MISSING `id` deterministically + positionally, and default any MISSING `name` to
|
|
123
|
+
* its (now-resolved) `id`.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* The positional id scheme is stable and human-legible: the workflow is `wf`, phase `i`
|
|
127
|
+
* is `phase-<i>`, and task `j` of that phase is `<phaseId>-task-<j>` (so a provided phase
|
|
128
|
+
* id flows into its tasks' synthesized ids). A PROVIDED `id` / `name` at any level is kept
|
|
129
|
+
* VERBATIM — synthesis touches only the omitted ones. A missing `name` defaults to the
|
|
130
|
+
* resolved `id` (never the other way round), so the result always has both. `run`,
|
|
131
|
+
* `description`, the per-phase `concurrency` / `bail`, the per-task `retries` / `timeout`, and
|
|
132
|
+
* the workflow `bail` carry over unchanged. The result is a complete
|
|
133
|
+
* {@link WorkflowDefinition}; the caller still validates it against the STRICT contract.
|
|
134
|
+
*
|
|
135
|
+
* @param draft - The draft workflow (id/name optional at all three levels)
|
|
136
|
+
* @returns A complete {@link WorkflowDefinition} with every id/name filled
|
|
137
|
+
*/
|
|
138
|
+
export declare function completeDraft(draft: WorkflowDraft): WorkflowDefinition;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Complete one {@link PhaseDraft} into a strict {@link PhaseDefinition} — the per-phase
|
|
142
|
+
* step of {@link completeDraft} (phase `index` → `phase-<index>` when its id is omitted).
|
|
143
|
+
*
|
|
144
|
+
* @param phase - The draft phase
|
|
145
|
+
* @param index - The phase's positional index in the workflow
|
|
146
|
+
* @returns A complete {@link PhaseDefinition}
|
|
147
|
+
*/
|
|
148
|
+
export declare function completePhaseDraft(phase: PhaseDraft, index: number): PhaseDefinition;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Complete one {@link TaskDraft} into a strict {@link TaskDefinition} — the per-task leaf
|
|
152
|
+
* step of {@link completeDraft} (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>`
|
|
153
|
+
* when its id is omitted).
|
|
154
|
+
*
|
|
155
|
+
* @param task - The draft task
|
|
156
|
+
* @param phaseId - The (resolved) parent phase id, so the synthesized task id nests under it
|
|
157
|
+
* @param index - The task's positional index within its phase
|
|
158
|
+
* @returns A complete {@link TaskDefinition}
|
|
159
|
+
*/
|
|
160
|
+
export declare function completeTaskDraft(task: TaskDraft, phaseId: string, index: number): TaskDefinition;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The per-unit handle a runner handler receives — wraps the unit's identity,
|
|
164
|
+
* input, cancellation, and the run controls (`wait` / `spawn` / `abort`).
|
|
165
|
+
*
|
|
166
|
+
* @remarks
|
|
167
|
+
* - **Built by the Runner per unit.** The runner constructs one `Controller` per
|
|
168
|
+
* unit it dispatches, handing it the unit's `id`, `input`, the unit's `Abort`
|
|
169
|
+
* handle, the queue attempt's `signal`, and a `spawn` callback that launches a
|
|
170
|
+
* sibling through the same queue.
|
|
171
|
+
* - **Signal.** `signal` is the queue attempt's signal, which ANY-combines the
|
|
172
|
+
* unit's own abort, the runner-level abort (the runner aborts every unit), and
|
|
173
|
+
* the per-attempt timeout — so it fires on any of the three. `aborted` and
|
|
174
|
+
* `abort(reason)` delegate to the unit's `Abort` (the cancellation source of
|
|
175
|
+
* truth); since the attempt signal ANY-includes that abort, `abort()` fires
|
|
176
|
+
* `signal` too.
|
|
177
|
+
* - **`wait` promise-parks (never a timer).** It resolves the instant the unit's
|
|
178
|
+
* `signal` fires (immediately if already aborted) via a one-shot listener — no
|
|
179
|
+
* `setTimeout`, no polling, no busy-yield — so a parked unit costs no CPU.
|
|
180
|
+
* - **`spawn` is fire-and-track.** It delegates to the runner's launch-a-sibling
|
|
181
|
+
* callback, which routes the sibling through the queue; the runner's `execute`
|
|
182
|
+
* awaits the spawn closure, so the sibling runs whether or not its promise is
|
|
183
|
+
* awaited. (Inline-awaiting a spawn from a slot-holding handler on a bounded
|
|
184
|
+
* runner can deadlock — fan out instead; see {@link ControllerInterface.spawn}.)
|
|
185
|
+
* - **Event-free by design.** The per-unit handle carries no Emitter; observe the
|
|
186
|
+
* {@link RunnerInterface.emitter} instead (`unit` / `spawn` / `settle` / `fail` carry the id).
|
|
187
|
+
*/
|
|
188
|
+
export declare class Controller<TInput, TResult> implements ControllerInterface<TInput, TResult> {
|
|
189
|
+
#private;
|
|
190
|
+
readonly id: string;
|
|
191
|
+
readonly input: TInput;
|
|
192
|
+
readonly signal: AbortSignal;
|
|
193
|
+
constructor(id: string, input: TInput, abort: AbortInterface, signal: AbortSignal, spawn: (input: TInput) => Promise<TResult>);
|
|
194
|
+
get aborted(): boolean;
|
|
195
|
+
wait(): Promise<void>;
|
|
196
|
+
spawn(input: TInput): Promise<TResult>;
|
|
197
|
+
abort(reason?: unknown): void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The per-unit handle a {@link RunnerHandler} receives — the running unit's
|
|
202
|
+
* identity, input, cancellation, and the controls to cooperate with the run.
|
|
203
|
+
*
|
|
204
|
+
* @remarks
|
|
205
|
+
* One `Controller` is built per unit the {@link RunnerInterface} runs (a declared
|
|
206
|
+
* input or a `spawn`ed sibling). It exposes:
|
|
207
|
+
* - `id` — the unit's identifier (a random UUID).
|
|
208
|
+
* - `input` — the unit's work payload.
|
|
209
|
+
* - `signal` — the unit's cancellation: fires on the unit's own `abort()`, a
|
|
210
|
+
* runner-level `abort` (the runner aborts every unit), or this attempt's timeout
|
|
211
|
+
* expiring (it reflects the underlying queue attempt's signal, which ANY-combines
|
|
212
|
+
* all three).
|
|
213
|
+
* - `aborted` — whether the unit's cancellation has fired.
|
|
214
|
+
*
|
|
215
|
+
* @typeParam TInput - The unit's work input
|
|
216
|
+
* @typeParam TResult - The value a unit resolves
|
|
217
|
+
*/
|
|
218
|
+
export declare interface ControllerInterface<TInput, TResult> {
|
|
219
|
+
readonly id: string;
|
|
220
|
+
readonly input: TInput;
|
|
221
|
+
/** Fires on the unit's own `abort()`, a runner-level abort, or this attempt's timeout. */
|
|
222
|
+
readonly signal: AbortSignal;
|
|
223
|
+
readonly aborted: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Park until this unit's `signal` aborts — **promise-parked**, never a timer.
|
|
226
|
+
*
|
|
227
|
+
* @remarks
|
|
228
|
+
* Resolves the moment the unit's `signal` fires (unit abort, runner abort, or
|
|
229
|
+
* timeout) and resolves immediately if it has already fired. It registers a
|
|
230
|
+
* one-shot `'abort'` listener and never polls — no `setTimeout`, no `delay`, no
|
|
231
|
+
* busy-yield — so a parked unit consumes no CPU until it is actually cancelled.
|
|
232
|
+
*
|
|
233
|
+
* @returns A promise that resolves once the unit's `signal` aborts
|
|
234
|
+
*/
|
|
235
|
+
wait(): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Add a sibling unit to the run; returns its result promise.
|
|
238
|
+
*
|
|
239
|
+
* @remarks
|
|
240
|
+
* **Fire-and-track.** The spawned unit is routed through the same backing queue
|
|
241
|
+
* as every declared unit, so it actually runs (in FIFO wake order) and its result
|
|
242
|
+
* joins the run's ordered output after the declared units, in spawn order. The
|
|
243
|
+
* runner's `execute` awaits the full transitive spawn closure (it tracks an
|
|
244
|
+
* outstanding-unit count, not a one-time snapshot), so a caller does NOT need to
|
|
245
|
+
* await the returned promise to make the sibling run.
|
|
246
|
+
*
|
|
247
|
+
* **Deadlock caveat.** On a bounded-`concurrency` runner, do NOT `await` a
|
|
248
|
+
* `spawn`ed promise *inline* from within a handler — the handler holds a queue
|
|
249
|
+
* slot while it awaits, and if every slot is held by a handler awaiting its own
|
|
250
|
+
* spawn, no slot is free to run the spawns and the run deadlocks. The intended
|
|
251
|
+
* pattern is fan-out: `spawn` siblings and return, letting the runner drain them.
|
|
252
|
+
*
|
|
253
|
+
* @param input - The sibling unit's work payload
|
|
254
|
+
* @returns The sibling's result promise (it runs regardless of whether it's awaited)
|
|
255
|
+
*/
|
|
256
|
+
spawn(input: TInput): Promise<TResult>;
|
|
257
|
+
/**
|
|
258
|
+
* Cancel this unit — fires its `signal` with the optional reason.
|
|
259
|
+
*
|
|
260
|
+
* @param reason - An optional cancellation reason carried on the signal
|
|
261
|
+
*/
|
|
262
|
+
abort(reason?: unknown): void;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a {@link DatabaseWorkflowStore} over any {@link DriverInterface} — the durable,
|
|
267
|
+
* driver-pluggable backing for the W-d persistence seam, the opt-in twin of
|
|
268
|
+
* {@link createMemoryWorkflowStore}.
|
|
269
|
+
*
|
|
270
|
+
* @remarks
|
|
271
|
+
* Builds a one-table database (`snapshots`, keyed by `id`) over the supplied driver, the snapshot
|
|
272
|
+
* held as ONE OPAQUE JSON COLUMN — the column map is `{ id; snapshot }` where `snapshot` is a
|
|
273
|
+
* `rawShape` (a JSON blob), exactly as {@link createDatabaseQueueStore} stores its `input`. The
|
|
274
|
+
* snapshot is already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless
|
|
275
|
+
* AND keeps the row type FLAT — a structured multi-column snapshot table would force the contract to
|
|
276
|
+
* `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results) and trip TS2589;
|
|
277
|
+
* the opaque column sidesteps it (the column reads back as `unknown`, narrowed on `get` by
|
|
278
|
+
* {@link import('./helpers.js').isWorkflowSnapshot}). The `driver` DEFAULTS to
|
|
279
|
+
* {@link createMemoryDriver}, so the store ALSO works in memory out of the box; pass a server
|
|
280
|
+
* `createJSONDriver` / `createSQLiteDriver` (or a browser IndexedDB driver) for a persistent one —
|
|
281
|
+
* the durability is the driver's job, the store engine is shared. It swaps in behind
|
|
282
|
+
* {@link WorkflowStoreInterface} WITHOUT touching the runner or the entity tree.
|
|
283
|
+
*
|
|
284
|
+
* @param driver - The storage backend the snapshots persist to (defaults to {@link createMemoryDriver})
|
|
285
|
+
* @returns A {@link WorkflowStoreInterface} over the driver
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* import { createDatabaseWorkflowStore, createMemoryDriver, createWorkflow, restoreWorkflow } from '@src/core'
|
|
290
|
+
*
|
|
291
|
+
* const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
|
|
292
|
+
* const workflow = createWorkflow(definition)
|
|
293
|
+
* await store.set(workflow.snapshot()) // persist the run state (one JSON column)
|
|
294
|
+
* const snapshot = await store.get(definition.id)
|
|
295
|
+
* const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
export declare function createDatabaseWorkflowStore(driver?: DriverInterface): WorkflowStoreInterface;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Create a {@link DeferredInterface} — a promise whose settlement is driven
|
|
302
|
+
* externally, so a caller can resolve/reject it from outside the executor.
|
|
303
|
+
*
|
|
304
|
+
* @typeParam T - The value the deferred promise resolves
|
|
305
|
+
* @returns A deferred `promise` plus its `resolve` / `reject`
|
|
306
|
+
*/
|
|
307
|
+
export declare function createDeferred<T>(): DeferredInterface<T>;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
|
|
311
|
+
* {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the DEFAULT
|
|
312
|
+
* backend behind the W-d persistence seam.
|
|
313
|
+
*
|
|
314
|
+
* @remarks
|
|
315
|
+
* The snapshot analogue of the server package's `createMemorySessionStore`
|
|
316
|
+
* (and the {@link createMemoryQueueStore} family), but LEANER — there is no idle-TTL, so no
|
|
317
|
+
* options bag (AGENTS §21 minimal): a persisted run-state lives until an explicit `delete`. This is
|
|
318
|
+
* the zero-plumbing DEFAULT (a plain `Map`); its driver-pluggable twin is
|
|
319
|
+
* {@link createDatabaseWorkflowStore} (the snapshot as one opaque JSON column over a `databases`
|
|
320
|
+
* table) — for a DURABLE store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
|
|
321
|
+
* driver, and it swaps in WITHOUT touching the runner or the entity tree. Restore stays a caller
|
|
322
|
+
* concern: read a snapshot back and rebuild the live tree with {@link restoreWorkflow}.
|
|
323
|
+
*
|
|
324
|
+
* @returns A memory-backed {@link WorkflowStoreInterface}
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@src/core'
|
|
329
|
+
*
|
|
330
|
+
* const store = createMemoryWorkflowStore()
|
|
331
|
+
* const workflow = createWorkflow(definition)
|
|
332
|
+
* await store.set(workflow.snapshot()) // persist the run state
|
|
333
|
+
* const snapshot = await store.get(definition.id)
|
|
334
|
+
* const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
export declare function createMemoryWorkflowStore(): WorkflowStoreInterface;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Create a thin generic orchestrator that drives declared units — and any they
|
|
341
|
+
* `spawn` — through a bounded-concurrency queue, collecting their results in order.
|
|
342
|
+
*
|
|
343
|
+
* @remarks
|
|
344
|
+
* The Runner composes the workers `Queue` for backpressure, FIFO ordering, bounded
|
|
345
|
+
* concurrency, retries, and the per-attempt timeout — it adds only orchestration, not
|
|
346
|
+
* a second concurrency engine. `execute(inputs)` runs the unit set ONCE (a second call
|
|
347
|
+
* throws) and resolves the units' results in order: the declared inputs first, then
|
|
348
|
+
* any `spawn`ed siblings in spawn order. Each unit's handler gets a `Controller` — its
|
|
349
|
+
* `id` / `input`, a `signal` that fires on the unit's `abort`, a runner-level `abort`,
|
|
350
|
+
* or the attempt's timeout, a promise-parked `wait()`, and `spawn(input)` to fan out
|
|
351
|
+
* sibling units. The run is **fail-fast**: the first unit failure (after retries)
|
|
352
|
+
* aborts every other unit and rejects `execute` with that error. **Observable (§13):** a
|
|
353
|
+
* typed `emitter` surfaces `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort`.
|
|
354
|
+
*
|
|
355
|
+
* Because `spawn` is fire-and-track (the runner awaits the whole spawn closure via an
|
|
356
|
+
* outstanding-unit count, not a one-time snapshot), a handler need NOT await its spawns
|
|
357
|
+
* for them to run — and on a bounded runner it should NOT `await` a spawn inline (a
|
|
358
|
+
* slot-holding handler awaiting its own spawn can deadlock); fan out and return instead.
|
|
359
|
+
*
|
|
360
|
+
* @typeParam TInput - The work input each unit carries
|
|
361
|
+
* @typeParam TResult - The value a unit's handler resolves
|
|
362
|
+
* @param options - The `handler` plus optional `concurrency` (default `1`), `retries`
|
|
363
|
+
* (default `0`), and a default per-attempt `timeout` in milliseconds
|
|
364
|
+
* @returns A working {@link RunnerInterface}
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* import { createRunner } from '@src/core'
|
|
369
|
+
*
|
|
370
|
+
* // A handler that fans out one sibling per declared unit, then returns its own value.
|
|
371
|
+
* const runner = createRunner<number, number>({
|
|
372
|
+
* concurrency: 4,
|
|
373
|
+
* handler: (controller) => {
|
|
374
|
+
* if (controller.input < 10) controller.spawn(controller.input + 100) // fire-and-track
|
|
375
|
+
* return controller.input
|
|
376
|
+
* },
|
|
377
|
+
* })
|
|
378
|
+
*
|
|
379
|
+
* const results = await runner.execute([1, 2, 3])
|
|
380
|
+
* // [1, 2, 3, 101, 102, 103] — declared inputs first (in order), then spawns (in order)
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
export declare function createRunner<TInput, TResult>(options: RunnerOptions<TInput, TResult>): RunnerInterface<TInput, TResult>;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Create the safe cross-environment cooperative-yield default — a
|
|
387
|
+
* {@link SchedulerInterface} built on `setTimeout` / `clearTimeout` alone, so it
|
|
388
|
+
* runs unchanged in both the browser and Node.
|
|
389
|
+
*
|
|
390
|
+
* @remarks
|
|
391
|
+
* `yield()` gives the host a turn via a zero-delay macrotask (so pending I/O,
|
|
392
|
+
* timers, and rendering actually run — a microtask would not); `delay(ms)` resumes
|
|
393
|
+
* after at least `ms`. Pass `options.signal` to make a pending yield/delay reject
|
|
394
|
+
* with the signal's `reason` on abort (with full timer/listener cleanup).
|
|
395
|
+
* `options.priority` is accepted for contract compliance but treated uniformly by
|
|
396
|
+
* this default — environment backends honour it.
|
|
397
|
+
*
|
|
398
|
+
* @returns A working {@link SchedulerInterface}
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* import { createAbort, createScheduler } from '@src/core'
|
|
403
|
+
*
|
|
404
|
+
* const abort = createAbort()
|
|
405
|
+
* const scheduler = createScheduler()
|
|
406
|
+
*
|
|
407
|
+
* // A cooperative loop: do a unit of work, then hand the host a turn.
|
|
408
|
+
* while (!abort.signal.aborted) {
|
|
409
|
+
* doSomeWork()
|
|
410
|
+
* await scheduler.yield({ signal: abort.signal })
|
|
411
|
+
* }
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```ts
|
|
416
|
+
* import { createScheduler } from '@src/core'
|
|
417
|
+
*
|
|
418
|
+
* // A backoff: wait a growing interval between retries.
|
|
419
|
+
* const scheduler = createScheduler()
|
|
420
|
+
* for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
421
|
+
* if (await tryOnce()) break
|
|
422
|
+
* await scheduler.delay(2 ** attempt * 100)
|
|
423
|
+
* }
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
export declare function createScheduler(): SchedulerInterface;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Build the live W-b entity tree from a {@link WorkflowDefinition} — the whole
|
|
430
|
+
* {@link WorkflowInterface} → {@link import('./types.js').PhaseInterface} →
|
|
431
|
+
* {@link import('./types.js').TaskInterface} tree, each level wired with its lineage
|
|
432
|
+
* context, its emitter, and the cascade.
|
|
433
|
+
*
|
|
434
|
+
* @remarks
|
|
435
|
+
* The definition is the DECLARATIVE blueprint; this seeds an initial all-`pending`
|
|
436
|
+
* {@link WorkflowSnapshot} from it ({@link definitionToSnapshot}) and constructs the live
|
|
437
|
+
* tree over that one path. The `bail` failure policy resolves to `options.bail`, else the
|
|
438
|
+
* definition's `bail`, else the graceful {@link import('./constants.js').DEFAULT_BAIL}; it
|
|
439
|
+
* feeds {@link import('./helpers.js').deriveWorkflowStatus}. Per-phase / per-task initial
|
|
440
|
+
* listeners + metadata travel through `options.phases[id].on` /
|
|
441
|
+
* `options.phases[id].tasks[id]` (the AGENTS §8 nested-by-id bag). The W-b tree is the
|
|
442
|
+
* state machine ONLY — it does not execute tasks (W-c drives the transitions).
|
|
443
|
+
*
|
|
444
|
+
* @param definition - The workflow definition to bring to life
|
|
445
|
+
* @param options - Runtime options (initial listeners, `bail` override, per-node options)
|
|
446
|
+
* @returns The live {@link WorkflowInterface} root
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* import { createWorkflow } from '@src/core'
|
|
451
|
+
*
|
|
452
|
+
* const workflow = createWorkflow(definition, { on: { complete: () => done() } })
|
|
453
|
+
* const phase = workflow.phase('phase-build')
|
|
454
|
+
* phase?.task('task-compile')?.start() // pending → running (cascades up)
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
export declare function createWorkflow(definition: WorkflowDefinition, options?: WorkflowOptions): WorkflowInterface;
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Compile the workflow definition contract — the JSON Schema, guard, parser, and
|
|
461
|
+
* seeded generator for a {@link WorkflowDefinition}, all derived from one shape and
|
|
462
|
+
* kept in lockstep.
|
|
463
|
+
*
|
|
464
|
+
* @remarks
|
|
465
|
+
* The returned {@link ContractInterface}:
|
|
466
|
+
* - `schema` — the emitted JSON Schema for a workflow definition.
|
|
467
|
+
* - `is` — a total guard that narrows `unknown` to a valid {@link WorkflowDefinition}
|
|
468
|
+
* (malformed input returns `false`, never throws).
|
|
469
|
+
* - `parse` — coerces `unknown` to a {@link WorkflowDefinition}, or `undefined` when
|
|
470
|
+
* it does not match.
|
|
471
|
+
* - `generate` — produces a deterministic valid {@link WorkflowDefinition} from an
|
|
472
|
+
* optional seeded random source.
|
|
473
|
+
*
|
|
474
|
+
* @returns The compiled {@link WorkflowDefinition} contract
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```ts
|
|
478
|
+
* import { createWorkflowContract } from '@src/core'
|
|
479
|
+
*
|
|
480
|
+
* const contract = createWorkflowContract()
|
|
481
|
+
* const definition = contract.generate() // a valid WorkflowDefinition
|
|
482
|
+
* contract.is(definition) // true
|
|
483
|
+
* contract.parse({ id: '', phases: [] }) // undefined (malformed)
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
export declare function createWorkflowContract(): ContractInterface<WorkflowDefinition>;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Compile the LENIENT workflow DRAFT contract — identical to
|
|
490
|
+
* {@link createWorkflowContract} EXCEPT `id` and `name` are OPTIONAL at all three levels
|
|
491
|
+
* (workflow / phase / task), so a small model can omit the six identity strings.
|
|
492
|
+
*
|
|
493
|
+
* @remarks
|
|
494
|
+
* The widened authoring surface {@link createWorkflowTool} parses an authored blob through
|
|
495
|
+
* before {@link import('./helpers.js').completeDraft} fills the missing ids/names. It does
|
|
496
|
+
* NOT relax the canonical contract — {@link createWorkflowContract} stays byte-for-byte
|
|
497
|
+
* unchanged and STRICT, and the completed draft is re-validated against THAT strict gate
|
|
498
|
+
* before running (soundness preserved). A PROVIDED `id` / `name` still carries `minLength: 1`,
|
|
499
|
+
* so an explicitly-empty `id: ''` is REJECTED (parses to `undefined`), never auto-filled —
|
|
500
|
+
* keeping "garbage" distinct from "omitted". `run` stays required.
|
|
501
|
+
*
|
|
502
|
+
* @returns The compiled {@link WorkflowDraft} contract
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```ts
|
|
506
|
+
* import { createWorkflowDraftContract, completeDraft } from '@src/core'
|
|
507
|
+
*
|
|
508
|
+
* const draft = createWorkflowDraftContract()
|
|
509
|
+
* const parsed = draft.parse({ phases: [{ tasks: [{ run: { via: 'function', name: 'f' } }] }] })
|
|
510
|
+
* const definition = parsed && completeDraft(parsed) // ids/names filled positionally
|
|
511
|
+
* draft.parse({ id: '', phases: [] }) // undefined — an explicit empty id is rejected
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
export declare function createWorkflowDraftContract(): ContractInterface<WorkflowDraft>;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Create a workflow runner — a {@link WorkflowRunnerInterface} that EXECUTES a live W-b
|
|
518
|
+
* workflow tree by COMPOSING the shipped substrate: phases sequential, tasks concurrent,
|
|
519
|
+
* each task dispatched BY NAME under the workflow's `bail` policy.
|
|
520
|
+
*
|
|
521
|
+
* @remarks
|
|
522
|
+
* The runner is THIN — it re-implements no concurrency / retry / abort logic. Per-phase
|
|
523
|
+
* bounded concurrency is one {@link createRunner} per phase;
|
|
524
|
+
* `bail` maps onto that Runner's fail-fast (`true` — the first failure aborts the in-flight
|
|
525
|
+
* siblings + skips the rest) vs settle-all (`false` — failures are recorded, the run
|
|
526
|
+
* finishes); the run-level abort / timeout / budget ({@link import('./types.js').WorkflowRunOptions})
|
|
527
|
+
* fold through `AbortSignal.any` (the agent runtime's pattern); pacing is the shipped
|
|
528
|
+
* scheduler. `execute(definition, options?)` BUILDS the live tree from the definition itself
|
|
529
|
+
* (via {@link createWorkflow} — one source of truth, returned in `WorkflowResult.workflow`),
|
|
530
|
+
* drives the live entity (`start` → `complete` / `fail`), and resolves a
|
|
531
|
+
* {@link import('./types.js').WorkflowResult}.
|
|
532
|
+
*
|
|
533
|
+
* A task is dispatched on its {@link import('./types.js').TaskForm}: `function` → the
|
|
534
|
+
* `functions` registry, `tool` → the `tools` {@link ToolManagerInterface}, `agent` → the
|
|
535
|
+
* `agents` {@link import('./types.js').WorkflowAgents} resolver (W-c2), behind a depth + cycle
|
|
536
|
+
* guard. A task whose handler is NOT found (an unregistered name for ANY form) AUTO-COMPLETES
|
|
537
|
+
* (the ROADMAP no-handler rule).
|
|
538
|
+
*
|
|
539
|
+
* The runner is constructed with a reference to {@link createWorkflowTool} (the workflow-tool
|
|
540
|
+
* binder) so it can BIND a depth/cycle-aware workflow tool onto a dispatched subagent's context
|
|
541
|
+
* — the propagation seam — WITHOUT this module's classes importing its own `factories.ts` (the
|
|
542
|
+
* factories→classes direction; the binder is injected as a value at construction).
|
|
543
|
+
*
|
|
544
|
+
* @param options - The behavior registries (`functions` / `tools` / `agents`) the runner
|
|
545
|
+
* dispatches a task by name through, plus an optional pacing `scheduler` (default the shipped
|
|
546
|
+
* cross-environment one). Omitting `functions` / `tools` / `agents` makes those task forms
|
|
547
|
+
* auto-complete (no handler). See {@link WorkflowRunnerOptions}.
|
|
548
|
+
* @returns A working {@link WorkflowRunnerInterface}
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```ts
|
|
552
|
+
* import { createWorkflowRunner, createToolManager } from '@src/core'
|
|
553
|
+
*
|
|
554
|
+
* const tools = createToolManager()
|
|
555
|
+
* const runner = createWorkflowRunner({
|
|
556
|
+
* functions: { compile: async (controller) => `built ${controller.task.id}` },
|
|
557
|
+
* tools,
|
|
558
|
+
* })
|
|
559
|
+
* const definition = { id: 'w', name: 'W', phases: [{ id: 'p', name: 'P', tasks: [
|
|
560
|
+
* { id: 't', name: 'T', run: { via: 'function', name: 'compile' } },
|
|
561
|
+
* ] }] }
|
|
562
|
+
* const result = await runner.execute(definition) // builds + drives the tree
|
|
563
|
+
* result.status // 'completed'
|
|
564
|
+
* result.workflow.phase('p')?.task('t')?.status // 'completed'
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
export declare function createWorkflowRunner(options?: WorkflowRunnerOptions): WorkflowRunnerInterface;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Wrap a {@link WorkflowDefinition} as an LLM-callable {@link ToolInterface} — it ADVERTISES
|
|
571
|
+
* the SIMPLE flat authoring shape (`{ name?, steps: [{ name, via? }] }`) as its `parameters` so
|
|
572
|
+
* even a small model can author a complete tree, and its handler EXPANDS / COMPLETES the
|
|
573
|
+
* authored blob, validates it against the STRICT contract, runs it through `runner`, and
|
|
574
|
+
* returns the run SUMMARY (throwing a typed {@link WorkflowError} on failure).
|
|
575
|
+
*
|
|
576
|
+
* @remarks
|
|
577
|
+
* A plain {@link ToolManagerInterface}-compatible tool (so `createMCPServer` / `createMCPRoutes`
|
|
578
|
+
* expose it for free — nothing MCP is wired here). It is ALSO the propagation carrier the
|
|
579
|
+
* {@link WorkflowRunner} binds onto a dispatched subagent (W-c2): because a tool handler receives
|
|
580
|
+
* ONLY the model-supplied `args` (no ambient context, no signal), the run's depth + ancestry are
|
|
581
|
+
* CLOSED OVER at bind time via {@link WorkflowToolOptions}, and the handler runs the nested
|
|
582
|
+
* workflow at `depth + 1` with the extended ancestry.
|
|
583
|
+
*
|
|
584
|
+
* **Widened authoring surface (additive — the canonical contract + runner stay STRICT and
|
|
585
|
+
* unchanged).** A 2B model reliably CALLS the tool but cannot reliably emit the full four-level
|
|
586
|
+
* nested {@link WorkflowDefinition} (six required `id`/`name` strings, a nested tagged union,
|
|
587
|
+
* all-or-nothing). So the tool ACCEPTS three authoring forms and converges them on the SAME
|
|
588
|
+
* strict {@link createWorkflowContract} gate before running (soundness preserved):
|
|
589
|
+
* - the FLAT shape `{ name?, steps: [{ name, via? }] }` — the ADVERTISED `parameters` (the simplest
|
|
590
|
+
* form, {@link import('./helpers.js').expandSteps}'d into one one-task phase per step);
|
|
591
|
+
* - a nested DRAFT with any `id`/`name` OMITTED — {@link createWorkflowDraftContract}-parsed then
|
|
592
|
+
* {@link import('./helpers.js').completeDraft}'d (missing ids synthesized positionally);
|
|
593
|
+
* - the full nested {@link WorkflowDefinition} — the advanced escape-hatch (documented in the
|
|
594
|
+
* description), accepted as the draft super-set.
|
|
595
|
+
*
|
|
596
|
+
* The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the PLAIN
|
|
597
|
+
* run-summary VALUE on success and THROWS a typed {@link WorkflowError} on every failure path. It
|
|
598
|
+
* does NOT build a {@link ToolResult} itself — the `@orkestrel/agent` package's `ToolManager`
|
|
599
|
+
* performs the ONE canonical wrap (`{ id, name, value }` on a return; `{ id, name, error }` on a
|
|
600
|
+
* throw, ISOLATED so nothing escapes the run), so the outcome appears EXACTLY ONCE, identically,
|
|
601
|
+
* over BOTH the agent loop and MCP (a throw → MCP `isError: true`):
|
|
602
|
+
* - **No authored args** (an empty `arguments`) ⇒ runs the WRAPPED `definition`.
|
|
603
|
+
* - **A `steps` array** ⇒ the FLAT form: parse it, {@link import('./helpers.js').expandSteps} it.
|
|
604
|
+
* - **Otherwise** ⇒ the nested form: {@link createWorkflowDraftContract}-parse it,
|
|
605
|
+
* {@link import('./helpers.js').completeDraft} it.
|
|
606
|
+
* - **Strict gate** ⇒ the expanded / completed result is validated against
|
|
607
|
+
* {@link createWorkflowContract}.`is`; a blob that can't expand, or whose result fails the strict
|
|
608
|
+
* gate (e.g. an explicit empty `id`, `concurrency: 0`) ⇒ THROW a `TOOL` {@link WorkflowError} (no run).
|
|
609
|
+
* - **Over-deep / cyclic** ⇒ THROW a `DEPTH` {@link WorkflowError} when the nested run would exceed
|
|
610
|
+
* {@link MAX_WORKFLOW_DEPTH}, or the target workflow id is already an ancestor (a cycle) — the
|
|
611
|
+
* same `code` the agent-task guard raises.
|
|
612
|
+
* - **Otherwise** ⇒ `runner.execute(target, { depth: depth + 1, ancestry: … })`, RETURNING the
|
|
613
|
+
* plain summary of the terminal run (`{ status, count }`, via {@link workflowToolSummary}).
|
|
614
|
+
*
|
|
615
|
+
* @param definition - The workflow the tool runs when called with no authored args
|
|
616
|
+
* @param runner - The {@link WorkflowRunnerInterface} that executes the (nested) workflow
|
|
617
|
+
* @param options - The depth + ancestry to run the nested workflow under (see
|
|
618
|
+
* {@link WorkflowToolOptions}); omitted ⇒ depth `0` / empty ancestry (a top-level wrap)
|
|
619
|
+
* @returns A {@link ToolInterface} (named {@link import('./constants.js').WORKFLOW_TOOL_NAME})
|
|
620
|
+
* whose `parameters` advertise the FLAT authoring schema (the nested form stays accepted)
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```ts
|
|
624
|
+
* import { createWorkflowRunner, createWorkflowTool, createToolManager } from '@src/core'
|
|
625
|
+
*
|
|
626
|
+
* const runner = createWorkflowRunner()
|
|
627
|
+
* const tool = createWorkflowTool(definition, runner)
|
|
628
|
+
* const tools = createToolManager()
|
|
629
|
+
* tools.add(tool) // a model can now author + run a workflow in one call
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
export declare function createWorkflowTool(definition: WorkflowDefinition, runner: WorkflowRunnerInterface, options?: WorkflowToolOptions): ToolInterface;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* A {@link WorkflowStoreInterface} backed by one table of the `databases` layer — a
|
|
636
|
+
* workflow's durable run-state IS a row, so persistence reduces to keyed point-access
|
|
637
|
+
* (`get` / `set` / `delete`) over a `TableInterface`, the driver-pluggable twin of the
|
|
638
|
+
* plain-`Map` {@link import('./MemoryWorkflowStore.js').MemoryWorkflowStore}.
|
|
639
|
+
*
|
|
640
|
+
* @remarks
|
|
641
|
+
* The store is driver-agnostic: it holds a single {@link TableInterface} whose backend
|
|
642
|
+
* (memory, JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a
|
|
643
|
+
* JSON / SQLite / IndexedDB backend swaps in WITHOUT touching the runner or the entity tree
|
|
644
|
+
* — the same seam as `@orkestrel/queue`'s `DatabaseQueueStore`.
|
|
645
|
+
* The driver defaults to memory ({@link import('../factories.js').createDatabaseWorkflowStore}
|
|
646
|
+
* passes `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the
|
|
647
|
+
* durable plumbing by passing a JSON / SQLite / IndexedDB driver.
|
|
648
|
+
*
|
|
649
|
+
* The {@link WorkflowSnapshot} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
|
|
650
|
+
* `{ id; snapshot }` ({@link WorkflowSnapshotRow}), the snapshot the whole JSON blob (a `rawShape`
|
|
651
|
+
* column the factory builds) — exactly as `DatabaseQueueStore` stores its `input`. The snapshot is
|
|
652
|
+
* already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless AND
|
|
653
|
+
* sidesteps a TS2589 instantiation-depth blow-up: a structured multi-column table would force the
|
|
654
|
+
* contract to `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results),
|
|
655
|
+
* tripping the compiler — one JSON column keeps the row type flat (`snapshot` reads back as `unknown`).
|
|
656
|
+
*
|
|
657
|
+
* - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
|
|
658
|
+
* the row `{ id: snapshot.id, snapshot }`.
|
|
659
|
+
* - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
|
|
660
|
+
* a {@link WorkflowSnapshot} ({@link import('../helpers.js').isWorkflowSnapshot} — the AGENTS §14
|
|
661
|
+
* boundary narrow for an untrusted storage read), or `undefined` if none is stored.
|
|
662
|
+
* - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
663
|
+
*
|
|
664
|
+
* UNLIKE the server package's `SessionStoreInterface` there is NO
|
|
665
|
+
* idle-TTL / eviction — a persisted run-state is durable orchestration state that lives until an
|
|
666
|
+
* explicit `delete`. The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the
|
|
667
|
+
* §22 method bijection with {@link WorkflowStoreInterface}). Restore stays a caller concern: read a
|
|
668
|
+
* snapshot back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* ```ts
|
|
672
|
+
* import { createDatabaseWorkflowStore, createMemoryDriver, createWorkflow, restoreWorkflow } from '@src/core'
|
|
673
|
+
*
|
|
674
|
+
* const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
|
|
675
|
+
* const workflow = createWorkflow(definition)
|
|
676
|
+
* await store.set(workflow.snapshot()) // persist the run state (one JSON column)
|
|
677
|
+
* const snapshot = await store.get(definition.id)
|
|
678
|
+
* const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
|
|
679
|
+
* await store.delete(definition.id) // drop it
|
|
680
|
+
* ```
|
|
681
|
+
*/
|
|
682
|
+
export declare class DatabaseWorkflowStore implements WorkflowStoreInterface {
|
|
683
|
+
#private;
|
|
684
|
+
/**
|
|
685
|
+
* Wrap a table as a workflow store.
|
|
686
|
+
*
|
|
687
|
+
* @param table - The {@link TableInterface} holding the snapshots — its row is the
|
|
688
|
+
* {@link WorkflowSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
|
|
689
|
+
*/
|
|
690
|
+
constructor(table: TableInterface<WorkflowSnapshotRow>);
|
|
691
|
+
/** Resolve the persisted snapshot for `id`, narrowing the opaque JSON column back to a `WorkflowSnapshot`. */
|
|
692
|
+
get(id: string): Promise<WorkflowSnapshot | undefined>;
|
|
693
|
+
/** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
|
|
694
|
+
set(snapshot: WorkflowSnapshot): Promise<void>;
|
|
695
|
+
/** Drop a snapshot by id; an absent id is a no-op (no throw). */
|
|
696
|
+
delete(id: string): Promise<void>;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** The default {@link import('./types.js').WorkflowDefinition.bail} — graceful (continue on a leaf failure). */
|
|
700
|
+
export declare const DEFAULT_BAIL = false;
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* The default per-phase task concurrency the {@link import('./factories.js').createWorkflowRunner}
|
|
704
|
+
* runner applies when a {@link import('./types.js').PhaseDefinition} omits its `concurrency`
|
|
705
|
+
* throttle — a large cap that is effectively unbounded for any realistic phase.
|
|
706
|
+
*
|
|
707
|
+
* @remarks
|
|
708
|
+
* The determinism principle fixes that a phase's tasks run CONCURRENTLY; `concurrency` is
|
|
709
|
+
* only an optional resource throttle (max-in-flight). With none declared, the runner runs
|
|
710
|
+
* all of a phase's tasks at once — modelled as this large finite cap so the value flows
|
|
711
|
+
* straight into the substrate {@link import('./types.js').RunnerInterface}'s
|
|
712
|
+
* `concurrency` (which expects a positive integer) without a special unbounded branch. No
|
|
713
|
+
* realistic phase declares enough tasks to reach it, so it behaves as "run them all".
|
|
714
|
+
*/
|
|
715
|
+
export declare const DEFAULT_PHASE_CONCURRENCY = 1000000;
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* A promise paired with its externally-callable `resolve`/`reject` — the settle path
|
|
719
|
+
* is exposed to the caller instead of being buried in an executor closure.
|
|
720
|
+
*
|
|
721
|
+
* @typeParam T - The value the deferred's `promise` resolves
|
|
722
|
+
*/
|
|
723
|
+
export declare interface DeferredInterface<T> {
|
|
724
|
+
readonly promise: Promise<T>;
|
|
725
|
+
resolve(value: T): void;
|
|
726
|
+
reject(reason: unknown): void;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Convert a {@link WorkflowDefinition} into an INITIAL {@link WorkflowSnapshot} — every
|
|
731
|
+
* node `pending`, no results, empty metadata — so the live W-b tree has ONE construction
|
|
732
|
+
* path (snapshot-driven) for both a fresh build and a restore.
|
|
733
|
+
*
|
|
734
|
+
* @remarks
|
|
735
|
+
* The structural fields (`id` / `name` / `description` + the ordered phases / tasks)
|
|
736
|
+
* carry over verbatim; the W-b live tree is the DECLARATIVE state machine, so the
|
|
737
|
+
* execution-only definition fields (per-phase `run` / `concurrency`, per-task `retries` /
|
|
738
|
+
* `timeout`) are intentionally dropped (W-c reads them from the definition when it drives
|
|
739
|
+
* transitions). The `bail` policy carries over — at the workflow tier AND, per phase, the
|
|
740
|
+
* EFFECTIVE policy (`phase.bail ?? workflowBail`) on each {@link PhaseSnapshot} — so the seeded
|
|
741
|
+
* snapshot is self-contained; a fresh seed has no `override`. `created` / `updated` are stamped now.
|
|
742
|
+
* {@link import('./factories.js').createWorkflow} builds from this.
|
|
743
|
+
*
|
|
744
|
+
* The optional `bail` override is the EFFECTIVE workflow policy the tree will run under
|
|
745
|
+
* (`createWorkflow` / the runner resolve `options.bail ?? definition.bail ?? DEFAULT_BAIL` and
|
|
746
|
+
* pass it here), so an `options.bail` override reaches BOTH the workflow tier AND the
|
|
747
|
+
* inheritance default of every phase that declares no `bail` of its own — otherwise the
|
|
748
|
+
* per-phase seeds would silently ignore the override. Omitted ⇒ the definition's own `bail`
|
|
749
|
+
* (defaulting to the graceful {@link import('./constants.js').DEFAULT_BAIL}).
|
|
750
|
+
*
|
|
751
|
+
* @param definition - The workflow definition to seed from
|
|
752
|
+
* @param bail - The EFFECTIVE workflow bail to seed both tiers with (defaults to the definition's)
|
|
753
|
+
* @returns An initial, all-`pending` {@link WorkflowSnapshot}
|
|
754
|
+
*/
|
|
755
|
+
export declare function definitionToSnapshot(definition: WorkflowDefinition, bail?: boolean): WorkflowSnapshot;
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Derive a phase's status from its tasks' statuses (tasks are concurrent, so this
|
|
759
|
+
* is an order-insensitive reduction).
|
|
760
|
+
*
|
|
761
|
+
* @remarks
|
|
762
|
+
* The truth table (most-severe terminal wins; `bail`-agnostic — a phase surfaces a
|
|
763
|
+
* task failure as `failed` so the workflow's `bail` policy can decide):
|
|
764
|
+
* - no tasks ⇒ `pending`.
|
|
765
|
+
* - any task `running`, OR a mix of started-and-unsettled tasks (some non-`pending`
|
|
766
|
+
* but not all terminal) ⇒ `running`.
|
|
767
|
+
* - every task `pending` ⇒ `pending`.
|
|
768
|
+
* - all terminal: any `failed` ⇒ `failed`; else any `stopped` ⇒ `stopped`; else any
|
|
769
|
+
* `completed` ⇒ `completed`; else (all `skipped`) ⇒ `skipped`.
|
|
770
|
+
*
|
|
771
|
+
* So an all-`skipped` phase is `skipped`, an all-`stopped` phase is `stopped`, a
|
|
772
|
+
* phase with completed tasks and some skips is `completed`, and a single failed
|
|
773
|
+
* task makes the phase `failed`.
|
|
774
|
+
*
|
|
775
|
+
* @param tasks - The phase's task statuses, in any order
|
|
776
|
+
* @returns The derived {@link PhaseStatus}
|
|
777
|
+
*/
|
|
778
|
+
export declare function derivePhaseStatus(tasks: readonly TaskStatus[]): PhaseStatus;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Derive a workflow's status from its phases' {@link PhaseDerivation}s — each phase's status
|
|
782
|
+
* paired with the EFFECTIVE `bail` it ran under (`phase.bail ?? workflow.bail`) — so the
|
|
783
|
+
* failure outcome is PER-PHASE-bail-aware (phases are sequential, but the derivation is an
|
|
784
|
+
* order-insensitive reduction over the settled set).
|
|
785
|
+
*
|
|
786
|
+
* @remarks
|
|
787
|
+
* `bail` is now a per-phase override (AGENTS §4.4), so it is carried on each
|
|
788
|
+
* {@link PhaseDerivation} rather than passed as one scalar. It is the ONLY axis that changes
|
|
789
|
+
* the failure outcome, decided per phase:
|
|
790
|
+
* - **A `failed` phase whose effective `bail` is `true` (halt)** propagates ⇒ the workflow is
|
|
791
|
+
* `failed` (the database-transaction halt) — even when the workflow default is graceful.
|
|
792
|
+
* - **A `failed` phase whose effective `bail` is `false` (graceful)** is DATA, not a workflow
|
|
793
|
+
* failure — it folds into completion like a settled phase. A graceful failed phase NEVER
|
|
794
|
+
* makes the workflow `failed` — even when the workflow default is strict.
|
|
795
|
+
*
|
|
796
|
+
* The rest of the table is shared:
|
|
797
|
+
* - no phases ⇒ `pending`.
|
|
798
|
+
* - any phase `running`, OR a mix of started-and-unsettled phases (some non-`pending`
|
|
799
|
+
* but not all terminal) ⇒ `running`.
|
|
800
|
+
* - every phase `pending` ⇒ `pending`.
|
|
801
|
+
* - all terminal (a `failed` phase counts as terminal here): any `stopped` ⇒ `stopped`; else
|
|
802
|
+
* any `completed` (or any graceful-bail `failed`, folded into completion) ⇒ `completed`;
|
|
803
|
+
* else (all `skipped`) ⇒ `skipped`.
|
|
804
|
+
*
|
|
805
|
+
* @param phases - The workflow's per-phase {@link PhaseDerivation}s (status + effective bail), in any order
|
|
806
|
+
* @returns The derived {@link WorkflowStatus}
|
|
807
|
+
*/
|
|
808
|
+
export declare function deriveWorkflowStatus(phases: readonly PhaseDerivation[]): WorkflowStatus;
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Expand a flat {@link WorkflowSteps} blob into a strict {@link WorkflowDefinition} — each
|
|
812
|
+
* step becomes a one-task phase, IN ORDER.
|
|
813
|
+
*
|
|
814
|
+
* @remarks
|
|
815
|
+
* The expansion of the tool's ADVERTISED surface (AGENTS §21 — the simplest form a small
|
|
816
|
+
* model can author). Each {@link WorkflowStep} maps to a phase holding exactly one task:
|
|
817
|
+
* the step's `name` becomes the task's `run.name`, and its `via` becomes the task's `run.via`
|
|
818
|
+
* (defaulting to `'function'` when omitted). Ids/names are auto-filled positionally — it
|
|
819
|
+
* builds an ids-omitted {@link WorkflowDraft} and delegates to {@link completeDraft}, so the
|
|
820
|
+
* two lenient surfaces share ONE synthesis path (step `i` → phase `phase-<i>`, its task
|
|
821
|
+
* `phase-<i>-task-0`). The optional `name` becomes the workflow's `name`. The result is a
|
|
822
|
+
* complete definition the caller validates against the STRICT contract before running.
|
|
823
|
+
*
|
|
824
|
+
* @param flat - The flat steps blob (`{ name?, steps: [{ name, via? }] }`)
|
|
825
|
+
* @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
|
|
826
|
+
*/
|
|
827
|
+
export declare function expandSteps(flat: WorkflowSteps): WorkflowDefinition;
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Narrow a {@link TaskForm} to the `agent` form — a task that runs a registered
|
|
831
|
+
* agent (a subagent).
|
|
832
|
+
*
|
|
833
|
+
* @param form - The task form to test
|
|
834
|
+
* @returns `true` when `form.via` is `'agent'`
|
|
835
|
+
*/
|
|
836
|
+
export declare function isAgentTask(form: TaskForm): form is {
|
|
837
|
+
readonly via: 'agent';
|
|
838
|
+
readonly name: string;
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Narrow a {@link TaskForm} to the `function` form — a task that runs a registered
|
|
843
|
+
* function.
|
|
844
|
+
*
|
|
845
|
+
* @param form - The task form to test
|
|
846
|
+
* @returns `true` when `form.via` is `'function'`
|
|
847
|
+
*/
|
|
848
|
+
export declare function isFunctionTask(form: TaskForm): form is {
|
|
849
|
+
readonly via: 'function';
|
|
850
|
+
readonly name: string;
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Test whether a {@link LifecycleStatus} is TERMINAL — a node in this state will not
|
|
855
|
+
* transition further.
|
|
856
|
+
*
|
|
857
|
+
* @remarks
|
|
858
|
+
* The ONE terminal check across all three tiers (AGENTS §4.4 "one concept = one word"):
|
|
859
|
+
* a task, a phase, and a workflow share the same {@link LifecycleStatus} vocabulary, so a
|
|
860
|
+
* single predicate covers them — {@link derivePhaseStatus} and {@link deriveWorkflowStatus}
|
|
861
|
+
* both consult it to tell a settled node from an in-flight one. Terminal: `completed` /
|
|
862
|
+
* `failed` / `skipped` / `stopped`; the only non-terminal states are `pending` and
|
|
863
|
+
* `running`.
|
|
864
|
+
*
|
|
865
|
+
* @param status - The lifecycle status to test (a task / phase / workflow status)
|
|
866
|
+
* @returns `true` when the status is terminal
|
|
867
|
+
*/
|
|
868
|
+
export declare function isTerminalStatus(status: LifecycleStatus): boolean;
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Narrow a {@link TaskForm} to the `tool` form — a task that runs a registered tool.
|
|
872
|
+
*
|
|
873
|
+
* @param form - The task form to test
|
|
874
|
+
* @returns `true` when `form.via` is `'tool'`
|
|
875
|
+
*/
|
|
876
|
+
export declare function isToolTask(form: TaskForm): form is {
|
|
877
|
+
readonly via: 'tool';
|
|
878
|
+
readonly name: string;
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Narrow an unknown caught value to a {@link WorkflowError}.
|
|
883
|
+
*
|
|
884
|
+
* @param value - The value to test (typically a `catch` binding)
|
|
885
|
+
* @returns `true` when `value` is a {@link WorkflowError}
|
|
886
|
+
*
|
|
887
|
+
* @example
|
|
888
|
+
* ```ts
|
|
889
|
+
* try {
|
|
890
|
+
* task.complete('done')
|
|
891
|
+
* } catch (error) {
|
|
892
|
+
* if (isWorkflowError(error) && error.code === 'TRANSITION') retry()
|
|
893
|
+
* }
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
export declare function isWorkflowError(value: unknown): value is WorkflowError;
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Narrow an `unknown` to a {@link WorkflowSnapshot} — the AGENTS §14 boundary guard for an
|
|
900
|
+
* UNTRUSTED snapshot read (a storage row a {@link import('./stores/DatabaseWorkflowStore.js').DatabaseWorkflowStore}
|
|
901
|
+
* reads back from its opaque JSON column, a snapshot loaded from disk).
|
|
902
|
+
*
|
|
903
|
+
* @remarks
|
|
904
|
+
* A total guard (it NEVER throws — adversarial input returns `false`, AGENTS §14). It checks the
|
|
905
|
+
* snapshot's SHAPE — `id` / `name` / `status` strings, a `boolean` `bail`, an array of `phases`,
|
|
906
|
+
* `created` / `updated` numbers — enough to safely impose the {@link WorkflowSnapshot} type at a
|
|
907
|
+
* storage boundary WITHOUT a cast. It is complementary to
|
|
908
|
+
* {@link import('./factories.js').assertSnapshot}, which validates the DEEPER invariant (every
|
|
909
|
+
* node's status / override drawn from the lifecycle vocabulary) and THROWS a `RESTORE`
|
|
910
|
+
* {@link import('./errors.js').WorkflowError} — the deep gate a {@link import('./factories.js').restoreWorkflow}
|
|
911
|
+
* applies. A boundary read narrows shape with this guard; a restore validates vocabulary with `assertSnapshot`.
|
|
912
|
+
*
|
|
913
|
+
* @param value - The value to test (an opaque storage read)
|
|
914
|
+
* @returns `true` when `value` has the structural shape of a {@link WorkflowSnapshot}
|
|
915
|
+
*/
|
|
916
|
+
export declare function isWorkflowSnapshot(value: unknown): value is WorkflowSnapshot;
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* The shared lifecycle vocabulary every tier draws from — `pending` before it runs,
|
|
920
|
+
* `running` while in flight, then one of the terminal states `completed` / `failed` /
|
|
921
|
+
* `skipped` / `stopped`.
|
|
922
|
+
*
|
|
923
|
+
* @remarks
|
|
924
|
+
* The ONE literal set behind the three semantic tiers ({@link TaskStatus} /
|
|
925
|
+
* {@link PhaseStatus} / {@link WorkflowStatus}), which alias it so each keeps its own
|
|
926
|
+
* name + doc while the vocabulary lives in one place (AGENTS §4.4 "one concept = one
|
|
927
|
+
* word"). It also types the single runtime terminal check
|
|
928
|
+
* {@link import('./helpers.js').isTerminalStatus} — every tier's value is a
|
|
929
|
+
* `LifecycleStatus`, so the one predicate accepts them all. The tiers stay distinct
|
|
930
|
+
* types (a phase status is not a task status) even though they currently share a body.
|
|
931
|
+
*/
|
|
932
|
+
export declare type LifecycleStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'stopped';
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* The maximum nesting depth a workflow's `agent` task may spawn into (W-c) — the
|
|
936
|
+
* bound the runner's depth/cycle guard enforces.
|
|
937
|
+
*
|
|
938
|
+
* @remarks
|
|
939
|
+
* The limit lives in ONE place. The `agent` {@link import('./types.js').TaskForm} is
|
|
940
|
+
* bounded by it when the {@link import('./WorkflowRunner.js').WorkflowRunner} resolves a
|
|
941
|
+
* subagent: an agent running at this depth can no longer author + run a nested workflow
|
|
942
|
+
* (that would be depth `MAX_WORKFLOW_DEPTH + 1`), so the over-deep `agent` task is
|
|
943
|
+
* rejected (a typed `DEPTH` `task.fail`). The chain therefore nests workflows down to
|
|
944
|
+
* this depth, and the `agent` task in the depth-`MAX_WORKFLOW_DEPTH` workflow fails.
|
|
945
|
+
*/
|
|
946
|
+
export declare const MAX_WORKFLOW_DEPTH = 8;
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* The in-memory {@link WorkflowStoreInterface} — a process-lifetime `Map` of
|
|
950
|
+
* {@link WorkflowSnapshot}s keyed by workflow id, the DEFAULT store
|
|
951
|
+
* {@link import('../factories.js').createMemoryWorkflowStore} builds.
|
|
952
|
+
*
|
|
953
|
+
* @remarks
|
|
954
|
+
* A plain `Map<string, WorkflowSnapshot>` (AGENTS §21 — the snapshot is already pure,
|
|
955
|
+
* self-contained JSON, so no encoding is needed for the memory tier). UNLIKE the server
|
|
956
|
+
* package's `SessionStoreInterface`'s memory store there is
|
|
957
|
+
* NO idle-TTL and NO eviction: a persisted workflow run-state is durable orchestration state
|
|
958
|
+
* that lives until an explicit `delete`, never silently aging out (a run that vanished
|
|
959
|
+
* mid-flight would be a silent data loss, not a freed session). A durable backend (JSON /
|
|
960
|
+
* SQLite / IndexedDB) swaps in through the SAME interface without touching the runner or the
|
|
961
|
+
* entity tree — its driver-pluggable twin is
|
|
962
|
+
* {@link import('./DatabaseWorkflowStore.js').DatabaseWorkflowStore} (the snapshot as one opaque
|
|
963
|
+
* JSON column), exactly as `@orkestrel/queue`'s `MemoryQueueStore`
|
|
964
|
+
* twins `DatabaseQueueStore`.
|
|
965
|
+
*
|
|
966
|
+
* - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored.
|
|
967
|
+
* - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
|
|
968
|
+
* - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
969
|
+
*
|
|
970
|
+
* The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
|
|
971
|
+
* bijection with {@link WorkflowStoreInterface}). Restore is a caller concern: read a snapshot
|
|
972
|
+
* back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
|
|
973
|
+
*
|
|
974
|
+
* @example
|
|
975
|
+
* ```ts
|
|
976
|
+
* import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@src/core'
|
|
977
|
+
*
|
|
978
|
+
* const store = createMemoryWorkflowStore()
|
|
979
|
+
* const workflow = createWorkflow(definition)
|
|
980
|
+
* await store.set(workflow.snapshot()) // persist the run state
|
|
981
|
+
* const snapshot = await store.get(definition.id)
|
|
982
|
+
* const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
|
|
983
|
+
* await store.delete(definition.id) // drop it
|
|
984
|
+
* ```
|
|
985
|
+
*/
|
|
986
|
+
export declare class MemoryWorkflowStore implements WorkflowStoreInterface {
|
|
987
|
+
#private;
|
|
988
|
+
get(id: string): Promise<WorkflowSnapshot | undefined>;
|
|
989
|
+
set(snapshot: WorkflowSnapshot): Promise<void>;
|
|
990
|
+
delete(id: string): Promise<void>;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* The live DERIVED state machine (W-b) for one phase — an observable (AGENTS §13) whose
|
|
995
|
+
* {@link PhaseStatus} is computed from its tasks (never set directly) and recomputed
|
|
996
|
+
* reactively as a task transitions (the middle tier of the cascade).
|
|
997
|
+
*
|
|
998
|
+
* @remarks
|
|
999
|
+
* - **Derived status.** `status` is `#override` when one is in force, else
|
|
1000
|
+
* {@link derivePhaseStatus} over the live tasks' statuses. {@link #recompute} (passed to
|
|
1001
|
+
* each child {@link Task}) re-derives on every child transition; a CHANGE emits the matching
|
|
1002
|
+
* event AND escalates to the workflow ({@link #escalate}, the upward step of the cascade).
|
|
1003
|
+
* - **Override (AGENTS §10).** `skip` / `stop` FORCE the phase's status (e.g. skipping a whole
|
|
1004
|
+
* phase), overriding the derived value; the override is PERSISTED in the snapshot's own
|
|
1005
|
+
* `override` field and restored DIRECTLY (no divergence guess), so a forced phase round-trips.
|
|
1006
|
+
* - **Children (AGENTS §9).** `tasks` is the lean {@link TaskManager} (an accessor + `count`,
|
|
1007
|
+
* no batch matrix); built positionally from the snapshot so order survives an interior `skip`.
|
|
1008
|
+
* `results()` collects the settled tasks' {@link TaskResult}s (the phase tier of the result
|
|
1009
|
+
* tree); `workflow` navigates UP to the live parent.
|
|
1010
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link PhaseEventMap}) fires
|
|
1011
|
+
* `start` / `complete` / `fail` / `stop` on a derived-status CHANGE, strictly AFTER the
|
|
1012
|
+
* recompute + escalate; the emitter isolates a listener throw and routes it to its `error`
|
|
1013
|
+
* handler (the `error` option); `fail` carries the failing task's {@link TaskResult}.
|
|
1014
|
+
*/
|
|
1015
|
+
export declare class Phase implements PhaseInterface {
|
|
1016
|
+
#private;
|
|
1017
|
+
constructor(snapshot: PhaseSnapshot, workflow: WorkflowInterface, escalate: () => void, options?: PhaseOptions, bail?: boolean);
|
|
1018
|
+
get emitter(): EmitterInterface<PhaseEventMap>;
|
|
1019
|
+
get id(): string;
|
|
1020
|
+
get name(): string;
|
|
1021
|
+
get description(): string | undefined;
|
|
1022
|
+
get context(): PhaseContext;
|
|
1023
|
+
get workflow(): WorkflowInterface;
|
|
1024
|
+
get bail(): boolean;
|
|
1025
|
+
get status(): PhaseStatus;
|
|
1026
|
+
get tasks(): TaskManagerInterface;
|
|
1027
|
+
task(id: string): TaskInterface | undefined;
|
|
1028
|
+
results(): readonly TaskResult[];
|
|
1029
|
+
skip(): void;
|
|
1030
|
+
stop(): void;
|
|
1031
|
+
snapshot(): PhaseSnapshot;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/** Every {@link PhaseStatus} value, frozen — the lifecycle vocabulary of a phase. */
|
|
1035
|
+
export declare const PHASE_STATUSES: readonly PhaseStatus[];
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* The ambient context of a phase — its own identity plus a back-reference to the
|
|
1039
|
+
* workflow it belongs to.
|
|
1040
|
+
*
|
|
1041
|
+
* @remarks
|
|
1042
|
+
* Extends {@link WorkflowContext} (so the phase carries its own `id` / `name`) and
|
|
1043
|
+
* adds `workflow`, the parent's context — the lineage pointer back UP the tree.
|
|
1044
|
+
*/
|
|
1045
|
+
export declare interface PhaseContext extends WorkflowContext {
|
|
1046
|
+
readonly workflow: WorkflowContext;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* The serializable definition of one phase — its identity, its ordered tasks, and
|
|
1051
|
+
* an optional resource throttle.
|
|
1052
|
+
*
|
|
1053
|
+
* @remarks
|
|
1054
|
+
* Pure JSON DATA. `tasks` are the phase's tasks, which run CONCURRENTLY (the fixed
|
|
1055
|
+
* determinism principle). `concurrency` is the optional per-phase resource throttle
|
|
1056
|
+
* — the maximum number of tasks in flight at once (a positive integer); omitted ⇒
|
|
1057
|
+
* unbounded. It is a throttle, NOT a sequencing control: phases are always
|
|
1058
|
+
* sequential, tasks within a phase always concurrent.
|
|
1059
|
+
*/
|
|
1060
|
+
export declare interface PhaseDefinition {
|
|
1061
|
+
readonly id: string;
|
|
1062
|
+
readonly name: string;
|
|
1063
|
+
readonly description?: string;
|
|
1064
|
+
readonly tasks: readonly TaskDefinition[];
|
|
1065
|
+
/** Max tasks in flight at once (a resource throttle); omitted ⇒ unbounded. */
|
|
1066
|
+
readonly concurrency?: number;
|
|
1067
|
+
/**
|
|
1068
|
+
* @remarks
|
|
1069
|
+
* The per-phase failure-policy OVERRIDE (AGENTS §4.4). Omitted ⇒ the phase INHERITS the
|
|
1070
|
+
* workflow `bail`; supplied, it wins (`effectiveBail = phase.bail ?? workflow.bail`). A
|
|
1071
|
+
* `bail: true` phase HALTS the run on its first task failure even under a graceful workflow
|
|
1072
|
+
* default; a `bail: false` phase does NOT halt even under a strict workflow default.
|
|
1073
|
+
*/
|
|
1074
|
+
readonly bail?: boolean;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Convert one {@link import('./types.js').PhaseDefinition} into an initial, all-`pending`
|
|
1079
|
+
* {@link PhaseSnapshot} — the per-phase step of {@link definitionToSnapshot}.
|
|
1080
|
+
*
|
|
1081
|
+
* @remarks
|
|
1082
|
+
* The snapshot persists the EFFECTIVE failure policy this phase runs under: the phase's own
|
|
1083
|
+
* `bail` when it declares one, else the `workflowBail` it inherits — so a restore reinstates
|
|
1084
|
+
* the same per-phase policy without a silent default (`effectiveBail = phase.bail ?? workflowBail`).
|
|
1085
|
+
*
|
|
1086
|
+
* @param phase - The phase definition to seed from
|
|
1087
|
+
* @param workflowBail - The workflow-level `bail` default the phase inherits when it declares none
|
|
1088
|
+
* @returns An initial {@link PhaseSnapshot}
|
|
1089
|
+
*/
|
|
1090
|
+
export declare function phaseDefinitionToSnapshot(phase: WorkflowDefinition['phases'][number], workflowBail: boolean): PhaseSnapshot;
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* One phase's contribution to the workflow-status derivation — its {@link PhaseStatus} paired
|
|
1094
|
+
* with the EFFECTIVE `bail` policy it ran under (`phase.bail ?? workflow.bail`, AGENTS §4.4).
|
|
1095
|
+
*
|
|
1096
|
+
* @remarks
|
|
1097
|
+
* The input shape of {@link import('./helpers.js').deriveWorkflowStatus}: since `bail` is now a
|
|
1098
|
+
* per-phase override, the workflow `failed` derivation is per-phase-bail-aware, so each phase
|
|
1099
|
+
* must carry its OWN effective policy rather than the derivation taking one scalar `bail`. A
|
|
1100
|
+
* `failed` phase propagates `failed` to the workflow only when ITS `bail` is `true`; a `failed`
|
|
1101
|
+
* phase whose `bail` is `false` folds into completion. {@link import('./Workflow.js').Workflow}
|
|
1102
|
+
* builds one per live phase (`{ status: phase.status, bail: phase.bail }`).
|
|
1103
|
+
*/
|
|
1104
|
+
export declare interface PhaseDerivation {
|
|
1105
|
+
readonly status: PhaseStatus;
|
|
1106
|
+
readonly bail: boolean;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/** A draft phase — a {@link PhaseDefinition} with OPTIONAL `id` / `name` and {@link TaskDraft} tasks. */
|
|
1110
|
+
export declare interface PhaseDraft {
|
|
1111
|
+
readonly id?: string;
|
|
1112
|
+
readonly name?: string;
|
|
1113
|
+
readonly description?: string;
|
|
1114
|
+
readonly tasks: readonly TaskDraft[];
|
|
1115
|
+
/** Max tasks in flight at once (a resource throttle); omitted ⇒ unbounded. */
|
|
1116
|
+
readonly concurrency?: number;
|
|
1117
|
+
/** The per-phase failure-policy OVERRIDE; omitted ⇒ inherits the workflow `bail` (`effectiveBail = phase.bail ?? workflow.bail`). */
|
|
1118
|
+
readonly bail?: boolean;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* The shape of a PHASE in a draft workflow — identical to {@link phaseShape} EXCEPT
|
|
1123
|
+
* `id` and `name` are OPTIONAL, and its tasks are {@link taskDraftShape}s.
|
|
1124
|
+
*/
|
|
1125
|
+
export declare const phaseDraftShape: ObjectShape<{
|
|
1126
|
+
id: OptionalShape<StringShape>;
|
|
1127
|
+
name: OptionalShape<StringShape>;
|
|
1128
|
+
description: OptionalShape<StringShape>;
|
|
1129
|
+
tasks: ArrayShape<ObjectShape<{
|
|
1130
|
+
id: OptionalShape<StringShape>;
|
|
1131
|
+
name: OptionalShape<StringShape>;
|
|
1132
|
+
description: OptionalShape<StringShape>;
|
|
1133
|
+
run: UnionShape<[ ObjectShape<{
|
|
1134
|
+
via: LiteralShape<readonly ["function"]>;
|
|
1135
|
+
name: StringShape;
|
|
1136
|
+
}>, ObjectShape<{
|
|
1137
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
1138
|
+
name: StringShape;
|
|
1139
|
+
}>, ObjectShape<{
|
|
1140
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
1141
|
+
name: StringShape;
|
|
1142
|
+
}>]>;
|
|
1143
|
+
retries: OptionalShape<NumberShape>;
|
|
1144
|
+
timeout: OptionalShape<NumberShape>;
|
|
1145
|
+
}>>;
|
|
1146
|
+
concurrency: OptionalShape<NumberShape>;
|
|
1147
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
1148
|
+
}>;
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* The push observation surface (AGENTS §13) of the phase entity (W-b) — analogous
|
|
1152
|
+
* to {@link WorkflowEventMap}, scoped to one phase.
|
|
1153
|
+
*
|
|
1154
|
+
* @remarks
|
|
1155
|
+
* `start` fires when the phase begins; `complete` when all its tasks settled
|
|
1156
|
+
* successfully; `fail` when a task failed under `bail` (carrying the
|
|
1157
|
+
* {@link TaskResult}); `stop` when the phase was ended. A throwing listener is
|
|
1158
|
+
* isolated by the emitter and routed to its `error` handler, not the domain surface
|
|
1159
|
+
* (AGENTS §13). A `type` alias (AGENTS §4.5) so it satisfies `EventMap`.
|
|
1160
|
+
*/
|
|
1161
|
+
export declare type PhaseEventMap = {
|
|
1162
|
+
/** The phase began — its `id`. */
|
|
1163
|
+
readonly start: readonly [id: string];
|
|
1164
|
+
/** Every task in the phase settled successfully. */
|
|
1165
|
+
readonly complete: readonly [];
|
|
1166
|
+
/** A task failed under `bail` — the failing task's result. */
|
|
1167
|
+
readonly fail: readonly [result: TaskResult];
|
|
1168
|
+
/** The phase was permanently stopped. */
|
|
1169
|
+
readonly stop: readonly [];
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
/** Initial {@link PhaseEventMap} listeners — the reserved `on` option (AGENTS §8). */
|
|
1173
|
+
export declare type PhaseHooks = EmitterHooks<PhaseEventMap>;
|
|
1174
|
+
|
|
1175
|
+
/** The minimal data to create a phase context — a partial {@link PhaseContext}. */
|
|
1176
|
+
export declare type PhaseInput = Partial<PhaseContext>;
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* The live derived state machine (W-b) for one {@link PhaseDefinition} — an
|
|
1180
|
+
* observable (AGENTS §13) phase whose {@link PhaseStatus} is DERIVED from its tasks
|
|
1181
|
+
* (never set directly) and recomputed reactively as a task transitions (the cascade).
|
|
1182
|
+
*
|
|
1183
|
+
* @remarks
|
|
1184
|
+
* - **Derived status.** `status` is computed via
|
|
1185
|
+
* {@link import('./helpers.js').derivePhaseStatus} over the live tasks' statuses,
|
|
1186
|
+
* UNLESS an override is in force. It recomputes whenever a child task transitions; a
|
|
1187
|
+
* CHANGE emits.
|
|
1188
|
+
* - **Children.** `tasks` is the lean {@link TaskManagerInterface} (AGENTS §9 — an
|
|
1189
|
+
* accessor + `count`, no batch matrix); `task(id)` / `tasks().tasks()` read in positional
|
|
1190
|
+
* order. `results` collects the settled tasks' {@link TaskResult}s (the phase tier of the
|
|
1191
|
+
* result tree); `workflow` navigates UP to the live parent.
|
|
1192
|
+
* - **Override.** `skip` / `stop` (AGENTS §10) FORCE the phase's status, overriding the
|
|
1193
|
+
* derived value (e.g. skipping a whole phase); the override survives a snapshot.
|
|
1194
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link PhaseEventMap}) fires
|
|
1195
|
+
* `start` / `complete` / `fail` / `stop` on a derived-status change; the emitter isolates a
|
|
1196
|
+
* listener throw and routes it to its `error` handler (the `error` option).
|
|
1197
|
+
*/
|
|
1198
|
+
export declare interface PhaseInterface {
|
|
1199
|
+
readonly emitter: EmitterInterface<PhaseEventMap>;
|
|
1200
|
+
readonly id: string;
|
|
1201
|
+
readonly name: string;
|
|
1202
|
+
readonly description?: string;
|
|
1203
|
+
readonly context: PhaseContext;
|
|
1204
|
+
readonly workflow: WorkflowInterface;
|
|
1205
|
+
readonly status: PhaseStatus;
|
|
1206
|
+
/** The RESOLVED effective failure policy this phase runs under (`phase.bail ?? workflow.bail`); mirrors {@link WorkflowInterface.bail}. */
|
|
1207
|
+
readonly bail: boolean;
|
|
1208
|
+
readonly tasks: TaskManagerInterface;
|
|
1209
|
+
/** Look up one live task by its `id`. */
|
|
1210
|
+
task(id: string): TaskInterface | undefined;
|
|
1211
|
+
/** The settled tasks' results, in positional order — the phase tier of the result tree. */
|
|
1212
|
+
results(): readonly TaskResult[];
|
|
1213
|
+
skip(): void;
|
|
1214
|
+
stop(): void;
|
|
1215
|
+
snapshot(): PhaseSnapshot;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* The lean child manager (AGENTS §9) of a {@link import('../Workflow.js').Workflow}'s
|
|
1220
|
+
* live phases — an insertion-ordered registry keyed by phase `id`, the phase analogue
|
|
1221
|
+
* of {@link import('../tasks/TaskManager.js').TaskManager}.
|
|
1222
|
+
*
|
|
1223
|
+
* @remarks
|
|
1224
|
+
* - **Positional store.** Phases live in an insertion-ordered `Map` keyed by `id`;
|
|
1225
|
+
* `append` adds one at the end, `phase(id)` looks one up, `phases()` lists them in
|
|
1226
|
+
* positional order, `count` is the size. A snapshot RESTORE re-`append`s in the
|
|
1227
|
+
* snapshot's order, reproducing it exactly.
|
|
1228
|
+
* - **No batch matrix.** A workflow's phases are a fixed positional set, so AGENTS §9.2
|
|
1229
|
+
* is deliberately omitted.
|
|
1230
|
+
* - **Event-free.** A purely structural container — the live {@link PhaseInterface}s own
|
|
1231
|
+
* their own emitters.
|
|
1232
|
+
*
|
|
1233
|
+
* @example
|
|
1234
|
+
* ```ts
|
|
1235
|
+
* const phases = new PhaseManager()
|
|
1236
|
+
* phases.append(phase) // a live Phase
|
|
1237
|
+
* phases.phase(phase.id) // the same phase
|
|
1238
|
+
* phases.count // 1
|
|
1239
|
+
* ```
|
|
1240
|
+
*/
|
|
1241
|
+
export declare class PhaseManager implements PhaseManagerInterface {
|
|
1242
|
+
#private;
|
|
1243
|
+
get count(): number;
|
|
1244
|
+
append(phase: PhaseInterface): void;
|
|
1245
|
+
phase(id: string): PhaseInterface | undefined;
|
|
1246
|
+
phases(): readonly PhaseInterface[];
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* The lean child manager (AGENTS §9) of a {@link WorkflowInterface}'s live phases —
|
|
1251
|
+
* positional accessors plus `count`, the phase analogue of {@link TaskManagerInterface}.
|
|
1252
|
+
*
|
|
1253
|
+
* @remarks
|
|
1254
|
+
* `append` adds one live {@link PhaseInterface} at the end; `phase(id)` looks one up;
|
|
1255
|
+
* `phases()` lists them in positional order; `count` is the tally. No batch matrix.
|
|
1256
|
+
*/
|
|
1257
|
+
export declare interface PhaseManagerInterface {
|
|
1258
|
+
readonly count: number;
|
|
1259
|
+
append(phase: PhaseInterface): void;
|
|
1260
|
+
phase(id: string): PhaseInterface | undefined;
|
|
1261
|
+
phases(): readonly PhaseInterface[];
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* The runtime options for a {@link PhaseInterface} — the construction bag the live
|
|
1266
|
+
* derived phase state machine (W-b) carries.
|
|
1267
|
+
*
|
|
1268
|
+
* @remarks
|
|
1269
|
+
* The reserved `on` (AGENTS §8) wires initial {@link PhaseEventMap} listeners.
|
|
1270
|
+
* `tasks` keys per-task {@link TaskOptions} by task `id`, so {@link createWorkflow}
|
|
1271
|
+
* can thread a leaf's options (its `on` / `metadata`) down through the phase when it
|
|
1272
|
+
* builds the whole tree.
|
|
1273
|
+
*/
|
|
1274
|
+
export declare interface PhaseOptions {
|
|
1275
|
+
readonly on?: PhaseHooks;
|
|
1276
|
+
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
1277
|
+
readonly error?: EmitterErrorHandler;
|
|
1278
|
+
/** Per-task {@link TaskOptions}, keyed by the task's `id`. */
|
|
1279
|
+
readonly tasks?: Readonly<Record<string, TaskOptions>>;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* The shape of a {@link import('./types.js').PhaseDefinition} — identity, its ordered
|
|
1284
|
+
* {@link taskShape} tasks, and an optional positive-integer `concurrency` throttle
|
|
1285
|
+
* (max tasks in flight; omitted ⇒ unbounded).
|
|
1286
|
+
*/
|
|
1287
|
+
export declare const phaseShape: ObjectShape<{
|
|
1288
|
+
id: StringShape;
|
|
1289
|
+
name: StringShape;
|
|
1290
|
+
description: OptionalShape<StringShape>;
|
|
1291
|
+
tasks: ArrayShape<ObjectShape<{
|
|
1292
|
+
id: StringShape;
|
|
1293
|
+
name: StringShape;
|
|
1294
|
+
description: OptionalShape<StringShape>;
|
|
1295
|
+
run: UnionShape<[ ObjectShape<{
|
|
1296
|
+
via: LiteralShape<readonly ["function"]>;
|
|
1297
|
+
name: StringShape;
|
|
1298
|
+
}>, ObjectShape<{
|
|
1299
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
1300
|
+
name: StringShape;
|
|
1301
|
+
}>, ObjectShape<{
|
|
1302
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
1303
|
+
name: StringShape;
|
|
1304
|
+
}>]>;
|
|
1305
|
+
retries: OptionalShape<NumberShape>;
|
|
1306
|
+
timeout: OptionalShape<NumberShape>;
|
|
1307
|
+
}>>;
|
|
1308
|
+
concurrency: OptionalShape<NumberShape>;
|
|
1309
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
1310
|
+
}>;
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* A JSON-serializable snapshot of one phase's state — its identity, status, its forced
|
|
1314
|
+
* override (if any), and its nested task snapshots.
|
|
1315
|
+
*
|
|
1316
|
+
* @remarks
|
|
1317
|
+
* Pure JSON DATA. `status` is the EFFECTIVE status (override-or-derived) at snapshot time.
|
|
1318
|
+
* `override` is the forced status of a whole-phase `skip` / `stop` (AGENTS §10) — PRESENT only
|
|
1319
|
+
* when one is in force, so a restore reinstates it DIRECTLY (no fragile derivation comparison)
|
|
1320
|
+
* and a genuinely-derived phase carries none. A leaf {@link TaskSnapshot} needs no `override`
|
|
1321
|
+
* field — a task's terminal status IS its forced marker. `tasks` are the phase's
|
|
1322
|
+
* {@link TaskSnapshot}s in order.
|
|
1323
|
+
*/
|
|
1324
|
+
export declare interface PhaseSnapshot {
|
|
1325
|
+
readonly id: string;
|
|
1326
|
+
readonly name: string;
|
|
1327
|
+
readonly description?: string;
|
|
1328
|
+
readonly status: PhaseStatus;
|
|
1329
|
+
/** The forced status of a whole-phase `skip` / `stop`; present only when an override is in force. */
|
|
1330
|
+
readonly override?: PhaseStatus;
|
|
1331
|
+
/**
|
|
1332
|
+
* The EFFECTIVE failure policy this phase ran under (`phase.bail ?? workflow.bail`, AGENTS §4.4)
|
|
1333
|
+
* — persisted (REQUIRED, like {@link WorkflowSnapshot.bail}) so a restore reinstates the same
|
|
1334
|
+
* per-phase policy identically without a silent default.
|
|
1335
|
+
*/
|
|
1336
|
+
readonly bail: boolean;
|
|
1337
|
+
readonly tasks: readonly TaskSnapshot[];
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* The lifecycle status of a phase — the same vocabulary as {@link TaskStatus},
|
|
1342
|
+
* derived from its tasks' statuses.
|
|
1343
|
+
*
|
|
1344
|
+
* @remarks
|
|
1345
|
+
* A semantic tier of the shared {@link LifecycleStatus} vocabulary. A phase is
|
|
1346
|
+
* `failed` only when a task failed AND the workflow's `bail` policy is in force (a
|
|
1347
|
+
* failure under graceful mode is data, not a phase failure). `stopped` propagates
|
|
1348
|
+
* when every task was stopped. See {@link import('./helpers.js').derivePhaseStatus}.
|
|
1349
|
+
*/
|
|
1350
|
+
export declare type PhaseStatus = LifecycleStatus;
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Rebuild an equivalent live W-b entity tree from a {@link WorkflowSnapshot} — the
|
|
1354
|
+
* inverse of {@link WorkflowInterface.snapshot}, restoring structure + each node's status
|
|
1355
|
+
* + recorded results + positional order + the persisted `#override`.
|
|
1356
|
+
*
|
|
1357
|
+
* @remarks
|
|
1358
|
+
* Round-trip fidelity is paramount: a `snapshot()` → `restoreWorkflow()` reproduces the
|
|
1359
|
+
* same status at every node (each `#override` restored DIRECTLY from the snapshot's own
|
|
1360
|
+
* `override` field, not guessed from a status divergence), the same recorded
|
|
1361
|
+
* {@link import('./types.js').TaskResult}s, and the same positional order (an interior
|
|
1362
|
+
* `skip` / `remove` survives). The snapshot is SELF-CONTAINED — it persists the `bail`
|
|
1363
|
+
* policy it ran under, so the restore re-derives status IDENTICALLY without a silent
|
|
1364
|
+
* default; the snapshot's `bail` is the source of truth, while an explicit `options.bail`
|
|
1365
|
+
* still wins when supplied (to deliberately re-run under a different policy). A structurally
|
|
1366
|
+
* invalid snapshot (a status — or override — outside the lifecycle vocabulary, or a
|
|
1367
|
+
* non-boolean `bail`) throws a `RESTORE` {@link WorkflowError}.
|
|
1368
|
+
*
|
|
1369
|
+
* @param snapshot - The snapshot to restore (carries its own `bail` + `override`)
|
|
1370
|
+
* @param options - Runtime options (initial listeners, an optional `bail` override, per-node options)
|
|
1371
|
+
* @returns The restored live {@link WorkflowInterface} root
|
|
1372
|
+
*
|
|
1373
|
+
* @example
|
|
1374
|
+
* ```ts
|
|
1375
|
+
* import { restoreWorkflow } from '@src/core'
|
|
1376
|
+
*
|
|
1377
|
+
* const restored = restoreWorkflow(workflow.snapshot()) // bail comes from the snapshot
|
|
1378
|
+
* restored.status === workflow.status // true
|
|
1379
|
+
* ```
|
|
1380
|
+
*/
|
|
1381
|
+
export declare function restoreWorkflow(snapshot: WorkflowSnapshot, options?: WorkflowOptions): WorkflowInterface;
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* A thin generic orchestrator that drives declared units — and any they `spawn` —
|
|
1385
|
+
* through a bounded-concurrency {@link createQueue}, collecting ordered results.
|
|
1386
|
+
*
|
|
1387
|
+
* @remarks
|
|
1388
|
+
* - **Drives the Queue (no reimplemented concurrency).** Every unit (declared or
|
|
1389
|
+
* spawned) is `enqueue`d on one internal `Queue`, so backpressure, FIFO ordering,
|
|
1390
|
+
* bounded concurrency, retries, and the per-attempt timeout are all the Queue's —
|
|
1391
|
+
* the Runner adds only orchestration (launching, ordering, draining, fail-fast).
|
|
1392
|
+
* - **Spawns actually run, results stay ordered (the B2 fix).** Declared inputs and
|
|
1393
|
+
* `spawn`ed siblings flow through the SAME `#launch`, which appends the unit's `id`
|
|
1394
|
+
* to an ordered `#order` list and records its settled value into `#values` by `id`.
|
|
1395
|
+
* Results are read back as `#order.map(id => #values.get(id))` — declared first (in
|
|
1396
|
+
* input order), then spawns (in spawn order). There is no one-time task snapshot,
|
|
1397
|
+
* so a unit spawned mid-handler is run and ordered like any other.
|
|
1398
|
+
* - **`execute` awaits the full spawn closure via a count gate.** `#launch` increments
|
|
1399
|
+
* an outstanding-unit `#count` BEFORE enqueuing and every settle decrements it,
|
|
1400
|
+
* resolving the `#drained` deferred at zero. Because `spawn` calls `#launch` (so
|
|
1401
|
+
* `#count += 1`) before the parent handler returns, the count never reaches zero
|
|
1402
|
+
* mid-run — `execute` parks on `#drained` and so awaits the entire transitive
|
|
1403
|
+
* closure, not just the declared units.
|
|
1404
|
+
* - **`spawn` is fire-and-track.** A spawned unit runs through the queue regardless of
|
|
1405
|
+
* whether its promise is awaited; the Runner never awaits a spawned promise from
|
|
1406
|
+
* within a handler's slot (it awaits the count gate instead), so a slot-holding
|
|
1407
|
+
* handler can fan out without the Runner deadlocking it. (An inline `await` of a
|
|
1408
|
+
* spawn by a bounded handler can still deadlock — that caveat is the caller's.)
|
|
1409
|
+
* - **Per-unit Controller + signal.** Each unit gets a `Controller` carrying its `id`,
|
|
1410
|
+
* `input`, the unit's `Abort` (so `aborted` / `abort` delegate to it), and the queue
|
|
1411
|
+
* attempt's `signal` (which ANY-combines the unit abort + runner abort + timeout). A
|
|
1412
|
+
* `spawn` callback is injected so `controller.spawn(input)` delegates to `#launch`.
|
|
1413
|
+
* - **One-shot + fail-fast.** `execute` runs once (a second call throws). The first
|
|
1414
|
+
* unit failure (after its retries) records the error and `abort()`s the run, so every
|
|
1415
|
+
* sibling's signal fires; later failures are ignored and `execute` rejects with the
|
|
1416
|
+
* first error. A user `abort(reason)` likewise rejects a running `execute`.
|
|
1417
|
+
* - **Observable (§13).** The owned {@link emitter} ({@link RunnerEventMap}) carries the run
|
|
1418
|
+
* lifecycle — `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort` — for
|
|
1419
|
+
* fire-and-forget observers. Every event is emitted directly, strictly AFTER the relevant
|
|
1420
|
+
* launch / settle / drain transition; the emitter isolates a listener throw and routes it
|
|
1421
|
+
* to its `error` handler (the `error` option), so a buggy observer can NEVER reorder, throw
|
|
1422
|
+
* into, or corrupt the one-shot / fail-fast / spawn-tracking engine: the outstanding-unit
|
|
1423
|
+
* count gate stays balanced and fail-fast still fires regardless of what a listener does.
|
|
1424
|
+
* Observation is purely a side-channel.
|
|
1425
|
+
*/
|
|
1426
|
+
export declare class Runner<TInput, TResult> implements RunnerInterface<TInput, TResult> {
|
|
1427
|
+
#private;
|
|
1428
|
+
constructor(options: RunnerOptions<TInput, TResult>);
|
|
1429
|
+
get emitter(): EmitterInterface<RunnerEventMap<TResult>>;
|
|
1430
|
+
get active(): number;
|
|
1431
|
+
get stopped(): boolean;
|
|
1432
|
+
execute(inputs: readonly TInput[]): Promise<readonly TResult[]>;
|
|
1433
|
+
abort(reason?: unknown): void;
|
|
1434
|
+
destroy(): void;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* The per-entry reliability OVERRIDES for one unit — its extra attempts on failure and its
|
|
1439
|
+
* per-attempt deadline, resolved from the unit's input via {@link RunnerOptions.entries}.
|
|
1440
|
+
*
|
|
1441
|
+
* @remarks
|
|
1442
|
+
* The unit's `id` and `signal` stay Runner-managed (it mints the id and owns the per-unit
|
|
1443
|
+
* abort), so only the two reliability knobs are exposed here. Each field OVERRIDES the
|
|
1444
|
+
* runner-level `retries` / `timeout` default for that one unit; an omitted field falls back
|
|
1445
|
+
* to the default. This is the per-unit slice of the backing Queue's
|
|
1446
|
+
* `@orkestrel/queue` `QueueEntryOptions` surfaced cleanly — the Queue already
|
|
1447
|
+
* resolves default→override.
|
|
1448
|
+
*/
|
|
1449
|
+
export declare interface RunnerEntryOptions {
|
|
1450
|
+
readonly retries?: number;
|
|
1451
|
+
readonly timeout?: number;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* The push observation surface of a {@link RunnerInterface} (AGENTS §13) — the run
|
|
1456
|
+
* lifecycle a fire-and-forget observer (logging, metrics, tracing) subscribes to,
|
|
1457
|
+
* ALONGSIDE the eventual `execute` result.
|
|
1458
|
+
*
|
|
1459
|
+
* @typeParam TResult - The value a unit resolves; the `finish` payload is the run's ordered
|
|
1460
|
+
* `readonly TResult[]`, so the map is `RunnerEventMap<TResult>` — mirroring how the
|
|
1461
|
+
* {@link RunnerInterface} is generic.
|
|
1462
|
+
*
|
|
1463
|
+
* @remarks
|
|
1464
|
+
* Listener isolation is the emitter's (AGENTS §13): every event is emitted directly and a
|
|
1465
|
+
* listener throw is routed to the emitter's OWN `error` handler (the `error` option), never
|
|
1466
|
+
* onto this domain map and never into the one-shot / fail-fast / spawn-tracking engine — so a
|
|
1467
|
+
* buggy observer can never reorder, throw into, or corrupt the run. Every emit sits AFTER the
|
|
1468
|
+
* relevant unit-launch / settle / drain transition, so a throwing observer cannot unbalance
|
|
1469
|
+
* the outstanding-unit count gate or break fail-fast. Subscribe via `runner.emitter.on(...)`.
|
|
1470
|
+
*
|
|
1471
|
+
* Declared as a `type` alias (not `interface extends EventMap`, §4.5 — `EventMap` is a
|
|
1472
|
+
* `type` kind): a type-literal satisfies the `EventMap` constraint
|
|
1473
|
+
* (`Record<string, readonly unknown[]>`) structurally, whereas an interface lacks the
|
|
1474
|
+
* required index signature.
|
|
1475
|
+
*/
|
|
1476
|
+
export declare type RunnerEventMap<TResult> = {
|
|
1477
|
+
/** `execute` began — emitted once at the top of a non-empty run. */
|
|
1478
|
+
readonly start: readonly [];
|
|
1479
|
+
/** A unit's handler began running — the unit's id (declared or spawned). */
|
|
1480
|
+
readonly unit: readonly [id: string];
|
|
1481
|
+
/** A sub-unit was spawned — its id + the spawning parent's id (when known). */
|
|
1482
|
+
readonly spawn: readonly [id: string, parent: string | undefined];
|
|
1483
|
+
/** A unit completed successfully — its id (after its outcome was recorded). */
|
|
1484
|
+
readonly settle: readonly [id: string];
|
|
1485
|
+
/** A unit failed — its id + the error (always `unknown`). */
|
|
1486
|
+
readonly fail: readonly [id: string, error: unknown];
|
|
1487
|
+
/** The batch settled — the run's ordered results (the same array `execute` resolves). */
|
|
1488
|
+
readonly finish: readonly [results: readonly TResult[]];
|
|
1489
|
+
/** The run was aborted (fail-fast, a user `abort`, or `destroy`) — the cancel reason. */
|
|
1490
|
+
readonly abort: readonly [reason: unknown];
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Runs one unit's work, given its {@link ControllerInterface}.
|
|
1495
|
+
*
|
|
1496
|
+
* @typeParam TInput - The unit's work input
|
|
1497
|
+
* @typeParam TResult - The value the handler resolves for a unit
|
|
1498
|
+
*/
|
|
1499
|
+
export declare type RunnerHandler<TInput, TResult> = (controller: ControllerInterface<TInput, TResult>) => Promise<TResult> | TResult;
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* A thin generic orchestrator that drives declared units — plus any they `spawn` —
|
|
1503
|
+
* through a bounded-concurrency queue, collecting their results in order.
|
|
1504
|
+
*
|
|
1505
|
+
* @remarks
|
|
1506
|
+
* One-shot: `execute` runs the unit set once and resolves their results. A `Runner`
|
|
1507
|
+
* does not reimplement concurrency or retries — it composes the workers `Queue`
|
|
1508
|
+
* (the backpressure + retry + timeout engine), routing every unit (declared and
|
|
1509
|
+
* spawned) through it so spawned work actually runs.
|
|
1510
|
+
*
|
|
1511
|
+
* Exposes a typed {@link emitter} (AGENTS §13) carrying its run lifecycle moments
|
|
1512
|
+
* ({@link RunnerEventMap}) for fire-and-forget observers, ALONGSIDE the eventual `execute`
|
|
1513
|
+
* result. Emitting is observation-only — every event fires AFTER the relevant unit-launch /
|
|
1514
|
+
* settle / drain transition, so a buggy observer can never reorder or corrupt the one-shot /
|
|
1515
|
+
* fail-fast / spawn-tracking engine: the emitter isolates a listener throw and routes it to
|
|
1516
|
+
* its `error` handler (the `error` option), never the run. Subscribe via
|
|
1517
|
+
* `runner.emitter.on(...)`.
|
|
1518
|
+
*
|
|
1519
|
+
* @typeParam TInput - The unit's work input
|
|
1520
|
+
* @typeParam TResult - The value a unit resolves
|
|
1521
|
+
*/
|
|
1522
|
+
export declare interface RunnerInterface<TInput, TResult> {
|
|
1523
|
+
readonly emitter: EmitterInterface<RunnerEventMap<TResult>>;
|
|
1524
|
+
readonly active: number;
|
|
1525
|
+
readonly stopped: boolean;
|
|
1526
|
+
/**
|
|
1527
|
+
* Run all `inputs` — and anything they `spawn` — to completion; resolve their
|
|
1528
|
+
* results in order: the declared inputs first (in input order), then the spawned
|
|
1529
|
+
* units (in spawn order).
|
|
1530
|
+
*
|
|
1531
|
+
* @remarks
|
|
1532
|
+
* One-shot — a second call throws. **Fail-fast** — the first unit failure (after
|
|
1533
|
+
* its retries) aborts every other in-flight + pending unit and rejects the run
|
|
1534
|
+
* with that error; later failures are ignored. An empty `inputs` resolves to `[]`.
|
|
1535
|
+
*
|
|
1536
|
+
* @param inputs - The declared units to run
|
|
1537
|
+
* @returns The units' results, in order (declared first, then spawns)
|
|
1538
|
+
*/
|
|
1539
|
+
execute(inputs: readonly TInput[]): Promise<readonly TResult[]>;
|
|
1540
|
+
/**
|
|
1541
|
+
* Cancel every in-flight + pending unit (and the backing queue), making a running
|
|
1542
|
+
* `execute` reject.
|
|
1543
|
+
*
|
|
1544
|
+
* @param reason - An optional cancellation reason propagated to every unit's signal
|
|
1545
|
+
*/
|
|
1546
|
+
abort(reason?: unknown): void;
|
|
1547
|
+
/** Tear the runner down — `abort` plus stop the backing queue; idempotent. */
|
|
1548
|
+
destroy(): void;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Options for `createRunner`.
|
|
1553
|
+
*
|
|
1554
|
+
* @remarks
|
|
1555
|
+
* - `handler` — runs each unit's work against its {@link ControllerInterface};
|
|
1556
|
+
* rejecting fails the unit (and, after retries are exhausted, fails the run).
|
|
1557
|
+
* - `concurrency` — the maximum units in flight at once; defaults to `1` (ordered,
|
|
1558
|
+
* one-at-a-time). Floored at `1`.
|
|
1559
|
+
* - `retries` — the default extra attempts per unit on failure (or a per-attempt
|
|
1560
|
+
* timeout); defaults to `0`.
|
|
1561
|
+
* - `timeout` — the per-attempt deadline in milliseconds; defaults to none (a
|
|
1562
|
+
* non-positive value means no deadline).
|
|
1563
|
+
* - `entries` — per-entry `retries` / `timeout` overrides, resolved from each
|
|
1564
|
+
* input; falls back to the runner-level `retries` / `timeout` defaults.
|
|
1565
|
+
* - `on` — the reserved {@link EmitterHooks} key (§8): initial listeners for the runner's
|
|
1566
|
+
* {@link RunnerEventMap}, wired at construction (e.g. `{ finish: (r) => log(r) }`).
|
|
1567
|
+
*/
|
|
1568
|
+
export declare interface RunnerOptions<TInput, TResult> {
|
|
1569
|
+
readonly on?: EmitterHooks<RunnerEventMap<TResult>>;
|
|
1570
|
+
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
1571
|
+
readonly error?: EmitterErrorHandler;
|
|
1572
|
+
readonly handler: RunnerHandler<TInput, TResult>;
|
|
1573
|
+
readonly concurrency?: number;
|
|
1574
|
+
readonly retries?: number;
|
|
1575
|
+
readonly timeout?: number;
|
|
1576
|
+
/** Per-entry `retries` / `timeout` overrides, resolved from each input; falls back to the runner-level defaults. */
|
|
1577
|
+
readonly entries?: (input: TInput) => RunnerEntryOptions;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* One unit the {@link RunnerInterface} is tracking: the queue payload it was enqueued
|
|
1582
|
+
* with — its `id` (a random UUID) keys it in the runner's ordered launch list and value
|
|
1583
|
+
* map, and `input` is the unit's work payload handed to the handler's `Controller`.
|
|
1584
|
+
*
|
|
1585
|
+
* @remarks
|
|
1586
|
+
* The runner's internal bookkeeping shape, published through the barrel because §5
|
|
1587
|
+
* centralizes every file-local type — declared or spawned, every unit flows through the
|
|
1588
|
+
* one queue as a `RunnerUnit`, so backpressure / ordering / retries / timeout stay the
|
|
1589
|
+
* Queue's behavior and the runner adds only orchestration.
|
|
1590
|
+
*
|
|
1591
|
+
* @typeParam TInput - The unit's work input
|
|
1592
|
+
*/
|
|
1593
|
+
export declare interface RunnerUnit<TInput> {
|
|
1594
|
+
readonly id: string;
|
|
1595
|
+
readonly input: TInput;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/**
|
|
1599
|
+
* The safe cross-environment cooperative-yield default — a {@link SchedulerInterface}
|
|
1600
|
+
* built on `setTimeout` / `clearTimeout` alone, so it runs unchanged in both the
|
|
1601
|
+
* browser and Node.
|
|
1602
|
+
*
|
|
1603
|
+
* @remarks
|
|
1604
|
+
* - **Cross-environment.** Uses ONLY `setTimeout` / `clearTimeout` — universally
|
|
1605
|
+
* available. It deliberately avoids env-specific fast paths (`setImmediate`,
|
|
1606
|
+
* `scheduler.yield`, `requestAnimationFrame`, `node:timers/promises`,
|
|
1607
|
+
* `MessageChannel`); those belong to the environment backends, built with the
|
|
1608
|
+
* agent loop that consumes them.
|
|
1609
|
+
* - **`yield` is a macrotask host-turn, not a microtask.** `yield()` waits on a
|
|
1610
|
+
* `setTimeout(0)`, NOT `queueMicrotask`. A microtask drains before the host
|
|
1611
|
+
* regains control, so it would not actually let pending I/O, timers, or
|
|
1612
|
+
* rendering run — it only defers within the current task. A zero-delay timer is
|
|
1613
|
+
* the correct cross-environment "give the host a turn".
|
|
1614
|
+
* - **Abort-aware.** A pending `yield` / `delay` rejects with `signal.reason` when
|
|
1615
|
+
* the signal aborts (the standard `AbortSignal` convention). An already-aborted
|
|
1616
|
+
* signal rejects immediately without arming a timer. Either settle path clears
|
|
1617
|
+
* the timer and removes the abort listener — no leaked timer, no leaked
|
|
1618
|
+
* listener, and no double-settle.
|
|
1619
|
+
* - **Priority is accepted but uniform.** `options.priority` is part of the
|
|
1620
|
+
* contract, but a `setTimeout`-based default cannot act on urgency, so it treats
|
|
1621
|
+
* every priority the same. Environment backends honour it.
|
|
1622
|
+
* - **Event-free.** A pure functional primitive — no Emitter, no events.
|
|
1623
|
+
*
|
|
1624
|
+
* @example
|
|
1625
|
+
* ```ts
|
|
1626
|
+
* const scheduler = new Scheduler()
|
|
1627
|
+
* while (!signal.aborted) {
|
|
1628
|
+
* doSomeWork()
|
|
1629
|
+
* await scheduler.yield({ signal }) // let the host run between work units
|
|
1630
|
+
* }
|
|
1631
|
+
* ```
|
|
1632
|
+
*/
|
|
1633
|
+
export declare class Scheduler implements SchedulerInterface {
|
|
1634
|
+
#private;
|
|
1635
|
+
/**
|
|
1636
|
+
* Yield control back to the host so other tasks (I/O, timers, rendering) can
|
|
1637
|
+
* run, then resume — a macrotask turn via `setTimeout(0)` (NOT a microtask,
|
|
1638
|
+
* which would resume before the host regains control).
|
|
1639
|
+
*/
|
|
1640
|
+
yield(options?: SchedulerOptions): Promise<void>;
|
|
1641
|
+
/**
|
|
1642
|
+
* Resume after at least `ms` milliseconds; abort rejects with `signal.reason`.
|
|
1643
|
+
*
|
|
1644
|
+
* @remarks
|
|
1645
|
+
* `ms` should be a non-negative finite number. The primitive stays minimal and
|
|
1646
|
+
* does no validation: it passes `ms` straight to the host `setTimeout`, which
|
|
1647
|
+
* clamps a negative value or `NaN` to ~0 — so an out-of-domain `ms` resolves on
|
|
1648
|
+
* the next host turn rather than throwing.
|
|
1649
|
+
*/
|
|
1650
|
+
delay(ms: number, options?: SchedulerOptions): Promise<void>;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* A cooperative host-yield primitive: a loop decides WHAT to do; the scheduler
|
|
1655
|
+
* decides WHEN the host regains control. Abort-aware — a pending yield/delay
|
|
1656
|
+
* rejects with the signal's reason when aborted.
|
|
1657
|
+
*/
|
|
1658
|
+
export declare interface SchedulerInterface {
|
|
1659
|
+
/**
|
|
1660
|
+
* Yield control back to the host so other tasks (I/O, timers, rendering) can
|
|
1661
|
+
* run, then resume.
|
|
1662
|
+
*/
|
|
1663
|
+
yield(options?: SchedulerOptions): Promise<void>;
|
|
1664
|
+
/** Resume after at least `ms` milliseconds. */
|
|
1665
|
+
delay(ms: number, options?: SchedulerOptions): Promise<void>;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/** Options for a single cooperative yield/delay. */
|
|
1669
|
+
export declare interface SchedulerOptions {
|
|
1670
|
+
/**
|
|
1671
|
+
* A relative urgency hint. Honoured by environment backends; the
|
|
1672
|
+
* cross-environment default treats all priorities the same.
|
|
1673
|
+
*/
|
|
1674
|
+
readonly priority?: SchedulerPriority;
|
|
1675
|
+
/** A signal whose abort rejects a pending yield/delay with its `reason`. */
|
|
1676
|
+
readonly signal?: AbortSignal;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/**
|
|
1680
|
+
* Relative urgency hint for cooperative scheduling. Honoured by environment
|
|
1681
|
+
* backends; the cross-environment default treats all priorities uniformly.
|
|
1682
|
+
*/
|
|
1683
|
+
export declare type SchedulerPriority = 'user' | 'normal' | 'background';
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* The shape of ONE flat step — `{ name, via? }` — the building block of
|
|
1687
|
+
* {@link workflowStepsShape}.
|
|
1688
|
+
*
|
|
1689
|
+
* @remarks
|
|
1690
|
+
* `name` is the REGISTERED behavior name the step runs (it becomes the task's `run.name`);
|
|
1691
|
+
* `via` is the optional execution mechanism (defaults to `'function'` when omitted). The
|
|
1692
|
+
* tool expands each step into a one-task phase, in order
|
|
1693
|
+
* ({@link import('./helpers.js').expandSteps}).
|
|
1694
|
+
*/
|
|
1695
|
+
export declare const stepShape: ObjectShape<{
|
|
1696
|
+
name: StringShape;
|
|
1697
|
+
via: OptionalShape<LiteralShape<readonly ["function", "tool", "agent"]>>;
|
|
1698
|
+
}>;
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Convert one flat {@link WorkflowStep} into a {@link TaskForm} — `name` → the form's `name`,
|
|
1702
|
+
* `via` → the form's discriminant (defaulting to `'function'`).
|
|
1703
|
+
*
|
|
1704
|
+
* @param step - The flat step
|
|
1705
|
+
* @returns The {@link TaskForm} the step's task runs
|
|
1706
|
+
*/
|
|
1707
|
+
export declare function stepToForm(step: WorkflowStep): TaskForm;
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* The live leaf state machine (W-b) for one task — an observable (AGENTS §13), guarded
|
|
1711
|
+
* synchronous task whose explicit {@link TaskStatus} advances through the AGENTS §10
|
|
1712
|
+
* transitions, recording a {@link TaskResult} on a terminal outcome.
|
|
1713
|
+
*
|
|
1714
|
+
* @remarks
|
|
1715
|
+
* - **Guarded transitions (AGENTS §10).** `start` (→ `running`), then `complete(value)`
|
|
1716
|
+
* (→ `completed`, records a {@link import('@orkestrel/contract').Success}), `fail(error)`
|
|
1717
|
+
* (→ `failed`, records a {@link import('@orkestrel/contract').Failure}), `skip` (→ `skipped`),
|
|
1718
|
+
* `stop` (→ `stopped`). Each consults {@link canTransitionTask} FIRST and throws a
|
|
1719
|
+
* `TRANSITION` {@link WorkflowError} on an illegal move (e.g. completing a non-`running`
|
|
1720
|
+
* task) — the legal graph is the single source of truth, so the leaf can never reach an
|
|
1721
|
+
* impossible state.
|
|
1722
|
+
* - **Override (snapshot fidelity).** `skip` / `stop` set `#override` to the forced terminal
|
|
1723
|
+
* status, so a RESTORE can tell a forced leaf (`skipped` / `stopped`) from a run-produced
|
|
1724
|
+
* one and reinstate it AS an override — preserving the round-trip.
|
|
1725
|
+
* - **The cascade.** Every status change records its boxed result (when any), fires the leaf's
|
|
1726
|
+
* OWN event, THEN calls the parent phase's `#recompute` (injected at construction) so the
|
|
1727
|
+
* transition propagates UP (Task → Phase → Workflow re-derive). The own-event-before-cascade
|
|
1728
|
+
* order means an observer sees the CAUSE (this leaf changed) before the EFFECT (the parents
|
|
1729
|
+
* re-derive) — the project precedent (`Runner.#settle` emits its own `fail` before propagating).
|
|
1730
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link TaskEventMap}) fires the
|
|
1731
|
+
* matching event strictly AFTER the state change, BEFORE the cascade; the emitter isolates
|
|
1732
|
+
* a listener throw and routes it to its `error` handler (the `error` option), so a buggy
|
|
1733
|
+
* observer can never corrupt a transition.
|
|
1734
|
+
*/
|
|
1735
|
+
export declare class Task implements TaskInterface {
|
|
1736
|
+
#private;
|
|
1737
|
+
constructor(context: TaskContext, phase: PhaseInterface, workflow: WorkflowInterface, recompute: () => void, options?: TaskOptions, status?: TaskStatus, result?: TaskResult);
|
|
1738
|
+
get emitter(): EmitterInterface<TaskEventMap>;
|
|
1739
|
+
get id(): string;
|
|
1740
|
+
get name(): string;
|
|
1741
|
+
get description(): string | undefined;
|
|
1742
|
+
get context(): TaskContext;
|
|
1743
|
+
get phase(): PhaseInterface;
|
|
1744
|
+
get workflow(): WorkflowInterface;
|
|
1745
|
+
get status(): TaskStatus;
|
|
1746
|
+
get result(): TaskResult | undefined;
|
|
1747
|
+
start(): void;
|
|
1748
|
+
complete(value: unknown): void;
|
|
1749
|
+
fail(error: unknown): void;
|
|
1750
|
+
skip(): void;
|
|
1751
|
+
stop(): void;
|
|
1752
|
+
snapshot(): TaskSnapshot;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* Every {@link TaskStatus} value, frozen — the lifecycle vocabulary of a task.
|
|
1757
|
+
*
|
|
1758
|
+
* @remarks
|
|
1759
|
+
* Ordered pending → running → terminal (`completed` / `failed` / `skipped` /
|
|
1760
|
+
* `stopped`). The source of truth for the union; compose guards / shapes from it.
|
|
1761
|
+
*/
|
|
1762
|
+
export declare const TASK_STATUSES: readonly TaskStatus[];
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* The legal {@link TaskStatus} transition graph of the live W-b task state machine —
|
|
1766
|
+
* each current status mapped to the statuses it may move to directly, frozen.
|
|
1767
|
+
*
|
|
1768
|
+
* @remarks
|
|
1769
|
+
* The source of truth behind {@link import('./helpers.js').canTransitionTask} and the
|
|
1770
|
+
* `TRANSITION` guard ({@link import('./errors.js').WorkflowError}). A `pending` task may
|
|
1771
|
+
* `start` (→ `running`), `skip` (→ `skipped`), or `stop` (→ `stopped`); a `running` task
|
|
1772
|
+
* may `complete` (→ `completed`), `fail` (→ `failed`), `skip` (→ `skipped`), or `stop`
|
|
1773
|
+
* (→ `stopped`). Every terminal status maps to an empty list — a settled task never
|
|
1774
|
+
* transitions again. So completing a non-`running` task, or starting a settled one, is
|
|
1775
|
+
* rejected.
|
|
1776
|
+
*/
|
|
1777
|
+
export declare const TASK_TRANSITIONS: Readonly<Record<TaskStatus, readonly TaskStatus[]>>;
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* The three task-form mechanisms (the {@link TaskVia} discriminant), frozen.
|
|
1781
|
+
*
|
|
1782
|
+
* @remarks
|
|
1783
|
+
* The runtime source of truth for the `via` axis — drive the contract's literal
|
|
1784
|
+
* shape and any guard from this array rather than repeating the literals.
|
|
1785
|
+
*/
|
|
1786
|
+
export declare const TASK_VIAS: readonly TaskVia[];
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* The ambient context of a task — its own identity plus a back-reference to the
|
|
1790
|
+
* phase (and, transitively, the workflow) it belongs to.
|
|
1791
|
+
*
|
|
1792
|
+
* @remarks
|
|
1793
|
+
* Extends {@link WorkflowContext} and adds `phase`, the parent {@link PhaseContext}
|
|
1794
|
+
* — so a task carries its FULL lineage (workflow → phase → task) for a
|
|
1795
|
+
* {@link TaskResult} or a runner.
|
|
1796
|
+
*/
|
|
1797
|
+
export declare interface TaskContext extends WorkflowContext {
|
|
1798
|
+
readonly phase: PhaseContext;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
/**
|
|
1802
|
+
* The lean per-task handle a {@link import('./types.js').WorkflowFunction} receives — the
|
|
1803
|
+
* running task's folded cancellation, its input, its lineage, and read-UP access to the
|
|
1804
|
+
* result tree.
|
|
1805
|
+
*
|
|
1806
|
+
* @remarks
|
|
1807
|
+
* - **A leaf handle, NOT the runner `Controller`.** A workflow task is a leaf of the
|
|
1808
|
+
* declarative W-b tree, not a fan-out unit, so this carries none of the runner
|
|
1809
|
+
* `Controller`'s `spawn` / `wait` — only what a leaf needs.
|
|
1810
|
+
* - **Folded signal.** `signal` is the cancellation the runner folds for THIS run: it fires
|
|
1811
|
+
* on a workflow-level abort / timeout / budget ceiling, or — under `bail: true` — when a
|
|
1812
|
+
* sibling task fails (the runner aborts the in-flight siblings via the substrate's
|
|
1813
|
+
* fail-fast). A handler races its work against it; `aborted` reads it.
|
|
1814
|
+
* - **Input + lineage.** `input` is the task's open `metadata` bag (its
|
|
1815
|
+
* {@link import('./types.js').TaskInput} payload, `{}` when none); `task` is the full
|
|
1816
|
+
* {@link TaskContext}, so `task.phase` / `task.phase.workflow` navigate UP the lineage.
|
|
1817
|
+
* - **Read-up results.** `results()` returns every settled task's {@link TaskResult} across
|
|
1818
|
+
* the phases that have already finished (a closure over the live
|
|
1819
|
+
* {@link import('./types.js').WorkflowInterface}), so a `function` task can read an earlier
|
|
1820
|
+
* phase's output. Read-only — a task records its OWN outcome by returning / throwing, not
|
|
1821
|
+
* by mutating the tree.
|
|
1822
|
+
* - **Event-free.** Like the runner `Controller`, the per-task handle carries no Emitter;
|
|
1823
|
+
* observe the W-b entities' own emitters (`task.emitter` / `phase.emitter`) instead.
|
|
1824
|
+
*/
|
|
1825
|
+
export declare class TaskController implements TaskControllerInterface {
|
|
1826
|
+
#private;
|
|
1827
|
+
readonly signal: AbortSignal;
|
|
1828
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
1829
|
+
readonly task: TaskContext;
|
|
1830
|
+
constructor(signal: AbortSignal, input: Readonly<Record<string, unknown>>, task: TaskContext, results: () => readonly TaskResult[]);
|
|
1831
|
+
get aborted(): boolean;
|
|
1832
|
+
results(): readonly TaskResult[];
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* The per-task handle a {@link WorkflowFunction} receives — the running task's
|
|
1837
|
+
* cancellation, its input, its lineage, and read-UP access to the result tree.
|
|
1838
|
+
*
|
|
1839
|
+
* @remarks
|
|
1840
|
+
* A NEW, lean handle (NOT the runner `Controller` — it carries no `spawn` / `wait`; a
|
|
1841
|
+
* workflow task is a leaf of the declarative tree, not a fan-out unit). It exposes:
|
|
1842
|
+
* - `signal` — the task's folded cancellation: fires on a workflow-level `abort` /
|
|
1843
|
+
* `timeout` / `budget` ceiling, or — under `bail: true` — when a sibling task fails (the
|
|
1844
|
+
* runner aborts the in-flight siblings). A handler races its work against it.
|
|
1845
|
+
* - `aborted` — whether `signal` has fired.
|
|
1846
|
+
* - `input` — the task's `metadata` bag (the open consumer payload from its
|
|
1847
|
+
* {@link TaskInput}); `{}` when none.
|
|
1848
|
+
* - `task` — the task's full {@link TaskContext} (so `task.phase` / `task.phase.workflow`
|
|
1849
|
+
* navigate UP the lineage).
|
|
1850
|
+
* - `results()` — every settled task's {@link TaskResult} across already-finished phases,
|
|
1851
|
+
* so a `function` task can read an earlier phase's output (the W-b result tree, read-only).
|
|
1852
|
+
*/
|
|
1853
|
+
export declare interface TaskControllerInterface {
|
|
1854
|
+
/** Fires on a workflow-level abort / timeout / budget, or a sibling failure under `bail: true`. */
|
|
1855
|
+
readonly signal: AbortSignal;
|
|
1856
|
+
readonly aborted: boolean;
|
|
1857
|
+
/** The task's open `metadata` bag (its {@link TaskInput} payload); `{}` when none. */
|
|
1858
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
1859
|
+
readonly task: TaskContext;
|
|
1860
|
+
/** Every settled task's result across already-finished phases — the result tree, read-only. */
|
|
1861
|
+
results(): readonly TaskResult[];
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
/**
|
|
1865
|
+
* The serializable definition of one task — its identity plus the behavior it runs
|
|
1866
|
+
* (a {@link TaskForm}, referenced by name).
|
|
1867
|
+
*
|
|
1868
|
+
* @remarks
|
|
1869
|
+
* Pure JSON DATA: a UI or an LLM authors it, it round-trips through the contract
|
|
1870
|
+
* (factories.ts), and it carries NO functions. `id` is the positional identity
|
|
1871
|
+
* within its phase; `name` is the human label; `description` is optional prose;
|
|
1872
|
+
* `run` is the {@link TaskForm} naming the registered behavior.
|
|
1873
|
+
*/
|
|
1874
|
+
export declare interface TaskDefinition {
|
|
1875
|
+
readonly id: string;
|
|
1876
|
+
readonly name: string;
|
|
1877
|
+
readonly description?: string;
|
|
1878
|
+
readonly run: TaskForm;
|
|
1879
|
+
/**
|
|
1880
|
+
* @remarks
|
|
1881
|
+
* Extra attempts after the first on failure (a non-negative integer); the runner threads it
|
|
1882
|
+
* to this task's substrate unit, OVERRIDING the phase Runner's `retries` default. Omitted ⇒
|
|
1883
|
+
* the default (no extra attempts). Execution-only: it is NOT persisted in a snapshot.
|
|
1884
|
+
*/
|
|
1885
|
+
readonly retries?: number;
|
|
1886
|
+
/**
|
|
1887
|
+
* @remarks
|
|
1888
|
+
* The per-attempt deadline in milliseconds (a non-negative integer); the runner threads it to
|
|
1889
|
+
* this task's substrate unit, OVERRIDING the phase Runner's `timeout` default. Omitted (or a
|
|
1890
|
+
* non-positive value) ⇒ no deadline. Execution-only: it is NOT persisted in a snapshot.
|
|
1891
|
+
*/
|
|
1892
|
+
readonly timeout?: number;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
/**
|
|
1896
|
+
* Convert one {@link import('./types.js').TaskDefinition} into an initial, `pending`
|
|
1897
|
+
* {@link TaskSnapshot} — the per-task leaf step of {@link definitionToSnapshot} (no
|
|
1898
|
+
* result yet, empty metadata).
|
|
1899
|
+
*
|
|
1900
|
+
* @param task - The task definition to seed from
|
|
1901
|
+
* @returns An initial {@link TaskSnapshot}
|
|
1902
|
+
*/
|
|
1903
|
+
export declare function taskDefinitionToSnapshot(task: WorkflowDefinition['phases'][number]['tasks'][number]): TaskSnapshot;
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* A draft task — a {@link TaskDefinition} with OPTIONAL `id` / `name`.
|
|
1907
|
+
*
|
|
1908
|
+
* @remarks
|
|
1909
|
+
* The tool synthesizes a missing `id` positionally and defaults a missing `name` to
|
|
1910
|
+
* its `id` ({@link import('./helpers.js').completeDraft}). A PROVIDED `id` / `name` is
|
|
1911
|
+
* preserved verbatim (and must be non-empty — the draft contract's `minLength: 1`).
|
|
1912
|
+
*/
|
|
1913
|
+
export declare interface TaskDraft {
|
|
1914
|
+
readonly id?: string;
|
|
1915
|
+
readonly name?: string;
|
|
1916
|
+
readonly description?: string;
|
|
1917
|
+
readonly run: TaskForm;
|
|
1918
|
+
/** Extra attempts after the first on failure (a non-negative integer); overrides the phase Runner default. Execution-only. */
|
|
1919
|
+
readonly retries?: number;
|
|
1920
|
+
/** The per-attempt deadline in milliseconds (a non-negative integer); overrides the phase Runner default. Execution-only. */
|
|
1921
|
+
readonly timeout?: number;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/**
|
|
1925
|
+
* The shape of a TASK in a draft workflow — identical to {@link taskShape} EXCEPT `id`
|
|
1926
|
+
* and `name` are OPTIONAL (the tool synthesizes any missing one positionally).
|
|
1927
|
+
*
|
|
1928
|
+
* @remarks
|
|
1929
|
+
* A PROVIDED `id` / `name` still carries `minLength: 1`, so an explicitly-empty `id: ''`
|
|
1930
|
+
* is INVALID (rejected by the draft contract), never auto-filled — keeping "garbage"
|
|
1931
|
+
* distinct from "omitted". `run` stays required.
|
|
1932
|
+
*/
|
|
1933
|
+
export declare const taskDraftShape: ObjectShape<{
|
|
1934
|
+
id: OptionalShape<StringShape>;
|
|
1935
|
+
name: OptionalShape<StringShape>;
|
|
1936
|
+
description: OptionalShape<StringShape>;
|
|
1937
|
+
run: UnionShape<[ ObjectShape<{
|
|
1938
|
+
via: LiteralShape<readonly ["function"]>;
|
|
1939
|
+
name: StringShape;
|
|
1940
|
+
}>, ObjectShape<{
|
|
1941
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
1942
|
+
name: StringShape;
|
|
1943
|
+
}>, ObjectShape<{
|
|
1944
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
1945
|
+
name: StringShape;
|
|
1946
|
+
}>]>;
|
|
1947
|
+
retries: OptionalShape<NumberShape>;
|
|
1948
|
+
timeout: OptionalShape<NumberShape>;
|
|
1949
|
+
}>;
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* The push observation surface (AGENTS §13) of the task entity (W-b) — the
|
|
1953
|
+
* lifecycle moments of one task.
|
|
1954
|
+
*
|
|
1955
|
+
* @remarks
|
|
1956
|
+
* `start` fires when the task begins; `complete` when it finishes successfully
|
|
1957
|
+
* (carrying its {@link TaskResult}); `fail` when it errors (carrying the result);
|
|
1958
|
+
* `skip` when it is intentionally not executed; `stop` when it is ended early. A
|
|
1959
|
+
* throwing listener is isolated by the emitter and routed to its `error` handler,
|
|
1960
|
+
* not the domain surface (AGENTS §13). A `type` alias (AGENTS §4.5) so it satisfies
|
|
1961
|
+
* `EventMap`.
|
|
1962
|
+
*/
|
|
1963
|
+
export declare type TaskEventMap = {
|
|
1964
|
+
/** The task began — its `id`. */
|
|
1965
|
+
readonly start: readonly [id: string];
|
|
1966
|
+
/** The task finished successfully — its result. */
|
|
1967
|
+
readonly complete: readonly [result: TaskResult];
|
|
1968
|
+
/** The task failed — its result. */
|
|
1969
|
+
readonly fail: readonly [result: TaskResult];
|
|
1970
|
+
/** The task was intentionally skipped. */
|
|
1971
|
+
readonly skip: readonly [];
|
|
1972
|
+
/** The task was permanently stopped. */
|
|
1973
|
+
readonly stop: readonly [];
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* A {@link TaskDefinition}'s behavior reference — a descriptive tagged union over
|
|
1978
|
+
* the three execution forms a task can take, discriminated by `via` (the axis is
|
|
1979
|
+
* the execution MECHANISM, never a bare `kind`; AGENTS §4.4). Each form references
|
|
1980
|
+
* its behavior BY NAME through a registry (the runner resolves the name at W-b);
|
|
1981
|
+
* a definition NEVER inlines a function, so the whole tree stays JSON-serializable.
|
|
1982
|
+
*
|
|
1983
|
+
* @remarks
|
|
1984
|
+
* - `function` — a registered function the runner invokes directly.
|
|
1985
|
+
* - `tool` — a registered {@link ToolInterface} the
|
|
1986
|
+
* runner executes as a tool call.
|
|
1987
|
+
* - `agent` — a registered agent (a subagent). It will later carry a depth/cycle
|
|
1988
|
+
* guard (W-c, bounded by {@link import('./constants.js').MAX_WORKFLOW_DEPTH}); in
|
|
1989
|
+
* W-a it is purely the typed reference.
|
|
1990
|
+
*
|
|
1991
|
+
* `name` is the registry key (it identifies the behavior the form runs). A
|
|
1992
|
+
* descriptive `via` literal (not `kind`) names the axis that varies, and the
|
|
1993
|
+
* `is*` guards narrow on `run.via` cleanly.
|
|
1994
|
+
*/
|
|
1995
|
+
export declare type TaskForm = {
|
|
1996
|
+
readonly via: 'function';
|
|
1997
|
+
readonly name: string;
|
|
1998
|
+
} | {
|
|
1999
|
+
readonly via: 'tool';
|
|
2000
|
+
readonly name: string;
|
|
2001
|
+
} | {
|
|
2002
|
+
readonly via: 'agent';
|
|
2003
|
+
readonly name: string;
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* The shape of a {@link import('./types.js').TaskForm} — a descriptive tagged union
|
|
2008
|
+
* over the three execution mechanisms, discriminated by the `via` literal (never a
|
|
2009
|
+
* bare `kind`; AGENTS §4.4). Each variant pairs the `via` discriminant with a `name`
|
|
2010
|
+
* (the registry key for the behavior).
|
|
2011
|
+
*
|
|
2012
|
+
* @remarks
|
|
2013
|
+
* The union and each `via` literal + `name` carry a `description` so the emitted JSON
|
|
2014
|
+
* Schema spells out what the discriminant means and that `name` is a REGISTERED key
|
|
2015
|
+
* (not a human label) — the field-level guidance a small model needs to fill `run`.
|
|
2016
|
+
*/
|
|
2017
|
+
export declare const taskFormShape: UnionShape<[ ObjectShape<{
|
|
2018
|
+
via: LiteralShape<readonly ["function"]>;
|
|
2019
|
+
name: StringShape;
|
|
2020
|
+
}>, ObjectShape<{
|
|
2021
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
2022
|
+
name: StringShape;
|
|
2023
|
+
}>, ObjectShape<{
|
|
2024
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
2025
|
+
name: StringShape;
|
|
2026
|
+
}>]>;
|
|
2027
|
+
|
|
2028
|
+
/** Initial {@link TaskEventMap} listeners — the reserved `on` option (AGENTS §8). */
|
|
2029
|
+
export declare type TaskHooks = EmitterHooks<TaskEventMap>;
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* The minimal data to create a task context — a partial {@link TaskContext} plus
|
|
2033
|
+
* any creation-only fields.
|
|
2034
|
+
*
|
|
2035
|
+
* @remarks
|
|
2036
|
+
* `metadata` is an open consumer bag the workflow system stores and carries into a
|
|
2037
|
+
* {@link TaskSnapshot} but never interprets. All members are optional (the storing
|
|
2038
|
+
* layer fills identity / lineage).
|
|
2039
|
+
*/
|
|
2040
|
+
export declare interface TaskInput extends Partial<TaskContext> {
|
|
2041
|
+
/** An open consumer bag — stored and snapshotted, never interpreted by the workflow. */
|
|
2042
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* The live leaf state machine (W-b) for one {@link TaskDefinition} — an observable
|
|
2047
|
+
* (AGENTS §13), guarded synchronous task whose explicit {@link TaskStatus} advances
|
|
2048
|
+
* through the AGENTS §10 transitions.
|
|
2049
|
+
*
|
|
2050
|
+
* @remarks
|
|
2051
|
+
* - **Identity + lineage.** `id` / `name` / `description` mirror the definition;
|
|
2052
|
+
* `context` is the task's full {@link TaskContext} (so `context.phase` /
|
|
2053
|
+
* `context.phase.workflow` navigate UP the tree), and `phase` / `workflow` are the
|
|
2054
|
+
* live parent entities for direct lineage navigation.
|
|
2055
|
+
* - **State machine (AGENTS §10).** `status` is the explicit current state. `start`
|
|
2056
|
+
* moves `pending → running`; the terminal transitions are `complete(value)` (records
|
|
2057
|
+
* a {@link import('@orkestrel/contract').Success}), `fail(error)` (records a
|
|
2058
|
+
* {@link import('@orkestrel/contract').Failure}), `skip` (AGENTS §10 — intentionally not run),
|
|
2059
|
+
* and `stop` (AGENTS §10 — ended early). Each is GUARDED: an illegal transition (e.g.
|
|
2060
|
+
* completing a non-`running` task) throws a {@link import('./errors.js').WorkflowError}.
|
|
2061
|
+
* `skip` / `stop` set the override so the leaf's terminal state survives a snapshot.
|
|
2062
|
+
* - **Result.** `result` is the recorded {@link TaskResult} once the task settled with an
|
|
2063
|
+
* outcome (`completed` / `failed`), else `undefined` — the lineage-navigable leaf of the
|
|
2064
|
+
* result tree.
|
|
2065
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link TaskEventMap}) fires
|
|
2066
|
+
* `start` / `complete` / `fail` / `skip` / `stop` strictly AFTER each transition; the
|
|
2067
|
+
* emitter isolates a listener throw and routes it to its `error` handler (the `error` option).
|
|
2068
|
+
*/
|
|
2069
|
+
export declare interface TaskInterface {
|
|
2070
|
+
readonly emitter: EmitterInterface<TaskEventMap>;
|
|
2071
|
+
readonly id: string;
|
|
2072
|
+
readonly name: string;
|
|
2073
|
+
readonly description?: string;
|
|
2074
|
+
readonly context: TaskContext;
|
|
2075
|
+
readonly phase: PhaseInterface;
|
|
2076
|
+
readonly workflow: WorkflowInterface;
|
|
2077
|
+
readonly status: TaskStatus;
|
|
2078
|
+
/** The recorded outcome once the task settled with one (`completed` / `failed`), else `undefined`. */
|
|
2079
|
+
readonly result: TaskResult | undefined;
|
|
2080
|
+
start(): void;
|
|
2081
|
+
complete(value: unknown): void;
|
|
2082
|
+
fail(error: unknown): void;
|
|
2083
|
+
skip(): void;
|
|
2084
|
+
stop(): void;
|
|
2085
|
+
snapshot(): TaskSnapshot;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/**
|
|
2089
|
+
* The lean child manager (AGENTS §9) of a {@link import('../phases/Phase.js').Phase}'s live
|
|
2090
|
+
* tasks — an insertion-ordered registry keyed by task `id`, so positional order is
|
|
2091
|
+
* preserved across an interior `skip` / `remove`.
|
|
2092
|
+
*
|
|
2093
|
+
* @remarks
|
|
2094
|
+
* - **Positional store.** Tasks live in an insertion-ordered `Map` keyed by `id`;
|
|
2095
|
+
* `append` adds one at the end (the build-time wiring path), `task(id)` looks one up,
|
|
2096
|
+
* `tasks()` lists them in positional order, `count` is the size. A `skip` is a STATUS
|
|
2097
|
+
* change on a stored task (never a removal), so order survives it; a snapshot RESTORE
|
|
2098
|
+
* re-`append`s in the snapshot's order, reproducing it exactly.
|
|
2099
|
+
* - **No batch matrix.** A phase's tasks are a fixed positional set, so AGENTS §9.2 (the
|
|
2100
|
+
* bulk verb overloads) is deliberately omitted — there is no `remove` family here.
|
|
2101
|
+
* - **Event-free.** A purely structural container — the live {@link TaskInterface}s own
|
|
2102
|
+
* their own emitters; the manager observes nothing.
|
|
2103
|
+
*
|
|
2104
|
+
* @example
|
|
2105
|
+
* ```ts
|
|
2106
|
+
* const tasks = new TaskManager()
|
|
2107
|
+
* tasks.append(task) // a live Task
|
|
2108
|
+
* tasks.task(task.id) // the same task
|
|
2109
|
+
* tasks.count // 1
|
|
2110
|
+
* ```
|
|
2111
|
+
*/
|
|
2112
|
+
export declare class TaskManager implements TaskManagerInterface {
|
|
2113
|
+
#private;
|
|
2114
|
+
get count(): number;
|
|
2115
|
+
append(task: TaskInterface): void;
|
|
2116
|
+
task(id: string): TaskInterface | undefined;
|
|
2117
|
+
tasks(): readonly TaskInterface[];
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/**
|
|
2121
|
+
* The lean child manager (AGENTS §9) of a {@link PhaseInterface}'s live tasks —
|
|
2122
|
+
* positional accessors plus `count`, backed by an insertion-ordered store so order
|
|
2123
|
+
* is preserved across an interior `skip` / `remove`.
|
|
2124
|
+
*
|
|
2125
|
+
* @remarks
|
|
2126
|
+
* `append` adds one live {@link TaskInterface} at the end (the build-time wiring path);
|
|
2127
|
+
* `task(id)` looks one up; `tasks()` lists them in positional order; `count` is the
|
|
2128
|
+
* tally. No batch matrix (AGENTS §9.2 is deliberately omitted — a phase's tasks are a
|
|
2129
|
+
* fixed positional set, not a bulk-mutated collection).
|
|
2130
|
+
*/
|
|
2131
|
+
export declare interface TaskManagerInterface {
|
|
2132
|
+
readonly count: number;
|
|
2133
|
+
append(task: TaskInterface): void;
|
|
2134
|
+
task(id: string): TaskInterface | undefined;
|
|
2135
|
+
tasks(): readonly TaskInterface[];
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* The runtime options for a {@link TaskInterface} — the construction bag the live
|
|
2140
|
+
* leaf state machine (W-b) carries that the W-a {@link TaskDefinition} did not.
|
|
2141
|
+
*
|
|
2142
|
+
* @remarks
|
|
2143
|
+
* The reserved `on` (AGENTS §8) wires initial {@link TaskEventMap} listeners; a
|
|
2144
|
+
* {@link createTask}-built tree threads each level's `on` from its parent options.
|
|
2145
|
+
* `metadata` is the open consumer bag carried verbatim into a {@link TaskSnapshot}
|
|
2146
|
+
* (mirrors {@link TaskInput.metadata}), never interpreted by the workflow.
|
|
2147
|
+
*/
|
|
2148
|
+
export declare interface TaskOptions {
|
|
2149
|
+
readonly on?: TaskHooks;
|
|
2150
|
+
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
2151
|
+
readonly error?: EmitterErrorHandler;
|
|
2152
|
+
/** An open consumer bag — stored and snapshotted, never interpreted by the workflow. */
|
|
2153
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* The structured outcome of a task execution — its full lineage, its terminal
|
|
2158
|
+
* status, the moment it settled, and its boxed produced outcome.
|
|
2159
|
+
*
|
|
2160
|
+
* @remarks
|
|
2161
|
+
* Carries the complete lineage (`task` / `phase` / `workflow` contexts) so a result
|
|
2162
|
+
* is self-describing wherever it travels. `status` is the terminal state this
|
|
2163
|
+
* result records. `result` BOXES the produced outcome in a {@link Result}: it is
|
|
2164
|
+
* PRESENT exactly when `status` is `completed` (a {@link import('@orkestrel/contract').Success})
|
|
2165
|
+
* or `failed` (a {@link import('@orkestrel/contract').Failure}), and ABSENT when `status` is
|
|
2166
|
+
* `skipped` or `stopped` (terminal, but produced no outcome) — a pending/running
|
|
2167
|
+
* task has no result at all (a non-terminal status, per
|
|
2168
|
+
* {@link import('./helpers.js').isTerminalStatus}). This boxed `result` REPLACES separate
|
|
2169
|
+
* `value?` / `error?` fields: a success's payload is `result.value`, a failure's reason is `result.error`.
|
|
2170
|
+
* `timestamp` is when the result was created (ms since epoch).
|
|
2171
|
+
*/
|
|
2172
|
+
export declare interface TaskResult {
|
|
2173
|
+
readonly task: TaskContext;
|
|
2174
|
+
readonly phase: PhaseContext;
|
|
2175
|
+
readonly workflow: WorkflowContext;
|
|
2176
|
+
readonly status: TaskStatus;
|
|
2177
|
+
/** The boxed outcome — present for `completed` (Success) / `failed` (Failure), absent otherwise. */
|
|
2178
|
+
readonly result?: Result<unknown>;
|
|
2179
|
+
readonly timestamp: number;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* The shape of a {@link import('./types.js').TaskDefinition} — identity plus the
|
|
2184
|
+
* behavior reference ({@link taskFormShape}). `description` is optional prose.
|
|
2185
|
+
*/
|
|
2186
|
+
export declare const taskShape: ObjectShape<{
|
|
2187
|
+
id: StringShape;
|
|
2188
|
+
name: StringShape;
|
|
2189
|
+
description: OptionalShape<StringShape>;
|
|
2190
|
+
run: UnionShape<[ ObjectShape<{
|
|
2191
|
+
via: LiteralShape<readonly ["function"]>;
|
|
2192
|
+
name: StringShape;
|
|
2193
|
+
}>, ObjectShape<{
|
|
2194
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
2195
|
+
name: StringShape;
|
|
2196
|
+
}>, ObjectShape<{
|
|
2197
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
2198
|
+
name: StringShape;
|
|
2199
|
+
}>]>;
|
|
2200
|
+
retries: OptionalShape<NumberShape>;
|
|
2201
|
+
timeout: OptionalShape<NumberShape>;
|
|
2202
|
+
}>;
|
|
2203
|
+
|
|
2204
|
+
/**
|
|
2205
|
+
* A JSON-serializable snapshot of one task's state — the leaf of the snapshot tree
|
|
2206
|
+
* the durable store (W-d) persists.
|
|
2207
|
+
*
|
|
2208
|
+
* @remarks
|
|
2209
|
+
* Pure JSON DATA (no class instances, no functions). `result` is the task's
|
|
2210
|
+
* {@link TaskResult} when it has settled with an outcome, else `undefined`.
|
|
2211
|
+
* `metadata` is the open consumer bag carried from the task's {@link TaskInput}.
|
|
2212
|
+
*/
|
|
2213
|
+
export declare interface TaskSnapshot {
|
|
2214
|
+
readonly id: string;
|
|
2215
|
+
readonly name: string;
|
|
2216
|
+
readonly description?: string;
|
|
2217
|
+
readonly status: TaskStatus;
|
|
2218
|
+
readonly result?: TaskResult;
|
|
2219
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* The lifecycle status of a task — `pending` before it runs, `running` while in
|
|
2224
|
+
* flight, then one of the terminal states: `completed` (a success), `failed` (a
|
|
2225
|
+
* genuine error), `skipped` (intentionally not executed, AGENTS §10 `skip`), or
|
|
2226
|
+
* `stopped` (permanently ended, AGENTS §10 `stop`).
|
|
2227
|
+
*
|
|
2228
|
+
* @remarks
|
|
2229
|
+
* A semantic tier of the shared {@link LifecycleStatus} vocabulary. Uses `running` /
|
|
2230
|
+
* `failed` (the project vocabulary), and keeps `skipped` and `stopped` DISTINCT — a
|
|
2231
|
+
* skip is "deliberately not run", a stop is "ended early". The terminal members are
|
|
2232
|
+
* exactly those for which a {@link TaskResult} is meaningful: `completed` / `failed`
|
|
2233
|
+
* box a {@link Result}, while `skipped` / `stopped` are terminal WITHOUT a boxed
|
|
2234
|
+
* outcome. See {@link import('./helpers.js').isTerminalStatus}.
|
|
2235
|
+
*/
|
|
2236
|
+
export declare type TaskStatus = LifecycleStatus;
|
|
2237
|
+
|
|
2238
|
+
/** The discriminant literal of a {@link TaskForm} — the execution mechanism axis. */
|
|
2239
|
+
export declare type TaskVia = TaskForm['via'];
|
|
2240
|
+
|
|
2241
|
+
/**
|
|
2242
|
+
* The {@link TaskStatus} values that are TERMINAL — a task in one of these will
|
|
2243
|
+
* not transition further, frozen.
|
|
2244
|
+
*
|
|
2245
|
+
* @remarks
|
|
2246
|
+
* The source of truth behind {@link import('./helpers.js').isTerminalStatus}.
|
|
2247
|
+
* `pending` and `running` are the only non-terminal members.
|
|
2248
|
+
*/
|
|
2249
|
+
export declare const TERMINAL_TASK_STATUSES: readonly TaskStatus[];
|
|
2250
|
+
|
|
2251
|
+
/**
|
|
2252
|
+
* One unit's settled outcome — a discriminated union so a value of `undefined` is still
|
|
2253
|
+
* a success (`{ ok: true, value: undefined }`), never mistaken for a failure or an
|
|
2254
|
+
* absent result.
|
|
2255
|
+
*
|
|
2256
|
+
* @remarks
|
|
2257
|
+
* The runner records each settled unit's outcome through this shape: a success boxes the
|
|
2258
|
+
* resolved `value` (presence tracked by the union tag, so `undefined` is a valid result),
|
|
2259
|
+
* a failure carries the `error` (always `unknown`). The FIRST failure is fail-fast.
|
|
2260
|
+
*
|
|
2261
|
+
* @typeParam TResult - The value a unit resolves
|
|
2262
|
+
*/
|
|
2263
|
+
export declare type UnitOutcome<TResult> = {
|
|
2264
|
+
readonly ok: true;
|
|
2265
|
+
readonly value: TResult;
|
|
2266
|
+
} | {
|
|
2267
|
+
readonly ok: false;
|
|
2268
|
+
readonly error: unknown;
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
/**
|
|
2272
|
+
* The live DERIVED state machine (W-b) for a whole workflow — the observable (AGENTS §13)
|
|
2273
|
+
* ROOT whose {@link WorkflowStatus} is computed from its phases under the `bail` policy and
|
|
2274
|
+
* recomputed reactively as the cascade propagates up from a task transition.
|
|
2275
|
+
*
|
|
2276
|
+
* @remarks
|
|
2277
|
+
* - **Construction.** Built from a {@link WorkflowSnapshot} (the unified input —
|
|
2278
|
+
* {@link import('./factories.js').createWorkflow} seeds an initial snapshot from a
|
|
2279
|
+
* {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').restoreWorkflow}
|
|
2280
|
+
* passes a persisted one). Each child {@link Phase} is wired to escalate to {@link #recompute}.
|
|
2281
|
+
* - **Derived status.** `status` is `#override` when forced, else
|
|
2282
|
+
* {@link deriveWorkflowStatus} over the live phases' statuses feeding `bail`. `failed` is
|
|
2283
|
+
* reachable ONLY under `bail: true` (a single failed task halts the workflow); under
|
|
2284
|
+
* `bail: false` a failed phase folds into `completed`. {@link #recompute} diffs on each phase
|
|
2285
|
+
* change; a CHANGE emits.
|
|
2286
|
+
* - **Override (AGENTS §10).** `skip` / `stop` FORCE the status; the override is PERSISTED in the
|
|
2287
|
+
* snapshot's own `override` field and restored DIRECTLY (no divergence guess). The snapshot also
|
|
2288
|
+
* persists `bail`, so a restore re-derives status identically without a silent policy default.
|
|
2289
|
+
* - **Result tree.** `results()` flattens every phase's `results()` ({@link collectResults}) — the
|
|
2290
|
+
* workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
|
|
2291
|
+
* navigate UP.
|
|
2292
|
+
* - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
|
|
2293
|
+
* JSON); {@link import('./factories.js').restoreWorkflow} rebuilds an equivalent live tree.
|
|
2294
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
|
|
2295
|
+
* `start` / `complete` / `fail` / `stop` on a derived-status CHANGE; the emitter isolates a
|
|
2296
|
+
* listener throw and routes it to its `error` handler (the `error` option); `fail` carries
|
|
2297
|
+
* the failing task's {@link TaskResult}.
|
|
2298
|
+
*/
|
|
2299
|
+
export declare class Workflow implements WorkflowInterface {
|
|
2300
|
+
#private;
|
|
2301
|
+
constructor(snapshot: WorkflowSnapshot, options?: WorkflowOptions);
|
|
2302
|
+
get emitter(): EmitterInterface<WorkflowEventMap>;
|
|
2303
|
+
get id(): string;
|
|
2304
|
+
get name(): string;
|
|
2305
|
+
get description(): string | undefined;
|
|
2306
|
+
get context(): WorkflowContext;
|
|
2307
|
+
get bail(): boolean;
|
|
2308
|
+
get status(): WorkflowStatus;
|
|
2309
|
+
get phases(): PhaseManagerInterface;
|
|
2310
|
+
phase(id: string): PhaseInterface | undefined;
|
|
2311
|
+
results(): readonly TaskResult[];
|
|
2312
|
+
skip(): void;
|
|
2313
|
+
stop(): void;
|
|
2314
|
+
complete(): void;
|
|
2315
|
+
snapshot(): WorkflowSnapshot;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/** Every {@link WorkflowStatus} value, frozen — the lifecycle vocabulary of a workflow. */
|
|
2319
|
+
export declare const WORKFLOW_STATUSES: readonly WorkflowStatus[];
|
|
2320
|
+
|
|
2321
|
+
/**
|
|
2322
|
+
* The DESCRIPTION {@link import('./factories.js').createWorkflowTool} advertises — a
|
|
2323
|
+
* multi-line guide that teaches a small model how to author a complete workflow tree.
|
|
2324
|
+
*
|
|
2325
|
+
* @remarks
|
|
2326
|
+
* Presents the SIMPLE flat shape (`{ name, steps: [{ name, via }] }`) as the PRIMARY way with
|
|
2327
|
+
* one complete worked example ({@link WORKFLOW_TOOL_FLAT_EXAMPLE}), names the three `via`
|
|
2328
|
+
* values + that a step's `name` is a REGISTERED name (not a human label), and documents the full nested
|
|
2329
|
+
* {@link WorkflowDefinition} as the ADVANCED form with a minimal example
|
|
2330
|
+
* ({@link WORKFLOW_TOOL_NESTED_EXAMPLE}). Both examples are interpolated VERBATIM from the
|
|
2331
|
+
* validated constants, so a parity test pins them — the description can never drift from a
|
|
2332
|
+
* real, contract-valid example. The `parameters` the tool advertises are the FLAT shape's
|
|
2333
|
+
* schema; the nested form is the documented escape-hatch (the tool accepts both).
|
|
2334
|
+
*/
|
|
2335
|
+
export declare const WORKFLOW_TOOL_DESCRIPTION: string;
|
|
2336
|
+
|
|
2337
|
+
/**
|
|
2338
|
+
* A complete FLAT authoring example — the PRIMARY way a small model authors a workflow
|
|
2339
|
+
* through {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name, via }] }`.
|
|
2340
|
+
*
|
|
2341
|
+
* @remarks
|
|
2342
|
+
* Each step becomes a one-task phase, in order; a step's `name` is a REGISTERED behavior name
|
|
2343
|
+
* (not a label) and `via` is the execution mechanism. The tool expands this
|
|
2344
|
+
* ({@link import('./helpers.js').expandSteps}) into a valid {@link WorkflowDefinition}. It
|
|
2345
|
+
* is embedded VERBATIM in {@link WORKFLOW_TOOL_DESCRIPTION} and guarded by a parity test
|
|
2346
|
+
* (it must expand to a tree the STRICT contract accepts), so the doc example can never drift.
|
|
2347
|
+
*/
|
|
2348
|
+
export declare const WORKFLOW_TOOL_FLAT_EXAMPLE: WorkflowSteps;
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* The name under which the {@link import('./WorkflowRunner.js').WorkflowRunner} BINDS the
|
|
2352
|
+
* depth/cycle-aware workflow tool onto a dispatched `agent` task's
|
|
2353
|
+
* `AgentContextInterface` (the future `@orkestrel/agent` package, W-c2).
|
|
2354
|
+
*
|
|
2355
|
+
* @remarks
|
|
2356
|
+
* The propagation seam's well-known key: before running an `agent` task, the runner adds a
|
|
2357
|
+
* {@link import('./factories.js').createWorkflowTool}-built tool under this name to the
|
|
2358
|
+
* resolved agent's `context.tools`, so the subagent can author + run a NESTED workflow
|
|
2359
|
+
* (bounded by {@link MAX_WORKFLOW_DEPTH}). A subagent that wants to fan out into a workflow
|
|
2360
|
+
* calls this tool by this name; the bound handler runs the nested workflow at depth + 1.
|
|
2361
|
+
*/
|
|
2362
|
+
export declare const WORKFLOW_TOOL_NAME = "workflow";
|
|
2363
|
+
|
|
2364
|
+
/**
|
|
2365
|
+
* A minimal NESTED authoring example — the ADVANCED escape-hatch form a model may use
|
|
2366
|
+
* instead of the flat shape: a full {@link WorkflowDefinition}.
|
|
2367
|
+
*
|
|
2368
|
+
* @remarks
|
|
2369
|
+
* The full four-level form, documented in {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced
|
|
2370
|
+
* alternative. It is embedded VERBATIM and guarded by a parity test (`createWorkflowContract().is`
|
|
2371
|
+
* must accept it), so the doc example can never drift from a valid definition.
|
|
2372
|
+
*/
|
|
2373
|
+
export declare const WORKFLOW_TOOL_NESTED_EXAMPLE: WorkflowDefinition;
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* The `agent`-task behavior resolver (W-c2) — resolves a registered agent BY NAME to a
|
|
2377
|
+
* live {@link AgentInterface} the runner runs as a subagent.
|
|
2378
|
+
*
|
|
2379
|
+
* @remarks
|
|
2380
|
+
* The `agent` analogue of the {@link WorkflowFunctions} registry / the
|
|
2381
|
+
* {@link ToolManagerInterface} the other two forms dispatch through: the runner resolves
|
|
2382
|
+
* an `agent`-form {@link TaskForm}'s `name` here. A name absent from the resolver is the
|
|
2383
|
+
* no-handler case — the task AUTO-COMPLETES (the ROADMAP rule), exactly like an
|
|
2384
|
+
* unregistered `function` / `tool`.
|
|
2385
|
+
*
|
|
2386
|
+
* **Resolve a FRESH agent per call.** A phase's tasks run CONCURRENTLY, so two `agent`
|
|
2387
|
+
* tasks naming the same agent are resolved concurrently — and the runner BINDS a
|
|
2388
|
+
* depth/cycle-aware workflow tool onto each resolved agent's `context.tools` (the
|
|
2389
|
+
* propagation seam — see {@link WorkflowToolOptions}). An implementation that returns the
|
|
2390
|
+
* SAME instance for both would have them race on that mutation, so it must mint a fresh
|
|
2391
|
+
* agent each call (exactly as `AgentRegistry.build` does). A function resolver
|
|
2392
|
+
* (`(name) => …`) is the minimal shape; the agents module's `AgentRegistry` is not a
|
|
2393
|
+
* direct fit (its `build` rehydrates from a serializable `AgentJobInput`, not a bare
|
|
2394
|
+
* name), so a small adapter `(name) => registry.build({ provider, ... })` bridges it.
|
|
2395
|
+
*
|
|
2396
|
+
* @param name - The agent's registry key (the `agent`-form's `name`)
|
|
2397
|
+
* @returns The resolved live agent, or `undefined` when the name is unregistered
|
|
2398
|
+
*/
|
|
2399
|
+
export declare type WorkflowAgents = (name: string) => AgentInterface | undefined;
|
|
2400
|
+
|
|
2401
|
+
/**
|
|
2402
|
+
* The ambient context of a workflow — the identity every level inherits.
|
|
2403
|
+
*
|
|
2404
|
+
* @remarks
|
|
2405
|
+
* The root of the context chain: a {@link PhaseContext} and {@link TaskContext}
|
|
2406
|
+
* both extend it, so a task at the leaf still sees the workflow's `id` / `name`.
|
|
2407
|
+
* `description` is optional (it mirrors the optional definition field).
|
|
2408
|
+
*/
|
|
2409
|
+
export declare interface WorkflowContext {
|
|
2410
|
+
readonly id: string;
|
|
2411
|
+
readonly name: string;
|
|
2412
|
+
readonly description?: string;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
/**
|
|
2416
|
+
* The serializable definition of a whole workflow — its identity, its ordered
|
|
2417
|
+
* phases, and the `bail` failure policy.
|
|
2418
|
+
*
|
|
2419
|
+
* @remarks
|
|
2420
|
+
* Pure JSON DATA — the root a UI/LLM authors and the contract validates. `phases`
|
|
2421
|
+
* are the workflow's phases, which run SEQUENTIALLY. `bail` is the failure policy
|
|
2422
|
+
* (a boolean behavioral toggle, AGENTS §4.4): `false` (the default) is GRACEFUL —
|
|
2423
|
+
* a failed leaf task is recorded as data and the workflow still completes; `true`
|
|
2424
|
+
* is a database-transaction HALT — a single failed task propagates `failed` to the
|
|
2425
|
+
* whole workflow. See {@link import('./helpers.js').deriveWorkflowStatus}.
|
|
2426
|
+
*/
|
|
2427
|
+
export declare interface WorkflowDefinition {
|
|
2428
|
+
readonly id: string;
|
|
2429
|
+
readonly name: string;
|
|
2430
|
+
readonly description?: string;
|
|
2431
|
+
readonly phases: readonly PhaseDefinition[];
|
|
2432
|
+
/** Failure policy: `false` (default) continues gracefully, `true` halts on the first failure. */
|
|
2433
|
+
readonly bail?: boolean;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
/**
|
|
2437
|
+
* A draft workflow — a {@link WorkflowDefinition} with OPTIONAL `id` / `name` at all
|
|
2438
|
+
* three levels (workflow / phase / task).
|
|
2439
|
+
*
|
|
2440
|
+
* @remarks
|
|
2441
|
+
* The lenient authoring form `createWorkflowDraftContract` validates and
|
|
2442
|
+
* {@link import('./helpers.js').completeDraft} completes into a strict
|
|
2443
|
+
* {@link WorkflowDefinition}. `run` stays required; the `bail` policy carries over.
|
|
2444
|
+
*/
|
|
2445
|
+
export declare interface WorkflowDraft {
|
|
2446
|
+
readonly id?: string;
|
|
2447
|
+
readonly name?: string;
|
|
2448
|
+
readonly description?: string;
|
|
2449
|
+
readonly phases: readonly PhaseDraft[];
|
|
2450
|
+
/** Failure policy: `false` (default) continues gracefully, `true` halts on the first failure. */
|
|
2451
|
+
readonly bail?: boolean;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
/**
|
|
2455
|
+
* The shape of a DRAFT workflow — identical to {@link workflowShape} EXCEPT `id` and
|
|
2456
|
+
* `name` are OPTIONAL at all three levels (workflow / phase / task), so a small model
|
|
2457
|
+
* can omit the six identity strings and let the tool synthesize them positionally.
|
|
2458
|
+
*
|
|
2459
|
+
* @remarks
|
|
2460
|
+
* The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract}
|
|
2461
|
+
* compiles. `run` stays required; a provided `id` / `name` still has `minLength: 1` (so an
|
|
2462
|
+
* explicitly-empty `id: ''` is REJECTED, not auto-filled). After
|
|
2463
|
+
* {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
|
|
2464
|
+
* validated against the STRICT {@link import('./factories.js').createWorkflowContract} gate
|
|
2465
|
+
* before running.
|
|
2466
|
+
*/
|
|
2467
|
+
export declare const workflowDraftShape: ObjectShape<{
|
|
2468
|
+
id: OptionalShape<StringShape>;
|
|
2469
|
+
name: OptionalShape<StringShape>;
|
|
2470
|
+
description: OptionalShape<StringShape>;
|
|
2471
|
+
phases: ArrayShape<ObjectShape<{
|
|
2472
|
+
id: OptionalShape<StringShape>;
|
|
2473
|
+
name: OptionalShape<StringShape>;
|
|
2474
|
+
description: OptionalShape<StringShape>;
|
|
2475
|
+
tasks: ArrayShape<ObjectShape<{
|
|
2476
|
+
id: OptionalShape<StringShape>;
|
|
2477
|
+
name: OptionalShape<StringShape>;
|
|
2478
|
+
description: OptionalShape<StringShape>;
|
|
2479
|
+
run: UnionShape<[ ObjectShape<{
|
|
2480
|
+
via: LiteralShape<readonly ["function"]>;
|
|
2481
|
+
name: StringShape;
|
|
2482
|
+
}>, ObjectShape<{
|
|
2483
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
2484
|
+
name: StringShape;
|
|
2485
|
+
}>, ObjectShape<{
|
|
2486
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
2487
|
+
name: StringShape;
|
|
2488
|
+
}>]>;
|
|
2489
|
+
retries: OptionalShape<NumberShape>;
|
|
2490
|
+
timeout: OptionalShape<NumberShape>;
|
|
2491
|
+
}>>;
|
|
2492
|
+
concurrency: OptionalShape<NumberShape>;
|
|
2493
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
2494
|
+
}>>;
|
|
2495
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
2496
|
+
}>;
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* An error thrown by the workflow entity + W-c2 recursion layer.
|
|
2500
|
+
*
|
|
2501
|
+
* @remarks
|
|
2502
|
+
* Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
|
|
2503
|
+
* offending node id / status. Thrown for an illegal lifecycle transition
|
|
2504
|
+
* (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
|
|
2505
|
+
* passed to {@link import('./factories.js').restoreWorkflow} (`RESTORE`), an over-deep /
|
|
2506
|
+
* cyclic nested-workflow dispatch (`DEPTH`), and a malformed
|
|
2507
|
+
* {@link import('./factories.js').createWorkflowTool} args blob (`TOOL`). On the
|
|
2508
|
+
* workflow-tool seam the `DEPTH` / `TOOL` throw is ISOLATED by the
|
|
2509
|
+
* `@orkestrel/agent` package's `ToolManager` into the tool result's
|
|
2510
|
+
* top-level `error` (AGENTS §14 — the universal tool-handler contract).
|
|
2511
|
+
*/
|
|
2512
|
+
export declare class WorkflowError extends Error {
|
|
2513
|
+
readonly code: WorkflowErrorCode;
|
|
2514
|
+
readonly context?: Readonly<Record<string, unknown>>;
|
|
2515
|
+
constructor(code: WorkflowErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/**
|
|
2519
|
+
* The machine-readable code of a {@link import('./errors.js').WorkflowError} — the
|
|
2520
|
+
* fault the live W-b state machine raises (AGENTS §12).
|
|
2521
|
+
*
|
|
2522
|
+
* @remarks
|
|
2523
|
+
* - `TRANSITION` — an illegal state-machine transition (e.g. `start`ing a task that is
|
|
2524
|
+
* not `pending`, or `complete`/`fail`ing one that is not `running`); the guard names
|
|
2525
|
+
* the offending current status + requested transition in the error `context`.
|
|
2526
|
+
* - `RESTORE` — a {@link import('./factories.js').restoreWorkflow} given a structurally
|
|
2527
|
+
* invalid {@link WorkflowSnapshot} (a status outside the lifecycle vocabulary).
|
|
2528
|
+
* - `DEPTH` — a nested-workflow dispatch (W-c2) that the runner's depth / cycle guard
|
|
2529
|
+
* rejected: running it would push the nested-workflow chain past
|
|
2530
|
+
* {@link import('./constants.js').MAX_WORKFLOW_DEPTH}, OR its target agent (or a
|
|
2531
|
+
* workflow it would author) is already an ancestor of the current run (a re-entry
|
|
2532
|
+
* cycle). Raised on BOTH seams of the W-c2 recursion — an `agent`-task dispatch
|
|
2533
|
+
* (`fail`ed with this code; it never runs) AND the
|
|
2534
|
+
* {@link import('./factories.js').createWorkflowTool} handler (thrown, then ISOLATED
|
|
2535
|
+
* by the `@orkestrel/agent` package's `ToolManager` into the tool
|
|
2536
|
+
* result's `error`). The error `context` names the offending agent / workflow id + the depth.
|
|
2537
|
+
* - `TOOL` — the {@link import('./factories.js').createWorkflowTool} handler was handed
|
|
2538
|
+
* a MALFORMED / over-constraint authored args blob (e.g. an empty `id`, `concurrency: 0`)
|
|
2539
|
+
* that the contract rejected, so no workflow ran. The handler THROWS it (rather than
|
|
2540
|
+
* returning a failure result), and the `@orkestrel/agent` package's `ToolManager`
|
|
2541
|
+
* ISOLATES the throw into the canonical tool result's top-level `error` (AGENTS §14 — the
|
|
2542
|
+
* universal tool-handler contract); the error `context` names the wrapped workflow id.
|
|
2543
|
+
*/
|
|
2544
|
+
export declare type WorkflowErrorCode = 'TRANSITION' | 'RESTORE' | 'DEPTH' | 'TOOL';
|
|
2545
|
+
|
|
2546
|
+
/**
|
|
2547
|
+
* The push observation surface (AGENTS §13) of the workflow entity (W-b) — the
|
|
2548
|
+
* lifecycle moments a fire-and-forget observer subscribes to via
|
|
2549
|
+
* `workflow.emitter.on`.
|
|
2550
|
+
*
|
|
2551
|
+
* @remarks
|
|
2552
|
+
* Present-tense events with arg tuples. `start` fires when the workflow begins;
|
|
2553
|
+
* `complete` when every phase settled successfully; `fail` when a phase failed
|
|
2554
|
+
* under `bail` (carrying the failing {@link TaskResult}); `stop` when the workflow
|
|
2555
|
+
* was permanently ended. A throwing listener never reaches the domain surface — the
|
|
2556
|
+
* emitter isolates it and routes it to its OWN `error` handler (the `error` option,
|
|
2557
|
+
* AGENTS §13). Declared as a `type` alias (not `interface extends EventMap`, AGENTS
|
|
2558
|
+
* §4.5) so the type-literal satisfies `EventMap` structurally.
|
|
2559
|
+
*/
|
|
2560
|
+
export declare type WorkflowEventMap = {
|
|
2561
|
+
/** The workflow began — its `id`. */
|
|
2562
|
+
readonly start: readonly [id: string];
|
|
2563
|
+
/** Every phase settled successfully. */
|
|
2564
|
+
readonly complete: readonly [];
|
|
2565
|
+
/** A phase failed under `bail` — the failing task's result. */
|
|
2566
|
+
readonly fail: readonly [result: TaskResult];
|
|
2567
|
+
/** The workflow was permanently stopped. */
|
|
2568
|
+
readonly stop: readonly [];
|
|
2569
|
+
};
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* A registered workflow function — the behavior a `function`-form
|
|
2573
|
+
* {@link TaskDefinition} runs, resolved BY NAME through the {@link WorkflowFunctions}
|
|
2574
|
+
* registry (AGENTS §4.5 — a `Handler` function type the framework invokes).
|
|
2575
|
+
*
|
|
2576
|
+
* @remarks
|
|
2577
|
+
* Receives a {@link TaskControllerInterface} — the running task's folded `signal`, its
|
|
2578
|
+
* `input` (the task's `metadata` bag), its lineage {@link TaskContext}, and read-UP access
|
|
2579
|
+
* to earlier phases' {@link TaskResult}s. A returned value becomes the task's
|
|
2580
|
+
* {@link import('@orkestrel/contract').Success} ({@link TaskInterface.complete}); a throw / rejection
|
|
2581
|
+
* becomes its {@link import('@orkestrel/contract').Failure} ({@link TaskInterface.fail}). Long work
|
|
2582
|
+
* should honour `controller.signal` (a workflow-level abort / timeout / budget, or — under
|
|
2583
|
+
* `bail: true` — a sibling's failure, fires it) so a cancel stops it promptly.
|
|
2584
|
+
*/
|
|
2585
|
+
export declare type WorkflowFunction = (controller: TaskControllerInterface) => Promise<unknown> | unknown;
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* The `function`-task behavior registry — workflow function names mapped to their
|
|
2589
|
+
* {@link WorkflowFunction} handlers.
|
|
2590
|
+
*
|
|
2591
|
+
* @remarks
|
|
2592
|
+
* The runner resolves a `function`-form {@link TaskForm}'s `name` here. A name absent
|
|
2593
|
+
* from the registry is the no-handler case — the task AUTO-COMPLETES (the ROADMAP rule),
|
|
2594
|
+
* exactly like an unsupported form. A plain record (not a manager) — the registry is a
|
|
2595
|
+
* lookup, with no lifecycle of its own.
|
|
2596
|
+
*/
|
|
2597
|
+
export declare type WorkflowFunctions = Readonly<Record<string, WorkflowFunction>>;
|
|
2598
|
+
|
|
2599
|
+
/** Initial {@link WorkflowEventMap} listeners — the reserved `on` option (AGENTS §8). */
|
|
2600
|
+
export declare type WorkflowHooks = EmitterHooks<WorkflowEventMap>;
|
|
2601
|
+
|
|
2602
|
+
/** The minimal data to create a workflow context — a partial {@link WorkflowContext}. */
|
|
2603
|
+
export declare type WorkflowInput = Partial<WorkflowContext>;
|
|
2604
|
+
|
|
2605
|
+
/**
|
|
2606
|
+
* The live derived state machine (W-b) for a whole {@link WorkflowDefinition} — the
|
|
2607
|
+
* observable (AGENTS §13) root whose {@link WorkflowStatus} is DERIVED from its phases
|
|
2608
|
+
* under the `bail` policy and recomputed reactively as the cascade propagates up.
|
|
2609
|
+
*
|
|
2610
|
+
* @remarks
|
|
2611
|
+
* - **Derived status.** `status` is computed via
|
|
2612
|
+
* {@link import('./helpers.js').deriveWorkflowStatus} over the live phases' statuses,
|
|
2613
|
+
* feeding the definition's `bail`, UNLESS an override is in force. It recomputes when a
|
|
2614
|
+
* phase's status changes (the top of the cascade); a CHANGE emits — `fail` carries the
|
|
2615
|
+
* failing {@link TaskResult} (under `bail: true`).
|
|
2616
|
+
* - **Children.** `phases` is the lean {@link PhaseManagerInterface} (AGENTS §9);
|
|
2617
|
+
* `phase(id)` / `phases().phases()` read in positional order. `results` collects ALL
|
|
2618
|
+
* tasks' results across every phase (the workflow tier of the result tree).
|
|
2619
|
+
* - **Override.** `skip` / `stop` (AGENTS §10) FORCE the workflow's status; the override
|
|
2620
|
+
* survives a snapshot.
|
|
2621
|
+
* - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot}
|
|
2622
|
+
* (pure JSON — structure + each node's status + recorded results + positional order);
|
|
2623
|
+
* {@link restoreWorkflow} rebuilds an equivalent live tree.
|
|
2624
|
+
* - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
|
|
2625
|
+
* `start` / `complete` / `fail` / `stop` on a derived-status change; the emitter isolates a
|
|
2626
|
+
* listener throw and routes it to its `error` handler (the `error` option).
|
|
2627
|
+
*/
|
|
2628
|
+
export declare interface WorkflowInterface {
|
|
2629
|
+
readonly emitter: EmitterInterface<WorkflowEventMap>;
|
|
2630
|
+
readonly id: string;
|
|
2631
|
+
readonly name: string;
|
|
2632
|
+
readonly description?: string;
|
|
2633
|
+
readonly context: WorkflowContext;
|
|
2634
|
+
readonly bail: boolean;
|
|
2635
|
+
readonly status: WorkflowStatus;
|
|
2636
|
+
readonly phases: PhaseManagerInterface;
|
|
2637
|
+
/** Look up one live phase by its `id`. */
|
|
2638
|
+
phase(id: string): PhaseInterface | undefined;
|
|
2639
|
+
/** Every settled task's result across all phases, in positional order — the workflow tier of the result tree. */
|
|
2640
|
+
results(): readonly TaskResult[];
|
|
2641
|
+
skip(): void;
|
|
2642
|
+
stop(): void;
|
|
2643
|
+
complete(): void;
|
|
2644
|
+
snapshot(): WorkflowSnapshot;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
/**
|
|
2648
|
+
* The runtime options for a {@link WorkflowInterface} — the construction bag the
|
|
2649
|
+
* live derived workflow state machine (W-b) carries, the root {@link createWorkflow}
|
|
2650
|
+
* accepts.
|
|
2651
|
+
*
|
|
2652
|
+
* @remarks
|
|
2653
|
+
* The reserved `on` (AGENTS §8) wires initial {@link WorkflowEventMap} listeners.
|
|
2654
|
+
* `phases` keys per-phase {@link PhaseOptions} by phase `id`, so the whole tree's
|
|
2655
|
+
* initial listeners + per-task metadata can be supplied in one nested bag (the
|
|
2656
|
+
* AGENTS §8 "entity is the key" grouping), each leaf reachable by its lineage of ids.
|
|
2657
|
+
*/
|
|
2658
|
+
export declare interface WorkflowOptions {
|
|
2659
|
+
readonly on?: WorkflowHooks;
|
|
2660
|
+
/**
|
|
2661
|
+
* The failure policy (AGENTS §4.4) the live tree applies — the same boolean toggle as
|
|
2662
|
+
* {@link WorkflowDefinition.bail}, fed to {@link import('./helpers.js').deriveWorkflowStatus}.
|
|
2663
|
+
* {@link import('./factories.js').createWorkflow} defaults it to the definition's `bail`. A
|
|
2664
|
+
* {@link WorkflowSnapshot} PERSISTS the policy, so {@link import('./factories.js').restoreWorkflow}
|
|
2665
|
+
* takes it from the snapshot (the source of truth); an explicit `options.bail` on restore still
|
|
2666
|
+
* wins when supplied. Omitted on a fresh build ⇒ the graceful {@link import('./constants.js').DEFAULT_BAIL}.
|
|
2667
|
+
*/
|
|
2668
|
+
readonly bail?: boolean;
|
|
2669
|
+
/** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
|
|
2670
|
+
readonly error?: EmitterErrorHandler;
|
|
2671
|
+
/** Per-phase {@link PhaseOptions}, keyed by the phase's `id`. */
|
|
2672
|
+
readonly phases?: Readonly<Record<string, PhaseOptions>>;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
/**
|
|
2676
|
+
* The structured outcome of a {@link WorkflowRunnerInterface.execute} run — the settled
|
|
2677
|
+
* live workflow, its final status, and the flattened result tree.
|
|
2678
|
+
*
|
|
2679
|
+
* @remarks
|
|
2680
|
+
* Boxes the settled {@link WorkflowInterface} itself (so a caller can navigate the whole
|
|
2681
|
+
* live tree — every phase / task's final `status`, its recorded {@link TaskResult}, its
|
|
2682
|
+
* lineage) ALONGSIDE the two read-throughs the run produced: `status` is the workflow's
|
|
2683
|
+
* derived {@link WorkflowStatus} at settle (`completed` under graceful mode even with
|
|
2684
|
+
* failed leaves; `failed` under `bail: true`; `stopped` on a workflow-level abort /
|
|
2685
|
+
* timeout / budget), and `results` is the workflow-tier {@link TaskResult} list (every
|
|
2686
|
+
* settled task across all phases, in positional order — the same array `workflow.results()`
|
|
2687
|
+
* yields). Returning the live `workflow` (not just a snapshot) keeps the entity tree the
|
|
2688
|
+
* source of truth — the runner adds only the convenience `status` / `results` projections.
|
|
2689
|
+
*/
|
|
2690
|
+
export declare interface WorkflowResult {
|
|
2691
|
+
readonly workflow: WorkflowInterface;
|
|
2692
|
+
readonly status: WorkflowStatus;
|
|
2693
|
+
readonly results: readonly TaskResult[];
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
/**
|
|
2697
|
+
* The thin orchestrator that EXECUTES a live W-b workflow tree by COMPOSING the shipped
|
|
2698
|
+
* substrate — phases sequential, tasks concurrent — dispatching each task BY NAME under the
|
|
2699
|
+
* `bail` policy, including the W-c2 `agent` form behind a depth + cycle guard.
|
|
2700
|
+
*
|
|
2701
|
+
* @remarks
|
|
2702
|
+
* - **Composes, never re-implements.** Per-phase bounded concurrency is one
|
|
2703
|
+
* {@link createRunner} per phase (the substrate {@link RunnerInterface} over the workers
|
|
2704
|
+
* `Queue`); `bail` maps onto that Runner's fail-fast vs settle-all; the run-level abort /
|
|
2705
|
+
* timeout / budget fold through {@link createAbort} / {@link createTimeout} +
|
|
2706
|
+
* `AbortSignal.any` (exactly as the agent runtime folds its bounds); pacing is the shipped
|
|
2707
|
+
* {@link SchedulerInterface}. The runner writes ZERO concurrency / retry / abort logic of
|
|
2708
|
+
* its own — it only sequences phases, dispatches a task, and drives the live entity.
|
|
2709
|
+
* - **Phases sequential, tasks concurrent.** `#execute` awaits the phases in order (phase
|
|
2710
|
+
* N+1 starts only once phase N has fully settled). Within a phase, ALL its tasks are the
|
|
2711
|
+
* one Runner's `inputs`, run at `concurrency` = the phase's
|
|
2712
|
+
* {@link PhaseDefinition.concurrency} (default {@link DEFAULT_PHASE_CONCURRENCY}).
|
|
2713
|
+
* - **Dispatch by name.** `#dispatch` branches on the task's
|
|
2714
|
+
* {@link import('./types.js').TaskForm} (read from the `definition`, correlated by `id`):
|
|
2715
|
+
* `function` → the {@link WorkflowFunctions} registry, `tool` → the
|
|
2716
|
+
* {@link ToolManagerInterface}, `agent` → the {@link WorkflowAgents} resolver (W-c2). A
|
|
2717
|
+
* handler that is NOT found (an unregistered name for ANY form) AUTO-COMPLETES — the
|
|
2718
|
+
* ROADMAP no-handler rule.
|
|
2719
|
+
* - **`agent` form + depth/cycle guard (W-c2).** An `agent` task resolves its subagent via
|
|
2720
|
+
* `agents`, BINDS a depth/cycle-aware workflow tool onto the subagent's `context.tools`
|
|
2721
|
+
* (the propagation seam), folds the task's cancellation into the agent run (a workflow
|
|
2722
|
+
* cancel `abort`s the subagent), and drives it: success → `complete(result)`, throw →
|
|
2723
|
+
* `fail(error)`. Before running, the guard REJECTS the task into a typed `DEPTH`
|
|
2724
|
+
* {@link WorkflowError} (`fail`) when running it would push the nested chain past
|
|
2725
|
+
* {@link MAX_WORKFLOW_DEPTH}, OR when its target agent is already an ancestor (a cycle).
|
|
2726
|
+
* The rejected task never runs the agent.
|
|
2727
|
+
* - **`bail` → substrate.** Under `bail: true` (halt) a genuine task failure `fail`s the leaf
|
|
2728
|
+
* THEN re-throws, so the substrate Runner fail-fasts — it aborts the in-flight siblings
|
|
2729
|
+
* (their `controller.signal` fires; a mid-flight sibling `skip`s) and rejects the phase run;
|
|
2730
|
+
* `#execute` then `skip`s the remaining tasks / phases (the workflow derives `failed`).
|
|
2731
|
+
* Under `bail: false` (graceful) a failure `fail`s the leaf and RESOLVES (never throws), so
|
|
2732
|
+
* the Runner settles every unit (allSettled) and the run finishes (the workflow derives
|
|
2733
|
+
* `completed`, the failure recorded in the result tree).
|
|
2734
|
+
* - **Abort / Timeout / Budget fold.** `#execute` folds the run's external `signal`, a
|
|
2735
|
+
* {@link TimeoutInterface}, and the `@orkestrel/budget` package's `BudgetInterface`'s
|
|
2736
|
+
* `signal` into one `runSignal` (`AbortSignal.any`); a fire aborts the active phase's Runner
|
|
2737
|
+
* (cancelling every in-flight task) and HALTS the run — the remaining tasks / phases `skip`
|
|
2738
|
+
* and the workflow is force-`stop`ped (settles `stopped`). Each task's
|
|
2739
|
+
* {@link TaskController} signal `AbortSignal.any`-combines the substrate per-unit signal with
|
|
2740
|
+
* `runSignal`, so a handler observes either cause directly.
|
|
2741
|
+
* - **Re-entrant-safe.** No shared per-run mutable field: the active-Runner holder is LOCAL to
|
|
2742
|
+
* each `#execute`, so a nested `execute` (the bound workflow tool re-entering this instance
|
|
2743
|
+
* while the outer run is suspended on an `agent` task) cannot clobber the outer run's state.
|
|
2744
|
+
*/
|
|
2745
|
+
export declare class WorkflowRunner implements WorkflowRunnerInterface {
|
|
2746
|
+
#private;
|
|
2747
|
+
constructor(functions: WorkflowFunctions, tools: ToolManagerInterface | undefined, agents: WorkflowAgents | undefined, scheduler: SchedulerInterface, workflowTool: WorkflowToolBinder | undefined);
|
|
2748
|
+
execute(definition: WorkflowDefinition, options?: WorkflowRunOptions): Promise<WorkflowResult>;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
/**
|
|
2752
|
+
* A thin orchestrator that EXECUTES a live {@link WorkflowInterface} tree by composing the
|
|
2753
|
+
* shipped substrate — phases sequential, tasks concurrent, dispatched by name under the
|
|
2754
|
+
* `bail` policy.
|
|
2755
|
+
*
|
|
2756
|
+
* @remarks
|
|
2757
|
+
* `execute(definition, options?)` BUILDS the live W-b entity tree from the definition itself
|
|
2758
|
+
* (via {@link import('./factories.js').createWorkflow}) and drives it to a terminal
|
|
2759
|
+
* {@link WorkflowResult} — phases SEQUENTIALLY and, within each phase, the tasks CONCURRENTLY
|
|
2760
|
+
* through ONE substrate {@link RunnerInterface} (concurrency =
|
|
2761
|
+
* the phase's {@link PhaseDefinition.concurrency}). The definition is the SINGLE source of
|
|
2762
|
+
* truth: the runner owns both the declarative state (the live tree it constructs) and the
|
|
2763
|
+
* EXECUTION-ONLY fields the snapshot deliberately dropped — each task's {@link TaskForm}
|
|
2764
|
+
* (`run`) and each phase's `concurrency` (so there is no separately-supplied workflow to drift
|
|
2765
|
+
* from the definition). The freshly-built live tree is returned in {@link WorkflowResult.workflow}.
|
|
2766
|
+
* Each task is dispatched by its {@link TaskForm} — a `function` through the
|
|
2767
|
+
* {@link WorkflowFunctions} registry, a `tool` through the {@link ToolManagerInterface}; a
|
|
2768
|
+
* task whose handler is not found AUTO-COMPLETES. The runner DRIVES the live entity (`start`
|
|
2769
|
+
* → `complete` / `fail`), never re-implementing status. The `bail` policy maps onto the
|
|
2770
|
+
* substrate's fail-fast (`bail: true` — the first failure aborts in-flight siblings and skips
|
|
2771
|
+
* the rest) vs settle-all (`bail: false` — failures are recorded and the run finishes). The
|
|
2772
|
+
* {@link WorkflowOptions} half of the options is forwarded to `createWorkflow` (initial
|
|
2773
|
+
* listeners, a `bail` override, per-node options); the Abort / Timeout / Budget bounds fold
|
|
2774
|
+
* per run via `AbortSignal.any`, halting the run and `stop`ping the workflow.
|
|
2775
|
+
*/
|
|
2776
|
+
export declare interface WorkflowRunnerInterface {
|
|
2777
|
+
/**
|
|
2778
|
+
* Execute a workflow definition to completion — BUILD its live tree, run the phases
|
|
2779
|
+
* sequentially with each phase's tasks concurrent — resolving its terminal
|
|
2780
|
+
* {@link WorkflowResult} (whose `workflow` is the freshly-built live tree).
|
|
2781
|
+
*
|
|
2782
|
+
* @remarks
|
|
2783
|
+
* One-shot. The runner BUILDS the live tree from `definition` internally (one source of
|
|
2784
|
+
* truth — the per-task {@link TaskForm} (`run`) and per-phase `concurrency` come from the
|
|
2785
|
+
* same definition the tree is constructed from, so the executed tree can never drift from
|
|
2786
|
+
* the form/throttle metadata). The {@link WorkflowOptions} part of `options` (initial `on`
|
|
2787
|
+
* listeners, a `bail` override, the per-node `phases` bag) is forwarded to the build.
|
|
2788
|
+
* Under `bail: false` (graceful) every task settles (a failure is recorded on its
|
|
2789
|
+
* {@link TaskInterface}) and the workflow reaches `completed`; under `bail: true` (halt)
|
|
2790
|
+
* the first failure aborts the in-flight sibling tasks AND `skip`s the remaining tasks /
|
|
2791
|
+
* phases, settling the workflow `failed`. A {@link WorkflowRunOptions} abort / timeout /
|
|
2792
|
+
* budget fires every in-flight task's signal and `stop`s the run. `execute` resolves
|
|
2793
|
+
* (never rejects) on a cancel — the partial outcome is read from the returned
|
|
2794
|
+
* {@link WorkflowResult} (its `workflow` / `status` / `results`). A run-level cancel
|
|
2795
|
+
* (abort / timeout / budget) that fires on the SAME tick as a genuine task failure resolves
|
|
2796
|
+
* the run as `stopped` — the cancel supersedes the same-tick failure, and that task's error
|
|
2797
|
+
* is not recorded.
|
|
2798
|
+
*
|
|
2799
|
+
* @param definition - The {@link WorkflowDefinition} to build the live tree from and drive
|
|
2800
|
+
* @param options - The construction options ({@link WorkflowOptions}: `on` / `bail` /
|
|
2801
|
+
* `phases`) PLUS the per-run bounds (`signal` / `timeout` / `budget`)
|
|
2802
|
+
* @returns The run's terminal {@link WorkflowResult} (its `workflow` is the built tree)
|
|
2803
|
+
*/
|
|
2804
|
+
execute(definition: WorkflowDefinition, options?: WorkflowRunOptions): Promise<WorkflowResult>;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
/**
|
|
2808
|
+
* The options for `createWorkflowRunner` — the behavior registries the runner dispatches
|
|
2809
|
+
* a task BY NAME through, plus the optional pacing scheduler.
|
|
2810
|
+
*
|
|
2811
|
+
* @remarks
|
|
2812
|
+
* - `functions` — the {@link WorkflowFunctions} registry for `function`-form tasks. A name
|
|
2813
|
+
* absent here is the no-handler case (the task auto-completes). Omitted ⇒ an empty
|
|
2814
|
+
* registry (every `function` task auto-completes).
|
|
2815
|
+
* - `tools` — the {@link ToolManagerInterface} (the shipped tool registry) a `tool`-form
|
|
2816
|
+
* task is dispatched through: the runner resolves `run.name` via `tools.tool(name)` and
|
|
2817
|
+
* invokes it with the task's input. An unregistered tool name is the no-handler case
|
|
2818
|
+
* (auto-completes). Omitted ⇒ every `tool` task auto-completes.
|
|
2819
|
+
* - `agents` — the {@link WorkflowAgents} resolver (W-c2) an `agent`-form task is dispatched
|
|
2820
|
+
* through: the runner resolves `run.name` to a live
|
|
2821
|
+
* {@link AgentInterface}, BINDS a depth/cycle-aware workflow
|
|
2822
|
+
* tool onto its `context.tools` (the propagation seam), folds the task's cancellation into
|
|
2823
|
+
* the agent run, and drives it (success → `complete`, throw → `fail`), all behind the
|
|
2824
|
+
* depth + cycle guard. An unregistered agent name is the no-handler case (auto-completes).
|
|
2825
|
+
* Omitted ⇒ every `agent` task auto-completes.
|
|
2826
|
+
* - `scheduler` — the {@link SchedulerInterface} that paces the tree (a cooperative
|
|
2827
|
+
* `yield` between phases). Omitted ⇒ the shipped cross-environment default
|
|
2828
|
+
* ({@link createScheduler}).
|
|
2829
|
+
*
|
|
2830
|
+
* The reserved `on` key (AGENTS §8) is intentionally ABSENT: the runner is THIN and drives
|
|
2831
|
+
* the W-b entities' OWN emitters (subscribe via `workflow.emitter` / `phase.emitter` /
|
|
2832
|
+
* `task.emitter`), so it owns no event map of its own — there is nothing for an `on` to
|
|
2833
|
+
* wire. A future runner-level emitter would introduce its own `EmitterHooks` here.
|
|
2834
|
+
*/
|
|
2835
|
+
export declare interface WorkflowRunnerOptions {
|
|
2836
|
+
readonly functions?: WorkflowFunctions;
|
|
2837
|
+
readonly tools?: ToolManagerInterface;
|
|
2838
|
+
/** The {@link WorkflowAgents} resolver for `agent`-form tasks (W-c2); omitted ⇒ every `agent` task auto-completes. */
|
|
2839
|
+
readonly agents?: WorkflowAgents;
|
|
2840
|
+
readonly scheduler?: SchedulerInterface;
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
/**
|
|
2844
|
+
* The options for one {@link WorkflowRunnerInterface.execute} call — the live tree's
|
|
2845
|
+
* CONSTRUCTION options ({@link WorkflowOptions}) PLUS the per-run BOUNDS (an external abort,
|
|
2846
|
+
* a deadline, and a cost ceiling), each bound folded into every task's cancellation.
|
|
2847
|
+
*
|
|
2848
|
+
* @remarks
|
|
2849
|
+
* `execute` is single-source: it BUILDS the live tree from the definition internally (via
|
|
2850
|
+
* {@link import('./factories.js').createWorkflow}), so these options carry BOTH halves of
|
|
2851
|
+
* that one call —
|
|
2852
|
+
* - the **construction** half is {@link WorkflowOptions} (`on` initial listeners, the `bail`
|
|
2853
|
+
* override, the per-node `phases` bag); `execute` forwards it straight to `createWorkflow`,
|
|
2854
|
+
* so construction-time emitter listeners, a `bail` override, and per-node options all apply
|
|
2855
|
+
* to the tree it builds (`createWorkflow` resolves `bail` as `options.bail ?? definition.bail
|
|
2856
|
+
* ?? DEFAULT_BAIL`); and
|
|
2857
|
+
* - the **run-control** half is the three bounds below, used only for the run-level fold.
|
|
2858
|
+
*
|
|
2859
|
+
* The three bounds compose via `AbortSignal.any` (exactly as the agent runtime folds its
|
|
2860
|
+
* own): a fire of ANY of them cancels every in-flight task (its
|
|
2861
|
+
* {@link TaskControllerInterface.signal} fires) and HALTS the run — the remaining tasks
|
|
2862
|
+
* and phases are `skip`ped and the workflow settles `stopped`.
|
|
2863
|
+
* - `signal` — an external cancellation (a caller `AbortController`).
|
|
2864
|
+
* - `timeout` — a whole-run deadline in milliseconds. A non-positive value (`0` or negative)
|
|
2865
|
+
* ⇒ NO deadline (the runner arms an `@orkestrel/timeout` `TimeoutInterface`
|
|
2866
|
+
* only when `timeout > 0`).
|
|
2867
|
+
* - `budget` — a whole-run cost ceiling (a {@link BudgetInterface} over {@link TokenUsage}
|
|
2868
|
+
* — its `signal` fires when a task-reported usage crosses `max`); the runner folds its
|
|
2869
|
+
* `signal` and `start`s it. (A `max: 0` budget is exhausted from its first `start`, so it
|
|
2870
|
+
* cancels the run at entry — a DIFFERENT primitive from the `timeout: 0` "no deadline" case.)
|
|
2871
|
+
*
|
|
2872
|
+
* The last two are the W-c2 DEPTH / CYCLE bookkeeping — the run's position in a nested
|
|
2873
|
+
* workflow→agent→workflow chain, threaded across the
|
|
2874
|
+
* `execute → agent-task → createWorkflowTool → nested execute` boundary so the guard can
|
|
2875
|
+
* bound recursion. A TOP-LEVEL `execute` omits both (depth `0`, empty ancestry); only the
|
|
2876
|
+
* runner-bound workflow tool ({@link import('./factories.js').createWorkflowTool}) supplies
|
|
2877
|
+
* them, incrementing the depth and extending the ancestry for each nested run.
|
|
2878
|
+
* - `depth` — the run's nesting depth (default `0`). An `agent` task is REJECTED (a typed
|
|
2879
|
+
* `DEPTH` `task.fail`) when running it would push the chain past
|
|
2880
|
+
* {@link import('./constants.js').MAX_WORKFLOW_DEPTH}.
|
|
2881
|
+
* - `ancestry` — the identifiers of the workflows + agents already in this run chain
|
|
2882
|
+
* (e.g. `workflow:<id>` / `agent:<name>`). An `agent` task whose target agent — or the
|
|
2883
|
+
* workflow it would author — is already present is a CYCLE and is likewise rejected.
|
|
2884
|
+
* Default empty.
|
|
2885
|
+
*/
|
|
2886
|
+
export declare type WorkflowRunOptions = WorkflowOptions & {
|
|
2887
|
+
readonly signal?: AbortSignal;
|
|
2888
|
+
readonly timeout?: number;
|
|
2889
|
+
readonly budget?: BudgetInterface<TokenUsage>;
|
|
2890
|
+
/** The run's nesting depth (W-c2); default `0`. Threaded by the bound workflow tool, never by a top-level caller. */
|
|
2891
|
+
readonly depth?: number;
|
|
2892
|
+
/** The workflow / agent identifiers already in this run chain (W-c2 cycle guard); default empty. */
|
|
2893
|
+
readonly ancestry?: readonly string[];
|
|
2894
|
+
};
|
|
2895
|
+
|
|
2896
|
+
/**
|
|
2897
|
+
* The shape of a {@link import('./types.js').WorkflowDefinition} — the contract root:
|
|
2898
|
+
* identity, its ordered {@link phaseShape} phases, and the optional `bail` boolean
|
|
2899
|
+
* failure policy (the literal pair `true`/`false`, the runtime mirror of the boolean
|
|
2900
|
+
* toggle; omitted ⇒ the graceful default).
|
|
2901
|
+
*/
|
|
2902
|
+
export declare const workflowShape: ObjectShape<{
|
|
2903
|
+
id: StringShape;
|
|
2904
|
+
name: StringShape;
|
|
2905
|
+
description: OptionalShape<StringShape>;
|
|
2906
|
+
phases: ArrayShape<ObjectShape<{
|
|
2907
|
+
id: StringShape;
|
|
2908
|
+
name: StringShape;
|
|
2909
|
+
description: OptionalShape<StringShape>;
|
|
2910
|
+
tasks: ArrayShape<ObjectShape<{
|
|
2911
|
+
id: StringShape;
|
|
2912
|
+
name: StringShape;
|
|
2913
|
+
description: OptionalShape<StringShape>;
|
|
2914
|
+
run: UnionShape<[ ObjectShape<{
|
|
2915
|
+
via: LiteralShape<readonly ["function"]>;
|
|
2916
|
+
name: StringShape;
|
|
2917
|
+
}>, ObjectShape<{
|
|
2918
|
+
via: LiteralShape<readonly ["tool"]>;
|
|
2919
|
+
name: StringShape;
|
|
2920
|
+
}>, ObjectShape<{
|
|
2921
|
+
via: LiteralShape<readonly ["agent"]>;
|
|
2922
|
+
name: StringShape;
|
|
2923
|
+
}>]>;
|
|
2924
|
+
retries: OptionalShape<NumberShape>;
|
|
2925
|
+
timeout: OptionalShape<NumberShape>;
|
|
2926
|
+
}>>;
|
|
2927
|
+
concurrency: OptionalShape<NumberShape>;
|
|
2928
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
2929
|
+
}>>;
|
|
2930
|
+
bail: OptionalShape<LiteralShape<readonly [true, false]>>;
|
|
2931
|
+
}>;
|
|
2932
|
+
|
|
2933
|
+
/**
|
|
2934
|
+
* A JSON-serializable snapshot of a whole workflow's state — its identity, status, its
|
|
2935
|
+
* forced override (if any), the `bail` policy it ran under, its nested phase snapshots,
|
|
2936
|
+
* and creation / update timestamps.
|
|
2937
|
+
*
|
|
2938
|
+
* @remarks
|
|
2939
|
+
* Pure JSON DATA — the COMPLETE, SELF-CONTAINED payload the durable store (W-d) persists,
|
|
2940
|
+
* designed in full at W-a so its shape is fixed from the start. It can be written to disk,
|
|
2941
|
+
* sent to a prompt companion, loaded across conversations, or reviewed by an agent. Because
|
|
2942
|
+
* it is self-contained, it carries the policy it ran under: `bail` (AGENTS §4.4) is the
|
|
2943
|
+
* failure policy, so {@link import('./factories.js').restoreWorkflow} re-derives status
|
|
2944
|
+
* IDENTICALLY without a silent default. `status` is the EFFECTIVE status (override-or-derived)
|
|
2945
|
+
* at snapshot time; `override` is the forced status of a whole-workflow `skip` / `stop`,
|
|
2946
|
+
* PRESENT only when one is in force (so a restore reinstates it DIRECTLY rather than guessing
|
|
2947
|
+
* from a status divergence). `phases` are the workflow's {@link PhaseSnapshot}s in order;
|
|
2948
|
+
* `created` / `updated` are ms since epoch.
|
|
2949
|
+
*/
|
|
2950
|
+
export declare interface WorkflowSnapshot {
|
|
2951
|
+
readonly id: string;
|
|
2952
|
+
readonly name: string;
|
|
2953
|
+
readonly description?: string;
|
|
2954
|
+
readonly status: WorkflowStatus;
|
|
2955
|
+
/** The forced status of a whole-workflow `skip` / `stop`; present only when an override is in force. */
|
|
2956
|
+
readonly override?: WorkflowStatus;
|
|
2957
|
+
/** The failure policy the workflow ran under (AGENTS §4.4) — persisted so a restore re-derives identically. */
|
|
2958
|
+
readonly bail: boolean;
|
|
2959
|
+
readonly phases: readonly PhaseSnapshot[];
|
|
2960
|
+
readonly created: number;
|
|
2961
|
+
readonly updated: number;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
/**
|
|
2965
|
+
* One row of the table a {@link import('./stores/DatabaseWorkflowStore.js').DatabaseWorkflowStore}
|
|
2966
|
+
* persists — a workflow `id` plus its {@link WorkflowSnapshot} held as ONE OPAQUE JSON column.
|
|
2967
|
+
*
|
|
2968
|
+
* @remarks
|
|
2969
|
+
* The Database twin of {@link WorkflowStoreInterface} stores the snapshot whole (the `snapshot`
|
|
2970
|
+
* column is a `rawShape`, an opaque JSON blob — exactly as
|
|
2971
|
+
* `@orkestrel/queue`'s `StoredEntry` stores a queue entry's `input`), so the row
|
|
2972
|
+
* type stays FLAT and the deeply-nested snapshot shape (workflow → phases → tasks → results) never
|
|
2973
|
+
* forces the contract to `Infer` it — sidestepping a TS2589 instantiation-depth blow-up. The column
|
|
2974
|
+
* therefore reads back as the broad `unknown`; the store narrows it to a {@link WorkflowSnapshot} on
|
|
2975
|
+
* `get` ({@link import('./helpers.js').isWorkflowSnapshot}, the AGENTS §14 boundary narrow). `id`
|
|
2976
|
+
* mirrors {@link WorkflowSnapshot.id} (the primary key), so a `set` writes `{ id: snapshot.id, snapshot }`.
|
|
2977
|
+
*/
|
|
2978
|
+
export declare interface WorkflowSnapshotRow {
|
|
2979
|
+
readonly id: string;
|
|
2980
|
+
/** The whole {@link WorkflowSnapshot} as one opaque JSON blob — read back as `unknown`, narrowed on `get`. */
|
|
2981
|
+
readonly snapshot: unknown;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
/**
|
|
2985
|
+
* The lifecycle status of a workflow — the same vocabulary as {@link TaskStatus},
|
|
2986
|
+
* derived from its phases' statuses under the `bail` policy.
|
|
2987
|
+
*
|
|
2988
|
+
* @remarks
|
|
2989
|
+
* A semantic tier of the shared {@link LifecycleStatus} vocabulary. `failed` is
|
|
2990
|
+
* reachable ONLY under `bail: true` (a single failed task halts the whole workflow);
|
|
2991
|
+
* under `bail: false` a workflow `completed`s even with failed leaf tasks. See
|
|
2992
|
+
* {@link import('./helpers.js').deriveWorkflowStatus}.
|
|
2993
|
+
*/
|
|
2994
|
+
export declare type WorkflowStatus = LifecycleStatus;
|
|
2995
|
+
|
|
2996
|
+
/**
|
|
2997
|
+
* One flat step — `{ name, via? }` — the building block of a {@link WorkflowSteps} blob.
|
|
2998
|
+
*
|
|
2999
|
+
* @remarks
|
|
3000
|
+
* `name` is the REGISTERED behavior name the step runs (it becomes the task's `run.name`,
|
|
3001
|
+
* NOT a human label). `via` is the optional execution mechanism — `'function'` (the
|
|
3002
|
+
* default when omitted), `'tool'`, or `'agent'`.
|
|
3003
|
+
*/
|
|
3004
|
+
export declare interface WorkflowStep {
|
|
3005
|
+
/** The registered behavior name this step runs (becomes the task's `run.name`). */
|
|
3006
|
+
readonly name: string;
|
|
3007
|
+
/** How to run it — `'function'` (default), `'tool'`, or `'agent'`. */
|
|
3008
|
+
readonly via?: TaskVia;
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
/**
|
|
3012
|
+
* The FLAT authoring blob `createWorkflowTool` advertises — `{ name?, steps }` — the
|
|
3013
|
+
* simplest surface a small model can fill.
|
|
3014
|
+
*
|
|
3015
|
+
* @remarks
|
|
3016
|
+
* Each {@link WorkflowStep} becomes a one-task phase, in order
|
|
3017
|
+
* ({@link import('./helpers.js').expandSteps}); `name` is the optional workflow name
|
|
3018
|
+
* (defaulted when omitted). The expanded tree is validated against the STRICT
|
|
3019
|
+
* {@link import('./factories.js').createWorkflowContract} gate before running.
|
|
3020
|
+
*/
|
|
3021
|
+
export declare interface WorkflowSteps {
|
|
3022
|
+
readonly name?: string;
|
|
3023
|
+
readonly steps: readonly WorkflowStep[];
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
/**
|
|
3027
|
+
* The FLAT authoring shape `createWorkflowTool` advertises as its `parameters` — the
|
|
3028
|
+
* simplest surface a small model can fill: `{ name?, steps: [{ name, via? }] }`.
|
|
3029
|
+
*
|
|
3030
|
+
* @remarks
|
|
3031
|
+
* The deliberately-reduced surface (AGENTS §21): a flat ordered list of steps, each a
|
|
3032
|
+
* `{ name, via? }`. The tool EXPANDS it ({@link import('./helpers.js').expandSteps}) into a
|
|
3033
|
+
* full {@link import('./types.js').WorkflowDefinition} — one one-task phase per step, in
|
|
3034
|
+
* order — then validates against the STRICT
|
|
3035
|
+
* {@link import('./factories.js').createWorkflowContract} gate. The full nested form is
|
|
3036
|
+
* STILL accepted by the tool (it branches on the args' shape) and is documented as the
|
|
3037
|
+
* advanced escape-hatch in the tool's description — but THIS is what `parameters` advertises.
|
|
3038
|
+
*/
|
|
3039
|
+
export declare const workflowStepsShape: ObjectShape<{
|
|
3040
|
+
name: OptionalShape<StringShape>;
|
|
3041
|
+
steps: ArrayShape<ObjectShape<{
|
|
3042
|
+
name: StringShape;
|
|
3043
|
+
via: OptionalShape<LiteralShape<readonly ["function", "tool", "agent"]>>;
|
|
3044
|
+
}>>;
|
|
3045
|
+
}>;
|
|
3046
|
+
|
|
3047
|
+
/**
|
|
3048
|
+
* The durable persistence seam for a {@link WorkflowSnapshot} — three async primitives
|
|
3049
|
+
* (`get` / `set` / `delete`) keyed by a workflow id, the snapshot analogue of
|
|
3050
|
+
* the server package's `SessionStoreInterface` (and the
|
|
3051
|
+
* {@link QueueStoreInterface} driver-swap pattern).
|
|
3052
|
+
*
|
|
3053
|
+
* @remarks
|
|
3054
|
+
* The store persists the W-a {@link WorkflowSnapshot} — the COMPLETE, self-contained,
|
|
3055
|
+
* pure-JSON run state — so a JSON / SQLite / IndexedDB backend swaps in WITHOUT touching the
|
|
3056
|
+
* runner or the entity tree: the in-memory default
|
|
3057
|
+
* {@link import('./stores/MemoryWorkflowStore.js').MemoryWorkflowStore} and its driver-pluggable
|
|
3058
|
+
* twin {@link import('./stores/DatabaseWorkflowStore.js').DatabaseWorkflowStore} (the snapshot as
|
|
3059
|
+
* one opaque JSON column) share THIS one interface. Restore is NOT a store concern — a caller reads
|
|
3060
|
+
* a snapshot back and rebuilds the live tree with the shipped {@link import('./factories.js').restoreWorkflow}.
|
|
3061
|
+
*
|
|
3062
|
+
* Every primitive is async (a `Promise`), so a durable backend (a database round-trip) fits the
|
|
3063
|
+
* same shape as the memory one. The snapshot carries its OWN id, so `set` takes no separate id
|
|
3064
|
+
* param (mirroring {@link QueueStoreInterface.save} / the server package's
|
|
3065
|
+
* `SessionStoreInterface.set`, which key off the value's own
|
|
3066
|
+
* `id`). UNLIKE a session store there is NO idle-TTL / eviction — a persisted workflow run-state
|
|
3067
|
+
* lives until an explicit `delete`, never silently expiring (it is durable orchestration state,
|
|
3068
|
+
* not an ephemeral session). It is concrete over {@link WorkflowSnapshot} — no generic parameter
|
|
3069
|
+
* (AGENTS §21 minimal-interface), since the snapshot is the ONE payload a workflow store persists.
|
|
3070
|
+
*/
|
|
3071
|
+
export declare interface WorkflowStoreInterface {
|
|
3072
|
+
/**
|
|
3073
|
+
* Resolve the persisted snapshot for `id`, or `undefined` if none is stored.
|
|
3074
|
+
*
|
|
3075
|
+
* @param id - The workflow id to resolve (a {@link WorkflowSnapshot.id})
|
|
3076
|
+
* @returns The persisted snapshot, or `undefined` if absent
|
|
3077
|
+
*/
|
|
3078
|
+
get(id: string): Promise<WorkflowSnapshot | undefined>;
|
|
3079
|
+
/**
|
|
3080
|
+
* Insert or replace a snapshot under its own `snapshot.id` (no separate id param —
|
|
3081
|
+
* mirroring {@link QueueStoreInterface.save}).
|
|
3082
|
+
*
|
|
3083
|
+
* @param snapshot - The snapshot to store (keyed by its `id`)
|
|
3084
|
+
*/
|
|
3085
|
+
set(snapshot: WorkflowSnapshot): Promise<void>;
|
|
3086
|
+
/**
|
|
3087
|
+
* Drop a snapshot by id; an absent id is a no-op (no throw).
|
|
3088
|
+
*
|
|
3089
|
+
* @param id - The workflow id to drop
|
|
3090
|
+
*/
|
|
3091
|
+
delete(id: string): Promise<void>;
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
/**
|
|
3095
|
+
* The ancestry identifier of a workflow run — `workflow:<id>`.
|
|
3096
|
+
*
|
|
3097
|
+
* @remarks
|
|
3098
|
+
* The {@link import('./WorkflowRunner.js').WorkflowRunner}'s cycle guard records one of
|
|
3099
|
+
* these per workflow in the current nested run chain (carried on
|
|
3100
|
+
* {@link import('./types.js').WorkflowRunOptions.ancestry}). Tagging the bare id keeps a
|
|
3101
|
+
* workflow id and an {@link agentTag} agent name in ONE namespaced set without collision,
|
|
3102
|
+
* so re-entering a workflow OR an agent already in the chain is a single `includes` check.
|
|
3103
|
+
*
|
|
3104
|
+
* @param id - The workflow definition's `id`
|
|
3105
|
+
* @returns The namespaced ancestry tag (`workflow:<id>`)
|
|
3106
|
+
*/
|
|
3107
|
+
export declare function workflowTag(id: string): string;
|
|
3108
|
+
|
|
3109
|
+
/**
|
|
3110
|
+
* The workflow-tool binder — the signature of {@link import('./factories.js').createWorkflowTool},
|
|
3111
|
+
* threaded into the {@link WorkflowRunnerInterface} at construction (W-c2) so the runner can BIND a
|
|
3112
|
+
* depth/cycle-aware workflow tool onto a dispatched subagent.
|
|
3113
|
+
*
|
|
3114
|
+
* @remarks
|
|
3115
|
+
* The runner receives a REFERENCE to `createWorkflowTool` rather than importing it, because
|
|
3116
|
+
* `createWorkflowTool` lives in `factories.ts` which imports the runner CLASS (the
|
|
3117
|
+
* factories→classes direction); importing the factory back into the class would be a cycle. The
|
|
3118
|
+
* factory (which legitimately owns `createWorkflowContract`) is passed as a value at construction,
|
|
3119
|
+
* and the runner invokes it with `this` as the `runner` argument — keeping the cycle-free seam an
|
|
3120
|
+
* explicit, typed contract rather than a hidden dependency.
|
|
3121
|
+
*
|
|
3122
|
+
* @param definition - The workflow the bound tool runs when called with no authored args
|
|
3123
|
+
* @param runner - The runner that executes the (nested) workflow (the runner passes `this`)
|
|
3124
|
+
* @param options - The depth + ancestry the nested workflow runs under (see {@link WorkflowToolOptions})
|
|
3125
|
+
* @returns The bound {@link ToolInterface}
|
|
3126
|
+
*/
|
|
3127
|
+
export declare type WorkflowToolBinder = (definition: WorkflowDefinition, runner: WorkflowRunnerInterface, options?: WorkflowToolOptions) => ToolInterface;
|
|
3128
|
+
|
|
3129
|
+
/**
|
|
3130
|
+
* Options for {@link import('./factories.js').createWorkflowTool} — the depth + ancestry the
|
|
3131
|
+
* wrapped {@link WorkflowDefinition} runs the NESTED workflow at when an LLM invokes the tool.
|
|
3132
|
+
*
|
|
3133
|
+
* @remarks
|
|
3134
|
+
* This is the PROPAGATION carrier across the agent/tool boundary. A `Tool`'s handler receives
|
|
3135
|
+
* ONLY the model-supplied `args` (no ambient context, no signal — see
|
|
3136
|
+
* the `@orkestrel/agent` package's `ToolOptions`), so the run's position in the
|
|
3137
|
+
* workflow→agent→workflow chain CANNOT be threaded through a tool call at runtime. Instead the
|
|
3138
|
+
* runner CLOSES it over the tool at BIND time: when it dispatches an `agent` task at depth `D`
|
|
3139
|
+
* with ancestry `A`, it builds the agent's workflow tool with `{ depth: D, ancestry: A }`, so
|
|
3140
|
+
* the handler's closure carries them. On invocation the handler runs the nested workflow at
|
|
3141
|
+
* `depth: D + 1` with `ancestry: A ∪ { workflow:<id> }` — bounded by
|
|
3142
|
+
* {@link import('./constants.js').MAX_WORKFLOW_DEPTH} (an over-deep / cyclic nested run THROWS a
|
|
3143
|
+
* typed `DEPTH` {@link import('./errors.js').WorkflowError}, which the `ToolManager` isolates into
|
|
3144
|
+
* the tool result's top-level `error`, AGENTS §14).
|
|
3145
|
+
*
|
|
3146
|
+
* Both fields are OPTIONAL: a workflow tool built for a TOP-LEVEL caller (not by the runner's
|
|
3147
|
+
* agent-task binding) omits them — its nested run starts the chain at depth `1` with the bare
|
|
3148
|
+
* `workflow:<id>` ancestry.
|
|
3149
|
+
*/
|
|
3150
|
+
export declare interface WorkflowToolOptions {
|
|
3151
|
+
/** The depth the INVOKING agent runs at; the nested workflow runs at `depth + 1`. Default `0`. */
|
|
3152
|
+
readonly depth?: number;
|
|
3153
|
+
/** The ancestry of the invoking run; the nested run extends it with its own `workflow:<id>`. Default empty. */
|
|
3154
|
+
readonly ancestry?: readonly string[];
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
/**
|
|
3158
|
+
* Summarize a terminal {@link WorkflowResult} into the PLAIN value a
|
|
3159
|
+
* {@link import('./factories.js').createWorkflowTool} handler returns on success.
|
|
3160
|
+
*
|
|
3161
|
+
* @remarks
|
|
3162
|
+
* This is the run summary the handler returns DIRECTLY — NOT a `ToolResult` (the future `@orkestrel/agent` package).
|
|
3163
|
+
* The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the plain value
|
|
3164
|
+
* (and throws on failure), so the `@orkestrel/agent` package's `ToolManager` performs
|
|
3165
|
+
* the ONE canonical wrap (`{ id, name, value }`) and the model reads exactly this summary — once,
|
|
3166
|
+
* identically — over BOTH the agent loop and MCP. The summary is LEAN: the workflow's terminal `status`
|
|
3167
|
+
* and the COUNT of settled task results — enough for a caller / model to react without serializing the
|
|
3168
|
+
* whole live tree. (It carries no synthetic `id` / `name`: a tool handler has no call id; the manager
|
|
3169
|
+
* supplies the canonical envelope's identity.)
|
|
3170
|
+
*
|
|
3171
|
+
* @param result - The terminal {@link WorkflowResult} the run produced
|
|
3172
|
+
* @returns The plain success summary — `{ status, count }`
|
|
3173
|
+
*/
|
|
3174
|
+
export declare function workflowToolSummary(result: WorkflowResult): Readonly<{
|
|
3175
|
+
status: WorkflowStatus;
|
|
3176
|
+
count: number;
|
|
3177
|
+
}>;
|
|
3178
|
+
|
|
3179
|
+
export { }
|