开源 FlowScript:把 Skill 变成可执行、可检查、可回放的工作流

Agent Skill 是一种很自然的能力封装方式:用 Markdown 描述任务目标、输入输出、约束和执行步骤,模型读完后就知道应该怎么做。

但当任务从"一次提示词"发展成多步骤流程,问题也随之出现:

  • 参数由谁校验?
  • 脚本执行顺序由谁决定?
  • 一个步骤的输出怎样传给下一步?
  • 分支条件来自模型判断,还是结构化状态?
  • 中途失败后,已经生成的产物还能不能继续使用?
  • 最后怎样还原这个 Agent 到底调用了哪些工具?

如果所有事情都依赖模型在上下文里"记住并正确执行",Skill 虽然容易编写,却很难获得稳定、可检查的执行过程。

我和我朋友最近开源的 FlowScript 就是在尝试解决这个问题:保留 Markdown Skill 的可读性,同时为它增加一套由 Runtime 执行的受控工作流,并且在工作流执行过程中生成 Agent Skill 的执行路径,当出现工作流无法处理的问题,还可以回滚到 Agent Skill 模式尝试修复。

项目目前还是验证设计思路的 MVP,不是生产级编排平台,也不是安全沙箱。但它已经能够跑通一个包含 LLM、参数校验、Python 脚本、结构化分支、artifact 交接和最终报告的完整 Demo。

双模式 Skill:同一份能力,两种执行方式

FlowScript Skill 同时面向两类执行者。

普通 Agent 阅读 SKILL.md,按照自然语言说明完成任务;兼容 FlowScript 的 Runtime 则加载 FLOWSCRIPT.md 中的 DSL,按照明确的节点和分支执行。

一个典型 Skill 包结构如下:

text 复制代码
my-skill/
├── SKILL.md
├── FLOWSCRIPT.md
├── input_schema.json
├── agents/
│   └── openai.yaml
├── prompts/
├── schemas/
├── scripts/
├── references/
└── tests/
    └── replay_cases.jsonl

这里有一个刻意的职责划分:

  • SKILL.md 负责解释能力、输入、输出和约束;
  • FLOWSCRIPT.md 负责声明执行顺序、artifact 路径和分支;
  • JSON Schema 负责约束模型输出;
  • Python 脚本负责确定性处理;
  • Runtime 负责调度、工具调用和状态记录。

完整 DSL 不会再复制一遍到 SKILL.md。这样既避免两份流程逐渐不一致,也不会让普通 Agent 被大量执行细节淹没。

FlowScript DSL 长什么样

FLOWSCRIPT.md 仍然是普通 Markdown,只是在其中包含一个 fenced flow 代码块。

下面是一个精简后的参数校验节点:

flow 复制代码
version: 1
mode: controlled
inputs:
  schema: input_schema.json
steps:
  - id: validate_params
    type: validator
    command:
      executable: python
      args:
        - scripts/validate.py
        - --input
        - ${input.params}
        - --output
        - ${output.status}
        - --normalized-output
        - ${output.params}
      accepted_exit_codes: [0, 1, 2]
      status_source: ${output.status}
      timeout_seconds: 30
    output:
      status: runs/{run_id}/artifacts/validation_status.json
      params: runs/{run_id}/valid_params.json
    branches:
      valid:
        when: ${validate_params.output.status.state} == "valid"
        next: generate_and_profile
      needs_input:
        when: ${validate_params.output.status.state} == "needs_input"
        next: request_clarification
      invalid:
        when: ${validate_params.output.status.state} == "invalid"
        next: fallback_skill_mode

这里最重要的不是 YAML 语法,而是执行权的归属。

模型不会生成一段 shell 命令,也不能决定下一步去哪。Runtime 根据 DSL 构造 argv,执行已经声明的 Python 脚本,然后读取状态 JSON 中的 state 字段选择分支。

当前 MVP 的分支表达式故意只支持显式的 ==!=。能力不算复杂,但行为比较容易预测和检查。

LLM 负责内容,不负责流程控制

FlowScript 不是要消除 LLM,而是缩小 LLM 在流程中的职责范围。

以 Demo 中的请求解析节点为例:

  1. Runtime 读取对应语言的 prompt;
  2. Runtime 读取 input_schema.json
  3. 模型调用专用的 submit_skill_inputs 工具;
  4. Runtime 校验工具参数是否符合 Schema;
  5. Runtime 将参数写入声明好的 resolved_params.json
  6. 后续校验节点再决定是否可以继续。

LLM 内容节点也采用同样的模式。模型提交 summaryrisksrecommendationscluster_insights,但模型不能自行选择保存路径,也不能绕过结构校验直接生成最终报告。

