新增 docs/agentic-design/ 教育文档,全中文+英文技术术语,覆盖: - README.md: 总索引,整体架构图,阅读指南 - 00-codebase-tour.md: 代码库全景,递归覆盖所有重要目录 - 01-agent-loop.md: Query loop 状态机,token budget,compaction - 02-tool-system.md: Tool interface,权限检查,并发执行,sibling abort - 03-multi-agent-coordination.md: Agent 类型,coordinator 4 阶段,XML 通信 - 04-permission-system.md: Permission mode,rule system,YOLO 分类器,denial tracking - 05-context-and-memory.md: System prompt 分层,compaction,auto-dream 后台巩固 - 06-feature-gating.md: Compile-time feature,runtime flags,Bun dead-code elimination 总计 ~2.5 小时阅读,每篇含 Design Decision 专栏和常见误解纠正 https://claude.ai/code/session_017Vdqo9B8eTXiEDcqnv9DxM
9.8 KiB
Feature Gating:内部功能和公开功能的隔离
为什么需要 Feature Gating?
Claude Code 是一个复杂系统,有很多功能:
- 一些已发布、稳定、所有用户可用
- 一些还在测试、只给内部用户("ant" 用户)
- 一些只给特定高级用户(Max/Pro)
- 一些通过实验 A/B 测试
如果不做隔离,代码会变得一团糟:
// 不好的做法
if (user.isInternal) {
// KAIROS 模式逻辑
} else if (user.isMax) {
// 高级功能
} else if (featureFlags.has('experimental_search')) {
// A/B 测试
} else {
// 默认行为
}
// ... 这样嵌套太深,难以维护
Claude Code 的解决方案:编译期和运行期双层 gating。
编译期 Gating:Bun 的 feature() API
Bun(JavaScript 运行时)提供了一个编译期特性检测 API:
if (feature("KAIROS")) {
// 这段代码只在 KAIROS 构建中出现
// 其他构建完全不包含这代码
} else {
// 默认行为
}
工作原理
源代码:
if (feature("KAIROS")) {
launchKAIROS()
} else {
launchDefault()
}
↓ Bun 编译(--feature KAIROS)
if (true) {
launchKAIROS() // 只保留这段
} else {
// 被 dead code elimination 删除
}
↓ 输出的二进制
launchKAIROS() // 这是最终的可执行代码
优势
- 二进制体积小:内部功能完全不包含在公开二进制中
- 安全:用户不能"解锁"内部功能(不存在于二进制)
- 性能:不需要运行时检查这些条件
相关代码散布在整个 codebase 中,比如 entrypoints/cli.tsx, bootstrap/state.ts。
Compile-Time Feature 列表
Claude Code 在构建时支持这些 feature flag:
| Flag | 启用时 | 作用 |
|---|---|---|
KAIROS |
启动 KAIROS mode(always-on Claude) | 全天候 assistant |
PROACTIVE |
同上(别名) | 同上 |
BRIDGE_MODE |
启用 claude.ai 远程控制 | 在 Web UI 中控制 CLI |
VOICE_MODE |
启用语音输入 | 用话筒说指令 |
DAEMON |
启用 daemon 模式 | 后台运行的 Claude |
COORDINATOR_MODE |
启用 coordinator 多 agent | 复杂任务分解 |
BUDDY |
启用 Tamagotchi 伴侣系统 | 养一个宠物 AI |
WORKFLOW_SCRIPTS |
启用工作流脚本 | 自动化重复任务 |
NATIVE_CLIENT_ATTESTATION |
启用本地证明 | 设备信任 |
TRANSCRIPT_CLASSIFIER |
启用 AFK 自动模式 | 离开电脑时自动工作 |
HISTORY_SNIP |
启用历史压缩优化 | Context 更高效 |
EXPERIMENTAL_SKILL_SEARCH |
启用技能搜索(实验) | 发现新 skill 命令 |
ABLATION_BASELINE |
科学研究 baseline | 论文用 |
默认只编译:KAIROS=0, BRIDGE_MODE=0, ...(全是 0,最小二进制)
内部构建时:编译脚本设置 --feature KAIROS=1 --feature BRIDGE_MODE=1 ...
相关文件:构建脚本(如 scripts/build.sh,如果存在),或 bunfig.toml
运行期 Gating:GrowthBook Feature Flags
编译期 feature 决定"可能性",运行期 flags 决定"激活"。
系统启动时,从 GrowthBook(特性管理平台)获取动态配置:
// 启动时
const flags = await growthbook.getFeatures({
userId: currentUser.id,
organization: currentUser.org
})
// 现在可以做运行期决策
if (flags.has('tengu_scratch')) {
// 启用共享 scratchpad(coordinator 模式)
enableScratchpad()
}
if (flags.has('tengu_amber_flint')) {
// 启用 in-process teammate swarms
enableTeammates()
}
if (flags.has('tengu_penguins_off')) {
// 禁用某个实验
disableExperiment()
}
Flag 命名约定
Runtime flags 以 tengu_ 开头("tengu" 是 Claude Code 的内部代号):
tengu_scratch # 共享 scratchpad(为 coordinator)
tengu_amber_flint # In-process teammate
tengu_onyx_plover # 某个实验
tengu_penguins_off # 禁用什么功能
...
这样容易区分:
KAIROS(编译期)vstengu_kairos_v2(运行期)
相关文件:bootstrap/state.ts(初始化),services/analytics/(GrowthBook 集成)
两层系统的协作
场景 1:Compile-Time Only(最常见)
新功能 KAIROS(always-on Claude)
↓ 编译
├─ 构建 ant(内部):--feature KAIROS=1
│ → KAIROS 代码被编译进去
│
└─ 构建 public(公开):--feature KAIROS=0
→ KAIROS 代码被 dead-code-elimination 删除
→ 二进制不含任何痕迹
场景 2:Compile-Time + Runtime(用于 A/B 测试)
新的 memory 系统(已编译进去)
↓ 运行时
├─ 用户在 tengu_new_memory=true 组(50%)
│ → 使用新系统
│
└─ 用户在 tengu_new_memory=false 组(50%)
→ 使用旧系统
→ 收集对比数据
场景 3:运行期特定用户
高级功能(编译期编译进去)
↓ 运行时
├─ Max 订阅用户 → 启用计算机使用
├─ Pro 订阅用户 → 启用高级 memory
└─ 免费用户 → 基础功能
相关文件:services/policyLimits/(根据订阅级别应用 policy)
对 System Prompt 的影响
Feature gating 会改变 system prompt 的内容:
const systemPrompt = [
BASE_PROMPT,
// 只有在编译时启用 BRIDGE_MODE 时才包含
feature("BRIDGE_MODE") ? BRIDGE_MODE_INSTRUCTIONS : "",
// 运行时检查:如果用户有 tengu_coordinator flag
flags.get("tengu_coordinator") ? COORDINATOR_INSTRUCTIONS : "",
// 运行时 API beta 特性
BETAS_NEGOTIATED_WITH_API, // 如 "interleaved_thinking"
]
这也影响 prompt cache:
缓存键 = MD5(system_prompt_prefix)
如果 flag 变化,cache key 变化,旧 cache 失效
所以,高频变化的 runtime flag 应该放在 cache boundary 之后(动态部分),而不是之前(静态部分)。
Betas 协商
Claude API 定期发布新特性(beta),Claude Code 需要:
- 请求启用这些 beta
- 如果启用,在 system prompt 中告诉 Claude(这个 API 支持什么新功能)
例子:
// constants/betas.ts
const BETAS_REQUESTED = [
"interleaved-thinking", // 支持在回复中穿插思考
"structured-outputs", // 支持返回结构化 JSON
"context-1m", // 100 万 token context
"web-search", // 网络搜索能力
]
// API 返回确认
const BETAS_ENABLED = [
"interleaved-thinking", // ✓ 已启用
// "structured-outputs", // ✗ 暂无权限
"context-1m", // ✓ 已启用
"web-search", // ✓ 已启用
]
// System prompt 中加入这些信息
You have access to the following beta features:
- Interleaved thinking: You can output <thinking> blocks
- Context 1M: You can use up to 1,000,000 tokens
- Web search: You can call the WebSearchTool
相关文件:constants/betas.ts
Design Decision 专栏
为什么同时用编译期和运行期?
只用编译期:
缺点:无法做快速的 A/B 测试(需要重新编译)
无法根据用户身份动态启用功能
只用运行期:
缺点:内部功能可能被反编译/逆向工程
内部代码暴露在公开二进制中
启动时需要网络请求拿 flags(慢)
两者结合:
编译期决定"可能性"(物理隔离内部代码)
运行期决定"激活"(灵活的 A/B 测试)
最安全、最灵活
为什么 Bun 的 feature() 而不是其他工具?
Bun 的 feature() 是编译期指令,会进行 dead code elimination:
其他工具(如 rollup 的条件编译):
需要额外的 webpack 插件
编译配置复杂
Bun:
原生支持
编译快(Bun 本身就快)
输出体积最小
常见误解
误解 1:"Runtime flag 可以改变编译期行为?"
实际:不能。Runtime flag 只能在编译期已包含的代码中做选择。如果代码在编译时被 dead-code-elimination 删除了,运行时再想启用也没办法。
// 如果编译时 --feature KAIROS=0,这段代码被删除
if (feature("KAIROS")) {
launchKAIROS()
}
// 运行时即使 growthbook.flags.get("enable_kairos") == true,也没用
// 代码不存在于二进制中
误解 2:"Feature flag 对性能有开销?"
实际:编译期 feature flag 没有开销(代码级别选择)。运行期 flag 有极小开销(map lookup),可忽略不计。
误解 3:"所有用户都能看到内部代码?"
实际:不能。公开二进制中编译时被删除的代码,用户看不到。只有 ant(内部)用户的二进制才包含这些代码。
关键要点
- 编译期 feature:Bun 的
feature()API,dead code elimination,对二进制体积和安全性的保障 - 运行期 flags:GrowthBook,动态启用特性,支持 A/B 测试
- 两层协作:编译决定可能性,运行时决定激活
- System prompt 影响:不同 flag 组合 → 不同 system prompt → 不同 cache key
- API Beta:协商启用新的 Claude API 特性,在 system prompt 中告诉 Claude
深入阅读
bootstrap/state.ts:运行时 flag 初始化(56 KB)constants/betas.ts:Beta feature 列表constants/system.ts和constants/systemPromptSections.ts:System prompt 组装entrypoints/cli.tsx:编译期条件编译示例
后记
这 6 篇文档覆盖了 Claude Code 最核心的 agentic 设计决策。还有很多其他话题(Bridge 模式、Voice 输入、Plugin 系统等),但这些是最重要的基础。
如果你想进一步了解,建议:
- 读完这 6 篇
- 打开
query.ts和Tool.ts,对照源代码 - 运行 Claude Code,用
--verboseflag 看内部日志 - 探索
tools/AgentTool/的源代码
祝学习愉快!