
kkarsyline/codex-app-server-gateway-guide
Developer tools将 Codex App Server 接入现有聊天前端与网关的实践教程,涵盖流式事件、thread 生命周期、refill、多后端路由与 MCP 工具同步。
개요
文档状态:2026-08-22。本文聚焦 Codex;API 与 Claude Code -p 网络上有较多相关教程,这仅讲兼容边界。 本文在 gateway 的 mode 值中使用 codex,对应官方 Codex App Server 集成。claude-p 保留原名,因为它对应真实的 claude -p 执行模式。 这套方案适合已经拥有聊天前端、SSE 网关、会话数据库和自定义工具系统的人。最终效果是:同一前端可以按 conversation 在 API、Claude Code -p、Codex App Server 三条后端之间选择;三条后端共用消息存储、附件、流式 UI 和工具气泡;各 provider 的会话语义、提示词载体与原生事件留在各自 adapter 内。 1. gateway 负责产品语义:conversation、消息树、附件、持久化、统一 SSE、后台任务。 2. provider adapter 负责协议语义:启动、认证、session/thread、工具事件、错误和中断。 3. 工具注册拥有单一真源,再为 CC 与 Codex 渲染各自配置;不要维护两份人工列表。 4. persona、style、记忆检索和 refill 各有独立职责;混成一个巨大 system prompt 会让更新、缓存与会话恢复一起失控。 本文把 refill 定义为“在新建、恢复、分叉或 compact 后,为 Codex thread 回填必要的历史上下文”。gateway 从自己的消息库中按预算取出历史消息摘要、最近几组完整对话和必要状态,组成一个临时 context block,放进下一次 turn/start。refill 只填当前 thread 缺少的部分,不重复整份 transcript,也不替代 Codex 原生的 thread history。 OpenAI 将 App Server 定位为富客户端集成接口,覆盖认证、conversation history、approvals 和 streamed agent events。自动化或 CI 更适合 SDK。官方 App Server 文档 我们选择一个长期运行的 App Server 进程服务多个逻辑 conversation。
README
把 Codex 接进现有聊天前端:App Server 网关适配、三引擎切换与 MCP 工具同步
文档状态:2026-08-22。本文聚焦 Codex;API 与 Claude Code
-p网络上有较多相关教程,这仅讲兼容边界。本文在 gateway 的 mode 值中使用
codex,对应官方 Codex App Server 集成。claude-p保留原名,因为它对应真实的claude -p执行模式。
这套方案适合已经拥有聊天前端、SSE 网关、会话数据库和自定义工具系统的人。最终效果是:同一前端可以按 conversation 在 API、Claude Code -p、Codex App Server 三条后端之间选择;三条后端共用消息存储、附件、流式 UI 和工具气泡;各 provider 的会话语义、提示词载体与原生事件留在各自 adapter 内。
1. 最终架构
flowchart LR
UI[Chat UI] -->|HTTP + SSE| GW[Chat Gateway]
GW --> DB[(Conversation DB)]
GW --> R{conversation.mode}
R -->|api| API[API Provider]
R -->|claude-p| CCP[Claude Code Adapter]
R -->|codex| CXP[Codex App Server Adapter]
CCP --> CC[Claude Code process/session]
CXP --> AS[Persistent Codex App Server]
AS --> T1[Codex thread A]
AS --> T2[Codex thread B]
REG[Canonical Tool Registry] --> RCC[CC renderer/consumer]
REG --> RCX[Codex renderer/consumer]
RCC --> CC
RCX --> AS
MEM[Optional memory service] -->|MCP or bounded injection| GW
MEM -->|MCP| CC
MEM -->|MCP| AS
最重要的边界有四条:
- gateway 负责产品语义:conversation、消息树、附件、持久化、统一 SSE、后台任务。
- provider adapter 负责协议语义:启动、认证、session/thread、工具事件、错误和中断。
- 工具注册拥有单一真源,再为 CC 与 Codex 渲染各自配置;不要维护两份人工列表。
- persona、style、记忆检索和 refill 各有独立职责;混成一个巨大 system prompt 会让更新、缓存与会话恢复一起失控。
本文把 refill 定义为“在新建、恢复、分叉或 compact 后,为 Codex thread 回填必要的历史上下文”。gateway 从自己的消息库中按预算取出历史消息摘要、最近几组完整对话和必要状态,组成一个临时 context block,放进下一次 turn/start。refill 只填当前 thread 缺少的部分,不重复整份 transcript,也不替代 Codex 原生的 thread history。
2. 为什么选 App Server
Codex 有三类常见接入面:
| 接入面 | 适合场景 | 长聊天前端中的局限 |
|---|---|---|
codex exec --json |
CI、脚本、一次性自动化 | 每轮进程与历史管理成本高,深度交互能力有限 |
| Codex SDK | 应用内编程调用、希望使用类型封装 | 很合适;仍需自行定义 gateway 事件与持久化契约 |
codex app-server |
自定义富客户端、完整 thread/turn/event/approval 集成 | 协议面较宽,需要严谨 adapter 与版本测试 |
OpenAI 将 App Server 定位为富客户端集成接口,覆盖认证、conversation history、approvals 和 streamed agent events。自动化或 CI 更适合 SDK。官方 App Server 文档
我们选择一个长期运行的 App Server 进程服务多个逻辑 conversation。这样做可以减少反复启动成本,也便于集中处理认证、模型目录、MCP 初始化与全局通知。每个前端 conversation 仍绑定独立 Codex thread,adapter 必须维护 ownership,严禁把一个 thread id 同时交给两个窗口。
3. 先冻结你自己的网关契约
先让前端只认识一套 provider-neutral contract。一个够用的最小集合如下:
{"event":"start","conversation_id":"conv_demo","session_action":"resume"}
{"event":"session_bound","provider":"codex","session_id":"opaque-thread-id"}
{"event":"thinking_delta","text":"正在检查……","reasoning_source":"summary"}
{"event":"delta","text":"结论是……","phase":"final_answer"}
{"event":"tool_call_start","tool_call_id":"item_1","tool_name":"search","arguments":null,"tool_round":1}
{"event":"tool_call_result","tool_call_id":"item_1","tool_name":"search","arguments":{"q":"demo"},"result":"...","tool_round":1}
{"event":"status","kind":"rate_limit","detail":{}}
{"event":"server_request","request_id":"req_1","request_type":"user_input","payload":{}}
{"event":"done","text":"完整正文","usage":{}}
重要的细节:
session_bound在 native thread 创建或恢复成功后立即发出,gateway 收到后再落库。- tool start 时参数可能尚未完整,UI 应允许
arguments: null,完成事件再回填。 done.text是整轮最终文本,方便断线恢复和一致性校验;前端实时展示仍消费 delta。- reasoning 只展示官方提供的 summary 或明确可见的 reasoning text。不要把隐藏 chain-of-thought 写进产品承诺。
- provider 原生 id 只在 adapter 与数据库绑定层流转;公开日志可使用哈希或业务侧 id。
4. Codex runtime 与认证
4.1 固定版本,保存校验信息
App Server 仍在快速演化。部署时应固定 Codex 版本,并记录:
- CLI 版本;
- 安装包来源;
- artifact SHA-256;
- 该版本生成的 App Server schema;
- adapter 测试所用 fixture 版本。
升级流程应先生成/比较 schema,再跑 adapter parity tests,最后做真实 E2E。生产验证过某一版,不代表 main 分支或半年后的 CLI 会保持所有字段形状。
本文实际工程曾锁定并验证 Codex 0.148.0。当前官方文档已经展示更丰富的 thread 字段和新能力,因此示例使用 opaque thread id,不使用 UUID 正则。若你的固定版本确实只返回 UUID,可以在版本专属 adapter 内收紧校验。当前官方 Python SDK 仍公开 base_instructions / developer_instructions,并在 wire 上映射到 camelCase;这两个字段可在 SDK API reference 与 SDK FAQ 核对。App Server 网页的简短 start 示例并非完整 schema。
4.2 认证目录必须持久化
远程或无头环境可以使用:
codex login --device-auth
codex login status
官方也支持先在有浏览器的机器登录,再复制 ~/.codex/auth.json。该文件包含访问令牌,权限应设为 0600,放进持久卷,绝不能进入 Git、日志、issue 或教程截图。官方认证文档
使用 ChatGPT 登录身份时,启动 App Server 前清掉会偷偷改变 provider 或计费身份的环境变量,例如:
for key in (
"OPENAI_API_KEY",
"CODEX_API_KEY",
"CODEX_ACCESS_TOKEN",
"OPENAI_BASE_URL",
"OPENAI_API_BASE",
):
child_env.pop(key, None)
若你明确选择 API key 模式,则反过来:把凭据放进 secret manager 或受限 env 文件,并在 health/log 中只输出“存在/缺失”,永远不输出值。
5. 启动 App Server 与 initialize 握手
最容易调试的 transport 是 stdio JSONL:
codex app-server --listen stdio://
父进程需要同时读取 stdout 和 stderr。stdout 每行是一条省略 jsonrpc: "2.0" 的 JSON-RPC 消息;stderr 只能当诊断日志,别混进协议 parser。
连接建立后顺序固定:
{"method":"initialize","id":1,"params":{"clientInfo":{"name":"my_gateway","title":"My Gateway","version":"0.1.0"}}}
{"method":"initialized","params":{}}
任何 thread 请求都应等 initialize 成功。父进程需要维护:
- 自增 request id;
request_id -> Future的 pending map;thread_id -> Queue的通知路由;- reader task 与 stderr task;
- App Server epoch;
- 进程退出时对全部 pending future/queue 的失败广播。
精简实现见 examples/codex_app_server_bridge.py。
6. conversation 与 Codex thread 生命周期
数据库至少保存这些字段:
CREATE TABLE codex_window_sessions (
conversation_id TEXT PRIMARY KEY,
codex_thread_id TEXT UNIQUE,
codex_session_id TEXT,
runtime_hash TEXT,
resume_safe INTEGER NOT NULL DEFAULT 1,
generation INTEGER NOT NULL DEFAULT 0,
last_completed_message_id TEXT,
compact_refill_pending INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
updated_at TEXT NOT NULL
);
当前 App Server 还可能返回 thread.sessionId:root thread 通常以自己的 thread id 作为 session tree root,forked thread 会保留 root session id。codex_thread_id 负责具体 conversation ownership;codex_session_id 用于 fork 树审计和跨 thread 关联。固定版本没有该字段时保留 NULL,不要自行推导。
resume_safe 是关键状态位:
- turn 已被 App Server 接受后置为
0; - 收到同一 turn 的
turn/completed并完成持久化后置为1; - 连接在“请求可能已送达、响应未知”的窗口断掉时保持不安全;
- 不安全状态禁止盲目重试,否则可能产生双回复。
6.1 fresh
{"method":"thread/start","id":10,"params":{
"cwd":"/workspace",
"approvalPolicy":"never",
"sandbox":"workspaceWrite",
"personality":"none",
"model":"YOUR_PINNED_MODEL",
"baseInstructions":"",
"developerInstructions":""
}}
字段名和 sandbox 枚举要以你的固定版本 schema 为准。当前 SDK 使用 snake_case Python 参数并在 wire 上转换为 camelCase;旧版与新版的 sandbox 结构也可能不同。
6.2 warm
同一 App Server epoch 内,conversation 已绑定 thread,且 runtime fingerprint 未变化时,直接在现有 thread 上 turn/start。这一条不重复执行完整 refill。
6.3 cold resume
App Server 重启、逻辑 session 被 idle reaper 回收,或 adapter 重新加载时:
{"method":"thread/resume","id":11,"params":{
"threadId":"opaque-thread-id",
"baseInstructions":"",
"developerInstructions":""
}}
恢复成功后返回的 thread id 必须与目标一致。恢复时配置覆盖的首轮生效语义曾在部分版本出现过问题;生产系统应有“改变 style 后立即 cold resume”的回归用例,不能只测试 fresh。
6.4 fork
{"method":"thread/fork","id":12,"params":{
"threadId":"source-thread-id",
"baseInstructions":"",
"developerInstructions":""
}}
新 thread id 必须与 source 不同。fork 期间锁住 source ownership 与 target slot,避免 source 正在前进时从错误 tip 分叉。若产品允许“从任意历史消息分支”,数据库的 source tip 和 native transcript 必须精确对齐;对齐证明不足时退回 fresh + frozen hydration。
6.5 runtime fingerprint
将这些值稳定序列化并哈希:
model
+ reasoning effort
+ identity prompt hash
+ output style hash
+ MCP registry digest
+ provider-specific config version
fingerprint 变化时轮换逻辑 session,随后 resume/fork/fresh。继续把旧 warm thread 当成同一 runtime,会出现“前端已经改了 persona,模型还在读旧内容”的幽灵状态。
7. persona、style 与上下文窗口
推荐把提示词分为四层:
| 层 | 内容 | Codex 载体 |
|---|---|---|
| Identity | persona、用户关系背景、长期身份事实 | baseInstructions |
| Style | 语气、格式、输出偏好 | developerInstructions |
| Turn context | 当前时间、附件说明、动态状态 | 当前 turn/start.input |
| Refill | 历史消息摘要、最近完整 exchanges、分支上下文 | fresh/resume/fork/compact 时的受限输入块 |
personality 建议设为 none,由你自己的 style 完成输出控制。若希望保留 Codex 内置 personality,就把它纳入 runtime fingerprint,并做冲突测试。baseInstructions / developerInstructions 属于官方协议/SDK 字段;“identity 放前者、style 放后者”是本文 adapter 的映射策略,仍需对目标版本生成的 schema 与 fresh/resume/fork 首轮行为做测试。
7.1 为什么要把 style 单独拿出来
- 修改文风无需重建 identity 文本;
- 日志能分别显示 persona hash 和 style hash;
- API、CC、Codex 可以共用同一个前端
prompt_blocks.style,adapter 再映射到各自载体; - provider 迁移时,关系身份和输出格式不会互相污染。
7.2 “start hook/启动提示”怎么处理
若你的项目在 thread 启动时加入自定义提示模板,把它当成一段可替换的 prompt resource:
- 不把模板硬编码进 adapter 状态机;
- 为有模板/无模板各跑一套功能回归;
- 保留工具调用、MCP、compact、resume、approval 的 canary;
- 模板失效时只替换资源层,不改协议层。
8. turn/start 与输入格式
{"method":"turn/start","id":30,"params":{
"threadId":"opaque-thread-id",
"input":[
{"type":"text","text":"用户消息"},
{"type":"localImage","path":"/workspace/uploads/image.png"}
],
"effort":"high",
"summary":"detailed"
}}
输入 item 的类型随协议版本变化。把附件转换集中在一个函数里,并对未知类型 fail closed。远程前端上传文件后,seat 只能访问受控共享卷内的副本;不要接受用户直接传宿主绝对路径。
refill context 可以垫在当前用户消息前:
...
...
full refill 只用于 fresh 或无法安全恢复的情况。cold resume、fork 和 compact 后使用更小的 light refill;warm turn 不执行 refill。
9. App Server 事件翻译
App Server 的 item/completed 应作为该 item 的权威完成态;delta 用于实时 UI。当前官方事件说明见 App Server events。
| App Server 事件/Item | Gateway 事件 | 处理要点 |
|---|---|---|
item/agentMessage/delta |
delta |
同 item 按顺序拼接 |
item/reasoning/summaryTextDelta |
thinking_delta |
稳定的可读 reasoning summary 通路 |
item/reasoning/textDelta |
thinking_delta |
只在模型支持时出现,不作产品保证 |
item/started + mcpToolCall |
tool_call_start |
记录 item id、tool、server、round |
item/completed + tool item |
tool_call_result |
最终参数与结果从 completed item 回填 |
item/commandExecution/outputDelta |
status/tool_progress |
按 itemId 顺序拼接;适合可折叠进度,不必写入正文 |
contextCompaction item completed |
status/compact_boundary |
给下一轮设置 refill pending |
thread/tokenUsage/updated |
内部 usage snapshot | done 时附上最终快照 |
account/rateLimits/updated |
status/rate_limit |
只持久化安全字段 |
turn/completed |
done 或 error |
核对 turn id、status、error |
9.1 别把 reasoning summary 写成“完整 CoT”
官方说明 summaryTextDelta 提供 readable reasoning summaries;textDelta 只在模型支持时出现。教程、UI 和 marketing 都应使用“reasoning summary/思考摘要”这一表述。隐藏推理链没有稳定提取契约。
9.2 server request 是双向协议
App Server 可能向 client 发起 approval、user input 或 MCP elicitation 请求。adapter 要区分:
- 可由固定策略自动回答的请求;
- 必须转发前端、等待用户输入的请求;
- 该产品明确不支持、应返回 JSON-RPC error 的请求。
转发前端时记录 request 所属 thread/conversation。用户回填时再次校验 ownership,防止 A 窗口替 B 窗口批准操作。
10. 客户端断线之后继续排空
移动端切后台、网络抖动和用户离开页面都可能切断 SSE。若 gateway 的 adapter HTTP client 随前端断线一起关闭,native agent 仍可能继续工作,后半段正文和工具结果却无人读取。
稳定做法:
- gateway 创建独立 producer task 读取 adapter;
- producer 在收到 start 后插入一条空 assistant placeholder;
- 每隔几秒原地覆写已累计正文、thinking 与 tool log;
- 前端 SSE 只是 producer 的订阅者;
- 订阅者断开时 producer 继续读到
turn/completed; - 最终一次原子写回并标记 session resume-safe。
adapter 自己也要有 detached drain:若 gateway 到 adapter 的 HTTP 连接中断,而 turn/start 已送达,adapter 继续消费该 thread queue 到 terminal event,再释放 lifecycle lock。这样下一条消息不会与仍在运行的 turn 叠到同一 thread。
11. 三引擎自由切换
conversation 创建时固定 mode:
api | claude-p | codex
已有 conversation 的 mode 不允许被普通 chat 请求偷偷覆盖。若用户明确切换 provider,建议创建新 conversation 或执行显式 migration 流程;两种 native transcript 不能假装拥有同一条隐形历史。
路由骨架见 examples/provider_router.py。三条路径共享:
- conversation/message/attachment 表;
- parent/active-leaf 规则;
- 前端 SSE event contract;
- placeholder 与 detached persistence;
- memory preflight 的接口;
- 历史消息摘要生成器;
- tool UI 与 tool audit schema。
各自保留:
| 能力 | API | Claude Code -p |
Codex App Server |
|---|---|---|---|
| 上游形态 | HTTP request/stream | CLI process + stream-json | 长期 App Server + JSON-RPC |
| 会话原语 | gateway 重放 messages | session resume/fork | thread start/resume/fork |
| Identity | system/developer message | system prompt file | baseInstructions |
| Style | system block | output style/append prompt | developerInstructions |
| 工具 | API tool schema + middleware executor | 内建工具 + MCP | App Server items + MCP |
| Context compact | gateway 历史消息摘要/裁剪 | CC 原生 compact + refill | contextCompaction + refill |
| 中断 | 取消 HTTP/background request | control request/process signal | turn/interrupt |
| 分支 | DB 截断后重放 | 依版本与 session 能力 | thread/fork 或 fresh hydration |
| 计费/额度 | token/cost | 订阅侧 usage | 订阅或 API 身份对应 usage |
前端只显示 provider-neutral 名称与能力开关。某 provider 暂不支持的能力应在 capability response 中明确关闭,别靠失败后猜。
11.1 CC 与 Codex 的原语对照
| 产品层语义 | Claude Code -p 常见原语 |
Codex App Server 原语 |
|---|---|---|
| 新会话 | 启动 claude -p / 新 session |
thread/start |
| 冷恢复 | --resume |
thread/resume {threadId} |
| 原生分叉 | --resume ... --fork-session |
thread/fork {threadId} |
| 发一轮消息 | 写 stream-json stdin | turn/start |
| 中断 | control request / process control | turn/interrupt {threadId, turnId} |
| 正文增量 | content block text delta | item/agentMessage/delta |
| 思考显示 | provider thinking/summary event | item/reasoning/summaryTextDelta;可选 textDelta |
| 工具开始 | tool_use content block start |
tool item 的 item/started |
| 工具完成 | replayed tool result/result event | tool item 的 item/completed |
| Turn 完成 | result event | turn/completed |
| Context 压缩 | CC compact/system event | completed contextCompaction item |
| Identity | --system-prompt-file 等 |
baseInstructions |
| Output style | Output Style / append prompt | developerInstructions |
| MCP 配置 | JSON/CLI MCP config | config.toml [mcp_servers.*] |
这里对齐的是产品行为,字段与事件原样照搬会出错。adapter 负责把两套 native 原语翻译成同一 gateway contract。
12. MCP 工具注册如何同步给 CC 与 Codex
Codex 将 MCP 配置放在 config.toml 的 [mcp_servers.] 下;CLI、桌面端和 IDE 扩展共享该配置。官方 MCP 文档
要让 CC 与 Codex 的工具注册完全同步,核心是 canonical registry:
flowchart TD
A[App manifest / admin action] --> B[Canonical registry transaction]
B --> C[Validate names, URL, transport, health]
C --> D1[Render CC fragment]
C --> D2[Render Codex config block]
D1 --> E1[CC consumer verify]
D2 --> E2[Codex consumer verify]
E1 --> F[ACK: room + digest + heartbeat + ready]
E2 --> F
F --> G[Commit registry revision]
每个 revision 建议包含:
{
"revision": 42,
"digest": "sha256-of-canonical-json",
"servers": {
"demo": {
"transport": "http",
"url_by_room": {
"cc": "http://cc-tools.example:8000/mcp",
"codex": "http://codex-tools.example:8000/mcp"
}
}
}
}
“同一工具”指 manifest、schema 与 revision 相同;两个 room 仍各用自己的 hostname/容器实例,避免跨房串流量或身份。
同一 canonical server 可渲染成两种 provider 配置。CC 的最小脱敏 fragment 例如:
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://memory.example:8000/mcp"
}
}
}
启动时以 claude -p --mcp-config /path/to/generated-mcp.json ... 加载;registry digest 改变后轮换受影响的 CC session。Codex renderer 则把同一条 canonical record 写成:
[mcp_servers.memory]
url = "http://memory.example:8000/mcp"
enabled = true
| Canonical 字段 | CC renderer | Codex renderer |
|---|---|---|
| stable server name | mcpServers. |
[mcp_servers.] |
| room-local URL | url |
url |
| stdio command/args | command / args |
command / args |
| secret header/token | 版本支持的 env/header 配置 | bearer_token_env_var / env_http_headers |
| enabled/required/timeout | 按 CC 目标版本渲染或由 wrapper 强制 | Codex config 对应键 |
| registry digest | session runtime fingerprint | App Server transition/consumer ACK |
两边原生配置格式会变化,canonical registry 只存 provider-neutral 意图;renderer 和验证器随目标版本更新。
12.1 consumer 应完成的验证
- fragment 名称与 app id 合法;
- fragment 声明的 room 与当前 consumer 一致;
- URL hostname 只能属于当前 room allowlist;
- TCP/HTTP 可达;
- 原生 CLI 能列出预期 MCP server;
- 生成配置使用原子替换;
- 写入带 heartbeat 的 ACK,禁止伪造静态
ready=true。
12.2 Codex 动态变更为何常要轮换
修改 config.toml 并不保证运行中的 App Server、已有 thread、代理 NO_PROXY 和 MCP client 都立刻刷新。保守流程如下:
关闭新 turn 入口
→ 等待/排空 active turns
→ 写入候选配置
→ 用 codex mcp list 或 schema-aware client 验证
→ 轮换逻辑 sessions
→ 重启 App Server
→ 等待 initialize + MCP ready
→ 写 ACK
→ 重新开放 turn 入口
任一步失败都保留 ready=false 与明确错误,并恢复上一 committed revision。别让聊天继续进入一个工具清单半新半旧的进程。
12.3 legacy SSE MCP
一些旧 MCP server 只实现 legacy SSE,Codex 侧常用 streamable HTTP。可放一个窄 bridge:
- 对 Codex 暴露 loopback streamable-HTTP endpoint;
- bridge 连接旧 SSE endpoint;
- session id、消息方向、超时和关闭语义逐项转译;
- 限制目标 allowlist,禁止把它做成任意 URL proxy。
若上游已支持当前 MCP transport,直接升级上游更省心。
13. 可选:路由中转与进程隔离
Codex App Server 不要求 Docker。若机器本身拥有合适且稳定的网络出口,可以直接以本地进程、systemd 或 launchd 服务运行 adapter 与 App Server,只需持久化 CODEX_HOME、限制工作目录权限并做好进程重启。
我们的部署位于远程 VPS,需要把 Codex 的登录、模型请求和 MCP 流量经过指定路由中转,同时禁止意外回落到 VPS 默认出口,所以增加了独立 gateway、隔离网络和 fail-closed probe。Docker 只是实现这一边界的一种方式;VM、network namespace 或 Kubernetes 也能完成同样目标。没有路由中转需求时,可以跳过本节。
13.1 Docker 路由中转示例
以下是可解析结构的脱敏片段。外部 bridge 名、代理端口和内部 service hostname 需按你的环境替换;不使用代理时删除 HTTP_PROXY、HTTPS_PROXY 和相关 gateway 配置:
services:
codex-seat:
image: your-codex-seat:PINNED
command: ["python3", "/opt/example-adapter/codex_app_server_adapter.py"]
restart: unless-stopped
working_dir: /srv/example-codex/workspace
environment:
HOME: /srv/example-codex/home
CODEX_HOME: /srv/example-codex/config
CODEX_BIN: /opt/example-codex/bin/codex
HTTP_PROXY: http://egress.example:7890
HTTPS_PROXY: http://egress.example:7890
NO_PROXY: 127.0.0.1,localhost,egress.example,tools.example,memory.example
volumes:
- example_codex_home:/srv/example-codex/home
- example_codex_config:/srv/example-codex/config
- example_workspace:/srv/example-codex/workspace
cap_drop: ["ALL"]
security_opt: ["no-new-privileges:true"]
networks: [agent_internal, gateway_bridge, memory_bridge]
networks:
agent_internal:
internal: true
gateway_bridge:
external: true
name: REPLACE_WITH_GATEWAY_BRIDGE
memory_bridge:
external: true
name: REPLACE_WITH_MEMORY_BRIDGE
volumes:
example_codex_home: {}
example_codex_config: {}
example_workspace: {}
13.2 使用路由中转时的检查项
- Codex 进程没有绕过中转的默认外网回退;
- DNS、IPv4、IPv6 从进程所在的隔离边界归因;
- gateway health 只证明代理可用,额外 probe 还要证明 direct bypass 失败;
- 内部 MCP、gateway 和记忆服务地址加入
NO_PROXY或等价 bypass; - Docker 方案避免使用 host network,并将 auth/config/workspace 分卷;
- health 同时检查 App Server、认证状态和 MCP consumer freshness;
- 网络/代理变化先做只读检查和回滚设计。
14. 上下文与记忆系统只需要一个通用接口
每个人的记忆库、历史消息摘要、检索和归档设计都不同,Codex adapter 无需理解内部 bucket、embedding、画像或评分算法。它只消费一段带预算的 context,或把记忆服务作为 MCP 挂给 Codex。
Codex 侧的处理顺序可以固定为:
fresh / 无法安全恢复 → full refill:带硬预算的 recovery context
cold resume / fork → light refill:历史消息摘要和最近缺口
warm → 不重复补历史
contextCompaction 完成 → 下一轮加入一次 post-compact light refill
普通记忆检索 → gateway preflight 注入,或由 Codex 主动调用 memory MCP
记忆接口只需提供 recall(query, budget)、可选的异步 remember(turn) 和 health()。返回内容应有字符预算、来源标记、超时和失败降级。
15. 流式 Markdown 前端
SSE adapter 只负责提供增量文本;SwiftUI 端可以用 Microsoft SwiftStreamingMarkdown 渲染逐步增长的完整 Markdown snapshot。它采用 MIT License。
一个常见实现是:
delta 到达
→ append 到 message buffer
→ 合并为“目前为止的完整 Markdown”
→ 推送给 StreamedMarkdownSource
→ tool/thinking 使用独立 UI block,避免塞进 Markdown 正文
不要每个字符都触发数据库写入。UI 可以高频刷新,gateway 持久化按时间窗口节流,并在 terminal event 强制 final flush。
16. 测试矩阵
fake App Server 可以覆盖绝大多数 adapter 状态机,真实 Codex E2E 负责证明协议 fixture 没有空转。
16.1 单元/协议测试
- initialize 之前拒绝 thread 请求;
- fresh 返回新 thread 并绑定;
- resume 必须返回目标 thread;
- fork 必须返回新 thread;
- thread ownership 唯一;
- agent/reasoning/tool delta 与 completed fallback;
- usage、rate limit、auth required;
- server request resolve 校验 conversation ownership;
- interrupt;
- 客户端断线后 detached drain;
- ghost turn 与迟到 terminal event 不得串进新 turn;
- App Server epoch 改变后 warm session 自动转 cold resume;
- compact pending 在消息被接受后清除,新 compact boundary 仍能留下;
- dynamic MCP config 写入失败时维持旧 committed revision。
16.2 真实 E2E
- fresh 对话,验证 persona/style 与普通 streaming;
- 调一个只读 MCP 工具,核对 start/result/UI/落库;
- 重启 App Server 后 cold resume;
- 相同 fingerprint 的 warm turn 不执行 refill;
- 修改 effort/style 后轮换且首轮生效;
- fork 后 child id 与 source 不同;
- 触发或模拟 compact,下一轮只吃 light refill;
- 前端中途断开,后台仍完整落库;
- registry apply → 新工具可用 → down → 工具消失;
- 若使用路由中转,从 Codex 进程所在隔离边界验证出口、DNS 与旁路策略。
17. 踩坑
1:每轮启动一个 App Server
能回字,长期会话、MCP 初始化、认证诊断和并发管理会变得很难看。优先维持一个 App Server runtime,再在其上管理逻辑 thread。
2:按 conversation id 猜 thread id
thread id 由 Codex 返回。数据库显式绑定,adapter 验证唯一 ownership。
3:请求发送后发生网络错误就自动重试
这段状态可能已经被模型接受。先标记 ambiguous/unsafe,恢复并查询 terminal state,确认安全后再决定。
4:把 delta 当最终事实
delta 可缺失、重复或在断线时丢一截。item/completed 和 turn/completed 才是完成态权威来源。
5:MCP 配置文件改了,运行态仍旧
同时考虑 App Server 进程、thread、环境变量、代理 bypass 与 consumer ACK。把变更做成带锁、可回滚的 transition。
6:把 API context assembly 原封不动塞进 native agent 每一轮
native thread 已持有历史。每轮重复整段 summary/persona 会浪费上下文并强化旧信息。使用 fresh/full、resume/light、warm/none、post-compact/light 的状态机。
7:升级 Codex 时不核对 schema
App Server 协议仍在演进。升级前先 diff 新旧 schema,再用保存的 JSONL fixture 重放 start、resume、fork、tool、compact 和 error 路径;字段变化应先在 adapter 兼容层消化。
18. 推荐的实现顺序
- 冻结 gateway SSE 与 persistence contract。
- 写 fake App Server,先完成 initialize/thread/turn 最小闭环。
- 接 agent text delta 与 terminal completion。
- 加 thread ownership、resume-safe 与 lifecycle lock。
- 加 reasoning、tool、usage、rate limit、interrupt。
- 加 detached drain 与 gateway background producer。
- 加 persona/style/runtime fingerprint。
- 加 fresh/resume/fork/compact refill。
- 接静态 MCP,再做 canonical registry 和动态 transition。
- 最后处理网络隔离、真实 E2E、可重复验收与回滚。
19. 官方资料
- Codex App Server
- Codex SDK
- Codex MCP
- Codex authentication
- Codex configuration reference
- Codex hooks
- OpenAI Codex source
License
本仓库的原创文档、图表与示例代码采用 MIT License。
文中涉及的第三方项目及依赖保留各自许可证,详见 ATTRIBUTION.md。
설치
This server does not publish a one-line install command.
Open the repository installation guide설정
{
"mcpServers": {
"memory": {
"type": "http",
"url": "http://memory.example:8000/mcp"
}
}
}