给 Agent Skill 装上「黑匣子」:STOP 可观测性协议设计与实现
Agent Skill 生态正在爆发,但 Skill 执行过程是黑盒。STOP(Skill Transparency & Observability Protocol)是一个开放规范,让 Skill 的能力声明、执行追踪、结果验证变得标准化和可观测。本文介绍 STOP 的设计思路、规范细节,以及 CLI 工具和 Runtime SDK 的实现。
目录
问题:Skill 是黑盒
AI Agent 的能力越来越依赖 Skill(技能插件)。OpenClaw 的 SundialHub 上已经有 4 万多个 Skill,各种 Agent 框架也在构建自己的 Skill 生态。
但有一个根本问题:Skill 执行过程完全不透明。
你调用一个 Skill,它做了什么?调了哪些 API?读了哪些文件?成功还是失败?你不知道。
这带来几个实际痛点:
-
调试靠猜 — Skill 失败了,你只能翻日志祈祷能找到线索
-
信任是二元的 — 要么完全信任一个 Skill,要么完全不用
-
组合很脆弱 — 串联多个 Skill 时,没有 stderr,出错了不知道断在哪
-
安全审计靠人工 — 没有标准方式知道一个 Skill 实际做了什么
这就像早期的微服务——没有 tracing、没有 metrics、没有 health check,出了问题全靠经验和运气。
后来 SRE 领域发展出了可观测性三支柱(Logs、Metrics、Traces),微服务的运维才变得可控。
STOP 要做的,就是把这套方法论搬到 Skill 层。
STOP 是什么
STOP(Skill Transparency & Observability Protocol)是一个开放规范,定义了:
-
Skill 如何声明自己的能力(Manifest)
-
运行时如何输出执行追踪(Trace)
-
如何验证执行结果(Assertions)
-
如何渐进式采纳(Levels)
核心设计原则:
-
最小侵入 — L0 只需要一个 YAML 文件,零运行时开销
-
渐进式 — 从声明到追踪到断言,按需逐步加
-
标准化 — 基于 OpenTelemetry 的 span 模型,可对接现有基础设施
-
平台无关 — 不绑定任何特定 Agent 框架
项目地址:github.com/echoVic/sto…
四层规范设计
1. Manifest:能力声明
Manifest 是 STOP 的基础——一个 skill.yaml 文件,声明 Skill 的输入、输出、使用的工具、副作用等。
把它理解为 Skill 的 package.json,但关注点是可观测性和信任,而不是依赖管理。
sop: "0.1"
name: juejin-publish
version: 1.2.0
description: 发布 Markdown 文章到掘金
inputs:
- name: article_path
type: file_path
required: true
description: Markdown 文章路径
constraints:
pattern: "\\.md$"
outputs:
- name: article_url
type: url
description: 发布后的文章链接
guaranteed: true
- name: article_id
type: string
description: 掘金文章 ID
guaranteed: true
tools_used:
- exec
- web_fetch
- read
side_effects:
- type: filesystem
access: read
paths: ["${inputs.article_path}"]
- type: network
description: POST 请求到掘金 API
destinations: ["juejin.cn"]
requirements:
env_vars: [JUEJIN_SESSION_ID]
有了这个文件,你立刻能知道:
- 这个 Skill 需要什么输入(一个 .md 文件路径)
- 它会产生什么输出(文章 URL 和 ID)
- 它用了哪些工具(exec、web_fetch、read)
- 它有什么副作用(读文件 + 网络请求到 juejin.cn)
- 它需要什么环境(JUEJIN_SESSION_ID 环境变量)
这就是 L0 的全部——一个 YAML 文件,零运行时改动。
skill.yaml 和 SKILL.md 是互补关系:
| 维度 |
SKILL.md |
skill.yaml |
| 受众 |
Agent(LLM) |
Runtime(机器) |
| 格式 |
自由 Markdown |
结构化 YAML |
| 用途 |
教 Agent 怎么用 |
告诉 Runtime 做了什么 |
2. Trace:执行追踪
Trace 是 Skill 的「飞行记录仪」——记录运行时发生了什么、什么顺序、花了多久、是否成功。
采用 OpenTelemetry 的 span 树模型:
Trace
└── Root Span (skill execution)
├── Span: read article.md
├── Span: exec python3 publish.py
│ └── Span: POST juejin.cn/api
└── Span: assertions check
每个 span 的结构:
interface Span {
span_id: string;
trace_id: string;
parent_span_id?: string;
start_time: string; // ISO-8601
end_time: string;
duration_ms: number;
kind: SpanKind; // skill.execute | tool.call | file.read | http.request | ...
name: string;
status: "ok" | "error" | "skipped";
attributes: Record<string, any>;
}
Trace 输出为 NDJSON 格式(每行一个 span),存储在 .sop/traces/ 目录:
{"trace_id":"t_abc","span_id":"s_001","kind":"skill.execute","name":"juejin-publish","status":"ok","duration_ms":3420}
{"trace_id":"t_abc","span_id":"s_002","parent_span_id":"s_001","kind":"file.read","name":"read article","duration_ms":12}
{"trace_id":"t_abc","span_id":"s_003","parent_span_id":"s_001","kind":"tool.call","name":"exec: python3 publish.py","duration_ms":3100}
{"trace_id":"t_abc","span_id":"s_004","parent_span_id":"s_003","kind":"http.request","name":"POST juejin.cn/api","duration_ms":2200}
关键设计决策:
-
NDJSON 而非 JSON — 流式写入,不需要等执行完才输出
-
兼容 OpenTelemetry — 可以直接转发到 Jaeger、Grafana 等
-
敏感数据脱敏 — 不记录凭证、文件内容,只记录元数据
3. Assertions:断言验证
Assertions 回答一个关键问题:「这个 Skill 真的成功了吗?」
没有断言时,Skill 成功的判断标准是:
- 没抛异常(弱信号)
- LLM 说成功了(不可靠)
- 人工检查(不可扩展)
有了断言,成功变成可机器验证的:
assertions:
pre:
- check: file_exists
path: "${inputs.article_path}"
message: "文章文件必须存在"
- check: env_var
name: JUEJIN_SESSION_ID
message: "需要掘金 Session ID"
post:
- check: output.article_url
matches: "^https://juejin\\.cn/post/\\d+$"
- check: output.article_id
not_empty: true
支持的检查类型:
| 类型 |
用途 |
env_var |
环境变量是否存在 |
file_exists |
文件是否存在 |
file_not_empty |
文件是否非空 |
file_matches |
文件内容是否匹配正则 |
tool_available |
工具是否可用 |
output.* |
输出字段验证(matches/equals/not_empty/greater_than) |
duration |
执行时间是否在限制内 |
custom |
自定义脚本验证 |
基于历史断言通过率,还可以计算 Trust Score:
| 分数 |
标签 |
含义 |
| 0.95+ |
✅ Trusted |
稳定通过所有断言 |
| 0.80-0.94 |
⚠️ Unstable |
偶尔失败 |
| < 0.80 |
🔴 Unreliable |
频繁失败 |
Skill 平台(如 SundialHub)可以展示 Trust Score,帮用户选择可靠的 Skill。
4. Levels:渐进式采纳
STOP 不要求一步到位,定义了四个等级:
| 等级 |
名称 |
你需要做什么 |
你能获得什么 |
| L0 |
Manifest |
写一个 skill.yaml |
静态分析、依赖审计、副作用可见 |
| L1 |
Trace |
Runtime 自动输出(无需 Skill 作者改动) |
执行时间线、工具调用审计 |
| L2 |
Assertions |
在 skill.yaml 里加断言规则 |
自动成功验证、Trust Score |
| L3 |
Full |
定义自定义指标和基线 |
成本追踪、异常检测、SLA 监控 |
决策树:
个人/内部 Skill? → L0
需要调试失败? → L1
需要用户/平台信任? → L2
生产环境大规模运行? → L3
L0 的成本是零——只需要一个 YAML 文件。 这是刻意设计的,降低采纳门槛。
CLI 工具:stop-cli
为了让开发者快速上手,我们提供了 stop-cli:
# 安装
npm install -g stop-cli
# 或直接用 npx
npx stop-cli init
stop init
交互式生成 skill.yaml:
$ stop init
🛑 stop init — Generate skill.yaml
Skill name (kebab-case) (my-skill): juejin-publish
Version (1.0.0): 1.2.0
Description: Publish markdown articles to Juejin
Author: echoVic
Observability level (L0/L1/L2/L3) (L0): L2
Tools used (comma-separated): exec,read,web_fetch
✅ Created skill.yaml
stop validate
校验 skill.yaml 是否符合规范:
$ stop validate
✅ skill.yaml is valid
如果有问题会明确报错:
$ stop validate bad-skill.yaml
❌ Missing required field: version
❌ Input "foo": unknown type "invalid_type"
❌ Side effect: unknown type "banana"
⚠️ name should be kebab-case: "BAD_NAME"
3 error(s), 1 warning(s)
校验内容包括:
- 必填字段(sop、name、version、description)
- 名称格式(kebab-case)
- 输入/输出类型合法性
- 副作用类型合法性
- 可观测性等级合法性
-
${inputs.x} 插值引用检查
Runtime SDK:stop-runtime
stop-runtime 是给 Agent Runtime 集成用的 SDK,提供三个核心能力:
npm install stop-runtime
Manifest 加载
import { loadManifest, parseManifest } from 'stop-runtime';
// 从文件加载
const manifest = loadManifest('./skill.yaml');
// 从字符串解析
const manifest = parseManifest(yamlString);
Assertion Runner
import { runAssertions } from 'stop-runtime';
// 跑 pre-checks
const preResults = runAssertions(manifest.assertions.pre, {
env: process.env,
inputs: { article_path: './article.md' },
tools: ['exec', 'read', 'web_fetch'],
}, 'pre');
// 跑 post-checks
const postResults = runAssertions(manifest.assertions.post, {
outputs: {
article_url: 'https://juejin.cn/post/123456',
article_id: '123456',
},
duration_ms: 3420,
}, 'post');
// 检查结果
for (const r of postResults) {
console.log(`${r.check}: ${r.status}`); // output.article_url: pass
}
每个 assertion 结果包含:
interface AssertionResult {
check: string; // 检查类型
status: 'pass' | 'fail';
severity: 'error' | 'warn';
message?: string;
value?: any;
}
Tracer
import { createTracer } from 'stop-runtime';
const tracer = createTracer(manifest);
// 记录工具调用
const spanId = tracer.startSpan('tool.call', 'exec: python3 publish.py');
// ... 执行工具 ...
tracer.endSpan(spanId, 'ok', { 'tool.name': 'exec' });
// 记录 HTTP 请求
const httpSpan = tracer.startSpan('http.request', 'POST juejin.cn/api', spanId);
tracer.endSpan(httpSpan, 'ok', { 'http.status_code': 200 });
// 完成并输出
tracer.finish('ok');
// 导出 NDJSON
console.log(tracer.toNDJSON());
// 或写入文件(.sop/traces/)
tracer.writeTo();
实战示例
以 juejin-publish Skill 为例,完整的 STOP 集成流程:
1. 创建 manifest(L0)
cd skills/juejin-publish/
stop init
# 填写信息,生成 skill.yaml
2. 添加断言(L2)
在 skill.yaml 中加入 assertions 部分(见上文示例)。
3. Runtime 集成
import { loadManifest, runAssertions, createTracer } from 'stop-runtime';
async function executeSkill(skillDir: string, inputs: Record<string, any>) {
const manifest = loadManifest(`${skillDir}/skill.yaml`);
const tracer = createTracer(manifest);
// Pre-checks
const preResults = runAssertions(manifest.assertions?.pre ?? [], {
env: process.env,
inputs,
tools: ['exec', 'read', 'web_fetch'],
}, 'pre');
const preErrors = preResults.filter(r => r.status === 'fail' && r.severity === 'error');
if (preErrors.length > 0) {
tracer.finish('error');
throw new Error(`Pre-check failed: ${preErrors.map(e => e.message).join(', ')}`);
}
// Execute skill
const execSpan = tracer.startSpan('tool.call', 'exec: python3 publish.py');
const outputs = await runPublishScript(inputs);
tracer.endSpan(execSpan, 'ok');
// Post-checks
const postResults = runAssertions(manifest.assertions?.post ?? [], {
outputs,
}, 'post');
const status = postResults.some(r => r.status === 'fail' && r.severity === 'error') ? 'error' : 'ok';
tracer.finish(status);
tracer.writeTo();
return { outputs, assertions: postResults, traceId: tracer.traceId };
}
执行后,.sop/traces/ 目录下会生成 trace 文件,可以用来调试、审计、或对接监控系统。
总结
STOP 协议的核心思路很简单:把 SRE 的可观测性方法论搬到 Agent Skill 层。
-
L0 Manifest — 一个 YAML 文件,让 Skill 从黑盒变成白盒
-
L1 Trace — 执行追踪,知道发生了什么
-
L2 Assertions — 断言验证,知道是否真的成功
-
L3 Full — 指标 + 异常检测,生产级监控
工具已经可用:
# CLI
npx stop-cli init
npx stop-cli validate
# SDK
npm install stop-runtime
项目地址:github.com/echoVic/sto…
这是一个早期规范(0.1.0-draft),欢迎参与讨论和贡献。Skill 生态需要可观测性,就像微服务需要 tracing 一样。
如果你也在做 Agent 相关的开发,欢迎试用 STOP 并提 Issue/PR。让我们一起把 Skill 从黑盒变成白盒。