flutter_agentic_graph 0.2.0
flutter_agentic_graph: ^0.2.0 copied to clipboard
Stateful agent graphs for Flutter — nodes, conditional edges, cycles, checkpointing, and human-in-the-loop. LangGraph-style orchestration for flutter_agentic.
flutter_agentic_graph — Stateful Agent Graphs for Flutter #
LangGraph-style agent orchestration, built Flutter-first. Model your AI workflow as a graph: nodes that read and write shared state, conditional edges, cycles with a recursion limit, checkpointing for pause/resume, and human-in-the-loop interrupts — all on top of flutter_agentic.
👉 View on pub.dev · API docs · GitHub
Part of the flutter_agentic family: flutter_agentic (core SDK) · flutter_agentic_ui · flutter_agentic_tools · flutter_agentic_memory
Why graphs? #
Linear chains break down the moment your agent needs to loop (retry until quality is good), branch (route easy vs hard queries), or wait for a human (approve before sending). A StateGraph expresses all three naturally:
┌─────────┐ needs revision ┌─────────┐
START → │ write │ ──────────────────────→ │ revise │
└─────────┘ ←──────────────────────┘─────────┘
│ good enough
▼
[approval] ← human-in-the-loop interrupt
│
END
Installation #
dependencies:
flutter_agentic_graph: ^0.1.1
Quick start #
import 'package:flutter_agentic_graph/flutter_agentic_graph.dart';
final graph = StateGraph(reducers: {'log': Reducers.append})
..addNode('draft', (state, ctx) async {
return {'draft': 'v${(state['attempts'] as int? ?? 0) + 1}',
'attempts': (state['attempts'] as int? ?? 0) + 1,
'log': 'drafted'};
})
..addNode('review', (state, ctx) async {
final ok = (state['attempts'] as int) >= 2; // your quality check
return {'approved': ok, 'log': 'reviewed'};
})
..setEntryPoint('draft')
..addEdge('draft', 'review')
..addConditionalEdge('review',
(s) => s['approved'] == true ? StateGraph.end : 'draft'); // cycle!
final app = graph.compile(recursionLimit: 10);
final result = await app.invoke({}) as GraphComplete;
print(result.state['draft']); // v2
Agents as nodes #
Wrap any AgenticAgent (with tools, memory, any of the 7 providers) as a node:
graph.addNode('researcher',
agentNode(researchAgent, inputKey: 'question', outputKey: 'findings'));
graph.addNode('writer',
agentNode(writerAgent, inputKey: 'findings', outputKey: 'article'));
Human-in-the-loop #
Static breakpoints — pause around a node:
final app = graph.compile(
checkpointer: MemoryCheckpointer(),
interruptBefore: ['publish'], // always ask before publishing
);
final result = await app.invoke({'draft': '…'}, threadId: 'run42');
if (result is GraphInterrupted) {
// show the draft to the user…
await app.resume('run42', update: {'draft': editedByUser});
}
Dynamic interrupts — a node decides to ask:
graph.addNode('confirm', (state, ctx) {
final answer = ctx.resumeValue;
if (answer == null) ctx.interrupt('Send this email?'); // pauses here
return {'confirmed': answer};
});
// later:
await app.resume('run42', resumeValue: true);
Checkpointing #
Every node execution is checkpointed per threadId, so long-running agent workflows survive interruption. MemoryCheckpointer ships in the box; implement Checkpointer for Hive/SQLite/server persistence.
Live progress events #
await app.invoke(input, onEvent: (e) {
switch (e) {
case NodeStarted(:final node): showSpinner(node);
case NodeCompleted(:final node): markDone(node);
case GraphPaused(:final payload): askUser(payload);
case NodeFailed(:final node): showError(node);
case GraphCompleted(): celebrate();
}
});
// or: app.stream(input).listen(...)
State channels & reducers #
Node updates are merged into shared state by key. Default is overwrite; use Reducers.append for message logs, Reducers.add for counters, or your own function:
StateGraph(reducers: {
'messages': Reducers.append,
'tokensUsed': Reducers.add,
});
License #
MIT — see LICENSE.