Tool Call Role Design: Claude Code vs Codex

2026-05-22#agent-runtime#tool-call#role-design#claude-code#codex

背景

这次讨论的问题是:为什么 Claude Code / Anthropic 的工具结果看起来会放在 role: user,而 Codex / OpenAI Responses API 更像是把工具调用和工具结果作为独立事件处理。

严格区分一下:

  • Claude 不是把 tool call 本身定义成 user。Claude 的 tool call 是 assistant 侧的 tool_use
  • 被放到 role: user 的是工具执行后的 tool_result
  • 这个 user 不等于真人用户,更像 client / environment 侧把外部观察结果递回模型。

核心判断

Claude 的设计可以理解成“对话输入流”:

  • assistant:模型自己说的话、自己的 tool_use
  • user:外部世界给模型的新输入,包括真人消息、环境上下文、工具结果
  • tool_result 通过 content block type 和 tool_use_id 与前面的工具调用对应

这种设计的好处是 schema 简单、对话 loop 直接、content block 统一。代价是 transcript 可读性差:大量 role: user 里其实不是人说的话,而是 shell output、file read、MCP response 这类工具观察。

Codex / OpenAI Responses 的设计更像“agent 事件流”:

  • 用户消息仍是 role: user
  • 模型请求工具是 function_call
  • 工具返回是 function_call_output
  • 两者用 call_id 对应

这种设计的好处是审计、回放、状态机和权限分析更清楚。工具输出不混进 user role,角色体系主要表达指令权威,工具输出则是 observation。代价是协议更复杂,适配别的模型或框架时需要处理 typed items。

一句话:

Claude 把工具结果放进对话输入流;Codex 把工具结果放进 agent 事件流。

各自适合什么

Claude 这种做法适合:

  • 快速实现一个 assistant tool_use -> client execute -> user tool_result -> assistant continue 的 loop
  • 使用两角色 message schema,避免扩展太多 role
  • 把文本、图片、工具调用、工具结果都放进统一 content block

Codex 这种做法适合:

  • 复杂本地 agent runtime
  • 多工具、多轮执行、approval、sandbox、sub-agent 等事件审计
  • 把工具输出当作可追踪的 observation,而不是混在 human/user message 里
  • 后续做 Trace Log、回放、压缩、权限边界分析

可发展的文章角度

可以写成一篇关于 agent runtime 表示层的文章,暂定角度:

  • 标题方向:对话流还是事件流:agent 工具结果到底应该放在哪里
  • 主线问题:tool result 是用户输入、环境观察,还是 agent runtime event?
  • 核心冲突:简单 API schema 与长期可审计 runtime 之间的取舍
  • 可延展到已有文章里的 Trace Log:一个好的 Trace Log 不只记录最终输出,还要能区分人类指令、系统约束、模型决策、工具观察和环境副作用

后续问题

  • 如果工具输出里有 prompt injection,它在 role: user 下会不会更容易被误当成高权威指令?
  • 一个自研 agent harness 是否应该显式定义 observation / tool_result 层,而不是复用 user
  • Trace Log 里应该怎样同时保留可读性和模型可继续消费的结构?