"河"修好了,"渠"谁来管?——Skill 工程化再往深一层

参考来源:《当我把 AI 变成一个"算法":Skill 工程化设计的心路历程》原文链接



1. 引言:渠修好了,然后呢?

上一篇《当我把 AI 当算法用:Skill 工程化的落地实践与反思》写完以后,后台收到一条留言:

“你的清单很好,但我的真实情况是——做完一个项目后,积累了 30 多个 Skill。现在每次加一个新 Skill,不确定会不会搞崩旧的。”

这条留言让我意识到,上一篇文章只回答了问题的一半。

修好一条渠——用 CLI 收敛确定性、用 Gate 卡住关键步骤、用模板变量和状态持久化解决记忆漂移——这些是"单渠工程"。但当渠的数量从 3 条变成 30 条、50 条,新的问题出现了:

  • 两个 Skill 的指令互相矛盾,Agent 该听谁的?
  • 50 个 Skill 全部塞进上下文,Token 预算爆了。
  • 一个 Skill 改了行为,没人知道它影响了哪些下游场景。

修渠是第一步。管渠,才是第二步。

而这一步触及了一个更深层的问题:当 Skill 本身也变成需要管理的对象时,谁来管理"管理 Skill 的人"?

2. 回看原文:河与渠之外,还有水闸

快速复盘一下公众号原文《当我把 AI 变成一个"算法":Skill 工程化设计的心路历程》的核心框架。

原文提出一个精妙的比喻:LLM 是河——有概率性、不可控、但天然灵动。外部程序是渠——给河划定边界,让它只流向我们需要的地方。 Agent 的行为不是靠抑制河的本性,而是靠修渠来引导。CLI 工具、Gate 门禁、模板变量和状态持久化,分别从不同维度收窄 Agent 的自由度,让模糊的 LLM 变成可预测的决策引擎。

这个框架在"单任务、单 Skill"的层面是自洽的。但原文在收尾处埋了一个伏笔:

“修渠不难。知道哪里该修渠、哪里该留河,才是最需要经验判断的地方。”

这句话在单 Skill 场景下指向的是"分界线决策矩阵"——我们上一篇的第四节已经把这件事讲清楚了。但如果把视角拉到整个 Skill 体系,这句话的含义需要更新:

它不再仅仅是"一个 Skill 里哪些事交给 CLI、哪些事留给 LLM",而是**“多个 Skill 构成的系统中,谁来决定哪个 Skill 上场、什么时候上场、上场后能不能跟其他 Skill 和平共处”**。

3. Skill 的递归陷阱

3.1 从"规则写到 200 行"到"Skill 写到 50 个"

上一篇文章提到一个现象:规则写到 200 行,AI 开始叛逆。原因是长上下文中的指令优先级被稀释——LLM 的注意力窗口承受不住过多约束。

同样的逻辑,在 Skill 层面也在重演。

让我们做一个思想实验。假设你的 Agent 已经经历了三个阶段:

  • 阶段一:1 个 Skill,“帮我创建通义千问模型部署”。干净利落,运行完美。
  • 阶段二:扩展到 10 个 Skill。“创建部署”、“监控运行状态”、“自动扩缩容”、“日志分析”、“成本优化建议”……每个 Skill 单独测试都 OK。
  • 阶段三:积累到 50 个 Skill。问题出现了——

某天用户说:"帮我把 qwen-plus 从北京节点迁移到杭州节点,同时保持自动扩缩容策略不变。"这触发了至少 4 个 Skill:

  1. deploy_model:创建新部署
  2. auto_scale_policy:保持扩缩容策略
  3. cost_optimizer:检查迁移后成本变化
  4. region_validator:验证杭州节点的可用性

结果:cost_optimizer 给出的建议是"建议关闭自动扩缩容以节省成本",而 auto_scale_policy 的指令是"保持自动扩缩容策略不变"。两条指令互相矛盾。Agent 在概率空间里掷了一次骰子,选了 cost_optimizer 的建议,把扩缩容关了。

