跳到主要内容

技能开发规范(原文全文)

什么时候读:其它 reference 没覆盖到、或需要核对原文措辞时。本文是规范原文,未做改写。 日常写技能请走 SKILL.md 的路由,只读对应的分段 reference;本文是兜底与仲裁依据。

面向技能作者(含 AI 代码生成)。本文档定义:技能的形态与目录结构、平台注入的 运行时上下文、配置与密钥的取用、文件与产物的收发约定、返回信封、数据表操作。 按本文档编写的技能可直接被平台加载运行,并兼容主流 Agent Skill(OpenClaw / Hermes)布局。

修订记录:2026-08 对照 datatable 技能实现与踩坑档案 #54–#72 补订,补订处标 ★2026-08 补。


1. 技能形态​

skill_type说明入口是否暴露给模型
handler代码型(默认)。平台按 ExecType 执行代码见 2.3是(trigger.type=explicit)
skill_md文档型。平台不执行代码,把 SKILL.md 交给隔离子代理按文档执行无是
knowledge纯知识型,只参与检索无否(不进工具清单)

handler 支持的 ExecType 与取上下文的方式:

ExecType触发条件输入上下文能否拿 db
PYTHON(进程内)exec.config 是 .py,模块内有入口函数函数参数函数参数仅 async def
PYTHON(CLI)脚本同时含 argparse 与 __main__,或运行中抛 SystemExitargvSKILL_CONTEXT 环境变量否
SHELL / INLINE_SHELL / NODE / RUBY / GO按 exec.primary.typeargv + SKILL_INPUTSKILL_CONTEXT否
HTTPexec.config 指向 TOML请求体不适用否
BROWSER / MCP———未实现

db 是活的 SQLAlchemy AsyncSession,不参与序列化:子进程的 SKILL_CONTEXT 里没有它, 同步入口被平台显式置为 None。需要数据库的技能必须写成进程内 async Python。


2. 技能目录与文件​

2.1 目录结构​

<skill_name>/
├── SKILL.md # 必需。frontmatter + 说明文档
├── handler.py # handler 型必需
├── schema.json # 可选。完整 JSON Schema,优先级高于代码内常量
├── references/ # 可选。长文档拆分到这里
│ └── *.md
└── *.toml # 可选。HTTP 型的 exec 配置

平台按以下顺序定位 SKILL.md,取第一个存在的:

<skill_dir>/<frontmatter.skill_md 或 SKILL.md>
<skill_dir>/<origin_path>/{SKILL.md, skill.md}
<skill_dir>/optional-skills/<name>
<skill_dir>/docs/<name>
<skill_dir>/README.md

2.2 SKILL.md frontmatter​

---
name: fetch_price
description: 一句话说清「什么时候该调它」,模型靠这句选工具
source: builtin # builtin / shared / market
category: data # 会以 [category] 前缀出现在工具描述里
trigger-type: explicit # explicit 才会进模型的工具清单
cost-tier: low
tags: finance, price
async: false # true = 长耗时技能,见 §8
force_raw_prompt: false # true = 用用户原文覆盖 prompt 字段,见 §3.4
metadata:
openclaw:
primaryEnv: XXX_API_KEY # 主凭证变量名
requires:
env: [XXX_API_KEY] # 也支持 [{name: X, required: true}]
bins: [curl] # 需要的命令行工具
---

requires 在技能执行之前校验:缺 env 直接返回 needs_config 提示,不进入执行; 声明了 exec_mode: shell 或 runtime 的技能不校验 bins(在容器里跑,宿主机有无无关)。

文档长度:skill_md 型的 SKILL.md 超过 8000 字符会被截头部注入,尾部丢失。 主文档保持精简,细节放 references/,在正文写明「执行 X 前先读 references/xxx.md」。 read_skill_file 单次返回上限 40000 字符。

2.3 handler.py 入口​

async def execute(input_data: dict, runtime_context: dict) -> dict:
...
项规则
函数名execute / run / handler / main / handle,按此顺序取第一个存在的
传参按必填位置参数个数绑定:0 → f();1 → f(input);2 及以上 → f(input, ctx)
第二参数不得设默认值,否则 runtime_context 丢失
返回dict;非 dict 被包成 {"result": ...}
异步需要 ctx["db"] 时必须 async def
无入口函数平台回退去找同目录/上级的 SKILL.md,把技能静默降级为文档型(症状不是报错)

子进程型取上下文:

input = json.loads(os.environ.get("SKILL_INPUT") or "{}")
rc = json.loads(os.environ.get("SKILL_CONTEXT") or "{}")

stdout 输出约定(子进程型),平台按顺序尝试解析:整个 stdout 为 JSON → 最后一行为 JSON → 媒体路径行 → 兜底包成 {"output": "<原始 stdout>"}。 日志一律走 stderr;退出码非 0 判为技术失败。子进程默认超时 60 秒。


3. 输入​

3.1 input_schema​

标准 JSON Schema,放 schema.json 或代码内常量(schema.json 优先)。

{
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "标的代码,如 BTC / AAPL"},
"image": {"type": "string", "x-input-type": "image"},
"limit": {"type": "integer", "default": 100}
},
"required": ["symbol"]
}

