第 19 章:外部迁移与向后兼容
阅读契约: 本章回答一个问题:Codex 如何导入外部 agent configuration 与 history,却不继承外部 runtime 的语义?阅读时跟住四个 owner:detection、conversion、native runtime refresh、session-import ledger。读完后,应该能分清 native artifact、imported artifact、skipped external construct 和 compatibility bridge。

源码边界: 本章只有在链接到固定 Codex commit 或本章源码地图的 files、structs、functions、protocol request/response shapes、import records 时,才把说法视为 verified source。把 migration 概括成 conservative translation,是从可见 converters 与 ledger boundaries 得出的 surrounding contract inference。本章不声称模拟任何外部 agent 的私有 runtime。
第 18 章把 Codex-native extension planes 拆成 skills、plugins、connectors 和 typed contributors。本章追问:当用户带来不诞生于这些 planes 的 artifacts 时怎么办?这些 artifacts 可能是 external configs、commands、hooks、subagents、MCP servers 和 JSONL sessions。
你在这里:Codex 已能在明确 trust boundaries 下加载 native extension surfaces。
问题:用户带来的外部状态很有价值,但它可能使用不同的 prompt rules、hook timing、command expansion、permissions、session schemas 和 trust assumptions。
心智模型:migration 是单向 adapter:把外部状态转成 native Codex artifacts 加 provenance,而不是把隐藏 compatibility mode 塞进每个后续 turn。
Agent system 的 backward compatibility 不只是接受旧 API 字段。它也意味着接纳用户历史与本地自动化,同时不能让含糊行为变成隐藏权威。Migration layer 应该读取 source artifact,识别 supported constructs,翻译成 native shapes,跳过 unsupported 或 unsafe cases,并记录足够 metadata,让用户和测试都能理解结果。
不变量是:migration 完成后,turn loop 应该运行 Codex-native tools、hooks、skills、MCP configs 和 rollout history。它不应该反复追问“外部 runtime 原本会怎么做?”
一、Migration 从 detection 开始,不从 mutation 开始
源码把 migration 暴露成 typed items。ExternalAgentConfigMigrationItemType 包括 config、skills、AGENTS.md、plugins、MCP server config、subagents、hooks、commands 和 sessions。Detection 返回带 description、可选 cwd、可选 details 的 ExternalAgentConfigMigrationItem records。
Shape-level:
{
"item_type": "McpServerConfig",
"description": "Import external MCP server configuration",
"cwd": "/project",
"details": {
"mcp_servers": [{ "name": "github" }],
"commands": [],
"sessions": []
}
}
这是 detection report,不是 mutation。Request processor 的 detect() 把 core migration items 映射成 app-server client 可见的 protocol response items。随后 client 可以请求 import 选中的 items。
1.1 Import 有 foreground 与 background phases
ExternalAgentConfigRequestProcessor::import() 展示 runtime shape:
selected migration items
-> validate pending session imports
-> import config-like items
-> refresh runtime config when needed
-> send import response
-> background plugin/session imports
-> clear plugin/skill caches if plugin imports ran
-> notify import completed
这个拆分很重要。有些 import 立即产生 local config changes;有些 plugin 与 session work 会在后台继续。Processor 先发送 response,等 background imports 结束后再发 ExternalAgentConfigImportCompleted。Migration UI 因而不能把“request accepted”误读成“所有 plugin 和 session 都已经完成 import”。
二、Config migration 是翻译成 native shapes
External configuration migration 处理几类 artifact:
| Source artifact | Native destination | Verified source surface | Conservative rule |
|---|---|---|---|
| MCP server entries | Codex MCP config | build_mcp_config_from_external() | 导入 supported server records,并遵守 merge rules |
| hooks | Codex hook config | import_hooks() | 只复制可表达的 hook mappings 与 scripts |
| commands | skills/workflow units | import_commands() | 要求 parseable documents 与 bounded metadata |
| subagents | native agent definitions | import_subagents() | 要求 stable frontmatter 与 safe target names |
| plugins | plugin config/store | pending plugin import path | 分开 local 与 remote import work |
Converter source 把 conservative style 写得很具体。build_mcp_config_from_external() 读取 external MCP server maps,应用 enabled/disabled server lists,并返回以 mcp_servers 为 root 的 TOML table。import_commands() 只为 supported、unique command sources 创建 skill directories,并跳过 existing targets。import_subagents() 对 agent files 也使用同样保守策略。
Shape-level,边界有意是 lossy:
external command document
-> parse frontmatter and body
-> derive stable skill name and description
-> skip if target already exists
-> write native SKILL.md
external dynamic behavior
-> no safe native representation
-> skip and report
Importer 永远不应该猜 permission boundary。如果 source command 依赖 Codex 无法表达的动态 provider-specific expansion,安全结果是 skipped,而不是把它偷塞进 shell hook。
三、Session import 把 history 当作 evidence 保留
Session import 不同于 config migration,因为 history 不是未来 capability,而是过去 conversation 的证据。Session importer 必须把外部 JSONL transcript 中足够多内容翻译成 Codex rollout items,让 thread 可以 list、resume、audit,同时仍标记 source 是 imported。

