把一个跑了多年的网盘 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.postDelayed 转 Coroutine.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 自己跟自己商量,越聊越跑偏"的情况。
落地前的几条实操建议
-
先建静态分析,再接 AI。依赖图、API 调用统计、模块边界——这些数据 AI 自己做不准,必须先用工程工具生成,再喂给调度层。没有数据底座,三层架构就是空中楼阁。
-
Skill 的 prompt 要短、要硬。迁移场景下,长 prompt 里夹杂的"建议性描述"("你可以考虑……")会被 AI 当成许可。改成硬性规则("必须……""不允许……"),输出稳定性立刻提升。
-
编译检查是底线,不是目标。能编译 ≠ 迁移正确。我们额外加了 diff review:对比原代码和迁移后代码的语义差异,如果 AI 做了超出映射范围的重构,直接打回。
-
标记人工介入点比强行自动化更高效。某些模式(比如 Android 的
BroadcastReceiver体系在 KMP 里没有直接对应物)AI 反复尝试都会出问题。SubAgent 重试两次后标记为人工介入,比让它继续试十次更省时间。 -
Team 层不要让 AI 自由调整全局顺序。迁移顺序是工程决策,应该由人基于业务优先级和风险评估来定,AI 只负责在给定顺序下执行调度。把"先迁哪个模块"交给 AI,它大概率会选最简单的而不是最关键的。
存量代码迁移是工程问题,不是对话问题。三层架构的本质是把 AI 的能力关在合适的笼子里——Skill 关住输出格式,SubAgent 关住执行节奏,Agent Team 关住全局优先级。笼子不是限制,是让 AI 在边界内做到稳定可复用。