跳到主要内容

输出:返回信封、直显块、产物、结果卸载、异步

什么时候读:写返回值、报错、要在前端直接显示表格/清单、产出文件、返回很大、或任务耗时很长时。

目录​

  1. 返回信封(成功/失败字段)
  2. 失败预算:怎样会被拉黑
  3. 直显块 display_md / display_only
  4. 产物(文件、图片、视频)
  5. 中间文件不是产物
  6. 结果卸载(>12000 字符)
  7. 异步技能(async: true)

1. 返回信封​

成功:

{"success": True, "data": {...}} # 或按技能语义组织字段

失败字段:

字段语义
success / okFalse = 失败。平台据此翻转「假成功」;缺省或 None 不算失败
error面向模型的原因。失败必须给,为空会被平台补成无意义文案。平台取原因的顺序:success=False 时 stderr → error → msg → user_message;ok=False 时 error → msg
user_message面向用户的说明,可直接转述
need_param缺失或形状错误的参数名
error_typerecoverable(改参数可成功)/ config(配置/授权)/ transient(抖动)。recoverable retryable need_input need_param 四个值(或带 need_param 键)= 可恢复:喂回模型自纠,不计失败预算
non_retriabletrue = 同参数重试必然失败,平台把该技能本轮拉黑。只在「不可恢复的失败」上生效——同时标了可恢复类 error_type / need_param 时被忽略
redirect建议改用的 action / 技能
action_note做过 action/参数键别名归一时回写,成功失败都可带

约束:

  1. 可自纠的错误必须标 recoverable 并给改法。不得写「内部错误 / 联系管理员」——模型会照抄给用户然后停手。
  2. 部分成功必须显式标注成功与失败条数,不得只返回 success=true。
  3. non_retriable 只给部署级问题(缺模块、配置错、授权不足)。连接抖动、对象过期不要标。
  4. 每条错误信息过一遍「照着它排查会走到哪」。数字要能对账,不给过滤后的「共 0 行」。
  5. 自报失败时带上被调用方的原始信封(upstream 字段)。
  6. 配置类错误文案(含「未配置 / 密钥 / api_key / 401」)不要写操作引导(「请先 save_skill_param…」)——平台会截掉并换成「改用其它技能」的 _hint。
  7. 不要用 code 字段表达失败:非 0/200 的 code 只记日志不翻转。
  8. 「做了一部分」用 success: True + completed: False 或 warning: "...",前端显示为 partial。
  9. 写入后读回核验,不信「写成功」。返回 verified_total。
  10. 别让超时 / 连接类异常裸抛到平台:对话线对 TimeoutError ConnectionError OSError 及 SQLAlchemy OperationalError / InterfaceError 会自动整次重跑技能,最多 3 次(该技能本轮已失败过,或本角色近 30 分钟内最近 2 次调用都失败——熔断——则只跑 1 次)。 非幂等写操作要么做成幂等,要么自己捕获并返回信封。库结构类错误被平台标 non_retriable。

2. 失败预算(roles_chat_helpers,核对 2026-10)​

项现行值说明
单技能失败上限FAIL_PER_TOOL,默认 4交给 GuardChain,到限即本轮封禁该技能(源码注释仍写「2 次」,以值为准)
全局失败上限FAIL_TOTAL,默认 8本轮累计到限强制收尾
死循环同一 (工具, 参数) 第 4 次调用(计数 >3)直接停用该技能,返回 non_retriable
立即拉黑不可恢复失败 + non_retriable: true本轮不再给模型这个工具
不计数可恢复失败(见 §1 error_type)、参数校验失败、守卫拦截喂回模型自纠

技能要保证「同参数重试无意义」时明确报错(non_retriable 或 need_param),不要让模型原样重试。

3. 直显块​

{"display_md": "**结果**\n\n| 列 | 值 |\n|---|---|\n| a | 1 |",
"display_only": True} # 可选

display_md 在富前端渠道(会话 channel 为 web / web_admin)追加到回复正文之后(模型正文里已逐字包含同一段时不重复追加), 文本渠道回退给模型转述。用于表格、地图链接、清单,避免模型复述时丢失或篡改数据。

display_only:现行对话线未实现。 规范定义它为「替换模型正文、只呈现 display_md」,但 roles_chat_helpers 里只留了一个未使用的占位变量,没有任何代码读这个键(核对 2026-10)。现状 = 无论写不写都只是追加。

  • 可以照规范写(将来实现后自动生效),但不能依赖它去重或防篡改。

  • 真要「逐字不许改」,在角色提示词里明令不复述该工具返回,或把这段文本做成工具动作的唯一输出。

  • 仅富前端渠道生效。

  • 只影响展示与落库正文,不影响工具执行。

  • 结果被卸载(§6)时 display_md 保留前 15000 字(TOOL_DISPLAY_KEEP_CHARS),超出截断并注明全文位置。