这种方式的核心原则可以概括为:

模型提交内容,Runtime 控制持久化;模型参与理解,Runtime 控制执行。

Runtime 如何执行工作流

当前 Runtime 的执行过程可以简化为:

text 复制代码
用户请求或结构化 JSON
          │
          ▼
  加载 FLOWSCRIPT.md
          │
          ▼
校验节点、引用、路径和 Schema
          │
          ▼
  ┌────── 当前节点 ──────┐
  │                      │
  │ LLM 节点             │ Script / Validator 节点
  │ 读取 prompt + schema │ 构造声明好的 Python argv
  │ 获取结构化工具参数    │ 执行并捕获退出码
  │ 写入 JSON artifact   │ 读取状态 artifact
  │                      │
  └──────────┬───────────┘
             ▼
       选择 next / branch
             │
             ▼
     读取最终主要产物
             │
             ▼
写入 context、trace 和 result

WorkflowEngine 是唯一能够决定执行顺序的组件。模型适配层在代码中叫 FakeAgent,但它并不是另一个会自主规划的 Agent,而是负责把模型输出规范化为工具参数,并记录为可检查的工具交互。

为什么重点设计 skill_agent_context.json

普通执行日志适合排查 Runtime,但并不一定适合回答另一个问题:

如果把这次运行看成一个 Skill Agent 会话,这个 Agent 对外表现为做了什么?

因此 FlowScript 每次运行都会生成 skill_agent_context.json。它的顶层不是包含大量内部状态的对象,而是一组 OpenAI 风格的消息:

json 复制代码
[
  {
    "role": "user",
    "content": "为 Alice 和 Bob 生成模拟数据,并按员工分组。"
  },
  {
    "role": "assistant",
    "content": "",
    "tool_calls": [
      {
        "id": "call_0001",
        "type": "function",
        "function": {
          "name": "read_file",
          "arguments": "{\"path\":\"prompts/parse_request_cn.md\",\"max_bytes\":64000}"
        }
      }
    ]
  },
  {
    "role": "tool",
    "tool_call_id": "call_0001",
    "name": "read_file",
    "content": "{\"status\":\"success\",\"path\":\"prompts/parse_request_cn.md\"}"
  }
]

它会依次记录:

  • 用户原始请求;
  • prompt 和 Schema 的读取;
  • 模型提交的结构化输入;
  • write_json 持久化;
  • Python exec 调用及结果;
  • 状态和 artifact 的读取;
  • LLM 解读提交;
  • 最终报告读取;
  • 最终 Assistant 回复。

其中 function.arguments 和工具消息的 content 都是 JSON 编码后的字符串。消费者如果需要结构化字段,需要先解析外层消息,再解析内层 JSON 字符串。

这样设计的好处是,上层 Agent、调试器或回放工具不需要理解 WorkflowEngine 的全部内部事件,就可以读取一条接近真实 Agent 工具调用链的上下文。当发生问题无法继续用Workflow处理时,可以切换到skill Agent下继续运行

context、trace 和 result 分别解决什么问题

一次执行会生成三种 Runtime 产物:

text 复制代码
runs/{run_id}/runtime/
├── skill_agent_context.json
├── trace.jsonl
└── result.json

它们面向不同的使用场景。

skill_agent_context.json

面向 Agent 风格的回放、审计和下游上下文。只保留用户、Assistant 和工具之间的可观察交互。

trace.jsonl

面向 Runtime 调试和测试。每一行是一个事件,包含:

json 复制代码
{
  "seq": 12,
  "timestamp": 1783512626.49,
  "run_id": "demo-001",
  "step_id": "validate_params",
  "actor": "runtime",
  "event": "branch_selected",
  "payload": {
    "branch": "valid",
    "next": "generate_and_profile"
  }
}

节点开始、模型请求、artifact 写入、分支选择和节点失败等内部细节都放在这里。

result.json

面向快速判断执行结果。它汇总运行状态、已完成步骤、所选分支、主要产物、工具调用次数、模型调用次数和耗时。

即使执行失败,Runtime 也会尽量写入失败结果、刷新上下文,并保留已经生成的 trace,避免只得到一个退出码。

大文件不会直接塞进上下文

完整记录工具调用不等于把所有文件原样复制进 JSON。

当前 Runtime 对读取结果做了简单限制:

  • 小于 64,000 字节的文本或 JSON 可以完整记录;
  • 更大的文件记录路径、字节数、SHA-256 和预览;
  • CSV 文件额外记录数据行数;
  • exec 只保留 stdout 和 stderr 的最后 16,000 个字符。

这套策略还比较基础,但至少能避免一个几万行的 CSV 直接撑爆 Agent 上下文。

