flowneer 0.7.1 → 0.9.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 +223 -761
- package/dist/{FlowBuilder-DJkzGH5l.d.ts → FlowBuilder-B_BIRk3f.d.ts} +42 -52
- package/dist/errors-u-hq7p5N.d.ts +16 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +58 -43
- package/dist/plugins/agent/index.d.ts +57 -144
- package/dist/plugins/agent/index.js +2 -833
- package/dist/plugins/config/index.d.ts +26 -0
- package/dist/plugins/config/index.js +159 -0
- package/dist/plugins/dev/index.d.ts +87 -2
- package/dist/plugins/dev/index.js +131 -0
- package/dist/plugins/eval/index.d.ts +1 -1
- package/dist/plugins/graph/index.d.ts +1 -1
- package/dist/plugins/index.d.ts +147 -3
- package/dist/plugins/index.js +522 -673
- package/dist/plugins/llm/index.d.ts +18 -2
- package/dist/plugins/llm/index.js +16 -11
- package/dist/plugins/memory/index.d.ts +1 -1
- package/dist/plugins/messaging/index.d.ts +6 -1
- package/dist/plugins/observability/index.d.ts +1 -1
- package/dist/plugins/output/index.d.ts +1 -1
- package/dist/plugins/persistence/index.d.ts +1 -1
- package/dist/plugins/resilience/index.d.ts +1 -1
- package/dist/plugins/telemetry/index.d.ts +1 -1
- package/dist/plugins/tools/index.d.ts +1 -1
- package/dist/presets/agent/index.d.ts +435 -0
- package/dist/presets/agent/index.js +956 -0
- package/dist/presets/config/index.d.ts +64 -0
- package/dist/presets/config/index.js +879 -0
- package/dist/presets/index.d.ts +7 -0
- package/dist/presets/index.js +1317 -0
- package/dist/presets/pipeline/index.d.ts +88 -0
- package/dist/presets/pipeline/index.js +620 -0
- package/dist/presets/rag/index.d.ts +77 -0
- package/dist/presets/rag/index.js +617 -0
- package/dist/schema-DFNzcQP5.d.ts +72 -0
- package/dist/src/index.d.ts +4 -18
- package/dist/src/index.js +58 -43
- package/package.json +9 -2
- package/dist/patterns-CCtG27Gv.d.ts +0 -195
package/README.md
CHANGED
|
@@ -9,10 +9,30 @@
|
|
|
9
9
|
<a href="https://www.npmjs.com/package/flowneer"><img src="https://img.shields.io/npm/d18m/flowneer" /></a>
|
|
10
10
|
<a href="https://deepwiki.com/Fanna1119/flowneer"><img src="https://deepwiki.com/badge.svg" /></a>
|
|
11
11
|
<a href="https://github.com/Fanna1119/flowneer"><img src="https://img.shields.io/badge/GitHub-%23121011.svg?logo=github&logoColor=white)" /></a>
|
|
12
|
-
|
|
12
|
+
<a href="https://context7.com/fanna1119/flowneer"><img src="https://img.shields.io/badge/-Context7-black?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiByeD0iNCIgZmlsbD0iIzA1OTY2OSIvPgo8cGF0aCBkPSJNMTAuNTcyNCAxNS4yNTY1QzEwLjU3MjQgMTcuNTAyNSA5LjY2MTMgMTkuMzc3OCA4LjE3ODA1IDIxLjEwNDdIMTEuNjMxOUwxMS42MzE5IDIyLjc3ODZINi4zMzQ1OVYyMS4xODk1QzcuOTU1NTcgMTkuMzU2NiA4LjU4MDY1IDE3Ljg2MjggOC41ODA2NSAxNS4yNTY1TDEwLjU3MjQgMTUuMjU2NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNy40Mjc2IDE1LjI1NjVDMTcuNDI3NiAxNy41MDI1IDE4LjMzODcgMTkuMzc3OCAxOS44MjIgMjEuMTA0N0gxNi4zNjgxVjIyLjc3ODZIMjEuNjY1NFYyMS4xODk1QzIwLjA0NDQgMTkuMzU2NiAxOS40MTk0IDE3Ljg2MjggMTkuNDE5NCAxNS4yNTY1SDE3LjQyNzZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTAuNTcyNCAxMi43NDM1QzEwLjU3MjQgMTAuNDk3NSA5LjY2MTMxIDguNjIyMjQgOC4xNzgwNyA2Ljg5NTMyTDExLjYzMTkgNi44OTUzMlY1LjIyMTM3TDYuMzM0NjEgNS4yMjEzN1Y2LjgxMDU2QzcuOTU1NTggOC42NDM0MyA4LjU4MDY2IDEwLjEzNzMgOC41ODA2NiAxMi43NDM1TDEwLjU3MjQgMTIuNzQzNVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNy40Mjc2IDEyLjc0MzVDMTcuNDI3NiAxMC40OTc1IDE4LjMzODcgOC42MjIyNCAxOS44MjIgNi44OTUzMkwxNi4zNjgxIDYuODk1MzJMMTYuMzY4MSA1LjIyMTM4TDIxLjY2NTQgNS4yMjEzOFY2LjgxMDU2QzIwLjA0NDQgOC42NDM0MyAxOS40MTk0IDEwLjEzNzMgMTkuNDE5NCAxMi43NDM1SDE3LjQyNzZaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K" alt="Badge"></a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Flowneer** is a tiny (~3 kB gzipped), zero-dependency TypeScript flow builder that gives you full control over deterministic, stateful LLM agents and workflows.
|
|
16
|
+
|
|
17
|
+
### Why Flowneer?
|
|
18
|
+
|
|
19
|
+
- **Ultra-lightweight** — ~3 kB gzipped core, zero dependencies
|
|
20
|
+
- **Fluent & composable** — Chain steps with shared mutable state
|
|
21
|
+
- **Full control flow primitives** — `.startWith()`, `.then()`, `.branch()`, `.loop()`, `.parallel()`, `.batch()`, `.anchor()` jumps
|
|
22
|
+
- **Streaming-first** — Real-time `.stream()` with event/chunk yielding
|
|
23
|
+
- **Precise extensibility** — Subclass with `.extend([plugins])` and scope hooks/plugins exactly where needed (via `StepFilter` globs/predicates)
|
|
24
|
+
- **Production-ready patterns** — Built-in presets for ReAct, sequential crews, supervisor-workers, round-robin debate, refinement loops
|
|
25
|
+
|
|
26
|
+
### Plugins unlock what you actually need
|
|
27
|
+
|
|
28
|
+
- Tool calling & registries
|
|
29
|
+
- ReAct / reasoning loops
|
|
30
|
+
- Memory (buffer, summary, KV)
|
|
31
|
+
- Human-in-the-loop interrupts
|
|
32
|
+
- Structured output parsing
|
|
33
|
+
- Rate limiting, retries, timeouts, tracing, eval, graph export/import
|
|
34
|
+
|
|
35
|
+
No forced abstractions. No monolith. Just a fast, deterministic builder that stays out of your way while giving you structured concurrency, cancellation, observability, and agentic power.
|
|
16
36
|
|
|
17
37
|
> Flowneer is currently under heavy development with ongoing pattern exploration and architectural refinement. Breaking changes are expected frequently, potentially on a daily basis, as the core design is actively evolving.
|
|
18
38
|
|
|
@@ -23,6 +43,7 @@ bun add flowneer
|
|
|
23
43
|
```
|
|
24
44
|
|
|
25
45
|
## For LLM Agents
|
|
46
|
+
|
|
26
47
|
[llms.txt](https://fanna1119.github.io/flowneer/llms.txt)
|
|
27
48
|
[llms-full.txt](https://fanna1119.github.io/flowneer/llms-full.txt)
|
|
28
49
|
|
|
@@ -50,6 +71,8 @@ await new FlowBuilder<State>()
|
|
|
50
71
|
|
|
51
72
|
Every step receives a **shared state object** (`s`) that you mutate directly. That's the whole data model.
|
|
52
73
|
|
|
74
|
+
---
|
|
75
|
+
|
|
53
76
|
## API
|
|
54
77
|
|
|
55
78
|
### `startWith(fn, options?)`
|
|
@@ -65,12 +88,7 @@ Append a sequential step.
|
|
|
65
88
|
Route to a named branch based on the return value of `router`.
|
|
66
89
|
|
|
67
90
|
```typescript
|
|
68
|
-
|
|
69
|
-
role: string;
|
|
70
|
-
message: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await new FlowBuilder<AuthState>()
|
|
91
|
+
await new FlowBuilder<{ role: string; message: string }>()
|
|
74
92
|
.startWith(async (s) => {
|
|
75
93
|
s.role = "admin";
|
|
76
94
|
})
|
|
@@ -84,7 +102,7 @@ await new FlowBuilder<AuthState>()
|
|
|
84
102
|
})
|
|
85
103
|
.then(async (s) => console.log(s.message))
|
|
86
104
|
.run({ role: "", message: "" });
|
|
87
|
-
//
|
|
105
|
+
// -> Welcome, admin!
|
|
88
106
|
```
|
|
89
107
|
|
|
90
108
|
### `loop(condition, body)`
|
|
@@ -92,11 +110,7 @@ await new FlowBuilder<AuthState>()
|
|
|
92
110
|
Repeat a sub-flow while `condition` returns `true`.
|
|
93
111
|
|
|
94
112
|
```typescript
|
|
95
|
-
|
|
96
|
-
ticks: number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
await new FlowBuilder<TickState>()
|
|
113
|
+
await new FlowBuilder<{ ticks: number }>()
|
|
100
114
|
.startWith(async (s) => {
|
|
101
115
|
s.ticks = 0;
|
|
102
116
|
})
|
|
@@ -109,21 +123,19 @@ await new FlowBuilder<TickState>()
|
|
|
109
123
|
)
|
|
110
124
|
.then(async (s) => console.log("done, ticks =", s.ticks))
|
|
111
125
|
.run({ ticks: 0 });
|
|
112
|
-
//
|
|
126
|
+
// -> done, ticks = 3
|
|
113
127
|
```
|
|
114
128
|
|
|
115
129
|
### `batch(items, processor, options?)`
|
|
116
130
|
|
|
117
|
-
Run a sub-flow once per item. The current item is written to `shared.__batchItem` by default.
|
|
131
|
+
Run a sub-flow once per item. The current item is written to `shared.__batchItem` by default. Pass a `{ key }` option to name the item slot — required for nested batches.
|
|
118
132
|
|
|
119
133
|
```typescript
|
|
120
|
-
|
|
134
|
+
await new FlowBuilder<{
|
|
121
135
|
numbers: number[];
|
|
122
136
|
results: number[];
|
|
123
137
|
__batchItem?: number;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
await new FlowBuilder<SumState>()
|
|
138
|
+
}>()
|
|
127
139
|
.startWith(async (s) => {
|
|
128
140
|
s.results = [];
|
|
129
141
|
})
|
|
@@ -136,119 +148,33 @@ await new FlowBuilder<SumState>()
|
|
|
136
148
|
)
|
|
137
149
|
.then(async (s) => console.log(s.results))
|
|
138
150
|
.run({ numbers: [1, 2, 3], results: [] });
|
|
139
|
-
//
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**Nested batches** — pass a `{ key }` option to give each level its own property name, so inner and outer items don't overwrite each other:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
interface NestedState {
|
|
146
|
-
groups: { name: string; members: string[] }[];
|
|
147
|
-
results: string[];
|
|
148
|
-
__group?: { name: string; members: string[] };
|
|
149
|
-
__member?: string;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
await new FlowBuilder<NestedState>()
|
|
153
|
-
.batch(
|
|
154
|
-
(s) => s.groups,
|
|
155
|
-
(b) =>
|
|
156
|
-
b
|
|
157
|
-
.startWith((s) => {
|
|
158
|
-
// s.__group is the current group
|
|
159
|
-
})
|
|
160
|
-
.batch(
|
|
161
|
-
(s) => s.__group!.members,
|
|
162
|
-
(inner) =>
|
|
163
|
-
inner.startWith((s) => {
|
|
164
|
-
// both s.__group and s.__member are accessible
|
|
165
|
-
s.results.push(`${s.__group!.name}:${s.__member!}`);
|
|
166
|
-
}),
|
|
167
|
-
{ key: "__member" },
|
|
168
|
-
),
|
|
169
|
-
{ key: "__group" },
|
|
170
|
-
)
|
|
171
|
-
.run({ groups: [{ name: "A", members: ["a1", "a2"] }], results: [] });
|
|
172
|
-
// → results: ["A:a1", "A:a2"]
|
|
151
|
+
// -> [2, 4, 6]
|
|
173
152
|
```
|
|
174
153
|
|
|
175
154
|
### `parallel(fns, options?, reducer?)`
|
|
176
155
|
|
|
177
|
-
Run multiple functions concurrently against the same shared state.
|
|
156
|
+
Run multiple functions concurrently against the same shared state. When a `reducer` is provided, each fn receives its own shallow clone and the reducer merges results back.
|
|
178
157
|
|
|
179
158
|
```typescript
|
|
180
|
-
|
|
181
|
-
posts?: any[];
|
|
182
|
-
users?: any[];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
await new FlowBuilder<FetchState>()
|
|
159
|
+
await new FlowBuilder<{ posts?: any[]; users?: any[] }>()
|
|
186
160
|
.parallel([
|
|
187
161
|
async (s) => {
|
|
188
|
-
|
|
189
|
-
s.posts = await res.json();
|
|
162
|
+
s.posts = await fetch("/posts").then((r) => r.json());
|
|
190
163
|
},
|
|
191
164
|
async (s) => {
|
|
192
|
-
|
|
193
|
-
s.users = await res.json();
|
|
165
|
+
s.users = await fetch("/users").then((r) => r.json());
|
|
194
166
|
},
|
|
195
167
|
])
|
|
196
|
-
.then(async (s) =>
|
|
197
|
-
console.log(
|
|
198
|
-
"Fetched",
|
|
199
|
-
s.posts?.length,
|
|
200
|
-
"posts and",
|
|
201
|
-
s.users?.length,
|
|
202
|
-
"users",
|
|
203
|
-
);
|
|
204
|
-
})
|
|
168
|
+
.then(async (s) => console.log(s.posts?.length, s.users?.length))
|
|
205
169
|
.run({});
|
|
206
|
-
// → Fetched 100 posts and 10 users
|
|
207
170
|
```
|
|
208
171
|
|
|
209
|
-
When a `reducer` is provided each fn receives its own **shallow clone** of `shared`, preventing concurrent write races. After all fns settle the reducer merges the drafts back into the original:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
interface ScoreState {
|
|
213
|
-
value: number;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
await new FlowBuilder<ScoreState>()
|
|
217
|
-
.parallel(
|
|
218
|
-
[
|
|
219
|
-
async (s) => {
|
|
220
|
-
s.value += 10;
|
|
221
|
-
},
|
|
222
|
-
async (s) => {
|
|
223
|
-
s.value += 20;
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
undefined,
|
|
227
|
-
(original, drafts) => {
|
|
228
|
-
// drafts[0].value === 10, drafts[1].value === 20 (each started at 0)
|
|
229
|
-
original.value = drafts.reduce((sum, d) => sum + d.value, 0);
|
|
230
|
-
},
|
|
231
|
-
)
|
|
232
|
-
.run({ value: 0 });
|
|
233
|
-
// original.value === 30
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
See [`withAtomicUpdates`](#withatomicupdates) for the plugin shorthand.
|
|
237
|
-
|
|
238
172
|
### `anchor(name)`
|
|
239
173
|
|
|
240
|
-
Insert a named marker in the step chain.
|
|
241
|
-
|
|
242
|
-
Any `NodeFn` can return `"#anchorName"` to jump back (or forward) to that anchor, enabling iterative refinement and reflection loops without nesting:
|
|
174
|
+
Insert a named marker in the step chain. Any `NodeFn` can return `"#anchorName"` to jump to that anchor, enabling iterative refinement loops without nesting.
|
|
243
175
|
|
|
244
176
|
```typescript
|
|
245
|
-
|
|
246
|
-
draft: string;
|
|
247
|
-
passes: number;
|
|
248
|
-
quality: number;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
await new FlowBuilder<RefineState>()
|
|
177
|
+
await new FlowBuilder<{ draft: string; quality: number; passes: number }>()
|
|
252
178
|
.startWith(async (s) => {
|
|
253
179
|
s.draft = await generateDraft(s);
|
|
254
180
|
})
|
|
@@ -258,31 +184,22 @@ await new FlowBuilder<RefineState>()
|
|
|
258
184
|
if (s.quality < 0.8) {
|
|
259
185
|
s.draft = await improveDraft(s.draft);
|
|
260
186
|
s.passes++;
|
|
261
|
-
return "#refine";
|
|
187
|
+
return "#refine";
|
|
262
188
|
}
|
|
263
189
|
})
|
|
264
190
|
.then(async (s) => console.log("Final draft after", s.passes, "passes"))
|
|
265
|
-
.run({ draft: "",
|
|
191
|
+
.run({ draft: "", quality: 0, passes: 0 });
|
|
266
192
|
```
|
|
267
193
|
|
|
268
|
-
>
|
|
194
|
+
> Pair with [`withCycles`](#resilience) to cap the maximum number of jumps.
|
|
269
195
|
|
|
270
196
|
### `fragment()` and `.add(fragment)`
|
|
271
197
|
|
|
272
|
-
Fragments are reusable partial flows
|
|
273
|
-
|
|
274
|
-
Create a fragment with the `fragment()` factory, chain steps on it, then embed it with `.add()`:
|
|
198
|
+
Fragments are reusable partial flows that can be spliced into any `FlowBuilder`.
|
|
275
199
|
|
|
276
200
|
```typescript
|
|
277
201
|
import { FlowBuilder, fragment } from "flowneer";
|
|
278
202
|
|
|
279
|
-
interface State {
|
|
280
|
-
input: string;
|
|
281
|
-
enriched: boolean;
|
|
282
|
-
summary: string;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Define reusable fragments
|
|
286
203
|
const enrich = fragment<State>()
|
|
287
204
|
.then(async (s) => {
|
|
288
205
|
s.enriched = true;
|
|
@@ -291,128 +208,47 @@ const enrich = fragment<State>()
|
|
|
291
208
|
s.input = s.input.trim();
|
|
292
209
|
});
|
|
293
210
|
|
|
294
|
-
const summarise = fragment<State>().loop(
|
|
295
|
-
(s) => !s.summary,
|
|
296
|
-
(b) =>
|
|
297
|
-
b.then(async (s) => {
|
|
298
|
-
s.summary = s.input.slice(0, 10);
|
|
299
|
-
}),
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
// Compose into a full flow
|
|
303
211
|
await new FlowBuilder<State>()
|
|
304
212
|
.startWith(async (s) => {
|
|
305
|
-
s.input = " hello
|
|
213
|
+
s.input = " hello ";
|
|
306
214
|
})
|
|
307
|
-
.add(enrich)
|
|
308
|
-
.
|
|
309
|
-
.then(async (s) => console.log(s.summary))
|
|
215
|
+
.add(enrich)
|
|
216
|
+
.then(async (s) => console.log(s.input))
|
|
310
217
|
.run({ input: "", enriched: false, summary: "" });
|
|
311
|
-
// → hello worl
|
|
312
218
|
```
|
|
313
219
|
|
|
314
|
-
Fragments support all step types
|
|
315
|
-
|
|
316
|
-
The same fragment can be reused across multiple flows:
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
const validate = fragment<State>().then(checkInput).then(sanitize);
|
|
320
|
-
|
|
321
|
-
const flowA = new FlowBuilder<State>().add(validate).then(handleA);
|
|
322
|
-
const flowB = new FlowBuilder<State>().add(validate).then(handleB);
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## using with `withCycles` plugin
|
|
326
|
-
|
|
327
|
-
`withCycles` guards against infinite anchor-jump loops. Each call registers one limit; multiple calls stack.
|
|
328
|
-
|
|
329
|
-
**Global limit** — throws after `n` total anchor jumps across the whole flow:
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
import { FlowBuilder } from "flowneer";
|
|
333
|
-
import { withCycles } from "flowneer/plugins/resilience";
|
|
334
|
-
|
|
335
|
-
FlowBuilder.use(withCycles);
|
|
336
|
-
|
|
337
|
-
const flow = new FlowBuilder<State>()
|
|
338
|
-
.withCycles(5) // max 5 total anchor jumps
|
|
339
|
-
.startWith(async (s) => {
|
|
340
|
-
s.count = 0;
|
|
341
|
-
})
|
|
342
|
-
.anchor("loop")
|
|
343
|
-
.then(async (s) => {
|
|
344
|
-
s.count += 1;
|
|
345
|
-
if (s.count < 3) return "#loop"; // jump back to "loop" anchor
|
|
346
|
-
})
|
|
347
|
-
.then(async (s) => console.log("done, count =", s.count));
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
**Per-anchor limit** — pass an anchor name as the second argument to restrict visits to that specific anchor only:
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
const flow = new FlowBuilder<State>()
|
|
354
|
-
.withCycles(5, "refine") // max 5 visits to the "refine" anchor
|
|
355
|
-
.startWith(generateDraft)
|
|
356
|
-
.anchor("refine")
|
|
357
|
-
.then(async (s) => {
|
|
358
|
-
s.quality = await score(s.draft);
|
|
359
|
-
if (s.quality < 0.8) {
|
|
360
|
-
s.draft = await improve(s.draft);
|
|
361
|
-
return "#refine";
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
**Mixed** — combine a global cap with independent per-anchor limits by chaining calls. Each limit is evaluated independently:
|
|
367
|
-
|
|
368
|
-
```typescript
|
|
369
|
-
const flow = new FlowBuilder<State>()
|
|
370
|
-
.withCycles(100) // global: max 100 total anchor jumps
|
|
371
|
-
.withCycles(5, "fast") // "fast" anchor: max 5 visits
|
|
372
|
-
.withCycles(10, "retry") // "retry" anchor: max 10 visits
|
|
373
|
-
...
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Unlisted anchors are unaffected by per-anchor limits. The global limit (if set) still counts every jump regardless of which anchor was targeted.
|
|
220
|
+
Fragments support all step types. They cannot be run directly — calling `.run()` on a fragment throws.
|
|
377
221
|
|
|
378
222
|
### `run(shared, params?, options?)`
|
|
379
223
|
|
|
380
|
-
Execute the flow. Optionally pass a `params` object that every step receives as a second argument.
|
|
224
|
+
Execute the flow. Optionally pass a `params` object that every step receives as a second argument, and an `AbortSignal` to cancel between steps.
|
|
381
225
|
|
|
382
226
|
```typescript
|
|
383
|
-
// Basic
|
|
384
227
|
await flow.run(shared);
|
|
385
|
-
|
|
386
|
-
// With params
|
|
387
228
|
await flow.run(shared, { userId: "123" });
|
|
388
229
|
|
|
389
|
-
// With AbortSignal — cancels between steps when the signal fires
|
|
390
230
|
const controller = new AbortController();
|
|
391
231
|
await flow.run(shared, undefined, { signal: controller.signal });
|
|
392
232
|
```
|
|
393
233
|
|
|
394
234
|
### `stream(shared, params?, options?)`
|
|
395
235
|
|
|
396
|
-
An async-generator alternative to `run()` that yields `StreamEvent` values as the flow executes.
|
|
236
|
+
An async-generator alternative to `run()` that yields `StreamEvent` values as the flow executes.
|
|
397
237
|
|
|
398
238
|
```typescript
|
|
399
|
-
import type { StreamEvent } from "flowneer";
|
|
400
|
-
|
|
401
239
|
for await (const event of flow.stream(shared)) {
|
|
402
|
-
if (event.type === "step:before") console.log("
|
|
403
|
-
if (event.type === "step:after") console.log("✓ step", event.meta.index);
|
|
240
|
+
if (event.type === "step:before") console.log("->", event.meta.index);
|
|
404
241
|
if (event.type === "chunk") process.stdout.write(event.chunk as string);
|
|
405
|
-
if (event.type === "error") console.error(event.error);
|
|
406
242
|
if (event.type === "done") break;
|
|
407
243
|
}
|
|
408
244
|
```
|
|
409
245
|
|
|
410
|
-
Steps emit chunks by assigning to `shared.__stream
|
|
246
|
+
Steps emit chunks by assigning to `shared.__stream`:
|
|
411
247
|
|
|
412
248
|
```typescript
|
|
413
249
|
.then(async (s) => {
|
|
414
250
|
for await (const token of llmStream()) {
|
|
415
|
-
s.__stream = token; //
|
|
251
|
+
s.__stream = token; // -> yields { type: "chunk", chunk: token }
|
|
416
252
|
}
|
|
417
253
|
})
|
|
418
254
|
```
|
|
@@ -425,7 +261,7 @@ Steps emit chunks by assigning to `shared.__stream`; each assignment yields a `"
|
|
|
425
261
|
| `error` | `meta`, `error` | When a step throws |
|
|
426
262
|
| `done` | `shared` | After the flow finishes |
|
|
427
263
|
|
|
428
|
-
###
|
|
264
|
+
### Step options
|
|
429
265
|
|
|
430
266
|
Any step that accepts `options` supports:
|
|
431
267
|
|
|
@@ -435,6 +271,8 @@ Any step that accepts `options` supports:
|
|
|
435
271
|
| `delaySec` | `0` | Seconds to wait between retries |
|
|
436
272
|
| `timeoutMs` | `0` | Milliseconds before the step is aborted (0 = no limit) |
|
|
437
273
|
|
|
274
|
+
---
|
|
275
|
+
|
|
438
276
|
## Error handling
|
|
439
277
|
|
|
440
278
|
When a step throws, the error is wrapped in a `FlowError` with the step index and type:
|
|
@@ -457,147 +295,76 @@ try {
|
|
|
457
295
|
}
|
|
458
296
|
```
|
|
459
297
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
```
|
|
463
|
-
FlowError: Flow failed at loop (step 1): exploded on tick 2
|
|
464
|
-
FlowError: Flow failed at batch (step 0): bad item: 3
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### `InterruptError`
|
|
468
|
-
|
|
469
|
-
`InterruptError` is a special error that **bypasses `FlowError` wrapping** — it propagates directly to the caller. Use it for human-in-the-loop and approval patterns (via [`withInterrupts`](#withinterrupts)).
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
import { FlowBuilder, InterruptError } from "flowneer";
|
|
298
|
+
`InterruptError` is a special error that bypasses `FlowError` wrapping and propagates directly to the caller. Use it for human-in-the-loop patterns via [`withInterrupts`](#observability) or [`withHumanNode`](#agent).
|
|
473
299
|
|
|
474
|
-
|
|
475
|
-
await flow.run(shared);
|
|
476
|
-
} catch (err) {
|
|
477
|
-
if (err instanceof InterruptError) {
|
|
478
|
-
// err.savedShared is a deep clone of state at the interrupt point
|
|
479
|
-
const approval = await askHuman(err.savedShared);
|
|
480
|
-
if (approval) await flow.run(shared); // resume from scratch or use withReplay
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
```
|
|
300
|
+
---
|
|
484
301
|
|
|
485
302
|
## Plugins
|
|
486
303
|
|
|
487
|
-
The core is intentionally small. Use `FlowBuilder.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
### Available plugins
|
|
492
|
-
|
|
493
|
-
| Category | Plugin | Method | Description |
|
|
494
|
-
| ----------------- | ------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
495
|
-
| **Observability** | `withHistory` | `.withHistory()` | Appends a shallow state snapshot after each step to `shared.__history` |
|
|
496
|
-
| | `withTiming` | `.withTiming()` | Records wall-clock duration (ms) of each step in `shared.__timings[index]` |
|
|
497
|
-
| | `withVerbose` | `.withVerbose()` | Prints the full `shared` object to stdout after each step |
|
|
498
|
-
| | `withInterrupts` | `.interruptIf(condition)` | Pauses the flow by throwing an `InterruptError` (with a deep-clone of `shared`) when condition is true |
|
|
499
|
-
| | `withCallbacks` | `.withCallbacks(handlers)` | LangChain-style lifecycle callbacks dispatched by step label prefix (`llm:*`, `tool:*`, `agent:*`) |
|
|
500
|
-
| **Persistence** | `withCheckpoint` | `.withCheckpoint(store)` | Saves `shared` to a store after each successful step |
|
|
501
|
-
| | `withAuditLog` | `.withAuditLog(store)` | Writes an immutable deep-clone audit entry to a store after every step (success and error) |
|
|
502
|
-
| | `withReplay` | `.withReplay(fromStep)` | Skips all steps before `fromStep`; combine with `.withCheckpoint()` to resume a failed flow |
|
|
503
|
-
| | `withVersionedCheckpoint` | `.withVersionedCheckpoint(store)` | Saves diff-based versioned checkpoints with parent pointers after each step that changes state |
|
|
504
|
-
| | | `.resumeFrom(version, store)` | Resolves a version id and skips all steps up to and including the saved step index |
|
|
505
|
-
| **Resilience** | `withCircuitBreaker` | `.withCircuitBreaker(opts?)` | Opens the circuit after `maxFailures` consecutive failures and rejects all steps until `resetMs` elapses |
|
|
506
|
-
| | `withFallback` | `.withFallback(fn)` | Catches any step error and calls `fn` instead of propagating, allowing the flow to continue |
|
|
507
|
-
| | `withTimeout` | `.withTimeout(ms)` | Aborts any step that exceeds `ms` milliseconds with a descriptive error |
|
|
508
|
-
| | `withCycles` | `.withCycles(n, anchor?)` | Throws after `n` anchor jumps globally, or after `n` visits to a named anchor — guards against infinite goto loops |
|
|
509
|
-
| **Messaging** | `withChannels` | `.withChannels()` | Initialises a `Map`-based message-channel system on `shared.__channels` |
|
|
510
|
-
| | `withStream` | `.withStream()` | Enables real-time chunk streaming via `shared.__stream` (see `.stream()`) |
|
|
511
|
-
| **LLM** | `withCostTracker` | `.withCostTracker()` | Accumulates per-step `shared.__stepCost` values into `shared.__cost` after each step |
|
|
512
|
-
| | `withRateLimit` | `.withRateLimit({ intervalMs })` | Enforces a minimum gap of `intervalMs` ms between steps to avoid hammering rate-limited APIs |
|
|
513
|
-
| | `withTokenBudget` | `.withTokenBudget(limit)` | Aborts the flow before any step where `shared.tokensUsed >= limit` |
|
|
514
|
-
| | `withStructuredOutput` | `.withStructuredOutput(opts)` | Parses and validates a step's LLM output (`shared.__llmOutput`) into a typed object via a Zod-compatible validator |
|
|
515
|
-
| **Tools** | `withTools` | `.withTools(registry)` | Attaches a `ToolRegistry` to `shared.__tools`; call `registry.execute()` or helpers from any step |
|
|
516
|
-
| **Agent** | `withReActLoop` | `.withReActLoop(opts)` | Built-in ReAct loop: think → tool-call → observe, with configurable `maxIterations` and `onObservation` |
|
|
517
|
-
| | `withHumanNode` | `.humanNode(opts?)` | Inserts a human-in-the-loop pause; pair with `resumeFlow()` to continue after receiving input |
|
|
518
|
-
| **Memory** | `withMemory` | `.withMemory(instance)` | Attaches a `Memory` instance to `shared.__memory`; choose `BufferWindowMemory`, `SummaryMemory`, or `KVMemory` |
|
|
519
|
-
| **Graph** | `withGraph` | `.withGraph()` | Describe a flow as a DAG with `.addNode()` / `.addEdge()`, then `.compile()` to a `FlowBuilder` chain |
|
|
520
|
-
| **Telemetry** | `withTelemetry` | `.withTelemetry(opts?)` | Structured span telemetry via `TelemetryDaemon`; accepts `consoleExporter`, `otlpExporter`, or a custom exporter |
|
|
521
|
-
| **Dev** | `withDryRun` | `.withDryRun()` | Skips all step bodies while still firing hooks — useful for validating observability wiring |
|
|
522
|
-
| | `withMocks` | `.withMocks(map)` | Replaces step bodies at specified indices with mock functions; all other steps run normally |
|
|
523
|
-
| | `withStepLimit` | `.withStepLimit(max?)` | Throws after `max` total step executions (default 1000); counter resets on each `run()` call |
|
|
524
|
-
| | `withAtomicUpdates` | `.parallelAtomic(fns, reducer, options?)` | Sugar over `parallel()` with a reducer — each fn runs on an isolated draft, reducer merges results |
|
|
525
|
-
|
|
526
|
-
Plugins are imported from `flowneer/plugins` (or their individual subpath) and registered once with `FlowBuilder.use()`:
|
|
304
|
+
The core is intentionally small. Use `FlowBuilder.extend([...plugins])` to create a subclass with plugins mixed in. Unlike the removed `use()`, `extend()` never mutates the base class — each call returns an isolated subclass.
|
|
305
|
+
|
|
306
|
+
### Using a plugin
|
|
527
307
|
|
|
528
308
|
```typescript
|
|
529
|
-
import {
|
|
309
|
+
import { FlowBuilder } from "flowneer";
|
|
310
|
+
import { withTiming } from "flowneer/plugins/observability";
|
|
311
|
+
import { withRateLimit } from "flowneer/plugins/llm";
|
|
312
|
+
|
|
313
|
+
const AppFlow = FlowBuilder.extend([withTiming, withRateLimit]);
|
|
530
314
|
|
|
531
|
-
|
|
532
|
-
|
|
315
|
+
const flow = new AppFlow<State>()
|
|
316
|
+
.withTiming()
|
|
317
|
+
.withRateLimit({ intervalMs: 500 })
|
|
318
|
+
.startWith(step1)
|
|
319
|
+
.then(step2);
|
|
533
320
|
```
|
|
534
321
|
|
|
535
|
-
|
|
322
|
+
Chain `extend()` calls to layer plugins on top of a base subclass:
|
|
536
323
|
|
|
537
324
|
```typescript
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
sendTo,
|
|
541
|
-
receiveFrom,
|
|
542
|
-
peekChannel,
|
|
543
|
-
} from "flowneer/plugins/messaging";
|
|
544
|
-
|
|
545
|
-
FlowBuilder.use(withChannels);
|
|
546
|
-
|
|
547
|
-
const flow = new FlowBuilder()
|
|
548
|
-
.withChannels()
|
|
549
|
-
.startWith(async (s) => {
|
|
550
|
-
sendTo(s, "results", { score: 42 });
|
|
551
|
-
})
|
|
552
|
-
.then(async (s) => {
|
|
553
|
-
const msgs = receiveFrom(s, "results"); // [{ score: 42 }]
|
|
554
|
-
});
|
|
325
|
+
const BaseFlow = FlowBuilder.extend([withTiming]);
|
|
326
|
+
const TracedFlow = BaseFlow.extend([withTrace]); // has both plugins
|
|
555
327
|
```
|
|
556
328
|
|
|
557
|
-
---
|
|
558
|
-
|
|
559
329
|
### Writing a plugin
|
|
560
330
|
|
|
331
|
+
A plugin is an object of functions that get mixed onto `FlowBuilder.prototype`. Each function receives the builder as `this` and should return `this` for chaining.
|
|
332
|
+
|
|
561
333
|
```typescript
|
|
562
|
-
import type {
|
|
334
|
+
import type {
|
|
335
|
+
FlowBuilder,
|
|
336
|
+
FlowneerPlugin,
|
|
337
|
+
StepFilter,
|
|
338
|
+
StepMeta,
|
|
339
|
+
} from "flowneer";
|
|
563
340
|
|
|
564
|
-
// 1. Augment the FlowBuilder interface for type safety
|
|
565
341
|
declare module "flowneer" {
|
|
566
342
|
interface FlowBuilder<S, P> {
|
|
567
|
-
withTracing(
|
|
343
|
+
withTracing(
|
|
344
|
+
fn: (meta: StepMeta, event: string) => void,
|
|
345
|
+
filter?: StepFilter,
|
|
346
|
+
): this;
|
|
568
347
|
}
|
|
569
348
|
}
|
|
570
349
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
350
|
+
export const tracingPlugin: FlowneerPlugin = {
|
|
351
|
+
withTracing(this: FlowBuilder<any, any>, fn, filter?: StepFilter) {
|
|
352
|
+
(this as any)._setHooks(
|
|
353
|
+
{
|
|
354
|
+
beforeStep: (meta: StepMeta) => fn(meta, "before"),
|
|
355
|
+
afterStep: (meta: StepMeta) => fn(meta, "after"),
|
|
356
|
+
onError: (meta: StepMeta) => fn(meta, "error"),
|
|
357
|
+
},
|
|
358
|
+
filter,
|
|
359
|
+
);
|
|
579
360
|
return this;
|
|
580
361
|
},
|
|
581
362
|
};
|
|
582
363
|
```
|
|
583
364
|
|
|
584
|
-
### Using a plugin
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
import { FlowBuilder } from "flowneer";
|
|
588
|
-
import { observePlugin } from "./observePlugin";
|
|
589
|
-
|
|
590
|
-
FlowBuilder.use(observePlugin); // one-time registration
|
|
591
|
-
|
|
592
|
-
const flow = new FlowBuilder<MyState>()
|
|
593
|
-
.withTracing((meta, event) => console.log(event, meta.type, meta.index))
|
|
594
|
-
.startWith(step1)
|
|
595
|
-
.then(step2);
|
|
596
|
-
```
|
|
597
|
-
|
|
598
365
|
### Lifecycle hooks
|
|
599
366
|
|
|
600
|
-
Plugins register hooks via `_setHooks()`.
|
|
367
|
+
Plugins register hooks via `_setHooks()`. Multiple registrations of the same hook compose — the first registered is the outermost.
|
|
601
368
|
|
|
602
369
|
| Hook | Called | Arguments |
|
|
603
370
|
| ---------------- | --------------------------------------------------------- | --------------------------------------- |
|
|
@@ -609,491 +376,186 @@ Plugins register hooks via `_setHooks()`. The following hook points are availabl
|
|
|
609
376
|
| `onError` | When a step throws (before re-throwing) | `(meta, error, shared, params)` |
|
|
610
377
|
| `afterFlow` | After the flow finishes (success or failure) | `(shared, params)` |
|
|
611
378
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
### What plugins are for
|
|
615
|
-
|
|
616
|
-
| Concern | Plugin / hook | Hook(s) used |
|
|
617
|
-
| ---------------------------- | --------------------------------- | ---------------------------------------- |
|
|
618
|
-
| Observability / tracing | `withHistory`, `withTiming` | `beforeStep` + `afterStep` |
|
|
619
|
-
| Lifecycle callbacks | `withCallbacks` | `beforeStep` + `afterStep` + `onError` |
|
|
620
|
-
| Persistence / checkpointing | `withCheckpoint` | `afterStep` |
|
|
621
|
-
| Versioned persistence | `withVersionedCheckpoint` | `beforeFlow` + `afterStep` |
|
|
622
|
-
| Step/execution skip | `withDryRun`, `withReplay` | `wrapStep` |
|
|
623
|
-
| Safe parallel isolation | `withAtomicUpdates` | `wrapParallelFn` (via core reducer) |
|
|
624
|
-
| Human-in-the-loop / approval | `withInterrupts`, `withHumanNode` | `then()` + `InterruptError` |
|
|
625
|
-
| Message passing | `withChannels` | `beforeFlow` |
|
|
626
|
-
| Real-time streaming | `withStream` / `.stream()` | `afterStep` (chunk injection) |
|
|
627
|
-
| Infinite-loop protection | `withCycles`, `withStepLimit` | `afterStep` / `beforeStep` |
|
|
628
|
-
| Tool calling | `withTools` | `beforeFlow` |
|
|
629
|
-
| Agent loops | `withReActLoop` | `then()` + `loop()` |
|
|
630
|
-
| Memory management | `withMemory` | `beforeFlow` |
|
|
631
|
-
| Structured output | `withStructuredOutput` | `afterStep` |
|
|
632
|
-
| Graph-based composition | `withGraph` | DSL compiler (pre-run) |
|
|
633
|
-
| Telemetry / spans | `withTelemetry` | `beforeStep` + `afterStep` + `afterFlow` |
|
|
634
|
-
| Cleanup / teardown | custom | `afterFlow` |
|
|
635
|
-
|
|
636
|
-
See [examples/observePlugin.ts](examples/observePlugin.ts) and [examples/persistPlugin.ts](examples/persistPlugin.ts) for complete implementations.
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Tool calling
|
|
379
|
+
Step-scoped hooks (`beforeStep`, `afterStep`, `onError`, `wrapStep`, `wrapParallelFn`) accept an optional [`StepFilter`](#stepfilter) as the second argument to `_setHooks()`. `beforeFlow` / `afterFlow` are unaffected. Unmatched `wrapStep`/`wrapParallelFn` hooks always call `next()` automatically so the middleware chain is never broken.
|
|
641
380
|
|
|
642
|
-
|
|
381
|
+
### `StepFilter`
|
|
643
382
|
|
|
644
383
|
```typescript
|
|
645
|
-
|
|
646
|
-
FlowBuilder.use(withTools);
|
|
647
|
-
|
|
648
|
-
const tools = new ToolRegistry([
|
|
649
|
-
{
|
|
650
|
-
name: "search",
|
|
651
|
-
description: "Search the web",
|
|
652
|
-
params: { query: { type: "string", description: "Query", required: true } },
|
|
653
|
-
execute: async ({ query }) => fetchSearchResults(query),
|
|
654
|
-
},
|
|
655
|
-
]);
|
|
656
|
-
|
|
657
|
-
const flow = new FlowBuilder<State>().withTools(tools).startWith(async (s) => {
|
|
658
|
-
const result = await s.__tools.execute({
|
|
659
|
-
name: "search",
|
|
660
|
-
args: { query: s.question },
|
|
661
|
-
});
|
|
662
|
-
s.searchResult = result;
|
|
663
|
-
});
|
|
384
|
+
type StepFilter = string[] | ((meta: StepMeta) => boolean);
|
|
664
385
|
```
|
|
665
386
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
---
|
|
669
|
-
|
|
670
|
-
## ReAct agent loop
|
|
671
|
-
|
|
672
|
-
`.withReActLoop` inserts a wired think → tool-call → observe loop. Your `think` function receives the current state (including `shared.__toolResults` from the previous round) and returns either a finish action or tool calls:
|
|
387
|
+
- **String array** — matches steps by `label`. Supports `*` as a glob wildcard (`"llm:*"` matches `"llm:summarise"`, `"llm:embed"`, …). Steps without a label are never matched.
|
|
388
|
+
- **Predicate** — return `true` to match. Use this for runtime conditions or multi-criteria logic.
|
|
673
389
|
|
|
674
390
|
```typescript
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const flow = new FlowBuilder<State>().withTools(tools).withReActLoop({
|
|
679
|
-
maxIterations: 8,
|
|
680
|
-
think: async (s) => {
|
|
681
|
-
const res = await llm(s.messages);
|
|
682
|
-
return res.toolCalls.length
|
|
683
|
-
? { action: "tool", calls: res.toolCalls }
|
|
684
|
-
: { action: "finish", output: res.text };
|
|
685
|
-
},
|
|
686
|
-
onObservation: (results, s) => {
|
|
687
|
-
s.messages.push({ role: "tool", content: JSON.stringify(results) });
|
|
688
|
-
},
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
// After run: s.__reactOutput holds the final answer
|
|
692
|
-
// s.__reactExhausted === true if maxIterations was reached
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
---
|
|
696
|
-
|
|
697
|
-
## Human-in-the-loop with `humanNode`
|
|
391
|
+
// Array form with glob
|
|
392
|
+
flow.addHooks({ beforeStep: log }, ["llm:*", "embed:*"]);
|
|
698
393
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
FlowBuilder.use(withHumanNode);
|
|
704
|
-
|
|
705
|
-
const flow = new FlowBuilder<DraftState>()
|
|
706
|
-
.startWith(generateDraft)
|
|
707
|
-
.humanNode({ prompt: "Please review the draft." })
|
|
708
|
-
.then(publishDraft);
|
|
709
|
-
|
|
710
|
-
try {
|
|
711
|
-
await flow.run(state);
|
|
712
|
-
} catch (e) {
|
|
713
|
-
if (e instanceof InterruptError) {
|
|
714
|
-
const feedback = await showReviewUI(e.savedShared);
|
|
715
|
-
await resumeFlow(flow, e.savedShared, { feedback });
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
---
|
|
721
|
-
|
|
722
|
-
## Multi-agent patterns
|
|
723
|
-
|
|
724
|
-
Four factory functions compose flows into common multi-agent topologies:
|
|
725
|
-
|
|
726
|
-
```typescript
|
|
727
|
-
import {
|
|
728
|
-
supervisorCrew,
|
|
729
|
-
sequentialCrew,
|
|
730
|
-
hierarchicalCrew,
|
|
731
|
-
roundRobinDebate,
|
|
732
|
-
} from "flowneer/plugins/agent";
|
|
733
|
-
|
|
734
|
-
// Supervisor → parallel workers → optional aggregator
|
|
735
|
-
const crew = supervisorCrew<State>(
|
|
736
|
-
(s) => {
|
|
737
|
-
s.plan = makePlan(s);
|
|
738
|
-
},
|
|
739
|
-
[researchAgent, codeAgent, reviewAgent],
|
|
740
|
-
{
|
|
741
|
-
post: (s) => {
|
|
742
|
-
s.report = compile(s);
|
|
743
|
-
},
|
|
744
|
-
},
|
|
394
|
+
// Predicate form
|
|
395
|
+
flow.addHooks(
|
|
396
|
+
{ beforeStep: log },
|
|
397
|
+
(meta) => meta.label?.startsWith("llm:") ?? false,
|
|
745
398
|
);
|
|
746
|
-
await crew.run(state);
|
|
747
|
-
|
|
748
|
-
// Round-robin debate across agents for N rounds
|
|
749
|
-
const debate = roundRobinDebate<State>([agentA, agentB, agentC], 3);
|
|
750
|
-
await debate.run(state);
|
|
751
399
|
```
|
|
752
400
|
|
|
753
|
-
|
|
401
|
+
`addHooks(hooks, filter?)` returns a `dispose()` function to remove the hooks.
|
|
754
402
|
|
|
755
403
|
---
|
|
756
404
|
|
|
757
|
-
##
|
|
405
|
+
## Available plugins
|
|
758
406
|
|
|
759
|
-
|
|
407
|
+
All plugins are imported from `flowneer/plugins` or their individual subpath (e.g. `flowneer/plugins/resilience`).
|
|
760
408
|
|
|
761
|
-
|
|
762
|
-
import {
|
|
763
|
-
BufferWindowMemory,
|
|
764
|
-
SummaryMemory,
|
|
765
|
-
KVMemory,
|
|
766
|
-
withMemory,
|
|
767
|
-
} from "flowneer/plugins/memory";
|
|
768
|
-
FlowBuilder.use(withMemory);
|
|
769
|
-
|
|
770
|
-
const memory = new BufferWindowMemory({ maxMessages: 20 });
|
|
771
|
-
|
|
772
|
-
const flow = new FlowBuilder<State>()
|
|
773
|
-
.withMemory(memory) // attaches to shared.__memory
|
|
774
|
-
.startWith(async (s) => {
|
|
775
|
-
s.__memory.add({ role: "user", content: s.userInput });
|
|
776
|
-
const history = s.__memory.toContext();
|
|
777
|
-
s.response = await llm(history);
|
|
778
|
-
s.__memory.add({ role: "assistant", content: s.response });
|
|
779
|
-
});
|
|
780
|
-
```
|
|
409
|
+
### Observability
|
|
781
410
|
|
|
782
|
-
|
|
|
783
|
-
|
|
|
784
|
-
| `
|
|
785
|
-
| `
|
|
786
|
-
| `
|
|
411
|
+
| Plugin | Method | Description |
|
|
412
|
+
| ---------------- | -------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
413
|
+
| `withHistory` | `.withHistory()` | Appends a shallow state snapshot after each step to `shared.__history` |
|
|
414
|
+
| `withTiming` | `.withTiming()` | Records wall-clock duration (ms) of each step in `shared.__timings[index]` |
|
|
415
|
+
| `withVerbose` | `.withVerbose()` | Prints the full `shared` object to stdout after each step |
|
|
416
|
+
| `withInterrupts` | `.interruptIf(condition)` | Throws an `InterruptError` (with a deep-clone of `shared`) when `condition` is true |
|
|
417
|
+
| `withCallbacks` | `.withCallbacks(handlers)` | LangChain-style lifecycle callbacks dispatched by step label prefix (`llm:*`, `tool:*`, `agent:*`) |
|
|
787
418
|
|
|
788
|
-
|
|
419
|
+
### Persistence
|
|
789
420
|
|
|
790
|
-
|
|
421
|
+
| Plugin | Method | Description |
|
|
422
|
+
| ------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
423
|
+
| `withCheckpoint` | `.withCheckpoint(store)` | Saves `shared` to a store after each successful step |
|
|
424
|
+
| `withAuditLog` | `.withAuditLog(store)` | Writes an immutable deep-clone audit entry to a store after every step (success and error) |
|
|
425
|
+
| `withReplay` | `.withReplay(fromStep)` | Skips all steps before `fromStep`; combine with `.withCheckpoint()` to resume a failed flow |
|
|
426
|
+
| `withVersionedCheckpoint` | `.withVersionedCheckpoint(store)` | Diff-based versioned checkpoints with parent pointers; use `.resumeFrom(version, store)` to restore |
|
|
791
427
|
|
|
792
|
-
|
|
428
|
+
### Resilience
|
|
793
429
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
} from "flowneer/plugins/output";
|
|
801
|
-
|
|
802
|
-
const obj = parseJsonOutput(llmText); // raw JSON, fenced, or embedded in prose
|
|
803
|
-
const items = parseListOutput(llmText); // dash, *, •, numbered, or newline-separated
|
|
804
|
-
const rows = parseMarkdownTable(llmText); // GFM table → Record<string,string>[]
|
|
805
|
-
const match = parseRegexOutput(llmText, /(?<id>\d+)/); // named or positional capture groups
|
|
806
|
-
```
|
|
430
|
+
| Plugin | Method | Description |
|
|
431
|
+
| -------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
432
|
+
| `withCircuitBreaker` | `.withCircuitBreaker(opts?)` | Opens the circuit after `maxFailures` consecutive failures and rejects all steps until `resetMs` elapses |
|
|
433
|
+
| `withFallback` | `.withFallback(fn)` | Catches any step error and calls `fn` instead of propagating |
|
|
434
|
+
| `withTimeout` | `.withTimeout(ms)` | Aborts any step that exceeds `ms` milliseconds |
|
|
435
|
+
| `withCycles` | `.withCycles(n, anchor?)` | Throws after `n` anchor jumps globally, or after `n` visits to a named anchor — guards against infinite loops |
|
|
807
436
|
|
|
808
|
-
|
|
437
|
+
### Messaging
|
|
809
438
|
|
|
810
|
-
|
|
439
|
+
| Plugin | Method | Description |
|
|
440
|
+
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
|
|
441
|
+
| `withChannels` | `.withChannels()` | `Map`-based message-channel system on `shared.__channels`; use `sendTo` / `receiveFrom` / `peekChannel` |
|
|
442
|
+
| `withStream` | `.withStream()` | Enables real-time chunk streaming via `shared.__stream` |
|
|
811
443
|
|
|
812
|
-
|
|
444
|
+
### LLM
|
|
813
445
|
|
|
814
|
-
|
|
446
|
+
| Plugin | Method | Description |
|
|
447
|
+
| ---------------------- | -------------------------------- | ------------------------------------------------------------------------ |
|
|
448
|
+
| `withCostTracker` | `.withCostTracker()` | Accumulates per-step `shared.__stepCost` values into `shared.__cost` |
|
|
449
|
+
| `withRateLimit` | `.withRateLimit({ intervalMs })` | Enforces a minimum gap of `intervalMs` ms between steps |
|
|
450
|
+
| `withTokenBudget` | `.withTokenBudget(limit)` | Aborts the flow before any step where `shared.tokensUsed >= limit` |
|
|
451
|
+
| `withStructuredOutput` | `.withStructuredOutput(opts)` | Parses and validates `shared.__llmOutput` via a Zod-compatible validator |
|
|
815
452
|
|
|
816
|
-
|
|
817
|
-
import { withStructuredOutput } from "flowneer/plugins/llm";
|
|
818
|
-
FlowBuilder.use(withStructuredOutput);
|
|
453
|
+
### Tools
|
|
819
454
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
455
|
+
| Plugin | Method | Description |
|
|
456
|
+
| ----------- | ---------------------- | ----------------------------------------------------------------------------------------- |
|
|
457
|
+
| `withTools` | `.withTools(registry)` | Attaches a `ToolRegistry` to `shared.__tools`; use `executeTool` / `executeTools` helpers |
|
|
823
458
|
|
|
824
|
-
|
|
825
|
-
// s.__validationError — set if parsing or validation failed
|
|
826
|
-
```
|
|
459
|
+
### Agent
|
|
827
460
|
|
|
828
|
-
|
|
461
|
+
| Plugin | Method | Description |
|
|
462
|
+
| --------------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
463
|
+
| `withReActLoop` | `.withReActLoop(opts)` | Built-in ReAct loop: think -> tool-call -> observe, with configurable `maxIterations` and `onObservation` |
|
|
464
|
+
| `withHumanNode` | `.humanNode(opts?)` | Inserts a human-in-the-loop pause; pair with `resumeFlow()` to continue after receiving input |
|
|
829
465
|
|
|
830
|
-
|
|
466
|
+
### Memory
|
|
831
467
|
|
|
832
|
-
|
|
468
|
+
| Plugin | Method | Description |
|
|
469
|
+
| ------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
470
|
+
| `withMemory` | `.withMemory(instance)` | Attaches a `Memory` instance to `shared.__memory`; choose `BufferWindowMemory`, `SummaryMemory`, or `KVMemory` |
|
|
833
471
|
|
|
834
|
-
|
|
835
|
-
import { runEvalSuite, exactMatch, f1Score } from "flowneer/plugins/eval";
|
|
836
|
-
|
|
837
|
-
const { results, summary } = await runEvalSuite(
|
|
838
|
-
[{ question: "What is 2+2?", expected: "4" }, ...],
|
|
839
|
-
myFlow,
|
|
840
|
-
{
|
|
841
|
-
accuracy: (item, s) => exactMatch(s.answer, item.expected),
|
|
842
|
-
f1: (item, s) => f1Score(s.answer, item.expected),
|
|
843
|
-
},
|
|
844
|
-
);
|
|
472
|
+
### Output
|
|
845
473
|
|
|
846
|
-
|
|
847
|
-
```
|
|
474
|
+
Pure parsing helpers — no plugin registration needed. Import from `flowneer/plugins/output`.
|
|
848
475
|
|
|
849
|
-
|
|
476
|
+
| Function | Description |
|
|
477
|
+
| -------------------- | ------------------------------------------------------------- |
|
|
478
|
+
| `parseJsonOutput` | Parse raw JSON, fenced, or embedded JSON from LLM text |
|
|
479
|
+
| `parseListOutput` | Parse dash, `*`, bullet, numbered, or newline-separated lists |
|
|
480
|
+
| `parseMarkdownTable` | Parse GFM tables to `Record<string, string>[]` |
|
|
481
|
+
| `parseRegexOutput` | Extract named or positional regex capture groups |
|
|
850
482
|
|
|
851
|
-
|
|
483
|
+
### Eval
|
|
852
484
|
|
|
853
|
-
|
|
485
|
+
| Export | Description |
|
|
486
|
+
| ---------------------------------------- | ----------------------------------------------------------------- |
|
|
487
|
+
| `runEvalSuite` | Run a flow against a labelled dataset and collect per-item scores |
|
|
488
|
+
| `exactMatch` | Scorer: exact string equality |
|
|
489
|
+
| `containsMatch` | Scorer: substring containment |
|
|
490
|
+
| `f1Score` | Scorer: token-level F1 |
|
|
491
|
+
| `retrievalPrecision` / `retrievalRecall` | Scorer: retrieval quality metrics |
|
|
492
|
+
| `answerRelevance` | Scorer: relevance signal |
|
|
854
493
|
|
|
855
|
-
|
|
494
|
+
### Graph
|
|
856
495
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
496
|
+
| Plugin | Method | Description |
|
|
497
|
+
| ----------- | -------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
498
|
+
| `withGraph` | `.withGraph()` | Describe a flow as a DAG with `.addNode()` / `.addEdge()`, then `.compile()` to a ready-to-run `FlowBuilder` |
|
|
860
499
|
|
|
861
|
-
|
|
862
|
-
.withGraph()
|
|
863
|
-
.addNode("fetch", (s) => {
|
|
864
|
-
s.data = fetch(s.url);
|
|
865
|
-
})
|
|
866
|
-
.addNode("parse", (s) => {
|
|
867
|
-
s.parsed = parse(s.data);
|
|
868
|
-
})
|
|
869
|
-
.addNode("validate", (s) => {
|
|
870
|
-
s.valid = validate(s.parsed);
|
|
871
|
-
})
|
|
872
|
-
.addNode("retry", (s) => {
|
|
873
|
-
s.url = nextUrl(s);
|
|
874
|
-
})
|
|
875
|
-
.addEdge("fetch", "parse")
|
|
876
|
-
.addEdge("parse", "validate")
|
|
877
|
-
.addEdge("validate", "retry", (s) => !s.valid) // conditional back-edge → loop
|
|
878
|
-
.addEdge("retry", "fetch")
|
|
879
|
-
.compile(); // returns a ready-to-run FlowBuilder
|
|
880
|
-
|
|
881
|
-
await flow.run({ url: "https://..." });
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
`compile()` runs Kahn's topological sort on unconditional edges, classifies conditional edges as forward jumps or back-edges, inserts `anchor` markers for back-edge targets, and emits the matching `FlowBuilder` chain. Throws descriptively on empty graphs, duplicate node names, unknown edge targets, or unconditional cycles.
|
|
885
|
-
|
|
886
|
-
---
|
|
887
|
-
|
|
888
|
-
## AI agent example
|
|
889
|
-
|
|
890
|
-
Flowneer's primitives map directly to common agent patterns:
|
|
891
|
-
|
|
892
|
-
```typescript
|
|
893
|
-
import { FlowBuilder } from "flowneer";
|
|
894
|
-
|
|
895
|
-
interface AgentState {
|
|
896
|
-
question: string;
|
|
897
|
-
history: Message[];
|
|
898
|
-
intent?: string;
|
|
899
|
-
answer?: string;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const agent = new FlowBuilder<AgentState>()
|
|
903
|
-
.startWith(classifyIntent)
|
|
904
|
-
.branch((s) => s.intent, {
|
|
905
|
-
weather: fetchWeather,
|
|
906
|
-
joke: tellJoke,
|
|
907
|
-
default: generalAnswer,
|
|
908
|
-
})
|
|
909
|
-
.then(formatAndRespond);
|
|
910
|
-
|
|
911
|
-
await agent.run({ question: "What's the weather in Paris?", history: [] });
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
A ReAct-style loop:
|
|
915
|
-
|
|
916
|
-
```typescript
|
|
917
|
-
const reactAgent = new FlowBuilder<AgentState>()
|
|
918
|
-
.startWith(think)
|
|
919
|
-
.loop(
|
|
920
|
-
(s) => !s.done,
|
|
921
|
-
(b) =>
|
|
922
|
-
b
|
|
923
|
-
.startWith(selectTool)
|
|
924
|
-
.branch(routeTool, {
|
|
925
|
-
search: webSearch,
|
|
926
|
-
code: runCode,
|
|
927
|
-
default: respond,
|
|
928
|
-
})
|
|
929
|
-
.then(observe),
|
|
930
|
-
)
|
|
931
|
-
.then(formatOutput);
|
|
932
|
-
```
|
|
933
|
-
|
|
934
|
-
See [examples/assistantFlow.ts](examples/assistantFlow.ts) for a full interactive agent.
|
|
935
|
-
|
|
936
|
-
### Agent-to-agent delegation
|
|
937
|
-
|
|
938
|
-
There is no special primitive for sub-agents — just call `anotherFlow.run(shared)` inside a `then`. Since `shared` is passed by reference, the sub-agent reads and writes the same state seamlessly:
|
|
939
|
-
|
|
940
|
-
```typescript
|
|
941
|
-
const researchAgent = new FlowBuilder<ReportState>()
|
|
942
|
-
.startWith(searchWeb)
|
|
943
|
-
.then(summariseSources);
|
|
500
|
+
### Telemetry
|
|
944
501
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
502
|
+
| Plugin | Method | Description |
|
|
503
|
+
| --------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| `withTelemetry` | `.withTelemetry(opts?)` | Structured span telemetry via `TelemetryDaemon`; accepts `consoleExporter`, `otlpExporter`, or a custom exporter |
|
|
948
505
|
|
|
949
|
-
|
|
950
|
-
.startWith(async (s) => {
|
|
951
|
-
s.query = "LLM benchmarks 2025";
|
|
952
|
-
})
|
|
953
|
-
.then(async (s) => researchAgent.run(s)) // delegate → sub-agent mutates s
|
|
954
|
-
.then(async (s) => writeAgent.run(s)) // delegate → sub-agent mutates s
|
|
955
|
-
.then(async (s) => console.log(s.report));
|
|
956
|
-
```
|
|
957
|
-
|
|
958
|
-
Any number of flows can be composed this way. Each sub-agent is itself a `FlowBuilder`, so it can have its own retries, branches, and plugins.
|
|
959
|
-
|
|
960
|
-
### Parallel sub-agents
|
|
961
|
-
|
|
962
|
-
Use `parallel` when sub-agents are independent and can run concurrently:
|
|
963
|
-
|
|
964
|
-
```typescript
|
|
965
|
-
const sentimentAgent = new FlowBuilder<AnalysisState>()
|
|
966
|
-
.startWith(classifySentiment)
|
|
967
|
-
.then(scoreSentiment);
|
|
506
|
+
### Dev
|
|
968
507
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
508
|
+
| Plugin | Method | Description |
|
|
509
|
+
| ------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
510
|
+
| `withDryRun` | `.withDryRun()` | Skips all step bodies while still firing hooks — useful for validating observability wiring |
|
|
511
|
+
| `withMocks` | `.withMocks(map)` | Replaces step bodies at specified indices with mock functions |
|
|
512
|
+
| `withStepLimit` | `.withStepLimit(max?)` | Throws after `max` total step executions (default 1000) |
|
|
513
|
+
| `withAtomicUpdates` | `.parallelAtomic(fns, reducer, options?)` | Sugar over `parallel()` with a reducer — each fn runs on an isolated draft |
|
|
972
514
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const orchestrator = new FlowBuilder<AnalysisState>()
|
|
976
|
-
.startWith(async (s) => {
|
|
977
|
-
s.text = "...input text...";
|
|
978
|
-
})
|
|
979
|
-
.parallel([
|
|
980
|
-
(s) => sentimentAgent.run(s), // writes s.sentiment
|
|
981
|
-
(s) => summaryAgent.run(s), // writes s.summary
|
|
982
|
-
(s) => toxicityAgent.run(s), // writes s.toxicity
|
|
983
|
-
])
|
|
984
|
-
.then(async (s) => {
|
|
985
|
-
console.log(s.sentiment, s.summary, s.toxicity);
|
|
986
|
-
});
|
|
987
|
-
```
|
|
515
|
+
---
|
|
988
516
|
|
|
989
|
-
|
|
517
|
+
## Presets
|
|
990
518
|
|
|
991
|
-
|
|
519
|
+
Presets are ready-made `FlowBuilder` factories for common patterns. Import from `flowneer/presets` or their individual subpath.
|
|
992
520
|
|
|
993
|
-
###
|
|
521
|
+
### Agent presets (`flowneer/presets/agent`)
|
|
994
522
|
|
|
995
|
-
|
|
523
|
+
| Preset | Description |
|
|
524
|
+
| -------------------- | ---------------------------------------------------------------------------------- |
|
|
525
|
+
| `createAgent` | LangChain-style factory — wire up tools and an LLM adapter to get a runnable agent |
|
|
526
|
+
| `withReActLoop` | ReAct think -> tool-call -> observe loop with configurable max iterations |
|
|
527
|
+
| `supervisorCrew` | Supervisor dispatches to parallel worker agents, with an optional aggregator step |
|
|
528
|
+
| `sequentialCrew` | Agents run in sequence, each receiving the output of the previous |
|
|
529
|
+
| `hierarchicalCrew` | Tree-structured multi-agent delegation |
|
|
530
|
+
| `roundRobinDebate` | Agents take turns responding for N rounds |
|
|
531
|
+
| `planAndExecute` | Planner LLM produces a step-by-step plan; executor LLM carries out each step |
|
|
532
|
+
| `reflexionAgent` | Generate -> critique -> revise loop (Reflexion paper) |
|
|
533
|
+
| `critiqueAndRevise` | Two-agent generate -> critique -> revise loop |
|
|
534
|
+
| `evaluatorOptimizer` | DSPy-style generate -> evaluate -> improve loop |
|
|
535
|
+
| `selfConsistency` | Parallel sampling + majority-vote aggregation |
|
|
536
|
+
| `tool` | Minimal tool-calling agent helper |
|
|
996
537
|
|
|
997
|
-
|
|
998
|
-
const reactAgent = new FlowBuilder<AgentState>()
|
|
999
|
-
.startWith(think)
|
|
1000
|
-
.anchor("act")
|
|
1001
|
-
.then(async (s) => {
|
|
1002
|
-
const result = await callTool(s.toolCall);
|
|
1003
|
-
s.observations.push(result);
|
|
1004
|
-
s.done = await shouldStop(s);
|
|
1005
|
-
if (!s.done) return "#act";
|
|
1006
|
-
})
|
|
1007
|
-
.then(formatOutput);
|
|
1008
|
-
```
|
|
538
|
+
### Pipeline presets (`flowneer/presets/pipeline`)
|
|
1009
539
|
|
|
1010
|
-
|
|
540
|
+
| Preset | Description |
|
|
541
|
+
| -------------------- | ------------------------------------------------------------------------ |
|
|
542
|
+
| `generateUntilValid` | Generate -> validate -> retry with error context until output passes |
|
|
543
|
+
| `mapReduceLlm` | Batch LLM calls across N items, then reduce results into a single output |
|
|
1011
544
|
|
|
1012
|
-
|
|
1013
|
-
import { withInterrupts, InterruptError } from "flowneer/plugins/observability";
|
|
1014
|
-
FlowBuilder.use(withInterrupts);
|
|
545
|
+
### RAG presets (`flowneer/presets/rag`)
|
|
1015
546
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
547
|
+
| Preset | Description |
|
|
548
|
+
| -------------- | --------------------------------------------------------- |
|
|
549
|
+
| `ragPipeline` | Standard retrieve -> augment -> generate pipeline |
|
|
550
|
+
| `iterativeRag` | RAG with follow-up retrieval loop for multi-hop questions |
|
|
1020
551
|
|
|
1021
|
-
|
|
1022
|
-
await flow.run(state);
|
|
1023
|
-
} catch (err) {
|
|
1024
|
-
if (err instanceof InterruptError) {
|
|
1025
|
-
// err.savedShared holds state at the pause point
|
|
1026
|
-
await showReviewUI(err.savedShared);
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
```
|
|
552
|
+
### Config presets (`flowneer/presets/config`)
|
|
1030
553
|
|
|
1031
|
-
|
|
554
|
+
| Preset | Description |
|
|
555
|
+
| ------- | ---------------------------------------------------------------- |
|
|
556
|
+
| `build` | Compile a `FlowConfig` JSON/object into a runnable `FlowBuilder` |
|
|
1032
557
|
|
|
1033
|
-
|
|
1034
|
-
Flowneer.ts Core — FlowBuilder, FlowError, InterruptError, Validator, StreamEvent, types
|
|
1035
|
-
index.ts Public exports
|
|
1036
|
-
plugins/
|
|
1037
|
-
observability/
|
|
1038
|
-
withHistory.ts State snapshot history
|
|
1039
|
-
withTiming.ts Per-step wall-clock timing
|
|
1040
|
-
withVerbose.ts Stdout logging
|
|
1041
|
-
withInterrupts.ts Human-in-the-loop / approval gates
|
|
1042
|
-
withCallbacks.ts LangChain-style lifecycle callbacks (llm:/tool:/agent: prefixes)
|
|
1043
|
-
persistence/
|
|
1044
|
-
withCheckpoint.ts Post-step state saves
|
|
1045
|
-
withAuditLog.ts Immutable audit trail
|
|
1046
|
-
withReplay.ts Skip-to-step for crash recovery
|
|
1047
|
-
withVersionedCheckpoint.ts Diff-based versioned saves + resumeFrom
|
|
1048
|
-
resilience/
|
|
1049
|
-
withCircuitBreaker.ts
|
|
1050
|
-
withFallback.ts
|
|
1051
|
-
withTimeout.ts
|
|
1052
|
-
withCycles.ts Guard against infinite goto loops
|
|
1053
|
-
llm/
|
|
1054
|
-
withCostTracker.ts
|
|
1055
|
-
withRateLimit.ts
|
|
1056
|
-
withTokenBudget.ts
|
|
1057
|
-
withStructuredOutput.ts Parse + validate LLM output via Zod-compatible validator
|
|
1058
|
-
messaging/
|
|
1059
|
-
withChannels.ts Map-based message channels (sendTo / receiveFrom)
|
|
1060
|
-
withStream.ts Real-time chunk streaming via shared.__stream
|
|
1061
|
-
tools/
|
|
1062
|
-
withTools.ts ToolRegistry + withTools plugin + helper functions
|
|
1063
|
-
agent/
|
|
1064
|
-
withReActLoop.ts Built-in ReAct think → tool-call → observe loop
|
|
1065
|
-
withHumanNode.ts humanNode() pause + resumeFlow() helper
|
|
1066
|
-
patterns.ts supervisorCrew / sequentialCrew / hierarchicalCrew / roundRobinDebate
|
|
1067
|
-
memory/
|
|
1068
|
-
types.ts Memory interface + MemoryMessage type
|
|
1069
|
-
bufferWindowMemory.ts Sliding-window conversation memory
|
|
1070
|
-
summaryMemory.ts Auto-summarising memory (user-supplied summarize fn)
|
|
1071
|
-
kvMemory.ts Key-value memory with JSON serialisation
|
|
1072
|
-
withMemory.ts Plugin that attaches memory to shared.__memory
|
|
1073
|
-
output/
|
|
1074
|
-
parseJson.ts Parse raw / fenced / embedded JSON from LLM output
|
|
1075
|
-
parseList.ts Parse dash / numbered / bullet / newline-separated lists
|
|
1076
|
-
parseTable.ts Parse GFM markdown tables to Record<string,string>[]
|
|
1077
|
-
parseRegex.ts Extract named or positional regex capture groups
|
|
1078
|
-
eval/
|
|
1079
|
-
index.ts Scoring functions + runEvalSuite
|
|
1080
|
-
graph/
|
|
1081
|
-
index.ts withGraph plugin — DAG compiler (addNode / addEdge / compile)
|
|
1082
|
-
telemetry/
|
|
1083
|
-
telemetry.ts TelemetryDaemon, consoleExporter, otlpExporter
|
|
1084
|
-
index.ts withTelemetry plugin wrapper
|
|
1085
|
-
dev/
|
|
1086
|
-
withDryRun.ts
|
|
1087
|
-
withMocks.ts
|
|
1088
|
-
withStepLimit.ts Cap total step executions
|
|
1089
|
-
withAtomicUpdates.ts parallelAtomic() shorthand
|
|
1090
|
-
examples/
|
|
1091
|
-
assistantFlow.ts Interactive LLM assistant with branching
|
|
1092
|
-
observePlugin.ts Tracing plugin example
|
|
1093
|
-
persistPlugin.ts Checkpoint plugin example
|
|
1094
|
-
clawneer.ts Full ReAct agent with tool calling
|
|
1095
|
-
streamingServer.ts SSE streaming server example
|
|
1096
|
-
```
|
|
558
|
+
---
|
|
1097
559
|
|
|
1098
560
|
## License
|
|
1099
561
|
|