## 核心对象：Plan、Binding、Invocation、Run

<!-- slot: 目标 -->
第一篇用业务语言介绍了 Workflow 的价值与生命周期。本章把镜头拉近，逐个讲清四个一等对象——**Plan**、**Binding**、**Invocation**、**Run**——以及图里的 **Node**。读完你能准确说出每个对象是什么、存什么、和其它对象什么关系，并避开几类最常见的混淆。

<!-- slot: 边界 -->
本章讲对象的**结构与关系**，不展开各节点类型的配置（可靠流程设计篇）、运行失败与恢复（可靠流程设计篇）、或外部系统对接（后续篇章）。

<!-- slot: 提示词 -->
本章不涉及从提示词创建 Workflow。

<!-- slot: 流程 -->
四个对象按下面的链路协作：

- **Plan**：可复用的编排逻辑，带版本历史。它定义输入契约、状态，以及一张由 Node（编排单元）和边组成的图。Plan 只描述“做什么”，不包含触发计划或运行身份。
- **Binding**（UI 称“触发器”）：可选的持久化部署，按计划（`cron` / `interval` / `absolute` / `relative`）自动产生 Invocation。一个 Plan 可以没有 Binding（只手动运行），也可以有多个。
- **Invocation**：一次被接受的、不可变的调用请求。手动运行或触发器到期都会产生一次 Invocation。
- **Run**：执行某次 Invocation 快照的运行实例，持有 Node 激活、等待、效果与终态。

要点：Plan 只定义“做什么”；Binding 负责“何时自动做”；一次 Invocation 对应一次 Run；**手动运行不会凭空合成一个 Binding**。

**常见混淆要先避开**：Plan 与 UI 里的“Workflow”是同一个东西（代码名叫 Plan，用户面叫 Workflow）；Binding 与 Scheduled Message 不是一回事——Binding 到点启动一整张 Workflow 的图，Scheduled Message 到点只发一条消息；Node 是图里的**编排单元**，不是“步骤/Step”，也不要把它和四个一等对象并列（Node 是 Plan 图的组成部分，不是独立的一等对象）。

再从生命周期看四个对象的关系：Plan 被创作、校验、发布，得到一个不可变版本；Binding 可选地挂在某个 Plan 版本上、按计划触发；每次触发或手动运行产生一次 Invocation；Run 消费这次 Invocation 指向的版本快照，走到终态。Run 不持有 Binding 的字段——它只认 Invocation 带来的版本。这解释了几个常见问题：删掉一个 Binding 不会影响已经发起的 Run；发布 Plan 新版本不会改变旧 Run 的行为；手动运行不会凭空造出一个 Binding。理解了这层隔离，后续排障时就不会把“改了配置”和“旧运行变了”混为一谈。

把每个对象“存什么”也记清楚，排障时才看得懂运行视图。Plan 存的是图的定义：输入与状态的契约、一组 Node、连接 Node 的边，以及它的版本历史；你改的是 Plan 的草稿，发布后才产生不可变版本。Binding 存的是“把哪个 Plan 版本、按什么计划、在什么范围内触发”，它是可选的——一个没有任何 Binding 的 Plan 只能手动运行。Invocation 存的是一次被接受的请求：它指向一个具体的 Plan 版本快照、带着这次调用的输入和发起者，并且一旦被接受就不再改变。Run 存的是执行过程：每个 Node 的激活状态、它产生的效果（发出的消息、请求的审批）、当前在等什么，以及最终的 `succeeded` / `failed` / `canceled`。所以，看到某个 Run 卡住时，你要查的是 Run 的激活与效果；看到某个触发器没按时跑时，要查的是 Binding 的计划与启用状态——两者查的地方不同，不要找错对象。

还有一类容易混淆的：Team Plan。前面说的 Plan 默认是工作空间里普通的 Plan；Team Plan 是团队拥有、可复用的 Plan，发布前要经团队 Owner 审批——这是“发布层面”的审批，和运行时图里的 `request_approval` 节点是两回事。两者背后的运行机制完全一样（都是 Plan / Invocation / Run），Team Plan 只是在发布准入上多了一道关。读后续章节时，把“发布审批”和“运行时审批”分开记，就不会把两个审批混成一个。

