来源:https://mp.weixin.qq.com/s/jCblt34Ea_p72YnHGRTu3g


我们在一棵纯 AOSP 17(android-17.0.0_r1,SDK 37)整机源码树上,围绕 Claude Code 搭建了一套四层 harness 工程,让 coding agent 能够在上千个 git project、千万行量级的 repo 工程里稳定地导航、编译、部署、验证,目标设备是 Cuttlefish 虚拟机(aosp_cf_x86_64_phone)。本文先分析 coding agent 在整机源码树上应用的困难,再梳理 Anthropic 官方博客给出的大库通用要点,接着盘点网络上已有的同类方案,最后介绍我们的四层解决方案——每层解决什么问题、如何协同运转,以及一路探索踩过的坑。

一、问题:为什么 coding agent 在整机源码树上"开箱不可用"

Claude Code 这类 agent 不给代码库建索引(不做 RAG),而是用 agentic search 现场导航——grep、读文件、跟引用,像一个新来的工程师翻代码。这个设计有一个巨大的好处和一个巨大的代价:

  • 好处:永不过期。 它永远读的是 live 代码,不会像向量索引那样"返回两周前已改名的函数、或引用一个已删除的模块,却不告诉你它过期了"。

  • 代价:上下文窗口是唯一稀缺资源。 导航的每一步(grep 结果、读过的文件)都在消耗上下文;导航质量完全取决于你把代码库"布置"得多好。

AOSP 整机源码树把这个矛盾推到极端,有三个放大器:

不搭 harness,直接在整机树上用 coding agent 会反复撞上这些墙(均为实际遇到或验证过的):

痛点

现象

导航失效

全树 grep 一次就吞光上下文;文本匹配跳到错误的同名符号

上下文盲区

每个新会话都不知道"当前在做哪个 feature、哪些仓能动、有哪些硬约束"

流程知识丢失

每次都要重新教它怎么编译、产物在哪、push 哪些文件

"编过 = 改对"幻觉

编译成功就认为功能正确,会话心满意足地结束,设备一跑就崩

危险操作无门禁

adb push/reboot、repo sync 这类动设备/动树的操作只靠模型自觉

知识污染

上下文文档混进 gerrit project 的提交

隐性经验反复付学费

"改了这个类布局必须连某个 so 一起重编"这类血泪知识,不固化下来每次重新踩

核心论断:模型不是瓶颈,环境才是。 社区先行者 utzcoz 用 Claude Code 做成 4 个 AOSP 级项目后的结论也是如此:这类工作 coding agent 开箱做不好,能做成靠的是围绕 agent 搭的 harness engineering——"harness 诚实,产出就诚实"。

二、官方参照:Anthropic《How Claude Code works in large codebases》的要点

上一节的困境不是 AOSP 独有的。Anthropic 官方博客《How Claude Code works in large codebases》归纳了 Claude Code 在大型代码库(百万行 monorepo、几十年遗留系统、几十个仓的分布式架构)落地成功的共性模式——它不是为 AOSP 写的,但几乎每一条都能对上整机树的处境,是我们方案的通用理论底座。这里先把它的要点提炼出来;后文第四节起的四层,本质就是把这些通用原则一条条落到整机树上的具体形态。

2.1 Claude Code 怎么在大库里导航:agentic search,而非 RAG

博客把第一节我们已经点到的机制说得更透:

  • agentic search:像工程师一样遍历文件系统、读文件、用 grep 精确定位、跟着引用跨库跳转;本地运行、不需要建立/维护/上传任何索引

  • RAG 在大规模下会失效:embedding 管线追不上活跃的工程团队——开发者查询时,索引反映的是几周/几天/几小时前的代码,于是返回一个两周前已改名的函数、或引用一个上个 sprint 已删除的模块,却完全不提示它已过期

  • 代价与甜区:agentic search 反过来要求足够的起始上下文才知道去哪找;导航质量取决于代码库被"布置"得多好(用 CLAUDE.md + skills 分层)。若向一个十亿行的库问一个模糊 pattern,会在开工前就撞上上下文窗口墙。在代码库布置上投入的团队,效果明显更好。

2.2 harness 与模型同等重要:五个扩展点 + 两项能力

博客点名一个最常见的误解——以为 Claude Code 的能力只由所用模型决定。实际上围绕模型的生态(harness)比模型本身更决定表现。harness 由五个扩展点构成,外加两项能力,且叠加有顺序——每一层都建立在前一层之上

组件

是什么

何时加载

最适合

常见误用

CLAUDE.md

自动读取的上下文文件

每个会话

项目约定、代码库知识(根=全局、子目录=局部)

把该进 skill 的可复用经验塞进来

hooks

关键时刻运行的脚本

事件触发

自动化一致行为、捕获会话经验(自我改进)

用 prompt 去做本应自动跑的事

skills

针对特定任务打包的指令

按需、相关时

跨会话/项目的可复用专长(渐进式披露、可按 path scope)

全塞进 CLAUDE.md

plugins

打包 skills/hooks/MCP

配好后常驻可用

把一套可用配置分发到全组织

让好做法停留在部落知识

LSP

语言服务器的实时代码智能

配好后常驻可用

符号级导航、类型语言里自动查错

以为它自动就有

MCP servers

连接外部工具与数据

配好后常驻可用

让 Claude 够到本来够不到的内部工具

基础没跑通就先建 MCP

subagents

独立上下文的隔离实例

被调用时

把探索与编辑分离、并行

在同一会话里既探索又编辑

其中几条博客特别强调的:hooks 最有价值的用法不是防错,而是让配置自我改进(stop hook 会话末反思→提议改 CLAUDE.md);skills 可 path-scoped,只在相关目录激活;LSP 是多语言大库里最高杠杆的投资之一(没有它,Claude 对文本 pattern-match,会落到错误的同名符号);subagents 的典型用法是只读子代理测绘子系统、写进文件,主代理再带全貌编辑

2.3 三个配置模式

