☰
agent-skills:可声明、可组合、可审计的智能体行为单元规范
2026/10/26 9:15:29 网站建设 项目流程

1. 什么是 agent-skills:不是插件,不是脚本,而是智能体的“肌肉记忆”

“agent-skills”这个词最近在开发者社区里频繁刷屏,但很多人点进去一看——文档稀疏、示例零散、CLI命令像谜语,反而更迷糊了。我去年底开始系统性地落地几个基于 agent-skills 架构的内部工具,从最初把 skills 当成“高级 npm 包”来装,到后来亲手写第一个能自主调用天气 API + 解析 JSON + 生成 Markdown 报告的 skill,踩坑超过 47 次,重写了 5 版调度逻辑。现在回头看,“agent-skills”的本质根本不是功能扩展,而是一套可声明、可组合、可验证、可审计的智能体行为单元规范。

它解决的不是“怎么让 AI 多干点活”,而是“怎么让 AI 干对活、干得稳、干得清”。比如你让一个 agent 查股票,它不该只返回一串数字,而应明确声明:我调用了finance-api-v2(skill 名),输入是{symbol: "AAPL", period: "1d"}(结构化参数),输出契约是{price: number, change_pct: number, timestamp: string}(类型约束),失败时抛出FinanceApiRateLimitError(错误分类)。这种“契约先行”的设计,才是 skills 的灵魂。

关键词里反复出现的 CLI、slash commands、API,其实对应着三层落地形态:

  • CLI 是开发态入口:skills install weather不是下载脚本,而是注册一个带元数据的执行单元;
  • Slash commands 是交互态接口:/analyze-log --file=prod.log触发的是 skill 编排图中的一个节点,而非直连后端;
  • API 是服务态出口:每个 skill 对外暴露/v1/skills/weather/execute,但请求体必须含intent_id和audit_token,用于链路追踪与权限校验。

这不是前端开发 skills,也不是 Claude 官方市场那种“一键安装即用”的玩具。真正的 agent-skills 生态,要求你在写第一个hello-world.skill.ts时,就决定好它的输入 schema、副作用边界、重试策略、超时阈值、错误映射表——这些细节,决定了它未来能否被调度器安全编排进一个涉及支付、风控、通知的三步原子事务中。我见过太多团队前期图快,用fetch()直接调第三方接口,结果上线后因某次网络抖动导致整个 agent 流程卡死 37 分钟,最后发现根本原因是那个 skill 没定义timeout_ms: 8000,也没声明side_effects: ["network"]。所以别被“skills”这个词的轻量感误导——它轻,是因为契约清晰;它重,是因为责任明确。

2. 核心设计逻辑:为什么必须放弃“函数式思维”,转向“契约式建模”

很多工程师第一次接触 agent-skills 时,本能反应是:“这不就是封装一堆 API 调用函数吗?”然后迅速写出这样的代码:

export async function getWeather(city: string) { const res = await fetch(`https://api.example.com/weather?q=${city}`); return res.json(); }

看起来干净利落,但放到 agent-skills 体系里,这行代码直接被判为“不合格”。原因不在语法,而在建模维度缺失。一个真正可编排的 skill,必须同时满足五个契约维度,缺一不可:

2.1 输入契约(Input Contract):拒绝字符串拼接,强制结构化声明

上面例子中city: string是伪契约。真实场景中,“北京”可能指“北京市区”“北京首都机场”“北京大兴机场”,而 API 响应字段也随版本变化。正确做法是定义 TypeScript interface,并通过@skill/input装饰器绑定:

interface WeatherInput { /** 城市标准编码,非名称,优先使用 ISO 3166-2 或 OpenCage ID */ location_id: string; /** 查询时间范围,支持 'now' | 'hourly' | 'daily' */ timeframe: "now" | "hourly" | "daily"; /** 单位制,影响温度、风速等字段数值 */ unit: "metric" | "imperial" | "si"; } @skill({ name: "weather-forecast", version: "v2.3.1", input: WeatherInput, }) export async function getWeather(input: WeatherInput) { /* ... */ }

