工作流

🔵 工作流——基于智能体和工具的声明式多步骤流水线。每个工作流都是 config/workflows/ 中的一个 YAML 文件,描述一个有序的步骤列表,这些步骤操作一个不断累积的变量作用域(payload input 加上每个步骤的 output_key)。

步骤支持热重载;通过 POST /v1/workflows/<name>/run 启动。

12.1 步骤类型

type 用途
agent 使用模板化 prompt 运行模板(或内联 instructions);可选地通过 output_schema 获得结构化结果
tool 使用模板化的 args 直接调用工具
saga 带补偿的有序工具组:当某步骤失败时,已执行的步骤按相反顺序回滚
condition 根据模板化的布尔表达式跳转到步骤 id
for_each 遍历集合,对每个元素执行嵌套步骤列表
loop 重复嵌套块直到满足 until(或 max_iterations)——智能体内部循环
verify 检查条件(工具和/或评判智能体)→ { passed, checks },步骤不会失败

模板使用 MiniJinja({{ var }}、{% if %}),从变量作用域渲染;{{ x | tojson }} 嵌入结构化值,env_var('NAME') 读取环境变量。

12.2 示例

name: triage
description: Просмотреть входящие коммиты и завести тикеты.
steps:
  - id: fetch
    type: tool
    tool: git_commits
    args: { since: "{{ input.since | default('yesterday') }}" }
    output_key: commits

  - id: process
    type: for_each
    over: commits.items          # dot-path в скоуп переменных → массив
    item: commit                 # переменная цикла
    concurrency: 3
    max_items: 100
    steps:
      - id: analyze
        type: agent
        template: review
        prompt: "Отревьюй {{ commit.sha }}: {{ commit.message }}"
        output_key: review
        output_schema:
          type: object
          properties:
            needs_action: { type: boolean }
            summary: { type: string }
          required: [needs_action, summary]
        retry: { max: 2, backoff_secs: 5 }
        timeout_secs: 120
      - id: branch
        type: condition
        when: "{{ review.needs_action }}"
        then: file_ticket
      - id: file_ticket
        type: tool
        tool: send_report
        args: { body: "{{ review.summary }}" }
    collect:
      reviews: "{{ results | tojson }}"

  - id: done
    type: tool
    tool: send_report
    args: { body: "{{ reviews }}" }

12.3 步骤参考

agent

字段 描述
template 要运行的模板(与 instructions 互斥)
instructions 不使用模板的内联系统提示
tools 额外的工具名称(与模板中的合并)
prompt 用户消息(MiniJinja)
output_key 将结果保存到作用域中的位置
output_schema JSON Schema——智能体通过 job_done 结束;经过验证的 payload 成为 output_key 的值
session 有状态会话的键(同一次运行中具有相同键的步骤共享上下文)
retry { max, backoff_secs }——出错时重试整个步骤
timeout_secs 单次运行的时限
budget token 限制 { max_per_turn, max_per_minute, max_total };对于内联 instructions 会替换默认限制(4000 / 50000 / 200000);对于 template 则被忽略
next 显式指定下一步骤的 id(见 §12.9)

没有 output_schema 时,步骤的输出是智能体最终回答的文本。有它时,如果智能体未返回有效的结构化结果,步骤就会失败。

tool

字段 描述
tool 已注册工具的名称
args 参数(字符串叶子节点为 MiniJinja;渲染为 JSON 的叶子节点以结构体形式传递)
output_key 保存结果的位置——如果有工具的 data 则用它,否则用 content
idempotency_key 用于有副作用工具的幂等键(见 05-tools-core 中的 idempotency:)
next 显式指定下一步骤的 id

condition

字段 描述
when MiniJinja 表达式 → true/false
then 为真时的步骤 id
else 为假时的步骤 id

for_each

字段 描述
over 作用域中的 dot-path → 数组(例如 commits.items)
item 循环变量的名称(默认为 item)
concurrency 最大并行迭代数(默认为 1)
max_items 元素的硬性上限
steps 嵌套子工作流,对每个元素运行一次
collect 名称 → 模板的映射,将每个元素的结果(可作为 results 访问)折叠回父作用域
next 显式指定下一步骤的 id

每次迭代都基于父作用域的副本 + item 运行。其输出成为 results(数组)中每个元素的结果,collect 可以对其重新格式化。

saga

带补偿的有序有副作用工具组:每个正向步骤都可以声明 compensate,当某步骤失败时,已执行的步骤按相反顺序回滚。步骤会持久化到 durable outbox,因此挂起的 saga 可以经受重启。 步骤的值是正向步骤结果的数组。