Verified lifecycle 是:
- Detection 只在 records 含 cwd、至少一条 user/assistant message,以及 latest timestamp 时总结 candidate sessions。
summarize_session()构建这个 summary。 - Import validation canonicalize selected paths,并拒绝不在 detected external projects root 下的 sessions。
validate_pending_session_imports()也会对 selected canonical paths 去重。 prepare_validated_session_imports()跳过已被 content ledger 记录的 sessions,并只加载 cwd 仍存在的 importable sessions。load_importable_session()是可见 cwd check。- Request processor 用
InitialHistory::Forked(rollout_items)启动新的 Codex thread,并在有 title 时 normalize 成 thread metadata。 - 成功后,
record_imported_session()把 source path、content SHA-256、imported thread ID 和 timestamp 写进 ledger。
3.1 JSONL shape 在边界处会改变
Parser 有意选择性保留。conversation_message_from_record() 只接受 user/assistant records,跳过 meta 与 sidechain records,提取 message content,并解析 timestamps。extract_message_text() 把 text blocks 变成 text,把 tool-use blocks 变成 notes,把 tool-result blocks 变成 notes,忽略 thinking blocks,并给 unsupported blocks 打标签。
Shape-level:
{
"type": "user",
"cwd": "/project",
"message": {
"content": [
{ "type": "text", "text": "Fix the parser" },
{ "type": "tool_result", "content": "long external output..." }
]
},
"isSidechain": false
}
会变成 Codex-importable conversation message,例如:
{
"role": "user",
"text": "Fix the parser\n[external tool result: long external output...]",
"timestamp": "parsed timestamp if present"
}
这只是 shape-level example,不是精确 serialized rollout record。关键不变量是:imported history 可以作为 history 使用,但不会被当作另一个 runtime 内部状态的 lossless replay。

