## 用提示词创建 Workflow

<!-- slot: 目标 -->
上一章你用六问法把需求想清楚，并写成了一段结构化的提示词。本章把这段提示词交给 Loop，生成一个 Plan 草稿，并带你读懂它：输入是什么、有哪些 Node、它们怎么连。读完你能看懂一份生成的 Plan 草稿，并判断它是否贴合你的需求。

<!-- slot: 边界 -->
本章覆盖从提示词到生成并读懂草稿；不展开手动编辑与诊断（第 6 章）、发布与版本（第 7 章）、运行与观察（第 8、9 章）。本章产物是一份“能看懂、但可能还要改”的草稿。

<!-- slot: 提示词 -->
直接复用第 4 章那段按“目标 / 输入 / 角色 / 审批 / 异常 / 完成”组织的提示词。为方便对照，这里再贴一次要点：把一次 Bug 交给分诊 Agent 返回 `assignee_team`，由审批人确认后通知频道；输入是 `bug_summary`、`destination`、`triage_agent`（分诊 Agent）和 `approver`（审批人）；Agent 回传不合结构就停在等待。

<!-- slot: 流程 -->
从提示词到一份可读草稿，主路径是：

1. 把提示词发给 Loop（创建入口）。
2. Loop 据此生成一份 Plan 草稿，同时给出 YAML 和可视化画布两种视图。
3. 先在可视化画布上扫一遍：入口在哪、有哪些 Node、按什么顺序连。
4. 再切到 YAML 看细节：每个 Node 的类型、它引用了哪些输入、回调或审批是怎么配的。
5. 对照第 4 章的需求卡，标出草稿里“对得上”和“对不上”的地方——对不上的，留到第 6 章编辑修正。

第一次生成的草稿很少完美，这很正常。生成只是把你的意图落到一个可编辑的结构上，真正的精修在编辑与诊断阶段。

读懂一份生成的草稿，按这个顺序最省力。先看 `input`：它声明了这个 Plan 要吃哪些输入、分别是什么类型——如果这里少了你在需求里写过的输入，多半是提示词里没说清、或生成时漏了。再看 `graph.entry`：它把输入注入到第一个 Node，相当于这张图从哪里开始。接着顺着 `graph.nodes` 和 `graph.edges` 走一遍：每个 Node 做一件事（发消息、等人审批、设置变量、等待），边把上一个 Node 的结果传给下一个。最后看 `end` 节点：它决定这次运行以成功还是失败收尾。

对照需求卡时，重点检查四类常见的不匹配。第一类是**缺步骤**：你需求里写了“要人工确认”，但草稿里没有 `request_approval`——通常是提示词里“审批”说得不够明确。第二类是**字段没串通**：某个 Node 用了 `input.X`，但 `X` 没在它的输入契约里声明、也没在边的 `bindings` 里传进来，这种会在校验时直接报错。第三类是**异常没着落**：审批被驳回后没有连到一个失败的 `end`，或 Agent 回传不合结构时没有“停在等待”的安排。第四类是**外部动作被塞进图里**：草稿试图用一个不存在的“HTTP 节点”去调外部系统，而正确做法是用 `send_message` 把这一步交给 Agent。把这四类标出来，第 6 章的编辑就有了明确的待办清单。

<!-- slot: 配置 -->
按上面提示词生成的草稿，结构大致如下（关键字段以 `loop.plan/v1` 为准；这是结构示意，实际草稿以生成为准）：

```yaml
apiVersion: loop.plan/v1
kind: PlanVersion
input:
  destination: {type: scope, required: true}
  bug_summary: {type: string, required: true}
  triage_agent: {type: agent, required: true}
  approver: {type: member, required: true}
graph:
  entry:
    nodeId: triage
    port: in
    input:
      destination: {ref: plan.input.destination}
      bug_summary: {ref: plan.input.bug_summary}
      triage_agent: {ref: plan.input.triage_agent}
      approver: {ref: plan.input.approver}
  nodes:
    - id: triage
      type: send_message
      input: {destination: {type: scope, required: true}, bug_summary: {type: string, required: true}, triage_agent: {type: agent, required: true}, approver: {type: member, required: true}}
      sendMessage:
        scope: {ref: input.destination}
        responder: {ref: input.triage_agent}
        text: "请分诊以下 Bug，返回 assignee_team：{{ input.bug_summary }}"
        completion: callback
        callbackOutput: {assignee_team: {type: string, required: true}}
    - id: approve
      type: request_approval
      input: {destination: {type: scope, required: true}, assignee_team: {type: string, required: true}, approver: {type: member, required: true}}
      requestApproval:
        assignee: "{{ input.approver }}"
        summary: "确认 Bug 指派给 {{ input.assignee_team }}"
    - id: notify
      type: send_message
      input: {destination: {type: scope, required: true}, assignee_team: {type: string, required: true}}
      sendMessage: {scope: {ref: input.destination}, text: "已确认，Bug 指派给 {{ input.assignee_team }}。", completion: sent}
    - id: done
      type: end
      end: {outcome: succeeded}
    - id: stopped
      type: end
      end: {outcome: failed, code: approval_rejected}
  edges:
    - {id: classified, source: {nodeId: triage, port: completed}, target: {nodeId: approve, port: in}, bindings: {destination: {ref: input.destination}, assignee_team: {ref: output.assignee_team}, approver: {ref: input.approver}}}
    - {id: approved, source: {nodeId: approve, port: approved}, target: {nodeId: notify, port: in}, bindings: {destination: {ref: input.destination}, assignee_team: {ref: input.assignee_team}}}
    - {id: rejected, source: {nodeId: approve, port: rejected}, target: {nodeId: stopped, port: in}}
    - {id: sent, source: {nodeId: notify, port: completed}, target: {nodeId: done, port: in}}
```