博客从成功部署里提炼出三个反复出现的模式:

  1. 让大库对 Claude 可读:CLAUDE.md 精简且分层(根只放指针 + 关键坑);在子目录而非仓根初始化(Claude 会自动向上加载沿途每个 CLAUDE.md,根上下文不丢);按子目录 scope test/lint 命令(跑全套会超时、烧上下文);用 .ignore / 版本化的 permissions.deny 排除生成物、构建产物、三方码;目录结构不给力时写一份轻量 codebase map;跑 LSP 让按符号而非字符串搜

  2. 随模型演进主动维护 CLAUDE.md:为旧模型缺陷写的规则会拖累新模型(如"每次重构拆成单文件改动"会阻止新模型做它本已擅长的协调跨文件编辑);为补模型/工具缺陷写的 skill/hook 一旦缺陷消失就成负担。每 3–6 个月、或模型换代后感觉见顶时做一次配置重审。

  3. 指派 owner:技术配置本身不驱动采纳;铺开最快的组织在放开前就有专人/小队把工具接进工作流,出现 agent manager(PM/工程混合角色)或至少一个 DRI,并尽早对齐治理(谁管 skill/plugin、避免重复造轮子、AI 代码走同样的 review)。

2.4 适用边界

博客最后划了适用范围:Claude Code 面向常规软件工程环境——工程师是主要贡献者、用 Git、标准目录结构。非常规设置(游戏引擎的大二进制资产、非常规版本控制、非工程师贡献代码)需要额外的配置工作。

这些都是通用结论;而整机树把每一条的难度都放大了(第一节的三个放大器)。后文第四节起,就是我们把这套通用原则逐条落到 AOSP 整机树上的具体形态——并在两处(子目录初始化、plugin 分发)因 repo 工程的现实做了有意背离(见第四节末)。

三、他山之石:网络上已有的类似方案

动手自建之前,我们先调研了社区在"AOSP + coding agent"这个方向上已有的探索(调研时间 2026-07)。有代表性的四个方案,恰好各占一个生态位:

方案

形态

一句话定位

Lightrion AOSP RAG

托管 MCP 服务(SaaS)

对公开 AOSP 各版本做语义检索,agent 即插即查

utzcoz《Using Claude Code on AOSP-scale projects》

方法论(博客)

4 个已交付 AOSP 级项目沉淀的 harness engineering 十模式

hyperb1iss/hyperdroid-skill

Claude Code 插件

Android 通用领域技能包(adb/fastboot/构建/LineageOS)+ crash 分析 agent

jonaschen/Android-Software

分层 skill 知识包

L1 路由 → L2 子系统专家,防幻觉路径与跨域错配

Lightrion AOSP RAG:托管的 AOSP 语义检索

一个商业 MCP 服务:把 AOSP 13–17 各发布版预先建好索引,暴露 search_code / get_chunk / get_file / list_versions / diff_versions 五个工具,任何 MCP 客户端加一个 bearer token 就能用自然语言检索 AOSP 源码,还能按 minor release 钉住版本、跨版本 diff。

  • 优点:零本地成本——不需要本地源码树、不需要自己建索引;多版本覆盖 + 跨版本 diff 是独有能力("这个函数在 15→17 之间改了什么"一问即得);接入是标准 MCP,五分钟配完。

  • 局限:它索引的是公开 AOSP 发布版,而整机开发的工作对象是自己的本地树——你刚改过的代码、本地 feature 分支、树上的任何 delta 它都不知道。这正是 Anthropic 官方博客点名的 RAG 死穴("返回已改名的函数却不告诉你它过期了")在 fork 场景下的极端形态。此外代码问题出网查询有保密性顾虑;且它只覆盖"读与查"这一层,编译、部署、验证、护栏全不涉及。

  • 我们的取舍:不能作为本地树的主导航(live 树必须 agentic search + LSP),但作为"查上游基线/跨版本差异"的补充通道有真实价值。

utzcoz 十模式:被四个交付项目验证过的方法论

作者用 Claude Code 做成并发布了 4 个 AOSP 级项目(ARM64→x86_64 二进制翻译器、AOSP 14 多窗口补丁集、Chromium WebXR 移植、64 章 AOSP 内核书),沉淀出十条 harness engineering 模式,分三组:跨会话保存状态(CLAUDE.md 行为契约、handoff 交接文档)、验证(模拟器基座、verify 脚本只编码一次、红条 TDD、读截图判对错)、输出可信(结论带源码路径行号、冷启动对抗 review)。

  • 优点:唯一经过"真的交付了东西"检验的完整方法论;对"编过 = 改对"幻觉、"似是而非 ≠ 正确"这两个 agent 根性问题给出了系统解法;多条模式可直接照抄(CLAUDE.md 只写 agent 默认会犯的错、verify 脚本单入口确定性输出)。

  • 局限:它是方法论而非可安装工件,每个项目都要自己重新落地;其项目形态是"单仓 fork + 模拟器",没有处理 repo 千仓工程特有的问题——上下文文件会污染 gerrit project、上下文如何随 feature 分支切换;对代码智能层(LSP/compdb)也没有展开。

  • 我们的取舍:十模式是本文方案在理念层的最大来源——CLAUDE.md 行为契约、verify 确定性脚本、"harness 诚实产出就诚实"都直接进入了设计;repo 工程特有的部分(第六、七节)则是我们补上的。

hyperdroid-skill:插件化的 Android 通用技能包

LineageOS 社区开发者的 Claude Code 插件(MIT):四个按触发词自动激活的 skill(android 设备/adb、android-fastboot 刷机/分区/防砖、android-build Gradle+AOSP 构建、lineageos repo/Gerrit 工作流)加一个受限工具的 crash-analyzer 子代理(自主收集 logcat/tombstone/ANR 后给诊断)。

  • 优点工程骨架是四家里最值得抄的——单仓分发多 skill + agent(plugin.json/marketplace 一键装)、渐进式披露(SKILL.md 速查 + references/ 深度页按需加载)、触发词自动激活、受限工具 + 固定工作流的子代理模板、仓库自校验 Makefile(CI 强制每个 skill 结构合规)。

  • 局限:内容是参考手册级的通用知识(adb/fastboot/构建命令速查),视角偏三方 ROM 玩机而非整机平台开发;不绑定任何具体源码树——不知道你的 feature、你的编译产物、你的验证脚本;对树内导航、上下文经济、gerrit 污染等核心矛盾不涉及。

  • 我们的取舍:抄骨架、自己填肉——渐进式披露和触发激活的思想直接体现在我们的 path-scoped skill 设计里(paths glob 替代触发词,粒度更准)。

Android-Software:分层专家路由的知识包