3.2 Ledger 基于 content,不只基于 path
Session ledger 既要保护 idempotency,又不能隐藏 changed source content。ImportedExternalAgentSessionRecord 保存 source path、content_sha256、imported thread ID 和 import time。contains_current_source() 在决定已经导入之前,会重新计算当前文件 hash。
这避免两个坏极端:
| Ledger design | Failure |
|---|---|
| path only | changed JSONL content 被永远压制 |
| no ledger | 每次 migration prompt 都可能重复导入同一 session |
| path + content hash | unchanged sessions 被跳过,changed sessions 可以再次被发现 |
四、Compatibility bridges 应该止步于边界
Migration 是一座桥,protocol compatibility 是另一座桥。前面章节已经讨论 generated schemas、v1/v2 protocol coexistence、request aliases、experimental gates 和 client-version behavior。第 19 章的一般规则是:compatibility code 应该明确自己桥接什么,以及在哪里结束。
| Bridge | 保护什么 | 不能做什么 |
|---|---|---|
| schema aliases | older clients and stored events | 隐藏不兼容语义 |
| experimental gates | unstable capabilities | 让 unstable fields 看起来像永久契约 |
| migration converters | 来自其他工具的 user workflows | 永久模拟另一个 runtime |
| import ledgers | 幂等 session import | 压制 changed source content |
| provenance markers | imported history 的可审计性 | 用私有 implementation detail 污染 model context |
codex-rs/app-server-protocol/src/protocol/mod.rs 是一个小但有用的提醒:protocol compatibility 被组织成 namespace,包含 common pieces、mappers、thread history 与 v1/v2 modules。Compatibility 应该留在 protocol edge 与 migration edge,而不是散落在 core logic 里,变成“这也许是外部东西”的分支。
五、Failure conditions 是产品契约
Migration 最危险的失败,是静默成功但改变语义。用户需要 report,runtime 也需要清晰 failure categories。
| Failure | Boundary owner | Correct behavior |
|---|---|---|
| unsupported command shape | command converter | skip and report,不 invent execution semantics |
| existing native target | importer | preserve existing Codex file |
| invalid external JSONL | session parser | skip record/session,不 corrupt rollout history |
| cwd no longer exists | session import preparation | 不导入 unusable working context 的 thread |
| stale session ledger | ledger hash check | skip 前重新计算 content hash |
| background plugin failure | import processor | warn,并在 background phase 后仍发送 completion notification |
| config-like import ran | request processor | refresh runtime config,并清理相关 caches |
产品契约不是“所有外部状态都变 native”,而是“supported state 变 native,unsupported state 带解释地留在外面”。
Trace Ledger
| 问题 | 第 19 章答案 |
|---|---|
| 用户请求现在在哪里? | 它可能由 imported native config、imported skills/hooks/MCP entries、background plugin imports,或 imported rollout history 支撑。 |
| 什么数据结构携带它? | migration items、migration details、converted native artifacts、app-server import responses、InitialHistory::Forked、thread metadata patches,以及 content-hash session ledger。 |
| 谁拥有下一步决策? | Detection 报告 candidates;converters 决定 supported native shapes;request processor refresh runtime config 并执行 background imports;import 后由 native loaders 拥有结果。 |
| 必须保持什么不变? | Migration 不能覆盖 native user work,不能盲目继承 trust,不能永久模拟另一个 runtime,也不能把 external history 当作 lossless native replay。 |
| 这里可能怎么失败? | unsupported semantics、disabled/duplicate entries、existing target conflicts、invalid JSONL、missing cwd、stale/invalid ledger data、remote plugin errors,或 source paths 超出 detected import boundary。 |
应用到实践
- 先 detect,再 mutate。 处理 external state 时使用。写 native files 前先返回 typed migration items 与 descriptions。风险:用户无法审计 silent migration。
- 翻译成 native contracts。 处理 commands、hooks、subagents、MCP servers 和 sessions 时使用。只转换 supported shapes。风险:把 foreign runtime assumptions 保留成隐藏行为。
- 记录有损边界。 导入 histories 时使用。保留 provenance 与 ledger data,并显式标出 unsupported blocks。风险:假装 external JSONL 是 native rollout replay。
- 让 idempotency 感知 content。 对可重复 import 使用。hash source content,而不是只看 path。风险:压制 changed sessions 或重复 unchanged sessions。
- Import 后结束 bridge。 对 compatibility code 使用。Conversion 后让 native loaders 拥有 migrated artifacts。风险:让 compatibility branches 扩散进 core turn loop。
接下来
第五部到这里结束:runtime 已能消费外部工具、加载 native extension packages,并把外部状态迁移成 native contracts。第 20 章转向一个 agent 之外的协作:multi-agent threads、graph edges、live status 和 trace reconstruction。
源码地图
| 概念 | 源码锚点 |
|---|---|
| External config model | codex-rs/app-server/src/config/external_agent_config.rs |
| Migration request processor | codex-rs/app-server/src/request_processors/external_agent_config_processor.rs |
| External migration converters | codex-rs/external-agent-migration/src/lib.rs |
| External session parser | codex-rs/external-agent-sessions/src/records.rs |
| External session ledger | codex-rs/external-agent-sessions/src/ledger.rs |
| TUI migration startup | codex-rs/tui/src/external_agent_config_migration_startup.rs |
| Protocol compatibility surface | codex-rs/app-server-protocol/src/protocol/mod.rs |
| Thread store | codex-rs/thread-store/src |