上线前 checklist
什么时候读:技能写完、交付前。逐条打勾,把未满足的列给用户。不跳条。 只核对适用的段:不碰数据表就跳过「数据表」段,不调别的技能就跳过「技能间调用」段。
形态与入口
- 入口是
async def execute(input_data, runtime_context)(名字、参数名、async 三项照抄;否则 create_skill / edit_skill 拒装),第二参数无默认值 - frontmatter
name匹配^[a-z0-9][a-z0-9_-]*$且与目录名一致 - 碰数据库 / 用 call_skill →
async def;直连从db_factory现开现还,不读ctx["db"] - 子进程型:日志走 stderr,stdout 只打最终 JSON;业务失败退出码 0 +
success:false - 模块内确有入口函数(漏了静默降级成文档型)
- 进程内 handler 里没有
argparse+__main__(会被判成 CLI)
输入
-
schema.json的required与实际必填一致,description写清取值范围 -
properties值都是对象,没有裸字符串 - 文件参数用约定字段名,直接用平台给的绝对路径,不再拼目录
- 不从
input_data读身份类字段 - 动作型:action / 参数键别名归一,别名表值集合 ⊆ 规范 action 集合(加断言),归一后回写
action_note - 动作型:无 action 返回
need_param=action+ 清单,不按入参形状默认成写动作 - 动作型:入口先拆
payload/input/args包装层 - 写动作的确认只认用户原话 / 「确认」前缀,预演返回回显收到的参数
- 数组/对象参数过一遍「可能是 JSON 字符串」掰形
上下文
- 固定词表的键直接判空,未写多余
.get(k, "") -
actor_is_admin/ 身份键为空的分支已处理,未把空值当最低档位放行 -
triggered_at当 float 用;attachments 读type(att_type是过渡别名);部门判断优先用actor_department_id - 不靠
channel判断调用来源(页面线会被覆盖);team_role缺失按无权限处理(页面线不注入) -
org_id缺失时返回error_type=config+non_retriable=true - 工号取
actor_emp_no(不读emp_no);行级过滤依赖它的技能在真库验过「非管理员能看到自己的行」,经 call_skill 路径也验过 - 部门路径匹配带首尾斜杠;判本级用末段
配置
- 密钥按四层顺序取(skill_configs[skill] → skill_configs → skill_args → ctx → env),未要求模型传密钥
- 文档中的凭证只用
$VAR引用,无「打印出来确认」步骤 - 非凭证常量名不含 key/token/secret/password/credential/auth 子串;自测按子串扫
- 需要配置:顶层
env:结构化声明(控制台表单读)+metadata.openclaw.requires.env/bins(执行前校验读),两处都写 - 不需要配置:写
env: [],避免正文嗅探冒出假配置项 - 技能目录里没有含密钥 / 内部数据的
.json.js.html等文件(/ui/asset免鉴权可下载,无面板也一样)
输出
- 失败必带
error;可自纠的标recoverable并给改法 -
non_retriable只用于部署级问题 - 部分成功显式标注成功/失败条数
- 每条错误信息过「照着它排查会走到哪」;数字能对账
- 产物返回
filename/local_path,未自拼下载链接 - 大结果放可卸载字段,关键少量字段放顶层
- 直显块:
display_md只会追加(display_only现行对话线未实现);不许改写的文本另在角色提示词里明令不复述 - 非幂等写操作不把 Timeout / Connection / OSError / DB 连接类异常裸抛(平台会整次重跑最多 3 次)
- 中间文件放工作区子目录,成功即删,失败保留并点名
- 给用户看的「下一步」不用指令式措辞,标注
for_user
数据库直连(自己读写库时;见 spec §7A / code-facts §3.7)
- 进程内
async,库访问收进单一_db(),每次从db_factory现开现还,没读ctx["db"] - 写操作
commit;跨语句原子才用一条会话(那时才手动 rollback),否则每条各自短会话 - 两次 DB 操作之间的慢 I/O(HTTP/调模型)期间手里无连接
- 表名全限定 + 标识符加引号;不裸建表;upsert 前查
pg_constraint不假设约束存在 -
_db做成唯一 seam 可 monkeypatch 离线自测;真库端到端验过一次 - 子进程 / 其它语言不直连;需落库拆成两个技能(落库那步进程内 async)
面板(带 ui:,或根目录有 panel.html / ui.html / index.html 才核)
- 本地面板:
ui:是技能目录内的.html相对路径且文件存在,建议放ui/子目录;不想要面板的技能根目录没有panel.html/ui.html/index.html - 本地面板:schema 写了
properties.action.enum,面板调的每个动作都在其中(网关只认它,不认 x-actions) - 本地面板:enum 里的写 / 删 / 结构类动作在 handler 内自鉴权(
actor_emp_no/actor_is_admin/ 业务条件),不依赖网关 - 本地面板:ui.html 只用
src=/href=相对路径;无/api、/xxx站点根路径、http(s)绝对地址;没有 CSSurl()与 JS 相对 import - 本地面板:app.js 完整 ready/init/invoke/invoke_result 握手,频道
skill_panel,ready必发 - 本地面板:无 localStorage/sessionStorage;无 fetch 直连后端;动作返回字段名与面板
res.data.xxx对得上 - 本地面板:入口目录树里没有敏感文件(静态资源免鉴权公开)
- 外部面板:
ui:是 http(s) URL;未生成本地面板文件 - 外部面板:技能说明提醒了对接方三件事(frame-ancestors 放行 + 接 panel_token + 用 PEM 公钥验签,
aud= 面板 URL 的 host[:port]、iss默认myinc-platform)
数据表
- 所有表由
create_table建,没有一列是 SQL 补的;没用 PRIMARY KEY / BIGSERIAL / JSONB / 自定义 id、created_at - 初始化幂等
- 表名全限定、写操作提交、保留字加引号、绑定参数不写
:name::type - SQL 通道:JSON 存 text;字段名符合标识符规则(禁
%);字符串字面量内无:紧跟字母/数字 - 不存在的表先查存在性;每组数据独立 try/except + rollback
- 批量按 1 / ≤100 / 文件导入 三档选路
- 真库端到端验过一次
- 技能名候选列表探测(
datatable在前、nocodb兜底),结果进环境自检 - 需要 own 范围的表,对应角色每条线都确认注入
allow_sql=False;未注入则不配 own - 链/定时任务里的删除,单次命中 ≤10 行
- 结构类操作(建表/改字段/删表)只给 owner/admin:技能线 handler 鉴权尚未恢复,需要时技能自己先判
team_role - 字段
type只用枚举里的 15 个(无 JSON / 高精度数字)
文档
- SKILL.md < 8000 字符,细节拆
references/并写明何时读 - frontmatter 是合法 YAML:值里出现「英文冒号+空格」的整句加引号(否则平台退回逐行解析,嵌套块全丢)
-
category是 13 个合法值之一;category/cost-tier/async/env写在顶层,不塞进metadata - 文档型:任何位置都没有无意放置的
openapi.*/endpoints_whitelist.*(会被判成 API 型) -
description说清「什么时候该调它」,带触发词 - 未 import 其它技能
- 动作型的 SKILL.md 明写「action 必须传中文原词」+ 三个调用示例;帮助表左列是触发场景
- 角色提示词(若有):明令不复述工具返回值;不许改的文本做成工具动作;可变配置先查再答;
positive-examples/negative-examples齐全
技能间调用
- 调 call_skill 前已 commit
- 收件人取自业务数据,未用 actor_* 当收件人
- 判失败看
ok/sent,未假定调用一定成功 - 未对 async 技能使用 call_skill
- 通知失败不回滚主业务
纪律
- 版本号每次发布 bump
- 有一个不经平台、直接读磁盘的部署核对脚本
- 每修一个 bug 留一条回归用例