Yoda
参考Agent 设计指南Session

Session 自动重命名

CC 用 Haiku 在首条用户消息上一次性生成英文标题(统一模块 sessionTitle.ts,多 surface 复用);Codex CLI 零生成、title=首条用户消息原文——harness 必须自建本地化命名管线

Session 自动重命名

结论

自动命名是「runtime 能力参差、harness 必须补齐」的典型案例,且补齐的具体姿势两边不同。Claude Code 有完整的 AI 标题生成器:单一模块 utils/sessionTitle.tsHaiku、固定英文 prompt(sentence-case 3-7 词、JSON schema 输出),触发点是首条真实用户消息、一次性;但在 2026-03-31 快照的交互模式里生成结果只喂终端 tab 标题,落盘成 ai-title 条目的是 SDK/IDE/远程会话路径。Codex CLI 则完全没有生成逻辑(全仓 grep 为负),ThreadMetadata.title 就是首条用户消息原文。结论:在 CC 上 harness 的问题是「英文标题需要本地化覆盖 + 防回写竞态」,在 Codex 上是「从零自建」。Yoda 的做法是两边统一自建管线:spawn 用户已装的 agent CLI 生成标题(复用其认证),用 titleSource 三态保证自家标题不被 runtime 的自动名覆盖。

研究问题

  • CC 的标题生成用什么模型、什么 prompt、何时触发、写到哪里?
  • 生成的标题能否被外部覆盖而不被回写冲掉?
  • Codex 侧「看起来像标题」的字段到底是什么?
  • harness 如何在不引入独立 API client 的前提下做本地化标题生成?

各 Agent 设计与实现

Claude Code

生成器:单一模块 + Haiku + 英文硬编码 prompt。 [一手源码] utils/sessionTitle.ts:1-13 的头注释自述这是「全 surface 的 AI 标题单一事实源」(此前 teleport.tsx 和 /rename 各有一个 Haiku 生成器,保留作兼容)。核心调用(sessionTitle.ts:79-112):

const result = await queryHaiku({
  systemPrompt: asSystemPrompt([SESSION_TITLE_PROMPT]),
  userPrompt: trimmed,
  outputFormat: { type: 'json_schema', schema: { ... title: string ... } },
  options: { querySource: 'generate_session_title', ... },
})

prompt(sessionTitle.ts:56-68)要求 "concise, sentence-case title (3-7 words)",给的全部是英文正反例({"title": "Fix login button on mobile"}),没有任何 locale 指令——这就是「CC 自动标题永远是英文」的根源 [一手源码]。长对话输入会被尾切到最近 1000 字符(MAX_CONVERSATION_TEXTsessionTitle.ts:26-54)。失败静默返回 null,打 tengu_session_title_generated 遥测。

交互模式触发:首条真实用户消息,一次性,resume 不重titled。 [一手源码] screens/REPL.tsx:2684-2697:找第一条非 meta 用户消息,跳过斜杠命令/<bash-input> 等合成消息(「等真正的 prose」),haikuTitleAttemptedRef 保证只尝试一次;该 ref 在有 initialMessages 时初始化为 true(REPL.tsx:1133)——resume 的会话不会被从对话中段重新起名。注意快照中的关键事实:交互路径的结果只进 terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Claude Code'REPL.tsx:1135),即终端 tab 标题,不落盘

落盘路径(ai-title 条目)在 SDK/远程侧。 [一手源码]

  • SDK 控制请求 generate_session_titlecli/print.ts:3783-3805):fire-and-forget 调 Haiku,persist: truesaveAiGeneratedTitleai-title 条目;注释明确建议有 stale-write guard 的客户端(VS Code)传 persist: false、走自己的 rename 路径以避免「AI 标题在用户中途改名之后才落地」的竞态(sessionStorage.ts:2658-2666)。
  • CCR 远程会话(hooks/useRemoteSession.ts:515-530):首条消息生成标题,失败 fallback 为消息截断 75 宽。
  • Bridge headless(bridge/initReplBridge.ts:302-345):消息数 1 时先用 deriveTitle 占位 + Haiku 升级,消息数 3 时对全量对话重新生成;用 genSeq 序号防止 count-1 的 Haiku 晚于 count-3 返回而覆盖更优标题,且每次落地前重查 /rename——用户显式改名永远短路自动名。