读懂它只需抓住三件事：`input` 是这个 Plan 要什么输入；`graph.nodes` 是一组编排单元（Node），每个有一个类型和一块配置；`graph.edges` 把上一个 Node 的输出端口连到下一个的输入端口，并在 `bindings` 里把需要的值传过去。例如 `triage` 用 `send_message` 加 `completion: callback` 调用分诊 Agent——注意 `sendMessage.responder` 指明了交给哪个 Agent，回传的 `assignee_team` 经边传给 `approve`；`approve` 是人工审批，审批人和要指派的团队都写在它的输入里，通过后走到 `notify` 通知频道。

再细看边的 `bindings`，这是草稿里最容易看漏、也最容易出错的地方。`bindings` 把上游 Node 的某个输出绑到下游 Node 的某个输入字段——例如上面把 `triage` 回传的 `assignee_team`（在边上写成 `{ref: output.assignee_team}`）绑给 `approve` 的 `assignee_team` 输入，又把 `approver` 从 `triage` 的输入透传给 `approve`。如果一条边忘了写 `bindings`、或写了不兼容的透传，下游就拿不到它需要的值，校验会报绑定缺失或不兼容（常见如 `binding.required_target`、`binding.passthrough_incompatible`）。

把上面这份草稿从头到尾顺着走一遍，就是一次完整的运行预演。入口把四个输入注入 `triage`；`triage` 把消息发给分诊 Agent、等它回传 `assignee_team`；边 `classified` 把 `assignee_team` 和 `approver` 传给 `approve`，于是审批卡的 summary 被渲染成“确认 Bug 指派给 X”，审批人能看到要指派给谁；审批人点通过后，边 `approved` 再把频道和团队传给 `notify` 发通知；通知发出后走到成功的 `end`。如果审批人驳回，则走 `rejected` 到失败的 `end`。能在脑子里把这条路径走通，就说明你读懂了这份草稿——第 6 章只要改不对的地方，不必从头重写。

把这份草稿保存为 `plan.yaml`，就能用公共入口校验它的结构：

```bash
$ python3 -c 'import json;print(json.dumps({"action":"validate","source":open("plan.yaml").read()}))' | loop exec workflow plan -
```

```text
{"data":{"diagnostics":[],"digest":"sha256:…"}}
```

得到空的 `diagnostics`，就说明这份草稿在结构上合法，可以进入第 6 章的编辑与诊断。

<!-- slot: 异常 -->
本章不展开运行失败处置。如果生成的草稿和你预期差很多，最常见原因是提示词里“异常怎么办”和“谁审批”说得不够死——回第 4 章把这两点补具体，再重新生成。一个实用的经验是：当草稿偏差较大时，先别急着在编辑器里逐个 Node 改，那样往往越改越乱；回到提示词把含糊的地方说死，再重新生成一份，通常比手动改十次更接近你要的结果。把生成当作和 Loop 的一次对话，而不是一次性命令，能省下后面大量编辑时间。

<!-- slot: 审批 -->
草稿里的 `request_approval` 是运行时审批节点：它会暂停 Run、等 `assignee` 指定的审批人决定，按 `approved` / `rejected` 走不同分支。`assignee` 在渲染时支持 `{{ }}` 插值（如 `{{ input.approver }}`），运行时再解析成具体成员；注意它没有创作期超时——“审批超时怎么办”是你在需求里设计的人工兜底，不是节点自动升级。配置细节在第 6、12 章。

<!-- slot: 外部系统 -->
本章不涉及外部系统对接。`triage` 通过 `send_message` + `completion: callback` 调用分诊 Agent（`responder` 指明是哪个 Agent）；如果分诊要读写代码托管，那是 Agent 经 App / Extension 完成，不是 Workflow 直接调用。

<!-- slot: 验收 -->
- [ ] 能把第 4 章的提示词发给 Loop 并得到一份 Plan 草稿。
- [ ] 能在可视化画布和 YAML 两种视图里读懂草稿：输入、Node、边、`responder` 与 `assignee` 的引用方式。
- [ ] 能把草稿保存为 `plan.yaml` 并用公共入口校验得到空的 `diagnostics`。
- [ ] 能对照需求卡，标出草稿里对得上和对不上的地方。

<!-- slot: 媒体槽位 -->
<!-- media: fig-05-01-create-from-prompt.png -->
图 5-1 从提示词到 Plan 草稿（截图槽位：提示词输入 → 生成的可视化画布 → 对应 YAML）。
