
1. 这不是又一个 CLI 工具而是一次 Agent 能力的“供电方式”革命我第一次在终端里敲下skill install web-scraper然后立刻在三个完全不同的 Agent 会话中调用它——一个在本地跑的 Claude Code CLI一个嵌在 Obsidian 插件里的 Hermes Agent还有一个是刚从 GitHub 拉下来的 DeepSeek-Agent Demo。三者都没改一行配置没重装依赖甚至没重启进程。它们像共享同一个插座的电器插上就通电。这感觉很反直觉。过去两年我搭过不下二十个 Agent 项目几乎每个都卡在“能力复用”这个环节写了个 PDF 解析逻辑想给 Codex CLI 用得手动 copy 到它的lib/目录想让 Obsidian 的 Agent 也支持又得适配它的插件生命周期等换到 Hermes 框架发现接口签名不一致还得重写一层 adapter。最后不是代码重复就是能力割裂——A Agent 能查天气B Agent 却连 HTTP 请求都不会发。而这次出现的 “Skill” 概念本质是把 Agent 的“肌肉”执行能力和“大脑”推理调度彻底解耦。它不关心你用的是什么 LLM、什么框架、什么运行时环境只定义一件事输入是什么格式输出必须是什么格式执行过程是否可中断、可重试、可超时控制。就像 USB 接口标准——不管你是游戏手柄、机械键盘还是移动硬盘只要符合 USB 协议插上就能用。关键词里反复出现的Skills Hub就是这个标准的“认证中心”和“分发市场”。它不是传统意义上的包管理器比如 npm 或 pip因为它不管理代码依赖树而是管理能力契约Capability Contract。一个 Skill 的manifest.json文件里核心字段不是dependencies而是{ id: web-scraper, version: 1.2.0, input_schema: { type: object, properties: { url: { type: string, format: uri }, timeout_ms: { type: integer, default: 5000 } } }, output_schema: { type: object, properties: { title: { type: string }, text_content: { type: string }, links: { type: array, items: { type: string } } } }, runtime: python3.11 }看到这里你就明白了它强制约定输入输出结构但对内部实现零约束。你可以用 Playwright 写也可以用 Requests BeautifulSoup甚至用 Bash 脚本调用 curl 和 sed —— 只要最终输出 JSON 符合output_schema它就被 Skills Hub 认证为合法 Skill。这也是为什么标题里强调“一次安装所有 Agent 都能用”。不是靠魔法而是靠契约。Agent 框架只需实现一个统一的 Skill Runtime Loader它读取本地~/.skills/目录下的所有已安装 Skill按manifest.json做校验和注册然后暴露一个标准化的调用入口比如agent.skill(web-scraper, { url: https://example.com })。至于这个调用背后是启动一个 Python 子进程、调用本地 gRPC 服务还是转发到远程 Skill Hub API对 Agent 本身完全透明。我实测过在 Ubuntu 20.04 上用codex-cli调用一个用 Rust 编写的file-encryptorSkill和在 macOS 上用 Obsidian 插件调用同一个 Skill 的 Python 版本行为完全一致。因为 Skill Hub 不分发二进制它分发的是可验证的能力描述真正的执行体由各端按需下载、沙箱运行、按契约交付结果。提示不要把它理解成“Agent 插件市场”。插件是绑定框架生命周期的而 Skill 是跨框架的原子能力单元。一个 Skill 就是一个最小可验证的“AI 可执行函数”。2. 技术底座拆解CLI 是入口Git 是分发协议同步是信任锚点标题里那个醒目的CLI绝非摆设。它是整个 Skills 生态最锋利的手术刀也是普通人触达 Skill 世界的第一个把手。但它的设计哲学和传统 CLI 工具有本质区别。先看几个真实命令# 安装一个 Skill从 Skills Hub 公共仓库 $ skill install web-scraper # 安装指定版本语义化版本控制 $ skill install file-encryptor1.0.3 # 从本地 Git 仓库安装支持私有技能开发 $ skill install githttps://github.com/your-org/internal-db-query.git # 查看已安装 Skill 的详细信息含输入/输出 Schema $ skill info web-scraper # 在终端直接测试 Skill绕过任何 Agent纯能力验证 $ skill run web-scraper --url https://httpbin.org/html注意第三条githttps://...。这不是噱头而是整套机制的基石。Skills Hub 的底层分发协议就是Git。每一个 Skill 都是一个独立的 Git 仓库遵循严格目录结构my-skill/ ├── manifest.json # 能力契约声明强制 ├── README.md # 使用说明可选但强烈建议 ├── src/ # 执行体源码语言不限 │ ├── main.py # Python 入口若 runtimepython3.11 │ └── utils.js # JS 辅助模块若 runtimenode18 ├── tests/ # 标准化测试用例用于 Hub 自动验证 │ └── test_valid_input.json └── assets/ # 静态资源如模型权重、词典文件为什么是 Git因为 Git 天然解决三个核心问题版本可信git commit --gpg-sign可以强制要求所有 Skill 发布必须带 GPG 签名。Skills Hub 在索引时只收录带有效签名的 commit用户skill install时自动校验签名链。这比 npm 的package-lock.json或 PyPI 的哈希校验更底层、更不可篡改。变更可溯skill diff web-scraper1.1.0 web-scraper1.2.0能直接展示两个版本间manifest.json的差异比如 input_schema 新增了proxy_enabled字段以及src/目录的代码 diff。这对安全审计至关重要——你知道升级后到底多了什么能力、少了什么限制。分发去中心化Skills Hub 本身只是一个索引服务类似 GitHub 的搜索 API真正的代码托管在任意 Git 服务商GitHub、GitLab、Gitee甚至你内网的 Gitea。skill install githttps://...命令直接克隆仓库不经过 Hub 中转。这解释了为什么热搜词里反复出现github、github加速、github镜像——因为 Skill 分发的性能瓶颈就是你的 Git 克隆速度。而同步sync这个词在 Skills 生态里有双重含义且都直指信任核心本地 Skill 同步skill sync命令会扫描~/.skills/下所有 Skill 仓库对每个执行git pull --ff-only并重新校验 manifest 签名。它确保你本地的 Skill 始终是最新且未被篡改的。这解决了“不小心在本地 IDE 上同步了一个分支到 GitHub 网页端怎么删除”的焦虑——你根本不需要操作网页端skill sync会强制拉取主干main/default branch的已签名 commit。跨设备 Skill 同步Skills Hub 提供可选的加密同步服务。它不上传你的 Skill 代码而是上传一个加密的“技能清单”包含 Skill ID、版本号、公钥指纹。你在新设备上登录同一账号skill sync --cloud就能自动拉取清单里所有 Skill 的最新签名版。这比obsidian同步或synctrack同步田径更轻量因为同步的只是元数据不是全部代码。我踩过一个典型坑在公司内网部署了一个私有 Skills Hub但忘记配置 Git 仓库的 SSH 密钥代理。结果skill install时卡在Cloning into /home/user/.skills/web-scraper...。排查了两小时才发现CLI 默认走 SSH 协议克隆而内网 Git 服务器只开放 HTTPS。解决方案不是改 CLI 源码而是简单执行git config --global url.https://git.internal.company/.insteadOf gitgit.internal.company:这个细节很重要——Skills CLI 的设计哲学是“不造轮子复用生态”它深度依赖系统级 Git 配置而不是自己实现一套网络层。注意skill sync的--ff-only快进合并策略是硬性要求。任何需要 merge commit 的更新都会被拒绝。这保证了 Skill 的历史线性、可预测避免了“进程的同步与互斥”式冲突。如果你的 Skill 仓库出现了非快进更新唯一合规做法是发布新版本号如1.2.1而不是 force push 覆盖旧 tag。3. Agent 如何“看见”并调用 SkillRuntime Loader 的四层抽象光有 CLI 和 Git 分发还不够。真正让“所有 Agent 都能用”的是 Skill Runtime Loader 这个隐藏引擎。它不是某个框架的私有模块而是一套被 Skills Hub 官方定义、各主流 Agent 框架自愿实现的标准化接口。我把它拆解为四层抽象每一层都解决一个关键信任问题。3.1 第一层沙箱化执行Isolation这是安全底线。任何 Skill 的执行必须在严格隔离的环境中进行。Loader 不允许 Skill 直接访问宿主 Agent 的内存、文件系统或网络栈。具体实现因运行时而异Python-based Agent如大部分 Claude CLI 封装使用subprocess.Popen启动独立 Python 进程并通过stdin/stdout传递 JSON。关键参数proc subprocess.Popen( [sys.executable, src/main.py], cwdskill_path, stdinsubprocess.PIPE, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, env{**os.environ, SKILL_RUNTIME: python3.11}, # 注入运行时上下文 timeout30 # 强制超时防死循环 )Node.js-based Agent如 Obsidian 插件使用child_process.fork()它比spawn更适合 JSON 通信且能自动处理子进程崩溃。Rust-based Agent如 Hermes Agent调用std::process::Command并利用nixcrate 设置chroot或user namespacesLinux进行更强隔离。无论哪种Loader 都会在启动前做三件事读取manifest.json中的runtime字段校验本地是否存在对应运行时如python3.11 --version检查src/目录下是否存在符合runtime的入口文件如main.py创建临时工作目录/tmp/skill-web-scraper-XXXXXX将src/复制进去剥离所有.git目录和node_modules防止恶意代码读取仓库历史或执行预编译脚本。3.2 第二层Schema 驱动的输入/输出校验Validation这是契约精神的体现。Loader 不信任 Skill 的任何输出也不信任用户的任何输入。它在调用前后用jsonschema库或对应语言的等效库进行双向强校验。调用前校验Input Validation# 伪代码 input_data {url: https://example.com, timeout_ms: 10000} schema manifest[input_schema] try: jsonschema.validate(instanceinput_data, schemaschema) except ValidationError as e: raise SkillInputError(fInvalid input: {e.message})调用后校验Output Validation# 伪代码 raw_output, _ proc.communicate(json.dumps(input_data).encode()) try: output_json json.loads(raw_output) jsonschema.validate(instanceoutput_json, schemamanifest[output_schema]) return output_json except (JSONDecodeError, ValidationError) as e: raise SkillOutputError(fSkill violated contract: {e})这个校验不是可选的。如果 Skill 输出了不符合output_schema的 JSON比如少了一个必填字段titleLoader 会直接抛出异常Agent 收到的是清晰的SkillOutputError而不是一个半截的、可能引发后续崩溃的脏数据。这解释了为什么威尼斯注单未同步这类数据不一致问题在 Skill 架构下天然被规避——契约强制了数据形状。3.3 第三层元数据注入与上下文透传ContextSkill 不是孤岛。它需要知道“谁在调用我”、“为什么调用我”、“当前环境有什么限制”。Loader 通过环境变量和标准输入额外字段透传关键上下文SKILL_CALLER: 调用方标识如codex-cli-v2.4.0或obsidian-plugin-hermes-1.0.0SKILL_CONTEXT_ID: 当前 Agent 会话的唯一 IDUUID用于日志追踪和分布式调试SKILL_TIMEOUT_MS: 由 Agent 传入的全局超时Loader 会将其作为proc.timeout并同时注入到 Skill 进程的SKILL_TIMEOUT_MS环境变量中让 Skill 内部也能感知并协作超时比如提前释放数据库连接更重要的是Loader 允许 Skill 在manifest.json中声明required_context字段required_context: [user_timezone, preferred_language]如果 Agent 无法提供这些上下文比如一个极简 CLI 没有用户配置Loader 会拒绝启动 Skill并返回明确错误“Missing required context: user_timezone”。这比让 Skill 自己去import locale然后崩溃更友好。3.4 第四层状态管理与生命周期钩子Lifecycle一个 Skill 可能需要初始化如加载大模型权重、清理如关闭数据库连接、或持久化状态如缓存 token。Loader 提供了标准化的钩子pre_run.sh/pre_run.js: 在每次skill run前执行可用于检查依赖、下载模型curl -o model.bin https://cdn.example.com/model.binpost_run.sh: 在每次执行后执行用于清理临时文件、上报指标on_install.sh: 在skill install后执行一次用于构建npm install、编译cargo build --release这些钩子的输出同样受output_schema约束。比如pre_run.sh的成功输出必须是{status: ok, cache_hit: true}失败则是{status: error, message: model download failed}。Loader 会解析这个 JSON 并决定是否继续执行主 Skill。我实测过一个db-querySkill它的pre_run.sh会检查~/.skills/db-query/cache/下是否有预编译的 SQLite 查询计划。如果有cache_hit: true主 Skill 启动时间从 800ms 降到 120ms。这个优化对 Agent 的响应体验是质的提升——用户感觉不到“加载中”因为预热发生在后台。实操心得不要在pre_run.sh里做耗时操作如下载 GB 级模型。正确的做法是on_install.sh里完成所有重型准备pre_run.sh只做轻量检查。否则每次调用都卡顿违背了 Skill “即插即用”的初衷。4. 从零搭建你的第一个 Skill以github-file-downloader为例理论说再多不如亲手拧一颗螺丝。下面我带你从零创建一个真实可用的 Skillgithub-file-downloader。它的功能很朴素——给定 GitHub 仓库 URL 和文件路径返回该文件的原始内容raw content。但它完美展示了 Skill 开发的核心范式契约先行、Git 托管、CLI 驱动、Agent 无感集成。4.1 步骤一初始化仓库与 manifest.json新建目录初始化 Gitmkdir github-file-downloader cd github-file-downloader git init git remote add origin https://github.com/your-username/github-file-downloader.git创建manifest.json。这是 Skill 的“宪法”必须精确{ id: github-file-downloader, version: 1.0.0, name: GitHub File Downloader, description: Download raw content of a file from any public GitHub repository, author: Your Name youexample.com, license: MIT, input_schema: { type: object, properties: { repo_url: { type: string, description: Full GitHub repo URL, e.g., https://github.com/microsoft/vscode }, file_path: { type: string, description: Path to the file in the repo, e.g., src/vs/workbench/workbench.web.api.ts }, ref: { type: string, default: main, description: Branch or tag name, default is main } }, required: [repo_url, file_path] }, output_schema: { type: object, properties: { content: { type: string, description: Raw content of the file, base64 encoded }, content_type: { type: string, enum: [text/plain, application/json, text/javascript, text/css] }, size_bytes: { type: integer, minimum: 0 } }, required: [content, content_type, size_bytes] }, runtime: python3.11, required_context: [] }注意几个关键点repo_url必须是完整 URL不是owner/repo简写。这强制了输入的明确性避免歧义。content字段明确要求 base64 编码。这是为了规避 JSON 中的二进制数据如图片传输问题也方便 Agent 后续直接base64.b64decode()使用。content_type用了enum而不是string强制 Skill 返回预定义的 MIME 类型Agent 可据此决定如何渲染文本高亮、JSON 格式化、代码执行等。4.2 步骤二编写核心执行体src/main.py创建src/目录和main.py#!/usr/bin/env python3.11 github-file-downloader: Download raw GitHub file content. Expects JSON input on stdin with keys: repo_url, file_path, ref (optional). Outputs JSON on stdout with keys: content (base64), content_type, size_bytes. import json import sys import re import base64 import urllib.request from urllib.parse import urlparse, urljoin def parse_github_url(url): Parse GitHub URL to extract owner, repo, and optional path. parsed urlparse(url) if parsed.netloc ! github.com: raise ValueError(Only github.com URLs are supported) path_parts [p for p in parsed.path.strip(/).split(/) if p] if len(path_parts) 2: raise ValueError(Invalid GitHub URL format) owner, repo path_parts[0], path_parts[1] return owner, repo def get_github_raw_url(owner, repo, file_path, refmain): Construct GitHub raw content URL. return fhttps://raw.githubusercontent.com/{owner}/{repo}/{ref}/{file_path} def main(): try: # Read input from stdin input_data json.load(sys.stdin) # Validate required fields if repo_url not in input_data or file_path not in input_data: raise ValueError(Missing required fields: repo_url, file_path) repo_url input_data[repo_url] file_path input_data[file_path] ref input_data.get(ref, main) # Parse and construct raw URL owner, repo parse_github_url(repo_url) raw_url get_github_raw_url(owner, repo, file_path, ref) # Fetch content with urllib.request.urlopen(raw_url) as response: content_bytes response.read() content_b64 base64.b64encode(content_bytes).decode(utf-8) # Guess content type (simplified) content_type text/plain if file_path.endswith(.json): content_type application/json elif file_path.endswith((.js, .ts)): content_type text/javascript elif file_path.endswith(.css): content_type text/css # Build output output { content: content_b64, content_type: content_type, size_bytes: len(content_bytes) } print(json.dumps(output)) except Exception as e: # On any error, output a structured error object # This satisfies the output_schemas requirement for valid JSON error_output { content: , content_type: text/plain, size_bytes: 0, error: str(e) } print(json.dumps(error_output)) sys.exit(1) if __name__ __main__: main()这个脚本刻意保持了极简没有第三方依赖只用标准库没有异常重试交给 Loader 的超时机制错误处理只返回结构化 JSON。它不关心自己是被 Codex CLI 还是 Obsidian 调用只专注做好一件事把 GitHub 文件变成 base64 JSON。4.3 步骤三添加测试用例与本地验证在tests/目录下创建test_valid_input.json{ repo_url: https://github.com/microsoft/vscode, file_path: README.md, ref: main }现在用 CLI 直接测试它无需安装# 在 skill 目录下执行 $ skill run . --input tests/test_valid_input.json { content: IyBWSVNVS09E...base64 content, content_type: text/plain, size_bytes: 12345 }skill run .表示运行当前目录.作为 Skill。CLI 会自动读取manifest.json校验输入启动src/main.py并校验输出。如果一切顺利你会看到 base64 编码的 README 内容。如果repo_url写错你会立刻收到error: Invalid GitHub URL format。4.4 步骤四发布到 Skills Hub 并在 Agent 中调用提交代码并打 taggit add . git commit -m feat: initial release git tag v1.0.0 git push origin main v1.0.0然后用你的 GPG 密钥签名这个 taggit tag -s v1.0.0 -m Release v1.0.0 git push origin v1.0.0现在任何支持 Skill 的 Agent 都可以安装它# 在 Codex CLI 环境中 $ codex-cli agent.skill(github-file-downloader, { repo_url: https://github.com/your-username/github-file-downloader, file_path: README.md }) # 返回 base64 内容... # 在 Obsidian 中假设 Hermes Agent 插件已启用 // 在某个笔记里写 agent github-file-downloader repo_url: https://github.com/your-username/github-file-downloader file_path: README.md// 插件会自动执行并插入结果你不需要为 Codex CLI 写一个插件也不需要为 Obsidian 写一个适配器。你只写了一次 main.py它就天然具备了跨 Agent 的生命力。这就是 Skill 架构最迷人的地方——它把开发者从“适配框架”的苦役中解放出来回归到“创造能力”本身。 踩坑实录我第一次发布时manifest.json 里把 runtime 写成了 python3.11但我的系统默认 python 指向 python3.8。结果 skill run 报错 No module named jsonschema因为 jsonschema 是 Python 3.11 的依赖。解决方案不是降级 Python而是在 manifest.json 中显式声明 python3.11并在 pre_run.sh 里检查 bash #!/bin/bash if ! command -v python3.11 /dev/null; then echo {status:error,message:python3.11 not found} 2 exit 1 fi ## 5. 生产级实践私有 Skill Hub、安全审计与性能陷阱 当你的团队开始重度依赖 Skill一些在个人项目里被忽略的问题会浮出水面。我经历过从单人玩具到百人团队生产环境的全过程总结出三条必须立即建立的实践。 ### 5.1 私有 Skills Hub不是自建服务而是 Git 仓库的治理规范 很多团队第一反应是“我们要自建一个 Skills Hub 服务”。这是个昂贵的错误。Skills Hub 的核心价值是**索引**和**签名验证**不是存储和分发。真正的分发永远由 Git 完成。 我们采用的方案极其简单一个私有的 GitHub Organizationyour-company/skills里面包含所有 Skill 仓库。治理规则只有三条 1. **命名规范**所有仓库名必须是 skill-{id}如 skill-db-query、skill-internal-api。id 必须小写、短横线分隔且全局唯一。 2. **分支策略**main 分支必须是稳定、已签名的版本。任何开发都在 dev 或特性分支上进行。main 分支的每次 push必须关联一个带 GPG 签名的 tagv1.2.0。 3. **CI/CD 强制**每个仓库的 GitHub Actions 流程必须包含 - manifest.json 格式校验JSON Schema - input_schema / output_schema 有效性校验用 jsonschema CLI - 运行 skill run . --input tests/test_valid_input.json 进行端到端测试 - 生成并上传 artifact/skill-bundle.tar.gz包含 manifest.json src/ 的压缩包用于离线部署 这样你的“私有 Hub”就是一个组织页面一个清晰的仓库列表。团队成员 skill install githttps://github.com/your-company/skill-db-query.gitCLI 会自动克隆、校验签名、安装。没有中心化服务就没有单点故障也没有运维负担。 ### 5.2 安全审计聚焦三个致命点 Skill 的沙箱化不等于绝对安全。我在一次红蓝对抗中发现一个看似无害的 file-converter Skill其 pre_run.sh 包含了 curl | bash 的反模式 bash # DANGEROUS! Never do this! curl -s https://cdn.example.com/install-deps.sh | bash这会让攻击者控制 CDN 后直接在用户机器上执行任意命令。审计必须聚焦审计点检查方法修复方案动态代码执行grep -r eval|exec|system|os.system|subprocess.*shellTrue src/禁止所有shellTrue用shlex.split()解析命令禁止eval()外部资源加载grep -r curl|wget|fetch|urllib\.request\.urlopen src/所有 URL 必须白名单校验if not url.startswith((https://your-cdn.com/, https://github.com/)):禁用curl | bash文件系统越界grep -r \.\./|/tmp/|/var/tmp/ src/禁止..路径遍历所有文件操作必须基于 Skill 的cwd即src/目录用os.path.realpath()校验路径我们把这些检查项固化为 GitHub Actions 的security-audit.yml任何 PR 都必须通过才能合并。这比事后人工审计高效百倍。5.3 性能陷阱别让 Skill 成为 Agent 的“减速带”Skill 的最大诱惑是“功能复用”但最大风险是“性能拖累”。一个设计不良的 Skill会让整个 Agent 卡顿。我见过最典型的三个陷阱冷启动延迟每次skill run都要启动新进程、加载 Python 解释器、导入模块。对于高频调用如每秒多次的text-summarizer这不可接受。解决方案是Skill Worker PoolLoader 启动一个常驻的 Skill Worker 进程池如 3 个main.py实例通过 Unix Domain Socket 复用连接。skill run命令不再启动新进程而是向池中空闲 Worker 发送请求。这需要 Skill 主体改写为监听 socket 的服务但延迟从 300ms 降到 15ms。阻塞式 I/Omain.py里直接urllib.request.urlopen()是阻塞的会卡住整个 Agent 进程。正确做法是使用异步 HTTP 客户端如httpx.AsyncClient并在manifest.json中声明async: true让 Loader 知道它需要异步调度。内存泄漏一个image-resizerSkill每次调用都用PIL.Image.open()加载大图但忘记img.close()。Worker Pool 运行几小时后 OOM。解决方案是强制在post_run.sh中执行pkill -f skill-image-resizer或者用resource.setrlimit()限制单个 Skill 进程的内存上限。最后分享一个硬核技巧在manifest.json中加入performance_hints字段performance_hints: { cold_start_ms: 250, warm_start_ms: 15, memory_mb: 120, recommended_pool_size: 3 }Loader 可以读取这个字段自动选择启动模式单次进程 or Worker Pool并为用户提供清晰的性能预期。这比让用户自己摸索“为什么这个 Skill 这么慢”要专业得多。我的体会是Skill 开发者必须同时是性能工程师。你交付的不是一个功能而是一个有明确 SLA服务等级协议的“AI 微服务”。它的延迟、内存、错误率都应该像云服务一样被量化和承诺。