给 Agent Skill 装上「黑匣子」:STOP 可观测性协议设计与实现

目录

  • 问题:Skill 是黑盒
  • STOP 是什么
  • 四层规范设计
    • 1. Manifest:能力声明
    • 2. Trace:执行追踪
    • 3. Assertions:断言验证
    • 4. Levels:渐进式采纳
  • CLI 工具:stop-cli
  • Runtime SDK:stop-runtime
  • 实战示例
  • 总结

问题: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)是一个开放规范,定义了:

  1. Skill 如何声明自己的能力(Manifest)
  2. 运行时如何输出执行追踪(Trace)
  3. 如何验证执行结果(Assertions)
  4. 如何渐进式采纳(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.yamlSKILL.md 是互补关系:

维度SKILL.mdskill.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 成功的判断标准是:

  1. 没抛异常(弱信号)
  2. LLM 说成功了(不可靠)
  3. 人工检查(不可扩展)

有了断言,成功变成可机器验证的:

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: "^\.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 不要求一步到位,定义了四个等级:

等级名称你需要做什么你能获得什么
L0Manifest写一个 skill.yaml静态分析、依赖审计、副作用可见
L1TraceRuntime 自动输出(无需 Skill 作者改动)执行时间线、工具调用审计
L2Assertions在 skill.yaml 里加断言规则自动成功验证、Trust Score
L3Full定义自定义指标和基线成本追踪、异常检测、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 一样。


本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com