[推断] Yoda 的 CC watcher 在线上版本(2.1.x)的交互会话 transcript 里观测到了 ai-title 行(yoda/src/main/core/session-title/claude-title-source.ts:18-21 注释),说明 03-31 之后的版本已把交互模式的 Haiku 标题也持久化了——快照滞后约两个月,此处行为已演进。

防覆盖设计回顾(详见《Session 命名同步》):ai-titlecustom-title 是不同条目类型 + 不同字段名,读取偏好 customTitle > aiTitlereAppendSessionMetadata 永不重追加 AI 标题(sessionStorage.ts:2641-2666)——AI 标题被设计为「可再生的临时物」,外部覆盖它是安全且预期的。

另有 /rename 的辅助生成器 generateSessionNamecommands/rename/generateSessionName.ts:10-45):同样 Haiku,但 prompt 要求 kebab-case 2-4 词("fix-login-bug" 风格),产物走 custom-title(视为用户改名)。

Codex CLI

没有任何自动命名。 [一手源码]core/ app-server/ tui/ cli/ 全量 grep generate.*title|title.*generat|summariz.*title 为负。ThreadMetadata.title 的注释就叫 "A best-effort thread title"(state/src/model/thread_metadata.rs:90-91),其唯一赋值点是 rollout 回放时取首条用户消息原文state/src/extract.rs:97-102):

if metadata.title.is_empty() {
    let title = strip_user_message_prefix(user.message.as_str());
    if !title.is_empty() { metadata.title = title.to_string(); }
}

显式命名只有 thread/name/set API(外部调用者负责生成内容);prefer_existing_explicit_titlethread_metadata.rs:242-255)在 backfill 对账时区分「显式标题」与「等于首条消息的自动标题」,保护前者。[推断] Codex App(桌面端)的自动重命名由 App 自己生成后经 API 写入——CLI/core 仓库中无此逻辑,无法源码验证 App 侧实现。

边界与相邻机制

  • 用户可关闭标题反映settings.terminalTitleFromRename !== false 控制 /rename 结果是否进终端标题(REPL.tsx:1127-1128);titleDisabled 时整条 Haiku 生成路径不触发(REPL.tsx:2684)。
  • task-summary 是另一条"自动描述"管线,别与标题混淆:CC 每 min(5 步, 2 分钟) fork 主线程生成「agent 正在干什么」的滚动摘要,写 task-summary 条目供 claude ps 展示(types/logs.ts:87-98 注释);它被 re-append、读最新一条即可,语义是状态而非命名(sessionStorage.ts:2676-2682)。harness 做会话列表的「副标题/进行中描述」应读它而不是 aiTitle。
  • 空输入防御generateSessionTitle 对空描述直接返回 null(sessionTitle.ts:83-84);REPL 失败时清 ref 允许下条消息重试(REPL.tsx:2694-2696)。

差异矩阵

