251 lines
12 KiB
Markdown
251 lines
12 KiB
Markdown
# Proposal C4: initial-user-turn 退回语义 cue + hire 升级为显式 tool
|
||
|
||
> 来源:问题.md C4(行 158-172)、附录 F 样本 4(行 479-491)
|
||
> 状态:proposed(待 B5 eval harness 就绪后实施)
|
||
> 提出:Roundtable 2026-04-20(Linus + Karpathy + Steve Jobs)
|
||
> Owner: TBD
|
||
> 估计工时:2 周(其中 1 周做 hire_agent tool,1 周改 prompt + eval 验证)
|
||
> Blocking dependencies:**B5(LLM 行为 eval harness)必须先就绪**,否则改 prompt 无法验证行为回归
|
||
>
|
||
> 圆桌投票记录:Linus 不改,Karpathy 不改,Jobs 改(2-1 决定今晚不动代码,写 proposal 等 eval)
|
||
|
||
## 问题陈述
|
||
|
||
`src/main/orchestration/initial-user-turn.ts:62-83` 的 CEO 初始 user turn 把 tool 选择、实现细节、API 路径、业务数字、语气**全写死**:
|
||
|
||
```ts
|
||
return [
|
||
"You are the CEO of this company. Your team is empty (zero active direct reports).",
|
||
"This wake has ONE job: call the `bash` tool to execute `node -e '...'` fetch POSTs that file 3–5 hire_agent requests against /api/companies/{companyId}/agents, then one approve_ceo_strategy POST against /api/companies/{companyId}/approvals.",
|
||
"The exact body shape and connectorId rules are in your Team Building Phase operating instructions above. Use a connectorId and model from the Available Connectors section (only ready connectors).",
|
||
"Start the first POST NOW. Do not write a plan document. Do not describe what you will do — execute the tool call.",
|
||
].join("\n\n");
|
||
```
|
||
|
||
五个耦合:
|
||
1. **Tool 名**:`bash`
|
||
2. **实现细节**:`node -e '...'` + `fetch` POST
|
||
3. **API 路径**:`/api/companies/{companyId}/agents`、`/api/companies/{companyId}/approvals`
|
||
4. **业务数字**:3-5 个 hire + 1 个 approve_ceo_strategy
|
||
5. **语气**:NOW / Do not
|
||
|
||
这段 prompt 没有任何机制让它和 `src/main/prompt-builder/ceo-modes/team-building.en.md` 保持一致——两处都在告诉 CEO "现在该做什么",容易 drift。
|
||
|
||
## 根因分析
|
||
|
||
Karpathy 视角(prompt 工程原则):**把实现细节泄漏到 LLM 层是最脆的设计**。
|
||
|
||
当前 prompt 假设:
|
||
- `bash` tool 永远存在(pi-agent-core 未来可能改名或增加 sandbox 模式)
|
||
- `node -e` 执行环境永远可用(container 化后可能没 node)
|
||
- `/api/companies/...` 路径永远稳定(REST → GraphQL 迁移时全部失效)
|
||
- CEO "理解" fetch POST 的 body shape(依赖 prompt 里其他部分说清楚)
|
||
|
||
每个假设破一次,CEO 就静默做错事。更糟的是——**CEO 一旦学会这个 pattern,其他 agent 会照抄**:所有业务逻辑都 `bash + node -e + fetch`,项目滑向 "everything is bash" 的反模式。
|
||
|
||
Linus 视角(第一性原理):hire 这件事**本来就应该是一个 tool**。当前让 CEO 通过 HTTP POST 自己的 API server 来 hire——这意味着:
|
||
1. main 进程要跑 HTTP server(`api-server.ts`)
|
||
2. CEO 要拼 body / 处理 auth / 处理 error
|
||
3. 出错只能通过 HTTP status code 感知
|
||
|
||
直接把 `hire_agent` 做成 pi tool,CEO 调用 tool → main 进程直接 DB 写 → 返回 ack。省掉整个 HTTP 层。
|
||
|
||
Jobs 视角(为什么他投改):这段 prompt 就是 Jobs 厌恶的"让工程问题泄漏给用户"——CEO 是用户视角的 agent,不该知道 HTTP endpoint。
|
||
|
||
**为什么今晚不改**(2-1 决定):Linus + Karpathy 认为无 eval 下改 prompt = 盲飞。CEO 的实际行为不能凭"prompt 读起来更干净"来验证,必须有 golden trace 对比。今晚没有 eval 框架(B5 blocked by user constraint "无 tests/fixture")就硬改,可能让 CEO 完全不 hire 而团队当场发现不了。
|
||
|
||
## 设计方案
|
||
|
||
分两步:
|
||
|
||
### Step 1:把 `hire_agent` 升级为显式 pi tool
|
||
|
||
新建 `src/main/agent-runtime/tools/hire-agent.ts`:
|
||
|
||
```ts
|
||
export const hireAgentTool: AgentTool = {
|
||
name: "hire_agent",
|
||
description: "Create a new direct report agent for this company. Use this when the CEO needs to fill a role.",
|
||
input: {
|
||
type: "object",
|
||
required: ["name", "role", "description", "connectorId", "model"],
|
||
properties: {
|
||
name: { type: "string", description: "Short display name for the agent, e.g. 'CTO'" },
|
||
role: { type: "string", description: "Business role, e.g. 'Chief Technology Officer'" },
|
||
description: { type: "string", description: "1-2 sentences describing responsibilities" },
|
||
connectorId: { type: "string", description: "Must be a ready connectorId from the Available Connectors list" },
|
||
model: { type: "string", description: "Must be allowed by the connector's enabledModels" }
|
||
}
|
||
},
|
||
handler: async (input, ctx) => {
|
||
// 校验 connectorId / model
|
||
// 直接调 db.agents.saveAgent(...)
|
||
// 返回 agentId
|
||
}
|
||
};
|
||
```
|
||
|
||
同时引入 `file_approval` tool,替代 `/api/companies/{companyId}/approvals` POST。
|
||
|
||
### Step 2:把 initial-user-turn 降级为语义 cue
|
||
|
||
After:
|
||
|
||
```ts
|
||
return [
|
||
"You are the CEO. Your team is currently empty.",
|
||
"Review your Team Building Phase operating instructions in the system prompt, then take action.",
|
||
].join("\n\n");
|
||
```
|
||
|
||
**没有** `bash`、没有 `node -e`、没有 API 路径、没有数字、没有"NOW"。系统 prompt 里的 `ceo-modes/team-building.en.md` 自己说做什么、做多少。
|
||
|
||
### SOP 层更新
|
||
|
||
`src/main/prompt-builder/ceo-modes/team-building.en.md` 需要明确:
|
||
- 描述任务(组建初始团队)
|
||
- 描述可用 tool(`hire_agent`、`file_approval`)
|
||
- 描述合理数量(3-5 个角色)
|
||
- 不描述具体实现(bash / fetch / HTTP)
|
||
|
||
这样 CEO 行为 = system prompt 决定,initial-user-turn 只是**触发信号**。
|
||
|
||
## Migration 计划
|
||
|
||
1. **Phase 0(前置)**:B5 eval harness 就绪——能 record 当前 CEO 在 team-building trigger 下的 tool call 序列(作为 baseline)
|
||
2. **Phase 1(1 周)**:实现 `hire_agent` / `file_approval` tool,注册到 pi-agent-factory。
|
||
- CEO 在某些场景开始用新 tool,但保留旧 HTTP API(dual-path)
|
||
3. **Phase 2(3 天)**:跑 eval 对比——CEO 在新 tool 可用时的行为 vs baseline
|
||
- 必须验证:新 tool 可用时 CEO 优先用 tool,且产出的 hire 记录 schema 与 HTTP 路径一致
|
||
4. **Phase 3(3 天)**:修改 `initial-user-turn.ts` 和 `ceo-modes/team-building.en.md`,删除实现细节
|
||
5. **Phase 4(1 周)**:eval 验证行为没有回退,下掉 HTTP `/api/companies/{id}/agents` POST 路径(保留 GET / DELETE 用于 UI)
|
||
|
||
## 风险与回滚
|
||
|
||
| 风险 | 触发条件 | 回滚方法 |
|
||
|------|---------|---------|
|
||
| CEO 不 hire(改 prompt 后) | 语义 cue 太弱 | eval 直接发现,revert prompt |
|
||
| CEO hire 错角色 | 新 tool 接收参数导致发散 | tool schema validation 层拒绝 |
|
||
| 旧 call site 依赖 HTTP | UI 或脚本直接调 `/api/companies/{id}/agents` | 保留 HTTP 路径直到 UI 迁移完 |
|
||
| eval fixture 未覆盖边缘 | 某些 trigger(例如"空公司 + 单 connector")没 baseline | phase 2 要求覆盖 10+ 初始状态组合 |
|
||
|
||
**回滚路径**:每个 phase 可独立 revert。tool 可先加不强推;prompt 改动可直接回到硬编码版本。
|
||
|
||
## 验证标准
|
||
|
||
**前置:B5 必须就绪**。具体验证:
|
||
|
||
1. **行为 eval**:在 10+ 种初始 company 状态下,CEO 产出的 hire 数量 / 角色 / 参数,新方案应与旧方案一致(允许 ±1 个 hire 的方差)
|
||
2. **Tool 覆盖**:新 `hire_agent` tool 被 CEO 调用的比例 ≥95%(剩余 <5% 走兼容 HTTP 路径)
|
||
3. **Prompt 长度**:initial user turn 从 ~400 token 降到 ~80 token
|
||
4. **Regression test**:旧 `curl -X POST /api/companies/{id}/agents` 仍能工作(兼容期)
|
||
5. **Cache friendliness**:initial user turn 变短后,system prompt 的 cache hit 率提升
|
||
|
||
## 不在范围
|
||
|
||
- **不改 system prompt 整体结构**:262 行的系统 prompt 走 C7 proposal 独立重构
|
||
- **不改 CEO 模式 state machine**:5 个 ceo-modes 文件结构保留,走 M7 proposal
|
||
- **不做 prompt A/B harness**:那是 C7 的事
|
||
- **不动 renderer**:hire 在 UI 层的表现不变(创建新 agent 卡片)
|
||
- **不改 approval 审批流**:`file_approval` tool 只是路径替换,approval 逻辑本身不动
|
||
|
||
## 参考文件清单
|
||
|
||
- `src/main/orchestration/initial-user-turn.ts:62-83`(硬编码段落)
|
||
- `src/main/orchestration/initial-user-turn.ts:78-82`(CEO lookup fail-open catch,关联 C6)
|
||
- `src/main/prompt-builder/ceo-modes/team-building.en.md`(对应的 operating instructions)
|
||
- `src/main/api-server.ts` + `src/main/api-endpoints.ts`(当前承载 hire POST 的 HTTP 层)
|
||
- `src/main/worker/pi-agent-factory.ts:131`(tool 注册点)
|
||
- `src/main/database/agents.ts`(CEO hire 最终写入的 DB 层)
|
||
|
||
## 附:为什么这件事不能省略也不能现在 rush
|
||
|
||
**不能省略**:每次 pi-agent-core 升级(B1 问题指出 62 patch 漂移),`bash` tool 的 schema / 沙盒行为可能悄悄变。prompt 硬编码 `bash + node -e + fetch` 时,升级 pi 就是一场赌博。
|
||
|
||
**不能现在 rush**(Linus + Karpathy 今晚投反对):prompt 行为是 agent 公司最敏感的地方,无 eval 改 = 赌 CEO 还按原样 hire。圆桌共识:**等 B5,然后一次性做完**。
|
||
|
||
Karpathy 原则:"没有 eval 的 prompt 改动是对随机函数做微调"——我们今晚同意把它写进 proposal 而不动代码。
|
||
|
||
## 附:gating matrix
|
||
|
||
| Gate | 条件 | 放行判断人 |
|
||
|------|------|----------|
|
||
| G1 | B5 eval harness 就绪 + baseline recorded | Karpathy |
|
||
| G2 | `hire_agent` tool 实现 + schema 固化 | Owner |
|
||
| G3 | Tool 可用期间 eval 通过:CEO 在 10+ 种初始状态下用 tool 而非 HTTP POST | Karpathy |
|
||
| G4 | Prompt 精简后 eval:hire 数量 / 角色 / 参数与 baseline 一致 | Karpathy |
|
||
| G5 | 旧 HTTP 路径灰度下线 2 周无回退 | Owner |
|
||
|
||
## 附:新 hire_agent tool 的 schema 草案
|
||
|
||
```json
|
||
{
|
||
"name": "hire_agent",
|
||
"description": "Create a new direct report agent for this company. Use when a role needs filling.",
|
||
"input_schema": {
|
||
"type": "object",
|
||
"required": ["name", "role", "description", "connectorId", "model"],
|
||
"properties": {
|
||
"name": {
|
||
"type": "string",
|
||
"description": "Short display name, e.g. 'CTO', 'Head of Marketing'",
|
||
"maxLength": 50
|
||
},
|
||
"role": {
|
||
"type": "string",
|
||
"description": "Business role title",
|
||
"maxLength": 100
|
||
},
|
||
"description": {
|
||
"type": "string",
|
||
"description": "1-2 sentences describing responsibilities and how this person contributes",
|
||
"maxLength": 500
|
||
},
|
||
"connectorId": {
|
||
"type": "string",
|
||
"description": "Connector ID from the Available Connectors section; must be ready"
|
||
},
|
||
"model": {
|
||
"type": "string",
|
||
"description": "Model name allowed by connector.enabledModels"
|
||
},
|
||
"department": {
|
||
"type": "string",
|
||
"enum": ["engineering", "product", "design", "marketing", "sales", "operations", "legal", "finance"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Tool handler(main 进程内):
|
||
1. 校验 connectorId / model 组合合法性
|
||
2. 生成 agentId
|
||
3. 调 `db.agents.saveAgent(...)` 写入
|
||
4. 发 telemetry `agent.hired`
|
||
5. 返回 `{ agentId, name, role }`
|
||
|
||
## 附:反模式对比
|
||
|
||
**Before**(`src/main/orchestration/initial-user-turn.ts:62-83`):五层耦合混在 prompt 字符串里——agent 必须"解析"提示词才知道要做什么。
|
||
|
||
**After**:agent 读到"your team is empty → 看 system prompt 的 team-building 段 → 用 hire_agent tool"这条清晰链路。每一步都是 agent 正常 reasoning 能处理的粒度。
|
||
|
||
## 附:与 M7 的协同
|
||
|
||
M7(CEO mode state machine)和 C4 共享 `resolveCeoMode` 函数——同一个函数决定:
|
||
- initial-user-turn 发什么 cue(C4)
|
||
- prompt-builder 加载哪个 ceo-modes markdown(M7)
|
||
|
||
两件事一次做完,避免 mode 判断在两处漂移。
|
||
|
||
## 附:为什么不选其他方案
|
||
|
||
**方案 A(当前 proposal)**:退回语义 cue + 显式 tool。
|
||
|
||
**方案 B(继续硬编码但写成 typed template)**:把字符串换成 template literal + 参数注入。治标不治本——实现细节仍泄漏,只是语法糖。
|
||
|
||
**方案 C(删掉 initial-user-turn,全靠 system prompt)**:失去"trigger 特定 cue"的能力——当用户 explicit 点击 "start team building" 时没有强信号区分于普通 wake。
|
||
|
||
方案 A 平衡了清晰度和表达力。
|