SkCC 的整体架构设计、模块划分与数据流
论文状态:已被 ACM CAIS 2026 — AgentSkills'26 Workshop 接收(poster)。arXiv: 2605.03353
架构版本:v2.0(基于《高级提示词工程格式与智能体技能架构》调研报告重构)
⚠️ 实现状态声明 (Updated 2026-04-16): 本文档描述的架构设计大部分已在源码中实现。部分早期设计(如 Codex 双负载架构、Analyzer trait 统一接口、Warning 诊断保留)在实现过程中进行了简化,详见下方各节标注。完整审查见 审查报告。实现状态如下:
文档描述 实现状态 NestedDataDetector(Analyzer阶段)✅ analyzer/nested_data.rs已创建nested_data.rs(IR阶段)✅ ir/nested_data.rs已创建KimiEmitter独立实现✅ 独立 kimi.rs已创建EmitterRegistry✅ backend/registry.rs已创建routing.rs路由清单生成器✅ 实现为 routing_manifest.rsAskama 模板引擎 ✅ 4个 .j2 模板 + context structs nexa-skill-wasm/crate⏳ 暂不实现(未来扩展) index.rs(CLI命令)✅ 已实现 Codex 双负载生成 ❌ 未实现 — 已重构为纯Markdown Analyzer trait 统一接口 ❌ 未实现 — 使用独立 struct Warning 诊断保留(compile_file Ok 路径) ✅ check/validate 模式返回 diagnostics,build 模式通过 tracing::warn! 输出
SkCC 采用经典编译器的四阶段管线架构,将人类可读的 SKILL.md 源文件转化为 AI Agent 可执行的结构化产物。整体设计遵循以下原则:
| 原则 | 描述 | 学术依据 |
|---|---|---|
| 消除格式税 | 避免强制模型解析复杂JSON输入,防止高达40%的性能衰退 | Format Tax 研究 (2025) |
| 解耦推理与格式化 | 输入端使用Markdown进行自由态推理,输出端通过API强制约束 | OpenAI Structured Outputs 最佳实践 |
| AST优化注入 | 编译期检测嵌套数据结构,自动选择最优格式 | Gemini 嵌套数据准确率测试 |
| 渐进式披露 | 仅提取name和description生成路由清单,解决上下文膨胀 | Agent Skills 开放标准 |
| 关注点分离 | 每个阶段职责明确,边界清晰 | 编译器设计经典原则 |
| 强类型中间表示 | 所有阶段通过 SkillIR 进行数据交换 |
Rust 类型系统优势 |
| 零拷贝优化 | 利用 Rust 生命周期实现高效字符串处理 | Rust 性能特性 |
| 可扩展后端 | 通过 Emitter Trait 支持新平台适配 |
Trait 抽象设计 |
| 版本 | 主要变更 | 学术依据 |
|---|---|---|
| v1.0 | 初始四阶段管线设计 | 编译器经典理论 |
| v2.0 | 消除格式税、AST优化、渐进式路由 | 2026年格式敏感性调研报告 |
graph TB
subgraph Input
A[SKILL.md 源文件]
B[YAML Frontmatter]
C[Markdown Body]
end
subgraph "Phase 1: Frontend"
D[Frontmatter Parser<br/>serde_yaml]
E[Markdown Parser<br/>pulldown-cmark]
F[AST Builder]
end
subgraph "Phase 2: IR Construction"
G[SkillIR Builder]
H[Type Mapping]
N[Nested Data Detector<br/>AST优化标记]
end
subgraph "Phase 3: Analyzer"
I[Schema Validator]
J[MCP Dependency Checker]
K[Permission Auditor]
L[Anti-Skill Injector]
end
subgraph "Phase 4: Backend"
M[Claude Emitter<br/>XML原教旨主义]
O[Codex Emitter<br/>纯Markdown输出]
P[Gemini Emitter<br/>YAML优化注入]
Q[Kimi Emitter<br/>完整Markdown]
end
subgraph Output
R[manifest.json]
S[routing_manifest.yaml<br/>渐进式路由清单]
T[claude.xml]
U[codex.md]
V[gemini.md<br/>含YAML块]
W[kimi.md]
X[signature.sha256]
end
A --> D
A --> E
B --> D
C --> E
D --> F
E --> F
F --> G
G --> H
H --> N
N --> I
I --> J
J --> K
K --> L
L --> M
L --> O
L --> P
L --> Q
M --> T
O --> U
P --> V
Q --> W
G --> R
G --> S
R --> X
SkCC 采用 Rust 的 Crate 模块化架构,每个编译阶段对应一个独立的 Crate:
nexa-skill-compiler/
├── nexa-skill-cli/ # CLI 入口 (clap, miette)
│ ├── src/
│ │ ├── main.rs # 程序入口
│ │ ├── commands/ # 命令定义
│ │ │ ├── build.rs # build 命令
│ │ │ ├── check.rs # check 命令
│ │ │ └── validate.rs # validate 命令
│ │ │ └── index.rs # index 命令(生成路由清单)
│ │ └── config.rs # 配置管理
│ └── Cargo.toml
│
├── nexa-skill-core/ # 核心编译逻辑
│ ├── src/
│ │ ├── lib.rs # 库入口
│ │ ├── frontend/ # Phase 1: 前端解析
│ │ │ ├── mod.rs
│ │ │ ├── frontmatter.rs
│ │ │ ├── markdown.rs
│ │ │ └── ast.rs
│ │ ├── ir/ # Phase 2: 中间表示
│ │ │ ├── mod.rs
│ │ │ ├── skill_ir.rs
│ │ │ ├── procedure.rs
│ │ │ ├── constraint.rs
│ │ │ └── nested_data.rs # 嵌套数据检测(新增)
│ │ ├── analyzer/ # Phase 3: 语义分析
│ │ │ ├── mod.rs
│ │ │ ├── schema.rs
│ │ │ ├── mcp.rs
│ │ │ ├── permission.rs
│ │ │ ├── anti_skill.rs
│ │ │ └── nested_data.rs # AST优化检测器(新增)
│ │ ├── backend/ # Phase 4: 后端生成
│ │ │ ├── mod.rs
│ │ │ ├── emitter.rs # Emitter Trait
│ │ │ ├── claude.rs # XML发射器
│ │ │ ├── codex.rs # 纯Markdown发射器
│ │ │ ├── gemini.rs # YAML优化发射器(重构)
│ │ │ ├── kimi.rs # 完整Markdown发射器
│ │ │ └── routing.rs # 路由清单生成器(新增)
│ │ └── error/ # 错误处理
│ │ ├── mod.rs
│ │ ├── diagnostic.rs
│ │ └── codes.rs
│ └── Cargo.toml
│
├── nexa-skill-templates/ # Askama 模板
│ ├── templates/
│ │ ├── claude_xml.html
│ │ ├── gemini_md.html # 支持YAML优化
│ │ └── routing_manifest.yaml # 路由清单模板(新增)
│ └── Cargo.toml
│
├── nexa-skill-wasm/ # WASM 绑定 (可选)
│ ├── src/
│ │ └── lib.rs
│ └── Cargo.toml
│
└── tests/ # 集成测试
├── fixtures/ # 测试用 SKILL.md
└ integration/
graph LR
A[nexa-skill-cli] --> B[nexa-skill-core]
A --> C[nexa-skill-templates]
B --> C
D[nexa-skill-wasm] --> B
E[外部依赖]
E --> |clap| A
E --> |miette| A
E --> |pulldown-cmark| B
E --> |serde_yaml| B
E --> |serde_json| B
E --> |askama| C
E --> |wasm-bindgen| D
E --> |rayon| B
职责:将原始 SKILL.md 物理文件解构为内存中的语法树。
输入:SKILL.md 文件路径或目录路径
输出:RawAST(原始抽象语法树)
关键组件:
| 组件 | 功能 | 技术实现 |
|---|---|---|
FrontmatterParser |
提取 YAML 元数据 | serde_yaml + 正则分割 |
MarkdownParser |
解析 Markdown Body | pulldown-cmark Event Stream |
ASTBuilder |
组装 RawAST | 状态机模式匹配 |
数据流:
SKILL.md
│
├─── FrontmatterParser ────► FrontmatterMeta (struct)
│ (提取 --- 区域)
│
└─── MarkdownParser ────► Event Stream
(pulldown-cmark)
│
└─── ASTBuilder ────► RawAST
(状态机匹配)
职责:将 RawAST 映射为强类型的 SkillIR,并进行嵌套数据检测。
输入:RawAST
输出:SkillIR(核心中间表示,含AST优化标记)
关键转换:
// RawAST → SkillIR 的核心映射逻辑
impl From<RawAST> for SkillIR {
fn from(raw: RawAST) -> Self {
SkillIR {
name: raw.frontmatter.name,
version: raw.frontmatter.version,
description: raw.frontmatter.description,
mcp_servers: raw.frontmatter.mcp_servers.unwrap_or_default(),
input_schema: raw.frontmatter.input_schema,
hitl_required: raw.frontmatter.hitl_required.unwrap_or(false),
pre_conditions: raw.frontmatter.pre_conditions.unwrap_or_default(),
post_conditions: raw.frontmatter.post_conditions.unwrap_or_default(),
fallbacks: raw.frontmatter.fallbacks.unwrap_or_default(),
context_gathering: extract_context_gathering(&raw.body),
procedures: extract_procedures(&raw.body),
few_shot_examples: extract_examples(&raw.body),
// 新增:嵌套数据检测标记
requires_yaml_optimization: NestedDataDetector::detect(&raw.frontmatter),
}
}
}职责:对 SkillIR 进行防呆设计和一致性审计。
输入:SkillIR
输出:ValidatedSkillIR(经过验证和增强的 IR)
分析器链 (Analyzer Chain):
graph LR
A[SkillIR] --> B[SchemaValidator]
B --> C[MCPDependencyChecker]
C --> D[PermissionAuditor]
D --> E[AntiSkillInjector]
E --> F[ValidatedSkillIR]
B -.-> |Warning| G[Diagnostic]
C -.-> |Error| G
D -.-> |Error| G
| 分析器 | 检查内容 | 错误级别 |
|---|---|---|
SchemaValidator |
input_schema 与 Examples 参数一致性 | Warning |
MCPDependencyChecker |
MCP 服务器是否在 Allowlist | Error |
PermissionAuditor |
权限声明与高危操作匹配 | Error |
AntiSkillInjector |
自动注入安全约束 | Silent |
职责:将 ValidatedSkillIR 序列化为特定平台的原生表示,并生成渐进式路由清单。
输入:ValidatedSkillIR + Target Flag
输出:平台特定产物文件 + 路由清单
Emitter Trait 定义:
pub trait Emitter: Send + Sync {
fn target(&self) -> TargetPlatform;
fn emit(&self, ir: &ValidatedSkillIR) -> Result<String, EmitError>;
fn file_extension(&self) -> &'static str { self.target().extension() }
fn requires_manifest(&self) -> bool { true }
fn generate_assets(&self, _ir: &ValidatedSkillIR) -> Vec<(String, String)> { Vec::new() }
fn pre_process(&self, _ir: &ValidatedSkillIR) -> Result<(), EmitError> { Ok(()) }
fn post_process(&self, content: &str) -> Result<String, EmitError> { Ok(content.to_string()) }
}后端发射策略(基于实证研究):
| 平台 | 发射策略 | 学术依据 |
|---|---|---|
| Claude | XML原教旨主义,强标签嵌套 | Anthropic官方指南 + 23%推理准确率提升 |
| Codex | 纯Markdown输出,Schema由API层(Structured Outputs)负责 | Format Tax消除 + 词元效率优化 |
| Gemini | AST优化:嵌套数据自动转YAML | YAML 51.9% > MD 48.2% > JSON 43.1% |
| Kimi | 完整Markdown,保留所有细节 | 超长上下文优势 |
/// SkCC 的核心中间表示
///
/// 这是编译管线中所有阶段的数据交换载体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillIR {
// ===== 元数据与路由 =====
/// 技能唯一标识符 (kebab-case)
pub name: String,
/// 版本号 (语义化版本)
pub version: String,
/// 触发条件与功能描述(用于渐进式路由)
pub description: String,
// ===== 接口与 MCP =====
/// MCP 服务器依赖列表
pub mcp_servers: Vec<String>,
/// 输入参数 JSON Schema
pub input_schema: Option<serde_json::Value>,
/// 输出参数 JSON Schema
pub output_schema: Option<serde_json::Value>,
// ===== 安全与控制 =====
/// 是否需要 Human-In-The-Loop 审批
pub hitl_required: bool,
/// 执行前必须满足的条件
pub pre_conditions: Vec<String>,
/// 执行后必须验证的条件
pub post_conditions: Vec<String>,
/// 错误恢复策略
pub fallbacks: Vec<String>,
/// 权限声明列表
pub permissions: Vec<Permission>,
/// 安全等级
pub security_level: SecurityLevel,
// ===== 执行逻辑 =====
/// 上下文收集步骤
pub context_gathering: Vec<String>,
/// 标准作业程序步骤
pub procedures: Vec<ProcedureStep>,
/// Few-shot 示例
pub few_shot_examples: Vec<Example>,
// ===== 编译期注入 =====
/// Anti-Skill 约束 (由 Analyzer 注入)
pub anti_skill_constraints: Vec<Constraint>,
// ===== AST优化标记(新增)=====
/// 是否需要YAML优化(嵌套数据深度>=3)
pub requires_yaml_optimization: bool,
/// 嵌套数据深度检测结果
pub nested_data_depth: Option<usize>,
}
/// 执行步骤定义
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcedureStep {
/// 步骤序号
pub order: u32,
/// 步骤指令文本
pub instruction: String,
/// 是否为关键步骤 (失败需停止)
pub is_critical: bool,
/// 步骤级别约束
pub constraints: Vec<String>,
}
/// 权限声明
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Permission {
/// 权限类型 (network, fs, db, etc.)
pub kind: PermissionKind,
/// 权限范围
pub scope: String,
}
/// Anti-Skill 约束
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Constraint {
/// 约束来源 (pattern_id)
pub source: String,
/// 约束内容
pub content: String,
/// 约束级别 (warning, error, block)
pub level: ConstraintLevel,
}/// 编译产物元数据清单
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
/// 技能名称
pub name: String,
/// 版本号
pub version: String,
/// 编译时间戳
pub compiled_at: DateTime<Utc>,
/// 编译器版本
pub compiler_version: String,
/// 目标平台列表
pub targets: Vec<TargetPlatform>,
/// 源文件哈希
pub source_hash: String,
/// 依赖的 MCP 服务器
pub mcp_servers: Vec<String>,
/// 安全等级
pub security_level: SecurityLevel,
/// 是否启用YAML优化
pub yaml_optimized: bool,
}/// 渐进式路由清单
///
/// 仅提取name和description,用于Agent初始化阶段的轻量级路由
/// 解决"上下文膨胀"问题
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutingManifest {
/// 生成时间戳
pub generated_at: DateTime<Utc>,
/// 技能总数
pub total_skills: usize,
/// 技能路由条目列表
pub skills: Vec<RoutingEntry>,
}
/// 路由条目
///
/// 极简结构,仅包含触发路由所需的最小信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutingEntry {
/// 技能名称(用于显式调用 @skill-name)
pub name: String,
/// 功能描述(用于隐式语义路由匹配)
pub description: String,
/// 技能目录路径
pub path: String,
/// 安全等级(用于HITL决策)
pub security_level: SecurityLevel,
}SkCC 采用分层错误处理策略,所有错误最终汇聚为 Diagnostic 结构,由 miette 渲染为终端可视化报告。
graph TB
subgraph "错误来源"
A[ParseError<br/>Frontend]
B[ValidationError<br/>Analyzer]
C[EmitError<br/>Backend]
D[IOError<br/>文件操作]
end
subgraph "错误聚合"
E[Diagnostic]
end
subgraph "错误渲染"
F[miette::Diagnostic]
G[终端可视化报告]
end
A --> E
B --> E
C --> E
D --> E
E --> F
F --> G
错误分类与处理策略:
| 错误类型 | 处理策略 | 用户可见性 |
|---|---|---|
ParseError |
立即终止,显示精确行号 | ✅ 必须显示 |
ValidationError |
收集所有错误后统一显示 | ✅ 必须显示 |
Warning |
继续编译,但记录警告 | ✅ check/validate 模式显示;build 模式静默接受(见 N3 修复计划) |
EmitError |
终止当前目标,继续其他目标 | ✅ 显示目标失败原因 |
IOError |
立即终止 | ✅ 显示文件路径 |
WASM 架构暂不实现,作为未来扩展。当前仅支持4个平台(Claude/Codex/Gemini/Kimi)。未来计划将 SkCC 核心库编译为 WebAssembly,用于浏览器端实时校验和预览。
设计预留:WASM 功能范围规划如下——Frontend 解析、IR 构建、Analyzer 校验、Backend 生成(部分)、路由清单生成;文件系统操作需通过 JS Bridge。
利用 Rust 的生命周期机制,在解析 Markdown 时使用 &str 引用而非 String 拷贝:
// pulldown-cmark 事件流中的文本引用
fn extract_text(event: &Event<'_>) -> &'_ str {
match event {
Event::Text(text) => text.as_ref(),
Event::Code(code) => code.as_ref(),
_ => "",
}
}对于多目标编译,使用 rayon 实现并行发射:
use rayon::prelude::*;
fn emit_parallel(ir: &ValidatedSkillIR, targets: &[TargetPlatform]) -> Vec<EmitResult> {
targets.par_iter()
.map(|target| target.emit(ir))
.collect()
}| 格式 | 词元消耗 | 相对效率 | 适用场景 |
|---|---|---|---|
| Markdown | 基准 | 100% | Codex主负载、Gemini主框架 |
| YAML | +10% | 90% | Gemini嵌套数据优化 |
| JSON | +34%~38% | 62%~66% | 仅Codex Schema负载 |
| XML | 中等 | 85% | Claude专用 |
| 阶段 | 预估内存占用 | 优化策略 |
|---|---|---|
| Frontend | ~2MB per file | 流式解析,不保留全文 |
| IR | ~500KB per skill | Arc 共享字符串 |
| Analyzer | ~100KB | 无额外分配 |
| Backend | ~1MB per target | 模板预编译 |
| RoutingManifest | ~50KB for 100 skills | 极简条目结构 |
开发者可通过实现 Emitter Trait 来支持新的目标平台:
// 示例:自定义 Kimi Emitter
pub struct KimiEmitter;
impl Emitter for KimiEmitter {
fn target(&self) -> TargetPlatform {
TargetPlatform::Kimi
}
fn emit(&self, ir: &ValidatedSkillIR) -> Result<String, EmitError> {
// Kimi 偏好纯文本,直接输出完整 Markdown
let mut output = String::new();
output.push_str(&format!("# {}\n\n", ir.name));
output.push_str(&ir.description);
output.push_str("\n\n## Procedures\n");
for step in &ir.procedures {
output.push_str(&format!("{}. {}\n", step.order, step.instruction));
}
Ok(output)
}
fn file_extension(&self) -> &'static str {
"md"
}
}通过实现 Analyzer Trait 添加自定义校验逻辑:
/// Analyzer orchestrator — 实际实现为独立 struct,非 trait
pub struct Analyzer {
schema_validator: SchemaValidator,
mcp_checker: MCPDependencyChecker,
permission_auditor: PermissionAuditor,
anti_skill_injector: AntiSkillInjector,
nested_data_detector: NestedDataDetector,
}
impl Analyzer {
pub fn new() -> Self {
Self {
schema_validator: SchemaValidator::new(),
mcp_checker: MCPDependencyChecker::new(),
permission_auditor: PermissionAuditor::new(),
anti_skill_injector: AntiSkillInjector::new(),
nested_data_detector: NestedDataDetector::new(),
}
}
/// Analyze SkillIR, returning validated IR with warnings or blocking diagnostics
pub fn analyze(&self, ir: SkillIR) -> Result<ValidatedSkillIR, (SkillIR, Vec<Diagnostic>)> {
let mut diagnostics = Vec::new();
let ir = self.nested_data_detector.detect(ir);
diagnostics.extend(self.schema_validator.validate(&ir));
diagnostics.extend(self.mcp_checker.check(&ir));
diagnostics.extend(self.permission_auditor.audit(&ir));
let ir = self.anti_skill_injector.inject(ir);
if diagnostics.iter().any(|d| d.is_blocking()) {
return Err((ir, diagnostics));
}
Ok(ValidatedSkillIR::new(ir, diagnostics)) // warnings preserved!
}
}
⚠️ 与早期设计的差异:早期设计定义了trait Analyzer,但实际实现使用独立的Analyzerstruct + 各子分析器(SchemaValidator,MCPDependencyChecker,PermissionAuditor,AntiSkillInjector,NestedDataDetector),通过Vec<Diagnostic>收集诊断结果。ValidatedSkillIR现携带Vec<Diagnostic>保留非阻断警告(N3修复)。
Agent Skills 开放标准之前,传统做法是将所有指令打包放入全局配置文件(如 agents.md),导致"上下文膨胀":模型在处理每一个简单请求时都需要重新阅读数以万计的无关背景信息。
SkCC 在编译期生成渐进式路由清单(routing_manifest.yaml),仅提取所有技能的 name 和 description:
# routing_manifest.yaml
generated_at: "2026-04-12T10:00:00Z"
total_skills: 15
skills:
- name: "database-migration"
description: "执行 PostgreSQL 数据库表结构修改、数据迁移或复杂 SQL DDL 操作。当用户要求修改数据库表结构时使用此技能。"
path: "build/database-migration"
security_level: "high"
- name: "api-client"
description: "调用外部 REST API 并处理响应数据。当用户需要与第三方 API 交互时使用。"
path: "build/api-client"
security_level: "medium"
- name: "file-organizer"
description: "整理和归档项目文件。当用户要求清理文件目录时使用。"
path: "build/file-organizer"
security_level: "low"graph TB
A[Agent启动] --> B[扫描技能目录]
B --> C[仅读取routing_manifest.yaml]
C --> D[注入name+description到系统提示词]
D --> E[常驻词元极小<br/>~50KB for 100 skills]
F[用户请求] --> G[语义路由匹配]
G --> H[触发activate_skill]
H --> I[动态拉取完整SKILL.md]
I --> J[注入到当前对话上下文]
| 发现 | 数据 | 影响 |
|---|---|---|
| Format Tax | 强制JSON输入导致40%性能衰退 | Codex采用纯Markdown输出,Schema由API层负责 |
| XML优势 | Claude XML比JSON高23%推理准确率 | Claude Emitter保持XML原教旨主义 |
| YAML嵌套优势 | YAML 51.9% > MD 48.2% > JSON 43.1% | Gemini实现AST优化注入 |
| 词元效率 | Markdown比JSON节省34%-38%词元 | Codex主负载采用Markdown |
| 决策 | 原因 | 效果 |
|---|---|---|
| 四阶段管线 | 编译器经典理论 | 关注点分离,易于扩展 |
| 纯Markdown输出 | 消除格式税 | 词元效率优化,Schema由API层负责 |
| YAML优化 | 套数据准确率 | +8.8%解析准确率 |
| 渐进式路由 | 解决上下文膨胀 | 常驻词元极小化 |
- COMPILER_PIPELINE.md - 编译管线各阶段详细实现
- IR_DESIGN.md - SkillIR 数据结构完整定义
- BACKEND_ADAPTERS.md - 各平台 Emitter 实现细节
- ROUTING_MANIFEST.md - 渐进式路由清单机制
- ERROR_HANDLING.md - 错误处理与诊断系统
- API_REFERENCE.md - 公开 Trait 和接口定义