乐于分享
好东西不私藏

OpenClaw 技能编排实战|如何组合多个技能完成复杂任务链

OpenClaw 技能编排实战|如何组合多个技能完成复杂任务链

本系列第十八篇:从“单个技能”到“工作流”——让多个 Skill 像乐高一样拼出真正的自动化

        欢迎回到 OpenClaw 系列教程。经过前面十七篇的积累,你已经掌握了 Skill 开发、Hook 拦截、Plugin 扩展等核心能力。你的“龙虾”已经拥有了十几项甚至几十项技能。

        但一个真实的自动化任务,很少是单个技能能独立完成的。比如“每天早上 9 点抓取行业新闻 → 生成摘要 → 发送到飞书群”——这需要新闻抓取技能、文本摘要技能、消息推送技能三者协同工作。如果你只是分别教会 AI 这三个技能,它并不会自动把它们串起来。

这就是技能编排要解决的问题。

        本文将系统讲解 OpenClaw 中技能编排的三种模式(串行、并行、条件分支),并通过两个完整实战案例(新闻监控推送、多源数据报表生成)演示如何将多个技能组合成可靠的工作流。同时,我们会探讨在 Skill 层、Hook 层、Plugin 层分别实现编排的适用场景与最佳实践。

一、技能编排的本质:从“会做”到“会串”

1.1 为什么需要编排?

OpenClaw 的 Agent 本身具备一定的任务拆解能力——当你给出一个复杂指令时,它会尝试分解成多个步骤,并依次调用相应的工具或技能。但这种“动态编排”存在三个局限:

  • 不可复现:同样的任务,今天和明天的执行路径可能不同,难以形成稳定的自动化

  • 缺乏流程控制:无法精确控制并行执行、条件判断、重试策略

  • 跨技能状态难共享:上一个技能的输出如何传递给下一个技能,缺少标准化机制

        技能编排的本质,是将特定场景下的任务流程固化、参数化、可复用,让 Agent 不再是“自由发挥”,而是“按图施工”。

1.2 编排的三种基本模式

模式
描述
适用场景
串行(Sequential)
步骤 A → B → C,后一步依赖前一步的输出
数据抓取 → 清洗 → 存储
并行(Parallel)
同时执行多个独立技能,最后聚合结果
同时从多个数据源获取信息
条件分支(Conditional)
根据上一步结果决定下一步执行哪个分支
错误时重试 / 满足阈值才发送通知

在实际工作流中,这三种模式往往会嵌套组合。

二、编排实现的三条路径

在 OpenClaw 生态中,实现技能编排有三种不同层级的方式,各有优劣。

路径
技术手段
复杂度
可维护性
适用场景
Skill 内编排
在 SKILL.md 中用自然语言描述执行顺序
固定步骤的简单工作流
Hook 编排
在 Hook 中监听事件,动态触发多个技能
需要跨会话状态或条件判断
Plugin 编排
编写 TypeScript 代码,注册 Workflow 工具
复杂、长周期、需状态持久化的工作流

路径一:Skill 内编排(最轻量)

        直接在 SKILL.md 的“使用说明”部分写出步骤顺序,Agent 会按顺序执行。适合步骤不超过 5 个、没有复杂分支的场景。

路径二:Hook 编排(中等复杂度)

        在 Hook 中监听特定事件(如定时心跳、消息接收),然后通过 API 调用多个技能。可以加入条件判断、重试逻辑。

路径三:Plugin 编排(企业级)

        编写 TypeScript Plugin,注册一个 Workflow 工具,内部实现状态机、并发控制、错误恢复。适合需要长期运行、可观测、可中断恢复的生产级自动化。

下面通过两个实战案例,分别演示 Skill 内编排和 Plugin 编排。

三、实战案例一:新闻监控 + 摘要生成 + 飞书推送(Skill 内编排)

        需求:当用户提供一批 RSS 源或关键词时,自动抓取最新文章,对每篇文章生成摘要,最后将摘要汇总发送到飞书群。