这不是任何一个 Skill 写得不好。每个 Skill 在单独测试时都完美运行。问题出在它们被放进同一个系统后,系统没有能力管理它们之间的关系。

3.2 递归的本质

这就是我所说的"Skill 的递归陷阱"。

在底层,我们解决"Agent 不听话"的方式是写 Skill——给 Agent 划边界。

在中层,当单个 Skill 也解决不了问题时,解决方案是写更多 Skill。

在顶层,当更多 Skill 自己开始打架时,解决方案是什么?

你不能继续写 Skill 来管理 Skill 之间的冲突——因为管理冲突的 Skill 本身也会与其它 Skill 产生新的冲突。这就是递归陷阱。

Skill 工程化的终极目标,不是写好单个 Skill,而是设计 Skill 的运行环境。

这和操作系统的发展史一模一样。1960 年代的计算机,每次只能运行一个程序,程序员手动操作硬件。问题的解法不是让单道程序写得更长,而是发明操作系统——一个不参与计算、但管理谁在什么时候计算的元程序。

Skill 体系需要的,正是一个"Skill 操作系统"。

4. Skill "操作系统"的四个核心模块

既然类比已经成立,下面逐一拆解这个"Skill 操作系统"应该具备的四个核心模块。每个模块会给出参考实现思路,使用的示例平台为阿里云百炼。

4.1 调度器(Scheduler):该谁上场?

操作系统的进程调度器只解决一个问题:CPU 时间怎么分。Skill 调度器同理——当多个 Skill 都声称自己适配当前用户请求,该让谁上场?

一个可落地的实现是基于触发条件的优先级调度。每个 Skill 声明自己的触发条件、优先级和互斥关系,调度器负责匹配和裁决。

以下是调度配置的参考格式:

# skill-scheduler.yaml —— Skill 调度规则配置
skills:
  - name: deploy_model
    triggers:
      - keywords: ["部署", "创建模型", "上线"]
      - intent: "create_deployment"
    priority: 10        # 0-100,数值越大越优先
    conflicts_with: []  # 与哪些 Skill 互斥
    context_budget: 800 # 占用上下文 token 预算

  - name: auto_scale_policy
    triggers:
      - keywords: ["扩缩容", "弹性", "实例数"]
      - intent: "manage_scaling"
    priority: 8
    conflicts_with: ["cost_optimizer_reduce"]
    context_budget: 600

  - name: cost_optimizer
    triggers:
      - keywords: ["成本", "优化", "省钱", "降本"]
      - intent: "optimize_cost"
    priority: 5         # 优先级低于扩缩容——成本不能压过稳定性
    conflicts_with: ["auto_scale_policy"]
    context_budget: 500

关键设计点

  1. 优先级反映业务约束——稳定性(扩缩容)> 成本优化,这是业务决策,不是技术决策。
  2. 互斥关系显式声明——conflicts_with 字段让两个互相矛盾的 Skill 永远不会同时激活。当 cost_optimizer 的"建议关闭扩缩容"和 auto_scale_policy 的"保持扩缩容"冲突时,调度器直接屏蔽低优先级的一方。
  3. context_budget 提前分配——避免调度器把 10 个 Skill 全部塞进上下文,导致 Token 超限。调度器根据用户意图只激活 2-3 个最相关的 Skill。

4.2 上下文管理器(Context Manager):谁的指令有效?

LLM 的上下文窗口再大也是有限的。把 50 个 Skill 的指令全部塞进去,不是在帮 Agent,而是在淹没它。

上下文管理器的核心职责是:根据当前任务,只把相关 Skill 的指令加载进上下文,并确保换出旧指令时关键状态不丢失。

参考实现思路:

