存量代码迁移不是问一句答一句:用 Skill / SubAgent / Agent Team 三层架构管住 AI

2026-05-26 17 预计阅读时间:1 分钟
来源:my.oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:13 分钟

把一个跑了多年的网盘 Android 客户端往 Kotlin Multiplatform(KMP)上搬,不是让 AI "帮我翻译一下这段 Java" 就能搞定的事。几十万行存量代码、跨平台语义对齐、模块间依赖纠缠——任何一个环节失控,AI 就会从助手变成制造混乱的源头。

我们在这场迁移里摸索出了一套三层架构:Skill → SubAgent → Agent Team,分别解决"怎么稳定执行""怎么调度不失控""怎么协作提效"。下面展开讲每一层做了什么、踩了什么坑,最后给一套可落地的配置示例。


Skill:让 AI 的单次输出不再飘

Skill 是最小执行单元——一个 Skill 只做一件事,比如"把 Android 的 Handler 回调模式转成 KMP 的 Coroutine 悬挂函数",或者"把 SharedPreferences 调用改写为 KMP 的 DataStore 操作"。

关键设计原则:

  • 输入输出强契约:Skill 的 prompt 里明确声明输入是什么(一段 Android 源码 + 上下文文件列表)、输出是什么(KMP 目标代码 + 改动说明),不允许 AI 自由发挥格式。
  • 约束比创意重要:迁移场景下,AI 的"创意"往往是灾难——它会自作主张引入新依赖、改命名风格、甚至重构调用链。Skill 的 prompt 必须用硬性规则锁死这些行为。
  • 可回归验证:每个 Skill 的输出要能跑过编译检查或 lint 规则,否则立刻打回重试。

举个实际例子:我们把 Handler.postDelayedCoroutine.delay 的 Skill prompt 精简到核心,大概是这样——

# skill_handler_to_coroutine.py — 一个 Skill 的 prompt 构建模板

SKILL_PROMPT = """你是一个 Android→KMP 代码迁移执行器。

## 输入
- 原始 Android 代码片段(下方 SOURCE_BLOCK)
- 所属模块:{module_name}
- 目标平台:KMP(Android + iOS 共享模块)

## 约束规则
1. 只做语法和 API 映射,不重构业务逻辑。
2. Handler.postDelayed → CoroutineScope.launch { delay(ms) },保持原延迟时长。
3. Handler.sendMessage / handleMessage → 用 Channel 或 Flow 替代,优先 Flow。
4. 不引入新第三方依赖,只用 kotlinx.coroutines 标准库。
5. 输出必须可编译——不要留 TODO 或占位符。

## 输出格式
```kotlin
// 迁移后的 KMP 代码
...

改动说明

  • 列出每一处 API 映射(原 API → 新 API)
  • 标注是否涉及线程模型变化

SOURCE_BLOCK

{source_code} """

这个模板里 `{module_name}``{source_code}` 由调度层动态填充。Skill 本身不关心"这段代码从哪来",它只关心"拿到这段代码后怎么稳定地执行转换"。

---

## SubAgent:调度不失控的中间层

一个 Skill 只能处理一个原子任务。但一次迁移可能涉及"先读文件 → 分析依赖 → 拆分子任务 → 逐个 Skill 执行 → 合并结果"这条链路。SubAgent 就是这条链路的调度器。

核心职责:

- **任务拆分**:拿到一个模块级迁移需求后,SubAgent 先做依赖分析,把大任务拆成 Skill 级子任务队列。
- **上下文管理**:每个 Skill 执行时需要的上下文(比如"这个类被另外三个类引用")由 SubAgent 凇备好再喂进去,而不是让 Skill 自己去猜。
- **失败兜底**:某个 Skill 输出编译失败时,SubAgent 决定是重试、降级(比如换一个更保守的映射策略),还是标记为人工介入点。

我们踩过的一个坑:早期让 SubAgent "自由规划执行顺序",结果它经常先改底层工具类、再改上层调用——编译中间态全是错的,后续 Skill 的输入就脏了。后来改成**自底向上的拓扑排序**,先迁移叶子节点(无内部依赖的工具类),再逐层往上,中间态始终可编译。