3.1 准备基础技能

我们需要三个基础技能(假设已安装或自定义开发):

  • rss_reader:读取 RSS 源,返回文章列表(标题、链接、正文片段)

  • smart_summarize:对给定文本生成 200 字以内的摘要(可复用第 15 篇的 summarize 技能)

  • feishu_sender:发送 Markdown 消息到飞书群(需提前配置 webhook)

3.2 编写编排 Skill:news_digest

创建 ~/.openclaw/workspace/skills/news_digest/SKILL.md

markdown

---name: news_digestdescription: Fetch latest news from RSS, summarize each article, and send digest to Feishu.triggers: ["新闻摘要""日报生成""news digest"]tools: [rss_reader, smart_summarize, feishu_sender]---# News Digest Workflow当用户要求生成新闻摘要时,严格按照以下步骤执行。## 输入参数用户需提供:- RSS 源列表(URL 数组)或 搜索关键词(将自动转换为 RSS)- 目标飞书群 webhook(若未提供则使用默认)## 执行步骤### 步骤 1:抓取文章调用 `rss_reader` 技能,参数为 RSS 源列表。等待返回结果,格式为 `[{title, link, content_snippet}, ...]`若返回为空,输出“暂无新文章”并终止。### 步骤 2:逐篇生成摘要对步骤 1 返回的每一篇文章,依次调用 `smart_summarize` 技能,输入为 `content_snippet`每篇文章的摘要结果保存到临时变量 `digest_items` 中。### 步骤 3:格式化摘要消息将 `digest_items` 格式化为飞书支持的 Markdown 格式:```markdown# 📰 今日新闻摘要 (YYYY-MM-DD)## 1. [标题1](链接1)摘要内容...## 2. [标题2](链接2)摘要内容...步骤 4:发送到飞书调用 feishu_sender 技能,参数为上一步生成的 Markdown 文本。发送成功后,回复用户“日报已发送至飞书群”。错误处理若某篇文章摘要生成失败(超时或返回空),跳过该篇,继续下一篇若所有文章都失败,回复用户“摘要生成失败,请检查 RSS 源是否有效”

    3.2 使用编排 Skill

    用户在聊天中输入:  > “帮我生成新闻摘要,RSS 源:https://news.cn/rss/tech, https://ai.news/rss”  Agent 读取 SKILL.md,自动按照四个步骤顺序执行,最终将摘要发送到飞书。整个过程无需人工干预。 

    优点:零代码,创建速度快,易于调整步骤顺序。

    局限:不支持并行(串行摘要效率较低),错误恢复机制简单,无法持久化工作流状态。

    四、实战案例二:多源数据报表生成(Plugin 编排)

            需求:每天凌晨 2 点,从三个数据源(数据库、API、本地 CSV)拉取指标,合并后生成 Excel 报表,再发送到钉钉,同时备份到云存储。

    4.1 创建 Plugin 结构

    bash 
    mkdir -p ~/.openclaw/workspace/extensions/daily-reportercd ~/.openclaw/workspace/extensions/daily-reporternpm init -ynpm install @openclaw/plugin-sdk csv-parse axios xlsx

    创建 openclaw.plugin.json

    json

    {  "id": "daily-reporter",  "name": "Daily Report Orchestrator",  "version": "1.0.0",  "description": "Orchestrate multi-source data collection, report generation, and multi-channel delivery.",  "permissions": ["read:files", "write:files", "http:send", "schedule:create"]}

    4.2 实现编排器:使用状态机

    我们需要三个基础技能(假设已安装或自定义开发):

    创建 index.ts

    typescript

    import { definePluginEntry } from "@openclaw/plugin-sdk";import { schedule } from "node-cron";import fs from "fs/promises";import path from "path";interface WorkflowState {  step: "init" | "fetch_db" | "fetch_api" | "fetch_csv" | "merge" | "generate_report" | "send" | "backup";  data: any;  errors: string[];  startTime: Date;}class DailyReporter {  private state: WorkflowState;  private readonly workspace = process.env.OPENCLAW_WORKSPACE || "~/.openclaw/workspace";  constructor() {    this.state = {      step: "init",      data: {},      errors: [],      startTime: new Date()    };  }  async run() {    console.log("[DailyReporter] 工作流开始");    await this.fetchDatabaseMetrics();    await this.fetchApiMetrics();    await this.fetchCsvMetrics();    await this.mergeData();    await this.generateExcelReport();    await this.sendToDingtalk();    await this.backupToCloud();    console.log("[DailyReporter] 工作流完成", { duration: Date.now() - this.state.startTime.getTime() });  }  private async fetchDatabaseMetrics() {    this.state.step = "fetch_db";    // 示例:通过 exec 工具调用 SQL 查询(实际应使用 Plugin 提供的数据库客户端)    const result = await this.runTool("exec", { command: "mysql -e 'SELECT ...' -B -N" });    this.state.data.dbMetrics = JSON.parse(result.stdout);  }  private async fetchApiMetrics() {    this.state.step = "fetch_api";    const response = await fetch("https://api.example.com/metrics");    this.state.data.apiMetrics = await response.json();  }  private async fetchCsvMetrics() {    this.state.step = "fetch_csv";    const csvContent = await fs.readFile(path.join(this.workspace, "input/metrics.csv"), "utf-8");    // 使用 csv-parse 解析    this.state.data.csvMetrics = this.parseCSV(csvContent);  }  private async mergeData() {    this.state.step = "merge";    // 合并三个数据源,按时间戳对齐    this.state.data.merged = { ...this.state.data.dbMetrics, ...this.state.data.apiMetrics, ...this.state.data.csvMetrics };  }  private async generateExcelReport() {    this.state.step = "generate_report";    const XLSX = require("xlsx");    const workbook = XLSX.utils.book_new();    const sheet = XLSX.utils.json_to_sheet([this.state.data.merged]);    XLSX.utils.book_append_sheet(workbook, sheet, "Daily Report");    const buffer = XLSX.write(workbook, { type: "buffer", bookType: "xlsx" });    const outputPath = path.join(this.workspace, `reports/report_{this.state.data.reportPath})`      }    };    await fetch(webhook, { method: "POST", body: JSON.stringify(message), headers: { "Content-Type""application/json" } });  }  private async backupToCloud() {    this.state.step = "backup";    // 示例:上传到 OSS    // await uploadFile(this.state.data.reportPath);  }  private async runTool(toolName: string, params: any) {    // 通过 Plugin API 调用 OpenClaw 内置工具    // 实际实现需使用 api.callTool()    console.log(`调用工具 {total}`。

    Agent 在执行时会自动维护内存变量。

    Plugin 编排示例:使用工作区文件

    typescript

    // 将中间结果写入临时 JSONawait fs.writeFile(path.join(workspace, ".cache/step1.json"), JSON.stringify(data));// 后续步骤读取const data = JSON.parse(await fs.readFile(path.join(workspace, ".cache/step1.json"), "utf-8"));

    这种方式适合数据量大、需要断点续传的场景。

    六、编排中的错误处理与重试

    可靠的工作流必须具备错误处理机制。以下是三种层级下的处理策略:

    层级
    错误处理方式
    重试支持
    Skill 内
    在 SKILL.md 中写明“若 A 失败,则执行 B”
    需手动描述,无自动重试
    Hook
    在事件回调中用 try-catch,根据错误类型决定下一步
    可编写递归重试逻辑
    Plugin
    使用 Promise.catch + 指数退避库(如 p-retry
    全功能支持

    Skill 内编排的错误处理示例

    markdown

    ## 错误处理- 若步骤 1(抓取 RSS)超时,重试最多 2 次,间隔 5 秒- 若仍失败,跳过该 RSS 源,继续处理下一个- 若所有 RSS 源均失败,回复用户“暂时无法获取新闻,请稍后重试”

    Plugin 编排中使用 p-retry

    typescript

    import pRetry from 'p-retry';async function fetchWithRetry(url: string) {  return pRetry(async () => {    const res = await fetch(url);    if (!res.okthrow new Error(`HTTP ${res.status}`);    return res.json();  }, { retries3factor2minTimeout1000 });}

    七、编排性能优化:并行执行

            对于相互独立的步骤,可以并行执行以缩短总耗时。Skill 内编排较难表达并行(Agent 默认串行思考),但可以通过以下方式实现:

    方法一:在 Skill 中用列表语法提示并行

    markdown

    ### 步骤 1:并行抓取三个数据源同时执行以下三个操作:- 调用 `rss_reader` 抓取源 A- 调用 `rss_reader` 抓取源 B  - 调用 `rss_reader` 抓取源 C等待所有操作完成后,合并结果。

    部分 Agent 模型能理解这种并行指令,但不是百分之百可靠。

    方法二:Plugin 中使用 Promise.all

    typescript

    const [dataA, dataB, dataC] = await Promise.all([  fetchSourceA(),  fetchSourceB(),  fetchSourceC()]);

    这是最可靠的方式。

    八、最佳实践总结

    1. 从简单开始:优先用 Skill 内编排,直到步骤超过 5 个或需要条件分支时再考虑 Hook/Plugin

    2. 显式定义输入输出:每个技能明确声明需要什么输入、产生什么输出,便于编排时连接

    3. 为编排单独创建 Skill:不要将编排逻辑散落在多个 Skill 中,而是创建一个“工作流技能”,专门负责调用其他技能

    4. 记录执行日志:在工作流的关键节点打印日志,便于排查问题

    5. 设置超时和熔断:避免某个技能卡住导致整个工作流挂起,可使用 Promise.race 设置超时

    6. 幂等性设计:工作流应能安全地重复执行,避免重复发送消息或重复写入数据

    九、常见问题与排障

    Q1:Agent 不按我 Skill 中写的顺序执行

    检查 SKILL.md 中的步骤描述是否清晰,使用“步骤 1”“步骤 2”这样的编号。也可在步骤前加上“必须”“严格按照顺序”等强约束词。

    Q2:如何传递复杂数据结构(如数组、对象)

    在 Skill 内编排中,用自然语言描述“将结果保存为 JSON 字符串”,后续步骤再解析。在 Plugin 中直接使用 TypeScript 对象传递即可。

    Q3:工作流执行到一半崩溃,如何恢复?

    Plugin 编排中可将工作流状态持久化到数据库,并在启动时检查是否有未完成的工作流,实现断点续传。

    Q4:并行执行时如何限制并发数?

    使用 p-limit 库控制并发度,避免资源耗尽。

    typescript

    import pLimit from 'p-limit';const limit = pLimit(3);const results = await Promise.all(urls.map(url => limit(() => fetch(url))));

    十、下一步做什么?

    技能编排是将 OpenClaw 从“玩具”变成“生产力工具”的关键一步。掌握编排后,你可以构建高度自动化的业务场景。

    • 第 19 篇:OpenClaw 微信接入教程|ClawBot 官方插件 + 企业微信自建应用,两种方案全掌握

    • 第 23 篇:多 Agent 协作——让多个 Agent 各自执行编排好的工作流,再汇总结果

    • 第 24 篇:定时任务——用 Cron 触发编排好的工作流,实现无人值守

    • 第 26 篇:任务编排高级篇——使用 Camunda 或 Temporal 集成外部工作流引擎

    💡 最终提醒:编排的价值在于将不确定性转化为确定性。不要试图编排一切——保留一定程度的 Agent 自主决策能力,会让系统更灵活。找到“固定流程”和“智能决策”的平衡点,是设计良好工作流的艺术。