跳到主要内容

数据截至 (上游 commit e741923f72c3)

Sim — 架构与原理

30 秒导读: Sim 是一个开源的 AI agent 工作空间。你在画布上拖块、连线,搭出一条「收到消息 → 让模型判断 → 调工具 → 回复」的 agent 工作流;也可以直接在聊天里用一句话让 AI 替你把这条流搭出来。本页讲全景:这些块最后是怎么变成一次真实执行的。 答案是一张带「哨兵节点」的 DAG,和一个用就绪队列 + 边激活/失活推进它的调度器。

路径约定: 本组文档所有源码路径都写成克隆根相对的完整路径(例 apps/sim/executor/execution/engine.ts:181)。行号锚定 frontmatter 里的 sourceCommit


1. 这是什么(零基础也能懂)

一句话定义: Sim 是一个自托管友好的 AI agent 工作空间——用可视化画布(或自然语言)编排「模型 + 工具 + 分支 + 循环」,再把它当成一个能被 HTTP、定时器、webhook 触发的服务跑起来。

给谁用、解决什么问题。 你想让 AI 干一件跨系统的活——比如「每天早上读 Gmail 里的新工单,让模型分类,重要的发到 Slack 并在 Notion 建一条记录」。纯写代码要处理鉴权、重试、日志、并发、断点续跑;纯 SaaS 自动化工具又不懂 LLM 那套(工具调用、流式、上下文)。Sim 想同时给你两样:编排的可视化LLM 原生的执行语义

它由几件东西组成:

部分干什么位置
画布(Workflows)拖块连线,块的位置与参数存成 Postgres 行apps/sim/app/workspace/apps/sim/stores/
执行引擎把画布编译成 DAG、调度执行、产出日志apps/sim/executor/
块注册表302 个块类型(Slack / Gmail / Agent / Loop…)apps/sim/blocks/registry-maps.ts:370 BLOCK_REGISTRY
工具层块背后真正发 HTTP 的那一层,3774 条工具定义apps/sim/tools/registry.ts:5542 tools
模型 Provider21 家 LLM 供应商的统一适配apps/sim/providers/registry.ts:31 providerRegistry
Copilot用自然语言让 AI 直接改画布、跑工作流apps/sim/lib/copilot/
实时协作服务独立的 Bun + Socket.IO 服务,多人同画布apps/realtime/

用起来什么样。 最小闭环只有三步:npx simstudio 起服务 → 浏览器里拖出「Start → Agent → Slack」三个块连起来 → 点 Run。点下去之后发生的事,就是本组文档要讲清楚的东西。

一句话直觉: 把画布当成电路图,把执行引擎当成跑这张电路图的仿真器——块是元件,线是导线。这个仿真器最有意思的地方在于:导线可以被「断电」,而断电会沿着下游一路传播。分支就是这么实现的(§5.2)。

仓库长什么样。 这是一个 bun + turbo 的 monorepo,根 package.json:7-10 声明 workspaces: ["apps/*", "packages/*"],共 4 个 app 目录 + 16 个 package 目录:

目录包名干什么
apps/simsim主应用:Next.js 页面 + API 路由 + 画布 + 执行引擎
apps/realtime@sim/realtimeBun + Socket.IO 服务,多人协作编辑画布
apps/docsdocs文档站
apps/pii@sim/piiPython/FastAPI 的 PII 识别脱敏服务,独立容器镜像
packages/db@sim/dbDrizzle schema + 客户端,94 张表
packages/workflow-types@sim/workflow-types纯类型:BlockState / Loop / Parallel
其余 13 个@sim/*auth、logger、utils、audit、security、platform-authz、workflow-persistence、realtime-protocol、runtime-secrets、testing、tsconfig、CLI(simstudio)、TS SDK

packages/python-sdk 是 Python 包,没有 package.json,不参与 JS 工作区构建。

一条硬边界: apps/realtime 刻意不依赖 Next.js、React、块/工具注册表和执行引擎——它只管协作光标和画布同步,不管跑工作流。这条边界由 CI 脚本 scripts/check-monorepo-boundaries.ts 强制。


2. 顶层全景

2.1 一次执行的数据形态变化

怎么读这张图: 从上到下是同一份工作流依次换的六种表示;每一站左边是产物,箭头旁写的是干这件事的函数

画布 · React Flow 你拖的块和线
│ 每次编辑发一条 socket op,一块一行落库

① 行存储 workflow_blocks / workflow_edges / workflow_subflows
│ Serializer.serializeWorkflow()

② 序列化工作流 SerializedWorkflow(纯 JSON:blocks / connections / loops / parallels)
│ DAGBuilder.build()

③ 执行图 DAG 每个节点带 incomingEdges / outgoingEdges;循环与并行摊平成哨兵节点
│ ExecutionEngine.run()

④ 调度 就绪队列 readyQueue,入边被消耗光的节点才可以跑
│ BlockExecutor → 16 个 handler 之一

⑤ 执行一个块 NormalizedBlockOutput(Agent / API / Function / Condition / Wait…)
│ EdgeManager.processOutgoingEdges()

⑥ 算出下一批就绪节点 ───▶ 回到 ④;直到无事可做,或撞上一个「暂停」

调度内核长这样(全篇最该记住的一张图):

┌────────────────────────────────────────────────┐
│ 就绪队列 readyQueue │
│ 入边都被消耗光的节点排在这里 │
└────────────────────────┴───────────────────────┘
│ 出队:队列里有几个就同时起几个

┌────────────────────────┬───────────────────────┐
│ 并发执行中的块 executing │
│ 不设并发上限,谁先跑完就先处理谁 │
└────────────────────────┴───────────────────────┘
│ 某个块产出 output

┌────────────────────────┬───────────────────────┐
│ EdgeManager.processOutgoingEdges │
│ - 该走的边: 划掉目标节点的这条入边 │
│ - 不该走的边: 标记失活,沿下游级联剪枝 │
│ - 入边被清空的目标: 成为新的就绪节点 │
└────────────────────────┴───────────────────────┘
│ 新就绪的节点
└──▶ 回到最上面的就绪队列

2.2 主干部件一句话职责

部件干什么文件 · 符号
画布React Flow 渲染块与边,编辑即改库apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
行存储每个块一行、每条边一行、每个容器一行packages/db/schema.ts:300 workflowBlocks:226 workflowEdges:258 workflowSubflows
Serializer行 / 前端状态 → SerializedWorkflowapps/sim/serializer/index.ts:159 serializeWorkflow
DAGBuilder序列化结构 → DAG(节点 + 入/出边 + 哨兵)apps/sim/executor/dag/builder.ts:52 build
ExecutionEngine就绪队列调度、并发追踪、暂停 / 取消收口apps/sim/executor/execution/engine.ts:181 run
EdgeManager决定哪条边激活、把失活沿下游级联剪枝apps/sim/executor/execution/edge-manager.ts:253 shouldActivateEdge
NodeExecutionOrchestrator分派:普通块交给 BlockExecutor,哨兵节点自己处理apps/sim/executor/orchestrators/node.ts:42
LoopOrchestrator / ParallelOrchestrator循环该不该再来一轮、并行分支怎么分批与汇总apps/sim/executor/orchestrators/loop.ts:69parallel.ts:43
BlockExecutor解析入参 → 找 handler → 归一化输出apps/sim/executor/execution/block-executor.ts:96 execute
VariableResolver把参数里的 <块名.字段>{{ENV}} 解析成真值apps/sim/executor/variables/resolver.ts:153
Handler 注册表16 类块处理器(agent / api / condition / wait / …)apps/sim/executor/handlers/registry.ts:33 createBlockHandlers
Provider 层统一各家 LLM 的请求 / 流式 / 工具调用apps/sim/providers/index.ts:164 executeProviderRequest
Tool 层按工具 ID 发出真实外部调用apps/sim/tools/index.ts:1513 executeTool
PauseResumeManager暂停落库、恢复时重建执行、排队的 resume 收尾apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts:490
LoggingSession块级开始 / 结束、trace span、最终落库apps/sim/lib/logs/execution/logging-session.ts:200

2.3 旁支一:触发面

主干只回答「怎么跑」,不回答「谁让它跑」。触发面有四条入口,全部收敛到同一个函数:

手动 Run / API POST ─┐
Chat 部署端点 ─┤
Webhook(外部回调) ─┼──▶ executeWorkflowCore() ──▶ §2.1 的主干
Schedule(定时) ─┘ 单一入口

webhook 与 schedule 走 trigger.dev 后台任务(apps/sim/background/webhook-execution.tsapps/sim/background/schedule-execution.ts:656),API 与 Chat 走 Next.js 路由(apps/sim/app/api/workflows/[id]/execute/route.tsapps/sim/app/api/chat/[identifier]/route.ts)。区别只在「谁把请求变成 ExecuteWorkflowOptions」,之后的路完全一样。336 条触发器声明在一张表里:apps/sim/triggers/registry.ts:482 TRIGGER_REGISTRY

2.4 旁支二:Copilot(Mothership)

这是「让 AI 替你搭工作流」的那半边。它不在本仓库里跑 LLM 主循环:

浏览器 Chat
│ SSE

本仓库客户端半边 远端 Go 服务(不在本仓库)
apps/sim/lib/copilot/request/* copilot.sim.ai
│ │
│ ─────── HTTP + SSE ─────────────▶ ├─ LLM 主循环
│ ◀────── 工具调用指令 ──────────── ├─ 上下文 / 子 agent 编排
▼ └─ 决定调哪个工具
按 catalog 的 route 分桶:sim / go / subagent / client

└─ route='sim' 的工具在本仓库执行(改画布、跑工作流、读知识库…)

远端地址默认值写死在 apps/sim/lib/copilot/constants.ts:3(SIM_AGENT_API_URL_DEFAULT = 'https://www.copilot.sim.ai'),实际解析走 apps/sim/lib/copilot/server/agent-url.ts:43 getMothershipBaseURL,出站请求统一经 apps/sim/lib/copilot/request/go/fetch.ts:29 fetchGo,SSE 消费循环在 apps/sim/lib/copilot/request/go/stream.ts:146 runStreamLoop

Copilot 也能反过来出现在工作流里:MothershipBlockHandler(apps/sim/executor/handlers/mothership/mothership-handler.ts:746)让一个块本身就是「叫 AI 来干活」。


3. 主线走一遍:按下 Run 之后

这一节只走高层链路,不进代码细节;每一站的「为什么」留给对应章节。

executeWorkflow ← 建 executionId、开日志会话、埋点、收口暂停状态

executeWorkflowCore ← 取工作流状态 + 环境变量 → 序列化 → 造 Executor

DAGExecutor.execute ← 建 DAG、还原快照、组装执行流水线

ExecutionEngine.run ← 就绪队列循环,直到没活干 / 出错 / 暂停 / 取消

第 1 站:executeWorkflow(apps/sim/lib/workflows/executor/execute-workflow.ts:92) 负责一次执行的「外壳」:生成 executionId、建 LoggingSession、把回调(onStream / onBlockStart / onBlockComplete)打包,跑完后发埋点,并调 handlePostExecutionPauseState(apps/sim/lib/workflows/executor/pause-persistence.ts:28)决定这次是「暂停了要存快照」还是「结束了要处理排队的恢复请求」。

第 2 站:executeWorkflowCore(apps/sim/lib/workflows/executor/execution-core.ts:376) 这是官方注释里说的 single source of truth,四件事顺序很重要:

  1. 取状态——三选一:显式覆盖、草稿表、已部署快照(loadWorkflowState,execution-core.ts:482)。
  2. 取环境变量——个人 + 工作区的加密变量,解密后进执行上下文。
  3. 定起点——没指定触发块时按执行种类(api / chat / external / manual)找起始块。
  4. 序列化 + 造执行器——new Serializer().serializeWorkflow(...)(:489)→ new Executor({...})(:694)→ executorInstance.execute(...)(:708)。

第 3 站:DAGExecutor.execute(apps/sim/executor/execution/executor.ts:87) Executor 只是 DAGExecutor 的别名(apps/sim/executor/index.ts:6)。这一站把序列化结构编译成 DAG——先算「从触发块出发能到哪些块」,再给每个循环 / 并行造一对哨兵,最后把连线翻译成节点上的 incomingEdges / outgoingEdges——然后还原快照里的并行批次与保存过的入边,在 buildExecutionPipeline(:346)里把六个部件按依赖串起来:VariableResolverBlockExecutorEdgeManagerLoop/ParallelOrchestratorNodeExecutionOrchestratorExecutionEngine,最后 return await engine.run(triggerBlockId)(:102)。

第 4 站:ExecutionEngine.run(apps/sim/executor/execution/engine.ts:181) 循环极简:只要还有活干(就绪队列非空,或还有在飞的执行)就继续调度(hasWork,:202);出现取消、错误、提前停止就跳出。

initializeQueue → while (hasWork) { processQueue } → 等所有在飞的执行落地

├─ 队列里所有节点全部并发发起
├─ 谁先完成,谁的出边就交给 EdgeManager 处理
└─ 处理结果 = 一批新就绪节点 → 入队

跑完后有四种归宿:正常返回、status: 'paused'(buildPausedResult,:493)、status: 'cancelled'、抛错。撞上暂停时,引擎把整张图当前的边状态打包成快照返回,由外层落进 paused_executions 表,等人或等时间到了再从快照恢复。


4. 阅读地图

4.1 六章顺序与各自解决什么问题

章节解决的问题读完你会知道
01 从画布到 DAG图形怎么变成可执行结构块为什么按行存、subBlocks 是什么、序列化和 DAG 编译各自做了哪些取舍
02 调度引擎谁先跑、谁不跑就绪队列如何判定 ready、条件 / 路由如何靠边失活实现、级联剪枝为什么能收敛
03 子流程与变量循环和并行怎么在一张平图里表达哨兵节点、并行分支克隆、<块名.字段> 引用怎么解析
04 Agent / Provider / 工具LLM 那一层怎么抽象Agent 块如何组装 prompt 与工具 schema、各家模型如何被统一、工具调用如何落地
05 暂停、恢复与触发一次执行怎么跨越请求边界活下来_pauseMetadata、快照序列化、paused_executions 表、四种触发入口
06 CopilotAI 怎么替你搭工作流远端 Go 大脑与本地客户端的分工、工具四路由、SSE 事件契约

4.2 分流建议

  • 只想懂执行引擎02 + 03。这两章是本项目工程密度最高的地方,也是最值得抄的部分。
  • 只想懂 agent 与工具 → 直接读 04,它对前面章节的依赖只有「块有 handler」这一个前提。
  • 想做二次开发 / 加集成01(块怎么定义)→ 04(工具怎么注册)。
  • 关心可靠性与运维05,顺带看 §6 的调度边界。
  • 对比其他 agent 框架的编排模型02 + 06:一个是「人画图 AI 执行」,一个是「AI 画图人监督」,Sim 罕见地两头都做。

想最快抓住精华:读本页 §2 的两张图 + §5,再直接看第 2 章。


5. 巧妙之处(可借鉴的技术)

5.1 哨兵节点:把子流程摊平进同一张 DAG

妙在哪: 循环和并行没有用「嵌套子执行器」实现,而是在编译期往同一张图里插入 sentinel-start / sentinel-end 两个虚拟节点,循环体的块就是它们之间的普通节点。于是调度器只需要认识「节点」和「边」,不需要认识「循环」这个概念。

哨兵由 LoopConstructor.createSentinelPair(apps/sim/executor/dag/construction/loops.ts:24)成对生成,节点本身由 createSubflowSentinelNode(apps/sim/executor/dag/construction/sentinels.ts:14)伪造成一个普通 SerializedBlock。运行时 NodeExecutionOrchestrator 看到 node.metadata.isSentinel 就分流到哨兵逻辑(apps/sim/executor/orchestrators/node.ts:88:122 handleSentinel),由它决定这一轮是「再来一次」还是「退出」,并通过 selectedRoute 反馈给边层。编译期的结构校验在 apps/sim/executor/dag/builder.ts:146 validateSubflow

5.2 边失活级联:用剪枝代替分支状态机

妙在哪: 条件块和路由块不维护任何「当前处于哪个分支」的状态。它们只输出一个 selectedOption / selectedRoute,剩下的全交给边:

  • shouldActivateEdge(apps/sim/executor/execution/edge-manager.ts:253)按边的 sourceHandle 前缀判断这条边该不该激活——condition-*router-*errorsource、循环控制边各有规则。
  • 没被激活的边进入 deactivateEdgeAndDescendants(:301):标记失活,然后沿下游递归传播,直到遇到「还有活跃入边」或「已经收到过激活边」的节点才停。

一个节点是否就绪,判据是 isNodeReady(:110):入边集合为空,或者活跃入边数为 0(countActiveIncomingEdges,:369)。后半句正是级联剪枝的收益——被剪掉的上游不会让汇聚节点永远等下去。

5.3 反向控制边不参与就绪判定

循环回边(loop_continue)在图上确实是一条指回起点的边,却不破坏拓扑推进——因为剪枝和就绪计算里专门把这类 handle 排除掉了(apps/sim/executor/constants.ts:92 CONTROL_BACK_EDGE_HANDLES,判定见 edge-manager.ts:237 isBackwardsEdge)。用一个 handle 名字的集合,划出「控制流边」和「数据流边」的界限,省掉了一整套环检测。

同理,边该不该走是块输出的一个纯函数:所有分支语义集中在 shouldActivateEdge 一个不到 50 行的判定里,handle 常量表在 constants.ts:63 EDGE新增一种分支块 = 让它的 output 带上对应字段,调度器一行都不用改。

5.4 并行不是「同一个节点跑 N 次」,而是把子图物理克隆 N 份

并行分支在 DAG 上是真的多出 N 组节点,id 用下标括号编码(₍0₎₍1₎…),由 ParallelExpander.expandParallel 生成(apps/sim/executor/utils/parallel-expansion.ts:36,id 编解码集中在 apps/sim/executor/utils/subflow-node-id-codec.ts)。代价是节点数膨胀,收益是每个分支的状态、日志、变量作用域天然隔离,调度器一行特判都不用写。

别读错这个常量: DEFAULTS.MAX_PARALLEL_BRANCHES: 20(apps/sim/executor/constants.ts:179)名字叫 branches,实际只 clamp 批大小——唯一的生产调用点是 resolveBatchSize(apps/sim/executor/orchestrators/parallel.ts:271-278),前端侧对称的 clampParallelBatchSize 也是夹 batchSize(apps/sim/stores/workflows/workflow/utils.ts:11)。分支数本身没有上限:resolveBranchCount(parallel.ts:185-201)直接取 config.count 或集合长度,不做任何截断。所以正确的说法是「并行批大小上限 20」,不是「最多 20 路分支」。

5.5 工具四路由:一个工具 ID 决定它在哪儿执行

Copilot 的工具目录是代码生成的(apps/sim/lib/copilot/generated/tool-catalog-v1.ts,由 scripts/sync-tool-catalog.ts 从 Go 侧的契约 JSON 生成),每个条目带一个 route 字段,取值是 'client' | 'go' | 'sim' | 'subagent'(:206),95 个工具各自归位(sim 72 / subagent 12 / go 7 / client 4)。

分发只是一次查表:routeToolCall(apps/sim/lib/copilot/tool-executor/router.ts:19)和 partitionToolBatch(:50)把一批工具调用按 route 分桶——本仓库执行的、远端 Go 执行的、交给子 agent 的、必须回到浏览器执行的。

5.6 暂停即快照,而且存的是「整张边状态」

人工审批、等待、定时唤醒这类「要等外部世界」的块不占着进程等。块的输出里带一个 _pauseMetadata,引擎看到就把这次执行标记为暂停(apps/sim/executor/execution/engine.ts:536),把整份执行态序列化成快照(buildPausedResult,:493serializePauseSnapshot,apps/sim/executor/execution/snapshot-serializer.ts:186),存进 paused_executions 表(packages/db/schema.ts:612,写入由 PauseResumeManager.persistPauseResult 完成,apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts:491)。

关键在于快照存的不是「跑到哪了」,而是「调度器当时的整张边状态」: 每个节点剩余的入边(dagIncomingEdges,snapshot-serializer.ts:202)、所有失活边、以及收到过激活边的节点集合(:225)。恢复时把这三样灌回新建的 DAG(apps/sim/executor/dag/builder.ts:83apps/sim/executor/execution/executor.ts:374 restoreDeactivatedEdges),调度器就能在另一个进程、另一个 HTTP 请求里从中断处接着算。光存块输出是不够的,分支剪枝的历史也必须存

5.7 函数块的引用不内联成字面量,而是注入成上下文变量

如果用户在 Function 块里写 <agent1.content>,朴素做法是把上游那段(可能几十 KB 的)文本直接拼进代码字符串——既是注入面,也是体积灾难。Sim 改成:块引用存成 __blockRef_0 这样的具名变量传给沙箱,只有循环项、工作流变量、环境变量这类小值才内联(apps/sim/executor/variables/resolver.ts:425 resolveCodeWithContextVars)。

5.8 同一条主干,两种工作流状态

「在编辑器里点 Run」和「线上被 webhook 打到」跑的是同一条代码路径,差别只是 useDraftState 这一个布尔——读草稿表还是读已部署快照(apps/sim/lib/workflows/executor/execution-core.ts:482 loadWorkflowState)。调试与生产不分叉,是这套执行器能只维护一份的关键。


6. 边界与局限

Copilot 的大脑不在这个仓库里。 本仓库只有客户端半边:发请求、解析 SSE、路由并执行 route: 'sim' 的工具。LLM 主循环、上下文管理、子 agent 编排都在远端 Go 服务(默认 https://www.copilot.sim.ai,见 apps/sim/lib/copilot/constants.ts:3)。自托管时这半边默认仍指向 SaaS 端点——要完全离线用 Copilot,代码里看不出现成方案。

Debug 模式没实现。 DAGExecutor.continueExecution(apps/sim/executor/execution/executor.ts:107-123)直接打一条 warn 并返回失败,错误信息就是 'Debug mode is not yet supported in the refactored executor'。单步调试目前只能靠日志和 run-from-block。

调度是进程内的,而且不设并发上限。 就绪队列是一个普通数组(apps/sim/executor/execution/engine.ts:32 private readyQueue: string[]),并发追踪是一个 Set<Promise>——一次扇出 50 个块就同时发 50 次调用。一次执行不跨机器、不跨进程:水平扩展靠「多个后台任务各跑各的执行」(apps/sim/background/*.ts),跨请求存活靠快照(§5.6),而不是分布式调度器。

并行的 20 不是分支数上限。 见 §5.4 的提醒——它只 clamp 批大小。想限制真实并发,只能靠图的形状。

有一个名字骗人的空函数。 Serializer.validateSubflowsBeforeExecution(apps/sim/serializer/index.ts:212-219)签名和注释都像在做子流程校验,函数体只有一行注释、不做任何事——空集合的循环 / 并行改成在运行时跳过了。看代码时别把它当成校验点。

块与工具的量级带来另一类成本。 302 个块、3774 个工具全部静态注册在两张大表里(apps/sim/blocks/registry-maps.ts:370apps/sim/tools/registry.ts:5542)。仓库因此提供了 apps/sim/tools/registry.minimal.ts 变体和 dev:full:minimal-registry 脚本来缩短本地启动时间——这是它自己承认的重量。


7. 横向对比(同 shelf 的兄弟做法)

Sim 落在 area: workflow-builder 这一格。总库把这一派的分界线画在「图 JSON 怎么变成能跑的东西」上,见总库导航 ../index.md 与流派综述 ../guide/agent-products.md 的「流派一:拖拽搭流程的可视化平台」。

项目图怎么被执行分支 / 循环怎么表达与 Sim 最大的不同
sim(本篇)编译成一张平的 DAG,进程内就绪队列推进循环 / 并行编译期摊平成哨兵节点 + 控制边;分支靠边失活级联没有分支状态机,也没有嵌套子执行器——运行时只认「节点和边」
activepieces工作流是一棵 trigger → action 递归树,引擎沿链表走分支 / 循环就是树上的嵌套节点编辑侧用一组纯函数 reducer(flowOperations.apply)在前后端跑同一份逻辑改树;Sim 的编辑侧是行级 socket op
langflowGraph 顶点 + 边,astep 按层异步推进引擎直接支持环与条件剪枝组件类和画布节点是同一样东西(类的 Input/Output 反射成 UI);Sim 的块定义与工具定义是两张独立注册表
dify图调度器被抽成外部包 graphon,Dify 自己守节点工厂 + 队列流水线由外部图引擎负责Sim 的调度器是自研且内嵌的,可暂停能力做在引擎里而非队列层
kestraYAML 声明的 Flow,队列驱动的无状态 Executor 状态机显式状态机一步步推进任务Kestra 天生跨进程 / 可集群(队列与仓储接口可插拔);Sim 的调度是进程内的,跨请求靠快照(§6)
rivetGraphProcessor,**拉取式(pull)**数据流分支 / 循环 / 竞速靠 control-flow-excluded 这个特殊数据类型在图里传播思路和 Sim 的「边失活」最接近——都用「传播一种排除标记」代替状态机,只是 Rivet 传在数据上,Sim 传在

一句话取舍: 同派项目都把编排逻辑从代码变成了数据(图 JSON),区别在于运行时到底认识几种结构。Sim 把「循环 / 并行 / 分支」全部编译成「节点 + 边」,换来一台只有一种概念的调度器;代价是节点 ID 的编码规则(₍N₎__obranch-N)变复杂,以及一堆为剪枝正确性打的补丁(见 02 章 §6.4)。


8. 代码地图(导航索引)

按符号名 grep 比按行号更抗上游漂移;下表给的是该打开的文件和该搜的符号。所有引用 as-of 38c088a8

主题文件路径关键符号
执行入口(外壳、日志会话、埋点)apps/sim/lib/workflows/executor/execute-workflow.tsexecuteWorkflowExecuteWorkflowOptions
执行核心(取状态、序列化、造执行器)apps/sim/lib/workflows/executor/execution-core.tsexecuteWorkflowCoreloadWorkflowStatefinalizeExecutionOutcome
执行器别名apps/sim/executor/index.tsExecutor(= DAGExecutor)
DAG 执行器与流水线组装apps/sim/executor/execution/executor.tsDAGExecutor.executebuildExecutionPipelineexecuteFromBlockcreateExecutionContext
调度主循环apps/sim/executor/execution/engine.tsExecutionEngine.runprocessQueuehasWorkinitializeQueuehandleNodeCompletionbuildPausedResult
边激活 / 失活 / 剪枝apps/sim/executor/execution/edge-manager.tsprocessOutgoingEdgesshouldActivateEdgedeactivateEdgeAndDescendantsisNodeReadycountActiveIncomingEdgesrestoreDeactivatedEdges
边 handle 与执行常量apps/sim/executor/constants.tsEDGEBlockTypeSUBFLOW_CONTROL_EDGE_HANDLESCONTROL_BACK_EDGE_HANDLESREFERENCEDEFAULTS
DAG 编译总入口apps/sim/executor/dag/builder.tsDAGBuilder.buildvalidateSubflowDAGNodeDAG
DAG 五个构造器apps/sim/executor/dag/construction/PathConstructorLoopConstructorParallelConstructorNodeConstructorEdgeConstructor
哨兵节点apps/sim/executor/dag/construction/sentinels.tscreateSubflowSentinelNode
哨兵 / 分支节点 id 编解码apps/sim/executor/utils/subflow-node-id-codec.tssubflow-utils.tsSubflowNodeIdCodecbuildSentinelStartIdbuildBranchNodeIdstripCloneSuffixesfindEffectiveContainerId
节点分派(普通块 vs 哨兵)apps/sim/executor/orchestrators/node.tsNodeExecutionOrchestrator.executeNodehandleSentinelhandleParallelSentinel
循环编排apps/sim/executor/orchestrators/loop.tsLoopOrchestratorinitializeLoopScopeevaluateLoopContinuationclearLoopExecutionStaterestoreLoopEdges
并行编排与分支克隆apps/sim/executor/orchestrators/parallel.tsapps/sim/executor/utils/parallel-expansion.tsParallelOrchestratorresolveBranchCountresolveBatchSizeprepareCurrentBatchaggregateParallelResultsParallelExpander.expandParallel
单块执行与错误端口apps/sim/executor/execution/block-executor.tsBlockExecutor.executefindHandlernormalizeOutputhandleBlockErrorhasErrorPortEdge
块状态apps/sim/executor/execution/state.tsExecutionStatesetBlockOutputgetScopedBlockOutput
块处理器注册(16 类)apps/sim/executor/handlers/registry.tscreateBlockHandlers
Agent 块apps/sim/executor/handlers/agent/agent-handler.tsAgentBlockHandlerformatToolsbuildMessages
变量与引用解析apps/sim/executor/variables/resolver.tsvariables/resolvers/VariableResolver.resolveInputsresolveSingleReferenceresolveCodeWithContextVarsLoopResolverParallelResolverBlockResolverEnvResolverWorkflowResolver
序列化apps/sim/serializer/index.tsserializer/types.tsSerializer.serializeWorkflowextractBlockParamsselectToolIdSerializedWorkflowSerializedBlock
块注册表(302 种)apps/sim/blocks/registry-maps.tsblocks/registry.tsBLOCK_REGISTRYBLOCK_META_REGISTRYgetBlockgetLatestBlock
工具注册表(3774 条)apps/sim/tools/registry.tstools
工具执行主管线apps/sim/tools/index.tsexecuteToolexecuteToolRequestexecuteMcpToolinjectHostedKeyIfNeeded
模型 Provider 抽象(21 家)apps/sim/providers/index.tsproviders/registry.tsexecuteProviderRequestMAX_TOOL_ITERATIONSproviderRegistrygetProviderExecutor
暂停快照序列化apps/sim/executor/execution/snapshot-serializer.tssnapshot.tsserializePauseSnapshotExecutionSnapshot
暂停落库 / 恢复排队apps/sim/lib/workflows/executor/human-in-the-loop-manager.tspause-persistence.tsPauseResumeManagerpersistPauseResultenqueueOrStartResumeprocessQueuedResumeshandlePostExecutionPauseState
触发器注册表(336 条)apps/sim/triggers/registry.tsTRIGGER_REGISTRY
后台执行任务apps/sim/background/schedule-execution.tswebhook-execution.tsexecuteScheduleJobexecuteWebhookJob
执行日志会话apps/sim/lib/logs/execution/logging-session.tsLoggingSession.safeStartsafeCompleteonBlockComplete
Copilot 远端地址与出站apps/sim/lib/copilot/constants.tsserver/agent-url.tsrequest/go/fetch.tsSIM_AGENT_API_URL_DEFAULTgetMothershipBaseURLfetchGo
Copilot 流式与工具路由apps/sim/lib/copilot/request/go/stream.tstool-executor/router.tsrunStreamLooprouteToolCallpartitionToolBatchisSimExecuted
Copilot 工具目录(生成物,95 条)apps/sim/lib/copilot/generated/tool-catalog-v1.tsTOOL_CATALOGToolCatalogEntry
AI 改工作流(操作引擎)apps/sim/lib/copilot/tools/server/workflow/edit-workflow/applyOperationsToWorkflowStateEditWorkflowOperationlintEditedWorkflowState
画布组件apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsxReactFlow 画布根组件
协作:操作入口 / 落库 / 权限apps/realtime/src/handlers/operations.tsdatabase/operations.tsmiddleware/permissions.tssetupOperationsHandlerspersistWorkflowOperationcheckWorkflowOperationPermission
数据模型(94 张表)packages/db/schema.tsworkflowBlocksworkflowEdgesworkflowSubflowsworkflowExecutionLogsworkflowExecutionSnapshotspausedExecutionsresumeQueue