约束:

  • properties 的值必须是对象,不能写成裸字符串("symbol": "string" 会被平台修正, 但不同 provider 的元校验可能直接拒绝整批工具)。
  • additionalProperties / items 只能是布尔或对象。
  • description 是模型填参的唯一依据,写清取值范围和格式,不要只写字段名。
  • 必填校验:缺字段和传空值("" / [] / {} / 纯空白)都判为未提供, 0 和 false 是合法值。校验不通过时平台把错误回给模型自纠,不会静默补默认值。

3.2 子命令​

技能可声明 cli_commands: {子命令: schema}。数量在 1~45 之间时,平台把技能扇出成 多个工具名 <skill>__<sub>;执行前还原为 skill + input["_subcommand"], CLI 型技能的 _subcommand 会被放到 argv[0](argparse 的硬要求), 参数校验也改用该子命令的 schema。

3.3 文件参数​

路径归一:以下字段名的值会被平台解析成绝对路径 —

input output file path video audio image src dst
inputs files videos images clips sources paths bgm input_file file_path

规则:绝对路径原样保留;相对路径只取 basename,挂到会话工作区 (模型拼的目录前缀一律丢弃,避免双层路径)。output / dst / output_path / out 视为待生成文件,无条件落到工作区;其余字段的文件不存在时保留原值(报错更清楚)。

附件引用:模型看到的素材清单里每项带引用 ID(img_1 / file_1)。平台在执行前:

  1. 字段值或数组项精确等于引用 ID → 替换成真实 local_path;
  2. code / command / script / cmd / instruction / shell / bash 这类字段内的 子串引用也会替换,路径统一转 posix 正斜杠,含空格时自动补引号;
  3. 数组字段自动去重。

技能内直接使用拿到的路径即可,不要再拼目录。

3.4 prompt 字段​

生成类技能(文生图/文生视频等)可声明 force_raw_prompt: true,平台会用用户本轮原文 覆盖 schema 中第一个必填字段。委托场景(上游产出而非用户直给)不覆盖。

3.5 动作型技能的入参归一 ★2026-08 补​

以 action 分发多种操作的技能(datatable、各分析师角色技能等),模型填参会系统性地漂移: 把中文动作名翻成英文、把参数键翻成英文、把「确认」翻成 confirmed: true、把用户整句 「确认初始化」塞进 action。文档写得再清楚也挡不住,归一必须在技能侧做,文档侧只是补充。

  1. action 别名归一:大小写、下划线/连字符、常见英文同义词、中文夹字,统一映射到 dispatch 里真实存在的规范 action(别名表的值集合必须 ⊆ 规范 action 集合,加自检断言)。 归一发生时在返回里回写 action_note: "initialize → 初始化",让模型下次直接用对。
  2. 参数键别名归一:record/row/values → data、filter → filters、order_by → sort、 confirm/approve/确定 → 确认 等,在 dispatch 之前统一归口;同样回显归一结果。
  3. 无 action 一律拒绝:返回 need_param=action + 支持的 action 清单。不得按入参形状 默认成某个写动作——更新即使幂等也是写动作,定时任务必须显式传 action。
  4. 确认只能来自用户原话:写动作采用「试算/预演 → 确认 → 执行」三段时,确认=true 只接受两种来源——模型按用户指示显式传入,或 action 以「确认」开头(去前缀归一到 写动作并补 确认=true)。技能不得自行补确认,也不得把「预演成功」视为确认。
  5. 预演返回必须回显收到的参数,并写明「缺 确认=true 所以只预演」。同一动作同样参数 连续出现 3 次一模一样的预演卡片,说明参数没翻对,不是技能坏了;回显就是让模型不用猜。
  6. 返回给用户看的「下一步」是给用户的,不是给模型的;技能在返回里不要用指令式措辞 (「下一步:说拉数据」会被模型当成自己的指令直接执行),改为「可继续的操作:…」并 明确标注 for_user。

4. runtime_context(全局注入)​

4.1 键的分组​

runtime_context
├── 固定上下文段 —— 谁、从哪来、什么时候、说了什么(恒存在)
├── 平台注入段 —— 组织/角色/会话/权限/会话对象
└── 配置段 —— skill_configs / skill_args 及其扁平副本

4.2 固定上下文段​

契约:下列键恒存在。无值时给对应类型的空值,不缺键。技能判空即可, 不需要 .get(k, default)。

key类型空值说明
raw_inputstr""用户本轮原文
attachmentslist[dict][]见 4.5
actor_namestr""姓名;无档案时回落为渠道昵称,不得用于实名核验
actor_emp_nostr""工号
actor_departmentstr""部门(裸文本,非组织树节点)
actor_department_pathstr""部门全路径,形如 /总部/销售中心/销售一部/。格式与匹配方式见 4.5
actor_positionstr""职位
actor_titlestr""职级。审批阈值判断用此字段,不用 position
actor_phonestr""电话
actor_emailstr""邮箱
actor_tagslist[str][]员工标签
actor_keystr""发起人定位键(contact_key)
channelstr""web / web_admin / 第三方渠道名
triggered_atfloat0.0触发时间,秒级时间戳,不是 ISO 串
triggered_at_strstr""YYYY-MM-DD HH:MM,UTC
actor_idstr""内部主键
actor_is_adminboolFalse管理员代填标记,见 4.4