面向 Android Software Owner / BSP 工程师的分层 skill 集(Beta,对 Android 15 验证):所有任务先过 L1 路由器(意图 → 已验证的 AOSP 路径映射),再加载对应的 L2 子系统专家(build/SELinux/HAL/framework/init/内核 GKI/bootloader/ATF/pKVM 等 12 个),每个专家带子系统知识、禁止动作和工具链;另有 hindsight notes 机制沉淀跨会话经验。

  • 优点:直击 agent 在 AOSP 上的三大失败模式(幻觉路径、跨域错配——把 bootloader 问题路由给 init、版本知识漂移);"MMU 式按需加载"与我们的上下文经济诉求同源;子系统覆盖面最广(连 LK/ATF/pKVM 这类 vendor 层都有路由位);没有本地源码也能回答问题。

  • 局限:本质是静态知识包——与"你这棵树"零绑定,答案来自预写的知识而非现场读码,版本演进要人工维护(对 A15 验证,用在 17 上就有漂移窗口,恰是它自己要解决的问题);无 symbol 级导航;无编译→部署→验证闭环;单人 Beta 项目,成熟度有限。

  • 我们的取舍:分层按需加载的思想与我们"索引粒度注入、详情按需 Read"殊途同归;但我们把"知识从哪来"反过来了——不预写知识,让 agent 现场读真代码,harness 只负责把它引到对的地方。

四方案对照与我们的位置

用整机树内开发需要的四类能力——代码智能、上下文、流程、护栏与验证,这也正是下文我们方案的四层框架——给它们做覆盖度体检(● 深度覆盖 ◐ 部分/通用级 ○ 基本不涉及):


方案

① 代码智能

② 上下文

③ 流程

④ 护栏与验证

Lightrion AOSP RAG

◐ 仅上游版本,不含本地改动

utzcoz 十模式

○ 未展开

◐ CLAUDE.md 契约 + handoff

◐ 脚本化约定

● verify/红条 TDD/对抗 review

hyperdroid-skill

◐ 通用命令速查

◐ crash-analyzer

Android-Software

○ 防幻觉路径 ≠ symbol 导航

◐ 分层按需加载

◐ 子系统流程知识

本文方案

● 本树 compdb + clangd

● 随 feature 分支注入

● path-scoped skill

● 硬门禁 + verify 闭环

结论一目了然:四个方案分别解决了检索、方法论、通用领域知识、知识路由,但没有一个解决"这一棵树"的问题——本地 fork 的 symbol 级实时导航、repo/gerrit 布局下不污染上游的上下文组织、随 feature 分支自动切换的工作状态、绑定本树目标设备的确定性验证环。这块空白,就是下文四层 harness 的主体;而各家的长处(utzcoz 的验证纪律、hyperdroid 的渐进披露骨架、Android-Software 的按需分层思想)都被吸收进了对应层的设计。

四、方案总览:四层 Harness

4.1 一个业务前提与五个术语

展开四层之前,先厘清本方案赖以成立的一个业务前提和几个反复出现的术语——它们决定了这套 harness 为什么可行、四层各自靠什么落地。

业务前提:一个专项 = 一个 feature = 一个本地分支 = 3–8 个单仓。 真实的手机厂商以专项的形式推进 OS 特性开发(一个专项就是一次成体系的特性迭代)。我们把一个专项对应成一个 feature,并约定 一个 feature 独占一个 repo 本地分支repo start <feature> --all),该 feature 的全部改动只在这个分支上进行——这样 harness 才有一个稳定的"当前在做什么"的锚点。一个 feature 通常只触及 3–8 个 git 单仓(例如"新增一个系统服务 + 一个边栏应用",落在 frameworks/baseframeworks/native、新建 app 仓、build/makesystem/sepolicy 上)。正是"涉及仓有限、且随分支固定"这个业务事实,让后文所有"随 feature 组织、随分支自动切换、按仓精简"的机制得以成立。

五个术语(四层各自的关键落地物,正文表格里会直接用到):

  • LSP(Language Server Protocol,语言服务器协议):编辑器/agent 与语言服务器之间的标准协议,把"跳到定义、查找所有引用、按符号导航、类型报错"这类代码智能能力暴露出来。它让 agent 按 symbol(符号) 而非文本字符串检索——同名函数在不同文件/语言里能被精确区分,不会 pattern-match 到错误的符号。C++ 侧我们用 clangd 作为语言服务器,喂给它一份 compdbcompile_commands.json,编译命令数据库)即可(第①层,详见第五节)。

  • SessionStart hook:Claude Code 在会话启动 / 恢复 / 清屏 / 压缩时触发的钩子脚本,它的 stdout 会被 Claude Code 注入为会话上下文。我们用它在每次会话"睁眼"时,按当前分支自动把"这个 feature 在做什么、涉及哪些仓、验证跑哪个脚本"喂给 agent(第②层,详见第六节)。

  • path-scoped skill:skill 是按需加载的打包指令;带 paths glob 的 skill 只在 agent 读到匹配路径的代码时才激活。我们用它承载"改到这片代码怎么编译 / push / 验证"这类过程性知识——不读到就零上下文占用(第③层,详见第七节)。

  • permissions.ask 硬门禁:Claude Code 的权限机制,把匹配到的危险命令(adb push/rebootcvd start/stoprepo syncm clean 等)变成执行前的系统级弹窗确认,不依赖模型记性(第④层护栏,详见第八节)。

  • features/<分支>/verify-*.sh 确定性脚本:每个 feature 自带的验证脚本,就是这个 feature 的"测试"——输出只有确定性的 PASS/FAIL,用来斩断"编过 = 改对"的幻觉(第④层验证,详见第八节)。

4.2 四层总览

我们把「导航、上下文、流程、护栏与验证」四件事做成工程化的基础设施,让 agent 在这棵树上的每次会话都站在同一套地基上:

解决什么

落地物

① 代码智能

按 symbol 而非文本导航

树根.clangd→ feature 精简 compdb(C++);Java/Kotlin 明确不配 LSP

② 上下文

每个会话自动知道"在哪、做什么、什么不能碰"

根CLAUDE.md(固定 bootstrap + 硬约束)+ SessionStart hook 按分支注入features/分支/_index.md

③ 流程

动到哪片代码就知道怎么编译/push/验证

树根.claude/skills/若干 path-scoped skill

④ 护栏与验证

防误操作设备、斩断"编过=改对"

permissions.ask硬门禁 +features/分支/verify-*.sh确定性脚本

