第 1 章:从自动化到智能化
我遇到了什么问题
2025 年初,我想做一个本地的"扣子"。
为什么是"本地"?因为 Coze 很好,但数据要上传到云端。对于企业用户来说,这不可接受——你的客户数据、内部流程、账号密码,都要经过别人的服务器。
我想做的是:同样的拖拽编排体验,但跑在你自己的电脑上,数据不出本机。
这个想法听起来简单,但一上来就面临一个选择:从头造一个,还是站在别人肩膀上?
我做了什么决策
第一个决策:前端不造轮子,直接用扣子
做本地扣子,第一件事是画布——用户拖拽编排流程的那个界面。
这个轮子太大了。节点拖拽、连线、参数配置、实时预览……一套完整的可视化编辑器,光前端就得干几个月。
我的判断是:画布是体验层,不是核心壁垒。Coze 的画布已经做得很好了,没必要重写。
所以第一个决策很明确——前端直接用扣子。用户在 Coze 上画好工作流,导出 JSON 定义,这就是"前端"给我的全部东西。
用户在扣子画布上拖拽编排
↓ 导出
工作流 JSON 定义
↓ 交给
本地后端执行这个决策的核心逻辑是:我要解决的问题不是"怎么画流程图",而是"怎么在本地跑流程图"。 画布是入口,执行才是价值。
第二个决策:后端用 C#,嵌入式架构
前端定了,接下来是核心问题:后端怎么接住 Coze 导出的 JSON,在本地把它跑起来?
我选了 C#。原因不是"C# 最好",而是:
- 我的技术栈是 .NET,效率最高
- 需要嵌入式运行(不依赖外部服务)
- 需要跟 Windows 桌面端深度集成
后端的核心任务只有一个:把 Coze 的工作流 JSON 变成可执行的程序。
我选了 WorkflowCore 作为工作流引擎。原因很简单:
- 轻量,嵌入式运行,不需要额外部署
- 支持 SQLite 持久化,适合本地场景
- 支持暂停/恢复/中断,适合长时间运行的流程
但 WorkflowCore 本身是个"裸引擎"——它只负责按定义执行流程,不管流程定义从哪来,不管执行状态怎么查,不管节点怎么扩展。
我做的事情是给它包了一层,让它能"听懂" Coze 的 JSON:
csharp
public interface IWorkflowService
{
// 注册工作流定义(从 Coze JSON 解析 → 引擎可执行)
Task<WorkflowRegisterResult> RegisterDefinitionAsync(CozeWorkflowDefinition definition);
// 启动工作流(传入定义ID + 输入数据 → 返回实例ID)
Task<WorkflowStartResult> StartWorkflowAsync(
string workflowDefineId, string version,
Dictionary<string, object> input, ExecuteConfig config = null);
// 暂停/恢复/停止(控制正在运行的工作流)
Task<WorkflowResult> SuspendWorkflowAsync(string workflowId);
Task<WorkflowResult> ResumeWorkflowAsync(string workflowId);
Task<WorkflowResult> TerminateWorkflowAsync(string workflowId);
// 单节点调试(绕过工作流,直接跑一个节点)
Task<NodeDebugResult> InvokeNodeDebugDirect(string nodeConfig, Dictionary<string, object> input);
}这层包装解决了一个核心问题:Coze 的 JSON 格式和 WorkflowCore 的执行模型之间,需要一个翻译层。

扣子画布导出 JSON → 翻译层解析 → WorkflowCore 注册 → 本地执行第三个决策:桌面端用 WPF,一体化交付
后端有了,怎么交付给用户?
我的场景是"本地运行"——不是 SaaS,不是 Docker,是用户装在自己电脑上就能用。
WPF 是最直接的选择:
- Windows 原生,不需要额外运行时
- 可以内嵌 WebView2 来加载 Coze 画布
- 可以直接调用本地后端服务
- 打包成一个 .exe,双击就能用
整体架构就变成了:

┌─────────────────────────────────────────┐
│ WPF 桌面客户端 │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ WebView2 │ │ C# 后端服务 │ │
│ │ 加载扣子画布 │ │ WorkflowCore │ │
│ │ 用户拖拽编排 │ │ 触发器引擎 │ │
│ │ 导出 JSON │──│ 代码运行器 │ │
│ └─────────────┘ └──────────────────┘ │
│ ↑ ↓ │
│ └──── SQLite 持久化 ────────────┘│
└─────────────────────────────────────────┘前端是扣子的画布(WebView2 内嵌),后端是 C# 的执行引擎,中间用 JSON 对接。 用户感知到的是"跟 Coze 一样的体验",但数据全程在本地。
先搭原型,验证核心链路
架构定了,我没有一上来就写完整系统。先搭原型,验证最关键的那条链路:扣子 JSON → 本地引擎 → 跑通。
原型只做三件事:
- 能导入 Coze 导出的工作流 JSON
- 能在本地启动这个工作流
- 能看到执行结果
其他所有东西——触发器、代码沙箱、版本管理、错误处理——原型阶段都不做。
原型验证通过了,才往下走。
原型验证通过后,长成了什么样
原型跑通之后,开始补全自动化的三件套:工作流引擎 + 触发器 + 代码运行器。
触发器:让流程自己跑起来
有了工作流引擎,第二个问题是:谁来启动工作流?
用户手动点"运行"当然可以,但自动化的价值就在于"不用人点"。
我设计了 7 种触发器:

csharp
public enum TriggerType
{
Schedule = 1, // 定时触发(Cron 表达式)
FileWatch = 2, // 文件变化触发(监听目录)
Socket = 3, // Socket 消息触发
Hotkey = 4, // 快捷键触发
Clipboard = 5, // 剪贴板变化触发
System = 6, // 系统事件触发
WebHook = 7, // HTTP 回调触发
Custom = 99 // 自定义触发
}这些触发器的设计原则是无状态——触发器本身不存数据,它只负责"检测到条件满足 → 发出事件 → 启动对应的工作流"。
触发器检测到事件 → 查找绑定的工作流 → 传入事件数据 → 启动工作流比如"每天早上 8 点从数据库拉数据生成报表"这个场景:
- 用户创建一个 Schedule 触发器,配 Cron 为
0 8 * * * - 用户在扣子画一个工作流:数据库查询 → 数据处理 → 生成 Excel → 发邮件
- 绑定触发器到工作流
- 每天 8 点,触发器发出事件,工作流自动启动
代码运行器:让流程能执行任意代码
工作流中的节点大部分是预定义的——HTTP 请求、数据转换、条件判断。但用户总会遇到"现有节点不够用"的情况,需要自己写代码。
这就是 CodeRunner 的价值:在工作流节点里安全地执行用户代码。
csharp
public interface ICodeRunnerService
{
// 执行命令(基础版:传入命令字符串,返回结果)
Task<ExecutionResult> ExecuteAsync(
RuntimeType runtimeType, string command, ExecutionOptions? options = null);
// 沙箱执行(隔离环境:传入沙箱句柄 + 脚本,返回结果)
Task<SandboxResult> ExecuteInSandboxAsync(
SandboxEnvironment environment);
}关键设计是沙箱隔离。用户代码不能访问宿主机的文件系统,不能访问网络,不能影响主进程。每种语言(Python、Node.js、PowerShell)有自己的沙箱环境。
三件套的协作

把三个模块放在一起看,自动化的全貌就清楚了:
触发器(什么时候跑)
↓ 触发
工作流引擎(怎么跑)← 扣子 JSON 定义
↓ 执行节点
代码运行器(节点里跑什么)
↓ 返回结果
下一个节点...一个完整的例子:
场景:每天 8 点,从数据库拉订单数据,用 Python 脚本做分析,生成报表发邮件。
- 扣子画布:用户拖拽画出
SQL查询 → Python分析 → 邮件发送的工作流- 导出 JSON:工作流定义存到本地 SQLite
- Schedule 触发器:
0 8 * * *,每天 8 点触发- 本地执行:WorkflowCore 加载定义,Python 节点通过 CodeRunner 在沙箱里执行
数据全程在本地,不经过任何云端。
回过头看这几个决策
回头看这个阶段的架构,有几个决策值得复盘:
决策 1:前端不造轮子
如果当时选择自己写画布,光前端就得干 3 个月,还不一定做得好。用扣子导出 JSON 的方式,让我把 100% 的精力放在执行引擎上——这才是真正的壁垒。
核心判断:区分"体验层"和"能力层"。体验层能用现成的就用,能力层必须自己建。
决策 2:嵌入式而非微服务
没有搞"前端一个服务、后端一个服务、数据库一个服务"的微服务架构。所有东西跑在一个进程里:WPF 宿主 + WebView2 画布 + C# 后端 + SQLite 存储。
核心判断:本地工具不需要分布式架构。一个 .exe 能解决的问题,不要拆成五个服务。
决策 3:先原型后系统
没有一上来就设计"完整架构"。先验证"扣子 JSON 能不能在本地跑通"这个核心假设,验证通过了再补全触发器、代码运行器、版本管理。
核心判断:架构不是设计出来的,是长出来的。先证明路走得通,再修路。
关键认知

这个阶段做完,我有了第一个关键认知:
自动化是"人告诉机器每一步怎么走",智能化是"人告诉机器要去哪里"。
工作流引擎是确定性的——你画好流程图,它按图执行。每一步做什么、走哪个分支、什么时候停,都是你预先定义好的。
但真实场景中,很多事情是"不确定"的:
- 用户说"帮我查一下最近的订单"——"最近"是多久?"查"是查数据库还是查 API?
- 用户说"把这个文件整理一下"——"整理"是重命名?分类?还是压缩?
- 用户说"写一封回复邮件"——回复什么内容?语气怎么样?
这些问题,工作流引擎回答不了。因为它需要你预先定义所有分支,而"用户意图"的分支是无穷的。
这就是下一章节要解决的问题:当工作流遇到 LLM。