```yaml
# subagent_config.yaml — SubAgent 调度策略配置示例

subagent:
  name: module_migration_scheduler
  description: "负责单个模块的 KMP 迁移调度"

  scheduling_strategy:
    order: dependency_topological  # 拉依赖图,自底向上
    max_concurrent_skills: 3       # 最多并行 3 个 Skill
    retry_limit: 2                 # 单个 Skill 失败最多重试 2 次
    fallback: mark_for_human       # 重试耗尽后标记人工介入

  context_preparation:
    include_imports: true           # 喂给 Skill 时带上 import 声明
    include_callers: true           # 带上调用方代码片段(最多 3 层)
    max_context_tokens: 8000        # 上下文 token 上限,防止溢出

  validation:
    compile_check: true             # 每个 Skill 输出做编译检查
    lint_check: true                # 跑 KMP lint 规则
    diff_review: true               # 对比原代码,确认只做映射不做重构

这份 YAML 不是某个现成框架的配置格式,而是我们内部调度引擎的消费格式——你可以按同样的思路在自己的编排层实现。


Agent Team:多模块协作的指挥体系

网盘客户端不是单一模块。文件列表、上传下载、分享、设置——每个模块有自己的迁移节奏和交叉依赖。Agent Team 就是在模块级别做协调。

它解决的问题:

  • 跨模块依赖协商:模块 A 迁移了 FileEntity 到共享层,模块 B 还在用 Android 原生的 FileEntity——Team 层要发现这个不一致,协调两边同步推进。
  • 优先级仲裁:哪些模块先迁(依赖少、改动小、验证快),哪些后迁(改动大、风险高),Team 层根据全局依赖图做排序。
  • 进度收敛:每个 SubAgent 上报模块级进度,Team 层判断整体迁移是否在收敛,是否需要调整策略(比如某个模块卡住时,先迁其他模块绕过它)。

实际操作中,Agent Team 的"全局依赖图"不是靠 AI 自己画出来的——我们先用静态分析工具(Android Lint + 自写 Kotlin 脚本)生成模块间依赖矩阵,再喂给 Team 层做决策。AI 在这里的角色是基于已有数据做调度判断,而不是"自己发现依赖关系"。

# agent_team_orchestrator.py — 简化版 Team 层调度逻辑

import json
from pathlib import Path

def load_dependency_matrix(path: str) -> dict:
    """加载静态分析生成的模块依赖矩阵"""
    with open(path) as f:
        return json.load(f)

def compute_migration_order(dep_matrix: dict) -> list[str]:
    """根据依赖数量排序:被依赖最多的模块先迁(共享层优先)"""
    # dep_matrix 格式: {"module_a": ["module_b", "module_c"], ...}
    # 被依赖次数
    depend_count = {}
    for mod, deps in dep_matrix.items():
        depend_count.setdefault(mod, 0)
        for d in deps:
            depend_count.setdefault(d, 0)
            depend_count[d] += 1

    # 按被依赖次数降序排列——共享底层先迁
    return sorted(depend_count.keys(), key=lambda m: depend_count[m], reverse=True)

def dispatch_module_to_subagent(module_name: str, dep_matrix: dict):
    """把单个模块交给 SubAgent 执行迁移"""
    # 构造 SubAgent 输入:模块名 + 该模块的依赖列表
    subagent_input = {
        "module": module_name,
        "internal_deps": dep_matrix.get(module_name, []),
        "strategy": "dependency_topological",
    }
    # 这里对接你的 SubAgent 调度引擎
    print(f"[Team] 分发模块 {module_name} → SubAgent,内部依赖: {subagent_input['internal_deps']}")
    return subagent_input

# —— 主流程 ——
dep_matrix = load_dependency_matrix("module_deps.json")
order = compute_migration_order(dep_matrix)

print(f"迁移顺序: {order}")
for mod in order:
    dispatch_module_to_subagent(mod, dep_matrix)

运行前你需要准备 module_deps.json,格式示例:

{
  "shared_core": ["file_list", "upload", "download", "settings"],
  "file_list": ["shared_core"],
  "upload": ["shared_core", "file_list"],
  "download": ["shared_core", "file_list"],
  "settings": ["shared_core"]
}

shared_core 被四个模块依赖,排在最前面先迁——这就是 Team 层做的全局决策。


三层之间的关系:不是嵌套,是分工

容易误解的一点:这三层不是"Team 包 SubAgent 包 Skill"的嵌套关系,而是各管各的维度——

层级 管什么 决策粒度 失败影响范围
Skill 单次代码转换的输出质量 函数/类级 单个文件
SubAgent 单模块内的执行顺序和上下文 模块级 单个模块进度
Agent Team 多模块间的优先级和依赖协调 项目级 整体迁移节奏

Skill 出错,SubAgent 兜底重试;SubAgent 卡住,Team 层调整优先级绕过;Team 层的判断依据来自静态分析数据,不是 AI 的"直觉"。每一层都有明确的输入来源和输出契约,这样才不会出现"AI 自己跟自己商量,越聊越跑偏"的情况。


落地前的几条实操建议

  1. 先建静态分析,再接 AI。依赖图、API 调用统计、模块边界——这些数据 AI 自己做不准,必须先用工程工具生成,再喂给调度层。没有数据底座,三层架构就是空中楼阁。

  2. Skill 的 prompt 要短、要硬。迁移场景下,长 prompt 里夹杂的"建议性描述"("你可以考虑……")会被 AI 当成许可。改成硬性规则("必须……""不允许……"),输出稳定性立刻提升。

  3. 编译检查是底线,不是目标。能编译 ≠ 迁移正确。我们额外加了 diff review:对比原代码和迁移后代码的语义差异,如果 AI 做了超出映射范围的重构,直接打回。

  4. 标记人工介入点比强行自动化更高效。某些模式(比如 Android 的 BroadcastReceiver 体系在 KMP 里没有直接对应物)AI 反复尝试都会出问题。SubAgent 重试两次后标记为人工介入,比让它继续试十次更省时间。

  5. Team 层不要让 AI 自由调整全局顺序。迁移顺序是工程决策,应该由人基于业务优先级和风险评估来定,AI 只负责在给定顺序下执行调度。把"先迁哪个模块"交给 AI,它大概率会选最简单的而不是最关键的。


存量代码迁移是工程问题,不是对话问题。三层架构的本质是把 AI 的能力关在合适的笼子里——Skill 关住输出格式,SubAgent 关住执行节奏,Agent Team 关住全局优先级。笼子不是限制,是让 AI 在边界内做到稳定可复用。


相关推荐