4. 产物​

{"ok": True,
"filename": "report.xlsx",
"local_path": "/abs/workspace/report.xlsx",
"media": "file", # image / video / audio / file
"images": [{...}], "videos": [{...}], "files": [{...}], "audios": [{...}]}

平台后处理链(技能不用自己做):

  1. 远程 URL 物化:明确字段(urls image_url(s) video_url(s) audio_url(s) file_url(s) output_url result_url oss_url cdn_url download_url abs_url)无条件下载;含糊字段(url link href src image video audio file)和正文里的链接先探 Content-Type,网页/JSON 跳过,最多 8 个。单文件默认 200 MB 上限。已有 local_path 的条目跳过。
  2. 产物登记:工作区里的文件登记为会话附件,正文自动补下载链接。没在返回里声明的文件也兜底采集(.py/.js/.sh/.log/.tmp/.srt/.pyc 除外,除非用户本轮明确要脚本)。
  3. 渠道投递:按 media / 扩展名分发到 send_image / send_video / send_file。

技能只需保证文件真实落在会话工作区(ctx["workspace_dir"],由 executor 注入;子进程同名键或 SKILL_WORKSPACE), 或返回可下载 URL,不要自己拼下载链接或域名。 call_skill 调的子技能不经过这条链,见 call-skill.md。

5. 中间文件不是产物​

兜底采集(backfill_artifacts)只看工作区顶层文件、只在返回里没有 filename/local_path 时介入、跳过 . 和 toolout_ 开头。它不区分「给用户的」和「技能自用的」。导入 JSON(*_import_*.json)、删除备份、临时拼接文件落在工作区根目录都会被当附件展示(踩坑 #63:20 个导入文件全被展示)。

规则:中间文件放工作区子目录;成功即删;失败保留供排查并在返回里点名。

6. 结果卸载(_offload_large_result)​

成功且 JSON 序列化后超过 TOOL_RESULT_OFFLOAD_CHARS(默认 12000)字符的 dict 返回,整份写到 <工作区>/.toolout/toolout_<技能>_<8位hex>.json(隐藏子目录,不会被当产物),上下文里只留收据:

收据里有什么规则
ok offloaded skill path chars schema_keys(前 20 个键) _hint固定
display_md保留,超 15000 字截断
顶层标量、≤500 字的字符串、序列化 ≤1000 字的小 dict原样透传
每个非空列表 k变成 k_count(条数)+ k_sample(前 3 条,每条截 300 字)
filename local_path download_url abs_url media images videos files audios透传(媒体列表 <12000 字、其余 <2000 字时)

所以大结果放列表字段(如 rows),关键少量字段(状态、计数、产物路径)放顶层标量。 失败返回不卸载;读取 / 翻页类内置工具不卸载,自带分页。

7. 异步技能​

耗时超过对话可等待范围的生成类技能,frontmatter 声明 async: true:

  • 非 web 渠道立即返回 {"ok": true, "async": true, "message": "..."},真实执行转后台,完成后按发起渠道主动推送产物。 web / web_admin 仍同步;但组织委托会话(external_id 以 orgdeleg: 开头,渠道虽是 web_admin)按非 web 处理、转后台。
  • 后台 ctx:role_id org_id chat_mode=True session_id is_admin + 发起时的固定上下文段(只含词表键)
    • db(懒会话)+ db_factory + 预取并平铺的配置 + abort_signal=None,再经 executor 补 workspace_dir / provider / call_skill。 没有 team_role(datatable 等按 member 处理)、request、channel_registry;user_id / team_id 为空。
  • 后台结果同样经「假成功翻转」;只回 filename 时平台按 <工作区>/<filename> 补 local_path;远程 URL 会物化。 没有任何媒体产物时推送 message / result / text 文本。
  • async: true 的技能不要用 call_skill 调:会同步执行并占用父技能的墙钟。

不声明 async 的技能有两层墙钟(核对 pipeline.py / roles_chat_helpers.py):

  • 内层 · 技能墙钟(pipeline.resolve_wall_sec,对话线、链、定时任务都生效):技能自声明 wall_sec / timeout_sec (或 metadata.openclaw.timeout)> policy 值 > 默认 SKILL_WALL_DEFAULT = 600 秒,一律不超过 SKILL_WALL_HARDCAP = 1800。 超时返回「技能执行超过 N s 已中止」。不声明就是 600 秒——长任务要么声明,要么 async: true。
  • 外层 · 工具墙钟(对话线 TOOL_WALL_SEC,默认 1800 秒)。整轮循环 MAX_LOOP_SEC 会被抬到不小于它 +60,实际还受反向代理超时约束。 子进程另有自己的 60 秒默认超时。 长循环轮询 ctx["abort_signal"]。别按固定秒数写死逻辑。