# Proposal C7: prompt-builder 按 role 条件拼装 + token 预算守门 > 来源:问题.md C7(行 202-215)、附录 C(行 395)、附录 H(行 574-577) > 状态:proposed(待 B5 + C4 完成后实施) > 提出:Roundtable 2026-04-20(Linus + Karpathy + Steve Jobs) > Owner: TBD > 估计工时:2 周 > Blocking dependencies:B5(eval harness)、C4(initial-user-turn 精简) ## 问题陈述 `src/main/prompt-builder/system-prompt.ts` 共 262 行,无条件拼装: - 15 个 SOP(`prompt-builder/sops/*.md`) - 11 个 API 文档(`prompt-builder/api-docs/*.md`) - 5 个 CEO 模式片段(`prompt-builder/ceo-modes/*.md`) - 7 个部门指南(`prompt-builder/department-guidelines/*.md`) - Operating instructions (CEO / non-CEO 分支) - Capability registry - Skill injection 问题细节: 1. **无条件拼接**:一个 customer_support IC 也会收到全 15 个 SOP(包括 engineering / legal / finance 类) 2. **版本无治理**:每个 SOP/api-doc 的 git blame 是唯一变更记录,没有 `version` frontmatter 3. **无 token 预算守门**:prompt 本身可能吃掉 20-30k input tokens;compactor 还没开始就先把 budget 吃大半 4. **A/B 实验无法做**:改一个 SOP 的某句话无法 attribute 到具体行为变化 5. **Cache invalidation 代价高**:Anthropic prompt caching 对稳定前缀敏感——262 行拼装每改一次就 cache miss 一次 ## 根因分析 Karpathy 视角:**prompt 越长越脆**。 长 prompt 的三个问题: 1. **Relevance dilution**:LLM 注意力机制对 prompt 中后段的 attention 权重递减(lost-in-the-middle 现象)。全塞 33 个 markdown 文件反而让关键信号被淹没。 2. **Cross-contamination**:legal SOP 的措辞会影响 engineering agent 的输出风格(比如加 disclaimers);这不是我们想要的。 3. **Evaluation intractability**:33 个片段两两组合有 1000+ 种可能影响;改一个不知道动了哪。 Linus 视角补充:**当前架构假设"一个 prompt 适配所有 agent"**。这和 AppDatabase pass-through 是同构的问题——用一个巨大的 god object 替代多个专用对象。正确做法是按 role 产出不同 prompt。 另一个层面的问题:**没有 budget**。prompt-builder 的输出长度是"算出来多少就多少",caller 无法预判。20k / 30k / 50k token 的 prompt 走 Anthropic 都能跑,但 cost 和 latency 完全不同,而且 compactor(B3 未修时)行为也会随 prompt 长度变化。 ## 设计方案 ### 核心原则 1. **按 role 条件拼装**:每个 agent 只看到自己需要的 2. **显式 budget**:prompt > threshold 直接 fail fast 3. **片段版本化**:每个 markdown 带 frontmatter `version: X` 4. **Cache-friendly layering**:稳定前缀(不变的 operating instructions)+ 动态后缀(per-turn context)显式分离 ### 片段分类表 | 片段类型 | 何时包含 | 共享度 | |---------|---------|--------| | Core operating instructions | 总是 | 所有 agent | | CEO operating instructions | `agent.role === "ceo"` | CEO only | | CEO mode(team-building/awaiting-*/operating) | CEO + 当前 phase | CEO only | | Department guidelines | 按 `agent.department` | 同部门 | | SOPs | 按"与 agent 能力相关" | 有限子集 | | API docs | 按 connector 可用性 | 动态 | | Capability registry | 总是 | 所有 agent | | Skill injection(pi 的 progressive disclosure) | 总是 | 所有 agent | ### 新 API 结构 ```ts // src/main/prompt-builder/index.ts(重写) export function buildSystemPrompt(ctx: PromptContext): string { const sections = resolveSections(ctx); // 新增 const parts = sections.map((s) => renderSection(s, ctx)); const prompt = parts.join("\n\n"); enforceBudget(prompt, ctx); // 新增 return prompt; } interface SectionRef { id: string; // "sop.task-delegation" | "department.engineering" | ... version: string; // 从 markdown frontmatter required: boolean; // 不满足 required 则 fail } function resolveSections(ctx: PromptContext): SectionRef[] { const sections: SectionRef[] = [ { id: "core.operating-instructions", version: "1.0", required: true }, { id: "core.capability-registry", version: "1.0", required: true }, ]; if (ctx.agent.role === "ceo") { sections.push({ id: "ceo.operating-instructions", version: "1.0", required: true }); sections.push({ id: `ceo.mode.${ctx.ceoMode}`, version: "1.0", required: true }); } if (ctx.agent.department) { sections.push({ id: `department.${ctx.agent.department}`, version: "1.0", required: true }); } for (const sop of resolveRelevantSops(ctx)) { sections.push({ id: `sop.${sop.slug}`, version: sop.version, required: false }); } // API docs 仅当对应 connector ready 时 for (const connector of ctx.allConnectors ?? []) { if (connector.status === "ready") { sections.push({ id: `api-docs.${connector.id}`, version: "1.0", required: false }); } } return sections; } ``` ### SOP 相关性规则 替代"全塞"的朴素规则: 1. **SOP 有 `tags` frontmatter**:`tags: [task, delegation, messaging]` 2. **Agent role / department 决定关注 tag 集合**: - CEO → 全 tag - Engineering dept → `[task, code, review]` - Customer support → `[task, messaging, escalation]` 3. **Loader 根据 tag 交集筛选** Markdown 示例: ```md --- id: sop.task-delegation version: 1.2 tags: [task, delegation] --- # Task delegation SOP ... ``` ### Token 预算守门 ```ts // src/main/prompt-builder/budget.ts const BUDGET_THRESHOLDS = { warn: 8_000, // 超过 warn 打 telemetry error: 16_000, // 超过 error 直接抛 }; function enforceBudget(prompt: string, ctx: PromptContext): void { const tokens = estimateTokensPrecise(prompt, ctx.locale); // 用 tiktoken 精确计算(C3 的 scope) recordMetric("prompt.tokens", tokens, { role: ctx.agent.role, companyId: ctx.companyId }); if (tokens > BUDGET_THRESHOLDS.error) { throw new PromptBudgetError(`System prompt ${tokens} tokens exceeds ${BUDGET_THRESHOLDS.error}`); } if (tokens > BUDGET_THRESHOLDS.warn) { logger.warn("prompt.budget.warn", { tokens, threshold: BUDGET_THRESHOLDS.warn }); } } ``` **Let it crash 原则**:超预算直接 throw,不做"自动裁剪"——因为自动裁剪会悄悄改行为。 ### Cache-friendly layering 当前 `buildTaskPrompt`(`prompt-builder/index.ts:48-`)已经把 system prompt(cacheable)和 per-turn context(non-cacheable)分开,这是对的。C7 保留这个分层,进一步: - system prompt 内部按 stability 排序:`[core → role → department → SOPs → skills]`(稳定度递减) - Anthropic cache_control breakpoint 标在 `core + role + department` 之后 ### A/B harness(可选阶段) SOP 片段支持 variant: ```md --- id: sop.task-delegation version: 1.2 variants: - id: default weight: 50 - id: concise weight: 50 path: ./task-delegation.concise.md --- ``` Loader 按 companyId hash 稳定分配到一个 variant,eval harness(B5)可以对比两个 variant 的行为差异。 ## Migration 计划 1. **Phase 0(前置)**:B5 eval harness 就绪 + C4 initial-user-turn 精简完成 2. **Phase 1(3 天)**:给所有 markdown 片段加 frontmatter(`id` / `version` / `tags`),loader 支持读取 3. **Phase 2(1 周)**:实现 `resolveSections` + `renderSection`,行为上保持与当前一致(所有 section 都包含),新旧切换走 feature flag 4. **Phase 3(3 天)**:按 role 开始差异化加载——CEO 仍看全量,非 CEO 开始按 tag 过滤 5. **Phase 4(3 天)**:加 budget enforcement,先 warn-only 跑一周观测分布 6. **Phase 5(持续)**:A/B harness + per-company variant 分配 ## 风险与回滚 | 风险 | 触发条件 | 回滚方法 | |------|---------|---------| | 漏包含 SOP 导致 agent 行为退化 | tag 过滤规则太严 | eval 发现 → 回到"全量包含" flag → 修 tag 规则 | | Budget error 触发生产崩 | 某 company 的 connector 数量极端 | 阶段 4 warn-only 期间监测 p99;超限前调高或加裁剪 | | Markdown frontmatter 格式错 | 作者漏写 version | Loader 启动时校验所有片段,任何格式错直接 fail | | Cache invalidation 增多 | 稳定前缀拆得太细 | 监控 cache hit 率,必要时合并片段 | ## 验证标准 **前置:B5 必须就绪**。 1. **行为 eval**:对比 5 种 agent role × 3 种 trigger 下的 tool call 序列,新旧方案一致 2. **Prompt 长度**:非 CEO agent 的 system prompt 长度降低 40%+ 3. **Cache hit rate**:观察 Anthropic cache hit rate,降低 <5%(cache 友好) 4. **Budget 分布**:99% 的 prompt 在 warn 阈值内 5. **A/B pilot**:至少一个 SOP 有 variant,eval 可观测两个 variant 的行为差异 ## 不在范围 - **不改 SOP 内容**:只是让加载条件化 + 版本化,具体文字不动(那是 Owner 业务的事) - **不做 AI-driven prompt optimization**:prompt 片段仍是人写的 markdown - **不改 pi 的 prompt 结构**:本项目只控制自己注入到 pi 的 systemPrompt,pi 内部行为不动 - **不做 per-agent fine-grained prompt**:按 role / department 分组即可,不做 per-agent 定制 - **不动 token 精确化**(C3 的 scope):本 proposal 可先用近似估算,C3 完成后自动精确 ## 参考文件清单 - `src/main/prompt-builder/system-prompt.ts:1-262`(主拼装函数) - `src/main/prompt-builder/sops/*.md`(15 个 SOP) - `src/main/prompt-builder/api-docs/*.md`(11 个 API 文档) - `src/main/prompt-builder/ceo-modes/*.md`(5 个 CEO 模式) - `src/main/prompt-builder/department-guidelines/*.md`(7 个部门指南) - `src/main/prompt-builder/operating-instructions.ts` + `operating-instructions-ceo.ts` - `src/main/prompt-builder/capability-registry.ts` - `src/main/prompt-builder/skill-injection.ts` - `src/main/prompt-builder/index.ts`(入口 + per-turn 拼装) ## 附:为什么现在做 prompt caching 的经济账:Anthropic cache 5 分钟 TTL,稳定前缀每 miss 一次多付 ~30% cost。262 行拼装每天被改几次就是每天多付 30% × N 次。这是隐性 recurring cost,估算 company 规模一大、agent 数量一多,年度多付上万美元(有具体数值可做 cost model,不在本 proposal 范围)。 更重要的是:**有了条件拼装 + version frontmatter,AI coding agent 改 prompt 时可以 diff 到具体片段**——而不是在 262 行大 blob 里大海捞针。这直接降低 team 改 prompt 的心理门槛,促进迭代。 ## 附:gating matrix | Gate | 条件 | 放行判断人 | |------|------|----------| | G1 | B5 eval harness + C4 完成 | Karpathy | | G2 | 所有 markdown 片段加了 frontmatter | Owner | | G3 | resolveSections 函数覆盖 CEO / 非 CEO / 所有 department | Owner | | G4 | Budget enforcement warn 阈值下 95% prompt 不触发 | Karpathy | | G5 | A/B variant 第一个实验跑出 eval 差异数据 | Karpathy | ## 附:markdown frontmatter schema ```ts export const SopFrontmatterSchema = z.object({ id: z.string().regex(/^sop\.[a-z-]+$/), version: z.string().regex(/^\d+\.\d+$/), tags: z.array(z.string()).min(1), locale: z.enum(["en", "zh"]).default("en"), variants: z.array(z.object({ id: z.string(), weight: z.number().min(0).max(100), path: z.string().optional(), })).optional(), }); ``` Build 时校验所有 markdown 符合 schema;不符合直接 fail。 ## 附:SOP tag 分类体系(初版草案) | Tag | 含义 | 关联 role | |-----|------|---------| | task | 任务管理相关 | 所有 | | delegation | 分派相关 | CEO, 管理层 | | code | 代码相关 | engineering | | review | 审查相关 | 所有 reviewer | | messaging | 消息沟通 | 所有 | | escalation | 升级相关 | 所有 | | finance | 财务相关 | finance, CEO | | legal | 法务相关 | legal, CEO | | marketing | 营销相关 | marketing | | customer | 客户交互 | support, sales | 规则:SOP 至少有 1 个 tag,agent 根据 role 计算 relevant tag 集合。