字段 描述
saga_id saga 的 id(默认为步骤的 id);持久化状态的键
steps { tool, args, idempotency_key?, max_attempts?, compensate? } 的列表
steps[].compensate { tool, args }——回滚时的补偿调用
output_key 将结果数组放到哪里
next 显式指定下一步骤的 id
steps:
  - id: checkout
    type: saga
    saga_id: order-{{ input.order_id }}
    output_key: checkout
    steps:
      - tool: reserve_stock
        args: { sku: "{{ input.sku }}", qty: 1 }
        compensate: { tool: release_stock, args: { sku: "{{ input.sku }}", qty: 1 } }
      - tool: charge_card
        args: { amount: "{{ input.amount }}", key: "{{ input.order_id }}" }

当 charge_card 失败时,内核会调用 release_stock 并将该步骤标记为 compensated;saga 步骤将以错误结束(saga '…' failed and was rolled back)。

loop

内部循环:重复 body,直到 until 渲染为 true,但不超过 max_iterations。

字段 描述
max_iterations 迭代次数的硬性上限(必填)
body 嵌套步骤列表,每次迭代都会执行
until MiniJinja 表达式 → true/false;true 停止循环(成功)
on_max 当 max_iterations 耗尽时该怎么做:fail(默认)或 success
iteration 计数器变量的名称(从 0 开始),默认为 iteration
output_key 将 { iterations, stopped, max_iterations } 放到哪里
next 显式指定下一步骤的 id

body 的输出会累积在循环作用域中,并在结束后转移到父作用域(因此每次迭代后 until 都能看到最新的 verification)。

steps:
  - id: fix
    type: loop
    max_iterations: 8
    until: "{{ verification.passed }}"
    on_max: fail
    body:
      - id: act
        type: agent
        template: bugfix-agent
        prompt: "Исправь дефект (попытка {{ iteration }}): {{ input.summary }}"
        output_key: attempt
      - id: check
        type: verify
        tool: run_tests
        args: { cmd: "cargo test" }
        output_key: verification

verify

检查一个或多个条件。与其他步骤不同,检查不通过不是步骤错误:会返回 { "passed": false, "checks": [...] },以便可以对其分支(例如在循环的 until 中)。

字段 描述
checks 检查列表:{ type: tool, tool, args, expect } 或 { type: agent, template/instructions, prompt, output_schema }
require all(默认)或 any
output_key 将结果放到哪里 { passed, checks }
next 下一步的显式 id

对于工具检查 expect 描述了如何读取结果:没有 expect 时取工具的 success;否则取 pointer(data 中的 JSON Pointer, 否则取解析后的 content)加上 equals/truthy。代理检查必须 返回结构化的 { "passed": bool }。

  - id: v
    type: verify
    require: all
    output_key: verification
    checks:
      - type: tool
        tool: run_tests
        args: { cmd: "cargo test" }
        expect: { pointer: /passed, equals: true }
      - type: agent
        template: reviewer
        prompt: "Проверь изменения: {{ diff }}"
        output_schema:
          type: object
          properties: { passed: { type: boolean }, reason: { type: string } }
          required: [passed]

12.4 访问控制

工作流可以声明它涉及的模板——那么 限定范围操作员 只有在这些模板位于其范围内时才会启动它:

name: reports
templates: [rss-agent, summarizer]   # шаблоны, которые воркфлоу может трогать
steps: [ ... ]

run / get_run 对超出其范围的限定范围操作员返回 403;list 不会显示它们。

12.5 触发器

工作流可以无需显式 API 调用而启动:

name: reports
triggers:
  cron:
    - schedule: "0 0 9 * * *"        # cron: sec min hour dom month dow (UTC)
      input: { topic: daily }         # фиксированный input (по умолчанию {})
  webhook:
    token_env: WF_REPORTS_TOKEN       # опциональный bearer (рекомендуется)
steps: [ ... ]
触发器 描述
cron[].schedule cron 表达式(6 个字段,秒级分辨率)
cron[].input 每次运行固定的 input
webhook 启用 POST /v1/workflows/<name>/webhook;JSON 正文成为 input
webhook.token_env 带 bearer 令牌的 env;未设置/为空 → 所有请求被拒绝

12.6 HTTP API

Endpoint Auth 描述
GET /v1/workflows 管理员 工作流列表 + 最近运行(?runs=N)
POST /v1/workflows/<name>/run 管理员 启动;JSON 正文成为 input。返回 { ok, id }
POST /v1/workflows/<name>/webhook 自己的令牌 通过 webhook 启动(JSON 正文 → input)
GET /v1/workflows/runs/<id> 管理员 运行状态 + 步骤历史
POST /v1/workflows/runs/<id>/cancel 管理员 在下一个步骤边界停止运行(状态 cancelled,日志被移除)
GET /workflows 管理员 HTML 页面(启动 + 查看)