class SkillContextManager:
    """Skill 上下文管理器 —— 模仿 OS 虚拟内存的换入换出机制。"""

    def __init__(self, max_context_tokens=4000):
        self.max_tokens = max_context_tokens
        self.active_skills = []       # 当前在上下文中的 Skill
        self.skill_registry = {}      # 完整 Skill 索引(驻留"磁盘")
        self.shared_state = {}        # 跨 Skill 共享状态(不随换出丢失)

    def activate(self, skill_name: str, user_intent: str):
        """激活一个 Skill,必要时换出低优先级 Skill。"""
        skill = self.skill_registry[skill_name]

        # 计算当前上下文占用
        used = sum(s.context_budget for s in self.active_skills)

        # 如果塞不下,按优先级换出最低的
        while used + skill.context_budget > self.max_tokens:
            victim = min(self.active_skills, key=lambda s: s.priority)
            self.swap_out(victim)
            used -= victim.context_budget

        skill.load_into_context(shared_state=self.shared_state)
        self.active_skills.append(skill)

    def swap_out(self, skill):
        """换出一个 Skill:保留共享状态,释放上下文空间。"""
        # 在换出前,把需要跨 Skill 传递的状态写入共享区
        self.shared_state.update(skill.export_shared_state())
        skill.unload_from_context()
        self.active_skills.remove(skill)

关键设计点

  1. 共享状态独立于上下文之外——当 Skill A 被换出、Skill B 换入时,Skill B 仍然能读取到 Skill A 留下的关键中间状态。类比 OS 中"写入磁盘的页面,下次换入时数据还在"。
  2. 优先级既是调度依据,也是换出依据——低优先级 Skill 不仅不参与本轮激活,空间不够时还被优先换出。

4.3 冲突检测器(Conflict Detector):谁和谁在打架?

操作系统有死锁检测。Skill 体系需要冲突检测。

冲突的类型不只是"两个 Skill 指令矛盾"。更隐蔽的冲突包括:

  • 资源冲突:Skill A 要求 API 限流策略为"激进",Skill B 要求"保守"。
  • 时序冲突:Skill A 要求"先扩容再迁移",Skill B 直接触发迁移。
  • 权限冲突:Skill A 要求"自动审批",Skill B 要求"强制人工确认"。

参考实现思路——在 Skill 注册时声明"控制域":

# skill-conflict.yaml —— Skill 控制域声明
conflict_domains:
  - name: scaling_policy
    description: "扩缩容策略"
    mutual_exclusive: true     # 同一控制域下只能有一个 Skill 处于 active
    allowed_values: ["aggressive", "conservative", "adaptive"]

  - name: migration_approval
    description: "迁移审批模式"
    mutual_exclusive: true
    allowed_values: ["auto", "manual_confirm"]

skills:
  - name: auto_scale_policy
    controls:
      - domain: scaling_policy
        value: adaptive

  - name: cost_optimizer_aggressive
    controls:
      - domain: scaling_policy
        value: conservative    # 与 auto_scale_policy 冲突!

检测逻辑:当一个 Skill 被调度器选中后,冲突检测器扫描该 Skill 声明的所有控制域,检查是否有已激活的 Skill 在同一控制域上声明了不同值。如有,按优先级裁决或报警通知开发者。

4.4 审计与回放(Audit & Replay):到底发生了什么?

操作系统有系统日志(syslog)。Skill 体系需要审计日志。

当 Agent 使用了 4 个 Skill 执行一个复杂任务,最终结果不符合预期时,你需要回答三个问题:

  1. 调度器激活了哪些 Skill?为什么是它们?
  2. 每个 Skill 在什么时间点做了什么操作?
  3. 如果重来一次,用同样的输入,结果是否一致?

参考日志格式:

{
  "trace_id": "sched-20260703-001",
  "timestamp": "2026-07-03T14:23:05+08:00",
  "user_intent": "把qwen-plus从北京迁移到杭州,保持扩缩容策略",
  "schedule_decision": {
    "activated": ["deploy_model", "auto_scale_policy", "region_validator"],
    "suppressed": ["cost_optimizer"],
    "suppress_reason": "conflicts_with auto_scale_policy (priority 8 > 5)"
  },
  "skills": [
    {
      "name": "deploy_model",
      "action": "create_deployment",
      "params": {"model": "qwen-plus", "region": "cn-hangzhou"},
      "result": "success",
      "duration_ms": 3200
    },
    {
      "name": "auto_scale_policy",
      "action": "preserve_policy",
      "source_deployment": "dep-bj-001",
      "target_deployment": "dep-hz-002",
      "result": "policy_copied",
      "duration_ms": 800
    }
  ],
  "context_snapshot": {
    "active_skills_count": 3,
    "context_tokens_used": 1900,
    "swapped_out": ["cost_optimizer"]
  }
}