★2026-08 补 工号键只有 actor_emp_no(actor_ctx.FIXED_CTX_FIELDS 是唯一事实源, 没有 emp_no),且 call_skill 只透传 actor_*。datatable handler 曾读 emp_no, 经 call_skill 调用时工号必为空,row_scope="own" 的表一律 fail closed——已改为 读 actor_emp_no 并回退 emp_no。新技能取工号一律用 actor_emp_no,不得自造别名。

4.3 平台注入段​

key类型说明
org_idstr组织 ID,取自 Agent.org_id 原值,可能为空。数据表操作遇空应拒绝执行;若技能自行按 org 查档案/组织表,须按 4.6 补伪 org 兜底
role_idstr当前角色
session_idstr当前会话
user_idstr当前用户,可能为空
team_idstr所属团队,可能为空
team_rolestrowner / admin / member。结构性操作的鉴权依据
is_adminbool会话是否管理员会话
chat_modebool对话线标记。为 True 时启用删除确认等交互式保护。★2026-08 补 现状:pipeline.build_runtime_ctx 对对话线/定时任务/链一律写死 True,删除确认闸三条线同样生效,见 §7.7
allow_sqlbool★2026-08 补 SQL 通道开关,缺省 True。现状:平台不在任何线注入 False,对话线可直接执行 SQL。开关保留给将来按角色收紧用;设了 False 会随 call_skill 下传(§9.1)
workspace_dirstr会话工作区绝对路径
dbAsyncSession数据库会话,仅进程内 async 入口可用
db_factorycallable会话工厂。长任务应按操作自开短会话,而不是长期持有 db
abort_signalasyncio.Event / None用户点了停止时被 set,长循环应轮询它
channel_registryobject渠道注册表(仅对话线;异步线为空)
requestobjectFastAPI Request(仅对话线;异步线为空)
skill_config.sandboxdict沙箱配置

以 _ 开头的键(如 _meta)为平台内部字段,不注入子进程,技能不得依赖。

4.4 管理员代填​

actor_is_admin=True 时,平台在注入前清空下列身份键,防止按管理员本人的部门/职级 判断申请人:

清空:actor_title, actor_department, actor_department_path, actor_position,
actor_tags, actor_key, actor_id, actor_emp_no
保留:actor_name, actor_phone, actor_email(承担审计的「谁填的」)

凡使用 actor_title / actor_department 做判断的分支,必须显式处理空值, 不得把空值当最低档位放行:

if ctx.get("actor_is_admin") or not ctx.get("actor_title"):
return {"success": False, "need_param": "actor_title",
"user_message": "取不到申请人职级,请补充申请人工号后重试。"}

4.5 actor_department_path 格式​

/根部门/…/上级部门/本级部门/
例:/总部/销售中心/销售一部/
项定义
分隔符/,首尾也带
顺序顶层在前,本级在末
是否含本级含。末段即 actor_department
是否含组织根不含组织名。首段是组织树的顶层部门
空值"":无部门、无 org_id、或解析失败
部门不在组织树退化成单段 /<部门名>/。与「本就只有一级部门」不可区分
停用部门保留在路径中(停部门不动员工档案,过滤会让链断在中间)
深度上限20 层,超出从顶端截断(防脏数据成环)
代填时被清空(见 4.4),部门类判断一律走不到

匹配必须带首尾斜杠:

if "/销售中心/" in ctx["actor_department_path"]: # 正确
if "销售中心" in ctx["actor_department_path"]: # 错误:会误命中「销售中心部」

判「是否本级」用末段,不要用 in:

segs = [x for x in ctx["actor_department_path"].split("/") if x]
current = segs[-1] if segs else ""

待核对:工作流线的该字段由 bridge 独立注入,与本节实现是否同源尚未确认。 若两侧不一致,同一条规则在对话线和工作流线的匹配结果会不同。

4.6 org_id 与伪组织兜底​

runtime_context["org_id"] 是 Agent.org_id 的原值,未做兜底,可能是空串。 但平台的身份/组织解析在其为空时会兜底成伪 org r_<role_id>, ChatUser / OrgDepartment 的行就是按那个值存的。

因此技能若自行按 org 查档案表或组织表,必须用同一兜底,否则一行都查不到且不报错:

org_id = (ctx.get("org_id") or "").strip() or f"r_{ctx.get('role_id', '')}"

数据表操作(§7)不适用本兜底:org_id 为空即配置异常,应直接拒绝执行, 不得用伪 org 拼出一个新 schema。

4.7 attachments 项结构​

{"type": "image", # 新技能只读此字段
"att_type": "image", # 过渡期别名,与 type 同值,两版本后移除
"filename": "报销单.pdf",
"local_path": "/abs/path/...", # 可能为空
"url": "https://..."} # 可能为空

清单包含本轮上传、历史上传、以及此前技能生成的产物。

