12 KiB
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 路径、业务数字、语气全写死:
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");
五个耦合:
- Tool 名:
bash - 实现细节:
node -e '...'+fetchPOST - API 路径:
/api/companies/{companyId}/agents、/api/companies/{companyId}/approvals - 业务数字:3-5 个 hire + 1 个 approve_ceo_strategy
- 语气:NOW / Do not
这段 prompt 没有任何机制让它和 src/main/prompt-builder/ceo-modes/team-building.en.md 保持一致——两处都在告诉 CEO "现在该做什么",容易 drift。
根因分析
Karpathy 视角(prompt 工程原则):把实现细节泄漏到 LLM 层是最脆的设计。
当前 prompt 假设:
bashtool 永远存在(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——这意味着:
- main 进程要跑 HTTP server(
api-server.ts) - CEO 要拼 body / 处理 auth / 处理 error
- 出错只能通过 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:
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:
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 计划
- Phase 0(前置):B5 eval harness 就绪——能 record 当前 CEO 在 team-building trigger 下的 tool call 序列(作为 baseline)
- Phase 1(1 周):实现
hire_agent/file_approvaltool,注册到 pi-agent-factory。- CEO 在某些场景开始用新 tool,但保留旧 HTTP API(dual-path)
- Phase 2(3 天):跑 eval 对比——CEO 在新 tool 可用时的行为 vs baseline
- 必须验证:新 tool 可用时 CEO 优先用 tool,且产出的 hire 记录 schema 与 HTTP 路径一致
- Phase 3(3 天):修改
initial-user-turn.ts和ceo-modes/team-building.en.md,删除实现细节 - Phase 4(1 周):eval 验证行为没有回退,下掉 HTTP
/api/companies/{id}/agentsPOST 路径(保留 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 必须就绪。具体验证:
- 行为 eval:在 10+ 种初始 company 状态下,CEO 产出的 hire 数量 / 角色 / 参数,新方案应与旧方案一致(允许 ±1 个 hire 的方差)
- Tool 覆盖:新
hire_agenttool 被 CEO 调用的比例 ≥95%(剩余 <5% 走兼容 HTTP 路径) - Prompt 长度:initial user turn 从 ~400 token 降到 ~80 token
- Regression test:旧
curl -X POST /api/companies/{id}/agents仍能工作(兼容期) - 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_approvaltool 只是路径替换,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 草案
{
"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 进程内):
- 校验 connectorId / model 组合合法性
- 生成 agentId
- 调
db.agents.saveAgent(...)写入 - 发 telemetry
agent.hired - 返回
{ 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 平衡了清晰度和表达力。