关键设计点

  1. 记录"为什么没激活",而不是只记录"激活了什么"——suppress_reason 字段让冲突检测的结果可追溯。
  2. 一条 trace 串联整个调用链——从调度决策到每个 Skill 的执行结果,全部可追踪。
  3. context_snapshot 记录了上下文管理的决策——哪些 Skill 被换出、当前 Token 占用是多少,方便定位"为什么这次执行表现不同"。

5. 从设计者到系统架构师

前四节讲的是"系统"。这一节讲的是"人"——从写好一个 Skill 到设计 Skill 体系的思维转变,需要三个关键动作。

5.1 思维转变的三个信号

当你的团队出现以下信号时,说明现有的"写 Skill"范式已经到头了:

信号 现象 根因
加一个 Skill,旧的崩了 上线新 Skill 后,旧场景回归出 Bug 缺乏冲突检测
调试变慢 定位一个错误需要翻 4 个 Skill 的日志 缺乏统一审计
上下文炸了 Agent 的行为越来越不稳定 缺乏上下文管理

5.2 Skill 元规则自检清单

从系统角度审视你当前的 Skill 体系,每次扩展前过一遍这 5 条:

  1. 这个新 Skill 和现有 Skill 有冲突吗? 检查控制域——有没有跟已注册 Skill 争抢同一个配置项、同一个流程节点的决策权?
  2. 加了它之后,单次请求平均激活几个 Skill? 如果超过 3 个,说明调度粒度过细——考虑合并一些逻辑相近的 Skill。
  3. 把最重要的 3 个 Skill 抽掉,系统还能运行吗? 不能说明你的 Skill 体系没有分层——核心 Skill 和辅助 Skill 应该按故障影响分开管理。
  4. 你能说出上一次"为什么激活了 Skill A 而不是 Skill B"吗? 不能说明缺审计——调度决策必须是可解释的。
  5. 一个新人接手这个 Skill 体系,他能在 1 小时内理解整体结构吗? 不能说明缺文档和元规则——Skill 的元管理不只是代码,还包括规则本身的组织结构。

5.3 实操起点

如果你现在就想动手搭建 Skill 操作系统,不需要从零实现调度器和上下文管理器。最小可行起步方案是:

  1. 用一份 YAML 文件集中管理所有 Skill 的元信息(触发条件、优先级、控制域声明)。
  2. 每次新增 Skill 时,手动跑一遍元规则自检清单的第 1 条——检查冲突。
  3. 调度逻辑先保持简单——只做关键词匹配 + 优先级排序,不要一上来就写意图识别。

先管起来,再优化。

5.4 边界说明

本文提出的 Skill 操作系统框架适用于 Skill 数量超过 15 个、且 Skill 之间存在复杂交互关系的场景。如果你的 Skill 体系仍在 10 个以内且各自独立,直接使用第一篇的"CLI + Gate + 模板变量"方案更经济。另外,调度器和冲突检测器的自动化实现有一定复杂度——建议先用 YAML 配置 + 手动检查的轻量方案跑通流程,确认收益后再考虑全自动化。

6. 总结

原文用"河与渠"比喻 LLM 与外部程序的关系,上一篇我们用这个框架解决了"单渠工程",这一篇把它推进了一步:

渠多了,需要水闸。

Skill 操作系统的四个核心模块——调度器、上下文管理器、冲突检测器、审计回放——本质上就是为 Skill 体系修建的水闸系统。它们不参与具体的"业务计算"(那是 Skill 自己的事),但它们决定哪个 Skill 在什么时候、以什么方式参与计算。

这还不是终点。当水闸多了,可能还需要"水闸的水闸"。Agent 工程化这件事,本质上是在每一个管理层级上做同一件事:把不确定性收窄,把确定性交给系统。

这条路才刚刚开始。


参考资料


感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
在这里插入图片描述

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