与官方博客扩展点框架的对应关系(博客要点见第二节):博客把 harness 拆成五个扩展点(CLAUDE.md、hooks、skills、plugins、MCP servers)外加两项能力(LSP、subagents),并强调叠加有顺序——每一层都建立在前一层之上。我们这张"四类能力"表是同一套东西按"解决什么问题"重排的:① 代码智能 = LSP;② 上下文 = CLAUDE.md + hooks(SessionStart 动态注入);③ 流程 = skills(path-scoped);④ 护栏 = hooks(permissions/校验)+ verify 脚本。博客说的叠加顺序在我们的探索历程(第十节)里也如实复现:先用 CLAUDE.md 定行为契约、再用 hook 注入上下文、再用 skill 承载流程、最后补护栏与验证——每一战都踩在上一战的地基上。五个扩展点里我们只暂缓了两个(plugins 与 MCP servers),另有两处对博客通用建议的有意背离,理由见本节末。

四层全部落在树根的游离文件上。这里有一个 repo 工程的关键事实:源码树根不是 git 仓(树根只有 .repo/,没有 .git/),所以放在树根的文件不被任何 gerrit project 跟踪,soong/kati 也不会把纯 markdown 目录当模块编进整机——这是整套方案"不污染上游"的地基。

<AOSP_ROOT>/# repo 工程根(非 git 仓)
├── CLAUDE.md# ② 固定 bootstrap + 硬约束(不随 feature 变)
├── .clangd# ① 指向 feature 精简 compdb(绝对路径)
├── compile_commands.json# ① 根符号链
├── gen-compdb-clangd.sh# ① compdb 两段式刷新脚本
├── .claude/ 
		│ 
		├── settings.json# ② hooks 注册 + ④ permissions 门禁 |
		├── hooks/load-feature.sh# ② 按分支注入 feature 上下文│ 
		├── hooks/check-branch-drift.sh# ② 会话中途切分支告警│ 
		├── rules/compdb-freshness.md# ① compdb 时效提醒(path-scoped rule)│ 
		└── skills/build-*/SKILL.md# ③ path-scoped 编译/验证 skill
		└── features/# ② 独立 git 仓(不在 manifest,repo/gerrit/soong 全不可见)    
		└── dev-sidebar/# 目录名 = repo 分支名 = feature 名        
├── _index.md# 启动注入的索引(几百 token)        
├── frameworks-base.md# 单仓约定(只写 feature 特有内容)        
├── check-branch.sh# 涉及仓分支一致性检查        
└── verify-sidebar.sh# ④ 确定性验证脚本

📦 可跑 Demo:本节这棵目录树的最小可运行复刻见同级 aosp-harness-demo/——四层落地物一应俱全,关键脚本都带 --demo,不需要真实 AOSP 树,./run-demo.sh 即可一键演示四层如何协同。下文五~八节每节开头的「▶ Demo」标注,就指向它对应的文件。

对博客扩展点的三点取舍(都是被 repo/整机树的现实逼出来的,不是遗漏):

  • 背离一:博客建议"在子目录而非仓根初始化"(让 agent scope 到与任务相关的部分,Claude 会自动向上加载沿途每个 CLAUDE.md,根上下文不丢)。我们反其道必须从树根启动——envsetup / lunch / m / adb 全都要求树根 cwd,repo 树也没有子目录级 git 仓的天然边界可供 scope。子目录上下文不会被"向上加载"自动带进来的代价,我们用 SessionStart hook 的索引注入(第六节)+ path-scoped skill(第七节)显式补回。

  • 背离二:博客把 plugins 当作"分发可复用配置、防止好做法停留在部落知识"的手段(打包 skills/hooks/MCP,marketplace 一键装)。我们刻意不把这套 harness 做成 plugin——它的价值恰恰在于是一组树根游离文件(见上),做成 plugin 会脱离"树根非 git 仓"这个不污染上游的地基。跨机分发改由 features/ 独立 git 仓推私有 remote 实现(第九节)。

  • 暂缓一:MCP servers。博客点名一个常见错误——"基础还没跑通就先建 MCP 连接"。我们目前只在"查上游基线 / 跨版本 diff"这类读侧场景把 Lightrion 这类 MCP 当补充通道(第三节);"把结构化检索暴露成 agent 可直接调用的工具"是后续可演进项,而非当前地基。

下文以一个真实工作中的 feature 为例(记作 dev-sidebar):在 AOSP 17 上新增一个系统服务 + 一个常驻边栏应用,涉及 frameworks/baseframeworks/native、新建 app 仓、build/makesystem/sepolicy 五个仓。

五、第①层 代码智能:让导航按 symbol,而不是按文本

Demoaosp-harness-demo 里对应 .clangdgen-compdb-clangd.sh(带 --demo 演示两段式过滤)、.claude/rules/compdb-freshness.md

这一层要做的事,一句话就是给 agent 配好 LSP——让它像 IDE 里的工程师那样按 symbol 导航,而不是对文本做 pattern-match。LSP 被 Anthropic 官方博客点名为多语言大库里"最高杠杆的投资之一":没有它,agent 在命名相似的代码里会落到错误的 symbol(整机树里 onTransact 这类同名符号成海,是最大的导航陷阱)。下面按"AOSP 提供了什么机制 → 我们怎么用 → Claude Code 侧怎么配 → 配好后 agent 怎么查代码 → 好处"讲清 C++ 侧的落地;Java/Kotlin 侧论证后刻意不配(见
5. 2)。

5.1 C++:clangd + 两段式 compdb

① AOSP 为 LSP 准备好的机制。 clangd(C/C++ 的语言服务器)要工作,只需喂它一份准确的 compile_commands.json(compdb,编译命令数据库——记录每个源文件用哪些 flag 编译)。AOSP 内建了生成它的机制:让 soong 在分析阶段顺带吐出全树 compdb(几分钟,不需要真编译);树内还自带一份与构建工具链同版本的 clangd(prebuilts/clang/host/linux-x86/clang-*/bin/clangd)。

SOONG_GEN_COMPDB=1 m nothing# → out/soong/development/ide/compdb/compile_commands.json

② 我们怎么用:两段式精简。 全树 compdb 对 clangd 太重(本树实测 **113,371 条 /

  1. 97GB)。脚本 gen-compdb-clangd.sh 把它做成两段——soong 全树库只作过滤源**,再按当前 feature 涉及的仓过滤出精简库;soong 版每文件已去重(同一 .cpp 的 shared/static/arch 变体只留一条),过滤后即净(用 ninja -t compdb 从构建图导出也行,但不去重,全树会膨胀到几十万条):

③ Claude Code 侧要做的配置。 让 agent 用上这套,只需四件事:

  • 树根放一个 .clangd,把 CompilationDatabase 指向精简库的绝对路径,并开后台索引:

