Smart Flow Brings Breakpoint Debugging to AI Workflow Orchestration
Today I'm happy to share our open-source AI visual workflow project with everyone again.
Over the past month, we've been doing one thing in our spare time: making the act of calling large models as reliable, controllable, and debuggable as writing code.
So the name of this project is Smart Flow, an Agent-oriented AI workflow orchestration platform.
Now, we have fully open-sourced it.
In this article, besides talking about the product, I will also lay out the architecture design, execution flow, and core code implementation for everyone to see.
github: https://github.com/MrXujiang/smart-flow
Demo address: http://smart-flow.jitword.com
✦ ✦
1. Why We Built This Wheel
After large models became popular, like many teams, we started using workflows to string various AI capabilities together. But the deeper we used them, the more painful it became: Once the process gets long, debugging is all guesswork.
Which node had a problem? What exactly did the model return last time? What does the intermediate variable look like? Most platforms can only give you a final result; the process is a complete black box.
We researched many excellent products on the market, but as developers, we always felt something was missing: they treat workflows as 'configuration,' while we want to treat workflows as 'code' — if it's code, you should be able to set breakpoints, step through, and inspect variables.
Since we couldn't wait for it, we built it ourselves. Thus, Smart Flow was born.
2. What is Smart Flow
In one sentence: An AI workflow IDE for developers.
We can drag and drop nodes on a visual canvas and connect them into flows;
We can step through execution just like debugging code;
We can publish a workflow as an Agent with one click and use it directly;
We can also connect it to real business using Webhooks and scheduled tasks.
More importantly: it's zero configuration. No need to install a database; one command npm run dev starts both the frontend and backend together.
3. Sharing Smart-Flow's Highlight Details
1. Debug Workflows Like Debugging Code
This is the biggest difference between Smart Flow and other platforms: breakpoints, single-step execution, real-time variable monitoring, and Mock data — a complete set of four.
Click on a node to set a breakpoint; execution automatically pauses there, and the input and output of each node are clearly visible. When a complex process goes wrong, you no longer rely on guessing, but on 'seeing'.
2. 17 Types of Nodes, Freely Combinable
Besides large model nodes, we have also built-in AI-native nodes like intent classification, JSON extraction, and text processing, as well as engineering nodes like code, HTTP requests, conditional branches, loops, and sub-workflows. AI is responsible for 'thinking,' code and HTTP are responsible for 'doing' — all handled on a single canvas.
3. Generate a Workflow with a Single Sentence
Built-in AI builder: describe the requirement in one natural language sentence, automatically generate a workflow draft, and then place it on the canvas for fine-tuning. From 'idea' to 'runnable process,' it only takes a few minutes.
4. One-Click Publish as Agent + Real Business Automation
After orchestration, you can publish it as an Agent with one click, and the conversation page directly supports multi-turn calls; each workflow can also generate a Webhook address, support Cron scheduled execution, with HMAC signature and rate limiting protection. It's not a demo; it can truly go into production.
4. Overall Architecture: Understand It at a Glance
First, the architecture diagram. We adopted a classic three-layer structure for the overall architecture design, but we customized each layer 'for AI workflows':
Reasons for a few key technology choices:**
**
1. Canvas uses FlowGram (same origin as Coze), the drag-and-drop and connection experience is enterprise-grade;**
**
2. SQLite is a low-cost local database solution that 'runs with a single command';**
**
3. SSE event stream allows the frontend to see the execution status of each node in real-time. Variables in the debug panel can be adjusted and configured at any time, and real-time preview is also possible, maximizing debugging efficiency.
5. Sharing the Complete Execution Flow
Now look at the flow chart. From trigger to result, there are six steps in total:
And the data flow between nodes looks like this (using placeholders to reference upstream output):
6. Core Implementation Breakdown (with Code)
Below are the three core code sections I think are most worth discussing, all simplified, but the logic is consistent with the real implementation.
1. Execution Engine: Topological Scheduling Advancing in 'Waves'
A workflow is not a single line, but a graph. We didn't use a simple 'whoever is first runs first' approach. Instead, we first count how many unfinished predecessors each node has (in-degree), and in each round, pick out the nodes whose 'predecessors are all completed' to form a wave, executing them in parallel:
server/src/engine/graph-scheduler.ts (simplified)
// Count the number of predecessor dependencies (in-degree) for each node
const indegree = countInputs(nodes);
// First wave: nodes with no predecessors (start)
let wave = nodes.filter(n => indegree[n.id] === 0);
while (wave.length) {
// Nodes within the same wave are independent of each other, execute in parallel
await Promise.all(wave.map(n => this.runNode(n)));
// Completed nodes decrement the in-degree of downstream nodes, pick out the next wave
wave = nodes.filter(n => !done(n) && indegree[n.id] === 0);
}
// Finished but still have unexecuted nodes? Indicates a cycle in the graph, throw an error directly
if (hasPending()) throw new Error('Workflow contains a cycle');
The benefits of doing this are very practical: The order is absolutely correct (a node only runs after all its upstream nodes have finished, so it won't read null values), parallelizable tasks automatically run in parallel (saving time and tokens), and it can also conveniently detect drawing mistakes like 'circular connections'.
2. Data Flow: {{ }} Template Interpolation
How is data passed between nodes? We support {{ expression }} in all string configurations, and the core is a replacement with path lookup:
server/src/engine/template.util.ts (simplified)
// "Order {{ input.order_id }} summary: {{ nodes.llm_1.output }}"
// When the entire string is a single expression, return the original value directly (preserving object/number types)
const single = template.match(/^\s*{{([^}]+)}}\s*$/);
if (single) return resolvePath(single[1], ctx);
// Otherwise, replace one by one: null becomes an empty string, objects are serialized to JSON
return template.replace(/{{([^}]+)}}/g, (_, expr) => {
const value = resolvePath(expr, ctx);
if (value == null) return '';
return typeof value === 'object' ? JSON.stringify(value) : String(value);
});
There's a detail we are quite proud of: if the entire string is a single expression, it will preserve the original type — if the upstream passes an array, the downstream loop node receives a real array, not a 'fake array' converted to a string.
3. Code Node: vm Sandbox + Timeout Protection
Letting users run custom code, safety is the top priority. We wrap the code into a function, throw it into Node.js's vm sandbox for execution, and add a timeout:
server/src/engine/node-executors.ts (simplified)
const sandbox = { input, nodes, variables };
const context = vm.createContext(sandbox, {
codeGeneration: { strings: false, wasm: false }, // Turn off dynamic code generation
});
const script = new vm.Script(
result = (function main(input, nodes, variables) {\n +
code + \n})(input, nodes, variables);
);
script.runInContext(context, { timeout }); // Timeout directly kills it
A piece of hard-coded infinite loop code can at most block itself for these few milliseconds, it cannot drag down the entire service. This is the confidence that allows our platform to open up the code node.
4. Debugging Session: Why a Breakpoint Can 'Pause' a Forward-Executing Process on the Server Side
Many people are curious about this. The answer is not complicated: every time the engine finishes executing a node, it asks the debugging session 'should I stop here?'. If yes, it suspends the execution context and waits for the frontend to send a 'continue/step' command to resume. Debugging is not magic; it's a series of 'checkpoints' buried in the engine's main loop.
Finally, extensibility: to add a new type of node, you only need to implement an executor function and register it with the engine, and add a form schema on the frontend. We deliberately kept the contribution barrier very low, welcoming community partners to add nodes.
7. Where Can It Be Used
· Intelligent Customer Service: The intent classification node first diverts traffic, then goes through different large model response branches;
· Content Production Line: Generation, polishing, and formatting are strung into an assembly line, producing batch output on a schedule;
· Order and Email Processing: Webhook receives business events, large model summarizes and extracts key points, then notifies the team;
· Data Inspection and Daily Reports: Cron triggers on schedule, pulls data, performs analysis, pushes reports, fully automated.
Within our own company, two flows—customer service diversion and daily report generation—have been running stably for several months.
✦ ✦
8. Written at the End
As an AI entrepreneur, I empathize with the anxiety of small teams: limited manpower, yet wanting to be both fast and stable. The open-source Smart Flow we built is both a small contribution to the community and a form of 'self-rescue' — the more people use it, the more problems are discovered and fixed, and the better the product will become.
The project has been fully open-sourced on GitHub, with documentation ready in Chinese, English, Japanese, and Korean. The application also includes a built-in development document to help everyone quickly get started and perform secondary development.
Project address: github.com/MrXujiang/smart-flow
If it helps you, a Star is welcome; for any ideas, Issues and PRs are always welcome.
Feel free to chat with me in the comments section too~
Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.
The thinking is very clear. I'm refactoring this exact part of the code right now, so I'll use this approach as a reference.
Sure.