proposal.md 是一次变更的入口文件。它不是详细技术设计,也不是产品宣传稿,而是用最短路径建立四个共识:为什么现在要做、具体改变什么、涉及哪些长期能力、影响边界在哪里。

如果 proposal 没有把问题说清楚,后面的 spec、design 和 tasks 会在错误前提上越写越完整。AI 编程最危险的情况不是生成速度慢,而是方向错误时仍然高速产出。proposal 的价值就是在成本最低的阶段暴露方向性问题。

📷 [图片 token=NUVabGiXHoCDJWxhurNccMXynxe(未能下载,见飞书原文)]

推荐组织结构

## Why

## What Changes

## Capabilities

### New Capabilities

### Modified Capabilities

## Impact

📷 [图片 token=OoTnbcz9foF9VcxxHxlcSCNsn9g(未能下载,见飞书原文)]

Why:解释问题与时机

Why 应描述当前可复现的问题、限制或机会,以及不处理会造成什么后果。它需要给出足够上下文,让不了解这次对话的人也能理解动机,但不应提前展开类名和具体代码。

主案例的 Why 很具体:百炼 text-embedding-v4 单次最多接受 10 条文本,而索引服务会把全部 chunk 交给默认 Embedding 客户端;文档产生 11 个以上 chunk 时会在向量生成阶段收到 HTTP 400,索引任务失败。这段话包含外部约束、当前行为、触发条件和用户可见后果,因果链完整。

📷 [图片 token=SKLdbItI5oI37Bx5gqqcY3ptnHg(未能下载,见飞书原文)]

What Changes:说明将发生的变化

这一节写“做完后系统有什么不同”,通常用几条有边界的变化描述。主案例写了三件事:客户端单批限制为 10;更大的输入自动分批并保持输入输出顺序;增加超过上限的回归测试。它没有在这里决定常量放在哪个函数,因为那属于 design。

📷 [图片 token=TafzbGDgEoCDJexn67gcj3cunoc(未能下载,见飞书原文)]

Capabilities:把 change 映射到长期规格

Capability 是主规格的逻辑分组,也是 delta spec 的目录名。New Capabilities 表示系统以前没有的长期能力;Modified Capabilities 表示修改已有能力。主案例没有新增产品能力,而是修改 qwen-openai-provider 的兼容性约束,所以 New 写“无”,Modified 指向现有 capability。

这个区分很重要。如果把 change 名直接当 capability,例如为每个 Bug 都创建一个永久规格目录,主规格会迅速变成按工单组织的历史堆积,而不是按系统能力组织的当前事实。

📷 [图片 token=IXMpbVHnioDEHuxEX0ncjEf2nHt(未能下载,见飞书原文)]

Impact:明确修改面和不修改面

Impact 应指出可能涉及的代码、契约、存储、测试、迁移和用户界面。主案例明确影响后端 Embedding 客户端构造、Provider 单测和索引回归测试;同时明确不修改 HTTP API、SSE 契约、Milvus schema 和前端行为。

“不修改什么”同样是重要信息。它让 Codex 不会为了修一个 Provider 兼容性问题,顺手重构前端或协议层,也让评审者能快速发现实际 diff 是否越界。

📷 [图片 token=N2LnbObwWoNtmWxzSFjcYx2snid(未能下载,见飞书原文)]

为什么不能用 design 代替 proposal

proposal 讨论的是意图和范围,design 讨论的是实现方法。如果一开始就写“在 OpenAIEmbeddings 构造时增加 chunk_size=10”,看似具体,却跳过了几个必要判断:为什么是 10、谁受到影响、顺序是否必须保持、业务层应不应该感知、接口和存储是否需要变化。

先确认 proposal,意味着即使技术方案后来改变,问题定义和验收边界仍然稳定。技术方案可以从 LangChain 参数调整为自定义适配器,但“每个请求最多 10 条、任意输入都返回完整且有序的向量”这一目标不应丢失。

📷 [图片 token=OF1DbrUhuoCK1FxZTc4cHeeHnFg(未能下载,见飞书原文)]

怎样判断 proposal 写得好不好

**问题是否可证实。**避免“体验不好”“需要优化”这类无法判断的描述。应说明触发条件、现有结果和期望结果。

**范围是否单一。**一个 change 应围绕一个可解释目标。修 Embedding 拆批时,不应顺便更换向量库、重做切分算法或设计新前端。

**能力命名是否长期稳定。**Capability 应描述系统边界,例如 qwen-openai-provider,而不是一次性的任务名。

**Impact 是否与仓库真实结构对应。**OncallAgent 有共享 HTTP/SSE 契约、FastAPI 后端、Vue 前端、SQLite、Milvus 和用户级 MCP 等边界。涉及哪一层应如实列出,不涉及也应明确排除。

📷 [图片 token=Fik5bMD1goj3c9x2ZMtc8MBNn1g(未能下载,见飞书原文)]

常见反例

把 proposal 写成口号。“提升系统稳定性和用户体验”没有说明什么会改变,也无法生成可靠的 spec。

**提前锁死实现。**proposal 中堆类名、函数名和伪代码,会让意图与方案耦合。实现细节应放进 design。

**只写要做什么,不写不做什么。**没有 Non-Goals 或 Impact 边界时,AI 很容易顺手扩大范围。

**Capability 与 change 混淆。**change 是一次修改,capability 是长期系统能力;归档 change 后,capability 仍留在主规格中。

📷 [图片 token=RKNObsTmgoFwzjxHX1rcFQ1inWd(未能下载,见飞书原文)]

面试表达

我把 proposal 当成变更的立项边界,而不是技术方案。它先把问题、变化、能力归属和影响面讲清楚。以 Embedding 批量兼容为例,proposal 明确故障来自 10 条上限,目标是透明拆批并保持顺序,同时排除 HTTP/SSE、Milvus 和前端变化。这样 design 可以专注决定约束放在哪一层,代码评审也能用 Impact 检查有没有越界。