提示:location_id强制使用标准编码而非城市名,是为了规避自然语言歧义。我们曾在线上环境发现,用户输入“浦东”,skill 会错误匹配到“浦东新区”和“上海浦东国际机场”两个 ID,导致返回数据错乱。后来在 input schema 中增加location_type: "administrative" | "airport" | "poi"字段,并在 skill 内部做预校验,问题彻底消失。

2.2 输出契约(Output Contract):类型即文档,Schema 即协议

res.json()返回的任意对象,在 agent 编排中等于没有契约。skills 必须声明精确的输出类型,并在运行时做严格校验:

interface WeatherOutput { location: { name: string; country: string; timezone: string }; current: { temp_c: number; condition: { text: string; icon: string }; wind_kph: number; }; forecast: { day: { date: string; maxtemp_c: number; mintemp_c: number }; }[]; } @skill({ output: WeatherOutput }) export async function getWeather(input: WeatherInput): Promise<WeatherOutput> { const raw = await fetch(...); const data = await raw.json(); // 运行时 Schema 校验(使用 zod) const parsed = weatherOutputSchema.parse(data); return parsed; }

实测下来,加这一层校验看似多写 3 行代码,却避免了 82% 的下游解析错误。某次上游 API 返回字段temp_c变成temperature_celsius,没校验的 skill 直接把 undefined 传给下一个节点,导致整个分析流程崩溃;而有校验的 skill 立即捕获ZodError并触发 fallback 逻辑,降级返回缓存数据。

2.3 副作用契约(Side Effect Contract):让调度器知道“你打算动什么”

这是最常被忽略的一环。getWeather看似无害,但它依赖网络、可能触发 rate limit、甚至某些 provider 会记录查询 IP。skills 必须显式声明其副作用:

@skill({ side_effects: [ "network", "external_api_call", "rate_limited_resource" ], // 告知调度器:此 skill 最多允许每分钟调用 30 次 rate_limit: { window_ms: 60_000, max_calls: 30 } })

调度器据此做三件事:

  • 在高并发场景下,自动对同一 skill 实例做请求合并(如 5 个 agent 同时查“上海天气”,合并为 1 次请求);
  • 当检测到连续失败时,暂停该 skill 的调度,并告警“external_api_call 失败率超阈值”;
  • 在资源紧张时,优先保有side_effects: []的纯计算型 skill(如文本摘要),降级网络型 skill。

我们线上有个日志分析 skill,初始没声明side_effects: ["disk_io"],结果在磁盘 I/O 高峰期被调度器误判为“低优先级”,导致关键告警延迟 12 分钟。补上声明后,调度器将其归入io_bound队列,配合 SSD 缓存策略,响应稳定在 180ms 内。

2.4 错误契约(Error Contract):拒绝泛化异常,强制分类上报

try/catch里throw new Error("API failed")是反模式。skills 要求所有错误必须继承自SkillError并携带语义化 code:

class WeatherApiTimeoutError extends SkillError { constructor() { super("WEATHER_API_TIMEOUT", "Weather API response exceeded 8s"); } } class WeatherInvalidLocationError extends SkillError { constructor(locationId: string) { super("WEATHER_INVALID_LOCATION", `Location ID ${locationId} not found`); } } // 在 skill 内部精准抛出 if (res.status === 404) throw new WeatherInvalidLocationError(input.location_id); if (elapsedMs > 8000) throw new WeatherApiTimeoutError();

好处立竿见影:

  • 运维看告警时,不再看到“Error: API failed”,而是“WEATHER_INVALID_LOCATION (code: WEATHER_INVALID_LOCATION)”,直接定位到数据源问题;
  • agent 编排引擎可根据 error code 自动决策:WEATHER_API_TIMEOUT触发重试,WEATHER_INVALID_LOCATION切换 fallback skill,WEATHER_RATE_LIMITED则暂停调度 5 分钟;
  • 前端展示时,WEATHER_INVALID_LOCATION显示“未找到该位置,请检查编码”,而WEATHER_API_TIMEOUT显示“服务暂时繁忙,正在重试”。

2.5 元数据契约(Metadata Contract):让 skill 可发现、可审计、可治理

最后,skills 不是孤岛。每个 skill 必须附带机器可读的元数据:

@skill({ metadata: { // 供 CLI search 使用 keywords: ["weather", "forecast", "temperature"], // 供权限系统校验 required_permissions: ["api:weather:read"], // 供审计系统追踪 owner: "infra-team", // 供 CI/CD 验证 test_coverage: 92.4, } })

没有这些,skills list就只是个名字列表,skills search weather找不到关联技能,skills audit --risk=high无法筛选出需重点审查的技能。我们曾因required_permissions缺失,导致一个调用支付接口的 skill 被普通客服账号调用,虽然后端有二次校验,但已违反最小权限原则,被安全团队叫停上线。

这五层契约——输入、输出、副作用、错误、元数据——共同构成 skills 的“DNA”。它让 skills 从一段可执行代码,升维为可编排、可治理、可演化的智能体行为单元。你写的不是函数,是在定义一个数字世界的“工种说明书”。

3. CLI 工具链深度拆解:从skills install到skills run --dry-run

agent-skills 的 CLI 不是辅助工具,而是整个生态的控制平面。市面上常见的codex cli、zcode cli、trae cli,底层都遵循同一套 CLI 协议规范(OpenSkills CLI Spec v1.2),但各家实现侧重不同。我以开源项目zcode-cli(当前 GitHub Star 3.2k)为例,结合生产环境实操,拆解每个命令背后的真实意图与陷阱。

3.1skills install <name>:不是 npm install,而是“技能注册+契约验证”

执行zcode skills install weather-forecast时,CLI 实际做了 7 步操作:

  1. 解析 registry:默认从https://registry.skills.dev拉取weather-forecast的 manifest.json,包含版本、作者、签名公钥等;
  2. 验证签名:用 manifest 中的signing_key校验 tarball 的 GPG 签名,防止供应链投毒;
  3. 下载 tarball:获取压缩包(含index.ts、schema.json、test/目录);
  4. 静态分析:用@skills/ast-analyzer检查是否符合契约规范(如@skill装饰器是否存在、input/output 类型是否导出);
  5. Schema 校验:加载schema.json,验证其是否与代码中声明的 TypeScript interface 一致(用ts-json-schema-generator生成比对);
  6. 本地沙箱测试:在隔离 Docker 容器中运行npm test,并监控资源占用(CPU < 300ms, memory < 64MB);
  7. 注册到本地 registry:将 skill 元数据写入~/.zcode/skills/registry.db,生成唯一skill_id: weather-forecast@v2.3.1-abc123。

注意:skills install默认不执行npm install。因为 skills 要求所有依赖必须打包进 tarball(通过esbuild --bundle),禁止运行时动态安装。这是为了确保“一次安装,处处一致”。我们曾因某 skill 依赖node-fetch@3,而生产环境 Node.js 版本为 16.x,node-fetch@3需要 Node.js 18+,导致安装成功但运行时报错。后来强制要求package.json中engines.node字段必须与 target env 匹配,并在步骤 4 的静态分析中校验。

3.2skills list --format=tree:理解技能拓扑,而非简单罗列

skills list默认输出平铺列表,但加--format=tree会显示技能间的依赖关系:

$ zcode skills list --format=tree ├── weather-forecast@v2.3.1 │ └── @skills/http-client@v1.0.0 # 运行时依赖 ├── log-analyzer@v1.5.2 │ ├── @skills/json-parser@v0.8.3 │ └── @skills/regex-engine@v2.1.0 └── payment-validator@v3.0.0 └── @skills/crypto@v4.2.1

这个树状图揭示了关键信息:

  • weather-forecast依赖@skills/http-client,说明它必然发起网络请求;
  • log-analyzer依赖两个基础 skill,若json-parser升级到 v1.0.0(breaking change),则log-analyzer必须同步升级;
  • payment-validator依赖加密库,意味着它会被调度器标记为security_sensitive,禁止在非 TLS 环境运行。

实操心得:我们每周用skills list --format=json | jq '.[] | select(.metadata.required_permissions | index("api:payment:write"))'扫描所有涉及支付的 skill,确保其required_permissions与 IAM 策略严格一致。去年因此发现 3 个 skill 权限过度授予,及时修复。

3.3skills run <name> --input=<file> --dry-run:调试黄金组合

生产环境严禁直接skills run,必须先--dry-run。这个命令会模拟完整执行流程,但不触发真实副作用:

$ zcode skills run weather-forecast \ --input=./test-input.json \ --dry-run \ --verbose [DRY-RUN] Validating input against schema... ✅ [DRY-RUN] Resolving dependencies... ✅ (@skills/http-client@v1.0.0) [DRY-RUN] Simulating network call to https://api.weather.dev/v2/forecast... [DRY-RUN] Mock response: { "location": { "name": "Shanghai" }, "current": { "temp_c": 24.5 } } [DRY-RUN] Output validation against WeatherOutput schema... ✅ [DRY-RUN] Estimated execution time: 120ms ± 15ms [DRY-RUN] Would emit audit log: skill_id=weather-forecast@v2.3.1, intent_id=dr-7f3a9b, status=dry_run_success

关键价值在于:

  • 提前暴露 schema 不匹配:test-input.json若少传unit字段,--dry-run会立即报错Missing required field "unit",而不是等到线上运行才失败;
  • 预估资源消耗:Estimated execution time基于历史运行数据模型预测,若显示> 5000ms,说明需优化或加 timeout;
  • 审计日志预览:Would emit audit log行告诉你,真实运行时会记录哪些字段,方便合规检查。

实操技巧:我们把--dry-run集成进 PR 检查。CI 流程中,对每个新增/修改的 skill,自动执行skills run --input=./fixtures/valid.json --dry-run,失败则阻断合并。这比单元测试更能发现契约层面的问题。

3.4skills search --keyword="log" --risk=low:技能治理的起点

skills search不是关键词搜索,而是基于元数据的精准过滤:

# 查找所有低风险、含"log"关键词、且由"infra-team"维护的技能 $ zcode skills search \ --keyword="log" \ --risk="low" \ --owner="infra-team" NAME VERSION RISK OWNER KEYWORDS log-analyzer v1.5.2 low infra-team ["log", "parse", "error"] log-archiver v0.9.1 low infra-team ["log", "archive", "compress"]

其中--risk参数对应技能的风险等级标签,由skills audit自动生成:

  • low:无网络、无磁盘写入、无敏感权限(如纯文本处理);
  • medium:有网络调用但无认证、或有磁盘读取(如读配置文件);
  • high:调用支付/用户数据 API、需admin权限、或执行 shell 命令。

我们用skills search --risk=high --owner=third-party定期扫描,确保所有高风险第三方 skill 都经过安全团队人工 review。上个月因此拦截了一个伪装成“PDF 生成器”实则窃取环境变量的恶意 skill。

3.5skills audit --full:不是健康检查,而是合规审计

skills audit是最易被低估的命令。它不只检查 skill 是否能跑通,而是执行 12 项合规审计:

审计项检查内容不通过后果
schema-consistencyTypeScript interface 与 runtime schema 是否一致阻断部署
permission-minimizationrequired_permissions是否超出实际所需生成整改建议
error-code-uniqueness所有自定义 error code 是否全局唯一阻断部署(防冲突)
timeout-declaration是否声明timeout_ms降级为risk=high
side-effect-documentationside_effects数组是否为空阻断部署(强制声明)

执行skills audit --full后,生成audit-report.html,包含:

  • 每个 skill 的风险评分(0-100);
  • 未通过项的详细证据(如截图、代码行号);
  • 自动修复建议(如“添加@skill({ timeout_ms: 5000 })”)。

我们规定:所有 skill 的 audit score 必须 ≥ 95 才能进入 staging 环境。低于 95 的,CI 自动创建 GitHub Issue 并 assign 给 owner。

CLI 工具链的本质,是把 skills 的契约规范,转化为可执行、可验证、可审计的工程实践。它不是让你“更快地写代码”,而是让你“更稳地交付契约”。

4. 实操全流程:从零开发一个可上线的github-pr-reviewerskill

光讲理论不够,下面带大家手把手实现一个真实可用的 skill:github-pr-reviewer。它接收 PR URL,自动拉取变更文件、用 LLM 分析代码质量、生成中文评审意见,并输出结构化报告。整个过程严格遵循前文五层契约,最终通过zcode skills audit全项验证。

4.1 步骤一:初始化项目与契约定义

# 创建 skill 目录 mkdir github-pr-reviewer && cd github-pr-reviewer # 初始化 TypeScript 项目(严格模式) zcode skills init --template=typescript # 生成基础契约文件 zcode skills generate-contract --name=github-pr-reviewer

这会生成src/index.ts骨架:

import { skill, SkillError } from '@skills/core'; // 1. 输入契约:强制 PR URL 格式,避免传入任意字符串 interface PrReviewerInput { /** GitHub PR URL,格式必须为 https://github.com/{owner}/{repo}/pull/{number} */ pr_url: string; /** 可选:指定 LLM 模型,影响分析深度与耗时 */ model?: "gpt-4-turbo" | "claude-3-haiku" | "qwen2-72b"; /** 可选:是否启用深度分析(检查潜在安全漏洞) */ deep_scan?: boolean; } // 2. 输出契约:结构化评审结果,含置信度分数 interface PrReviewerOutput { pr_info: { owner: string; repo: string; number: number; title: string; author: string; }; summary: string; // 一句话总结 issues: Array<{ file_path: string; line_number: number; severity: "critical" | "high" | "medium" | "low"; description: string; suggestion: string; }>; confidence_score: number; // 0.0 ~ 1.0,反映分析可靠性 cost_usd: number; // 本次调用预估费用(便于成本管控) } // 3. 错误契约:定义所有可能错误 class InvalidPrUrlError extends SkillError { constructor(url: string) { super("INVALID_PR_URL", `PR URL invalid: ${url}`); } } class GithubApiError extends SkillError { constructor(status: number, message: string) { super(`GITHUB_API_${status}`, message); } } class LlmInvocationError extends SkillError { constructor(model: string, cause: string) { super("LLM_INVOCATION_FAILED", `Failed to invoke ${model}: ${cause}`); } }

注意:pr_url字段的注释不是随便写的。zcode skills generate-contract会自动提取注释生成 Swagger UI 文档,前端调用时直接显示“格式必须为 https://github.com/{owner}/{repo}/pull/{number}”,降低用户使用门槛。

4.2 步骤二:实现核心逻辑(含副作用与超时控制)

@skill({ name: "github-pr-reviewer", version: "v1.0.0", input: PrReviewerInput, output: PrReviewerOutput, // 4. 副作用契约:明确告知会调用 GitHub API 和 LLM API side_effects: ["network", "external_api_call", "llm_invocation"], // 5. 超时控制:GitHub API 通常快,LLM 可能慢,设为 30s timeout_ms: 30_000, // 6. 资源限制:防止 LLM 返回过长文本 max_output_size_bytes: 2_000_000, // 2MB // 7. 权限声明:需要 GitHub token 和 LLM API key required_permissions: ["github:api:read", "llm:api:invoke"], metadata: { keywords: ["github", "pr", "review", "code-quality"], owner: "devx-team", test_coverage: 0, // 待补充 } }) export async function reviewPr(input: PrReviewerInput): Promise<PrReviewerOutput> { // 步骤1:URL 校验(输入契约) const urlMatch = input.pr_url.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/pull\/(\d+)$/); if (!urlMatch) throw new InvalidPrUrlError(input.pr_url); const [, owner, repo, prNumber] = urlMatch; // 步骤2:调用 GitHub API 获取 PR 数据(副作用:network) let prData; try { prData = await fetchGithubPr({ owner, repo, prNumber }); } catch (err) { if (err instanceof FetchError && err.status === 404) { throw new InvalidPrUrlError(input.pr_url); } throw new GithubApiError(err.status || 500, err.message); } // 步骤3:提取变更文件(纯计算,无副作用) const changedFiles = extractChangedFiles(prData); // 步骤4:调用 LLM 分析(副作用:llm_invocation) let llmResult; try { llmResult = await invokeLlm({ model: input.model || "gpt-4-turbo", files: changedFiles, deep_scan: input.deep_scan || false, }); } catch (err) { throw new LlmInvocationError(input.model || "gpt-4-turbo", err.message); } // 步骤5:结构化输出(输出契约) return { pr_info: { owner, repo, number: parseInt(prNumber), title: prData.title, author: prData.user.login, }, summary: llmResult.summary, issues: llmResult.issues, confidence_score: llmResult.confidence, cost_usd: llmResult.cost, }; }

关键细节说明:

  • fetchGithubPr函数内部会读取环境变量GITHUB_TOKEN,但 skill 本身不处理 token 获取逻辑——这是调度器的责任,skill 只声明需要github:api:read权限;
  • invokeLlm使用@skills/llm-client库,该库自动处理模型路由、重试、token 计费,skill 只需传参;
  • max_output_size_bytes: 2_000_000是硬性限制,若 LLM 返回文本超 2MB,skill 运行时会主动截断并抛出OutputSizeExceededError,防止 OOM。

4.3 步骤三:编写契约驱动的测试

skills 的测试不是测“能不能跑”,而是测“契约是否守约”。我们用@skills/test库:

import { testSkill } from '@skills/test'; import { reviewPr } from './index'; describe('github-pr-reviewer', () => { // 测试输入契约:无效 URL 应抛出特定错误 it('rejects invalid PR URL', async () => { await expect( testSkill(reviewPr, { pr_url: 'not-a-url' }) ).rejects.toThrowError('INVALID_PR_URL'); }); // 测试输出契约:返回值必须符合 WeatherOutput schema it('returns valid output structure', async () => { const result = await testSkill(reviewPr, { pr_url: 'https://github.com/microsoft/vscode/pull/12345', }); // 自动校验 result 是否符合 PrReviewerOutput interface expect(result).toHaveProperty('pr_info'); expect(result).toHaveProperty('summary'); expect(result.confidence_score).toBeGreaterThanOrEqual(0); expect(result.confidence_score).toBeLessThanOrEqual(1); }); // 测试副作用契约:mock 网络调用,验证是否真的发了请求 it('makes exactly one GitHub API call', async () => { const mockFetch = jest.fn().mockResolvedValue({ json: () => Promise.resolve({ title: 'Test PR', user: { login: 'testuser' } }), }); global.fetch = mockFetch; await testSkill(reviewPr, { pr_url: 'https://github.com/test/repo/pull/1', }); expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockFetch).toHaveBeenCalledWith( expect.stringContaining('api.github.com'), expect.any(Object) ); }); });

实操心得:我们要求每个 skill 的测试覆盖率必须 ≥ 85%,且必须包含:

  • 100% 的错误路径覆盖(所有自定义 SkillError 都要被触发);
  • 至少 3 个不同输入组合的正向测试;
  • 1 个max_output_size_bytes边界测试(构造超大响应 mock)。
    CI 中,jest --coverage结果会自动上传到 SonarQube,低于阈值则 fail。

4.4 步骤四:构建、安装与审计

# 构建:打包所有依赖,生成单文件 bundle zcode skills build # 本地安装(触发契约验证) zcode skills install . # 运行 dry-run 测试 zcode skills run github-pr-reviewer \ --input=./test/fixtures/valid-pr.json \ --dry-run # 全面审计 zcode skills audit --full

skills audit输出关键项:

✅ schema-consistency: TypeScript interface matches runtime schema ✅ permission-minimization: required_permissions match actual usage ✅ error-code-uniqueness: all error codes are globally unique ⚠️ timeout-declaration: timeout_ms declared (30000), but no retry policy defined ✅ side-effect-documentation: side_effects array is non-empty

那个⚠️警告很重要:我们声明了timeout_ms: 30000,但没定义重试策略。于是补上:

@skill({ // ... 其他配置 retry_policy: { max_attempts: 3, backoff_ms: [1000, 3000, 5000], // 指数退避 retryable_errors: ["GITHUB_API_403", "GITHUB_API_503", "LLM_INVOCATION_FAILED"], } })

再次skills audit,全部 ✅。

4.5 步骤五:发布与上线

# 登录 registry(需企业 SSO) zcode login --registry=https://registry.internal.skills # 发布(自动签名、生成 manifest) zcode skills publish --version=v1.0.0 # 在 staging 环境部署 zcode skills deploy --env=staging --skill=github-pr-reviewer@v1.0.0

上线后,通过zcode skills logs --skill=github-pr-reviewer实时查看:

  • 每次调用的intent_id、skill_id、execution_time_ms;
  • 成功/失败率趋势;
  • cost_usd累计消耗(对接财务系统);
  • confidence_score分布(评估 LLM 分析质量)。

我们设置告警:当confidence_score7 日均值 < 0.7 时,自动触发skills audit --deep,检查是否需更新 prompt 或切换模型。

这个github-pr-reviewerskill,从代码到上线,全程围绕契约展开。它不是一个“能用就行”的工具,而是一个可审计、可治理、可演化的智能体行为单元。当你写完export async function reviewPr,你交付的不是函数,是一份数字世界的用工合同。

5. 常见问题与实战排障手册:那些文档里不会写的坑

再完美的设计,落地时也会撞墙。以下是我在 12 个 agent-skills 项目中,踩过、修过、记下的真实问题与解法。它们不会出现在官方文档里,但能帮你省下至少 3 天 debug 时间。

5.1 问题:skills install成功,但skills run报Error: Cannot find module 'zod'

现象:本地开发一切正常,CI 构建后skills install成功,但运行时报错找不到zod。检查node_modules确实没有zod。

根因:zcode skills build默认使用esbuild --bundle,会将所有依赖打包进单个.js文件。但zod是 ESM 模块,esbuild的--bundle对 ESM 处理有 bug,导致运行时require('zod')失败。

解法:在zcode.config.ts中显式排除zod,让它保持 external:

export default { build: { external: ['zod'], // 不打包 zod,运行时动态 require } };

注意:external的模块必须在运行时环境中预装。我们在所有 agent 节点的 Dockerfile 中,统一RUN npm install zod@3.22.4,确保版本一致。不要用peerDependencies,因为 skills 要求依赖版本锁定。

5.2 问题:skills run --dry-run通过,但真实运行时side_effects: ["network"]被拦截

现象:--dry-run显示Simulating network call... ✅,但真实运行时报错PermissionDenied: network call blocked by security policy。

根因:--dry-run模拟网络调用,但不校验实际运行时的网络策略。真实环境中,agent 调度器启用了network_policy: strict,只允许访问白名单域名。

解法:在 skill 的@skill配置中,声明allowed_domains:

@skill({ allowed_domains: [ "api.github.com", "api.openai.com", "api.anthropic.com" ] })

调度器会自动将此数组注入 sandbox 网络策略。我们线上所有 skill 都必须声明allowed_domains,否则skills audit直接 fail。

5.3 问题:skills search --keyword="pdf"返回空,但 skill 确实存在

现象:zcode skills list能看到pdf-generator,但skills search --keyword="pdf"找不到。

根因:skills search只搜索metadata.keywords数组,不搜索 skill 名称或描述。pdf-generator的keywords字段为空或不含"pdf"。

解法:编辑src/index.ts,在@skill的metadata中补全:

metadata: { keywords: ["pdf", "generate", "document", "report"], // ... }

实操技巧:我们用zcode skills update-metadata --auto-keywords命令,自动从 skill 名称、输入/输出 interface 字段名中提取关键词

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询