# 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 平衡了清晰度和表达力。