English 书架

第 19 章:外部迁移与向后兼容

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

兼容性通道:展示外部配置迁移、session import、adapter 与旧状态保护
Compatibility lanes 让 Codex 能导入旧形态或外部形态,同时保护 native runtime model 不继承含糊边界。

源码边界: 本章只有在链接到固定 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 artifactNative destinationVerified source surfaceConservative rule
MCP server entriesCodex MCP configbuild_mcp_config_from_external()导入 supported server records,并遵守 merge rules
hooksCodex hook configimport_hooks()只复制可表达的 hook mappings 与 scripts
commandsskills/workflow unitsimport_commands()要求 parseable documents 与 bounded metadata
subagentsnative agent definitionsimport_subagents()要求 stable frontmatter 与 safe target names
pluginsplugin config/storepending 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。

History surface fanout:durable transcript、model projection、UI history、resume reconstruction、fork baseline 与 audit evidence 分流
Imported history 必须说明自己在重建哪一层:transcript evidence、model projection、UI history,还是 resume baseline。

Verified lifecycle 是:

  1. Detection 只在 records 含 cwd、至少一条 user/assistant message,以及 latest timestamp 时总结 candidate sessions。summarize_session() 构建这个 summary。
  2. Import validation canonicalize selected paths,并拒绝不在 detected external projects root 下的 sessions。validate_pending_session_imports() 也会对 selected canonical paths 去重。
  3. prepare_validated_session_imports() 跳过已被 content ledger 记录的 sessions,并只加载 cwd 仍存在的 importable sessions。load_importable_session() 是可见 cwd check。
  4. Request processor 用 InitialHistory::Forked(rollout_items) 启动新的 Codex thread,并在有 title 时 normalize 成 thread metadata。
  5. 成功后,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。

Replay、resume 与 fork path:从 stored history、forked items、rollback state 和 continuation baseline 重建 thread
Session import 的安全点在于 imported items 变成 forked native baseline,而不是外部 runtime 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 designFailure
path onlychanged JSONL content 被永远压制
no ledger每次 migration prompt 都可能重复导入同一 session
path + content hashunchanged 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 aliasesolder clients and stored events隐藏不兼容语义
experimental gatesunstable capabilities让 unstable fields 看起来像永久契约
migration converters来自其他工具的 user workflows永久模拟另一个 runtime
import ledgers幂等 session import压制 changed source content
provenance markersimported 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。

FailureBoundary ownerCorrect behavior
unsupported command shapecommand converterskip and report,不 invent execution semantics
existing native targetimporterpreserve existing Codex file
invalid external JSONLsession parserskip record/session,不 corrupt rollout history
cwd no longer existssession import preparation不导入 unusable working context 的 thread
stale session ledgerledger hash checkskip 前重新计算 content hash
background plugin failureimport processorwarn,并在 background phase 后仍发送 completion notification
config-like import ranrequest processorrefresh 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。

应用到实践

  1. 先 detect,再 mutate。 处理 external state 时使用。写 native files 前先返回 typed migration items 与 descriptions。风险:用户无法审计 silent migration。
  2. 翻译成 native contracts。 处理 commands、hooks、subagents、MCP servers 和 sessions 时使用。只转换 supported shapes。风险:把 foreign runtime assumptions 保留成隐藏行为。
  3. 记录有损边界。 导入 histories 时使用。保留 provenance 与 ledger data,并显式标出 unsupported blocks。风险:假装 external JSONL 是 native rollout replay。
  4. 让 idempotency 感知 content。 对可重复 import 使用。hash source content,而不是只看 path。风险:压制 changed sessions 或重复 unchanged sessions。
  5. 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。