Files
AgentCompany/docs/proposals/C7-prompt-builder-conditional-assembly.md
T

281 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 集合。