维度Claude CodeCodex CLI
自动命名有:Haiku + 固定英文 prompt(sentence-case 3-7 词)无:title = 首条用户消息原文(extract.rs:97-102
模型/调用queryHaiku + JSON schema 强约束输出
触发时机首条真实用户消息一次性;bridge 模式 count-3 再升级一次
语言英文硬编码,无 locale 感知跟随用户输入语言(因为就是原文)
持久化ai-title transcript 条目(SDK/远程路径;快照中交互路径仅终端标题)SQLite threads.title / session_index.jsonl(仅显式 set)
外部覆盖安全性安全:AI 标题可再生、永不 re-append、CAS 只保护用户标题安全:显式 set 直接覆盖;注意 backfill 会保护「显式标题」
harness 策略本地化标题覆盖(写 custom-title 或自有 DB)全量自建生成

最小复现

# CC:headless 下触发标题生成并观察落盘(SDK stream-json 模式发 generate_session_title 控制请求后)
grep '"type":"ai-title"' ~/.claude/projects/<slug>/<session-id>.jsonl
# 观察英文-only:任意中文首条消息的会话,ai-title 仍是英文

# Codex:确认 title 即首条消息原文(第三列为 1 = 自动标题,0 = 显式命名过)
sqlite3 ~/.codex/state_5.sqlite "SELECT title, first_user_message, title = first_user_message FROM threads ORDER BY created_at DESC LIMIT 3;"

# Codex:确认 CLI 仓库无生成逻辑(应为空输出)
grep -rni "generate.*title\|title.*generat" --include="*.rs" \
  codex-rs/core/src codex-rs/app-server/src codex-rs/tui/src codex-rs/cli/src | grep -v test

Harness 接入建议(Yoda 实践)

Yoda 对两个 runtime 统一采用自建命名管线 + 来源防覆盖

  • 生成不走独立 API client,而是 spawn 用户已装的 agent CLIgenerateConversationTitle.tstask-naming-serviceresolveNamingRuntime / requestAgentNamingPayload;摘要同理走 runAgentCligenerateSessionSummary.ts:11)。好处:复用用户的认证与模型配置,零额外密钥;代价:延迟高于直连 API,需做超时(Yoda 摘要 90s 超时,SUMMARY_TIMEOUT_MS)。
  • 本地化在自己手里:prompt 上下文取最近 12 条消息 / 6000 字符,标题截到 48 字符(generateConversationTitle.ts:37-40);摘要管线有显式语言档位 'app' | 'prompt' | 'en' | 'zh-CN'generateSessionSummary.ts:38)——这正是对 CC 英文-only 的覆盖方案。
  • 防回写竞态用来源三态而非时序titleSource ∈ {user, yoda, agent},watcher 把 runtime 自动名只在 titleSource === 'agent'(或空)时落库(session-title-manager.ts:69-73)。比「在 CC 自动命名完成之后再覆盖」的时序方案更稳——不依赖对 runtime 内部时序的假设,CC 的 ai-title 哪怕在 Yoda 命名之后才到也写不进来。
  • 命名 runtime 与会话 runtime 解耦generateSessionSummary.ts:27-31 注释):标题/摘要用独立配置的 utility agent 跑,会话本身的 runtime 挂了(如认证过期)命名功能仍可用。

失效条件

  • CC 若给 SESSION_TITLE_PROMPT 增加 locale 感知(跟随用户输入语言),harness 覆盖管线可降级为兜底;
  • CC 交互模式持久化行为已在快照后变化([推断] 项),新快照到手需确认 ai-title 的交互写入点;
  • CC 更换标题模型(Haiku → 其他)或改触发策略(如 bridge 的 count-3 升级推广到交互模式);
  • Codex CLI 若引入自动命名(向 App 看齐),Yoda 的「agent 来源仅 interim」规则仍成立,但需确认其写入位置(SQLite vs API 通知)。

参考资料

  • CC 源码:utils/sessionTitle.ts(生成器全文)、screens/REPL.tsx:1129-1135, 2684-2697cli/print.ts:3783-3805bridge/initReplBridge.ts:302-345hooks/useRemoteSession.ts:515-530commands/rename/generateSessionName.ts
  • Codex 源码:state/src/extract.rs:84-102state/src/model/thread_metadata.rs:90-91, 242-255
  • Yoda 实现:src/main/core/conversations/generateConversationTitle.tsgenerateSessionSummary.tssrc/main/core/session-title/session-title-manager.ts
  • 姊妹章:《Session 命名同步》(存储与冲突收敛细节)

On this page