codex cli 源码教程 | 第四篇:App Server 为什么是架构中枢

发布时间:2026/8/1 5:30:38
codex cli 源码教程 | 第四篇:App Server 为什么是架构中枢 上一篇分析了codex原生二进制的三层分发npm 启动器选择平台二进制arg0 将同一个程序复用为辅助工具Clap 与cli_main再将用户命令路由到不同模块。当 TUI 或 Exec 分支被选中后它们没有直接围绕codex-core各写一套会话逻辑而是继续收敛到 App Server。App Server 经常被误解为“提供给 VS Code 的一个附加服务”。从当前源码看它已经是 Codex 各种客户端之间的统一架构边界TUI 默认使用进程内 App Server。Exec 使用进程内 App Server。本地 Daemon 使用 Unix Socket App Server。IDE 和外部客户端可以使用 stdio 或 WebSocket。远程客户端仍使用同一套 Thread、Turn、Item 协议。本篇不罗列所有 API而是分析 App Server 如何组织传输、连接、请求、事件、背压和关闭。本篇目标阅读完成后你应该能够解释 App Server 为什么不是简单的 Core HTTP Wrapper。说清 Transport、Processor 和 Outbound Router 的职责。描述一个连接从打开、初始化到关闭的完整生命周期。跟踪一次 JSON-RPC 请求从传输层到具体 Request Processor。理解有界队列、过载错误和慢连接断开策略。解释进程内客户端为什么仍保留 App Server 协议语义。理解请求串行化、连接 Gate 与优雅关闭如何协同。1. 如果客户端直接调用 Core 会怎样假设没有 App ServerTUI、Exec、IDE 和 SDK 都直接依赖codex-core。每个客户端都需要自行处理Thread 创建、恢复和分叉。Turn 启动、中断和状态更新。Core Event 到 UI Event 的转换。审批请求与用户响应。配置加载和 Feature Negotiation。会话订阅和取消订阅。客户端断开后的资源清理。流式事件背压。Core 类型升级带来的兼容变化。结果会出现多套相似但不完全一致的会话实现。App Server 引入一个稳定边界Client Surface | v Thread / Turn / Item Protocol | v App Server | v Core ThreadManager / CodexThread客户端只依赖协议不需要理解 Core 内部对象生命周期。Core 可以继续重构只要 App Server Protocol 保持兼容。因此App Server 的核心价值不是“远程访问”而是“统一产品语义”。2. 它不是传统单向 HTTP 服务codex-rs/app-server/README.md 将协议描述为省略jsonrpc:2.0字段的双向 JSON-RPC。双向意味着双方都可以发起 RequestClient 发起 Request例如initializethread/startthread/resumeturn/startturn/interruptmodel/listskills/listServer 返回 Response 或 Error。Server 发起 Request例如命令执行审批。文件修改审批。用户输入请求。权限提升请求。MCP Elicitation。Client 必须返回 Response 或 Error否则正在运行的 Turn 可能一直等待。Server 主动发送 Notification例如thread/startedturn/starteditem/starteditem/agentMessage/deltaitem/completedturn/completedNotification 不需要响应主要用于推送流式状态。因此客户端连接不能采用“一次 HTTP 请求得到一次完整响应”的思路。它必须持续读取消息并同时维护Client Request ID。Server Request ID。Thread Subscription。流式 Notification。连接状态。3. 三个协议层核心对象App Server 对外使用 Thread、Turn、Item 描述 Agent 交互。3.1 ThreadThread 是可持续、可恢复、可分叉的会话资源。客户端可以thread/startthread/resumethread/forkthread/readthread/listthread/archivethread/unsubscribe3.2 TurnTurn 是 Thread 上的一次任务。客户端通过turn/start提交输入也可以turn/steerturn/interruptreview/start3.3 ItemItem 是 Turn 中的结构化输入和输出例如用户消息。Agent 消息。Reasoning。Shell 命令。文件修改。MCP 调用。Item 的 Started、Delta 和 Completed Notification 形成客户端看到的流式事件。Thread、Turn、Item 的字段和 API 将在下一篇详细展开。本篇先关注它们如何经过 App Server Runtime。4. App Server 的整体分层可以将运行时拆成五层Client | v Transport | v TransportEvent Queue | v MessageProcessor | v Request Processor / Core | v OutgoingEnvelope - Outbound Router - Writer对应源码层主要文件Runtime 编排app-server/src/lib.rsTransport 实现app-server-transport/src/transport连接出站状态app-server/src/transport.rs协议分发app-server/src/message_processor.rs出站消息app-server/src/outgoing_message.rs具体 APIapp-server/src/request_processors协议类型app-server-protocol这个分层有一个重要特点慢网络写入不会直接阻塞 Request Processor。5. Transport 只负责连接和字节Transport 独立在codex-app-server-transportcrate 中。它支持Transport用途消息边界stdio单客户端进程集成一行一个 JSONWebSocket实验性网络客户端一个文本 Frame 一个消息Unix Socket本地 Daemon 控制面Unix Socket 上的 WebSocketRemote Control远程控制连接内部远程连接Off禁用本地监听无本地连接所有具体 Transport 最终只向上层产生三类统一事件pubenumTransportEvent{ConnectionOpened{/* ... */},ConnectionClosed{/* ... */},IncomingMessage{/* ... */},}这意味着 Message Processor 不需要知道消息来自 stdin、TCP 还是 Unix Socket。5.1 ConnectionOpened打开连接时Transport 提交connection_idorigin该连接的 Writer Channel可选 Disconnect TokenConnectionId由进程级 Atomic Counter 生成。5.2 IncomingMessageTransport 将原始文本反序列化成RequestResponseNotificationError再放入统一事件队列。5.3 ConnectionClosedReader EOF、WebSocket Close、网络错误或主动 Disconnect 都会转化成关闭事件。上层据此清理未完成 RPC。文件监听。命令执行 Session。独立进程。Thread Subscription。6. 四种 Transport 的关键差异6.1 stdiostdio.rs 创建两个 Taskstdin Reader。stdout Writer。Reader 使用lines()按行读取 JSON。Writer 将每个出站消息序列化为 JSON再追加换行。stdio 只有一个连接也没有主动 Disconnect Token。stdin EOF 后Reader 发送ConnectionClosed作为单客户端模式最后一个连接关闭会让 App Server 退出。这也是为什么 stdout 绝不能混入日志。6.2 WebSocketwebsocket.rs 使用 Axum 监听 TCP/readyzListener 已就绪。/healthz基础健康检查。其他路径尝试 WebSocket Upgrade。所有携带OriginHeader 的请求都会被拒绝降低浏览器跨站访问本地 Listener 的风险。非 Loopback 地址如果没有配置认证Server 会拒绝启动。源码支持 Capability Token 和 Signed Bearer Token 等认证策略。WebSocket 目前仍被文档标记为实验性、不受支持的生产接口。6.3 Unix SocketUnix Socket 主要服务本地 App Server Daemon。默认路径为CODEX_HOME/app-server-control/app-server-control.sock它仍使用 WebSocket HTTP Upgrade 和 Frame不是自定义裸 JSON 协议。启动时会获取 Startup Lock 并准备 Socket Path避免多个 Daemon 同时覆盖同一控制 Socket。6.4 Off--listen off不启动本地 Transport。它通常需要 Remote Control 已启用否则 App Server 没有任何可用连接入口启动会返回错误。7. Runtime 启动时做了什么入口是run_main_with_transport_options它不只是启动一个 Listener而是完成整个 App Server Composition Root。主要步骤如下。7.1 创建有界 Channel运行时首先创建TransportEventQueue。OutgoingEnvelopeQueue。OutboundControlEventQueue。默认容量来自CHANNEL_CAPACITY 128有界 Channel 防止客户端或模型事件无限积压内存。7.2 加载基础配置运行时解析-cOverrides并创建Environment Manager。Config Manager。Thread Config Loader。Cloud Config Bundle Loader。严格模式下配置错误会阻止启动非严格模式下可以回退到默认配置同时生成 Config Warning。7.3 初始化状态与日志启动过程还会初始化 SQLite State。尝试备份损坏数据库并重新开始。执行 Personality Migration。检查 Exec Policy。收集 Project Config Warning。初始化 OTEL。初始化 Feedback 和 SQLite Log Layer。这些 Warning 会在客户端成功初始化后发送而不是在连接还未建立时丢失。7.4 启动 Transport根据AppServerTransport启动 stdio、Unix Socket、WebSocket 或禁用本地监听。7.5 构造 MessageProcessor最后创建共享MessageProcessor并启动 Processor Loop 和 Outbound Loop。8. 为什么必须拆成 Processor 与 Outbound 两个循环网络写入可能很慢客户端暂停读取。网络拥塞。WebSocket Writer 阻塞。stdout 下游进程不消费。如果 Request Processor 直接执行网络写入一个慢客户端可能阻塞所有请求。当前实现使用两个 TaskProcessor Loop - 处理连接事件 - 解析和分发请求 - 维护连接 Session - 产生 OutgoingEnvelope Outbound Loop - 维护 ConnectionId - Writer - 将消息路由到一个连接 - 广播消息 - 处理慢连接两个循环通过OutgoingEnvelopeOutboundControlEvent通信不共享可变的连接 HashMap。这降低了锁竞争也让慢写入与协议处理解耦。9. Outbound Control PlaneProcessor Loop 在连接变化时向 Outbound Loop 发送Opened Closed DisconnectAllOpened注册Writer Channel。Disconnect Token。Initialized Flag。Experimental API Flag。Notification Opt-out Set。Closed从 Outbound Connection Map 移除连接。DisconnectAll优雅重启结束时主动断开所有可断开的连接。这些控制事件使用tokio::select!的 Biased 分支优先处理确保连接关闭或重启不会长期排在普通出站消息之后。10. 每个连接都有独立 Session State新连接会创建ConnectionState其中包含ConnectionSessionStateOutbound Initialized FlagExperimental API FlagNotification Opt-out SetConnection Session 保存是否已经初始化。Client Name。Client Version。Experimental API 能力。Request Attestation 能力。OpenAI Form Elicitation 能力。需要屏蔽的 Notification Method。Connection RPC Gate。这意味着能力协商是连接级的不是简单的全进程 Boolean。11. Initialize 是连接状态机的入口客户端连接后第一条业务 Request 应该是{method:initialize,id:0,params:{clientInfo:{name:my_client,title:My Client,version:1.0.0}}}随后客户端发送initializedNotification。初始化处理位于initialize_processor.rs11.1 未初始化请求除initialize之外的 Request 会进入dispatch_initialized_client_request如果连接尚未初始化返回Not initialized11.2 重复初始化ConnectionSessionState使用OnceLock保存初始化结果。第二次initialize会返回Already initialized11.3 Initialize Response成功响应包含User Agent。Codex Home。Platform Family。Platform OS。clientInfo.name还会参与上游 User Agent、Analytics 与 Compliance 标识因此不能随意伪造为其他官方客户端。11.4 初始化后的通知连接状态提交后Server 会先向该连接发送Config Warning。Remote Control Status。其他初始化相关状态。然后才将 Outbound Initialized Flag 设为true。这样普通 Broadcast 不会在初始化通知之前抢先到达。12. Connection Capability 如何影响出站消息Outbound Router 会根据连接能力过滤消息。12.1 Experimental API如果 Notification 只属于 Experimental API而连接没有在 Initialize 中启用 Experimental Capability该消息不会发送给它。某些 Server Request 还会在发送前移除 Experimental Field。12.2 Notification Opt-out客户端可以在 Initialize 中传入optOutNotificationMethods匹配采用完整 Method Name不支持通配符。不支持前缀匹配。未知 Method 会被接受并忽略。12.3 Broadcast 只发给已初始化连接全局 Notification 广播时会先筛选连接已初始化。没有 Opt-out。Experimental Capability 允许。未完成握手的连接不会收到普通业务事件。13. MessageProcessor 是业务组合入口message_processor.rs 并不直接实现所有 API。它组合了大量专用 ProcessorInitialize。Thread。Turn。Command Exec。Process Exec。File System。Config。Account。App。Catalog。Environment。Marketplace。MCP。Plugin。Search。Feedback。Git。Windows Sandbox。它还构造ThreadManagerThreadStateManagerGoalServiceSkillsWatcherModelsRefreshWorkerThread Extensions因此MessageProcessor 是 App Server 到 Core 和其他领域服务的 Composition Root。新增 API 时合理做法通常是在 Protocol Crate 定义类型。在独立 Request Processor 实现业务。在 MessageProcessor 中注入依赖并路由。而不是继续把全部逻辑写进match ClientRequest。14. 一次 Request 的完整路径以thread/start为例消息大致经过以下步骤Client 通过 stdio、WebSocket 或内存 Channel 发送 Request。Transport 解析为JSONRPCMessage::Request。Transport 产生TransportEvent::IncomingMessage。Processor Loop 找到对应ConnectionState。MessageProcessor::process_request创建 Request Span。JSON Request 被反序列化为类型化ClientRequest。Server 验证连接已经 Initialize。Server 检查 Experimental API Requirement。Request 根据 Serialization Scope 入队或并发执行。ThreadRequestProcessor调用ThreadManager创建 Core Thread。Processor 通过OutgoingMessageSender产生 Response 和 Notification。Outbound Router 将消息发送给目标连接或所有订阅连接。简化调用链bytes/frame - JSONRPCMessage - TransportEvent - process_request - ClientRequest - serialization queue - request processor - core/service - OutgoingEnvelope - connection writer这个路径是后续阅读任何 App Server API 的通用模板。15. 为什么请求不能全部并发执行有些请求彼此独立可以直接并发读取模型列表。读取配置。查询状态。另一些请求操作同一资源必须保持顺序同一 Thread 的状态变更。同一 Process Handle 的写入和终止。同一 FS Watch ID。同一 MCP OAuth Server。某些全局配置写入。实现位于request_serialization.rs各 Request 的 Scope 声明位于app-server-protocol/src/protocol/common.rs15.1 Serialization KeyRequest 可以声明一个 Scope再转换为 KeyGlobal。Thread ID。Thread Path。Command Exec Process。Process Handle。Fuzzy Search Session。FS Watch。MCP OAuth Server。15.2 Exclusive同一个 Key 的 Exclusive Request 按 FIFO 顺序执行。15.3 Shared Read队首如果是 Shared Read连续的 Shared Read 可以并发执行后面的 Exclusive 仍需等待。15.4 不同 Key不同 Key 使用不同 Queue可以并行执行。这比“所有请求全局加一把锁”具有更好的并发度也避免同一资源上的操作乱序。16. Connection RPC Gate 解决什么问题连接断开时可能还有 Handler 正在执行或已经排在 Serialization Queue 中。connection_rpc_gate.rs 为每个连接维护是否继续接受新 Handler。当前运行中的 Task Token。关闭 Gateclose()禁止后续 Handler 启动。允许已经开始的 Handler 继续运行。排空 Gateshutdown()先关闭。再等待已开始 Handler 全部完成。连接清理有 30 秒超时避免异常 Handler 让资源永远无法释放。Serialization Queue 中尚未获得 Gate Token 的请求会在 Gate 关闭后直接跳过不再访问已经断开的客户端状态。17. 出站消息如何路由OutgoingEnvelope只有两种形式ToConnection Broadcast17.1 ToConnection用于Request Response。Request Error。连接专属 Server Request。初始化通知。17.2 Broadcast用于全局 Notification但只会发送给已初始化且未 Opt-out 的连接。17.3 Thread Scoped SenderThread Event 不应广播给所有客户端。ThreadScopedOutgoingMessageSender持有Thread ID。订阅该 Thread 的 Connection ID 列表。它只向订阅连接发送Thread Notification。审批 Request。Turn Event。这使多客户端连接同一 App Server 时仍能保持订阅边界。18. Server Request 为什么需要 Callback Map当 Server 向 Client 请求审批时Server 生成 Request ID。创建 One-shot Channel。将 Callback 保存到request_id_to_callback。发出 Server Request。Turn 等待 One-shot Receiver。Client Response 到达后根据 ID 完成 Callback。如果连接断开或 Turn 状态改变Pending Request 必须被取消否则Turn 永远等待。One-shot Receiver 永不结束。相关资源无法释放。OutgoingMessageSender因此同时管理Server Request Callback。Client Request Trace Context。连接关闭清理。Thread 状态变化后的 Pending Request Abort。19. 第一层背压Transport Ingress内部 Transport Event Queue 默认容量是 128。当 Queue 已满时不同消息采用不同策略。19.1 Client Request新 Request 不再继续排队而是立即返回code -32001 message Server overloaded; retry later.客户端应将它视为可重试错误并使用带随机抖动的指数退避。19.2 Client ResponseResponse 可能正在解除审批或用户输入等待不能直接丢弃。Queue 满时会等待容量。19.3 Notification 与其他事件连接生命周期和必须处理的事件同样会等待而不是静默丢失。这种区分很重要拒绝一个尚未开始的 Request 是安全的丢掉一个完成 Pending Callback 的 Response 则可能让 Turn 卡死。20. 第二层背压每个连接的 Writer每个 Transport Connection 都有独立 Writer Queue。20.1 WebSocketWebSocket 可能短暂落后于流式输出所以 Writer Queue 容量为32 * 1024高于内部 128 消息队列。如果 Queue 最终仍然填满Outbound Router 会认为该客户端过慢记录 Warning。从连接表移除。触发 Disconnect Token。这样一个不读取消息的客户端不会无限占用内存或拖住其他连接。20.2 stdiostdio 没有主动 Disconnect TokenWriter 满时 Outbound Router 会等待。这是单客户端模式下的自然背压下游停止读取 stdoutServer 最终也停止继续生产。20.3 WebSocket 控制消息Ping/Pong 使用独立的小型控制 Queue避免普通业务消息完全阻塞连接保活。如果控制 Queue 也满连接会关闭。21. 第三层背压进程内客户端进程内 App Server 不经过 JSON 文本和 Socket但仍使用有界 Channel。底层实现位于app-server/src/in_process.rs高层客户端位于app-server-client/src/lib.rs21.1 Command Submission进程内 Command 使用try_sendQueue 未满提交。Queue 已满返回WouldBlock。Runtime 已关闭返回BrokenPipe。21.2 Event 分级并非所有事件同等重要。不可丢失事件包括Agent Message Delta。Plan Delta。Reasoning Delta。Item Completed。Turn Completed。Thread Settings Updated。这些事件会等待 Consumer 获取容量。尽力而为事件可以在拥塞时丢弃例如部分命令输出和进度事件。客户端随后会收到Lagged { skipped }21.3 Server Request 不能静默丢弃如果拥塞导致 Server Request 无法交给客户端Facade 会主动返回 Overload Error。否则 Server 仍会等待一个客户端永远没有见过的审批请求。这种分级比“队列满就全部丢弃”或“全部阻塞”更符合交互式 Agent 的需求对话文本和完成信号必须完整。高频进度信息可以降级。需要响应的 Request 必须显式失败。22. 为什么 In-Process 仍保留 JSON-RPC 语义TUI 和 Exec 与 App Server 运行在同一进程它们本可以直接调用MessageProcessor或 Core。但进程内实现刻意保持类型化ClientRequest。相同 Initialize。相同 MessageProcessor。相同 Request Processor。相同 JSON-RPC Result/Error Envelope。相同 Server Request 与 Notification。它只省略JSON 文本序列化。stdin/stdout。WebSocket。进程边界。这带来两个好处本地 CLI 与远程 IDE 的行为更一致。不会为“性能更快的本地路径”形成第二套业务契约。源码注释将其概括为transport-local, but not protocol-free23. TUI 如何选择 App Servercodex-rs/tui/src/lib.rs 支持三个 TargetEmbedded LocalDaemon RemoteEmbedded默认启动InProcessAppServerClient。LocalDaemon连接本机 Unix Socket Daemon。TUI 会尝试用较短超时探测默认 Socket。Remote连接显式 WebSocket 或 Unix Socket Endpoint。TUI 上层通过统一AppServerClient枚举使用 In-Process 或 Remote Client后续 Thread API 不需要关心实际连接方式。24. Exec 为什么也使用 App Servercodex-rs/exec/src/lib.rs 会创建InProcessClientStartArgs再启动InProcessAppServerClient。随后 Exec 通过协议完成thread/startthread/resumeturn/startReview。Event Consumption。例如 Resume 不再直接读取 Rollout Storage而是调用thread/list与thread/resume。这进一步证明 App Server 已经不是 IDE 专属层而是统一会话入口。25. 优雅关闭不是简单 AbortApp Server 同时持有多个连接。正在执行的 RPC。正在运行的 Assistant Turn。Background Task。Thread Listener。Transport Acceptor。OTEL Provider。直接 Abort 可能导致Turn 中途丢失状态。Pending Response 未发出。Thread 没有正确 Shutdown。Telemetry 未 Flush。25.1 stdio 模式stdio 是单客户端模式。最后一个连接关闭后Processor Loop 退出。25.2 多连接模式收到第一次可优雅处理的关闭信号时标记 Shutdown Requested。继续接受 Request。等待 Running Assistant Turn 数量变为零。停止 Transport。断开全部连接。第二次可强制信号会进入 Forced Shutdown。25.3 排空顺序非强制关闭会依次关闭每个 Connection RPC Gate。等待已开始 Handler。排空 Connection Cleanup Task。排空后台任务。Shutdown Core Thread。停止 Outbound Router。取消 Transport Acceptor。Shutdown OTEL。强制关闭会跳过部分等待优先让进程退出。26. App Server 的并发边界现在可以总结它使用的主要并发机制机制解决的问题Bounded MPSC限制内存增长Outbound 独立 Task隔离慢网络写入Per-Connection Writer隔离不同客户端Atomic Capability Flag无锁读取出站连接能力RwLock Opt-out Set更新连接通知过滤Serialization Queue保证同资源请求顺序Connection RPC Gate断开时阻止新 HandlerTaskTracker等待已开始 HandlerCancellationToken主动断开和停止 AcceptorOne-shot ChannelRequest/Response CallbackWatch ChannelRunning Turn 和 Remote StatusBroadcast ChannelThread Created 等低频事件这些机制不是越多越好而是分别对应清晰的所有权边界。27. 为什么说 App Server 是架构中枢从源码看App Server 同时承担五个边界。27.1 客户端边界TUI、Exec、IDE、SDK 共享协议。27.2 传输边界stdio、Socket、WebSocket 和 In-Process 共享处理逻辑。27.3 Core 边界客户端不直接持有ThreadManager和CodexThread。27.4 生命周期边界连接、订阅、RPC、Turn 和 Shutdown 被集中管理。27.5 兼容边界Protocol Schema 可以生成 TypeScript 与 JSON Schema外部客户端不必跟随 Rust 内部类型变化。所以更准确的定义是App Server 是 Codex Agent Runtime 的客户端协议层和生命周期协调层。28. 设计取舍28.1 优点多客户端复用一套业务。Core 内部重构对客户端影响较小。Transport 可以独立扩展。背压和关闭策略集中。进程内与远程行为趋于一致。协议类型可生成 SDK Schema。28.2 成本即使进程内调用也需要维护 Request ID 和协议对象。类型会经历 Protocol 与 Core 之间的转换。MessageProcessor 依赖较多Composition Root 较重。连接级 Experimental Capability 可能产生跨客户端复杂性。新 API 需要同步 Protocol、Processor、Schema 和测试。这些成本换来的是多个产品表面之间的一致性。29. 常见误区误区一App Server 就是 VS Code 后端VS Code 是一个客户端但 TUI 与 Exec 同样使用 App Server。误区二进程内模式绕过协议它绕过序列化和 Socket不绕过 Initialize、Request Processor 和 Result/Error Envelope。误区三所有 Notification 都可以丢弃消息 Delta、Item Completed 和 Turn Completed 属于不可丢失事件。误区四所有请求都应该并发同一 Thread、Process 或 Watch 上的写操作需要按 Scope 串行。误区五隐藏 stdout 日志就足够还需要有界队列、慢连接策略、连接清理和 Pending Callback 失败处理。误区六客户端断开后直接 Abort HandlerConnection Gate 会阻止新 Handler同时允许已经开始的 Handler 在超时范围内完成。30. 动手练习练习一跟踪 Initialize从 stdio Reader 开始跟踪到 Initialize Response。记录ConnectionId在哪里生成。ConnectionState在哪里创建。OnceLock在哪里写入。Config Warning 何时发送。Outbound Initialized Flag 何时变为true。练习二画出三个有界队列围绕以下 Channel 画出 Producer 和 ConsumerTransportEventOutgoingEnvelopeOutboundControlEvent标记每个 Queue 满时的行为。练习三比较三种慢消费者策略分别分析WebSocket Writer Queue 满。stdio Writer Queue 满。In-Process Event Queue 满。回答哪种会断开哪种会等待哪种允许丢弃非关键事件哪种会产生Lagged练习四分析一个 Serialization Scope选择turn/start、process/writeStdin或fs/watch找到它的serialization_scope。Queue Key。Access 类型。与哪些请求互斥。与哪些请求可以并发。练习五跟踪连接关闭从TransportEvent::ConnectionClosed开始列出RPC Gate。Outbound Connection。Pending Request Context。FS Watch。Command Exec。Process Exec。Thread Subscription。各自在哪一步清理。31. 本篇小结App Server 之所以是架构中枢不是因为它提供了很多 API而是因为它统一了客户端、传输、Core 和生命周期。其核心运行路径可以概括为Transport - Bounded Ingress - Connection State - MessageProcessor - Scoped Request Execution - Core / Domain Processor - OutgoingEnvelope - Outbound Router - Per-Connection Writer关键设计包括所有 Transport 归一化为三类TransportEvent。Processor 与慢速 Outbound Write 分离。每个连接独立 Initialize 和协商 Capability。Broadcast 只发送给已初始化且满足能力要求的连接。同资源请求通过 Serialization Scope 保持顺序。Connection Gate 负责断开后的 Handler 排空。三层背压分别覆盖 Ingress、Writer 和 In-Process Consumer。进程内模式只省略传输不省略协议语义。优雅关闭等待 Assistant Turn、RPC 和后台任务。下一篇将聚焦 App Server Protocol详细拆解 Thread、Turn、Item 的数据模型、请求与通知如何配对以及客户端如何从事件流重建一段完整的 Agent 会话。