@cogitator-ai/workflows 0.2.0 → 0.2.2
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 +850 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @cogitator-ai/workflows
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@cogitator-ai/workflows)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
DAG-based workflow engine for Cogitator agents. Build complex multi-step workflows with branching, loops, checkpoints, human-in-the-loop, timers, and more.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
@@ -8,46 +11,176 @@ DAG-based workflow engine for Cogitator agents. Build complex multi-step workflo
|
|
|
8
11
|
pnpm add @cogitator-ai/workflows
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
##
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **DAG Builder** — Type-safe workflow construction with nodes, conditionals, loops
|
|
17
|
+
- **Checkpoints** — Save and resume workflow state
|
|
18
|
+
- **Pre-built Nodes** — Agent, tool, and function nodes
|
|
19
|
+
- **Timer System** — Delays, cron schedules, wait-until patterns
|
|
20
|
+
- **Saga Patterns** — Retries, circuit breakers, compensation, DLQ
|
|
21
|
+
- **Subworkflows** — Nested, parallel, fan-out/fan-in patterns
|
|
22
|
+
- **Human-in-the-Loop** — Approvals, choices, inputs, rating
|
|
23
|
+
- **Map-Reduce** — Parallel processing with aggregation
|
|
24
|
+
- **Triggers** — Cron, webhook, and event triggers
|
|
25
|
+
- **Observability** — Tracing and metrics with multiple exporters
|
|
12
26
|
|
|
13
|
-
|
|
27
|
+
## Quick Start
|
|
14
28
|
|
|
15
29
|
```typescript
|
|
16
|
-
import { WorkflowBuilder, WorkflowExecutor } from '@cogitator-ai/workflows';
|
|
30
|
+
import { WorkflowBuilder, WorkflowExecutor, agentNode } from '@cogitator-ai/workflows';
|
|
31
|
+
import { Cogitator, Agent } from '@cogitator-ai/core';
|
|
32
|
+
|
|
33
|
+
const cogitator = new Cogitator({
|
|
34
|
+
/* config */
|
|
35
|
+
});
|
|
36
|
+
const analyst = new Agent({ name: 'analyst', model: 'openai/gpt-4o', instructions: '...' });
|
|
37
|
+
|
|
38
|
+
const workflow = new WorkflowBuilder('data-pipeline')
|
|
39
|
+
.addNode('analyze', agentNode(analyst))
|
|
40
|
+
.addNode('report', async (ctx) => ({ output: `Report: ${ctx.state.analysis}` }))
|
|
41
|
+
.build();
|
|
42
|
+
|
|
43
|
+
const executor = new WorkflowExecutor(cogitator);
|
|
44
|
+
const result = await executor.execute(workflow, { input: 'Analyze this data...' });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
17
48
|
|
|
18
|
-
|
|
49
|
+
## Table of Contents
|
|
50
|
+
|
|
51
|
+
- [Core Concepts](#core-concepts)
|
|
52
|
+
- [Pre-built Nodes](#pre-built-nodes)
|
|
53
|
+
- [Conditional Branching](#conditional-branching)
|
|
54
|
+
- [Loops](#loops)
|
|
55
|
+
- [Checkpoints](#checkpoints)
|
|
56
|
+
- [Timer System](#timer-system)
|
|
57
|
+
- [Saga Patterns](#saga-patterns)
|
|
58
|
+
- [Subworkflows](#subworkflows)
|
|
59
|
+
- [Human-in-the-Loop](#human-in-the-loop)
|
|
60
|
+
- [Map-Reduce Patterns](#map-reduce-patterns)
|
|
61
|
+
- [Triggers](#triggers)
|
|
62
|
+
- [Observability](#observability)
|
|
63
|
+
- [Workflow Management](#workflow-management)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Core Concepts
|
|
68
|
+
|
|
69
|
+
### WorkflowBuilder
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { WorkflowBuilder } from '@cogitator-ai/workflows';
|
|
73
|
+
|
|
74
|
+
const workflow = new WorkflowBuilder<MyState>('my-workflow')
|
|
19
75
|
.initialState({ count: 0 })
|
|
20
|
-
.addNode('
|
|
21
|
-
state: { count: ctx.state.count + 1 },
|
|
22
|
-
}))
|
|
23
|
-
.addNode('done', async (ctx) => ({
|
|
24
|
-
output: `Final count: ${ctx.state.count}`,
|
|
76
|
+
.addNode('step1', async (ctx) => ({
|
|
77
|
+
state: { ...ctx.state, count: ctx.state.count + 1 },
|
|
25
78
|
}))
|
|
79
|
+
.addNode(
|
|
80
|
+
'step2',
|
|
81
|
+
async (ctx) => ({
|
|
82
|
+
output: `Count: ${ctx.state.count}`,
|
|
83
|
+
}),
|
|
84
|
+
{ after: ['step1'] }
|
|
85
|
+
)
|
|
26
86
|
.build();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### WorkflowExecutor
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { WorkflowExecutor } from '@cogitator-ai/workflows';
|
|
27
93
|
|
|
28
94
|
const executor = new WorkflowExecutor(cogitator);
|
|
29
|
-
const result = await executor.execute(workflow
|
|
95
|
+
const result = await executor.execute(workflow, {
|
|
96
|
+
input: 'Start the workflow',
|
|
97
|
+
context: { userId: '123' },
|
|
98
|
+
timeout: 60000,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(result.output);
|
|
102
|
+
console.log(result.state);
|
|
103
|
+
console.log(result.events);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Pre-built Nodes
|
|
109
|
+
|
|
110
|
+
### agentNode
|
|
111
|
+
|
|
112
|
+
Run an agent as a workflow node:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { agentNode } from '@cogitator-ai/workflows';
|
|
116
|
+
|
|
117
|
+
const workflow = new WorkflowBuilder('agent-flow')
|
|
118
|
+
.addNode(
|
|
119
|
+
'research',
|
|
120
|
+
agentNode(researchAgent, {
|
|
121
|
+
promptKey: 'researchPrompt', // State key for input
|
|
122
|
+
outputKey: 'researchResult', // State key for output
|
|
123
|
+
timeout: 30000,
|
|
124
|
+
onToolCall: (call) => console.log('Tool:', call.name),
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
.build();
|
|
30
128
|
```
|
|
31
129
|
|
|
32
|
-
###
|
|
130
|
+
### toolNode
|
|
131
|
+
|
|
132
|
+
Execute a tool directly:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { toolNode } from '@cogitator-ai/workflows';
|
|
136
|
+
|
|
137
|
+
const workflow = new WorkflowBuilder('tool-flow')
|
|
138
|
+
.addNode('calculate', toolNode('calculator', { expression: '2 + 2' }))
|
|
139
|
+
.build();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### functionNode
|
|
143
|
+
|
|
144
|
+
Custom function as a node:
|
|
33
145
|
|
|
34
146
|
```typescript
|
|
35
|
-
|
|
147
|
+
import { functionNode } from '@cogitator-ai/workflows';
|
|
148
|
+
|
|
149
|
+
const workflow = new WorkflowBuilder('func-flow')
|
|
150
|
+
.addNode(
|
|
151
|
+
'transform',
|
|
152
|
+
functionNode(async (ctx) => {
|
|
153
|
+
const transformed = processData(ctx.state.data);
|
|
154
|
+
return { state: { ...ctx.state, transformed } };
|
|
155
|
+
})
|
|
156
|
+
)
|
|
157
|
+
.build();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Conditional Branching
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
const workflow = new WorkflowBuilder('approval-flow')
|
|
36
166
|
.addNode('review', reviewNode)
|
|
37
167
|
.addConditional('check', (state) => state.approved, {
|
|
38
168
|
after: ['review'],
|
|
39
169
|
})
|
|
40
170
|
.addNode('approve', approveNode, { after: ['check:true'] })
|
|
41
171
|
.addNode('reject', rejectNode, { after: ['check:false'] })
|
|
172
|
+
.addNode('notify', notifyNode, { after: ['approve', 'reject'] })
|
|
42
173
|
.build();
|
|
43
174
|
```
|
|
44
175
|
|
|
45
|
-
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Loops
|
|
46
179
|
|
|
47
180
|
```typescript
|
|
48
|
-
const workflow = new WorkflowBuilder('retry')
|
|
181
|
+
const workflow = new WorkflowBuilder('retry-flow')
|
|
49
182
|
.addNode('attempt', attemptNode)
|
|
50
|
-
.addLoop('check', {
|
|
183
|
+
.addLoop('retry-check', {
|
|
51
184
|
condition: (state) => !state.success && state.attempts < 3,
|
|
52
185
|
back: 'attempt',
|
|
53
186
|
exit: 'done',
|
|
@@ -57,43 +190,730 @@ const workflow = new WorkflowBuilder('retry')
|
|
|
57
190
|
.build();
|
|
58
191
|
```
|
|
59
192
|
|
|
60
|
-
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Checkpoints
|
|
61
196
|
|
|
62
|
-
|
|
197
|
+
Save and resume workflow execution:
|
|
63
198
|
|
|
64
199
|
```typescript
|
|
65
|
-
import { FileCheckpointStore } from '@cogitator-ai/workflows';
|
|
200
|
+
import { FileCheckpointStore, InMemoryCheckpointStore } from '@cogitator-ai/workflows';
|
|
66
201
|
|
|
202
|
+
// File-based persistence
|
|
67
203
|
const store = new FileCheckpointStore('./checkpoints');
|
|
68
204
|
|
|
69
|
-
//
|
|
205
|
+
// Execute with checkpoints
|
|
70
206
|
await executor.execute(workflow, {
|
|
71
207
|
checkpointStore: store,
|
|
72
|
-
checkpointInterval: 5000,
|
|
208
|
+
checkpointInterval: 5000, // Save every 5 seconds
|
|
73
209
|
});
|
|
74
210
|
|
|
75
211
|
// Resume from checkpoint
|
|
76
212
|
const result = await executor.resume(checkpointId, store);
|
|
77
213
|
```
|
|
78
214
|
|
|
79
|
-
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Timer System
|
|
218
|
+
|
|
219
|
+
### Delay Nodes
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { delayNode, dynamicDelayNode, cronWaitNode, untilNode } from '@cogitator-ai/workflows';
|
|
223
|
+
|
|
224
|
+
const workflow = new WorkflowBuilder('timer-flow')
|
|
225
|
+
// Fixed delay
|
|
226
|
+
.addNode('wait', delayNode(5000)) // 5 seconds
|
|
227
|
+
|
|
228
|
+
// Dynamic delay based on state
|
|
229
|
+
.addNode(
|
|
230
|
+
'dynamic-wait',
|
|
231
|
+
dynamicDelayNode((state) => state.retryCount * 1000)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Wait for cron schedule
|
|
235
|
+
.addNode('cron-wait', cronWaitNode('0 9 * * *')) // Wait until 9 AM
|
|
236
|
+
|
|
237
|
+
// Wait until specific date
|
|
238
|
+
.addNode(
|
|
239
|
+
'until',
|
|
240
|
+
untilNode((state) => state.scheduledTime)
|
|
241
|
+
)
|
|
242
|
+
.build();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Duration Parsing
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { parseDuration, formatDuration } from '@cogitator-ai/workflows';
|
|
249
|
+
|
|
250
|
+
const ms = parseDuration('1h30m'); // 5400000
|
|
251
|
+
const str = formatDuration(5400000); // '1h 30m'
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Cron Utilities
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import {
|
|
258
|
+
validateCronExpression,
|
|
259
|
+
getNextCronOccurrence,
|
|
260
|
+
getNextCronOccurrences,
|
|
261
|
+
describeCronExpression,
|
|
262
|
+
CRON_PRESETS,
|
|
263
|
+
} from '@cogitator-ai/workflows';
|
|
264
|
+
|
|
265
|
+
// Validate
|
|
266
|
+
const valid = validateCronExpression('0 9 * * 1-5'); // true
|
|
267
|
+
|
|
268
|
+
// Get next occurrence
|
|
269
|
+
const next = getNextCronOccurrence('0 9 * * *');
|
|
270
|
+
|
|
271
|
+
// Get multiple occurrences
|
|
272
|
+
const nextFive = getNextCronOccurrences('0 9 * * *', 5);
|
|
273
|
+
|
|
274
|
+
// Human-readable description
|
|
275
|
+
const desc = describeCronExpression('0 9 * * 1-5'); // "At 09:00 on weekdays"
|
|
276
|
+
|
|
277
|
+
// Presets
|
|
278
|
+
CRON_PRESETS.EVERY_MINUTE; // '* * * * *'
|
|
279
|
+
CRON_PRESETS.HOURLY; // '0 * * * *'
|
|
280
|
+
CRON_PRESETS.DAILY; // '0 0 * * *'
|
|
281
|
+
CRON_PRESETS.WEEKLY; // '0 0 * * 0'
|
|
282
|
+
CRON_PRESETS.MONTHLY; // '0 0 1 * *'
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### TimerManager
|
|
286
|
+
|
|
287
|
+
Manage recurring timers:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { createTimerManager, createRecurringScheduler } from '@cogitator-ai/workflows';
|
|
291
|
+
|
|
292
|
+
const manager = createTimerManager({
|
|
293
|
+
maxConcurrent: 10,
|
|
294
|
+
defaultTimeout: 60000,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// One-shot timer
|
|
298
|
+
manager.schedule('task-1', 5000, async () => {
|
|
299
|
+
console.log('Executed after 5 seconds');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Recurring timer
|
|
303
|
+
const scheduler = createRecurringScheduler();
|
|
304
|
+
scheduler.schedule('daily-report', '0 9 * * *', async () => {
|
|
305
|
+
await generateDailyReport();
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Saga Patterns
|
|
312
|
+
|
|
313
|
+
### Retry with Backoff
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { executeWithRetry, withRetry, Retryable } from '@cogitator-ai/workflows';
|
|
317
|
+
|
|
318
|
+
// Function wrapper
|
|
319
|
+
const result = await executeWithRetry(async () => await unreliableOperation(), {
|
|
320
|
+
maxAttempts: 5,
|
|
321
|
+
initialDelay: 1000,
|
|
322
|
+
maxDelay: 30000,
|
|
323
|
+
backoffMultiplier: 2,
|
|
324
|
+
jitter: 0.1,
|
|
325
|
+
shouldRetry: (error) => error.code !== 'FATAL',
|
|
326
|
+
onRetry: (attempt, error, delay) => console.log(`Retry ${attempt} in ${delay}ms`),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Decorator-style
|
|
330
|
+
const retryableFetch = withRetry({ maxAttempts: 3 })(async (url: string) => await fetch(url));
|
|
331
|
+
|
|
332
|
+
// Class decorator
|
|
333
|
+
class ApiClient {
|
|
334
|
+
@Retryable({ maxAttempts: 3, initialDelay: 500 })
|
|
335
|
+
async request(endpoint: string) {
|
|
336
|
+
return fetch(endpoint);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Circuit Breaker
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { CircuitBreaker, createCircuitBreaker, WithCircuitBreaker } from '@cogitator-ai/workflows';
|
|
345
|
+
|
|
346
|
+
const breaker = createCircuitBreaker({
|
|
347
|
+
failureThreshold: 5,
|
|
348
|
+
successThreshold: 2,
|
|
349
|
+
timeout: 30000,
|
|
350
|
+
halfOpenMaxAttempts: 3,
|
|
351
|
+
onStateChange: (from, to) => console.log(`Circuit: ${from} -> ${to}`),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Use the breaker
|
|
355
|
+
try {
|
|
356
|
+
const result = await breaker.execute(async () => {
|
|
357
|
+
return await externalService.call();
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error instanceof CircuitBreakerOpenError) {
|
|
361
|
+
console.log('Circuit is open, using fallback');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Get stats
|
|
366
|
+
const stats = breaker.getStats();
|
|
367
|
+
console.log(stats.failures, stats.successes, stats.state);
|
|
368
|
+
|
|
369
|
+
// Decorator-style
|
|
370
|
+
class ServiceClient {
|
|
371
|
+
@WithCircuitBreaker({ failureThreshold: 3 })
|
|
372
|
+
async call() {
|
|
373
|
+
return fetch('/api');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Compensation (Saga)
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { CompensationManager, compensationBuilder } from '@cogitator-ai/workflows';
|
|
382
|
+
|
|
383
|
+
const saga = compensationBuilder<{ orderId: string }>()
|
|
384
|
+
.step({
|
|
385
|
+
name: 'reserve-inventory',
|
|
386
|
+
execute: async (ctx) => {
|
|
387
|
+
ctx.state.inventoryReserved = await inventory.reserve(ctx.data.orderId);
|
|
388
|
+
},
|
|
389
|
+
compensate: async (ctx) => {
|
|
390
|
+
await inventory.release(ctx.data.orderId);
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
.step({
|
|
394
|
+
name: 'charge-payment',
|
|
395
|
+
execute: async (ctx) => {
|
|
396
|
+
ctx.state.paymentId = await payments.charge(ctx.data.orderId);
|
|
397
|
+
},
|
|
398
|
+
compensate: async (ctx) => {
|
|
399
|
+
await payments.refund(ctx.state.paymentId);
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
.step({
|
|
403
|
+
name: 'ship-order',
|
|
404
|
+
execute: async (ctx) => {
|
|
405
|
+
await shipping.ship(ctx.data.orderId);
|
|
406
|
+
},
|
|
407
|
+
compensate: async (ctx) => {
|
|
408
|
+
await shipping.cancel(ctx.data.orderId);
|
|
409
|
+
},
|
|
410
|
+
})
|
|
411
|
+
.build();
|
|
412
|
+
|
|
413
|
+
const manager = new CompensationManager();
|
|
414
|
+
const result = await manager.execute(saga, { orderId: 'order-123' });
|
|
415
|
+
|
|
416
|
+
if (!result.success) {
|
|
417
|
+
console.log('Saga failed at:', result.failedStep);
|
|
418
|
+
console.log('Compensated steps:', result.compensatedSteps);
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Dead Letter Queue (DLQ)
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { createFileDLQ, createInMemoryDLQ } from '@cogitator-ai/workflows';
|
|
426
|
+
|
|
427
|
+
const dlq = createFileDLQ('./dlq');
|
|
428
|
+
|
|
429
|
+
// Add failed item
|
|
430
|
+
await dlq.add({
|
|
431
|
+
id: 'job-123',
|
|
432
|
+
payload: { orderId: 'order-456' },
|
|
433
|
+
error: 'Payment failed',
|
|
434
|
+
source: 'checkout-workflow',
|
|
435
|
+
attemptCount: 3,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Process DLQ
|
|
439
|
+
const items = await dlq.list({ source: 'checkout-workflow' });
|
|
440
|
+
for (const item of items) {
|
|
441
|
+
try {
|
|
442
|
+
await retryJob(item.payload);
|
|
443
|
+
await dlq.remove(item.id);
|
|
444
|
+
} catch {
|
|
445
|
+
await dlq.update(item.id, { attemptCount: item.attemptCount + 1 });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Idempotency
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { idempotent, Idempotent, createFileIdempotencyStore } from '@cogitator-ai/workflows';
|
|
454
|
+
|
|
455
|
+
const store = createFileIdempotencyStore('./idempotency');
|
|
456
|
+
|
|
457
|
+
// Function wrapper
|
|
458
|
+
const processOrder = idempotent(store, {
|
|
459
|
+
keyGenerator: (orderId: string) => `order:${orderId}`,
|
|
460
|
+
ttl: 24 * 60 * 60 * 1000, // 24 hours
|
|
461
|
+
})(async (orderId: string) => {
|
|
462
|
+
return await processOrderInternal(orderId);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// Safe to call multiple times
|
|
466
|
+
await processOrder('order-123'); // Executes
|
|
467
|
+
await processOrder('order-123'); // Returns cached result
|
|
468
|
+
|
|
469
|
+
// Decorator-style
|
|
470
|
+
class OrderService {
|
|
471
|
+
@Idempotent({ keyGenerator: (id) => `order:${id}`, ttl: 86400000 })
|
|
472
|
+
async process(orderId: string) {
|
|
473
|
+
return processOrderInternal(orderId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Subworkflows
|
|
481
|
+
|
|
482
|
+
### Nested Subworkflows
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { subworkflowNode, executeSubworkflow } from '@cogitator-ai/workflows';
|
|
486
|
+
|
|
487
|
+
const mainWorkflow = new WorkflowBuilder('main')
|
|
488
|
+
.addNode('prepare', prepareNode)
|
|
489
|
+
.addNode(
|
|
490
|
+
'process',
|
|
491
|
+
subworkflowNode(processingWorkflow, {
|
|
492
|
+
inputMapper: (state) => ({ items: state.items }),
|
|
493
|
+
outputMapper: (result) => ({ processedItems: result.output }),
|
|
494
|
+
maxDepth: 5,
|
|
495
|
+
errorStrategy: 'fail', // 'fail' | 'continue' | 'compensate'
|
|
496
|
+
})
|
|
497
|
+
)
|
|
498
|
+
.addNode('finalize', finalizeNode, { after: ['process'] })
|
|
499
|
+
.build();
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Parallel Subworkflows
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import { parallelSubworkflows, fanOutFanIn, scatterGather } from '@cogitator-ai/workflows';
|
|
506
|
+
|
|
507
|
+
// Fan-out/Fan-in pattern
|
|
508
|
+
const workflow = new WorkflowBuilder('parallel')
|
|
509
|
+
.addNode(
|
|
510
|
+
'distribute',
|
|
511
|
+
fanOutFanIn(
|
|
512
|
+
[
|
|
513
|
+
{ workflow: workflowA, input: { type: 'a' } },
|
|
514
|
+
{ workflow: workflowB, input: { type: 'b' } },
|
|
515
|
+
{ workflow: workflowC, input: { type: 'c' } },
|
|
516
|
+
],
|
|
517
|
+
{
|
|
518
|
+
concurrency: 3,
|
|
519
|
+
onProgress: (completed, total) => console.log(`${completed}/${total}`),
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
.build();
|
|
524
|
+
|
|
525
|
+
// Scatter-Gather (collect all results)
|
|
526
|
+
const results = await scatterGather(executor, workflows, inputs);
|
|
527
|
+
|
|
528
|
+
// Race (first to complete wins)
|
|
529
|
+
const winner = await raceSubworkflows(executor, [workflow1, workflow2]);
|
|
530
|
+
|
|
531
|
+
// Fallback (try until one succeeds)
|
|
532
|
+
const result = await fallbackSubworkflows(executor, [primary, secondary, tertiary]);
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Human-in-the-Loop
|
|
538
|
+
|
|
539
|
+
### Approval Node
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import { approvalNode, InMemoryApprovalStore, WebhookNotifier } from '@cogitator-ai/workflows';
|
|
543
|
+
|
|
544
|
+
const store = new InMemoryApprovalStore();
|
|
545
|
+
const notifier = new WebhookNotifier('https://slack.webhook.url');
|
|
546
|
+
|
|
547
|
+
const workflow = new WorkflowBuilder('approval-flow')
|
|
548
|
+
.addNode(
|
|
549
|
+
'request',
|
|
550
|
+
approvalNode({
|
|
551
|
+
message: (state) => `Approve expense: $${state.amount}`,
|
|
552
|
+
approvers: ['manager@company.com'],
|
|
553
|
+
timeout: 24 * 60 * 60 * 1000, // 24 hours
|
|
554
|
+
store,
|
|
555
|
+
notifier,
|
|
556
|
+
})
|
|
557
|
+
)
|
|
558
|
+
.addConditional('check', (state) => state.approved, { after: ['request'] })
|
|
559
|
+
.addNode('process', processNode, { after: ['check:true'] })
|
|
560
|
+
.addNode('reject', rejectNode, { after: ['check:false'] })
|
|
561
|
+
.build();
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Choice Node
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { choiceNode } from '@cogitator-ai/workflows';
|
|
568
|
+
|
|
569
|
+
const workflow = new WorkflowBuilder('choice-flow')
|
|
570
|
+
.addNode(
|
|
571
|
+
'select',
|
|
572
|
+
choiceNode({
|
|
573
|
+
message: 'Select processing method:',
|
|
574
|
+
choices: [
|
|
575
|
+
{ id: 'fast', label: 'Fast (less accurate)', value: 'fast' },
|
|
576
|
+
{ id: 'accurate', label: 'Accurate (slower)', value: 'accurate' },
|
|
577
|
+
],
|
|
578
|
+
store,
|
|
579
|
+
notifier,
|
|
580
|
+
})
|
|
581
|
+
)
|
|
582
|
+
.build();
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Input Node
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { inputNode } from '@cogitator-ai/workflows';
|
|
589
|
+
|
|
590
|
+
const workflow = new WorkflowBuilder('input-flow')
|
|
591
|
+
.addNode(
|
|
592
|
+
'get-details',
|
|
593
|
+
inputNode({
|
|
594
|
+
message: 'Please provide additional details:',
|
|
595
|
+
fields: [
|
|
596
|
+
{ name: 'reason', type: 'text', required: true },
|
|
597
|
+
{ name: 'priority', type: 'select', options: ['low', 'medium', 'high'] },
|
|
598
|
+
],
|
|
599
|
+
store,
|
|
600
|
+
notifier,
|
|
601
|
+
})
|
|
602
|
+
)
|
|
603
|
+
.build();
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Approval Chains
|
|
80
607
|
|
|
81
608
|
```typescript
|
|
82
|
-
import {
|
|
609
|
+
import { managementChain, chainNode } from '@cogitator-ai/workflows';
|
|
83
610
|
|
|
84
|
-
const workflow = new WorkflowBuilder('
|
|
85
|
-
.addNode('analyze', agentNode(analyzerAgent))
|
|
86
|
-
.addNode('transform', toolNode('json-transform', { mapping: '...' }))
|
|
611
|
+
const workflow = new WorkflowBuilder('chain-approval')
|
|
87
612
|
.addNode(
|
|
88
|
-
'
|
|
89
|
-
|
|
613
|
+
'approval',
|
|
614
|
+
managementChain({
|
|
615
|
+
steps: [
|
|
616
|
+
{ approver: 'team-lead@co.com', requiredFor: (state) => state.amount > 100 },
|
|
617
|
+
{ approver: 'manager@co.com', requiredFor: (state) => state.amount > 1000 },
|
|
618
|
+
{ approver: 'director@co.com', requiredFor: (state) => state.amount > 10000 },
|
|
619
|
+
],
|
|
620
|
+
store,
|
|
621
|
+
notifier,
|
|
622
|
+
})
|
|
90
623
|
)
|
|
91
624
|
.build();
|
|
92
625
|
```
|
|
93
626
|
|
|
94
|
-
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Map-Reduce Patterns
|
|
630
|
+
|
|
631
|
+
### Map (Parallel Processing)
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
import { mapNode, parallelMap, batchedMap } from '@cogitator-ai/workflows';
|
|
635
|
+
|
|
636
|
+
const workflow = new WorkflowBuilder('map-flow')
|
|
637
|
+
.addNode(
|
|
638
|
+
'process-items',
|
|
639
|
+
mapNode({
|
|
640
|
+
items: (state) => state.items,
|
|
641
|
+
mapper: async (item, index, ctx) => {
|
|
642
|
+
return await processItem(item);
|
|
643
|
+
},
|
|
644
|
+
concurrency: 5,
|
|
645
|
+
onProgress: ({ completed, total }) => console.log(`${completed}/${total}`),
|
|
646
|
+
})
|
|
647
|
+
)
|
|
648
|
+
.build();
|
|
649
|
+
|
|
650
|
+
// Batched processing
|
|
651
|
+
const results = await batchedMap(items, processItem, { batchSize: 10, concurrency: 3 });
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Reduce (Aggregation)
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
import { reduceNode, collect, sum, groupBy, stats } from '@cogitator-ai/workflows';
|
|
658
|
+
|
|
659
|
+
const workflow = new WorkflowBuilder('reduce-flow')
|
|
660
|
+
.addNode(
|
|
661
|
+
'aggregate',
|
|
662
|
+
reduceNode({
|
|
663
|
+
items: (state) => state.results,
|
|
664
|
+
reducer: (acc, item) => acc + item.value,
|
|
665
|
+
initialValue: 0,
|
|
666
|
+
})
|
|
667
|
+
)
|
|
668
|
+
.build();
|
|
669
|
+
|
|
670
|
+
// Built-in aggregators
|
|
671
|
+
const collected = collect(items); // Collect all
|
|
672
|
+
const total = sum(items, (i) => i.value); // Sum values
|
|
673
|
+
const grouped = groupBy(items, (i) => i.category); // Group by key
|
|
674
|
+
const statistics = stats(items, (i) => i.score); // { min, max, avg, sum, count }
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Map-Reduce
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
import { mapReduceNode, executeMapReduce } from '@cogitator-ai/workflows';
|
|
681
|
+
|
|
682
|
+
const workflow = new WorkflowBuilder('mapreduce-flow')
|
|
683
|
+
.addNode(
|
|
684
|
+
'word-count',
|
|
685
|
+
mapReduceNode({
|
|
686
|
+
items: (state) => state.documents,
|
|
687
|
+
mapper: async (doc) => {
|
|
688
|
+
const words = doc.text.split(/\s+/);
|
|
689
|
+
return words.map((w) => ({ word: w, count: 1 }));
|
|
690
|
+
},
|
|
691
|
+
reducer: (results) => {
|
|
692
|
+
return results.flat().reduce((acc, { word, count }) => {
|
|
693
|
+
acc[word] = (acc[word] || 0) + count;
|
|
694
|
+
return acc;
|
|
695
|
+
}, {});
|
|
696
|
+
},
|
|
697
|
+
concurrency: 10,
|
|
698
|
+
})
|
|
699
|
+
)
|
|
700
|
+
.build();
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## Triggers
|
|
706
|
+
|
|
707
|
+
### Cron Trigger
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
import { createCronTrigger, CronTriggerExecutor } from '@cogitator-ai/workflows';
|
|
711
|
+
|
|
712
|
+
const trigger = createCronTrigger({
|
|
713
|
+
expression: '0 9 * * 1-5', // 9 AM on weekdays
|
|
714
|
+
timezone: 'America/New_York',
|
|
715
|
+
workflow: dailyReportWorkflow,
|
|
716
|
+
executor,
|
|
717
|
+
onTrigger: (time) => console.log('Triggered at:', time),
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
trigger.start();
|
|
721
|
+
// Later: trigger.stop();
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Webhook Trigger
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
import { createWebhookTrigger, WebhookTriggerExecutor } from '@cogitator-ai/workflows';
|
|
728
|
+
|
|
729
|
+
const webhook = createWebhookTrigger({
|
|
730
|
+
path: '/webhooks/github',
|
|
731
|
+
workflow: githubEventWorkflow,
|
|
732
|
+
executor,
|
|
733
|
+
auth: {
|
|
734
|
+
type: 'hmac',
|
|
735
|
+
secret: process.env.WEBHOOK_SECRET!,
|
|
736
|
+
header: 'X-Hub-Signature-256',
|
|
737
|
+
},
|
|
738
|
+
rateLimit: {
|
|
739
|
+
maxRequests: 100,
|
|
740
|
+
windowMs: 60000,
|
|
741
|
+
},
|
|
742
|
+
inputMapper: (req) => ({ event: req.body.action, payload: req.body }),
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Handle incoming request
|
|
746
|
+
const result = await webhook.handle(request);
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### Trigger Manager
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
import {
|
|
753
|
+
createTriggerManager,
|
|
754
|
+
cronTrigger,
|
|
755
|
+
webhookTrigger,
|
|
756
|
+
eventTrigger,
|
|
757
|
+
} from '@cogitator-ai/workflows';
|
|
758
|
+
|
|
759
|
+
const manager = createTriggerManager({ executor });
|
|
760
|
+
|
|
761
|
+
manager.register(
|
|
762
|
+
'daily-report',
|
|
763
|
+
cronTrigger({
|
|
764
|
+
expression: '0 9 * * *',
|
|
765
|
+
workflow: reportWorkflow,
|
|
766
|
+
})
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
manager.register(
|
|
770
|
+
'github-webhook',
|
|
771
|
+
webhookTrigger({
|
|
772
|
+
path: '/hooks/github',
|
|
773
|
+
workflow: githubWorkflow,
|
|
774
|
+
})
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
manager.register(
|
|
778
|
+
'order-created',
|
|
779
|
+
eventTrigger({
|
|
780
|
+
event: 'order.created',
|
|
781
|
+
workflow: orderProcessingWorkflow,
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
await manager.startAll();
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Observability
|
|
791
|
+
|
|
792
|
+
### Tracing
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
import {
|
|
796
|
+
createTracer,
|
|
797
|
+
OTLPSpanExporter,
|
|
798
|
+
ZipkinSpanExporter,
|
|
799
|
+
CompositeSpanExporter,
|
|
800
|
+
} from '@cogitator-ai/workflows';
|
|
801
|
+
|
|
802
|
+
// OTLP exporter (Jaeger, Tempo, etc.)
|
|
803
|
+
const otlpExporter = new OTLPSpanExporter({
|
|
804
|
+
endpoint: 'http://localhost:4318/v1/traces',
|
|
805
|
+
headers: { 'X-Api-Key': 'secret' },
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
// Zipkin exporter
|
|
809
|
+
const zipkinExporter = new ZipkinSpanExporter({
|
|
810
|
+
endpoint: 'http://localhost:9411/api/v2/spans',
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Composite (multiple exporters)
|
|
814
|
+
const exporter = new CompositeSpanExporter([otlpExporter, zipkinExporter]);
|
|
815
|
+
|
|
816
|
+
const tracer = createTracer({
|
|
817
|
+
serviceName: 'my-workflow-service',
|
|
818
|
+
exporter,
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// Execute with tracing
|
|
822
|
+
await executor.execute(workflow, { tracer });
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### Metrics
|
|
826
|
+
|
|
827
|
+
```typescript
|
|
828
|
+
import { createMetricsCollector, WorkflowMetricsCollector } from '@cogitator-ai/workflows';
|
|
829
|
+
|
|
830
|
+
const metrics = createMetricsCollector({
|
|
831
|
+
prefix: 'cogitator_workflow',
|
|
832
|
+
labels: { environment: 'production' },
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// Execute with metrics
|
|
836
|
+
await executor.execute(workflow, { metrics });
|
|
837
|
+
|
|
838
|
+
// Get metrics
|
|
839
|
+
const nodeMetrics = metrics.getNodeMetrics('my-node');
|
|
840
|
+
console.log(nodeMetrics.executionCount);
|
|
841
|
+
console.log(nodeMetrics.averageDuration);
|
|
842
|
+
console.log(nodeMetrics.errorRate);
|
|
843
|
+
|
|
844
|
+
const workflowMetrics = metrics.getWorkflowMetrics('my-workflow');
|
|
845
|
+
console.log(workflowMetrics.completionRate);
|
|
846
|
+
console.log(workflowMetrics.averageCompletionTime);
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## Workflow Management
|
|
852
|
+
|
|
853
|
+
### WorkflowManager
|
|
854
|
+
|
|
855
|
+
```typescript
|
|
856
|
+
import { createWorkflowManager, createFileRunStore } from '@cogitator-ai/workflows';
|
|
857
|
+
|
|
858
|
+
const runStore = createFileRunStore('./runs');
|
|
859
|
+
|
|
860
|
+
const manager = createWorkflowManager({
|
|
861
|
+
executor,
|
|
862
|
+
runStore,
|
|
863
|
+
concurrency: 10,
|
|
864
|
+
defaultTimeout: 300000,
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// Schedule a workflow run
|
|
868
|
+
const runId = await manager.schedule(workflow, {
|
|
869
|
+
input: 'Process this',
|
|
870
|
+
priority: 1,
|
|
871
|
+
scheduledAt: new Date(Date.now() + 60000), // 1 minute from now
|
|
872
|
+
tags: ['daily', 'report'],
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// Get run status
|
|
876
|
+
const run = await manager.getRun(runId);
|
|
877
|
+
console.log(run.status); // 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
878
|
+
|
|
879
|
+
// List runs
|
|
880
|
+
const runs = await manager.listRuns({
|
|
881
|
+
status: 'running',
|
|
882
|
+
workflowId: 'daily-report',
|
|
883
|
+
fromDate: new Date('2024-01-01'),
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// Cancel a run
|
|
887
|
+
await manager.cancel(runId);
|
|
888
|
+
|
|
889
|
+
// Get stats
|
|
890
|
+
const stats = await manager.getStats();
|
|
891
|
+
console.log(stats.pending, stats.running, stats.completed, stats.failed);
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### JobScheduler
|
|
895
|
+
|
|
896
|
+
```typescript
|
|
897
|
+
import { createJobScheduler, PriorityQueue } from '@cogitator-ai/workflows';
|
|
898
|
+
|
|
899
|
+
const scheduler = createJobScheduler({
|
|
900
|
+
concurrency: 5,
|
|
901
|
+
maxQueueSize: 1000,
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Add jobs with priority
|
|
905
|
+
scheduler.enqueue({ id: 'job-1', payload: data1, priority: 1 });
|
|
906
|
+
scheduler.enqueue({ id: 'job-2', payload: data2, priority: 10 }); // Higher priority
|
|
907
|
+
|
|
908
|
+
// Process jobs
|
|
909
|
+
scheduler.process(async (job) => {
|
|
910
|
+
await processJob(job.payload);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
scheduler.start();
|
|
914
|
+
```
|
|
95
915
|
|
|
96
|
-
|
|
916
|
+
---
|
|
97
917
|
|
|
98
918
|
## License
|
|
99
919
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cogitator-ai/workflows",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "DAG-based workflow engine for Cogitator agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"@types/node": "^25.0.0",
|
|
19
19
|
"cron-parser": "^4.9.0",
|
|
20
20
|
"nanoid": "^5.0.4",
|
|
21
|
-
"@cogitator-ai/core": "0.
|
|
22
|
-
"@cogitator-ai/types": "0.
|
|
21
|
+
"@cogitator-ai/core": "0.4.0",
|
|
22
|
+
"@cogitator-ai/types": "0.5.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"typescript": "^5.3.0",
|