我的 AI Skill 架构设计:让 Agent 可靠地完成复杂任务
背景
我做了一套面向 AI Agent 的垂直业务 Skill。
一开始我以为自己只是在做一个“复杂一点的 Skill”。但系统跑通之后,我发现真正有意思的不是业务本身,而是背后的架构问题:
怎么让一个 Agent 可靠地完成一个多步骤、多数据源、需要用户确认、还要生成可交付成果的复杂任务?
如果只是让 Agent 调一个 API,这件事并不难。
难的是:
- 任务不是一步完成,而是很多步骤串起来;
- 每一步会产生大量中间数据;
- 中间需要用户确认,不能完全自动跑到底;
- 外部 API 有凭据、限流、异步任务、失败重试;
- 最终结果不能只停留在对话窗口里,而要能保存、分享、复查。
这已经不是“给 Agent 一个工具”那么简单了。
更准确地说,我做的是一个:
Agent-facing vertical application package
面向 Agent 的垂直应用能力包。
Skill 只是用户看到的入口形态。它背后其实包含 CLI、本地文件状态、后端服务、异步任务、报告模板和安全边界。
这篇文章记录一下我目前的设计思路。
核心判断:Agent 不应该直接接触复杂系统
我现在越来越明确一个判断:
Agent 擅长理解、判断、交互和生成;
但不擅长凭据管理、网络调用、状态持久化、限流计费和长任务恢复。
所以我的设计原则是:
- Agent 负责认知层:理解任务、判断下一步、和用户交互、生成报告;
- CLI 负责执行层:把复杂 API 调用封装成稳定命令;
- 后端负责业务层:处理异步任务、数据清洗、上游接口和凭据;
- 文件系统负责状态层:保存中间结果、让任务可审计、可恢复;
- 报告模板负责交付层:把过程和结果变成用户能打开、能分享、能复查的文件。
也就是说,我不是让 Agent 直接面对整个复杂世界,而是给它修了一条比较可靠的路。
整体分层
整个系统分成四层:
┌─────────────────────────────────────────────────────────┐
│ 用户侧:Skill 包 │
│ Agent 读取说明 → 理解任务 → 调 CLI → 做判断 → 生成报告 │
│ │
│ INSTRUCTIONS.md / config.json / knowledge/ / tools/bin/ │
└────────────────────────────┬────────────────────────────┘
│ 本地 CLI 调用
┌────────────────────────────▼────────────────────────────┐
│ CLI 层:Go 编译的跨平台二进制 │
│ 原子命令 → 调后端 → 轮询异步任务 → 写文件到本地 │
└────────────────────────────┬────────────────────────────┘
│ HTTP
┌────────────────────────────▼────────────────────────────┐
│ 服务端 │
│ Skill 后端:业务编排 / 异步任务 / 数据清洗 │
│ 上游代理:凭据集中 / 全局限流 / 内部认证 │
└────────────────────────────┬────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────┐
│ 展示层:自包含 HTML 报告 │
│ Agent 把 JSON 中间产物嵌入模板 → 用户双击打开 │
└─────────────────────────────────────────────────────────┘
这四层各自承担不同职责。
最重要的是:Agent 不直接承担底层复杂性。
Agent 看到的是一个 Skill 包和一组稳定命令;复杂的网络、凭据、异步、轮询、数据清洗,都被压到 CLI 和服务端里。
第一层:用户侧 Skill 包
用户拿到的是一个文件夹,放到 Agent 的 skills 目录下就能使用。
大概结构是:
my_skill/
├── INSTRUCTIONS.md # Agent 的行为说明
├── config.json # 后端地址 + API Key
├── knowledge/ # 领域知识、评分规则、示例
└── tools/bin/ # 跨平台 CLI 二进制
为什么是 Skill 包,而不是 API 文档?
因为我要的不是:
Agent 能调我的 API。
我要的是:
Agent 能按我的设计完成一个完整任务。
API 文档只告诉 Agent:接口长什么样。
但 Skill 包要告诉 Agent:
- 这个任务的目标是什么;
- 正常路径怎么走;
- 哪些步骤必须等待用户确认;
- 哪些文件是上一步输出;
- 哪些动作绝对不能做;
- 最后应该生成什么交付物。
所以 INSTRUCTIONS.md 不是简单说明书,而是 Agent 的行为边界。
INSTRUCTIONS.md:软约束,而不是硬控制
INSTRUCTIONS.md 的作用不是写死所有流程分支。
它更像一组软约束:
- 正常路径是什么;
- 哪些步骤必须执行;
- 哪些步骤必须等待用户确认;
- 哪些数据不能编造;
- 哪些凭据不能暴露;
- 哪些输出必须保存为文件。
我不试图把所有用户交互和异常情况都用代码穷举出来。
因为用户可能一句话回答三个问题,也可能答非所问,也可能中途改变目标。这个部分如果全部写成确定性 workflow,会非常重。
所以我的选择是:
用 INSTRUCTIONS.md 画出道路和边界,
中间的自然语言交互交给 Agent 的泛化能力处理。
这也是 Skill 模式最有价值的地方之一:
把穷举所有情况的成本,转嫁给 LLM 的泛化能力。
但这里必须承认一个现实:INSTRUCTIONS.md 是软约束,不是硬约束。Agent 可能不完全遵守。
所以不能只靠说明文档兜底。凡是不能出错的地方,都要放到 CLI 或服务端里做硬约束。
第二层:CLI 层
CLI 是 Agent 和服务端之间的桥梁。
我选择用 Go 写 CLI,并编译成跨平台二进制。
为什么不让 Agent 直接调 HTTP API?
主要有四个原因。
1. 凭据隔离
如果让 Agent 自己拼 curl,请求里就要出现 token、header、签名参数。
这很危险,也很容易被写进对话、日志或错误信息里。
CLI 可以读取本地 config.json,把 API Key 注入请求。Agent 不需要知道 token 的具体内容。
2. 异步封装
很多任务不是秒级完成的。
比如拉取多个 ASIN 的一年数据、调用第三方分析接口、批量清洗结果,都可能需要几分钟。
如果让 Agent 直接面对:
submit → task_id → poll → download result
它很容易乱。
CLI 把这些封装起来,Agent 只需要理解:
执行命令 → 等文件生成
3. 输出落盘
大结果不适合走 stdout。
终端可能截断,Agent 上下文也不适合塞入大 JSON。
CLI 直接把结果写到本地文件:
my-cli expand --asins B001,B002 --site US --output 01_keywords.json
Agent 只需要读文件、判断文件、继续下一步。
4. 跨平台零依赖
Go 编译成单文件,Windows、macOS、Linux 都能直接运行。
用户不需要装 Python、Node、Docker,也不需要管依赖冲突。
CLI 命令设计:每个命令是一个原子步骤
CLI 不应该暴露一堆底层 HTTP 细节。
它应该暴露业务上稳定的原子操作。
例如:
# 扩词:提交任务、轮询、下载结果、写入文件都由 CLI 完成
my-cli expand --asins B001,B002,B003 --site US --output 01_keywords.json
# 健康检查
my-cli health
Agent 不需要知道背后发生了什么。
它只需要知道:
- 输入文件是什么;
- 输出文件在哪里;
- 失败时错误码是什么;
- 下一步应该读哪个文件。
这实际上是把不稳定的 API 世界,封装成 Agent 更容易理解的本地命令世界。
第三层:服务端
服务端对用户和 Agent 基本透明。
从 Skill 设计角度,服务端主要做两类事。
1. Skill 后端:业务编排
每个复杂 Skill 背后可以有自己的后端服务。
它负责:
- 接收 CLI 请求;
- 编排业务逻辑;
- 调用上游 API;
- 管理异步任务;
- 存储任务结果;
- 清洗和结构化数据;
- 把复杂返回值整理成 Agent 能用的格式。
这里的关键是:不要把脏数据直接丢给 Agent。
Agent 更适合处理结构清楚、有语义的数据,而不是各种上游 API 返回的杂乱字段。
2. 上游代理:基础设施能力
多个 Skill 可能共享一些底层能力,例如:
- 集中管理第三方 API 凭据;
- 全局限流;
- 内部服务认证;
- 计费上报;
- 幂等去重;
- 防止共享 Key 被打爆。
这些不属于某个 Skill 的业务逻辑,而是基础设施能力。
第四层:文件驱动的数据流
整个任务的数据流是文件驱动的。
大致是:
Agent 调 CLI → CLI 写 01_raw.json
Agent 读 01_raw.json → 判断 → 写 02_filtered.json
Agent 调 CLI → CLI 写 03_result.json
Agent 读 03_result.json → 生成 04_report.md / report.html
每个步骤的输出都是文件。
这看起来很朴素,但实际非常有价值。
1. 可审计
用户想知道某个关键词为什么被删除,可以直接打开中间文件。
不是只看最终结论,而是能看到过程。
2. 可恢复
对话断了,文件还在。
新的 Agent 接手,也可以根据当前目录里的文件判断任务跑到哪一步。
3. 不怕大数据
几百个关键词、评论、销量、评分、筛选理由,都可以写进 JSON。
不依赖终端输出,也不依赖 LLM 上下文长度。
4. 为报告提供数据源
最终的 HTML 报告不是凭空生成的,而是由这些中间 JSON 文件拼出来的。
所以文件系统在这里不仅是存储,更像是 Agent 的外部工作记忆。
展示层:自包含 HTML 报告
这是我最满意的一层。
Agent 在对话窗口里输出的报告有几个问题:
- 格式有限;
- 不方便交互;
- 不方便分享;
- 对话一长,前面内容容易丢;
- 换设备后查看体验差。
所以我选择让每个 Skill 自带一个 HTML 报告模板。
任务完成后,Agent 把中间产物嵌入模板,生成一个自包含的 HTML 文件。
report_template.html
+ 01_raw.json
+ 02_filtered.json
+ 03_result.json
+ 04_report.md
-----------------
= report.html
为什么是自包含 HTML?
因为它有几个优点:
- 零依赖:不需要服务器、不需要网络;
- 跨平台:有浏览器就能打开;
- 可分享:发一个文件即可;
- 可交互:前端 JS 可以做筛选、排序、图表;
- 可追溯:原始数据和过滤过程都可以嵌入。
这让 Agent 的产物从“对话里的答案”,升级成了“真正可交付的文件”。
为什么让 Agent 生成报告,而不是后端生成?
因为当前状态在用户本地。
中间 JSON 文件是 Agent 调 CLI 后写在本地的。后端并不知道用户本地有哪些文件,也不知道用户是否中途修改、筛选或暂停过。
Agent 在现场。
它知道当前目录里有什么文件,知道用户刚刚确认了什么,也知道下一步应该展示什么。
所以报告生成交给 Agent 更自然。
不过从长期看,这里也可以继续抽象:
- 报告模板规范化;
- 数据注入格式统一;
- 报告生成命令统一;
- 每个 Skill 的报告变成 Harness 可管理的 artifact。
用户交互:当前交给 Agent,自然但不够硬
我现在没有在架构层面管理用户交互。
例如没有统一的:
- pause;
- resume;
- approval;
- waiting_user;
- interaction checkpoint。
现在的做法是:
INSTRUCTIONS.md标注哪些步骤要用户确认;- 给出提问模板;
- 给出默认处理方式;
- Agent 根据用户回复继续往下走。
这是务实选择。
因为用户交互是最难穷举的。用户可能回答很自然,也可能乱跳,也可能中途改目标。
这部分交给 Agent,体验会比硬编码更灵活。
但这不是终局。
如果以后要升级成更强的 Skill Harness,这里应该抽象出统一状态:
{
"status": "waiting_user",
"step": "confirm_candidates",
"question": "请确认是否继续分析这 10 个候选商品",
"options": ["continue", "modify", "cancel"]
}
也就是说,Agent 仍然负责自然语言沟通,但底层 runtime 必须知道:
当前任务正在等待用户确认。
这才是真正可恢复、可追踪的 human-in-the-loop。
安全边界:软约束 + 硬约束
这套设计里,安全边界分两层。
软约束
写在 INSTRUCTIONS.md 里:
- 不泄露 token;
- 不编造数据;
- 不跳过确认;
- 不私自修改用户文件;
- 不把中间数据当最终结论。
软约束的作用是指导 Agent。
但软约束不能作为唯一防线。
硬约束
放在 CLI 和服务端里:
- API Key 不暴露给 Agent;
- 上游凭据只在服务端;
- 内部服务只监听 localhost;
- 服务间调用需要 Internal Token;
- 计费上报使用 UUID 做幂等;
- 上报失败自动重试;
- 后端做参数校验;
- Gateway 做鉴权和限流。
一句话:
凡是不能出错的地方,不能只靠 Agent 自觉。
这也是我现在对 Agent 工程化最大的体会之一。
这套设计的代价
这套架构不是没有代价。
1. 开发量大
每个重型 Skill 都要写:
- INSTRUCTIONS.md;
- CLI;
- 后端接口;
- 数据清洗;
- 报告模板;
- 配置文件;
- 用户说明。
这比直接暴露一个 API 麻烦很多。
2. 调试链路长
问题可能出在:
- Agent 理解;
- instructions 写得不清楚;
- CLI 参数;
- 后端逻辑;
- 上游 API;
- 本地文件;
- 报告模板。
定位问题需要逐层排查。
3. 软约束仍然不够硬
Agent 可能不完全听 INSTRUCTIONS.md。
后端能拦住一部分,但 Agent 的行为路径本身还没有被统一 runtime 管住。
4. 每个 Skill 仍然很重
这是我现在最大的感受。
当一个 Skill 里同时包含 CLI、后端、文件状态、报告模板和用户交互时,每开发一个新 Skill 都像从零开始。
这说明我已经到了下一个阶段:
我需要的不只是写更多 Skills,而是抽象一个 Skill Harness。
我现在意识到:复杂 Skill 里已经长出了 mini-harness
回头看这套架构,它已经不只是一个普通 Skill。
里面其实已经包含了很多 Harness 的局部能力:
- CLI 封装执行;
- config 管理配置;
- 文件保存中间状态;
- 后端做参数校验;
- Gateway 做限流和鉴权;
- 报告模板管理 artifact;
- INSTRUCTIONS.md 约束 Agent 行为。
这些都是 harness 思想。
但目前它们大多只服务于某一个具体 Skill。
所以更准确的说法是:
这个 Skill 内部已经有了 mini-harness,
但还没有抽象成统一的 Skill Harness。
下一步:从复杂 Skill 抽象到 Skill Harness
如果继续做更多重型 Skills,我不应该每次都从零开始搭一套结构。
下一步应该把共性抽出来。
1. 统一 Skill Manifest
每个 Skill 都应该有一个 skill.yaml:
name: amazon_analysis
version: 0.1.0
description: Analyze Amazon product data and generate a structured report
entrypoint: tools/bin/my-cli
input_schema: schemas/input.json
output_schema: schemas/output.json
timeout_seconds: 600
permission_level: normal
requires_backend: true
report_template: templates/report.html
eval_cases: eval_cases/
这样 Harness 才知道:
- 这个 Skill 是什么;
- 怎么调用;
- 输入输出是什么;
- 需要什么权限;
- 怎么测试;
- 会产出什么 artifact。
2. 统一 run 目录
每次执行都生成一个标准目录:
runs/{run_id}/
├── input.json
├── state.json
├── trace.jsonl
├── outputs/
├── artifacts/
├── reports/
└── logs/
这样无论哪个 Skill,都有一致的状态和产物结构。
3. 统一错误格式
不要让每个 Skill 自己随便报错。
统一成:
{
"ok": false,
"error_code": "UPSTREAM_TIMEOUT",
"message": "Upstream task timed out",
"retryable": true
}
这样 Agent 和人都不用猜发生了什么。
4. 统一 trace
每个步骤都记录:
{"step":"validate_input","status":"ok","duration_ms":12}
{"step":"call_backend","status":"ok","duration_ms":8421}
{"step":"generate_report","status":"failed","error_code":"TEMPLATE_ERROR"}
有了 trace,调试、复盘、eval 才有基础。
5. 统一 eval
每个 Skill 都应该能跑测试集:
skill-harness eval amazon_analysis
测试的不只是“能不能运行”,还要看:
- 输出格式是否正确;
- 关键字段是否存在;
- 错误输入是否被拦截;
- 用户确认流程是否合理;
- 报告是否生成;
- 中间 artifact 是否完整。
6. 统一接入 Agent
最终,Agent 不应该直接理解每个 Skill 的全部细节。
更合理的调用链是:
Agent
↓
MCP / CLI
↓
Skill Harness
↓
具体 Skill
MCP 不需要重复描述所有 Skill。
它只需要暴露几个固定能力:
list_skills
describe_skill
run_skill
get_run
get_run_trace
get_run_artifacts
run_eval
Skill 的真实描述来自 skill.yaml 和 INSTRUCTIONS.md,Harness 读取后动态返回。
这样只有一个事实源,不需要到处复制说明。
重新定义这套架构
所以我现在会这样定义它:
这不是一个简单的 AI Skill。
它是一个面向 Agent 的垂直应用能力包。
Skill 是入口,CLI 是执行桥梁,后端是业务支撑,文件系统是状态层,HTML 报告是交付层。
它已经具备 mini-harness 的雏形,下一步应该抽象成统一的 Skill Harness。
这个定义比“我做了一个 Skill”更准确。
总结
这套架构的核心不是“让 Agent 更聪明”。
而是反过来:
承认 Agent 不可靠,
然后用工程结构让它在可靠边界里发挥作用。
我的当前原则是:
- Agent 擅长的事让它做:理解、判断、交互、生成;
- Agent 不擅长的事不让它碰:凭据、网络、限流、计费、长任务状态;
- 软约束写进
INSTRUCTIONS.md; - 硬约束放进 CLI 和服务端;
- 中间状态落到文件;
- 最终交付变成可打开、可分享、可追溯的 HTML 报告。
短期看,这是一套很重的 Skill 设计。
长期看,它应该继续抽象成:
复杂 Skill
↓
多个复杂 Skill
↓
统一 Skill Manifest
↓
统一 Runner / Trace / Eval / Guardrails
↓
Skill Harness
这也是我接下来真正想沉淀的方向。
不是继续手搓一个又一个重型 Skill,而是把这些重型 Skill 背后的共性抽出来,形成自己的 Agent Skill Harness。
陕公网安备61011302002223号