@db-lyon/flowkit 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,375 +1,386 @@
1
- # @db-lyon/flowkit
2
-
3
- YAML-configured task and flow orchestration engine for Node.js.
4
-
5
- Define reusable **tasks** and compose them into **flows** using declarative YAML. Flowkit handles config layering, task resolution, sequential execution, nested flows, lifecycle hooks, and more.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @db-lyon/flowkit
11
- ```
12
-
13
- Requires Node.js >= 20.
14
-
15
- ## Quick start
16
-
17
- **1. Define your config** (`pipeline.yml`):
18
-
19
- ```yaml
20
- tasks:
21
- build:
22
- class_path: tasks.Build
23
- description: Compile the project
24
- options:
25
- target: production
26
-
27
- test:
28
- class_path: tasks.Test
29
- description: Run the test suite
30
-
31
- deploy:
32
- class_path: tasks.Deploy
33
- description: Deploy artifacts
34
-
35
- flows:
36
- ci:
37
- description: Build, test, deploy
38
- steps:
39
- 1:
40
- task: build
41
- 2:
42
- task: test
43
- 3:
44
- task: deploy
45
- options:
46
- environment: staging
47
- ```
48
-
49
- **2. Create a task** (`tasks/Build.ts`):
50
-
51
- ```typescript
52
- import { BaseTask, type TaskResult } from '@db-lyon/flowkit';
53
-
54
- interface BuildOptions {
55
- target: string;
56
- }
57
-
58
- export default class Build extends BaseTask<BuildOptions> {
59
- get taskName() { return 'build'; }
60
-
61
- protected validate() {
62
- if (!this.options.target) throw new Error('target is required');
63
- }
64
-
65
- async execute(): Promise<TaskResult> {
66
- this.logger.info(`Building for ${this.options.target}`);
67
- // ... do work ...
68
- return { success: true, data: { target: this.options.target } };
69
- }
70
- }
71
- ```
72
-
73
- **3. Run it**:
74
-
75
- ```typescript
76
- import {
77
- loadConfig,
78
- EngineConfigSchema,
79
- TaskRegistry,
80
- FlowRunner,
81
- } from '@db-lyon/flowkit';
82
-
83
- const { config } = loadConfig({
84
- filename: 'pipeline.yml',
85
- schema: EngineConfigSchema,
86
- configDir: './config',
87
- });
88
-
89
- const registry = new TaskRegistry();
90
- // Tasks with class_path like "tasks.Build" are resolved dynamically
91
- // from the filesystem (tasks/Build.ts), or register them explicitly:
92
- // registry.register('build', Build);
93
-
94
- const runner = new FlowRunner({
95
- tasks: config.tasks,
96
- flows: config.flows,
97
- registry,
98
- context: { logger: console },
99
- });
100
-
101
- const result = await runner.run({ flowName: 'ci' });
102
- console.log(result.success); // true
103
- ```
104
-
105
- ## Features
106
-
107
- ### YAML-driven configuration
108
-
109
- Define tasks and flows in YAML. Each task references a `class_path` (resolved to a file on disk or a registered constructor) and can carry default `options`. Flows are ordered sequences of steps that reference tasks or other flows.
110
-
111
- ### Config layering
112
-
113
- The config loader merges multiple YAML files in order:
114
-
115
- ```
116
- defaults (code) → pipeline.yml → pipeline.staging.yml → pipeline.local.yml
117
- ```
118
-
119
- ```typescript
120
- const { config } = loadConfig({
121
- filename: 'pipeline.yml',
122
- schema: EngineConfigSchema,
123
- env: 'staging', // loads pipeline.staging.yml overlay
124
- configDir: './config',
125
- });
126
- ```
127
-
128
- Environment overlays and `.local.yml` files let you customize per-environment or per-developer without touching the base config. See [docs/configuration.md](docs/configuration.md).
129
-
130
- ### Custom tasks
131
-
132
- Extend `BaseTask` to create your own tasks. The lifecycle is: `validate()` → `execute()` → result with timing. Exceptions are caught and returned as `{ success: false }` automatically.
133
-
134
- ```typescript
135
- class MyTask extends BaseTask<MyOptions> {
136
- get taskName() { return 'my_task'; }
137
- async execute(): Promise<TaskResult> {
138
- return { success: true };
139
- }
140
- }
141
- ```
142
-
143
- See [docs/custom-tasks.md](docs/custom-tasks.md).
144
-
145
- ### Built-in ShellTask
146
-
147
- Run shell commands without writing a custom task class:
148
-
149
- ```yaml
150
- tasks:
151
- lint:
152
- class_path: shell
153
- options:
154
- command: npm run lint
155
- cwd: /path/to/project
156
- timeout: 60000
157
- ```
158
-
159
- Register it in your registry:
160
-
161
- ```typescript
162
- import { ShellTask } from '@db-lyon/flowkit';
163
- registry.register('shell', ShellTask as any);
164
- ```
165
-
166
- ### AI agents
167
-
168
- Run LLM calls as steps. Flowkit has **no SDK dependencies** — you wire a
169
- model-agnostic `LLMProvider` onto the task context as `ctx.llm`, and two tasks
170
- consume it:
171
-
172
- - `agent_prompt` (`AgentPromptTask`) — single-shot prompt, with optional
173
- JSON-Schema structured output (validated, with a repair re-prompt).
174
- - `agent` (`AgentTask`) — a tool-calling loop. Tools reference flowkit tasks,
175
- flows, or other agents (or context handlers), gated by an allowlist and
176
- per-tool argument validation. Multiple tool calls in a turn (including
177
- parallel sub-agents) run concurrently under a cap.
178
-
179
- Reusable agents live under an `agents:` root key and run as flow steps or as
180
- other agents' tools, with mandatory budgets (`maxIterations`, `tokenBudget`,
181
- `maxAgentDepth`). Iteration and concurrency live in the agent runtime, so a flow
182
- stays a sequential spine with no `loop:` or parallel-step primitive.
183
-
184
- ```yaml
185
- tasks:
186
- extract:
187
- class_path: agent_prompt
188
- options:
189
- prompt: "Pull the ticket fields from:\n${steps.1.text}"
190
- schema:
191
- type: object
192
- required: [title, priority]
193
- properties:
194
- title: { type: string }
195
- priority: { type: string, enum: [low, medium, high] }
196
- ```
197
-
198
- Every call is hardened by a shared core: per-call `timeout` (with provider
199
- abort), `retries` with exponential backoff, structured-output validation +
200
- repair, and output-size caps. See [docs/ai-agents.md](docs/ai-agents.md).
201
-
202
- ### Nested flows
203
-
204
- A step can reference another flow instead of a task:
205
-
206
- ```yaml
207
- flows:
208
- ci:
209
- description: CI pipeline
210
- steps:
211
- 1: { task: build }
212
- 2: { task: test }
213
-
214
- release:
215
- description: Full release
216
- steps:
217
- 1: { flow: ci }
218
- 2: { task: deploy }
219
- ```
220
-
221
- A flow step's `options` override the options of tasks **inside** the nested
222
- flow, keyed by task name. Precedence, low → high: task default → enclosing-flow
223
- override → step's own inline options → runtime `params`.
224
-
225
- ```yaml
226
- release:
227
- steps:
228
- 1:
229
- flow: ci
230
- options:
231
- test: { coverage: 90 } # overrides the `test` task's options inside `ci`
232
- 2: { task: deploy }
233
- ```
234
-
235
- ### Conditional steps (`when`)
236
-
237
- A step runs only when its `when` is truthy. It accepts a boolean, or a string
238
- expression evaluated at run time. With no `conditionEvaluator` configured, a
239
- string is resolved for `${...}` references and tested for truthiness; supply a
240
- `conditionEvaluator` to plug in a real expression language. A falsy result skips
241
- the step (`skipReason: 'when'`) without failing the flow.
242
-
243
- ```yaml
244
- steps:
245
- 1: { task: build }
246
- 2: { task: deploy, when: '${steps.1.shouldDeploy}' }
247
- 3: { task: notify, when: false }
248
- ```
249
-
250
- ```typescript
251
- new FlowRunner({
252
- // ...
253
- conditionEvaluator: (expr, ctx) => evalMyDsl(expr, ctx), // ctx: { steps, params, context, error }
254
- });
255
- ```
256
-
257
- ### Continue on failure (`ignore_failure`)
258
-
259
- By default any failed step aborts the flow. Mark a step `ignore_failure: true`
260
- to record the failure but keep going (the step result has `ignoredFailure: true`).
261
-
262
- ```yaml
263
- steps:
264
- 1: { task: deploy }
265
- 2: { task: publish_release_notes, ignore_failure: true }
266
- 3: { task: announce }
267
- ```
268
-
269
- ### Skip steps
270
-
271
- Skip by task name or step number:
272
-
273
- ```typescript
274
- await runner.run({ flowName: 'release', skip: ['deploy'] });
275
- await runner.run({ flowName: 'release', skip: ['2'] });
276
- ```
277
-
278
- Or mark a step as permanently skipped in YAML:
279
-
280
- ```yaml
281
- steps:
282
- 3:
283
- task: None
284
- ```
285
-
286
- ### Plan mode
287
-
288
- Preview the execution plan without running anything:
289
-
290
- ```typescript
291
- const result = await runner.run({ flowName: 'ci', plan: true });
292
- result.steps.forEach(s =>
293
- console.log(`${s.stepNumber}: [${s.type}] ${s.name}${s.skipped ? ' (skip)' : ''}`)
294
- );
295
- ```
296
-
297
- Pass `expandNestedFlows: true` to recursively expand nested-flow steps into
298
- their child steps, each annotated with a hierarchical `path` (e.g. `2/1`):
299
-
300
- ```typescript
301
- await runner.run({ flowName: 'release', plan: true, expandNestedFlows: true });
302
- ```
303
-
304
- ### Lifecycle hooks
305
-
306
- Attach hooks to observe or react to flow execution:
307
-
308
- ```typescript
309
- const runner = new FlowRunner({
310
- // ...
311
- hooks: {
312
- beforeRun: async (flowName, plan) => { /* ... */ },
313
- beforeStep: async (step) => { /* ... */ },
314
- afterStep: async (step, result) => { /* ... */ },
315
- onStepError: async (step, error, completed) => { /* ... */ },
316
- afterRun: async (result) => { /* ... */ },
317
- },
318
- });
319
- ```
320
-
321
- `beforeRun`/`afterRun` fire once for the top-level flow. `beforeStep`/`afterStep` fire for every step including those inside nested flows.
322
-
323
- ### DAG utilities
324
-
325
- Topological sort with cycle and missing-dependency detection:
326
-
327
- ```typescript
328
- import { topologicalSort } from '@db-lyon/flowkit';
329
-
330
- const sorted = topologicalSort([
331
- { id: 'a', dependencies: [], data: null },
332
- { id: 'b', dependencies: ['a'], data: null },
333
- { id: 'c', dependencies: ['a', 'b'], data: null },
334
- ]);
335
- // sorted: [a, b, c]
336
- ```
337
-
338
- Throws `CircularDependencyError` or `MissingDependencyError` on invalid graphs.
339
-
340
- ### Logger interface
341
-
342
- Flowkit accepts any logger that implements the `Logger` interface (compatible with pino, winston, etc.):
343
-
344
- ```typescript
345
- interface Logger {
346
- debug(...args: unknown[]): void;
347
- info(...args: unknown[]): void;
348
- warn(...args: unknown[]): void;
349
- error(...args: unknown[]): void;
350
- child(bindings: Record<string, unknown>): Logger;
351
- }
352
- ```
353
-
354
- Pass it via the task context or flow runner config. A `noopLogger` is used by default.
355
-
356
- ## Sub-path exports
357
-
358
- ```typescript
359
- import { loadConfig } from '@db-lyon/flowkit/config';
360
- import { BaseTask, TaskRegistry } from '@db-lyon/flowkit/task';
361
- import { FlowRunner } from '@db-lyon/flowkit/flow';
362
- import { topologicalSort } from '@db-lyon/flowkit/dag';
363
- ```
364
-
365
- ## Docs
366
-
367
- - [Getting started](docs/getting-started.md) — step-by-step setup guide
368
- - [Custom tasks](docs/custom-tasks.md) — writing and registering tasks
369
- - [AI agents](docs/ai-agents.md) — LLM prompts, structured output, tool-calling agents
370
- - [Configuration](docs/configuration.md) — YAML schema, layering, deep merge
371
- - [API reference](docs/api-reference.md) — full type and function reference
372
-
373
- ## License
374
-
375
- MIT — see [LICENSE](LICENSE).
1
+ # @db-lyon/flowkit
2
+
3
+ YAML-configured task and flow orchestration engine for Node.js.
4
+
5
+ Define reusable **tasks** and compose them into **flows** using declarative YAML. Flowkit handles config layering, task resolution, sequential execution, nested flows, lifecycle hooks, and more.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @db-lyon/flowkit
11
+ ```
12
+
13
+ Requires Node.js >= 20.
14
+
15
+ ## Quick start
16
+
17
+ **1. Define your config** (`pipeline.yml`):
18
+
19
+ ```yaml
20
+ tasks:
21
+ build:
22
+ class_path: tasks.Build
23
+ description: Compile the project
24
+ options:
25
+ target: production
26
+
27
+ test:
28
+ class_path: tasks.Test
29
+ description: Run the test suite
30
+
31
+ deploy:
32
+ class_path: tasks.Deploy
33
+ description: Deploy artifacts
34
+
35
+ flows:
36
+ ci:
37
+ description: Build, test, deploy
38
+ steps:
39
+ 1:
40
+ task: build
41
+ 2:
42
+ task: test
43
+ 3:
44
+ task: deploy
45
+ options:
46
+ environment: staging
47
+ ```
48
+
49
+ **2. Create a task** (`tasks/Build.ts`):
50
+
51
+ ```typescript
52
+ import { BaseTask, type TaskResult } from '@db-lyon/flowkit';
53
+
54
+ interface BuildOptions {
55
+ target: string;
56
+ }
57
+
58
+ export default class Build extends BaseTask<BuildOptions> {
59
+ get taskName() { return 'build'; }
60
+
61
+ protected validate() {
62
+ if (!this.options.target) throw new Error('target is required');
63
+ }
64
+
65
+ async execute(): Promise<TaskResult> {
66
+ this.logger.info(`Building for ${this.options.target}`);
67
+ // ... do work ...
68
+ return { success: true, data: { target: this.options.target } };
69
+ }
70
+ }
71
+ ```
72
+
73
+ **3. Run it**:
74
+
75
+ ```typescript
76
+ import {
77
+ loadConfig,
78
+ EngineConfigSchema,
79
+ TaskRegistry,
80
+ FlowRunner,
81
+ } from '@db-lyon/flowkit';
82
+
83
+ const { config } = loadConfig({
84
+ filename: 'pipeline.yml',
85
+ schema: EngineConfigSchema,
86
+ configDir: './config',
87
+ });
88
+
89
+ const registry = new TaskRegistry();
90
+ // Tasks with class_path like "tasks.Build" are resolved dynamically
91
+ // from the filesystem (tasks/Build.ts), or register them explicitly:
92
+ // registry.register('build', Build);
93
+
94
+ const runner = new FlowRunner({
95
+ tasks: config.tasks,
96
+ flows: config.flows,
97
+ registry,
98
+ context: { logger: console },
99
+ });
100
+
101
+ const result = await runner.run({ flowName: 'ci' });
102
+ console.log(result.success); // true
103
+ ```
104
+
105
+ ## Features
106
+
107
+ ### YAML-driven configuration
108
+
109
+ Define tasks and flows in YAML. Each task references a `class_path` (resolved to a file on disk or a registered constructor) and can carry default `options`. Flows are ordered sequences of steps that reference tasks or other flows.
110
+
111
+ ### Config layering
112
+
113
+ The config loader merges multiple YAML files in order:
114
+
115
+ ```
116
+ defaults (code) → pipeline.yml → pipeline.staging.yml → pipeline.local.yml
117
+ ```
118
+
119
+ ```typescript
120
+ const { config } = loadConfig({
121
+ filename: 'pipeline.yml',
122
+ schema: EngineConfigSchema,
123
+ env: 'staging', // loads pipeline.staging.yml overlay
124
+ configDir: './config',
125
+ });
126
+ ```
127
+
128
+ Environment overlays and `.local.yml` files let you customize per-environment or per-developer without touching the base config. See [docs/configuration.md](docs/configuration.md).
129
+
130
+ ### Custom tasks
131
+
132
+ Extend `BaseTask` to create your own tasks. The lifecycle is: `validate()` → `execute()` → result with timing. Exceptions are caught and returned as `{ success: false }` automatically.
133
+
134
+ ```typescript
135
+ class MyTask extends BaseTask<MyOptions> {
136
+ get taskName() { return 'my_task'; }
137
+ async execute(): Promise<TaskResult> {
138
+ return { success: true };
139
+ }
140
+ }
141
+ ```
142
+
143
+ See [docs/custom-tasks.md](docs/custom-tasks.md).
144
+
145
+ ### Built-in ShellTask
146
+
147
+ Run shell commands without writing a custom task class:
148
+
149
+ ```yaml
150
+ tasks:
151
+ lint:
152
+ class_path: shell
153
+ options:
154
+ command: npm run lint
155
+ cwd: /path/to/project
156
+ timeout: 60000
157
+ ```
158
+
159
+ Register it in your registry:
160
+
161
+ ```typescript
162
+ import { ShellTask } from '@db-lyon/flowkit';
163
+ registry.register('shell', ShellTask as any);
164
+ ```
165
+
166
+ Existing YAML consumers need no change. To cancel one programmatic invocation,
167
+ pass an invocation-specific `AbortSignal`:
168
+
169
+ ```typescript
170
+ const controller = new AbortController();
171
+ const result = await new ShellTask({}, {
172
+ command: 'npm run build',
173
+ signal: controller.signal,
174
+ }).run();
175
+ ```
176
+
177
+ ### AI agents
178
+
179
+ Run LLM calls as steps. Flowkit has **no SDK dependencies** — you wire a
180
+ model-agnostic `LLMProvider` onto the task context as `ctx.llm`, and two tasks
181
+ consume it:
182
+
183
+ - `agent_prompt` (`AgentPromptTask`) — single-shot prompt, with optional
184
+ JSON-Schema structured output (validated, with a repair re-prompt).
185
+ - `agent` (`AgentTask`) — a tool-calling loop. Tools reference flowkit tasks,
186
+ flows, or other agents (or context handlers), gated by an allowlist and
187
+ per-tool argument validation. Multiple tool calls in a turn (including
188
+ parallel sub-agents) run concurrently under a cap.
189
+
190
+ Reusable agents live under an `agents:` root key and run as flow steps or as
191
+ other agents' tools, with mandatory budgets (`maxIterations`, `tokenBudget`,
192
+ `maxAgentDepth`). Iteration and concurrency live in the agent runtime, so a flow
193
+ stays a sequential spine with no `loop:` or parallel-step primitive.
194
+
195
+ ```yaml
196
+ tasks:
197
+ extract:
198
+ class_path: agent_prompt
199
+ options:
200
+ prompt: "Pull the ticket fields from:\n${steps.1.text}"
201
+ schema:
202
+ type: object
203
+ required: [title, priority]
204
+ properties:
205
+ title: { type: string }
206
+ priority: { type: string, enum: [low, medium, high] }
207
+ ```
208
+
209
+ Every call is hardened by a shared core: per-call `timeout` (with provider
210
+ abort), `retries` with exponential backoff, structured-output validation +
211
+ repair, and output-size caps. See [docs/ai-agents.md](docs/ai-agents.md).
212
+
213
+ ### Nested flows
214
+
215
+ A step can reference another flow instead of a task:
216
+
217
+ ```yaml
218
+ flows:
219
+ ci:
220
+ description: CI pipeline
221
+ steps:
222
+ 1: { task: build }
223
+ 2: { task: test }
224
+
225
+ release:
226
+ description: Full release
227
+ steps:
228
+ 1: { flow: ci }
229
+ 2: { task: deploy }
230
+ ```
231
+
232
+ A flow step's `options` override the options of tasks **inside** the nested
233
+ flow, keyed by task name. Precedence, low → high: task default → enclosing-flow
234
+ override → step's own inline options → runtime `params`.
235
+
236
+ ```yaml
237
+ release:
238
+ steps:
239
+ 1:
240
+ flow: ci
241
+ options:
242
+ test: { coverage: 90 } # overrides the `test` task's options inside `ci`
243
+ 2: { task: deploy }
244
+ ```
245
+
246
+ ### Conditional steps (`when`)
247
+
248
+ A step runs only when its `when` is truthy. It accepts a boolean, or a string
249
+ expression evaluated at run time. With no `conditionEvaluator` configured, a
250
+ string is resolved for `${...}` references and tested for truthiness; supply a
251
+ `conditionEvaluator` to plug in a real expression language. A falsy result skips
252
+ the step (`skipReason: 'when'`) without failing the flow.
253
+
254
+ ```yaml
255
+ steps:
256
+ 1: { task: build }
257
+ 2: { task: deploy, when: '${steps.1.shouldDeploy}' }
258
+ 3: { task: notify, when: false }
259
+ ```
260
+
261
+ ```typescript
262
+ new FlowRunner({
263
+ // ...
264
+ conditionEvaluator: (expr, ctx) => evalMyDsl(expr, ctx), // ctx: { steps, params, context, error }
265
+ });
266
+ ```
267
+
268
+ ### Continue on failure (`ignore_failure`)
269
+
270
+ By default any failed step aborts the flow. Mark a step `ignore_failure: true`
271
+ to record the failure but keep going (the step result has `ignoredFailure: true`).
272
+
273
+ ```yaml
274
+ steps:
275
+ 1: { task: deploy }
276
+ 2: { task: publish_release_notes, ignore_failure: true }
277
+ 3: { task: announce }
278
+ ```
279
+
280
+ ### Skip steps
281
+
282
+ Skip by task name or step number:
283
+
284
+ ```typescript
285
+ await runner.run({ flowName: 'release', skip: ['deploy'] });
286
+ await runner.run({ flowName: 'release', skip: ['2'] });
287
+ ```
288
+
289
+ Or mark a step as permanently skipped in YAML:
290
+
291
+ ```yaml
292
+ steps:
293
+ 3:
294
+ task: None
295
+ ```
296
+
297
+ ### Plan mode
298
+
299
+ Preview the execution plan without running anything:
300
+
301
+ ```typescript
302
+ const result = await runner.run({ flowName: 'ci', plan: true });
303
+ result.steps.forEach(s =>
304
+ console.log(`${s.stepNumber}: [${s.type}] ${s.name}${s.skipped ? ' (skip)' : ''}`)
305
+ );
306
+ ```
307
+
308
+ Pass `expandNestedFlows: true` to recursively expand nested-flow steps into
309
+ their child steps, each annotated with a hierarchical `path` (e.g. `2/1`):
310
+
311
+ ```typescript
312
+ await runner.run({ flowName: 'release', plan: true, expandNestedFlows: true });
313
+ ```
314
+
315
+ ### Lifecycle hooks
316
+
317
+ Attach hooks to observe or react to flow execution:
318
+
319
+ ```typescript
320
+ const runner = new FlowRunner({
321
+ // ...
322
+ hooks: {
323
+ beforeRun: async (flowName, plan) => { /* ... */ },
324
+ beforeStep: async (step) => { /* ... */ },
325
+ afterStep: async (step, result) => { /* ... */ },
326
+ onStepError: async (step, error, completed) => { /* ... */ },
327
+ afterRun: async (result) => { /* ... */ },
328
+ },
329
+ });
330
+ ```
331
+
332
+ `beforeRun`/`afterRun` fire once for the top-level flow. `beforeStep`/`afterStep` fire for every step including those inside nested flows.
333
+
334
+ ### DAG utilities
335
+
336
+ Topological sort with cycle and missing-dependency detection:
337
+
338
+ ```typescript
339
+ import { topologicalSort } from '@db-lyon/flowkit';
340
+
341
+ const sorted = topologicalSort([
342
+ { id: 'a', dependencies: [], data: null },
343
+ { id: 'b', dependencies: ['a'], data: null },
344
+ { id: 'c', dependencies: ['a', 'b'], data: null },
345
+ ]);
346
+ // sorted: [a, b, c]
347
+ ```
348
+
349
+ Throws `CircularDependencyError` or `MissingDependencyError` on invalid graphs.
350
+
351
+ ### Logger interface
352
+
353
+ Flowkit accepts any logger that implements the `Logger` interface (compatible with pino, winston, etc.):
354
+
355
+ ```typescript
356
+ interface Logger {
357
+ debug(...args: unknown[]): void;
358
+ info(...args: unknown[]): void;
359
+ warn(...args: unknown[]): void;
360
+ error(...args: unknown[]): void;
361
+ child(bindings: Record<string, unknown>): Logger;
362
+ }
363
+ ```
364
+
365
+ Pass it via the task context or flow runner config. A `noopLogger` is used by default.
366
+
367
+ ## Sub-path exports
368
+
369
+ ```typescript
370
+ import { loadConfig } from '@db-lyon/flowkit/config';
371
+ import { BaseTask, TaskRegistry } from '@db-lyon/flowkit/task';
372
+ import { FlowRunner } from '@db-lyon/flowkit/flow';
373
+ import { topologicalSort } from '@db-lyon/flowkit/dag';
374
+ ```
375
+
376
+ ## Docs
377
+
378
+ - [Getting started](docs/getting-started.md) — step-by-step setup guide
379
+ - [Custom tasks](docs/custom-tasks.md) — writing and registering tasks
380
+ - [AI agents](docs/ai-agents.md) — LLM prompts, structured output, tool-calling agents
381
+ - [Configuration](docs/configuration.md) — YAML schema, layering, deep merge
382
+ - [API reference](docs/api-reference.md) — full type and function reference
383
+
384
+ ## License
385
+
386
+ MIT — see [LICENSE](LICENSE).