开源 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 模式。

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

相关推荐
花椒技术6 小时前
我们把 26 个接口自动化场景接进了 Agent,效果真是没想到!!
agent·ai编程·测试
我是大卫10 小时前
【图】一图理解LLM、LangChain、RAG、AI Agent、MCP、token、context、prompt、tool概念
langchain·llm·agent
凌奕13 小时前
我读了六个 Coding Agent 的上下文压缩源码,发现网上流传的数据一半是错的
github·agent·claude
MomentYY13 小时前
RAG 建库:资料是怎么存进去的?
人工智能·agent·ai编程
武子康15 小时前
VLA 已经能输出动作,为什么机器人仍需要多时间尺度闭环
人工智能·机器人·agent
玉鸯16 小时前
RAG 全链路调优指南:从 Chunking 到 Reranker
python·llm·agent
编程小明17 小时前
Hold Rein 代码图插件:让 AI 先看懂项目关系,再开始写代码
agent·vibecoding
用户77833661321117 小时前
2026 年 7 月,RAG 缺的那块:实时搜索 + 知识库混合架构
api·agent
leeyi17 小时前
Document 组件源码:Loader / Transformer / Parser 为什么分成三个接口(第67篇-E53)
aigc·agent·ai编程
喝咖啡的女孩17 小时前
🌱 手把手做一只「会改代码」的 VS Code AI 小助手
agent