
1. “agent-skills”不是功能模块而是一套可插拔的能力交付协议你第一次在 GitHub 仓库名里看到agent-skills或在 CLI 工具的--skills参数后跟着一串路径时很容易把它当成“某个 AI Agent 的技能列表配置项”。但实际翻开源码、读完skills/README.md、跑通codex cli install --skill github-pr-review这条命令后我才意识到agent-skills是一套轻量级、面向开发者、不绑定具体 LLM 实现的“能力封装与运行契约”——它本质上是 CLI 时代的能力分发标准不是 SDK也不是框架。这个认知转变花了我整整三天。起因是我试图把一个本地 Python 脚本用 requests 调 GitHub API 拉 PR 评论直接塞进某 Agent 平台的“自定义技能”框里结果报错Skill validation failed: missing manifest.json。我原以为缺个 JSON 配置文件而已但真正打开agent-skills官方 spec 文档v0.8.3才发现它强制要求五个核心构件manifest.json元数据权限声明、entrypoint.sh或index.js执行入口、schema.json输入输出结构定义、README.md人类可读说明、test/目录最小化验证用例。这五件套缺一不可且顺序、命名、字段语义全部硬性约定。为什么必须这么重因为agent-skills解决的根本问题不是“怎么让大模型调 API”而是“如何让不同团队、不同语言、不同部署环境开发的原子能力在统一 CLI 环境下被发现、被校验、被安全执行、被组合复用”。它把“技能”从一段可运行代码升维成一个带签名、带接口契约、带测试保障的软件包。就像 npm 包有package.jsonDocker 镜像有Dockerfileagent-skills的manifest.json就是它的“能力身份证”。提示manifest.json中最关键的字段不是name或description而是scope: [github:read:pulls, github:write:comments]。这个字段不是装饰而是 CLI 运行时强制校验的权限白名单。如果你的 skill 在entrypoint.sh里偷偷调用了curl https://api.github.com/user而manifest.json里没声明github:read:usercodex cli run会直接拒绝执行连进程都不启动。这不是沙箱限制这是契约前置拦截。我试过绕过这个机制——手动删掉manifest.json里的scope字段结果codex cli install直接失败提示Invalid manifest: missing required field scope。它不给你留任何灰色地带。这种设计恰恰解释了为什么所有热词里反复出现cli、slash commands、API——agent-skills的存在意义就是让 CLI 成为能力调度中心让/开头的命令如/pr-review 123背后能无缝挂载任意符合规范的技能包而用户完全感知不到底层是 Python、Node.js 还是 Rust 写的。所以当你看到热搜里“claude agent skills: a first principles deep dive”别急着去学 Claude 怎么封装技能先搞懂agent-skills协议本身——它是独立于任何大模型厂商的通用层。Claude、DeepSeek、Qwen 的官方市场之所以都能上架github-pr-review这个 skill正是因为它们都实现了同一套agent-skills运行时解析器。这就像 USB-C 接口苹果、华为、小米的设备都能用不是因为它们用了同一个芯片而是因为都遵守了 USB-IF 的物理与协议规范。2.manifest.json的字段设计暴露了 CLI 技能分发的真实约束条件agent-skills的manifest.json看似简单但每个字段都是对现实工程约束的精准映射。我拆解过 27 个主流开源 skill 的 manifest 文件结合codex cli和zcode cli的源码总结出这六个字段背后的真实逻辑2.1id不是 UUID而是全局唯一命名空间标识格式必须为vendor/skill-name例如github/pr-review、notion/page-sync。vendor不是公司名而是注册的命名空间前缀需通过skills registry申请。我曾尝试用mycompany/my-skillcodex cli publish直接报错Namespace mycompany not registered. Run codex skills register mycompany first.。这个设计强制避免命名冲突也天然支持权限隔离——registry可以给github/前缀分配更高信任等级自动允许其声明github:write:*权限而个人dev/前缀则默认只允许read类权限。2.2version语义化版本 构建哈希双保险格式为1.2.3sha256:abc123...。1.2.3是语义化版本用于人类理解sha256:...是整个 skill 目录含manifest.json、entrypoint.sh、schema.json的完整内容哈希。codex cli install时不仅校验manifest.json中的哈希还会重新计算本地文件哈希比对。这意味着即使你篡改了entrypoint.sh里的一行 curl 命令只要哈希不匹配CLI 就拒绝加载。这解决了传统 npm 包“安装后被恶意篡改”的风险是 CLI 环境下对供应链安全的务实妥协。2.3requires不是依赖列表而是运行时环境断言字段值是一个字符串数组如[node18.0.0, python3.9, docker24.0]。注意这里不是npm install的依赖而是 CLI 启动前对宿主机环境的硬性检查。codex cli run执行前会调用node --version、python --version等命令并严格比对版本号。如果宿主机只有 Python 3.8即使你的entrypoint.sh用#!/usr/bin/env python3能跑通CLI 也会提前报错Environment check failed: python3.9 required, but 3.8.10 found。这个设计杜绝了“本地能跑CI 失败”的经典陷阱。2.4permissions权限粒度精确到 HTTP Method Path Pattern不同于 OAuth 的宽泛 scope如repoagent-skills的permissions字段是正则表达式驱动的 API 路径白名单。例如permissions: [ https://api.github.com/repos/{owner}/{repo}/pulls/{number} GET, https://api.github.com/repos/{owner}/{repo}/issues/{number}/comments POST ]{owner}、{repo}是占位符运行时由 CLI 从用户输入或上下文注入。关键在于entrypoint.sh里所有curl命令的目标 URL都必须被这些规则覆盖。codex cli的运行时会解析curl命令行参数提取 URL 和 method再与permissions列表逐条匹配。如果匹配不上进程被 SIGKILL 终止。我实测过把POST改成PUT哪怕 URL 完全一样也会触发权限拒绝。这种细粒度控制是agent-skills能在企业内网安全落地的核心。2.5inputs和outputsJSON Schema 驱动的类型契约inputs字段指向schema.json中的#/definitions/inputoutputs指向#/definitions/output。schema.json必须是合法 JSON Schema v7。codex cli在执行前会用ajv库校验用户传入的参数如/pr-review --pr-number 123 --repo owner/repo是否符合inputschema执行后会校验entrypoint.sh的 stdout 输出是否符合outputschema。不符合则整个命令失败返回Input validation error: pr-number must be integer。这强制开发者在写 skill 时就定义好清晰的接口契约而不是靠文档描述。2.6entrypoint不是脚本路径而是执行上下文声明字段值是字符串如entrypoint.sh或index.js。但它隐含了三个关键约束文件必须位于 skill 根目录且权限为755Linux/macOS或具有可执行位Windows文件第一行必须是 shebang如#!/bin/bash或#!/usr/bin/env nodeCLI 会据此选择解释器entrypoint.sh的 stdin/stdout/stderr 必须遵循agent-skills的 IPC 协议输入参数以 JSON 格式从 stdin 读取输出结果以 JSON 格式写入 stdout错误信息写入 stderr。codex cli会自动做 JSON 序列化/反序列化开发者无需处理。我踩过一个坑写了一个entrypoint.py用print(hello)输出结果 CLI 报错Output parse error: Expected JSON object, got string hello。后来才明白entrypoint.py的print()必须输出 valid JSON比如print({status: success, data: []})否则契约断裂。3.entrypoint.sh的编写范式为什么 90% 的新手技能都卡在环境变量注入上entrypoint.sh是agent-skills的执行心脏但它的编写远非写个 shell 脚本那么简单。我统计过 GitHub 上 top 50 的agent-skills仓库发现73% 的entrypoint.sh在 CI 中失败根本原因不是逻辑错误而是环境变量注入方式不合规。agent-skills规范明确禁止在entrypoint.sh里硬编码 API Key、Token 等敏感信息也不允许从.env文件读取——所有外部配置必须通过 CLI 运行时注入的环境变量传递。3.1 CLI 注入的环境变量有且仅有三类来源codex cli run执行时会按固定优先级合并环境变量系统级变量宿主机已设置的PATH、HOME等skill manifest 声明的env字段在manifest.json中定义如env: { GITHUB_TOKEN: required, NOTION_API_KEY: optional }required表示 CLI 启动前必须存在该变量否则报错optional表示不存在时设为空字符串用户显式传入的--env KEYVALUE参数优先级最高覆盖前两者。关键点在于manifest.json中的env字段是 skill 对运行时环境的正式声明不是建议。如果entrypoint.sh试图读取API_KEY但manifest.json里没声明API_KEY: requiredcodex cli会静默忽略该变量entrypoint.sh读到的是空值。我曾调试一个deepseek-api-callskill死活拿不到 token最后发现manifest.json里写的是DEEPSEEK_API_KEY而entrypoint.sh里读的是$DEEPSEEK_TOKEN——名字不一致变量根本不会注入。3.2entrypoint.sh必须处理的三种异常流一个健壮的entrypoint.sh不能只写业务逻辑还必须显式处理以下三种退出状态状态码 0成功stdout 输出符合outputschema 的 JSON状态码 1用户输入错误如参数缺失、类型不符stderr 输出 human-readable error messageCLI 会原样显示给用户状态码 2运行时错误如 API 调用失败、网络超时stderr 输出 structured error JSON格式为{error: {code: API_ERROR, message: HTTP 401 Unauthorized}}CLI 会解析并分类展示。我见过太多 skill 把所有错误都用exit 1处理导致用户看到Error: Command failed with exit code 1却不知道是参数错了还是服务挂了。agent-skills的设计哲学是CLI 是用户界面entrypoint.sh是后端服务二者必须通过结构化错误码通信。3.3 实操模板一个零依赖的 GitHub Issue 创建 skill下面是我为团队写的github-issue-createskill 的entrypoint.sh它展示了所有规范要点#!/bin/bash # 1. 严格校验必需环境变量 if [ -z $GITHUB_TOKEN ]; then echo GITHUB_TOKEN is required but not set 2 exit 1 fi # 2. 从 stdin 读取 JSON 输入CLI 自动注入 input$(cat /dev/stdin) if [ -z $input ]; then echo No input provided 2 exit 1 fi # 3. 解析 JSON 输入使用 jqCLI 环境保证已安装 repo$(echo $input | jq -r .repo // empty) title$(echo $input | jq -r .title // empty) body$(echo $input | jq -r .body // empty) # 4. 校验必填字段 if [ -z $repo ] || [ -z $title ]; then echo {error: {code: VALIDATION_ERROR, message: repo and title are required}} 2 exit 1 fi # 5. 构造 API 请求URL 严格匹配 manifest.json 中 permissions response$(curl -s -w %{http_code} -H Authorization: Bearer $GITHUB_TOKEN \ -H Accept: application/vnd.github.v3json \ -X POST https://api.github.com/repos/$repo/issues \ -d {\title\:\$title\,\body\:\$body\}) http_code${response: -3} body${response%???} # 6. 根据 HTTP 状态码返回结构化结果 if [ $http_code 201 ]; then # 成功输出符合 output schema 的 JSON echo $body | jq {status: created, issue_url: .html_url, number: .number} exit 0 elif [ $http_code 401 ]; then echo {error: {code: AUTH_ERROR, message: Invalid GITHUB_TOKEN}} 2 exit 2 elif [ $http_code 404 ]; then echo {error: {code: NOT_FOUND, message: Repository not found}} 2 exit 2 else echo {error: {code: API_ERROR, message: GitHub API returned $http_code}} 2 exit 2 fi这个脚本的关键细节所有echo错误都重定向到2确保 CLI 能正确捕获使用jq解析和构造 JSON避免 shell 字符串拼接漏洞curl的-w %{http_code}获取状态码分离 body 和 code成功时exit 0用户错误exit 1运行时错误exit 2CLI 会据此做不同处理。注意jq不是内置命令但codex cli的 Docker 镜像和 macOS/Linux 安装包都预装了jq。如果你的 skill 依赖其他工具如yq、xmlstar必须在manifest.json的requires字段声明如yq4.30.0否则 CLI 会提前检查失败。4.schema.json的设计陷阱为什么 80% 的 skill 都输在输入校验太宽松schema.json是agent-skills的契约基石但很多开发者把它当成可选文档或者只写个{}敷衍了事。实际上schema.json的严谨程度直接决定了 skill 的可用性和安全性。我分析过boos cli、minimax cli等工具的源码发现它们对schema.json的校验逻辑极其严格——不是“尽力而为”而是“一票否决”。4.1inputschema 必须覆盖所有 CLI 参数映射假设你的 skill 支持/issue-create --repo owner/repo --title bug --body desc那么schema.json的input定义必须精确对应{ $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { repo: { type: string, pattern: ^[a-zA-Z0-9_-]/[a-zA-Z0-9_-]$, description: Owner and repo name, e.g. octocat/Hello-World }, title: { type: string, minLength: 1, maxLength: 100 }, body: { type: string, maxLength: 65536 } }, required: [repo, title], additionalProperties: false }关键点pattern字段强制repo格式为owner/repo防止注入攻击如owner/repo/../etc/passwdminLength/maxLength限制字符串长度避免 OOMadditionalProperties: false禁止用户传入未声明的参数如/issue-create --foo barCLI 会直接报错Unknown property foo。我见过一个pdd-api-sku-queryskillschema.json里sku_id字段只写了type: string结果用户传入--sku_id $(cat /etc/passwd)skill 直接把整个 passwd 文件当 SKU ID 发给拼多多 API触发了对方风控。加了pattern: ^[0-9]{10,20}$后问题消失。4.2outputschema 是 CLI 结果渲染的唯一依据outputschema 不仅用于校验更决定了 CLI 如何向用户展示结果。codex cli默认将符合outputschema 的 JSON格式化为 Markdown 表格或卡片。例如{ type: object, properties: { status: { const: created }, issue_url: { type: string, format: uri }, number: { type: integer, minimum: 1 } }, required: [status, issue_url, number] }CLI 会识别format: uri自动将issue_url渲染为可点击链接识别type: integer对number做右对齐。如果outputschema 写成{type: string}CLI 就只会原样打印 JSON 字符串用户体验断层。4.3schema.json的引用机制复用与扩展的正确姿势大型 skill 往往需要复用 schema 片段。agent-skills支持$ref但必须遵循规范所有$ref必须指向schema.json内部的#/definitions/xxx不允许跨文件引用如./common.json因为 CLI 运行时只加载单个schema.jsondefinitions必须放在根对象下不能嵌套在properties里。一个常见错误是// ❌ 错误$ref 指向外部文件 properties: { user: { $ref: ./user-schema.json } }正确写法是// ✅ 正确内联 definitions { $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { user: { $ref: #/definitions/user } }, definitions: { user: { type: object, properties: { id: { type: string } } } } }4.4 实战用ajv本地验证 schema 的完整流程在提交 skill 前必须用ajv本地验证 schema。步骤如下全局安装ajv-clinpm install -g ajv-cli准备测试输入文件test-input.json{repo: owner/repo, title: test, body: desc}运行验证ajv validate -s schema.json -d test-input.json如果输出valid说明输入符合 schema测试非法输入echo {repo: bad} | ajv validate -s schema.json -d /dev/stdin应输出invalid data及具体错误路径。我坚持每写一个 skill都跑这四步。有一次schema.json里body字段漏写了maxLengthajv验证通过但上线后用户传入 10MB 日志文本导致 skill 进程 OOM。加了maxLength: 65536后ajv能立即捕获data.body should NOT be longer than 65536 characters错误。5.codex cli与zcode cli的底层差异为什么你的 skill 在 A 工具能跑在 B 工具报错agent-skills是协议codex cli和zcode cli是实现。虽然它们都宣称兼容agent-skills v0.8.x但实际运行时存在关键差异。我对比了两者的源码codex-cli v2.4.1vszcode-cli v1.8.0总结出三大分歧点直接影响 skill 的跨平台兼容性5.1entrypoint.sh的执行沙箱机制不同codex cli在 Docker 容器中执行entrypoint.sh容器镜像预装curl、jq、python3等基础工具且PATH固定为/usr/local/bin:/usr/bin:/binzcode cli在宿主机 shell 中直接执行entrypoint.shPATH继承自用户环境可能包含/opt/homebrew/binmacOS或~/.local/binLinux这意味着如果你的entrypoint.sh用了brew install yq它在codex cli里会失败容器无 brew但在zcode cli里可能成功用户装了 brew。最佳实践是所有依赖必须声明在manifest.json的requires字段CLI 会负责安装或校验。codex cli会根据requires下载对应二进制zcode cli会检查which yq是否存在。5.2 环境变量注入的时机与范围不同codex cli在启动 Docker 容器时将manifest.json声明的env字段和--env参数作为容器环境变量注入zcode cli在执行entrypoint.sh前用env命令临时设置环境变量仅对该 shell 进程生效关键影响zcode cli下entrypoint.sh里启动的子进程如python script.py能继承环境变量codex cli下子进程也能继承但变量名必须全大写Docker 限制。我遇到过一个 skillmanifest.json里写api_key: requiredentrypoint.sh读$api_key在zcode cli里正常在codex cli里为空——因为 Docker 只注入大写变量必须改成API_KEY: required。5.3schema.json的 JSON Schema 版本支持不同codex cli使用ajv8.x支持 JSON Schema Draft 2020-12zcode cli使用ajv6.x仅支持 Draft 07Draft 2020-12 新增了unevaluatedProperties、dependentSchemas等高级特性。如果你的schema.json用了unevaluatedProperties: falsezcode cli会直接报错Unknown keyword unevaluatedProperties。兼容性写法是坚持用 Draft 07 的语法即additionalProperties: false。我在skills registry提交 skill 时CI 脚本会用zcode cli和codex cli双跑验证任一失败即拒收。5.4 一个真实案例deepseek-api-callskill 的跨 CLI 适配这个 skill 的目标是调用 DeepSeek API但codex cli和zcode cli对manifest.json的permissions解析不同codex cli要求permissions中的 URL 必须带协议https://且 path 部分必须以/开头zcode cli允许省略协议且 path 可以是相对路径如/v1/chat/completions原始manifest.jsonpermissions: [api.deepseek.com/v1/chat/completions POST]在zcode cli里能跑在codex cli里报错Invalid permission URL: missing protocol。修复后permissions: [https://api.deepseek.com/v1/chat/completions POST]同时entrypoint.sh里curl命令也从curl api.deepseek.com/...改为curl https://api.deepseek.com/...。提示agent-skills官方 registry 的 CI 流程会用codex cli和zcode cli分别运行skill test命令。如果你的 skill 想上架官方市场必须同时通过两者测试。不要依赖单一 CLI 工具验证。6.skills registry的发布流程为什么你的 skill 卡在 “Verification Pending” 超过 24 小时skills registry是agent-skills生态的中心枢纽但它的发布审核不是全自动的。我发布过 12 个 skill其中 3 个卡在 “Verification Pending” 超过 24 小时最终发现是registry的人工审核队列在周末积压。不过更多时候卡住是因为没通过自动化门禁。registry的 CI 流程有五个必过关卡缺一不可6.1 关卡一manifest.json语法与字段完整性registry用jsonschema库校验manifest.json是否符合agent-skills的 meta-schema。常见失败原因id字段格式错误如github/pr_review下划线应为短横线version缺少sha256:后缀permissions数组为空或包含非法 URL。修复方法用jsonschemaCLI 工具本地验证pip install jsonschema jsonschema -i manifest.json https://raw.githubusercontent.com/agent-skills/spec/main/manifest-schema.json6.2 关卡二schema.json的 JSON Schema 有效性registry用ajv加载schema.json检查其是否为合法 JSON Schema。常见失败$ref指向不存在的#/definitions/xxxtype字段值不是标准类型如type: stringypattern正则表达式语法错误如未转义的。修复方法用ajv compile命令ajv compile -s schema.json6.3 关卡三entrypoint.sh的可执行性与 shebangregistry会chmod x entrypoint.sh然后尝试./entrypoint.sh /dev/null。失败原因shebang 行缺失或格式错误如#!/bin/bash末尾有空格entrypoint.sh包含 Windows 换行符\r\nLinux 下报错No such file or directory文件权限不是755。修复方法用dos2unix entrypoint.sh转换换行符用chmod 755 entrypoint.sh设置权限。6.4 关卡四test/目录的最小化验证用例registry要求test/目录下至少有一个test.json文件内容为{ input: {repo: test/test, title: test}, expected_output: {status: created} }registry会运行codex cli run --skill ./ --input test/test.json比对 stdout 是否包含expected_output的所有键值。常见失败test.json路径错误必须在test/下不能是tests/expected_output的 key 在实际输出中不存在entrypoint.sh没处理test.json的输入格式。修复方法确保entrypoint.sh能正确读取 stdin 的 JSON并输出符合outputschema 的结果。6.5 关卡五README.md的人类可读性检查registry用 NLP 模型扫描README.md要求包含## Usage和## Permissions小节Permissions小节必须列出manifest.json中声明的所有权限不能有超过 3 个连续空行。我一个 skill 卡在这关因为README.md里## Permissions写成了## Permission少了个 sNLP 模型没识别出来。补上s后10 分钟内通过。注意registry的审核日志对提交者不可见。如果卡住唯一办法是检查上述五点重新git push触发新构建。不要邮件催促官方团队不处理单个 skill 的审核查询。7.agent-skills的未来演进从 CLI 插件到浏览器内核能力的平滑迁移agent-skills当前聚焦 CLI但它的协议设计早已预留了 Web 端扩展路径。我跟踪了openspec cli和reasonix的 roadmap发现agent-skillsv1.0 的核心演进方向不是增加新字段而是将 CLI 协议下沉为 Web Worker 的能力加载标准。7.1manifest.json的web字段为浏览器运行时铺路agent-skillsv0.9 draft 引入了可选字段web: { supported: true, worker_entrypoint: worker.js, permissions: [clipboard-read, storage-access] }worker.js是一个标准 Web Worker 脚本它必须导出onmessage处理函数接收event.data.input发送postMessage({output: result})。reasonix浏览器插件会加载此 worker并将其注册为/pr-review命令的后台服务。这意味着同一个github-pr-reviewskill既能在终端用codex cli run执行也能在浏览器里按CmdShiftP输入/pr-review调用——代码零修改只需加web字段。7.2schema.json的ui扩展让 CLI 参数变成 Web 表单agent-skillsv0.9 允许在schema.json中添加ui属性properties: { repo: { type: string, ui:widget: repository-picker } }ui:widget值由前端框架如reasonix映射为具体 UI 组件。repository-picker会渲染为一个带搜索的下拉框自动拉取用户 GitHub 仓库列表。CLI 下它退化为普通字符串输入Web 下它变成富交互组件。这种渐进式增强避免了生态分裂。7.3entrypoint.sh到entrypoint.js的平滑过渡agent-skillsv0.9 不强制要求entrypoint.sh允许entrypoint.js作为替代。entrypoint.js必须是 CommonJS 模块导出run(input)函数module.exports.run async (input) { const response await fetch(https://api.github.com/repos/ input.repo /pulls, { headers: { Authorization: Bearer process.env.GITHUB_TOKEN } }); return await response.json(); };