@kindgi/flow 0.0.0-bootstrap.0 → 0.1.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 +201 -0
- package/README.md +262 -2
- package/dist/define.d.ts +36 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +29 -0
- package/dist/define.js.map +1 -0
- package/dist/errors.d.ts +252 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluator.d.ts +69 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/evaluator.js +220 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/flow.schema.json +517 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +43 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +1051 -0
- package/dist/loader.js.map +1 -0
- package/dist/scheduler.d.ts +91 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +187 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/types.d.ts +792 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +71 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -4
- package/src/define.ts +44 -0
- package/src/errors.ts +306 -0
- package/src/evaluator.ts +254 -0
- package/src/flow.schema.json +517 -0
- package/src/index.ts +85 -0
- package/src/loader.ts +1382 -0
- package/src/scheduler.ts +274 -0
- package/src/types.ts +853 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import type { EdgeId, FlowId, NodeId } from '@kindgi/types';
|
|
2
|
+
/**
|
|
3
|
+
* Path roots addressable in edge predicates.
|
|
4
|
+
*
|
|
5
|
+
* Every path in an operand starts with one of these roots. The kernel resolves
|
|
6
|
+
* each root against the current run state at the moment an edge is evaluated:
|
|
7
|
+
*
|
|
8
|
+
* - `runInput` → the input value passed to `runGraph()`.
|
|
9
|
+
* - `nodeOutputs.<id>` → the resolved output of a previously-completed node,
|
|
10
|
+
* keyed by its declared `NodeId`. Immutable — one
|
|
11
|
+
* writer per node, no fan-in contention.
|
|
12
|
+
* - `state` → the run's cumulative state channel. Last-writer-wins
|
|
13
|
+
* under concurrent writes; use `nodeOutputs.<id>` for
|
|
14
|
+
* conflict-free per-node results.
|
|
15
|
+
* - `iterationIndex` → 0-based iteration counter. Only meaningful inside a
|
|
16
|
+
* loop node's `exitCondition`. Resolves to `undefined`
|
|
17
|
+
* in any other context.
|
|
18
|
+
* - `iterationOutput` → the `$loop-end` output of the just-completed
|
|
19
|
+
* iteration. Only meaningful inside a loop node's
|
|
20
|
+
* `exitCondition`. Resolves to `undefined` elsewhere.
|
|
21
|
+
*/
|
|
22
|
+
export type PathRoot = 'runInput' | 'state' | `nodeOutputs.${string}` | 'iterationIndex' | 'iterationOutput';
|
|
23
|
+
/**
|
|
24
|
+
* A path into the run's addressable state. Dot-separated segments, always
|
|
25
|
+
* starting with a recognized root. Path syntax is deliberately narrow — no
|
|
26
|
+
* wildcards, no array indexing, no filters. If a predicate needs more, the
|
|
27
|
+
* caller should compute a normalized field into `state` from a prior node
|
|
28
|
+
* and reference that.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* 'runInput.userId'
|
|
32
|
+
* 'nodeOutputs.classify.category'
|
|
33
|
+
* 'state.retryCount'
|
|
34
|
+
* 'iterationOutput.finishReason' // only inside a loop exitCondition
|
|
35
|
+
* 'iterationIndex' // only inside a loop exitCondition
|
|
36
|
+
*/
|
|
37
|
+
export type Path = string;
|
|
38
|
+
/** Literal operand — a JSON-serializable value compared against a path. */
|
|
39
|
+
export interface LiteralOperand {
|
|
40
|
+
readonly literal: unknown;
|
|
41
|
+
}
|
|
42
|
+
/** Path operand — resolves at eval time against the current run's environment. */
|
|
43
|
+
export interface PathOperand {
|
|
44
|
+
readonly path: Path;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Operand = one side of a comparison. Every operand is either a static literal
|
|
48
|
+
* or a path reference. Keeping them symmetric means predicates naturally
|
|
49
|
+
* express path-vs-path comparisons (`state.attempts` vs `state.maxAttempts`)
|
|
50
|
+
* without a second operator flavor.
|
|
51
|
+
*/
|
|
52
|
+
export type Operand = LiteralOperand | PathOperand;
|
|
53
|
+
/**
|
|
54
|
+
* A declarative object (schema-version 1.8.0+): each key maps to an
|
|
55
|
+
* Operand — a `{ literal }`, or a `{ path }` rooted at `runInput`,
|
|
56
|
+
* `state` or `nodeOutputs.<nodeId>`. Resolved against the run
|
|
57
|
+
* environment with `resolveMapping`; a path that doesn't resolve omits
|
|
58
|
+
* its key, so a consumer can tell "not provided" from any real value.
|
|
59
|
+
*
|
|
60
|
+
* Used for a leaf node's `inputMapping`, a subgraph node's
|
|
61
|
+
* `inputMapping` and a flow's `output`.
|
|
62
|
+
*/
|
|
63
|
+
export type Mapping = Readonly<Record<string, Operand>>;
|
|
64
|
+
/**
|
|
65
|
+
* Predicate expression. Serializable JSON. Evaluated to a boolean at edge
|
|
66
|
+
* dispatch time; an edge fires iff its `when` evaluates true (or is absent).
|
|
67
|
+
*
|
|
68
|
+
* Design constraint: closed operator set + JSON-serializable operand shape.
|
|
69
|
+
* This is deliberately narrower than a JS closure so flows can round-trip
|
|
70
|
+
* through storage, get rendered in a visual editor, and stay reproducible
|
|
71
|
+
* across process restarts.
|
|
72
|
+
*/
|
|
73
|
+
export type Expr = {
|
|
74
|
+
readonly op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte';
|
|
75
|
+
readonly left: Operand;
|
|
76
|
+
readonly right: Operand;
|
|
77
|
+
} | {
|
|
78
|
+
readonly op: 'in' | 'notIn';
|
|
79
|
+
readonly value: Operand;
|
|
80
|
+
readonly set: Operand;
|
|
81
|
+
} | {
|
|
82
|
+
readonly op: 'exists' | 'notExists' | 'truthy' | 'falsy';
|
|
83
|
+
readonly value: Operand;
|
|
84
|
+
} | {
|
|
85
|
+
readonly op: 'and' | 'or';
|
|
86
|
+
readonly children: readonly Expr[];
|
|
87
|
+
} | {
|
|
88
|
+
readonly op: 'not';
|
|
89
|
+
readonly child: Expr;
|
|
90
|
+
};
|
|
91
|
+
/** The set of operator names recognized by the DSL, exported for tooling. */
|
|
92
|
+
export declare const OPERATORS: readonly ["eq", "ne", "lt", "lte", "gt", "gte", "in", "notIn", "exists", "notExists", "truthy", "falsy", "and", "or", "not"];
|
|
93
|
+
export type OperatorName = (typeof OPERATORS)[number];
|
|
94
|
+
/**
|
|
95
|
+
* A leaf node — the classic "one handler" primitive. `kind` distinguishes what
|
|
96
|
+
* this node executes:
|
|
97
|
+
* - `tool` → an in-process or MCP tool call.
|
|
98
|
+
* - `agent` → an agent invocation.
|
|
99
|
+
*
|
|
100
|
+
* Invoking another flow is not a leaf kind: it is the `SubflowNode`
|
|
101
|
+
* variant (`kind: 'subgraph'`, schema-version 1.7.0+) with `flowRef` /
|
|
102
|
+
* `inputMapping` / `outputSchema` / `convergence`.
|
|
103
|
+
*
|
|
104
|
+
* The flow package holds no handler bindings; the runtime looks `ref` up
|
|
105
|
+
* in its `HandlerRegistry` (`@kindgi/handler`).
|
|
106
|
+
*/
|
|
107
|
+
export interface LeafNode {
|
|
108
|
+
/** Node id (unique within the containing flow). */
|
|
109
|
+
readonly id: NodeId;
|
|
110
|
+
/**
|
|
111
|
+
* Which handler kind runs at this node. `'tool'` dispatches
|
|
112
|
+
* `ref` as a `ToolId`; `'agent'` dispatches `ref` as an `AgentId`.
|
|
113
|
+
*/
|
|
114
|
+
readonly kind: 'tool' | 'agent';
|
|
115
|
+
/**
|
|
116
|
+
* Reference to the concrete implementation. Interpretation depends on
|
|
117
|
+
* `kind` and is opaque to the flow package.
|
|
118
|
+
*/
|
|
119
|
+
readonly ref: string;
|
|
120
|
+
readonly config?: Readonly<Record<string, unknown>>;
|
|
121
|
+
/**
|
|
122
|
+
* What this node's handler receives (schema-version 1.8.0+): each key
|
|
123
|
+
* resolved when the node dispatches, so a node can combine the run
|
|
124
|
+
* input, state and the outputs of any earlier nodes. Absent → the
|
|
125
|
+
* output of the node's single upstream node, or the run input for a
|
|
126
|
+
* `$start` edge. The loader rejects paths that aren't rooted at
|
|
127
|
+
* `runInput` / `state` / `nodeOutputs.<existing node>`.
|
|
128
|
+
*/
|
|
129
|
+
readonly inputMapping?: Mapping;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* JSON Schema (draft 2020-12) describing the shape a loop's iteration
|
|
133
|
+
* outputs must conform to. Stored as an opaque object at the flow layer;
|
|
134
|
+
* the kernel validates each iteration's `$loop-end` output against this
|
|
135
|
+
* schema at iteration boundary. Malformed schema surfaces at flow load
|
|
136
|
+
* time via `LoopOutputSchemaError`.
|
|
137
|
+
*
|
|
138
|
+
* Kept as `object` here (not a specific TypeScript type) because JSON
|
|
139
|
+
* Schema's full expressiveness doesn't map cleanly onto a static type.
|
|
140
|
+
* The loader validates that the schema conforms to the draft-2020-12
|
|
141
|
+
* meta-schema; the kernel treats it as opaque input to Ajv.
|
|
142
|
+
*/
|
|
143
|
+
export type LoopOutputSchema = Readonly<Record<string, unknown>>;
|
|
144
|
+
/**
|
|
145
|
+
* Fields shared by every loop variant. See the concrete variants
|
|
146
|
+
* (`WhileLoopNode`, `ForeachLoopNode`) for variant-specific fields.
|
|
147
|
+
*
|
|
148
|
+
* Every iteration produces distinct journal entries (`iteration.started`,
|
|
149
|
+
* `iteration.completed`); body-node journal entries carry a `loopContext`
|
|
150
|
+
* field attributing them to the enclosing iteration. Iterations run one
|
|
151
|
+
* at a time, except in a foreach loop that sets `concurrency` (see
|
|
152
|
+
* `ForeachLoopNode`).
|
|
153
|
+
*
|
|
154
|
+
* Node ids in the body must be globally unique across the entire flow
|
|
155
|
+
* (including sibling loop bodies at any nesting level). This is enforced
|
|
156
|
+
* by the loader.
|
|
157
|
+
*
|
|
158
|
+
* State inside a loop is always loop-internal: body-handler `stateDelta`
|
|
159
|
+
* writes do NOT leak to outer flow state after loop completion. The
|
|
160
|
+
* escape contract is `outputSchema` — required on every loop, no
|
|
161
|
+
* exceptions. There is no `stateScope` option.
|
|
162
|
+
*/
|
|
163
|
+
interface LoopNodeCommon {
|
|
164
|
+
/** Node id (unique across the ENTIRE flow including nested loop bodies). */
|
|
165
|
+
readonly id: NodeId;
|
|
166
|
+
/** Discriminant — always `'loop'` for this node kind. */
|
|
167
|
+
readonly kind: 'loop';
|
|
168
|
+
/** The body sub-flow iterated per loop pass. */
|
|
169
|
+
readonly body: LoopBody;
|
|
170
|
+
/**
|
|
171
|
+
* Hard cap on iteration count. Required. Bounded to `1 ≤ maxIterations ≤ 10000`
|
|
172
|
+
* by the loader. When the cap is reached, the loop exits with
|
|
173
|
+
* `stopReason: 'max-iterations'`.
|
|
174
|
+
*/
|
|
175
|
+
readonly maxIterations: number;
|
|
176
|
+
/**
|
|
177
|
+
* REQUIRED. JSON Schema (draft 2020-12) describing the shape every
|
|
178
|
+
* iteration's `$loop-end` output must satisfy. Also constrains
|
|
179
|
+
* `finalOutput` and (when `collectAllIterations`) each element of
|
|
180
|
+
* `outputs[]`. Every loop declares a typed contract — no exceptions.
|
|
181
|
+
*/
|
|
182
|
+
readonly outputSchema: LoopOutputSchema;
|
|
183
|
+
/**
|
|
184
|
+
* When true, the loop node's output includes an `outputs` array with
|
|
185
|
+
* every iteration's `$loop-end` output. When false (default), only the
|
|
186
|
+
* last iteration's output is exposed via `finalOutput`; callers wanting
|
|
187
|
+
* history read the journal.
|
|
188
|
+
*/
|
|
189
|
+
readonly collectAllIterations?: boolean;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A `while`-style loop node: iteration is condition-driven. Runs the
|
|
193
|
+
* body until `exitCondition` evaluates true (or `maxIterations` fires).
|
|
194
|
+
*
|
|
195
|
+
* `evaluationTiming` selects do-while (default) vs while semantics:
|
|
196
|
+
* - `'after'` → body runs first, then exit condition is checked
|
|
197
|
+
* against the just-completed iteration output. Loop
|
|
198
|
+
* always runs at least once. This is `do { body } while (!exit)`.
|
|
199
|
+
* - `'before'` → exit condition is checked first, against the previous
|
|
200
|
+
* iteration's output (or `null` on iteration 0). Body
|
|
201
|
+
* may run zero times. This is `while (!exit) { body }`.
|
|
202
|
+
*
|
|
203
|
+
* Exit condition path roots: `state`, `iterationIndex`, `iterationOutput`,
|
|
204
|
+
* and `nodeOutputs.<bodyNodeId>` (of the just-completed iteration; not
|
|
205
|
+
* available on `before`-timed iteration 0).
|
|
206
|
+
*/
|
|
207
|
+
export interface WhileLoopNode extends LoopNodeCommon {
|
|
208
|
+
readonly loopKind: 'while';
|
|
209
|
+
readonly exitCondition: Expr;
|
|
210
|
+
/**
|
|
211
|
+
* When the exit condition is evaluated relative to the body. Default `'after'`.
|
|
212
|
+
*/
|
|
213
|
+
readonly evaluationTiming?: 'before' | 'after';
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A `foreach`-style loop node: iteration is array-driven. Runs the body
|
|
217
|
+
* once per element of `iterateOver` (up to `maxIterations`).
|
|
218
|
+
*
|
|
219
|
+
* `iterateOver` is resolved ONCE at loop-enter time (not re-resolved per
|
|
220
|
+
* iteration) — the array is pinned for the duration of the loop. If it
|
|
221
|
+
* resolves to a non-array, the loop fails immediately with
|
|
222
|
+
* `stopReason: 'iterate-over-not-array'`.
|
|
223
|
+
*
|
|
224
|
+
* Each iteration's `$loop-start` input is the corresponding array element.
|
|
225
|
+
* The loop exits when the array is exhausted (`stopReason: 'array-exhausted'`)
|
|
226
|
+
* or `maxIterations` fires. No exit condition — attempting to declare one
|
|
227
|
+
* is a load-time validation error.
|
|
228
|
+
*
|
|
229
|
+
* `state.$loop.currentElement` inside a foreach body handler is the
|
|
230
|
+
* element fed to the current iteration. `iterationIndex` is the 0-based
|
|
231
|
+
* position in the source array.
|
|
232
|
+
*/
|
|
233
|
+
export interface ForeachLoopNode extends LoopNodeCommon {
|
|
234
|
+
readonly loopKind: 'foreach';
|
|
235
|
+
/**
|
|
236
|
+
* Operand that must resolve to an array at loop-enter time. Typical
|
|
237
|
+
* shape: `{ path: 'runInput.items' }` or `{ path: 'nodeOutputs.list.records' }`.
|
|
238
|
+
*/
|
|
239
|
+
readonly iterateOver: Operand;
|
|
240
|
+
/**
|
|
241
|
+
* Parallel-worker count for iteration dispatch. Default `1` (strictly
|
|
242
|
+
* sequential). When set to N > 1, the
|
|
243
|
+
* kernel spins up N workers that pull iteration indices from a shared
|
|
244
|
+
* counter — iteration `i` is assigned in array order (0, 1, 2, …) but
|
|
245
|
+
* may settle in wall-clock order.
|
|
246
|
+
*
|
|
247
|
+
* Bounded by the loader to `1 ≤ concurrency ≤ min(maxIterations, 32)`.
|
|
248
|
+
* The upper cap of 32 is a fixed safety limit.
|
|
249
|
+
*
|
|
250
|
+
* Canonical guarantees under `concurrency > 1`:
|
|
251
|
+
* - `outputs[]` (when `collectAllIterations` is true) is sorted by
|
|
252
|
+
* iteration index, NOT settle order.
|
|
253
|
+
* - Journal `iteration.*` entries reflect wall-clock settle order.
|
|
254
|
+
* Consumers sort by `iteration` for canonical projection.
|
|
255
|
+
* - Handler failure in one iteration cancels all in-flight siblings
|
|
256
|
+
* cleanly (each journals `iteration.completed` with
|
|
257
|
+
* `stopReason: 'cancelled'`); the failed iteration journals
|
|
258
|
+
* `stopReason: 'body-failure'` and the loop node fails.
|
|
259
|
+
*
|
|
260
|
+
* State inside a loop body is per-iteration already (by design),
|
|
261
|
+
* so parallel iterations cannot race on shared body state.
|
|
262
|
+
*/
|
|
263
|
+
readonly concurrency?: number;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* A loop node — first-class iteration primitive (schema-version 1.2.0+).
|
|
267
|
+
* Discriminated by `loopKind` into `while` and `foreach` variants.
|
|
268
|
+
*/
|
|
269
|
+
export type LoopNode = WhileLoopNode | ForeachLoopNode;
|
|
270
|
+
/**
|
|
271
|
+
* Convergence mode for a `fanout` node — declared at flow-authoring time,
|
|
272
|
+
* decidable at flow-load time (so downstream consumers know exactly what
|
|
273
|
+
* shape they'll receive).
|
|
274
|
+
*
|
|
275
|
+
* - `'all-succeed'` — fanout fails on the FIRST branch failure. In-flight
|
|
276
|
+
* sibling branches are cancelled through the standard kernel abort
|
|
277
|
+
* path. Output emitted only when EVERY branch succeeds; shape is
|
|
278
|
+
* `{ [branchId: string]: output }`.
|
|
279
|
+
* - `'any-succeed'` — fanout succeeds on the FIRST branch success. In-flight
|
|
280
|
+
* sibling branches are cancelled through the standard kernel abort path.
|
|
281
|
+
* Output shape is `{ winnerBranchId, output }`. Fails only when every
|
|
282
|
+
* branch fails.
|
|
283
|
+
* - `'settle-all'` — fanout waits for every branch to reach a terminal
|
|
284
|
+
* state (success or fail); no sibling cancellation on failure. Output
|
|
285
|
+
* shape is `{ [branchId: string]: { status: 'succeeded' | 'failed',
|
|
286
|
+
* output? | error? } }`. This is the default recommended mode for
|
|
287
|
+
* supervisor-style "invoke N in parallel and reduce" patterns.
|
|
288
|
+
*/
|
|
289
|
+
export type ConvergenceMode = 'all-succeed' | 'any-succeed' | 'settle-all';
|
|
290
|
+
/**
|
|
291
|
+
* The set of convergence-mode names recognized by the runtime, exported for
|
|
292
|
+
* tooling that needs the closed enum without repeating literals.
|
|
293
|
+
*/
|
|
294
|
+
export declare const CONVERGENCE_MODES: readonly ["all-succeed", "any-succeed", "settle-all"];
|
|
295
|
+
/**
|
|
296
|
+
* One branch of a `fanout` node. Each branch is a distinct handler-registry
|
|
297
|
+
* entry executed concurrently with its siblings; a branch is NOT a sub-flow
|
|
298
|
+
* (unlike a loop body). `branchId` is the stable label used in the fan-in
|
|
299
|
+
* output object and the journal; `handler` is the string key used to look
|
|
300
|
+
* the handler up in the kernel's `HandlerRegistry` (same lookup mechanism
|
|
301
|
+
* as a leaf node's `ref`). `outputSchema` is REQUIRED — the typed contract
|
|
302
|
+
* for this branch's output, validated at settle time.
|
|
303
|
+
*/
|
|
304
|
+
export interface FanoutBranch {
|
|
305
|
+
readonly branchId: string;
|
|
306
|
+
readonly handler: string;
|
|
307
|
+
readonly outputSchema: LoopOutputSchema;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* A fanout node — first-class parallel-branch primitive (schema-version
|
|
311
|
+
* 1.6.0+). Runs N DIFFERENT handler bodies in parallel from a single
|
|
312
|
+
* dispatch and converges by a declared mode. Complementary to `foreach`
|
|
313
|
+
* (N iterations of the SAME body): fanout invokes N distinct handlers
|
|
314
|
+
* concurrently.
|
|
315
|
+
*
|
|
316
|
+
* The fanout node itself is treated by the outer scheduler as an ordinary
|
|
317
|
+
* node with a single output — its incoming edges resolve the input passed
|
|
318
|
+
* to every branch, and its outgoing edges receive the fan-in output. The
|
|
319
|
+
* fan-in shape depends on `convergence` (see `ConvergenceMode`).
|
|
320
|
+
*
|
|
321
|
+
* Sibling cancellation for `all-succeed` / `any-succeed` propagates via a
|
|
322
|
+
* shared `AbortController` composed with the outer abort signal, so
|
|
323
|
+
* cooperative branch handlers observe cancellation through their
|
|
324
|
+
* `NodeContext.abortSignal` — same discipline as parallel foreach and
|
|
325
|
+
* the outer executor's cancelRun path.
|
|
326
|
+
*
|
|
327
|
+
* Replay semantics: the outer envelope (`step.started` / `step.completed`
|
|
328
|
+
* / `step.failed` for the fanout node id) is journaled as with any other
|
|
329
|
+
* node. Per-branch progress is journaled through the fanout-specific
|
|
330
|
+
* `JournalKind` values (`fanout.dispatched`, `fanout.branch-completed`,
|
|
331
|
+
* `fanout.branch-failed`, `fanout.converged`, `fanout.cancelled-siblings`).
|
|
332
|
+
* On resume, if the fanout's outer `step.completed` is present the
|
|
333
|
+
* converged output is already in `nodeOutputs`; otherwise the whole
|
|
334
|
+
* fanout re-dispatches (same rule as loops — branch handlers are
|
|
335
|
+
* expected to be idempotent, or to use waitForToken for durable side
|
|
336
|
+
* effects).
|
|
337
|
+
*/
|
|
338
|
+
export interface FanoutNode {
|
|
339
|
+
/** Node id (unique within the containing flow). */
|
|
340
|
+
readonly id: NodeId;
|
|
341
|
+
/** Discriminant — always `'fanout'` for this node kind. */
|
|
342
|
+
readonly kind: 'fanout';
|
|
343
|
+
/**
|
|
344
|
+
* Parallel branches to dispatch. Each branch is one handler (see
|
|
345
|
+
* `FanoutBranch`), not a sub-flow. All branches receive the same
|
|
346
|
+
* input; convergence collects their outputs.
|
|
347
|
+
*/
|
|
348
|
+
readonly branches: readonly FanoutBranch[];
|
|
349
|
+
/**
|
|
350
|
+
* Optional bounded worker-pool count. Default: `branches.length` (all
|
|
351
|
+
* branches dispatch concurrently). Loader enforces `1 ≤ concurrency ≤
|
|
352
|
+
* branches.length`. When `concurrency < branches.length`, branches are
|
|
353
|
+
* pulled from the queue in declaration order (deterministic +
|
|
354
|
+
* replay-safe).
|
|
355
|
+
*/
|
|
356
|
+
readonly concurrency?: number;
|
|
357
|
+
/** The convergence rule. Decides the fan-in shape (see `ConvergenceMode`). */
|
|
358
|
+
readonly convergence: ConvergenceMode;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Convergence mode for a `subgraph` node (schema-version 1.7.0+). Declared
|
|
362
|
+
* at flow-authoring time; decidable at flow-load time so downstream
|
|
363
|
+
* consumers know exactly what shape they'll receive from the parent node.
|
|
364
|
+
*
|
|
365
|
+
* - `'success-only'` — child failure fails the PARENT node. The
|
|
366
|
+
* parent's `nodeOutputs` entry is never written; the journal records
|
|
367
|
+
* `subgraph.failed` and the parent step transitions to `step.failed`
|
|
368
|
+
* with an attribution that names the child run id + stop reason.
|
|
369
|
+
* - `'settle-all'` — the parent node's output is always a settled
|
|
370
|
+
* envelope: `{ status: 'succeeded'; output }` when the child run
|
|
371
|
+
* completed cleanly + its terminal output validated against the
|
|
372
|
+
* subgraph node's declared `outputSchema`; `{ status: 'failed';
|
|
373
|
+
* error }` otherwise (child failure, cancellation, or output
|
|
374
|
+
* schema violation). The parent step transitions to `step.completed`
|
|
375
|
+
* regardless of child outcome — the failure is DATA at the parent
|
|
376
|
+
* boundary, not a fault. This is the default recommended mode for
|
|
377
|
+
* supervisor-style orchestration where "sub-run failed" is a
|
|
378
|
+
* first-class outcome the parent flow handles explicitly.
|
|
379
|
+
*/
|
|
380
|
+
export type SubflowConvergence = 'success-only' | 'settle-all';
|
|
381
|
+
/**
|
|
382
|
+
* The set of subgraph convergence-mode names recognized by the runtime,
|
|
383
|
+
* exported for tooling that needs the closed enum without duplicating
|
|
384
|
+
* literals.
|
|
385
|
+
*/
|
|
386
|
+
export declare const SUBGRAPH_CONVERGENCE_MODES: readonly ["success-only", "settle-all"];
|
|
387
|
+
/**
|
|
388
|
+
* Reference to another registered flow resolvable via
|
|
389
|
+
* `FlowRegistryBinding.getVersion(...)`. Version is an EXACT semver — no
|
|
390
|
+
* ranges — so deterministic replay always resolves the same child flow.
|
|
391
|
+
* Cross-tenant lookup is rejected by construction: the kernel invokes the
|
|
392
|
+
* resolver with the PARENT run's tenantId; a flow registered under a
|
|
393
|
+
* different tenant will not be found.
|
|
394
|
+
*/
|
|
395
|
+
export interface SubflowRef {
|
|
396
|
+
readonly flowId: string;
|
|
397
|
+
readonly version: string;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* A first-class subgraph-invocation node (schema-version 1.7.0+).
|
|
401
|
+
* Dispatches a full kernel run of another registered flow as one
|
|
402
|
+
* outer-node step. The sub-run has its own independent journal, replay
|
|
403
|
+
* semantics, lease/cancel machinery — every kernel durability
|
|
404
|
+
* guarantee is inherited without re-invention.
|
|
405
|
+
*
|
|
406
|
+
* Complementary to the loop + fanout primitives:
|
|
407
|
+
* - `loop.foreach` runs the SAME body × N iterations,
|
|
408
|
+
* - `fanout` runs N DIFFERENT bodies concurrently,
|
|
409
|
+
* - `subgraph` runs a whole OTHER flow inline as a node.
|
|
410
|
+
*
|
|
411
|
+
* `inputMapping` is a declarative object whose values are `Operand`s
|
|
412
|
+
* (path into `runInput` / `state` / `nodeOutputs.<id>`, or a `literal`).
|
|
413
|
+
* The sub-run's `runInput` is the object produced by resolving every
|
|
414
|
+
* mapping at dispatch time.
|
|
415
|
+
*
|
|
416
|
+
* `outputSchema` is the ESCAPE CONTRACT declared on the parent — decoupled
|
|
417
|
+
* from the sub-flow's own internal shape. Parents state what they can
|
|
418
|
+
* consume; the kernel validates the sub-run's terminal output against
|
|
419
|
+
* this schema at settle time. A validation failure is a `settle-all`
|
|
420
|
+
* `{ status: 'failed', error }` OR a `success-only` parent-step failure.
|
|
421
|
+
*
|
|
422
|
+
* A kernel-enforced max depth (default 10, configurable via
|
|
423
|
+
* `RunOptions.maxSubflowDepth`) prevents runaway recursion. Exceeding
|
|
424
|
+
* the depth fails the parent node at dispatch with attribution
|
|
425
|
+
* `subgraph-depth-exceeded`.
|
|
426
|
+
*/
|
|
427
|
+
export interface SubflowNode {
|
|
428
|
+
/** Node id (unique within the parent flow). */
|
|
429
|
+
readonly id: NodeId;
|
|
430
|
+
/** Discriminant — always `'subgraph'` for this node kind. */
|
|
431
|
+
readonly kind: 'subgraph';
|
|
432
|
+
/**
|
|
433
|
+
* Which flow to invoke. `{flowId, version}` — `loadFlow` checks
|
|
434
|
+
* only the shape (exact semver). The runtime resolves the sub-flow
|
|
435
|
+
* through the run's `FlowResolver` (`@kindgi/runtime`) when the node
|
|
436
|
+
* dispatches; a flow it can't find fails the node with
|
|
437
|
+
* `subgraph-flow-not-found`. The exact version pins the child flow,
|
|
438
|
+
* so a mid-flight sub-run survives parent-flow edits.
|
|
439
|
+
*/
|
|
440
|
+
readonly flowRef: SubflowRef;
|
|
441
|
+
/**
|
|
442
|
+
* Declarative object mapping each top-level key of the sub-run's
|
|
443
|
+
* `runInput` to an Operand. Loader statically verifies every value is
|
|
444
|
+
* a valid Operand; runtime resolution mirrors edge-predicate operand
|
|
445
|
+
* resolution.
|
|
446
|
+
*/
|
|
447
|
+
readonly inputMapping: Mapping;
|
|
448
|
+
/**
|
|
449
|
+
* REQUIRED. JSON Schema (draft 2020-12) describing the shape the
|
|
450
|
+
* sub-run's terminal output must satisfy at the parent boundary. The
|
|
451
|
+
* loader validates well-formedness via `InvalidSubflowNodeError`;
|
|
452
|
+
* the kernel validates the sub-run's output at settle time.
|
|
453
|
+
*/
|
|
454
|
+
readonly outputSchema: LoopOutputSchema;
|
|
455
|
+
/**
|
|
456
|
+
* Convergence mode — closed enum, extensible additively. See
|
|
457
|
+
* `SubflowConvergence` for the semantics.
|
|
458
|
+
*/
|
|
459
|
+
readonly convergence: SubflowConvergence;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* A flow node — either a leaf (tool / agent), a loop (iterating a body
|
|
463
|
+
* sub-flow until an exit condition or array is exhausted), a fanout
|
|
464
|
+
* (running N different bodies in parallel with declared convergence),
|
|
465
|
+
* or a subgraph (invoking another registered flow as a full kernel
|
|
466
|
+
* sub-run). Discriminated by `kind`.
|
|
467
|
+
*
|
|
468
|
+
* Leaf nodes are the majority case. Loop nodes are used for iterative
|
|
469
|
+
* primitives (agent turn loops, retrieval refinement, batch ingest,
|
|
470
|
+
* retry-with-backoff). Fanout nodes are used for supervisor "invoke N
|
|
471
|
+
* sub-agents in parallel and reduce" patterns. Subgraph nodes are used
|
|
472
|
+
* for hierarchical orchestration — composing a large workflow out of
|
|
473
|
+
* smaller registered flows.
|
|
474
|
+
*/
|
|
475
|
+
export type FlowNode = LeafNode | LoopNode | FanoutNode | SubflowNode;
|
|
476
|
+
/**
|
|
477
|
+
* Backoff shape for `RetryPolicy`. Selects how the base `delayMs` grows
|
|
478
|
+
* across successive retry attempts.
|
|
479
|
+
*
|
|
480
|
+
* - `'fixed'` → same `delayMs` every retry.
|
|
481
|
+
* - `'linear'` → `delayMs * attempt` (capped by `maxDelayMs`).
|
|
482
|
+
* - `'exponential'` → `delayMs * 2^(attempt - 1)` (capped by `maxDelayMs`).
|
|
483
|
+
*
|
|
484
|
+
* `attempt` is 1-based: the first retry (after the initial failure) uses
|
|
485
|
+
* `attempt = 1`, the second retry `attempt = 2`, etc.
|
|
486
|
+
*/
|
|
487
|
+
export type BackoffShape = 'fixed' | 'linear' | 'exponential';
|
|
488
|
+
/**
|
|
489
|
+
* Declarative retry policy attached to a `FlowEdge` via `policy.retry`.
|
|
490
|
+
* When the destination node's handler fails and the destination has exactly
|
|
491
|
+
* one incoming edge carrying this policy, the kernel schedules successive
|
|
492
|
+
* attempts up to `maxAttempts` (inclusive of the initial call) before
|
|
493
|
+
* journaling `step.failed` terminally.
|
|
494
|
+
*
|
|
495
|
+
* Fan-in nodes (destinations with 2+ incoming edges) intentionally do NOT
|
|
496
|
+
* apply retry — there is no rule for which incoming edge's retry policy
|
|
497
|
+
* would win. A fan-in node with a retry policy on any
|
|
498
|
+
* incoming edge fails on first handler failure exactly as it would with no
|
|
499
|
+
* policy declared.
|
|
500
|
+
*/
|
|
501
|
+
export interface RetryPolicy {
|
|
502
|
+
/**
|
|
503
|
+
* Total handler attempts, including the first invocation. Bounded to
|
|
504
|
+
* `1 ≤ maxAttempts ≤ 10` by the loader. `1` is equivalent to omitting
|
|
505
|
+
* the policy (no retries). Values above 10 are a design smell — retry-
|
|
506
|
+
* heavy control flow belongs in a supervising flow, not on an edge.
|
|
507
|
+
*/
|
|
508
|
+
readonly maxAttempts: number;
|
|
509
|
+
/**
|
|
510
|
+
* Base delay between retries in milliseconds. Default `0`. Combined with
|
|
511
|
+
* `backoff` to produce the actual per-attempt delay. Must be non-negative.
|
|
512
|
+
*/
|
|
513
|
+
readonly delayMs?: number;
|
|
514
|
+
/**
|
|
515
|
+
* Backoff shape. Default `'fixed'`. `'exponential'` REQUIRES `maxDelayMs`
|
|
516
|
+
* so the delay cannot grow unbounded.
|
|
517
|
+
*/
|
|
518
|
+
readonly backoff?: BackoffShape;
|
|
519
|
+
/**
|
|
520
|
+
* Ceiling on the per-retry delay for `'linear'` / `'exponential'` backoff.
|
|
521
|
+
* Required when `backoff = 'exponential'`; ignored when `backoff = 'fixed'`.
|
|
522
|
+
* For `'linear'`, defaults to `60_000` ms if unset.
|
|
523
|
+
*/
|
|
524
|
+
readonly maxDelayMs?: number;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Per-edge runtime policy. Every field is optional. Four fields are
|
|
528
|
+
* honored by the kernel and are the complete set: `retry`,
|
|
529
|
+
* `timeoutMs`, `concurrencyKey`, and `priority`. There is no `suspend`
|
|
530
|
+
* field — declarative suspension uses `ctx.waitForToken` at handler
|
|
531
|
+
* level.
|
|
532
|
+
* Unknown keys are rejected at load time (`additionalProperties:
|
|
533
|
+
* false` on the JSON schema).
|
|
534
|
+
*/
|
|
535
|
+
export interface EdgePolicy {
|
|
536
|
+
/**
|
|
537
|
+
* Retry policy for the destination handler. When the sole incoming
|
|
538
|
+
* edge has this set, the kernel retries `step.failed` up to
|
|
539
|
+
* `maxAttempts` with the configured backoff. `retry` is bounded to
|
|
540
|
+
* `1 ≤ maxAttempts ≤ 10`.
|
|
541
|
+
*/
|
|
542
|
+
readonly retry?: RetryPolicy;
|
|
543
|
+
/**
|
|
544
|
+
* Per-edge handler-invocation timeout in milliseconds. When set on the
|
|
545
|
+
* SOLE incoming edge of a destination node, the kernel aborts the
|
|
546
|
+
* handler's `ctx.abortSignal` after `timeoutMs` and journals
|
|
547
|
+
* `step.failed` with `payload.reason: 'timeout'`, `limitMs`, and
|
|
548
|
+
* `elapsedMs`. Fan-in nodes (2+ incoming) ignore the timeout — same
|
|
549
|
+
* constraint as `retry`.
|
|
550
|
+
*
|
|
551
|
+
* Bounded to `1 ≤ timeoutMs ≤ 3_600_000` (1 hour). Larger values are
|
|
552
|
+
* almost always a design mistake: long-running work belongs behind
|
|
553
|
+
* `ctx.waitForToken` (durable) or in a `foreach` with `concurrency`
|
|
554
|
+
* (parallelization).
|
|
555
|
+
*
|
|
556
|
+
* Interaction with `retry`: a timed-out attempt counts as one failed
|
|
557
|
+
* attempt. If `retry.maxAttempts > 1`, the node retries the timed-out
|
|
558
|
+
* attempt on the standard schedule.
|
|
559
|
+
*
|
|
560
|
+
* Interaction with cancel: `cancelRun` and the timeout both abort the
|
|
561
|
+
* same handler. Whichever aborts first wins in the journal — cancel
|
|
562
|
+
* fires with `reason: 'cancelled'`, timeout with `reason: 'timeout'`.
|
|
563
|
+
*/
|
|
564
|
+
readonly timeoutMs?: number;
|
|
565
|
+
/**
|
|
566
|
+
* Per-key kernel semaphore. When set on the SOLE incoming edge of a
|
|
567
|
+
* destination node, the kernel serializes dispatch across every node
|
|
568
|
+
* (across every run, across every flow, within a single tenant) that
|
|
569
|
+
* shares the same key: at most one such node runs at a time.
|
|
570
|
+
*
|
|
571
|
+
* A node whose lease is unavailable at dispatch time is journaled as
|
|
572
|
+
* `step.concurrency-deferred` and re-considered on the next tick. When
|
|
573
|
+
* the current holder releases the lease (when that node completes,
|
|
574
|
+
* fails or is cancelled), a deferred waiter can acquire it and
|
|
575
|
+
* dispatch.
|
|
576
|
+
*
|
|
577
|
+
* Constraint: max length 256, printable ASCII only (Unicode >0x7E
|
|
578
|
+
* rejected — the key is a coordination handle, not a display label).
|
|
579
|
+
* Empty strings rejected.
|
|
580
|
+
*
|
|
581
|
+
* Fan-in nodes (2+ incoming) ignore `concurrencyKey` — same rule as
|
|
582
|
+
* retry + timeoutMs.
|
|
583
|
+
*
|
|
584
|
+
* Interaction with `retry`: retry-scheduled attempts RE-ACQUIRE the
|
|
585
|
+
* lease on each retry (they don't hold across the backoff delay).
|
|
586
|
+
*
|
|
587
|
+
* Interaction with `timeoutMs`: a timed-out handler releases its
|
|
588
|
+
* lease when the terminal `step.failed` lands.
|
|
589
|
+
*
|
|
590
|
+
* Interaction with cancellation: `cancelRun` releases the lease when
|
|
591
|
+
* the run's holding node journals its terminal event.
|
|
592
|
+
*
|
|
593
|
+
* Interaction with `waitForToken`: a suspending handler RELEASES the
|
|
594
|
+
* lease so peers can proceed. Resumed handler re-acquires the lease
|
|
595
|
+
* before continuing.
|
|
596
|
+
*
|
|
597
|
+
* Sweeper: on `resumeRun` boot, orphaned leases (holder run terminal)
|
|
598
|
+
* are cleaned before scheduling continues.
|
|
599
|
+
*/
|
|
600
|
+
readonly concurrencyKey?: string;
|
|
601
|
+
/**
|
|
602
|
+
* Declarative scheduler priority. When the ready set has more than one
|
|
603
|
+
* node, higher-priority nodes dispatch first; ties are broken by
|
|
604
|
+
* ascending lexical node id (deterministic + replay-safe). Applied to
|
|
605
|
+
* the destination node when it has EXACTLY ONE incoming edge — fan-in
|
|
606
|
+
* nodes (2+ incoming) inherit the default (`0`) regardless of any
|
|
607
|
+
* declared priority. Same fan-in rule as retry / timeoutMs /
|
|
608
|
+
* concurrencyKey.
|
|
609
|
+
*
|
|
610
|
+
* Integer, `-100 ≤ priority ≤ 100`. Default `0` (baseline). Positive
|
|
611
|
+
* = more urgent than baseline; negative = background. Integer-only:
|
|
612
|
+
* floats introduce IEEE-754 comparison ambiguity in a sort key that
|
|
613
|
+
* must round-trip identically across every replay. Bounded ±100
|
|
614
|
+
* because the primitive expresses tiers ("urgent" / "normal" /
|
|
615
|
+
* "background") plus finer-grained ordering within a tier — wider
|
|
616
|
+
* ranges add no expressive power.
|
|
617
|
+
*
|
|
618
|
+
* `priority` orders WHICH nodes dispatch first, not HOW MANY dispatch
|
|
619
|
+
* at once. `maxParallelism` still caps the concurrent dispatch count
|
|
620
|
+
* — priority just picks the top-N of the ready set.
|
|
621
|
+
*
|
|
622
|
+
* Interaction with `concurrencyKey`: when multiple nodes contend for
|
|
623
|
+
* the same lease within one executor tick, the priority sort applies
|
|
624
|
+
* before lease acquisition — the higher-priority node attempts (and
|
|
625
|
+
* wins) the lease first; the lower-priority sibling journals
|
|
626
|
+
* `step.concurrency-deferred` and re-attempts on the next tick.
|
|
627
|
+
* Priority does not order nodes that are already waiting on a lease
|
|
628
|
+
* across runs or processes.
|
|
629
|
+
*
|
|
630
|
+
* Interaction with loop bodies: iterations of a loop body dispatch
|
|
631
|
+
* per-iteration; each iteration reads the priority from the body
|
|
632
|
+
* node's edges independently.
|
|
633
|
+
*
|
|
634
|
+
* Determinism: because priority is declarative (from the flow) and
|
|
635
|
+
* tiebreak is lexical (from the node id), the same flow + same
|
|
636
|
+
* journal + same ready set produce the same dispatch order on every
|
|
637
|
+
* replay. No JournalKind is emitted — priority is a scheduling hint,
|
|
638
|
+
* not a durable state transition.
|
|
639
|
+
*/
|
|
640
|
+
readonly priority?: number;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Minimum + maximum values for `EdgePolicy.priority`. Kept as exported
|
|
644
|
+
* constants so downstream tooling (linters, UI editors, docs) can share
|
|
645
|
+
* the same bounds without duplicating literals.
|
|
646
|
+
*/
|
|
647
|
+
export declare const EDGE_POLICY_PRIORITY_MIN: -100;
|
|
648
|
+
export declare const EDGE_POLICY_PRIORITY_MAX: 100;
|
|
649
|
+
/** The `EdgePolicy.priority` default when the field is unset. */
|
|
650
|
+
export declare const EDGE_POLICY_PRIORITY_DEFAULT: 0;
|
|
651
|
+
/**
|
|
652
|
+
* A directed edge from one node (or the flow's virtual start) to another
|
|
653
|
+
* (or the flow's virtual end).
|
|
654
|
+
*
|
|
655
|
+
* Sentinel endpoints:
|
|
656
|
+
* - `$start` — the flow entry point. At least one edge must originate here.
|
|
657
|
+
* - `$end` — the flow exit. Zero or more edges may terminate here.
|
|
658
|
+
*/
|
|
659
|
+
export interface FlowEdge {
|
|
660
|
+
readonly id: EdgeId;
|
|
661
|
+
readonly from: NodeId | '$start';
|
|
662
|
+
readonly to: NodeId | '$end';
|
|
663
|
+
/**
|
|
664
|
+
* Optional predicate. When absent, the edge fires unconditionally as soon
|
|
665
|
+
* as its `from` node completes. When present, the edge fires iff the
|
|
666
|
+
* predicate evaluates true against the current run environment.
|
|
667
|
+
*/
|
|
668
|
+
readonly when?: Expr;
|
|
669
|
+
/**
|
|
670
|
+
* Optional per-edge runtime policy. Complete set of honored fields
|
|
671
|
+
* (each applies only when the destination has EXACTLY ONE incoming
|
|
672
|
+
* edge; fan-in destinations ignore per-edge policy): `retry`,
|
|
673
|
+
* `timeoutMs`, `concurrencyKey`, `priority`.
|
|
674
|
+
*/
|
|
675
|
+
readonly policy?: EdgePolicy;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* A directed edge inside a loop body. Same structure as `FlowEdge` but
|
|
679
|
+
* uses `$loop-start` / `$loop-end` sentinels instead of `$start` / `$end`.
|
|
680
|
+
* Enforcing distinct sentinels per scope makes journal queries unambiguous
|
|
681
|
+
* ("was this `$end` for the outer flow or a loop body?").
|
|
682
|
+
*/
|
|
683
|
+
export interface LoopEdge {
|
|
684
|
+
readonly id: EdgeId;
|
|
685
|
+
readonly from: NodeId | '$loop-start';
|
|
686
|
+
readonly to: NodeId | '$loop-end';
|
|
687
|
+
readonly when?: Expr;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* The sub-flow a loop node iterates. Must be a DAG (own cycle check runs
|
|
691
|
+
* at load time). Node ids must not collide with any other node in the
|
|
692
|
+
* enclosing flow or in sibling loop bodies at any nesting level.
|
|
693
|
+
*/
|
|
694
|
+
export interface LoopBody {
|
|
695
|
+
readonly nodes: readonly FlowNode[];
|
|
696
|
+
readonly edges: readonly LoopEdge[];
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* A durable, versioned task flow. Serializable JSON — the same flow
|
|
700
|
+
* definition can execute on any conforming runtime. Runs pin to a specific
|
|
701
|
+
* `version` so mid-flight executions survive flow edits.
|
|
702
|
+
*/
|
|
703
|
+
export interface Flow {
|
|
704
|
+
/**
|
|
705
|
+
* Globally-unique flow identifier. Convention:
|
|
706
|
+
* `<pack-id>.<flow-name>` (kebab-case, dot-namespaced) — e.g.
|
|
707
|
+
* `acme.brief-review-flow`. The framework's built-in agent-turn
|
|
708
|
+
* flow is `agent.turn` (`AGENT_TURN_FLOW_ID` in `@kindgi/agents`).
|
|
709
|
+
*/
|
|
710
|
+
readonly id: FlowId;
|
|
711
|
+
/**
|
|
712
|
+
* Semver — REQUIRED. Runs pin to a specific flow version at
|
|
713
|
+
* `runGraph` time so mid-flight executions survive later flow
|
|
714
|
+
* edits. `SubflowNode.flowRef.version` names one of these versions
|
|
715
|
+
* exactly.
|
|
716
|
+
*/
|
|
717
|
+
readonly version: string;
|
|
718
|
+
/** Human-readable name shown in UI + provenance. Doesn't affect execution. */
|
|
719
|
+
readonly name?: string;
|
|
720
|
+
/** Prose describing what the flow does. Surfaced in the flow catalog. */
|
|
721
|
+
readonly description?: string;
|
|
722
|
+
/**
|
|
723
|
+
* All nodes in the flow. Each node has a `kind` (`tool`, `agent`,
|
|
724
|
+
* `loop`, `fanout`, `subgraph`) and kind-specific fields.
|
|
725
|
+
* Order in the array doesn't matter — execution follows the edge
|
|
726
|
+
* DAG, not array order.
|
|
727
|
+
*/
|
|
728
|
+
readonly nodes: readonly FlowNode[];
|
|
729
|
+
/**
|
|
730
|
+
* Directed edges connecting node ids. Each edge carries an optional
|
|
731
|
+
* `policy` (`retry`, `timeoutMs`, `concurrencyKey`, `priority`). `$start` + `$end`
|
|
732
|
+
* sentinels are the outer flow's entry/exit.
|
|
733
|
+
*/
|
|
734
|
+
readonly edges: readonly FlowEdge[];
|
|
735
|
+
/**
|
|
736
|
+
* Flow-level default for concurrent node dispatch. Kernel uses this
|
|
737
|
+
* when `runGraph` is called without an explicit `options.maxParallelism`.
|
|
738
|
+
* Absent → `DEFAULT_MAX_PARALLELISM` (8) from `@kindgi/runtime`.
|
|
739
|
+
* Per-invocation overrides always win.
|
|
740
|
+
*/
|
|
741
|
+
readonly maxParallelism?: number;
|
|
742
|
+
/**
|
|
743
|
+
* Free-form key/value metadata that rides with the flow. Not
|
|
744
|
+
* interpreted by the framework — passthrough for pack-authored
|
|
745
|
+
* annotations (owner, ticket, tags).
|
|
746
|
+
*/
|
|
747
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
748
|
+
/**
|
|
749
|
+
* The run's declared output (schema-version 1.8.0+), resolved when the
|
|
750
|
+
* run completes. Absent → the output of the node feeding the run's
|
|
751
|
+
* `$end` edge (the lowest-id taken one when several fire).
|
|
752
|
+
*/
|
|
753
|
+
readonly output?: FlowOutputSpec;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* A flow's declared output: what the run returns, built from the
|
|
757
|
+
* finished run's environment rather than from whichever node happened to
|
|
758
|
+
* reach `$end` last.
|
|
759
|
+
*/
|
|
760
|
+
export interface FlowOutputSpec {
|
|
761
|
+
readonly mapping: Mapping;
|
|
762
|
+
/**
|
|
763
|
+
* Optional JSON Schema (draft 2020-12) the resolved output must
|
|
764
|
+
* satisfy. The loader checks it compiles; the kernel fails the run on
|
|
765
|
+
* a violation.
|
|
766
|
+
*/
|
|
767
|
+
readonly schema?: LoopOutputSchema;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Sentinel node ids used by flow edges. `$start` / `$end` are the outer
|
|
771
|
+
* flow's entry/exit. `$loop-start` / `$loop-end` are the body-scope
|
|
772
|
+
* entry/exit inside a loop node.
|
|
773
|
+
*
|
|
774
|
+
* These sentinels are never present in `flow.nodes` or `LoopBody.nodes` —
|
|
775
|
+
* they're virtual, and no handler executes at them.
|
|
776
|
+
*/
|
|
777
|
+
export declare const START_NODE: "$start";
|
|
778
|
+
export declare const END_NODE: "$end";
|
|
779
|
+
export declare const LOOP_START_NODE: "$loop-start";
|
|
780
|
+
export declare const LOOP_END_NODE: "$loop-end";
|
|
781
|
+
/** All sentinel ids. Useful for validators that need to reject them as node ids. */
|
|
782
|
+
export declare const SENTINEL_IDS: readonly ["$start", "$end", "$loop-start", "$loop-end"];
|
|
783
|
+
/** Type guard: is this node a loop node? */
|
|
784
|
+
export declare function isLoopNode(node: FlowNode): node is LoopNode;
|
|
785
|
+
/** Type guard: is this node a fanout node? */
|
|
786
|
+
export declare function isFanoutNode(node: FlowNode): node is FanoutNode;
|
|
787
|
+
/** Type guard: is this node a subgraph-invocation node? */
|
|
788
|
+
export declare function isSubflowNode(node: FlowNode): node is SubflowNode;
|
|
789
|
+
/** Type guard: is this node a leaf? */
|
|
790
|
+
export declare function isLeafNode(node: FlowNode): node is LeafNode;
|
|
791
|
+
export {};
|
|
792
|
+
//# sourceMappingURL=types.d.ts.map
|