运行写入 logs/workflows.db(workflow_runs);状态 running → success / error,带有 per-step 记录(id、status、output、error、retries、 timings)。重启之间的状态不会持久化——只有运行历史。

12.7 从代理和工具启动

工作流可以通过 workflow_tool 从另一个代理启动:

kind: workflow_tool
name: run_triage
description: Запустить воркфлоу триажа.
workflow: triage            # дефолтный воркфлоу (перекрывается аргументом `workflow`)

参数:workflow(名称,配置时可选)和 input(对象)。 工具返回 run id;启动异步继续。

12.8 本地测试

agent-os module workflow run config/workflows/smoke.yaml '{"items":["Cargo.toml","README.md"]}'
# или input из файла (удобно на Windows):
agent-os module workflow run config/workflows/agent_demo.yaml --args-file args.json

tool-步骤使用内置 + 来自 config/agents/ 的 YAML 工具; agent-步骤使用来自 config/server.yaml 的提供者(或 DEEPSEEK_API_KEY)。从仓库根目录运行,以便 config/ 能够解析。

12.9 备注

12.10 持久性(durable)

默认情况下,运行状态只存在于内存中:重启时未完成的 运行会丢失(历史中保留 running)。标志 durable: true 启用 运行日志——引擎在每个步骤边界提交 scope 和位置 (对于 loop——在每次迭代时),并且在服务器启动时未完成的运行 从最后一个边界重新启动(logs/workflows.db,表 workflow_run_state)。

name: long-harness
durable: true
budget:                                  # опциональный бюджет на весь запуск
  max_per_turn: 8000
  max_per_minute: 60000
  max_total: 500000
steps: [ ... ]

语义是 at-least-once(与 durable 会话和 outbox 一样):未完成的步骤 或迭代会整体重放,因此步骤的副作用必须是 幂等的(tool-步骤的 idempotency_key 字段或 saga)。loop 内部的嵌套步骤 从当前迭代的开头重新执行,而 for_each——仅从 未完成的元素开始(已完成的元素取自日志,不会 重放)。

12.11 测试框架(隔离工作区)

harness: 块将工作流转变为“内部测试框架”——在隔离工作目录中带有循环(loop/verify)的持久化运行。示例:修复工单中的缺陷。

name: jira-bugfix
durable: true
harness:
  workspace:
    kind: git_worktree       # или dir
    repo: "."
    base: origin/main
    cleanup: on_success      # on_success (по умолчанию) | always | never
triggers:
  webhook: { token_env: JIRA_HOOK_TOKEN }
steps:
  - id: fix
    type: loop
    max_iterations: 8
    until: "{{ verification.passed }}"
    body:
      - { id: act,   type: agent,  template: bugfix-agent,
          prompt: "Исправь: {{ input.summary }}", session: fix }
      - { id: check, type: verify, tool: run_tests,
          args: { cmd: "cargo test" }, output_key: verification }

12.12 Work items

Work item——测试框架的规范化工作单元(工单、支付、事件)。按 (source, external_id) 去重;生命周期: new → claimed → running → done | failed | escalated。

来源(Jira/GitHub/邮件/webhook)发送 ingest——对同一 (source, external_id) 的重复 ingest 会更新内容,但不会重置状态:

POST /v1/harness/work-items
{ "source": "jira", "external_id": "PROJ-123",
  "title": "Fix login", "body": "...", "priority": 2, "metadata": { } }

API(模块 harness,表 harness_work_items):

方法 用途
GET /v1/harness/work-items?status=&source= 列表(过滤器,最新/优先在上)
GET /v1/harness/work-items/:id 单个 item
POST .../:id/claim 领取工作(lease;第二个 claim 失败)
POST .../:id/status 操作员手动更改状态
POST .../:id/run {workflow, input?} claim + 启动 harness 工作流;运行链接到 item

关联运行完成后,item 状态会自动更新:success → done、cancelled → new(返回队列),否则 failed。

12.13 测试框架注册表

测试框架集合由注册表定义——config/harnesses/<id>.yaml 文件。测试框架是与领域无关的内部循环(loop/verify),不一定针对代码;workspace 是可选的。注册表将人类描述与执行工作流和工作来源关联起来。

id: jira-bugfix
title: Bugfix из тикета
description: |
  Забирает дефект, воспроизводит, чинит в изолированном workspace,
  прогоняет тесты и отдаёт на ревью.
workflow: jira-bugfix          # workflow с блоком harness:
enabled: true
source:
  kind: jira                   # какой источник кормит (* = любой)
  auto_run: true               # новый work item запускает харнесс сам
  filter: { project: PROJ }    # опц. равенство по metadata work item
defaults: { priority: 1 }      # опц. значения по умолчанию