# 树根 .clangdCompileFlags:  CompilationDatabase:<AOSP_ROOT>/out/soong/development/ide/compdb-feature/Index:  Background:Build
  • Claude Code 通过 LSP(plugin 层) 把 clangd 挂进来(博客原话:"LSP is accessed through the plugin layer"),agent 便获得符号级能力;

  • 让它拉起的是树内 prebuilt clangd(与生成 compdb 的工具链同版本,避免 flag 兼容噪音)——用 ~/.local/bin/clangd 的 shim 按 $PWD 分树分发,多树共存时防串台(见第十节踩坑二);

  • 树根启动 Claude Code,让 workspace root 与 .clangd、compdb 的绝对路径对齐。

④ 配好之后 agent 怎么查代码。 不再靠全树 grep 猜同名符号,而是走 LSP 的符号级能力——跳到定义(go-to-definition)、查所有引用(find-all-references)、列工程符号、hover 看类型签名。例如追一个 binder 调用,能从接口方法直接跳到具体实现,而不必在几十个同名 onTransact 里逐个排除;clangd 后台已建好索引(Index.Background: Build),preamble 秒级、AST 完整;只改函数体不用重生成 compdb——clangd 实时读源文件内容。

⑤ 好处。

  • 精准:按 symbol 区分同名符号,不落到错误符号——这是整机树最大的导航陷阱;

  • 省上下文:LSP 直接返回定义/引用的确切位置,把"过滤"放在 agent 读文件之前,不用打开一堆文件人工判断哪个才对(正是博客强调的 context 经济性);

  • :秒级 preamble + 完整 AST + 后台增量索引;

  • 永不过期:读的是 live 源文件,与 agentic search 同源的好处。

⑥ 两条属于"配置"一部分的实测坑。

  • 一律不要给 AOSP 的 clangd 加 --query-driver。社区常见的这个参数在 AOSP 上是反作用:clangd 会"裸跑"驱动探测系统 include(不带 compdb 里的 -nostdlibinc/-target 全套参数),把宿主机 glibc 头以 -isystem 注入,与 bionic 头混装后爆出上百个 __GLIBC_USE is not defined 之类的错误。AOSP 的 compdb 命令本身已带全套 bionic -isystem,什么都不需要补。

  • compdb 有时效:改了 Android.bp/Android.mkrepo sync、新增源文件后要重跑刷新脚本(只改函数体不用)——这条提醒本身也固化成了一条 path-scoped rule(读到构建文件时自动注入),不靠人记。

一个值得知道的上游坑:AOSP 曾有一个"省内存"的临时 commit(b790b9cb8,2025-03,soong 核心作者所写),在每个 cc 模块构建动作结束就把 compiler 字段置 nil;而 compdb 生成器按设计在所有模块之后才遍历——遍历时七万多个 cc 模块的 compiler 全是 nil,收集到 0 条,SOONG_GEN_COMPDB=1 m nothing 静默产出一个空 []。更新的 AOSP 基线已移除该 commit,但若你的基线恰好落在这个窗口,标准命令会"成功地什么都不生成"。检查方法:grep 'c.compiler = nil' build/soong/cc/cc.go;绕行方案是 ninja -t compdb 从 ninja 构建图提取,或给 cc.go 打三行补丁条件保留 compiler。

5.2 Java/Kotlin:论证之后,刻意不配

Java 侧我们完整走了一遍 aidegen + jdtls(Eclipse JDT 语言服务器)路线,最终结论是不配,且放弃本身是重要的探索成果:

  1. 命脉工具已弃用:aidegen 自己打印 "AIDEGen is no longer supported",官方引导到 Android Studio for Platform——那是 GUI IDE,对 agent 毫无帮助。

  2. 架构性不匹配:agent 的 LSP 以进程启动目录为 workspace root,而编译/adb 都要求从树根启动;jdtls 是 workspace 模型,会从根 URI 遍历整棵树找工程——实测空转吃约 2GB 内存、documentSymbol 反复 internal error;同一时刻 clangd 对 .cpp 秒回。

  3. 无法收窄:官方 jdtls 插件是薄封装,不暴露限制扫描范围的配置。

这一战沉淀出后续所有工具选型的判据

能靠"精确文件清单"喂的 LSP(clangd 读 compdb,不遍历树)就配;要靠"遍历大树 workspace"、且工具链已弃用的就不配。

Java/Kotlin 导航改用 Grep 搜符号 + Read,跨 Java↔JNI↔native 追踪时用 JNI 注册名(如 android_view_*)作 Grep 锚点。不硬配不是偷懒,是止损——后文"踩坑精选"里还有它写坏源码树的实证。

六、第②层 上下文:每个会话睁眼就知道"在哪、做什么、什么不能碰"

Demoaosp-harness-demo 里对应 CLAUDE.md.claude/settings.json.claude/hooks/{load-feature,check-branch-drift}.shfeatures/dev-sidebar/{_index,frameworks-base}.mdcheck-branch.sh(demo 用树根 CURRENT_FEATURE 模拟"锚定仓当前分支")。

这一层做的事一句话说清:在 Claude Code 的 SessionStart hook 里执行一个 shell 脚本,把「当前 feature 对应的上下文」注入到会话里。 于是 agent 每次启动"睁眼"就知道自己在做哪个 feature、能动哪些仓、什么不能碰——无需人工每次交代。

承接
4. 1 的前提(一个 feature = 一个 repo 本地分支 = 3–8 个单仓),本层要解决的是:让 agent 一启动就知道当前 feature 是什么、涉及哪些仓、每个仓的约定在哪。四条需求:按 feature 组织、随分支自动切换、不污染 gerrit、大单仓省上下文。下文先给出方案的具体构成与端到端流程
6. 1),再解释为什么非得这么设计——那个 git 语义层面的死结与破局(
6. 2),最后讲几个关键设计决策(
6. 3)。

6.1 方案概览:由哪些文件构成、启动时怎么跑

先看具体由哪些东西构成(都放在树根,不进任何 gerrit 仓):

  • features/ —— 一个放在树根的独立 git 仓:每个 feature 一个子目录,目录名就等于该 feature 的 repo 分支名。以 dev-sidebar 为例:

features/ └── dev-sidebar/# 目录名 = 分支名 = feature 名    ├── _index.md# 启动时注入的"索引"(几百 token)    ├── frameworks-base.md# 单仓约定(只在 agent 动到该仓时按需 Read)    ├── check-branch.sh# 涉及仓分支一致性检查    └── verify-sidebar.sh# 确定性验证脚本
  • _index.md —— 会话启动时喂给 agent 的"第一屏":内容极简——feature 目标一句话、涉及哪几个仓、每个仓的约定文件在哪、验证脚本入口。例如:

# feature: dev-sidebar目标:新增系统服务 SidebarService + 常驻边栏应用 SidebarApp 涉及仓:frameworks/base、frameworks/native、packages/apps/SidebarApp、build/make、system/sepolicy 单仓约定(动到再 Read):frameworks-base.md 验证入口:./features/dev-sidebar/verify-sidebar.sh
  • .claude/hooks/load-feature.sh —— 注册在 SessionStart 上的脚本:负责"读当前分支 → 选中对应 feature 目录 → 把它的 _index.md 打印到 stdout"。Claude Code 会把 SessionStart hook 的 stdout 自动当作会话上下文注入

启动时就跑这么一条链(工程师在树根敲 claude 那一刻):

一步步拆开:

  1. Claude Code 启动,先加载树根 CLAUDE.md(固定 bootstrap + 硬约束,见 6.3)。

  2. 触发 SessionStart hook load-feature.sh:它从 stdin 的 JSON 里拿到 cwd,再到锚定仓读当前 git 分支名(比如读出 dev-sidebar)。

  3. 脚本把 features/dev-sidebar/_index.md 打印到 stdout,这几百 token 被注入为会话上下文。

  4. 同一时刻,permissions.ask 护栏生效、clangd + feature 精简 compdb 就绪。

  5. 于是 agent "睁眼"第一屏就知道:在做 dev-sidebar、只该动那几个仓、验证跑哪个脚本——全程无需人工交代

hook 脚本的核心不过十几行(拿 cwd → 读分支 → cat 索引)。几个容易踩的细节都已加固:

  • SessionStart 不写 matcher = 四种触发源全覆盖(startup / resume / clear / compact)。初版只覆盖 startup+compact,/clear 之后 feature 上下文会静默消失。

  • 每条 prompt 跑一次分支漂移检测(UserPromptSubmit hook check-branch-drift.sh):比对当前分支与注入时的快照,会话中途 repo checkout 切了分支会收到一次告警,提示先读新分支索引再动手;没切则零输出、零打扰。

  • 锚定仓做成链:feature 不一定碰 frameworks/base,按 base → native → 下一候选的顺序找到第一个能读出分支的仓。

博客视角的一个补白:官方博客认为 hooks 最有价值的用法并不是"拦住 agent 做错事",而是让配置自我改进——用 stop hook 在会话结束、上下文还热的时候反思本次教训并提议更新 CLAUDE.md;用 start hook 动态加载团队/模块上下文,让每个人无需手动配置就拿到对的地基。我们当前的两个 hook(SessionStart 索引注入、UserPromptSubmit 分支漂移检测)已经落地了"动态加载"这一半,但还都停在"注入 + 防错"这一侧;"会话末反思 hook"已列入演进方向(第十一节),是把这套 harness 从"静态地基"推向"自我改进闭环"的下一步。

6.2 命门矛盾与破局:为什么 features/ 是"树根上的独立 git 仓"

上面这套结构里最不显然的一步,是"为什么 features/ 要单独做成一个放在树根的 git 仓,而不是就放进 frameworks/base 里、让它跟着分支走"。因为这里藏着一个 git 语义层面的死结:

即:「随分支变」与「不进 gerrit」在同一个 project 仓内不可兼得,必须把"随分支"这件事从 git 跟踪机制里拿出来,改由 hook 在会话启动时按当前分支动态注入(这正是 6. 1 那条链)。方案推演走了五版:

版本

方案

结局

v1

feature 目录与源码树同级,在 feature 目录启动

✗ CLAUDE.md 只沿 cwd 树向上加载,同级源码树的上下文完全加载不到

v2

features/放树内、独立 git 仓

✓ 部分成立,但深层单仓的约定仍进不来

v3

根 CLAUDE.md 随分支切换

根不是 git 仓、"分支"是 per-project 的——要么建瘦仓手动同步(两套分支),要么 hook 动态注入

v4

SessionStart hook 按分支注入

✓ 单一分支源、自动跟随、零手动同步

v5

大单仓约定也进features/,索引粒度注入

✓ 启动只注入几百 token 的索引,详情按需 Read

最终形态 = v4 + v5。

6.3 三个设计决策

为什么注入用"索引粒度"? 先说清什么叫索引粒度:像一本书的目录——只列"讲了什么、在第几页"这类指针,而不是把每一章正文都抄给你。落到本方案,就是启动时只注入 _index.md 里那几行指针(feature 目标 + 涉及仓清单 + 各约定文件的路径 + 验证入口),而把每个仓的完整约定全文灌进会话。为什么这么设计:frameworks/base 这类单仓的约定若全量注入会每个会话常驻、挤占上下文;改成只注入索引、agent 真动到某仓时再顺着路径 Read 对应的 frameworks-base.md——用"一个 feature 涉及的单仓有限、且不会全被碰到"这个业务事实,换来上下文经济性。

为什么硬约束放根 CLAUDE.md 而不是全靠 hook? 因为子代理不吃 hook 注入的内容,但会加载 CLAUDE.md。"改 public API 必须跑 m update-api,否则 checkapi 挂构建"这种不知道会出事故的规则,必须让派出去的子代理也看见。同时遵守一条纪律:CLAUDE.md 只写 agent 默认会犯的错,不写文档。本树的硬约束一共六条:

硬约束

防的是什么

不向任何 gerrit project 提交 harness/上下文文件

知识污染上游

禁配 Java LSP / 禁生成 Eclipse 工程文件

吃内存 + 写坏树(见踩坑精选)

改 public/System API 后必须m update-api

checkapi 挂构建

新增系统服务必须同步system/sepolicy

服务起不来(avc denied)

push framework.jar/services.jar 后注意 ART 缓存

dexpreopt/boot image 校验不一致拖慢甚至起不来

不手改out/下任何生成物

增量构建被破坏

为什么编译约定也要写进 CLAUDE.md? 有两个 agent 默认必错的点:envsetup 必须用 bash(工具默认 shell 可能是 zsh),且 source 后不能接 pipe(函数会进子 shell);一切编译必须后台跑 + 轮询日志(agent 的前台命令有超时上限,而单编模块十几分钟起步)。

