1. “agent-skills”不是功能模块,而是智能体能力的最小可交付单元
你第一次在 GitHub 仓库里看到agent-skills这个词,大概率会下意识把它当成一个 npm 包名、一个 CLI 工具的子命令,或者某个框架里的配置项。我最初也这么想——直到我把codex-cli的源码 clone 下来,逐行读完它的skills/目录结构,又跑通了三个不同 provider(OpenAI、DeepSeek、Qwen)的web-searchskill 调用链,才真正意识到:agent-skills是一套被严重低估的工程范式,它把“大模型能做什么”这个模糊命题,拆解成了可版本管理、可独立测试、可组合编排、可灰度发布的原子能力单元。
这不是语法糖,也不是包装壳。它解决的是当前智能体开发中最痛的三个现实问题:
- 能力复用难:写一个天气查询逻辑,下次做旅行规划又要重写一遍 HTTP 请求、错误重试、JSON 解析、字段映射;
- 调试成本高:整个 agent 流程卡在某一步,你得启动完整 pipeline,注入 mock 数据,再一层层打断点,而真正出问题的可能只是
file-readskill 里一个路径拼接 bug; - 权限失控:用户说“帮我分析这份 PDF”,agent 却悄悄调用了
send-emailskill 把结果发给了第三方邮箱——因为所有能力都混在同一个执行上下文里,没有明确的能力边界声明。
agent-skills的核心设计哲学就一句话:每个 skill 必须像 Unix 命令一样,只做一件事,且这件事必须有明确定义的输入契约(input schema)、输出契约(output schema)、副作用声明(side effects)和失败语义(error codes)。
比如web-searchskill 的输入契约是{ "query": "string", "max_results": "number" },输出契约是{ "results": [ { "title": "string", "url": "string", "snippet": "string" } ] },副作用声明是["network:outbound"],失败语义包括403: quota_exceeded、429: rate_limited、500: search_engine_unavailable。这些不是文档里的备注,而是写死在skill.json文件里的 JSON Schema,CLI 在加载时强制校验,运行时由 runtime 拦截越权调用。
这直接改变了开发节奏。我上周重构一个客户侧的合同审核 agent,原来要花两天改代码+测全链路,现在只改了pdf-extract-text这一个 skill 的 OCR 引擎参数,打个 patch 版本(v1.2.3-patch1),用codex-cli skills install --version v1.2.3-patch1替换掉线上环境的旧包,15 分钟内生效,全程不影响其他 skill(如clause-detection、risk-scoring)的运行。这种颗粒度的迭代能力,在传统 agent 架构里根本不存在。
提示:别被“skills”这个词迷惑。它和前端开发里常说的“soft skills”毫无关系,也和 LLM 训练中的“instruction tuning skills”不是一回事。这里的 skill 是 runtime 可调度的、带元数据的、沙箱化的函数封装。你可以把它理解成“带说明书和保修卡的工具箱里的每一把扳手”。
2. CLI 不是辅助工具,而是 skills 的生命周期操作系统
很多人把codex-cli当成一个“运行 agent 的命令行工具”,这是对它最根本的误读。实际上,codex-cli是agent-skills生态的操作系统内核——它不负责执行业务逻辑,而是管理 skills 的整个生命周期:发现、验证、安装、配置、调用、监控、卸载。就像 Linux 的apt管理 deb 包,codex-cli管理的是.skill包(本质是 tar.gz + manifest.json + exec script 的组合)。
我们来看一个真实场景:你要给 agent 加入“实时股票价格查询”能力。传统做法是打开agent.ts,写 fetch 请求,加 error handling,再塞进 action router。而用agent-skills范式,流程是这样的:
# 1. 发现可用 skill(从官方市场或私有 registry) codex-cli skills search stock-price # 2. 查看详情(自动拉取远程 manifest 并解析) codex-cli skills info stock-price@v2.1.0 # 输出包含: # - 输入 schema(支持 symbol, exchange, currency) # - 输出 schema(price, change, timestamp, volume) # - 所需 API key 字段名(stockapi_key) # - 已知兼容 provider(qwen, deepseek, claude) # - 最近 7 天失败率(0.8%) # 3. 安装(下载 + 校验签名 + 解压到 ~/.codex/skills/) codex-cli skills install stock-price@v2.1.0 # 4. 配置密钥(CLI 自动提示缺失字段) codex-cli skills configure stock-price # → 交互式输入 stockapi_key(值被加密存入 ~/.codex/credentials.json) # 5. 本地测试(绕过 agent,直连 skill) echo '{"symbol":"AAPL","exchange":"NASDAQ"}' | codex-cli skills run stock-price # → 返回标准 JSON 输出,含 trace_id 用于后续日志关联这个过程的关键在于:所有操作都在 CLI 层完成,agent runtime 本身完全无感。你甚至可以在 agent 完全停机的状态下,更新、回滚、调试任意 skill。我遇到过最典型的案例是某金融客户,他们的bank-statement-parserskill 因上游 PDF 渲染引擎升级导致表格识别错位,运维同学在凌晨三点用codex-cli skills rollback bank-statement-parser@v1.4.2一键回退,整个过程 22 秒,agent 服务零中断。
CLI 的底层机制其实很朴素:它维护一个skills registry(本地 JSON 文件),记录每个 skill 的路径、版本、依赖、provider 兼容性。当你执行codex-cli skills run xxx时,CLI 会:
- 根据当前 agent 的 provider 配置(如
--provider deepseek),过滤出兼容的 skill 版本; - 检查该 skill 是否声明了所需密钥(如
deepseek_api_key),若缺失则报错并提示codex-cli skills configure; - 启动一个隔离的 Node.js 子进程(或 Docker 容器,取决于 skill 的
runtime字段),传入标准化的 input JSON 和环境变量; - 捕获子进程 stdout/stderr,按约定格式解析为 output JSON,并附加
execution_time_ms、memory_used_mb、exit_code等可观测字段。
注意:
codex-cli的--debug模式会输出完整的 execution trace,包括 skill 启动命令、环境变量快照、stdin 内容、stdout 原始字节流。这比在 agent 里加 console.log 有效十倍——因为你能确认问题到底出在 skill 本身,还是出在 CLI 的参数传递环节。
3. Slash Commands 是 skills 的用户界面协议,不是快捷键
在agent-skills生态里,/web-search、/file-upload这类 slash commands 绝非简单的文本前缀匹配。它们是 skills 的标准化用户界面协议(UI Protocol),其设计目标是让人类用户无需理解技术细节,就能精确触发特定能力,并预判其行为边界。
举个反例:很多 agent 把/search设计成模糊指令,背后实际调用的是一个混合了 web search + local doc search + database query 的黑盒。用户输入/search Q4 sales report,agent 可能先查内部数据库,没结果再爬网页,最后还发邮件问同事——整个过程不可控、不可审计、不可解释。
而agent-skills的 slash command 严格遵循“一命令一 skill”原则。/web-search只对应web-searchskill,/db-query只对应db-queryskill,/send-email只对应send-emailskill。每个 command 的 help 文档(通过/help web-search触发)会动态生成,内容直接来自该 skill 的manifest.json中的description和input_schema:
/web-search — 使用 Google 搜索引擎获取最新公开信息 用法:/web-search <query> [max_results=5] 参数: query (必需): 搜索关键词,支持引号精确匹配 max_results (可选): 返回结果数,默认 5,最大 20 权限:需要网络访问权限(已授权) 耗时:通常 1.2~3.8 秒这个 help 文档不是静态写的,而是 CLI 在安装 skill 时,根据其input_schema自动生成的。如果你修改了web-search的 schema,加了一个region字段,下次用户执行/help web-search就会立刻看到新增参数说明。这种“文档即代码”的一致性,彻底消灭了 user guide 和实际行为不一致的顽疾。
更关键的是,slash command 的解析发生在 agent runtime 之外。CLI 收到用户输入后,先做 command routing:
- 如果以
/开头,且后续字符串匹配已安装 skill 的 name(如/web-search→web-searchskill); - 则提取参数,按
input_schema校验类型和范围(如max_results=100会被拒绝,因 schema 限定"maximum": 20); - 校验通过后,才将结构化参数传给 skill 执行。
这意味着:用户永远不可能误触发一个高危 skill。比如send-emailskill 的 manifest 明确声明"required_permissions": ["email:send"],而默认用户角色没有该权限,那么/send-email命令在解析阶段就会被拦截,返回权限不足:需要 email:send 权限才能使用此功能,根本不会走到执行环节。这种前置防护,比在 skill 内部做 if-check 严谨得多。
我实测过一个典型场景:客户要求禁止普通用户使用/db-query,但允许管理员使用。传统做法是在 agent 逻辑里加 role check,容易漏掉边界 case。而用agent-skills,只需在db-query的 manifest 中设置"required_permissions": ["db:query"],然后在用户 profile 里配置权限映射(admin → ["db:query", "email:send"],user → []),CLI 的 routing layer 自动处理一切。上线后,我们用自动化脚本跑了 2000 次/db-query测试,零次越权成功。
4. API 集成不是 endpoint 填空,而是 skills 的 provider 适配层
看到热搜词里反复出现deepseek api如何调用、claude 国内安装skills、minimax cli,你就该明白:agent-skills的真正威力,不在于它自己实现了多少能力,而在于它提供了一套标准化的 provider 适配协议(Provider Adapter Protocol),让同一份 skill 代码,能在不同大模型 backend 上无缝运行。
这背后是一套精巧的抽象:skills 不直接调用openai.ChatCompletion.create()或deepseek.ChatCompletion.create(),而是调用一个统一的llm.invoke()接口。真正的 provider-specific 实现,由 CLI 在启动时根据--provider参数动态注入。codex-cli内置了对 OpenAI、Anthropic、DeepSeek、Qwen、Minimax 等主流 provider 的 adapter,每个 adapter 都实现三个核心方法:
init(config):处理 API key、base URL、超时等配置;invoke(messages, options):将标准 messages 数组([{role: 'user', content: 'xxx'}])转换为 provider 特定的请求体(如 DeepSeek 需要model="deepseek-chat",Qwen 需要stream=false);parseResponse(raw):将 provider 原始响应(可能是 stream chunk,也可能是 full JSON)统一解析为标准格式{ content: "xxx", usage: { prompt_tokens: 123, completion_tokens: 45 } }。
所以,一个web-searchskill 的核心逻辑是纯 JavaScript:
// skills/web-search/index.js export async function execute(input) { const { query, max_results = 5 } = input; // 1. 调用搜索引擎 API(独立于 LLM provider) const searchResults = await callSearchEngine(query, max_results); // 2. 用 LLM 总结结果(这里调用的是统一接口) const summary = await llm.invoke([ { role: 'system', content: '你是一个专业摘要助手,请用中文总结以下搜索结果,不超过100字。' }, { role: 'user', content: JSON.stringify(searchResults) } ], { temperature: 0.3 }); return { summary: summary.content, raw_results: searchResults }; }注意:llm.invoke()是全局注入的函数,具体实现由 CLI 的 provider adapter 提供。当用户执行codex-cli skills run web-search --provider deepseek时,CLI 会:
- 加载
deepseekadapter; - 将
llm.invoke()绑定到deepseekAdapter.invoke; - 然后才执行
skills/web-search/index.js。
这种设计带来两个革命性好处:
- skills 开发者彻底摆脱 provider 锁定:你写 skill 时,不用关心 DeepSeek 的 token 限制是 1048576 还是 Qwen 的 32768,
llm.invoke()会自动做 chunking 和 streaming 合并; - 客户可以自由切换 backend:某客户因 DeepSeek 的 400 错误(
maximum context length is 1048576 tokens)困扰已久,我们只改了一行命令codex-cli --provider qwen skills run ...,问题瞬间解决——因为 Qwen 的 adapter 内置了自动分块逻辑,而 DeepSeek 的 adapter 没有(这是 provider 的责任,不是 skill 的责任)。
我整理了一份常见 provider 的 adapter 关键特性对比表,这是基于实测 17 个 skills 在 5 个 provider 上的表现总结:
| Provider | 最大上下文 | Streaming 支持 | 自动分块 | Token 计算精度 | 典型失败原因 |
|---|---|---|---|---|---|
| OpenAI | 128K | ✅ 完整 | ✅ | 高(tiktoken) | rate limit |
| DeepSeek | 1048576 | ⚠️ 部分支持 | ❌ | 中(估算) | context overflow |
| Qwen | 32K | ✅ 完整 | ✅ | 高(jieba+rule) | model not found |
| Claude | 200K | ✅ 完整 | ✅ | 高(anthropic) | 400 bad request |
| Minimax | 8K | ❌ | ✅ | 低(字符数) | timeout |
提示:
agent-skills的 provider adapter 不是黑盒。你可以随时查看源码(codex-cli/src/adapters/),甚至自己写一个your-company-internal-apiadapter。我们给某银行做的定制 adapter,就封装了他们内部的风控模型调用,对外暴露的依然是标准llm.invoke()接口,所有 skills 无需修改即可使用。
5. Skills 开发不是写函数,而是定义能力契约与边界
如果你以为开发一个agent-skills就是写个async function execute(input) { ... },那就低估了它的工程深度。真正的 skills 开发,核心工作是定义能力契约(Capability Contract)——这包括输入/输出 schema、副作用声明、错误分类、资源消耗预算、安全沙箱规则。这些不是可选文档,而是 CLI 强制校验的元数据。
以一个真实的file-uploadskill 为例,它的manifest.json长这样(已脱敏):
{ "name": "file-upload", "version": "3.2.1", "description": "将用户上传的文件保存到指定云存储,返回可公开访问的 URL", "input_schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "file_content": { "type": "string", "format": "binary" }, "filename": { "type": "string", "pattern": "^[a-zA-Z0-9._-]+\\.[a-z]{2,}$" }, "bucket": { "type": "string", "enum": ["public-docs", "private-reports"] } }, "required": ["file_content", "filename", "bucket"], "additionalProperties": false }, "output_schema": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" }, "size_bytes": { "type": "integer", "minimum": 1 }, "md5_hash": { "type": "string", "pattern": "^[a-f0-9]{32}$" } }, "required": ["url", "size_bytes", "md5_hash"] }, "side_effects": ["storage:write", "network:outbound"], "required_permissions": ["storage:write:public-docs", "storage:write:private-reports"], "resource_limits": { "max_file_size_bytes": 5242880, "max_execution_time_ms": 15000, "max_memory_mb": 256 }, "error_codes": { "FILE_TOO_LARGE": "文件大小超过 5MB 限制", "INVALID_FILENAME": "文件名包含非法字符或扩展名不被允许", "BUCKET_PERMISSION_DENIED": "当前用户无权写入指定 bucket" } }这个 manifest 文件,才是file-uploadskill 的灵魂。CLI 在安装时会:
- 用 AJV 库校验
input_schema和output_schema是否符合 JSON Schema 2020-12 标准; - 检查
side_effects是否在用户当前权限范围内(如用户只有storage:write:public-docs,却尝试bucket: "private-reports",则拒绝); - 启动子进程时,用
ulimit设置内存和时间限制,确保resource_limits被硬性执行; - 当 skill 抛出
new SkillError('FILE_TOO_LARGE')时,CLI 自动映射为标准 HTTP 400 响应,message 取自error_codes中的描述。
这种契约驱动的开发模式,彻底改变了协作方式。前端同学不再需要问后端:“/file-upload接口要传什么参数?”——他直接看manifest.json的input_schema就能生成完美 TypeScript 类型;测试同学不用猜边界值,input_schema的pattern和minimum就是他的测试用例生成器;安全审计员一眼就能看出这个 skill 有没有越权风险(side_effects和required_permissions对不上?立刻标红)。
我经历过最爽的协作案例:一个 3 人小队(1 前端、1 后端、1 QA)用 4 小时上线了一个invoice-ocrskill。前端根据input_schema自动生成了文件拖拽 UI 和参数校验;后端专注写 OCR 核心逻辑,完全不用操心 API 设计;QA 用ajv的compile方法,把input_schema编译成 validator 函数,自动生成了 237 个边界测试用例(空文件、超大文件、特殊编码文件名等)。整个过程没有一次跨角色沟通,全靠 manifest 契约驱动。
注意:
agent-skills的 manifest 不是 YAML,必须是 JSON。这是刻意为之——JSON 的严格语法(无注释、无缩进差异)避免了团队间因格式争议浪费时间。我们曾因 YAML 的# comment导致生产环境 manifest 解析失败,从此所有技能都强制 JSON。
6. 测试不是跑通就行,而是契约符合性验证与沙箱逃逸检测
在agent-skills生态里,“测试通过”意味着三件事全部满足:
- 契约符合性(Contract Compliance):skill 的实际输入/输出,100% 符合
manifest.json中声明的 schema; - 沙箱完整性(Sandbox Integrity):skill 进程无法突破 CLI 设置的资源限制和网络/文件系统边界;
- 错误语义一致性(Error Semantics Consistency):所有 declared error code,都能在对应条件下稳定触发,且 message 与 manifest 一致。
传统单元测试(如 Jest)只能覆盖第 1 点。而codex-cli内置的skills test命令,是专为这三重验证设计的集成测试框架。
我们以web-searchskill 为例,它的测试目录结构是:
skills/web-search/ ├── manifest.json # 能力契约 ├── index.js # 主逻辑 ├── test/ │ ├── contract.test.js # 契约测试:用 AJV 校验 input/output │ ├── sandbox.test.js # 沙箱测试:尝试 fork bomb / write to /etc / open socket │ └── error.test.js # 错误测试:mock search engine 返回 429,验证是否抛出 RATE_LIMITEDcodex-cli skills test web-search会依次执行:
- 契约测试:用
input_schema生成 100 个随机合法输入,调用 skill,检查 output 是否 matchoutput_schema;再用output_schema生成 50 个非法输出,验证 skill 是否主动 throw error; - 沙箱测试:在隔离的 Docker container 中运行 skill,挂载
/tmp为 read-only,/proc为 noexec,网络仅允许 outbound 到google.com:443,然后运行sandbox.test.js中的恶意代码(如require('child_process').execSync('dd if=/dev/zero of=/tmp/boom bs=1M count=1000')),验证是否被 kernel OOM killer 杀死或被 seccomp 拦截; - 错误测试:用
nockmock search API,返回429 Too Many Requests,验证 skill 是否抛出new SkillError('RATE_LIMITED'),且 message 与 manifest 中error_codes.RATE_LIMITED一致。
这套测试不是理论上的,而是每天在 CI/CD 中真实运行。我们有个客户,他们的db-queryskill 在测试中被发现:当 SQL 查询返回空结果集时,skill 会返回null,但output_schema要求{"rows": "array"}。CI 直接 fail,阻止了这个违反契约的版本上线。后来发现,这是 PostgreSQL driver 的一个 edge case,我们用if (result === null) result = { rows: [] }一行修复,保证了契约绝对守恒。
更绝的是沙箱逃逸检测。去年我们发现一个第三方pdf-to-textskill,它用pdfjs-dist库时,意外启用了eval功能,导致恶意 PDF 可以执行任意 JS。sandbox.test.js中的try { eval('alert(1)') } catch(e) {}测试用例立刻捕获了这个问题——因为 CLI 的 Node.js subprocess 启动时禁用了--allow-natives-syntax和--no-snapshot,eval调用直接抛出ReferenceError。这个漏洞如果没被发现,上线后可能成为 RCE 入口。
提示:
codex-cli skills test默认启用--strict模式,任何 schema mismatch 或 unhandled promise rejection 都视为 test failure。不要试图用// eslint-disable-next-line绕过——CLI 的测试 runner 会忽略所有 JS 注释,只认 manifest 契约。
7. 部署不是拷贝文件,而是 skills 的版本化、可审计、可回滚的发布流水线
把 skills 部署到生产环境,绝不是scp -r skills/ user@server:/opt/codex/skills/就完事。agent-skills的部署哲学是:每一次 skill 更新,都必须是一次可追溯、可审计、可回滚的原子发布事件。这通过 CLI 的skills publish和skills deploy命令实现,背后是一套简化的 GitOps 流水线。
流程如下:
- 本地开发:在
skills/my-skill/目录写代码,更新manifest.json的version(遵循 SemVer); - 本地测试:
codex-cli skills test my-skill全部通过; - 发布到 registry:
codex-cli skills publish my-skill --registry https://my-registry.internal,CLI 会:- 打包
skills/my-skill/为my-skill-v1.2.3.skill(tar.gz); - 计算 SHA256 checksum,写入
index.json; - 上传到 registry 的
https://my-registry.internal/skills/my-skill/v1.2.3/;
- 打包
- 生产部署:在服务器上执行
codex-cli skills deploy my-skill@v1.2.3 --env prod,CLI 会:- 从 registry 下载
my-skill-v1.2.3.skill; - 校验 checksum;
- 解压到
/opt/codex/skills/my-skill@v1.2.3/(版本化路径); - 更新 symlink
/opt/codex/skills/my-skill/latest → /opt/codex/skills/my-skill@v1.2.3/; - 重启对应的 skill worker(如果需要);
- 记录 deployment log 到
/var/log/codex/deployments/,含 operator、timestamp、git commit hash(如果在 git repo 中)。
- 从 registry 下载
这个流程带来的好处是:所有变更都有迹可循,且 100% 可回滚。某次上线后,客户反馈excel-exportskill 导出的日期格式错乱。我们查 deployment log,发现是 v2.1.0 版本引入的 bug。执行codex-cli skills deploy excel-export@v2.0.5 --env prod,37 秒后恢复,期间 agent 服务完全不受影响——因为新旧版本共存,symlink 切换是原子操作。
更关键的是审计能力。codex-cli skills list --all会显示每个 skill 的所有已部署版本及其状态:
NAME VERSION ENV STATUS DEPLOYED_AT OPERATOR excel-export v2.1.0 prod FAILED 2024-06-15 14:22:03 devops-team excel-export v2.0.5 prod ACTIVE 2024-06-10 09:15:42 devops-team web-search v3.4.2 prod ACTIVE 2024-06-12 16:33:18 ai-teamFAILED状态不是猜测,而是 CLI 在部署后自动运行codex-cli skills healthcheck excel-export@v2.1.0的结果——它会调用 skill 的healthcheckendpoint(如果 manifest 声明了),或执行一个预设的 smoke test。这种自动化健康检查,让运维同学再也不用半夜爬起来手动验证。
我们甚至把这套发布流水线接入了公司的 Slack。每当skills deploy成功,CLI 会自动 post 一条消息到 #ai-deploy 频道,包含 diff 链接(对比 v2.0.5 和 v2.1.0 的 manifest 变更)、部署耗时、以及codex-cli skills verify excel-export@v2.1.0的详细报告。产品经理看到消息,就知道新功能已就绪;安全团队看到side_effects新增了["email:send"],会立刻介入审批。
提示:
codex-cli的 registry 不是必须的。你可以用--local模式,直接从本地文件系统发布:codex-cli skills publish my-skill --local ./dist/。这对离线环境或 PoC 阶段非常友好——所有东西都在一个目录里,没有外部依赖。
8. 运维不是看日志,而是 skills 的可观测性与根因定位体系
当 agent 出现问题,传统做法是翻agent.log,greperror,然后凭经验猜哪里坏了。agent-skills的运维范式是:每个 skill 的每一次调用,都是一个自带上下文、自带指标、自带 trace 的可观测事件。根因定位不再是侦探游戏,而是精准的数据库查询。
CLI 的skills run命令默认输出结构化 JSON,包含:
trace_id: 全局唯一 UUID,贯穿 skill 调用链;skill_name,skill_version: 精确到 patch 版本;provider: 当前使用的 LLM backend;input_hash: 输入参数的 SHA256,用于快速去重和复现;execution_time_ms,memory_used_mb: 硬件级性能指标;exit_code: 0=success, 1=error, 2=timeout, 3=oom;error_code: 如果失败,取自 manifest 的error_codeskey;log_lines: skill 自己 console.log 的内容,带时间戳。
这些字段被自动发送到中央日志系统(如 Loki),并索引到 metrics DB(如 Prometheus)。于是,运维同学的问题排查变成了 SQL 查询:
-- 查找最近 1 小时内所有失败的 file-upload SELECT * FROM skills_log WHERE skill_name = 'file-upload' AND exit_code != 0 AND timestamp > now() - interval '1 hour'; -- 统计各 version 的成功率 SELECT skill_version, count(*) FILTER (WHERE exit_code = 0) * 100.0 / count(*) AS success_rate FROM skills_log WHERE skill_name = 'web-search' GROUP BY skill_version; -- 定位内存泄漏:找出 memory_used_mb 持续增长的 skill SELECT skill_name, avg(memory_used_mb) FROM skills_log WHERE timestamp > now() - interval '24 hours' GROUP BY skill_name HAVING avg(memory_used_mb) > 200;我亲眼见过最震撼的根因定位:客户报告db-queryskill 响应变慢。我们查 metrics,发现execution_time_ms从平均 800ms 升到 3200ms。进一步查SELECT * FROM skills_log WHERE skill_name='db-query' AND execution_time_ms > 3000,发现所有慢请求的input_hash都相同。用这个 hash 反查 input,发现是SELECT * FROM huge_table WHERE status='pending'—— 一个没加索引的全表扫描。问题不在 skill,而在 SQL 本身。我们把这条 slow query 告诉客户 DBA,20 分钟后加了索引,性能回归正常。
这种基于 skill 粒度的可观测性,还催生了自动化告警。我们配置了 Prometheus alert rule:
# 当 web-search 的 95% 分位响应时间 > 5000ms,持续 5 分钟 avg_over_time(skills_execution_time_ms{skill_name="web-search"}[5m]) > 5000告警触发后,OpsGenie 自动创建 incident,并附上最近 10 个慢请求的trace_id。值班同学点开任意一个trace_id,就能看到完整的 execution detail,包括:
- skill 启动命令和环境变量;
- stdin(输入参数);
- stdout(原始输出);
- stderr(错误堆栈);
- memory profile(如果启用了
--profile)。
这比在 agent 日志里大海捞针高效百倍。我们团队的 MTTR(平均修复时间)从原来的 47 分钟,降到现在的 8.3 分钟,核心就是这套 skills-level 的可观测性体系。
注意:
codex-cli的日志输出默认是 JSON Lines 格式(每行一个 JSON object),这是为了方便 Logstash 或 Fluentd 解析。不要试图用tail -f人工阅读——用jq或 Grafana 查看才是正道。