<!-- slot: 配置 -->
一个 Plan 的 YAML 结构如下（关键字段以 `loop.plan/v1` 为准；新书示例优先用 `scope` 类型）：

```yaml
apiVersion: loop.plan/v1
kind: PlanVersion
input:
  destination: {type: scope, required: true}
graph:
  entry:
    nodeId: greet
    port: in
    input:
      destination: {ref: plan.input.destination}
  nodes:
    - id: greet
      type: send_message
      input:
        destination: {type: scope, required: true}
      sendMessage:
        scope: {ref: input.destination}
        text: hello
        completion: sent
    - id: done
      type: end
      end: {outcome: succeeded}
  edges:
    - id: sent
      source: {nodeId: greet, port: completed}
      target: {nodeId: done, port: in}
```

顶层 `input` 是 Plan 的输入契约；`graph.entry` 把输入注入到某个 Node 的端口；`graph.nodes` 是 Node 列表，每个 Node 有 `id`、`type` 和对应的配置块；`graph.edges` 连接 Node 的端口。

节点里能引用什么？表达式按命名空间根解析：`input.*` 是**节点局部**的，必须在 Node 的 `input:` 契约里声明、并由 `entry.input` 或边 `bindings` 传入；而 `state.*`（运行态）、`plan.input.*`（Plan 级输入）等是**其它合法根**，不需要复制进节点 input。此外在特定作用域还允许 `loop.*`（循环体内）、`item` / `items`（`fan_out` / `join`）、以及节点完成后的部分 `output.*`。所以不要以为“所有引用都必须先塞进节点 input”——只有节点自己的 `input.*` 才需要。

举一个对照：一个 `send_message` 节点要把消息发到“本次调用的目标频道”，有两种合法写法。第一种是在节点 `input:` 里声明 `destination`、由 `entry.input` 绑定 `plan.input.destination`，节点里用 `input.destination`，并可以沿边透传给下游节点。第二种是跳过节点 input，直接在配置里写 `plan.input.destination`——它是 Plan 级引用，无需声明。两者都合法，差别只在于你是否需要把这个值继续传给后续节点。

顺带区分两个容易混的“状态”：写在 Plan 顶层 `input` / `state` 里的是**契约**——字段名、类型、是否必填，它们是 Plan 定义的一部分，发布后随版本一起固化；而运行时 `set_variable` 改的是某一次 Run 里的**状态值**，不会改变 Plan 的契约。所以“Plan 声明了哪些状态字段”和“某次 Run 里这些字段当前是什么值”，是两个层面的东西，排障时不要看混。

<!-- slot: 异常 -->
本章不展开运行失败处置；对象在异常下的行为（等待、回调拒绝、终态）在可靠流程设计篇说明。

<!-- slot: 审批 -->
本章不展开 `request_approval`；它是一个运行时 Node，在可靠流程设计篇详述。这里只需记住：运行时审批是图里的一个 Node，它会暂停 Run、等人决策，再按决定走向不同分支，因此它改变的是某一次 Run 的路径，而不是 Plan 本身。

<!-- slot: 外部系统 -->
本章不展开外部系统对接；Workflow 通过 `send_message` 与 Agent 协作，外部能力由 App + Extension 提供给 Agent。

<!-- slot: 验收 -->
- [ ] 能说出 Plan、Binding、Invocation、Run 各自的定义与职责。
- [ ] 能解释为什么 Plan 不包含触发计划，以及手动运行为什么不合成 Binding。
- [ ] 能避开常见混淆：Plan = Workflow、Binding ≠ Scheduled Message、Node 是编排单元而非“步骤”。
- [ ] 能看懂一个 Plan 的 YAML：`input` / `graph.entry` / `graph.nodes` / `graph.edges`。
- [ ] 能区分节点局部 `input.*`（需契约 + 绑定）与 `state.*` / `plan.input.*` 等其它合法引用根。

<!-- slot: 媒体槽位 -->
本章不新增媒体；生命周期图见上一章。