4.8 身份不可信输入​

工号、角色、组织、团队角色一律取自 runtime_context。input_data 中的同名字段视为 模型可伪造,必须忽略。


5. 配置与密钥​

5.1 注入结构​

平台按角色隔离存储技能参数,执行前注入:

ctx["skill_configs"] = {"<skill_name>": {"api_token": "...", ...}, "api_token": "...", ...}
ctx["skill_args"] = {"api_token": "...", ...}
ctx["api_token"] = "..." # 顶层扁平副本

同一份值会出现在四处(技能名嵌套、skill_configs 扁平、skill_args 扁平、顶层), CLI 风格的 --api-token 归一为 api_token。技能按下列顺序取,取到即止:

def _cfg(ctx, key, skill_name):
for src in (ctx.get("skill_configs", {}).get(skill_name) or {},
ctx.get("skill_configs") or {},
ctx.get("skill_args") or {},
ctx):
v = src.get(key)
if v is not None and str(v).strip():
return str(v)
return os.getenv(key, "")

5.2 凭证​

  • 名字含 key / token / secret / password / credential / auth 的参数 自动加密存储,且从暴露给模型的 schema 中摘除——模型不填,平台执行时注入。 技能不得要求模型传密钥。 ★2026-08 补 匹配是子串不是后缀:技能内的大常量名(如 PARAM_KEYS、AUTH_HEADERS) 也会被扫描命中并被摘除/加密。非凭证的常量名避开这些子串;自测脚本同样要按子串扫。
  • 凭证只能由用户在对话中直接提供:平台校验该值必须出现在用户本轮原文中, 否则拒绝保存。技能不得自行生成或从历史拼凑凭证。
  • 子进程/容器型技能的凭证走环境变量,文档中用 $VAR 引用,不展开、不打印。

6. 输出​

6.1 返回信封​

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

失败时:

字段语义
success / okFalse = 失败。平台据此翻转「假成功」,缺省或 None 不算失败
user_message面向用户的说明,可直接转述
error面向模型的错误原因。失败必须给原因,为空会被平台补成无意义文案
need_param缺失或形状错误的参数名
error_typerecoverable(改参数可成功)/ config(配置或授权问题)/ transient(抖动)
non_retriabletrue = 相同参数重试必然失败。平台会把该技能拉黑本轮
redirect建议改用的 action / 技能
action_note★2026-08 补 可选。技能对 action / 参数键做过别名归一时回写归一结果(见 §3.5),成功失败都可带

约束:

  1. 可自纠的错误必须标 recoverable 并给出改法,不得表述为「内部错误 / 联系管理员」 ——模型会照抄给用户然后停手。
  2. 部分成功必须显式标注:在返回里写明成功与失败条数,不得只返回 success=true。
  3. non_retriable 只给部署级问题(缺模块、配置错、授权不足)。连接抖动、 对象过期这类下一秒就能好的错误不要标,否则技能被拉黑。
  4. 失败预算:同一技能连续失败 2 次被本轮封禁,累计 4 次强制收尾;同一 (工具, 参数) 组合出现 3 次判定死循环。技能应保证「同参数重试无意义」时明确报错。

6.2 直显块​

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

display_md 会在富前端渠道(web / web_admin)直接追加到回复正文,文本渠道回退给模型 转述。用于表格、地图链接、清单类结果,避免模型复述时丢失或篡改数据。

display_only(可选,默认 false) 控制 display_md 与模型正文的关系:

值语义用在哪
省略 / false追加:display_md 接在模型正文之后(历史行为)直显块是对模型解读的补充(图表、附表,正文仍需模型说明)
true替换:丢弃模型本轮自述的正文,只呈现 display_md直显块本身就是完整答案,且逐字不许被改写(帮助、规则/清单、状态表、审计流水)

判据是「这段 display_md 需不需要模型再补一句话」:不需要就置 true。

置 true 的两个动机,缺一即可:去重——模型常会自己也写一版平行答案,与直显块措辞 不同、无法靠子串去重,两份都显示就是前端重复;防篡改——凡是阈值、规则、清单这类 "一个字都不许改"的内容,不该经过模型的嘴转述(模型会把 ADX>25 说成 >30)。

约束:

  • 仅富前端渠道(web / web_admin)生效;文本渠道无论此标记都回退模型转述。
  • 一轮内多个技能都产出直显块时,第一个 display_only=true 的块会清掉同轮之前已追加的 普通直显块,只保留独占块(及其后的产物链接)。混用需注意技能调用顺序。
  • 只影响展示与落库正文,不影响工具执行、来源标注、产物采集。

6.3 产物​

技能产出文件时,返回下列字段(顶层单产物 / 数组多产物均可):