Demo:模拟 CSV 数据质量报告

仓库包含一套中英文 Demo,用来验证完整工作流,而不只是 DSL 解析。

它会:

  1. 从用户请求中提取员工、区域和分组维度;
  2. 校验参数并补全默认时间范围;
  3. 生成确定性的模拟 CSV;
  4. 按时间筛选并计算字段画像;
  5. 按员工或区域生成聚类统计;
  6. 让模型生成结构化风险解读;
  7. 校验解读结构;
  8. 输出最终 Markdown 报告。

一次成功运行的目录结构如下:

text 复制代码
runs/<run_id>/
├── resolved_params.json
├── valid_params.json
├── data/
│   └── generated_data.csv
├── artifacts/
│   ├── validation_status.json
│   ├── profile.json
│   ├── profile_status.json
│   ├── interpretation.json
│   ├── interpretation_status.json
│   ├── final_report.md
│   ├── finalize_status.json
│   └── artifacts_manifest.json
└── runtime/
    ├── trace.jsonl
    ├── skill_agent_context.json
    └── result.json

如何运行

项目要求 Python 3.11 或更高版本,以及一个可用于请求的模型接口,比如lmstudio上部署qwen3.5-0.8B。

bash 复制代码
git clone https://github.com/whale-agent-lab/flowscript.git
cd flowscript
python -m pip install -e minimal_flowscript_agent

使用自然语言请求运行中文 Demo:

bash 复制代码
minimize-agent run \
  --skill skills_cn/csv-quality-report-demo \
  --request "为张伟和李娜生成 500 条华东、华南区域的模拟数据,并按员工分组。" \
  --language zh \
  --model qwen3.5-0.8b \
  --endpoint http://127.0.0.1:1234/v1/chat/completions

也可以传入结构化 JSON,跳过第一个请求解析模型调用:

bash 复制代码
minimize-agent run \
  --skill skills_cn/csv-quality-report-demo \
  --input-json input.json \
  --run-id demo-001 \
  --language zh

需要注意,--input-json 只跳过请求解析;后面的画像解读节点仍然需要模型 endpoint。

当前安全边界

FlowScript 试图减少模型对执行过程的自由度,但不能因此把它当成安全沙箱。

目前实现的约束包括:

  • 只执行 DSL 中预先声明的 Python 脚本;
  • 子进程使用 argv 和 shell=False
  • 脚本与 artifact 路径必须位于 Skill 根目录内;
  • 工作目录固定为 Skill 根目录;
  • 显式声明超时和可接受退出码;
  • 模型不能选择命令、保存路径或分支;
  • 分支读取结构化状态,而不是模型自由文本。

但被允许执行的 Python 脚本依然继承 Runtime 进程的系统权限。因此运行第三方 Skill 前,仍然必须审查其中的脚本和资源。

MVP 尚未解决的问题

为了先验证最小闭环,当前版本有不少明确限制:

  • 不支持暂停、恢复或覆盖已有运行;
  • 不支持多轮澄清;
  • clarification 和 fallback terminal 目前会记录后停止;
  • 不支持循环、并行节点和动态节点;
  • 只实现了 JSON Schema 的一个小型子集;
  • replay case 已经定义,但还没有完整的自动 replay runner。

后续更值得探索的方向包括 artifact 缓存、人工审批节点、可恢复执行、并行但可重放的调度,以及在执行出错后回滚到 Agent Skill 模式。

这个项目更像一个可以讨论和验证的设计提案,而不是已经完成的框架。

相关推荐
不好听6131 小时前
从零构建一个 AI Coding Agent:不会写代码?让 AI 帮你写
agent
学渣超1 小时前
微服务日志智能诊断系统(九) 让 LLM 成为日志分析专家
java·后端·agent
想要成为糕糕手1 小时前
从 0 到 1 打造 Coding Agent——mini cursor(终篇):装上命令行手脚,造一个会"干活"的 AI 程序员
langchain·agent·ai编程
leeyi1 小时前
让 Agent 说中文:Prompt 模板怎么管理(第47篇-E33)
aigc·agent·ai编程
不好听6131 小时前
让 Agent 开口说话:用 Chalk 实现可视化终端
agent
不好听6131 小时前
AI Agent 的四肢:Node.js 内置模块实战指南
agent
学渣超1 小时前
微服务日志智能诊断系统(七) 日志 RAG 引擎——蒸馏、索引与精准检索
java·后端·agent
掉鱼的猫1 小时前
代码审查 Agent Harness 实战:AI 自动 Code Review
java·llm·agent
学渣超1 小时前
微服务日志智能诊断系统(六) Fast Path vs Slow Path——双路径日志诊断策略
java·后端·agent