bash -c'source build/envsetup.sh >/dev/null 2>&1 \   && lunch aosp_cf_x86_64_phone-trunk_staging-userdebug >/dev/null 2>&1 \   && m services' > /tmp/build.log 2>&1 &# 后台 + 日志轮询,                                             # 看到 build completed successfully 才算完

(顺带一个 Android 17 的新变化:lunch 目标是三段式 product-release-variant,release 段如 trunk_staging,可从 out/soong.logTARGET_RELEASE= 反查。)

七、第③层 流程:动到哪片代码,就知道怎么编译验证

Demoaosp-harness-demo 里对应 .claude/skills/build-services-jar/SKILL.mdpaths: frameworks/base/services/**)与 build-sepolicy/SKILL.mdpaths: system/sepolicy/**),配合 features/dev-sidebar/frameworks-base.md 里"一句话指回 skill"的单一事实源写法。

这一层做什么。 一句话:让 agent 一动某片代码,就自动知道这片代码"怎么编译、产物在哪、要 push 哪些文件、怎么验证"——不用人每次交代,平时也不占上下文。这类"改了这片代码该怎么走完编译到验证"的知识是过程性知识(区别于第②层"在哪、做什么"那种背景知识)。

怎么做到的。 难点在于:把它全塞进根 CLAUDE.md,会每个会话常驻、白白挤占上下文。解法是 skill 按需加载 + paths glob 按路径激活——把每一类代码的编译验证流程各写成一个 skill,并在 frontmatter 里用 paths 标注它作用的代码路径;只有当 agent 真的 Read 到匹配路径的文件时,对应 skill 才被拉进上下文,其余时间零占用。

# .claude/skills/build-services-jar/SKILL.md(frontmatter 示意)---name:build-services-jardescription:编译/部署services.jar——改frameworks/base/services下代码时用paths:  -"frameworks/base/services/**"---

这里的 glob 就是文件路径的通配符匹配(和 shell 里 ls *.c* 同源):* 匹配单层路径内的任意字符,** 跨目录递归匹配任意层级。所以 frameworks/base/services/** 的意思是"frameworks/base/services/ 目录下任意深度的任意文件"。skill frontmatter 里的 paths 字段就是给这个 skill 挂一组这样的路径模式;agent 每 Read 一个文件,Claude Code 就拿该文件路径逐条比对这些模式,一旦命中就把对应 skill 载入上下文,不命中就当它不存在。这就是它按"你正在动的代码落在哪个目录"来决定加载哪个 skill——比 hyperdroid 那种按关键词触发的粒度更准(路径是确定的,关键词会误触),也正是官方博客说的 skill "可 path-scoped、只在相关目录激活"。

repo 工程有个特殊决策点:skill 放哪。嵌套进 frameworks/base/.claude/skills/ 会被该 gerrit project 跟踪——又是污染问题。结论与 feature 工作流同构:集中放树根 .claude/skills/(树根不属于任何 project),用 paths glob 做作用域。前提是从树根启动 agent,而编译/adb 本来就要求树根 cwd,天然满足。

另一条重要纪律是单一事实源:初版曾在 skill 和 feature 单仓约定里把编译/push 流程写了两遍,评审时判定必然漂移。最终分工——skill 承载不随 feature 变的通用流程(编译命令、产物、push 清单、已知坑);features/ 单仓约定只写 feature 特有内容,流程一句话指回 skill。本树目前有五个这样的 skill:framework.jar、services.jar、inputflinger、边栏 app、sepolicy,每个都写明"后台编译 + 日志轮询、产物与 push 清单、快环稳环、编过≠改对指向 verify 脚本"。

八、第④层 护栏与验证:斩断"编过 = 改对"

Demoaosp-harness-demo 里对应 .claude/settings.jsonpermissions.ask 门禁与 features/dev-sidebar/verify-sidebar.sh --demo(四步确定性断言、FAIL>0 非零退出)。

这一层做什么。 一句话:在 agent 干活的两个危险时刻各设一道拦截——动设备/动源码树前别让它误操作,收工宣布完成前别让它把"编译通过"当成"改动正确"。前三层帮 agent 把事做顺,这一层专门防它把事做砸或自我误判。

怎么做到的。 两道护栏,都不依赖模型的记性:一是 permissions 硬门禁permissions.ask),把危险命令变成系统级弹窗,模型上下文再长也绕不过(见
8. 1);二是 确定性验证脚本(verify 脚本),把"改对了没有"编码成只输出 PASS/FAIL 的机器判定,堵死 agent 拿模糊输出自我安慰的路(见
8. 2)。

8.1 permissions 硬门禁

"动设备先确认"写在文档里是软约束,模型上下文一长就可能被忽略。permissions.ask 把危险操作变成系统级弹窗,不依赖模型记性:

门禁命令

为什么要拦

adb push/adb reboot/adb remount…

直接改写运行中设备

cvd start/cvd stop/cvd reset、launch_cvd/stop_cvd

虚拟机生命周期操作

repo sync

动整棵源码树

m clean/m installclean

清掉数小时的构建产物

8.2 确定性验证脚本

AOSP 没有"改完跑一下"的现成测试套,verify 脚本就是这个 feature 的测试。utzcoz 的原话:"没有这个,会话结尾 agent 会因为 build 成功就确信改动生效了。" 验证环编码成脚本、只编码一次,输出只有确定性的 PASS/FAIL——中间态会诱导 agent 把模糊输出读成成功。

dev-sidebar 的 verify 脚本为例,它做四步确定性断言:

  1. sys.boot_completed=1(设备真的起来了);

  2. system_server 存活;

  3. crash buffer 扫描(无新增崩溃);

  4. 新增系统服务与边栏应用存在性(service list / pm list packages 命中)。

feature 早期允许部分断言标 SKIP,随开发推进逐项转为硬断言——这本身就是一种可执行的进度表。Cuttlefish 作为目标设备在这里显出独特价值:虚拟机的"稳环"(整机镜像 + cvd stop/start)是真机没有的兜底手段,push 出诡异状态时可以低成本回到干净基线。

九、串起来:一个会话的完整生命周期

四层不是四个孤立的配置,而是按时间协同的一条流水线:

触发时机

管什么

① 代码智能

启动 / Read C++ 文件

按 symbol 而非文本导航

② 上下文

启动 + 每条 prompt

每会话自动知道"在哪、做什么、什么不能碰"

③ 流程

Read 到匹配路径

动到哪片代码就知道怎么编/push/验证

④ 护栏与验证

危险命令 / 收工前

防误操作设备、斩断"编过=改对"

换 feature 的成本约等于零repo start dev-next --all + 建 features/dev-next/ 目录 + 重启会话,hook 自动跟随新分支;skills 完全不用动(paths 是按仓的,不随 feature 变)。features/ 是独立 git 仓,feature 上下文、上游调研、verify 脚本随开发演进提交,还可以推私有 remote 跨机同步。

一句话总结这套工作流:

启动时 hook 告诉 agent"在哪、做什么",干活时路径激活 skill 告诉它"怎么编怎么验",危险动作被 permissions 拦一道,收工前 verify 脚本判定"真的改对了"——四层各管一段,缺一层闭环就断。

十、这条路是怎么探索出来的

前面九节呈现的是结果——四层各就各位、彼此咬合,读起来像是一开始就照着蓝图搭的。真实过程要曲折得多:这套 harness 不是自顶向下设计出来的架构,而是被一个个具体的失败逼出来的。每一层的定型几乎都走同一条轨迹——先撞上一个具体故障 → 挖到根因 → 才沉淀出对应的设计决策,顺序恰恰和成品的呈现顺序相反。

回头看,整个探索大致是四场"战役"加一轮自审:前两场在代码智能层,分别啃下 C++(compdb 空数组的上游坑)和 Java(论证后刻意不配 LSP)两块硬骨头;第三场在上下文层,解开"随分支 ⇔ 被跟踪 ⇔ 污染 gerrit"这个 git 语义死结;第四场把流程知识从"全塞 CLAUDE.md"改造成 path-scoped skill。四层搭齐后我们没有就此收工,而是专门做了一轮自审——"配上了"不等于"能用",回头把六个会在真实会话里咬人的缺陷逐个补掉。这段历程里最有价值的往往不是最终方案,而是为什么放弃了另一些看似更直接的方案(比如 Java LSP、把 harness 做成 plugin)。

下面这张时间线先给全局,随后的「踩坑精选」再挑几个最有代表性、且都有实证的坑展开细节。

踩坑精选(每个都有实证)

坑一:Eclipse/jdtls 残留会写坏源码树。 一次 m 在 aconfig 阶段失败——flag already declared,而 frameworks/base 零本地提交。根因:此前 Java LSP 实验中 Eclipse 把资源文件连同 .class 拷进了 services/*/bin/,其中的 .aconfig 副本被 soong 的 **/*.aconfig glob 收进同一 aconfig_declarations,导致重复声明。更隐蔽的是 frameworks/base 自带 .gitignore 忽略 .project/.classpathgit status 完全看不见这批残留;甄别要用 git ls-files --error-unmatch 逐个判断是否 tracked(AOSP 一些老仓的 Eclipse 文件是 checked-in 的,不能一刀切删)。这让"禁配 Java LSP"从性能取舍升级成了硬约束——它不只吃内存,还会污染构建。

坑二:机器级 clangd 包装脚本劫持(多树共存必踩)。 clangd --check 日志里出现了另一棵树的 compdb 路径 + "Compile command inferred" + 错误的目标架构——树根 .clangd 明明配对了。根因:~/.local/bin/ 里给旧树写的 clangd 包装脚本硬编码了 --compile-commands-dir,而 CLI flag 优先级压过一切 .clangd 配置。修复:包装脚本按 $PWD 分树分发,每棵树用各自的 in-tree clangd + 各自 compdb 绝对路径;树根 .clangd 一律写绝对路径双保险。速查信号:--check 日志里 ①DB 路径不是本树 ②"inferred"字样 ③目标架构/API 级不对。

坑三:--query-driver 在 AOSP 上是反作用。(详见第五节——宿主 glibc 头污染 bionic,删掉即清零。)

元教训:harness 自身也要用工程标准对待——设计完要评审、加固、实测,而不是"配上了"就算完。三大件搭好后我们专门做了一轮"这套方案还有哪些缺陷"的自审,修掉的六个缺陷里最典型的三个:长编译撞工具超时上限(改为强制后台+轮询)、/clear 后上下文静默丢失(matcher 全覆盖 + 漂移检测)、"动设备先确认"是软约束(升级为 permissions 硬门禁)。

十一、边界与下一步

已知边界(都是明确认下的取舍,不是遗漏):

边界

说明

Java/Kotlin 无 LSP

跨 Java↔JNI↔native 追踪靠 Grep 搜符号 + JNI 注册名锚点

单树单分支

repo 树没有 git worktree 等价物,并行两个 feature 需要两棵树

hook 注入不进子代理

已用"硬约束下沉 CLAUDE.md"缓解,派发子代理时仍需转述 feature 上下文

compdb 有时效

改构建文件/repo sync 后要重跑刷新脚本(已有 rule 自动提醒)

何时重审:每 3–6 个月、或新一代模型发布后感觉规则见顶时,删过期/矛盾的规则——"为迁就某代模型缺陷写的规则,下一代模型上来就变成束缚"。另加一条我们自己的信号:依赖的工具链出现弃用声明时立即重审(aidegen 之鉴)。

可演进方向(按杠杆排序):

  1. 五段式 handoff 交接文档(What Was Done / How Verified / Files Modified / Blocker / Next)——跨会话 bug hunt 的最高杠杆,新会话读最新 handoff 即可冷启动续上;

  2. 会话末反思 hook——提议更新 CLAUDE.md,形成持续改进闭环;

  3. 冷启动 review 子代理对抗审查——作者 agent 偏 "ship it",无历史包袱的 reviewer 偏 "explain this"。

  4. 只读子代理测绘、主代理编辑——博客点名的 subagent 核心模式:整机树上"探索"极烧上下文,可先派一个只读 subagent 去测绘某个子系统(跟调用链、读 dumpsys、定位改动点),把发现写进一个文件,主代理再带着全貌下手编辑,避免探索的中间产物挤占主上下文。要与本树"hook 注入不进子代理"的边界(见上表)配套——派发时必须把 feature 上下文转述进子代理的任务描述。

结语

这套 harness 工程没有任何一处依赖"更聪明的模型":它做的全部事情,是把一位 AOSP 老工程师带新人时会做的四件事——给他一张地图(上下文)、教他怎么编译(流程)、告诉他哪些红线不能碰(护栏)、要求他改完必须验证(闭环)——翻译成了 agent 基础设施。模型每半年换代一次,而这棵树上的地基,每个会话都在复用。

参考资料