{"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。

技能只需保证文件真实落在工作区(或返回可下载 URL),不要自己拼下载链接或域名。

★2026-08 补 中间文件不是产物。 兜底采集不区分「给用户的」和「技能自用的」—— 导入用的 JSON(*_import_*.json)、删除备份、临时拼接文件落在工作区根目录,都会被当附件 展示给用户(踩坑 #63:20 个导入文件全被当作产物)。规则:中间文件放工作区子目录; 成功即删;失败保留供排查并在返回里点名。

6.4 结果卸载​

成功且序列化后超过 12000 字符的返回会被写入工作区文件,上下文里只保留收据 (offloaded=True、文件名、字段名清单、数组计数与首条样例)。以下字段会被保留在收据里:

filename local_path download_url abs_url media ok images videos files audios display_md

因此大结果要放在可被卸载的字段里,关键的少量字段(状态、计数、产物路径)放顶层。 读取类工具(file_read 等)不做卸载,应自带分页。


7. 数据表操作​

技能不自建数据库连接、不自拼建表 DDL。所有数据表操作通过统一入口下发:

input_data = {"action": "<操作名>", "payload": {...}}

payload 中的参数也允许平铺在顶层,由入口自动归拢。

★2026-08 补 统一入口的技能注册名在不同平台可能不同(线上为 datatable,历史为 nocodb)。 技能侧写死候选名列表逐个探测(list_tables 回「未注册」换下一个),命中即缓存, 探测结果进「环境」自检和连接类错误的信封里;确认后把真名放第一位。

7.1 存储模型​

项规则
组织隔离每个 org 一个 PG schema:org_data_<org_id 去非字母数字并小写>
逻辑表schema 下的真实表,表名即中文名
系统列id(bigserial 主键)、created_at 由平台创建,不得自建、不得写入
业务语义字段类型/必填/唯一/选项/精度存于元数据表,不从 information_schema 推断
标识符中文、字母、数字、下划线;1~50 字符;不得数字开头
外键不使用

7.2 权限模型​

表级:授权表是「例外表」不是白名单——某表无授权记录即全开;多角色时权限取并集、 行范围取宽;若某表存在未配置的角色,该表按全开处理。

行级:row_scope="own" 时按归属字段 = 当前操作人工号强制过滤,三处收口缺一不可: 筛选注入(query / aggregate / update_by / delete_by)、id 条件追加(update / delete / delete_batch)、写入盖值(insert / insert_batch / insert_from_file / upsert)。 附加约束:归属字段必须真实存在否则拒绝;取不到工号 fail closed;归属字段不得被 update 修改;行数统计同样受行级过滤约束;row_scope="own" 的角色禁用 SQL 通道。

操作级:结构类(create_table / add_field / update_field / delete_field / delete_table) 要求 team_role ∈ {owner, admin};SQL 类要求 allow_sql=True;批量脚本仅管理员线。

★2026-08 补 现状与本节的两处出入,技能作者不要依赖:

  1. datatable handler 里「结构类要 owner/admin」和「SQL 建表要 owner/admin」两段判断 当前被注释掉了,任何角色都能建表/改结构。是否恢复由平台决定;技能不得把 「非管理员建表会被拒」当作自己的安全边界。
  2. 「row_scope=own 禁 SQL」在代码里不是按 row_scope 判的,只看 allow_sql (缺省 True,当前无任何线注入 False)。所以 own 范围的行级隔离在 SQL 通道前是 不成立的——裸 SELECT 可绕过行过滤。这是已知的、有意保留的现状:对话线允许即兴 SQL 的价值高于行级隔离。将来要对某角色启用 own 范围表,必须同时给该角色所在的 每条线注入 allow_sql=False,否则隔离是假的。
  3. allow_sql 已加入 call_skill 透传白名单(§9.1)。现状下它是空操作;一旦某条线 设了 False,子技能会继承而不是以缺省 True 开回来。

7.3 操作清单​

action必填参数返回
list_tables—[{id, title, schema, qualified}]
list_columnstable_id[{title, uidt, required, options, precision, scale}]
querytable_id{list, pageInfo}
aggregatetable_id, metrics{rows, group_by, metrics}
inserttable_id, data写入后的完整记录
insert_batchtable_id, records{inserted, skipped, failed, errors, verified_total}
insert_from_filetable_id, file同上。可选 mapping({目标列: 源键})、defaults(每行固定值)、on_conflict=skip ★2026-08 补
upserttable_id, key_fields, data 或 records{inserted, updated, failed, errors, verified_total}
updatetable_id, row_id, data更新后的完整记录
update_bytable_id, filters, data{updated}
deletetable_id, row_idtrue
delete_batchtable_id, row_ids{deleted, requested, backup_file}
delete_bytable_id, filters{deleted, backup_file}
sql_querysql{rows, row_count, sql}
sql_executesql{affected, rows?, created?, table_id?}
create_tabletable_name, fields{id, title};表已存在时返回已有表并带 already_exists=true ★2026-08 补
add_fieldtable_id, field字段定义
update_fieldtable_id, field_title, patch字段最新定义
delete_fieldtable_id, field_title{deleted, field}
delete_tabletable_idtrue

table_id 是 UUID 不是表名;缺失时入口返回可用表清单供选择。 ★2026-08 补 缺 table_id 但给了 table_name(或 table / table_title)时,入口按 title 精确匹配自动解析成 id,不额外耗一轮;匹配不到才返回清单。

7.4 参数规范​

filters:数组,项间 AND(需要 OR 用 sql_query)。

[{"field": "部门", "op": "eq", "value": "研发"},
{"field": "薪资", "op": "between", "value": [8000, 20000]}]

op:eq ne gt gte lt lte contains in(数组)between(两元素数组) is_null not_null(省略 value)。不得写成字段映射 {"部门": "研发"}; 字段不存在时报错,不静默丢弃;值里残留未解析的引用变量({xxx} / $steps.)时拒绝执行。 ★2026-08 补 入口对字段映射形状做了容错(转成 eq 条件),但数组里无法解析的项 (字符串、空串)会被静默丢弃,丢光了就是全表返回——写 filters 时按规范形状写,别赌容错。

metrics:[{"field": "租金", "op": "sum", "alias": "总租金"}], op ∈ {sum, avg, max, min, count},count 可省略 field。

sort / limit / offset:sort 单字段,前缀 - 降序,默认 id DESC; query 上限 1000(默认 100),aggregate 上限 10000(默认 1000)。

写入:data 是单条对象不得传数组;records 是对象数组; on_conflict=skip 要求表上已有唯一约束;upsert 必须给 key_fields。

批量路径:1 条用 insert;2 ~ 100 条用 insert_batch;超过 100 条必须先把数据 一次性整份写入工作区 JSON 文件,再用 insert_from_file 导入,不得拆成多次 insert_batch。

★2026-08 补 两条补充:

  • insert 的 data 传数组时入口会放行并按批量处理,但这条路没有 100 条上限检查 (insert_batch / upsert 有)。大批量不得借 insert 绕过文件线。
  • insert_from_file 的导入文件由调用方技能生成,也由调用方负责清理:放工作区子目录, 导入成功后删除,失败保留(见 §6.3)。datatable 本身不删文件。

7.5 字段类型与写入值​

逻辑类型:文本 长文本 整数 数字 日期 日期时间 时间 勾选 单选 多选 邮箱 电话 链接 JSON。金额 / 百分比 / 高精度数字 是输入端别名, 落库归一为 数字 + 预设精度(金额 18,2;百分比 12,4;高精度 38,18;默认 38,2)。

写入值校验:数字全角转半角、允许千分位逗号;含数量级词(万/亿/千/k/M/bn)一律拒绝, 不静默换算;整数收到小数拒绝;日期用 ISO;单选/多选值必须在已配选项内; 未知字段静默丢弃;空串归一为 NULL。必填校验只在插入时执行。

7.6 SQL 通道​

一次一条语句,禁止分号拼接;表名写裸名由系统路由;建表不得定义 id / created_at / PRIMARY KEY;UPDATE / DELETE 必须带 WHERE;禁止事务控制语句;禁止访问 public、 pg_catalog、information_schema 及其他组织的 schema;CREATE 仅支持 TABLE / INDEX / VIEW。

补充约束(2026-08 实测确认):

  1. 列类型不支持 JSONB。JSON 数据用 text 列存字符串、应用层解析;语句中不得 出现 ::jsonb 强转(DEFAULT '[]'::jsonb 写成 DEFAULT '[]')。text 不校验 JSON 合法性,技能解析处须兜住坏 JSON(跳过坏行并在返回里点名,不整轮失败)。
  2. 建表字段名受 §7.1 标识符规则约束(中文、字母、数字、下划线;1~50 字符; 不得数字开头)。% 等符号不允许:「容差%」应写「容差百分比」。此限制只针对 标识符,数据值不受限(行内存 'MA20偏离%' 合法)。
  3. 字符串字面量内 : 后紧跟字母或数字会被识别为绑定参数占位符,导致报错 A value is required for bind parameter。内联 JSON 字面量必须在冒号后留空格 ("value": 0.85,而非 "value":0.85);运行时向 JSON 列写数据优先走结构化 insert / update(data 传对象,由平台处理转义),而不是拼 SQL。

7.7 删除保护​

chat_mode=True 且影响行数超过 10(或未知)时返回 confirm_token,用户确认后原参数不变、 携带 token 重调,5 分钟有效。所有删除在执行前把命中行整份落盘到工作区 del_backup_<表>_<时间戳>.json,文件名经 backup_file 返回;备份失败记日志不阻断删除。 filters / row_ids 为空一律拒绝,避免误删全表。

★2026-08 补 精确口径:确认与备份覆盖 delete_batch(按 row_ids 个数)、delete_by (按命中行数)、sql_execute 的 DELETE(按预检行数,预检失败视为未知→要确认); 按单个 row_id 的 delete 既不确认也不备份。备份文件落在工作区根目录,会被 §6.3 的兜底采集当附件展示——这是刻意的(用户能看到被删的数据)。

★2026-08 补 chat_mode 三条线都是 True。 pipeline.build_runtime_ctx 写死 chat_mode=True,不区分对话线/定时任务/技能链。后果:链或定时任务里 delete_by / delete_batch / SQL DELETE 命中超过 10 行,同样返回 confirm_token 等确认——而链里 没有人能回 token,该步必然卡死。链内删除要么控制在 ≤10 行(分批),要么改走 sql_execute 且预检行数 ≤10;真需要大批量自动清理,给 SkillRunPolicy 加 chat_mode 字段随 policy 走(CHAIN_POLICY 置 False),不要在技能里绕闸。

7.8 技能内约束​

  1. 会话取自 runtime_context["db"],不自建连接;需要数据库的入口必须 async。
  2. 所有写操作显式提交;批量写入逐行 SAVEPOINT 隔离,单行失败不影响整批。
  3. 捕获异常后先 rollback 再返回,否则同一 session 后续语句全部报事务已中止(25P02)。
  4. 错误分类依据 SQLSTATE,不依据异常类名或英文文本;无 SQLSTATE 表示未到达数据库, 属参数编码问题。
  5. rollback 会 expire ORM 对象,异常分支需要的表名/字段名应提前取成普通变量。
  6. 不假设唯一约束存在;upsert 按 key_fields 匹配,不依赖约束。
  7. 返回可核验的数字(inserted / updated / skipped / failed / verified_total), 不返回无数据支撑的成功结论。

8. 异步技能​

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

  • 非 web 渠道立即返回 {"ok": true, "async": true, "message": "..."},真实执行转入后台; 完成后按发起渠道主动推送产物。web 渠道仍走同步路径。
  • 后台执行时 request / channel_registry / abort_signal 为空, 配置在派发前已预取并平铺进 skill_configs / skill_args / 顶层,取法同 §5.1。
  • 后台 db 是独立会话,同时提供 db_factory。

不声明 async 的技能受单工具墙钟约束(默认 240 秒,超时中止)。 长循环应轮询 ctx["abort_signal"],被 set 时尽快返回。


9. 技能间协作​

技能是独立执行单元,不得互相 import(executor 按独立脚本加载,跨技能 import 运行时不成立)。 需要在技能内部调用另一个技能时,用平台注入的 ctx["call_skill"]。

9.1 call_skill​

result = await ctx["call_skill"](
"<skill_name>",
{"字段": "值"}, # 显式入参
source=上一步产出, # 可选,供 template / passthrough 取值
template={"目标字段": "文案 {源字段}"}, # 可选
passthrough=False, # 可选,整份 source 透传
其它字段="值", # 可选,等价于写进第二个参数
)
项规则
入参优先级显式 dict / kwargs > template > passthrough > 被调技能自身配置
配置注入被调技能的 skill_configs 由平台按 role_id + 被调技能名 重新查库注入,不继承调用方的
透传的上下文★2026-08 补 精确白名单 = _CALL_PASS_BASE(role_id session_id user_id team_id org_id team_role is_admin channel chat_mode workspace_dir channel_registry db_factory abort_signal allow_sql)+ ACTOR_KEYS(十个 actor_*)+ EXTRA_KEYS(actor_id actor_is_admin)。调用方的 skill_configs / skill_args / 扁平参数、以及词表外的自造键(如 emp_no)不透传
db每次调用新开独立会话,子技能出错不会污染调用方事务
返回成功 = 被调技能的 output dict;失败 = {"ok": False, "error": "...", "skill": "..."}。不抛异常
层数最多 3 层;不得直接自调

四条硬约束:

  1. 调用前先 commit。 子技能用的是另一个 session,调用方未提交的写入它看不到。
  2. 只有进程内 async Python 能用。 call_skill 是 callable,无法穿越进程边界; CLI / NODE / SHELL / GO 型技能的 SKILL_CONTEXT 里没有这个键。
  3. async: true 的技能不要用 call_skill 调。 后台派发在对话层,经 call_skill 会同步执行, 并占用父技能的墙钟预算(默认 240 秒)。
  4. 产物后处理不生效。 远程 URL 物化、附件登记、结果卸载都在对话层。 子技能只回 URL 时不会落盘,需要产物的场景由调用方自己保证文件落在工作区。

参数由技能作者在代码里写死,不经过模型——这正是 call_skill 相对「让模型连调多个工具」的价值: 零 token、参数不会漂移、不会被编造。

9.2 通知与消息投递​

平台提供两个投递技能,按收件人形态选,不是按内容选:

技能用于target / to
notify单向渠道:固定群、Webhook、邮件、Apprise URLtarget="ops"(管理员配的别名)或裸 Apprise URL
send_message双向渠道:飞书/企微/钉钉/Telegram/web 的具体收件人to="feishu:ou_xxx",多人传列表
call = ctx["call_skill"]

# 单向:推到固定运维群
await call("notify", {
"message": "3号机维修完成,费用 1200 元",
"title": "维修完成",
"target": "ops",
"attach": [report_path], # 可选,本地绝对路径
})

# 双向:发给指定的人
await call("send_message", {
"message": "您报修的 3号机 已修复。",
"to": f"{order['origin_channel']}:{order['origin_openid']}",
"attach": [photo_path],
})

# 两者可并行
await asyncio.gather(
call("notify", {"message": text, "target": "ops"}),
call("send_message", {"message": text, "to": ["feishu:ou_a", "feishu:ou_b"]}),
)

收件人必须来自业务数据,不能来自 actor_*。 actor_* 是「本轮说话的人」; 维修工回「完成了」时 actor 是维修工,报修人是另一个人。报修人的渠道和 ID 必须在 建单时就存进工单表(origin_channel / origin_openid / origin_session_id), 不能事后从当前会话推断。

notify 的成功判据是返回里的 sent,不是 ok:

r = await call("send_message", {...})
if not r.get("ok"):
logger.warning("通知失败:%s", r.get("error")) # 通知失败不应回滚业务

投递不等于等回复。 两个技能都是发出即结束。要「发出去 → 等对方回一句 → 继续」, 走审批那套(落 pending 记录,入站消息命中后反向驱动),不要指望在技能内 await 到人的回复。

9.3 其它串联方式​

  • 平台 skill_schedule 多步链(steps):定时任务场景,链内建「空结果护栏」——上一步产出空则中止。
  • 形态拿不到 db 时(CLI / 其它语言 / 文档型),把「取数」和「落库」拆成两个技能, 落库那步写成进程内 async Python,由取数那步用 call_skill 调用。

10. 上线前 checklist​

形态与入口

  • 入口函数名在 execute/run/handler/main/handle 内,第二参数无默认值
  • 碰数据库 → async def
  • 子进程型:日志走 stderr,stdout 只打最终 JSON
  • 模块内确有入口函数(漏了会被静默降级成文档型,不报错)

输入

  • schema.json 的 required 与实际必填一致,description 写清取值范围
  • 文件参数用约定字段名,直接使用平台给的绝对路径,不再拼目录
  • 不从 input_data 读身份类字段
  • ★2026-08 补 动作型技能:action / 参数键别名归一,别名表值集合 ⊆ 规范 action 集合(加断言),归一后回写 action_note
  • ★2026-08 补 无 action 一律返回 need_param=action + 清单,不按入参形状默认成写动作
  • ★2026-08 补 写动作的确认只认用户原话 / 「确认」前缀,预演返回回显收到的参数

上下文

  • 固定词表的键直接判空,未写多余的 .get(k, "")
  • actor_is_admin / 身份键为空的分支已处理,未把空值当最低档位放行
  • triggered_at 当 float 用;attachments 读 type 不依赖 att_type
  • org_id 缺失时返回 error_type=config + non_retriable=true
  • ★2026-08 补 工号取 actor_emp_no(不读 emp_no);行级过滤依赖它的技能在真库验过「非管理员能看到自己的行」,且经 call_skill 路径也验过一次

配置

  • 密钥按 §5.1 的四层顺序取,未要求模型传密钥
  • 文档中的凭证只用 $VAR 引用,无「打印出来确认」的步骤
  • ★2026-08 补 非凭证常量名不含 key/token/secret/password/credential/auth 子串;自测扫描按子串而非后缀

输出

  • 失败必带 error;可自纠的标 recoverable 并给改法
  • non_retriable 只用于部署级问题
  • 部分成功显式标注成功/失败条数
  • 产物返回 filename / local_path,未自拼下载链接
  • 大结果放可卸载字段,关键少量字段放顶层
  • 直显块若是「完整答案且不许模型改写」(帮助/清单/状态表/审计),置 display_only=true;仅作补充则省略
  • ★2026-08 补 中间文件(导入 JSON、临时拼接)放工作区子目录,成功即删,失败保留并点名
  • ★2026-08 补 返回里给用户看的「下一步」不用指令式措辞,标注 for_user

数据表

  • 表名全限定、写操作提交、保留字加引号、绑定参数不写 :name::type
  • SQL 通道:不用 JSONB(JSON 存 text);字段名符合 §7.1 标识符规则(禁 % 等符号)
  • SQL 字符串字面量内无 :紧跟字母/数字 模式(JSON 冒号后留空格),JSON 列写入优先走结构化 insert
  • 不存在的表先查存在性;每组数据独立 try/except + rollback
  • 批量按 1 / ≤100 / 文件导入 三档选路
  • 真库端到端验过一次(SQL 语法、提交是否落盘、约束、事务连坐,mock 覆盖不到)
  • ★2026-08 补 数据表技能名写候选列表探测(datatable 在前、nocodb 兜底),探测结果进环境自检
  • ★2026-08 补 需要行级隔离(own 范围)的表,对应角色的每条线(对话/定时/工作流)都确认已注入 allow_sql=False;未注入则隔离不成立,不要配 own 范围
  • ★2026-08 补 链/定时任务里的删除步骤,单次命中 ≤10 行(chat_mode 三条线恒 True,超过即等确认)

文档

  • SKILL.md 主文档 < 8000 字符,细节拆 references/ 并在正文写明何时读取
  • frontmatter 声明 requires.env / bins,缺配置时能被前置检查拦住
  • 未 import 其它技能
  • ★2026-08 补 动作型技能的 SKILL.md 明写「action 必须传中文原词」+ 三个调用示例;帮助表每行左列是触发场景(「连得上数据库吗」),右列是它回答什么

技能间调用

  • 调 call_skill 前已 commit 自己的写入
  • 收件人取自业务数据,未用 actor_* 当收件人
  • 判失败看返回的 ok / sent,未假定调用一定成功
  • 未对 async 技能使用 call_skill
  • 通知失败不回滚主业务