OpenCreator 常驻化方案剖析:用 PersistentAppServerExecutor 消除 Codex app-server 热路径重复初始化
【免费下载链接】OpenCreatorFormerly KrillinAI. Open-source AI workspace for creators, powered by Codex. Create videos, images, voice, avatars, translations, and edits with Agents in one place.项目地址: https://gitcode.com/GitHub_Trending/kr/OpenCreator
本篇技术指南基于 OpenCreator(前身 KrillinAI)已批准的设计方案《OpenCreator app-server 常驻化方案》(docs/specs/opencreator-app-server常驻化方案-2026-07-28.md)展开,结合仓库源码与测试逐一拆解其背景、需求、架构决策、实现细节与验收策略。读完本文,你将理解 OpenCreator 如何让用户手动对话复用 Daemon 管理的常驻 app-server 进程,在保持审批、取消、日程工具与 Web/Desktop 行为不变的前提下,消除每次对话的进程启动与固定 MCP 初始化开销,以及背后的串行队列、进程级能力凭证与异常恢复机制。
1. 背景与目标:为什么要让 app-server 常驻
在常驻化之前,OpenCreator 的对话执行链路存在明显的重复开销。方案文档给出的事实基线(见 docs/specs/opencreator-app-server常驻化方案-2026-07-28.md):
| 项目 | 已确认事实 |
|---|---|
| 对话执行 | apps/daemon/src/codex/app-server-runner.ts 每次运行都会启动、初始化并在 turn 完成后关闭 app-server |
| 会话查询 | apps/daemon/src/codex/app-server-client.ts 已实现按需启动、进程失效后重建的常驻请求客户端,但不处理 turn 通知、审批和运行结果 |
| 运行参数 | cwd、model、sandbox、reasoning和approvalPolicy可通过thread/start、thread/resume、turn/start按运行传入 |
| 进程参数 | profile和通过命令行注入的 MCP 配置在 app-server 启动时确定 |
| 日程权限 | opencreator_schedule当前使用绑定runId、threadId、createdBy和 scopes 的短期令牌;运行终止后撤销 |
| 调度边界 | 自动定时任务不能获得日程修改权限,只能保留现有允许范围 |
| 前端边界 | Web 与 Desktop 通过同一 Daemon/Runtime 链路执行对话,不应分别实现常驻逻辑 |
也就是说,每轮对话都要重复执行:进程 spawn、initialize握手、固定 MCP 初始化,而这些恰恰都发生在用户等待路径上。方案的目标很聚焦:让用户主动发起的对话复用一个由 OpenCreator Daemon 管理的常驻 app-server,消除热路径上的重复初始化,同时保持既有会话、审批、取消、日程工具和 Web/Desktop 行为。
方案明确划定了非目标,避免范围蔓延:
- 不实现多 app-server 进程池或多 turn 并发;
- 不将后台定时任务改造成常驻执行;
- 不实现崩溃后的 turn 自动恢复或自动重放;
- 不合并现有会话查询客户端与执行客户端;
- 不接入
codex app-server daemon/proxy、系统服务或全局 socket; - 不建设新的监控页面或性能平台;
- 不进行与常驻执行无关的 Runtime 重构。
方案还包含三条前提假设:OpenCreator 发版绑定的 Codex 版本继续支持当前使用的 app-server 协议;第一版允许不同手动运行在 Daemon 内串行进入常驻执行器;交互式进程凭证只在本机 Daemon 与其 app-server 子进程之间使用,并在进程退出时撤销。
2. 需求与业务规则:P0 级行为约束
方案以表格形式定义了功能需求(FR)、业务规则(BR)与非功能需求(NFR),这些是后续所有设计与验收的锚点:
| ID | 类型 | 优先级 | 描述 |
|---|---|---|---|
| FR-1 | 功能需求 | P0 | 用户主动发起的同 profile 对话必须复用同一个常驻执行 app-server,Daemon 退出前不因单个 turn 完成而关闭 |
| FR-2 | 功能需求 | P0 | 切换项目、目录、模型、沙箱或推理等级时必须按运行传入正确参数;只有 profile 改变才受控重启常驻进程 |
| FR-3 | 功能需求 | P0 | 会话新建与续接、事件输出、请求批准、完全访问权限和取消行为必须保持现有外部语义 |
| FR-4 | 功能需求 | P0 | 用户对话必须保留现有日程工具能力;后台定时任务继续使用一次性 app-server 和现有受限令牌 |
| FR-5 | 功能需求 | P0 | 常驻进程异常时只使当前 run 失败;下一次手动运行必须能够自动启动新进程 |
| BR-1 | 业务规则 | P0 | 第一版常驻执行器同一时间只能有一个活动 turn,后续运行必须排队,因此事件和审批只能归属当前活动 run |
| BR-2 | 业务规则 | P0 | 已发送的turn/start不得自动重放;profile 切换和进程替换只能在当前 turn 终止后发生 |
| NFR-1 | 非功能需求 | P0 | 常驻化不得扩大后台任务权限,不得产生 Web/Desktop 分叉,不得在 Daemon 退出后残留 Codex 子进程 |
| NFR-2 | 非功能需求 | P1 | 日志必须能够证明进程启动、初始化、复用、重启和退出原因,并记录关键运行时间点 |
其中 BR-1 直接决定了第一版架构形态:不引入 run/thread/turn 全局路由表,而是用一个activeRun承接所有通知、审批、日志与结果。BR-2 则是一条底线安全规则:turn/start一旦发出,无论进程是否崩溃,都不允许自动重放,以防止重复生成、重复文件修改和重复日程操作。
3. 方案比较:为什么选"双轨制"
方案在"交互式常驻 + 后台一次性"的选项上做了三方案比较:
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 一个交互式常驻进程,后台任务保留单次进程 | 直接优化用户等待;权限边界清晰;复用现有 runner 行为;改动可控 | 同一时刻只能串行执行;保留两种生命周期 | 推荐 |
| 所有运行共用常驻进程并支持并发 | 进程数量最少;理论吞吐更高 | 必须新增完整事件路由、并发控制和动态权限上下文,失败模式复杂 | 第一版排除 |
| 保持每轮一次性进程 | 无架构改动 | 无法消除重复启动和 MCP 初始化,不能达成目标 | 排除 |
这一"双轨制"判断的依据是:性能收益应集中在用户感知链路,而后台任务一旦常驻化,就必须同步解决权限扩大、并发路由与失败重放等复杂问题,风险与收益不成比例。
4. 关键设计决策:DEC-1 至 DEC-5
| DEC ID | 决策 | 理由 | 约束范围 |
|---|---|---|---|
| DEC-1 | 用户手动运行使用一个常驻执行器;后台定时任务继续使用现有一次性 runner | 将性能收益集中在用户感知链路,同时保持后台任务权限和失败边界 | RunManager、Daemon 生命周期、调度执行 |
| DEC-2 | 第一版只允许一个活动 turn,以activeRun直接承接通知、审批、日志和结果 | 避免引入 run/thread/turn 全局路由表和并发串线风险 | 常驻执行器、运行队列、审批 |
| DEC-3 | 交互式 app-server 使用进程级凭证,Daemon 将该凭证映射到当前活动 run;无活动 run 时拒绝 MCP 请求 | app-server 环境变量在启动时固定,无法继续使用每轮短期环境变量令牌;串行执行使活动上下文无歧义 | capability token、内部 MCP 路由 |
| DEC-4 | cwd/model/sandbox/reasoning/approvalPolicy按运行传入;profile 改变时在空闲状态重启常驻进程 | 这些运行参数已有协议支持,只有 profile 属于当前进程启动参数 | app-server 启动、thread/turn 请求 |
| DEC-5 | 进程异常不自动重放 turn;当前 run 终止,下一次请求重建进程 | 防止重复生成、重复文件修改和重复日程操作 | 取消、超时、崩溃恢复、回退 |
DEC-3 是整套方案中最关键的一个设计跃迁。原有后台路径通过每轮注入短期环境变量令牌来绑定 run/thread 权限;但 app-server 的环境变量在进程启动时就被固定,常驻进程无法随每轮 turn 更换环境变量。解决方案是把"绑定"从运行级升级为进程级:进程持有一个不可预测的凭证,Daemon 在每轮开始时把活动 run/thread 写入该凭证的上下文,结束时清空。
5. 详细设计:常驻执行器与数据流
5.1 常驻执行器的职责
Daemon 持有一个PersistentAppServerRunner实例,至少维护:
- 当前子进程和初始化状态;
- 当前 profile;
- 当前活动 run 的回调和运行上下文;
- 当前 thread ID、turn ID 和取消状态;
- 串行化入口,保证任何时刻最多一个活动 turn。
执行器按需启动,不要求 Daemon 启动时阻塞等待 Codex。第一次手动运行负责启动和initialize;后续同 profile 运行直接复用。现有会话查询客户端保持独立,第一版不承担 turn 通知或审批。
源码中对应的实现是 apps/daemon/src/runs/persistent-app-server-executor-2026-07-28.ts 的createPersistentAppServerExecutor。它对外暴露四个核心能力:
start(input):提交一个手动运行,返回PersistentAppServerExecution;isBusy():判断当前是否已有活动 turn;invalidate(reason):使进程失效(例如 MCP 配置变更),由底层 Runtime Manager 择机关闭重建;close(options):Daemon 关闭时优雅收尾。
在start内部,busy标志保证同一时刻只有一个活动 turn;启动流程先在runtimeInjector.prepare中准备 MCP 注入,再调用底层runtimeManager.startTurn,并通过startedPromise 对外暴露{ pid, reused }。执行结束后在finally中发出run_cleared生命周期事件并复位activeRun/activeScope/busy,但不关闭底层进程——这正是"常驻"语义的落点。
5.2 生命周期事件与可观测性
执行器定义了PersistentAppServerLifecycleEvent,覆盖以下事件(对应源码第 20-37 行):
| 事件 | 语义 |
|---|---|
process_started | 首次启动新进程 |
process_initialized | 进程完成initialize |
process_reused | 复用既有进程 |
mcp_refreshed | 固定 MCP 配置刷新 |
profile_restarted | profile 改变触发重启 |
process_exited | 进程退出 |
run_assigned | 当前 run 被登记为活动 run |
run_cleared | 活动 run 上下文被清空 |
每个事件都携带at时间戳、pid、profile、generation(进程代数)与可选reason。这直接支撑 NFR-2 的日志要求:进程启动、初始化、复用、重启和退出原因都有据可查。RunManager 侧会把生命周期事件与turn/started、首个模型事件等关键时间点一起记录(见 apps/daemon/src/runs/manager.ts 中appServerLifecycle的收集逻辑),形成可审计的时间线。
5.3 手动运行数据流
方案给出了八步数据流:
- RunManager 将手动运行提交给常驻执行器;
- 执行器等待前一轮结束;
- 执行器检查进程状态和 profile;
- 无进程时启动并初始化;profile 不同时关闭旧进程后启动新进程;
- 执行器登记
activeRun和对应的交互式能力上下文; - 根据续接模式发送
thread/start或thread/resume; - 发送
turn/start,并将通知、审批、stderr 和日志交给当前 run 的现有回调; - 收到
turn/completed后解析结果并清除活动运行上下文,但保留进程。
一个容易忽略的细节:切换 OpenCreator 项目只改变请求中的cwd,不改变 app-server 进程。因为在 app-server 协议中cwd属于按运行传入参数,只有profile属于进程启动参数(DEC-4)。源码里AppServerRuntimeScope仅区分project与creator-job两种 scope(见 apps/daemon/src/codex/app-server-runtime-manager.ts 第 19-22 行),并以projectId(无项目时退回thread.id)作为复用单位。
5.4 后台任务数据流:与常驻路径隔离
由 schedule 创建的运行继续调用现有一次性startCodexAppServer(apps/daemon/src/codex/app-server-runner.ts):
- 每轮签发绑定 run/thread 的受限令牌;
- 每轮注入独立 MCP 配置和环境变量;
- turn 结束后关闭进程并撤销令牌。
后台路径不接入交互式进程凭证,也不共享交互式activeRun。两条路径在 RunManager 中的分流依据是createdBy:仅当createdBy === 'api'且存在线程上下文时才走常驻执行器(见 apps/daemon/src/runs/manager.ts 第 297-301 行),否则走一次性 runner。源码中的startCodexAppServer也印证了"一次性"语义:它在finally中无条件manager.close()并清理隔离 home,进程随每轮运行结束而退出。
6. 交互式能力凭证:进程级权限映射
这是常驻化方案中安全设计的核心。常驻 app-server 启动时,Daemon 创建一个仅属于该进程实例的不可预测凭证,并在 MCP 配置中引用该凭证。Daemon 维护如下映射:
processCredential -> { appServerInstanceId, activeRunId, activeThreadId, createdBy: "api", allowedScopes }每轮开始时写入活动 run/thread,结束时清空。内部 MCP 路由收到请求后按五步校验:
- 校验进程凭证有效且未撤销;
- 确认当前存在活动交互式 run;
- 校验请求 scope 属于交互式允许范围;
- 使用当前活动 run/thread 构造现有审计 actor;
- 执行操作。
凭证在 app-server 退出、profile 切换、Daemon 关闭或执行器被强制重置时撤销。无活动 run、旧凭证或后台运行尝试使用交互式凭证时必须拒绝。
源码中该机制的落地分两层:
- 凭证存储:apps/daemon/src/agent-tools/capability-token.ts 定义了
AgentCapabilityTokenStore,其中issueProcess签发进程级租约AgentCapabilityProcessLease(token +activate/deactivate/revoke)。activate会校验createdBy === 'api'、活动上下文唯一(已有活动 run 时抛CAPABILITY_CONTEXT_ACTIVE)、scope 不超出maxScopes;inspect在无活动 run 时抛CAPABILITY_CONTEXT_INACTIVE。此外该文件还硬性规定:createdBy === 'schedule'的令牌不能获得任何 mutation scope(schedule:create/update/pause/resume/run_now、creator:action),从根源上保证后台任务无法修改日程。 - 路由校验:apps/daemon/src/agent-tools/mcp-routes.ts 的
authorizeMcpRequest调用capabilities.inspect(token)获取授权上下文,再以grant.scopes决定工具是否可用(toolNamesForScopes)。process 凭证的activate/deactivate由 apps/daemon/src/codex/app-server-runtime-manager.ts 的entry.injection.activate(...)在每轮启动与收尾时驱动(第 128-156 行)。
需要注意的是,常量AGENT_CAPABILITY_SCOPES中还包含creator:context、creator:artifact:read、creator:action等创作者相关 scope,说明交互式凭证同样覆盖创作者工具的授权范围。
7. 取消、超时与关闭回退
7.1 取消与超时语义
- 已获得 thread ID 和 turn ID 时,取消发送
turn/interrupt; - interrupt 成功后当前 run 按现有取消语义终止,app-server 可继续复用;
- app-server 无响应、interrupt 失败或协议状态不可判定时,终止进程并使当前 run 失败或取消;
- 进程终止后清除
activeRun、初始化状态和进程凭证; - 下一次手动运行重新启动,不重放前一轮请求。
源码层面,Runtime Manager 的startTurn返回的 execution 同时提供cancel()(终止当前 turn)与可选的steer()(向运行中 turn 注入消息);closeScope在中断后给interruptGraceMs(默认 1000ms)的沉降窗口,仍不收敛则升级为clearHost强制清理(见 apps/daemon/src/codex/app-server-runtime-manager.ts 第 266-282 行)。
7.2 Daemon 关闭与回退路径
Daemon 关闭时停止接收新运行,处理当前运行终止,然后关闭常驻 app-server 并撤销进程凭证。源码中close()会先取消活动执行,再按interruptGraceMs(默认 1000ms)与terminateGraceMs(默认 2000ms)依次关闭当前 scope 与所有 project scope(见 apps/daemon/src/runs/persistent-app-server-executor-2026-07-28.ts 第 209-235 行),对应 AC-7"无残留子进程"的验收要求。
现有一次性 runner 继续存在,既服务后台任务,也作为交互常驻化的紧急回退路径。回退不得改变会话、项目或前端接口——也就是说,前端完全不感知底层是常驻进程还是一次性进程,切换只在 Daemon 内部发生。
8. 异常处理矩阵与回滚策略
该改动不迁移数据库或持久化会话。常驻状态全部属于 Daemon 内存状态,Daemon 重启后重新创建即可。方案给出了完整的异常处理矩阵:
| 异常 | 对当前 run 的处理 | 后续处理 |
|---|---|---|
| 启动或 initialize 失败 | 失败 | 清空状态;下一轮重新启动 |
| turn 中进程退出 | 失败 | 撤销凭证;下一轮重新启动 |
| interrupt 成功 | 取消 | 保留进程 |
| interrupt 超时或失败 | 取消或失败 | 强制关闭进程;下一轮重建 |
| profile 改变 | 等待当前 turn 结束 | 关闭旧进程并启动新进程 |
| MCP 无活动 run | 拒绝操作 | 不影响进程 |
| 进程凭证失效 | 当前工具调用失败 | 关闭对应进程;下一轮重建 |
回滚策略同样清晰:由于 OpenCreator 发版绑定 Codex 版本,本方案不提供旧 Codex 协议兼容层。若发布后出现常驻执行问题,可以将手动运行临时切回现有一次性 runner;不得通过自动重放当前 turn 进行回滚。
9. 验收标准:从单元到真实 Codex
方案定义了 7 条验收标准(AC-1 至 AC-7),逐条映射到需求:
| AC ID | 关联需求 | 前置条件 | 操作 | 可观察结果 | 验证层级 |
|---|---|---|---|---|---|
| AC-1 | FR-1、FR-2、NFR-2 | 同一 Daemon、同一 profile | 在同一项目连续运行三轮,再切换项目运行一轮 | 四轮使用同一执行 PID;只出现一次 initialize;每轮 cwd 正确 | 单元、集成、真实 Codex |
| AC-2 | FR-2、BR-2 | 常驻进程空闲 | 切换到不同 profile 后运行 | 旧进程关闭,新进程只启动并初始化一次,没有进程重叠 | 单元、集成 |
| AC-3 | BR-1 | 同时提交两个手动运行 | 观察通知、审批和完成顺序 | 第二轮在第一轮终止后开始;事件和审批不串线 | 单元、集成 |
| AC-4 | FR-3、NFR-1 | Web 与 Desktop 使用同一 Fake Daemon 和真实 Daemon | 验证新会话、续接、两档权限、审批和取消 | 两端调用相同 Runtime API,外部行为与改造前一致 | 集成、Web/Desktop 门禁、App E2E |
| AC-5 | FR-4、NFR-1 | 分别存在手动 run 和后台任务 run | 手动读取/修改日程,并触发后台任务 | 手动功能可用;后台任务仍无法获得修改权限;两类进程和凭证相互隔离 | 单元、集成、真实 Codex |
| AC-6 | FR-5、BR-2 | turn 执行中模拟 app-server 崩溃 | 等待当前 run 终止,再提交下一轮 | 当前 run 明确失败且没有重复 turn;下一轮使用新 PID 成功运行 | 单元、集成 |
| AC-7 | NFR-1、NFR-2 | 常驻进程已运行 | 关闭 Daemon | app-server 退出、凭证撤销、无残留子进程;日志记录退出原因 | 集成、真实进程 |
其中 AC-1 精确刻画了"复用"的判定标准:连续四轮同一 PID + 仅一次 initialize;AC-6 则用"下一轮使用新 PID"证明崩溃后自动重建且无重放。
10. 测试策略:Fake app-server 与真实 Codex 双通道
测试策略分为三层:
- 单元测试:使用 Fake app-server 精确断言 spawn 次数、
initialize次数、请求顺序、PID 复用、profile 重启、串行队列、取消和崩溃行为;能力测试覆盖活动 run 映射、scope 校验、旧凭证撤销及无活动 run 拒绝。对应测试文件 apps/daemon/test/unit/persistent-app-server-executor-2026-07-28.test.ts,其用例标题与方案条目一一对应,例如:- "reuses one initialized process across run parameters and refreshes changed manifests"(复用与 manifest 刷新,对应 AC-1);
- "applies a changed permission profile to the next turn of a resumed thread"(profile 切换,对应 AC-2);
- "rejects a second start while one job is active"(串行约束,对应 BR-1/AC-3);
- "creates a new process after a crash without replaying the failed turn"(崩溃不重放,对应 AC-6);
- "keeps the host reusable after a matched interrupt terminal event"(interrupt 后进程可复用);
- "escalates close to SIGKILL when interrupt and SIGTERM are ignored"(关闭升级强制终止);
- "restarts when remote MCP configuration fingerprint changes"(MCP 指纹变更触发重启)。
- 集成测试:通过 RunManager 验证手动运行选择常驻路径、后台任务选择一次性路径,并覆盖会话续接、审批、日志、日程工具和 Daemon 关闭。
- 真实边界验证:使用发版绑定的 Codex,完成连续三轮、跨项目一轮、取消、审批、手动日程操作和后台任务。
性能门禁以可重复的结构事实为准,而非易受网络和模型波动影响的绝对耗时:
- 热路径不重新 spawn;
- 热路径不重新 initialize;
- profile 不变时不重新初始化固定 MCP;
- 记录改造前后
submit -> turn/start、submit -> turn/started、submit -> first model event,但不以首字绝对时间作为唯一发布条件。
这套门禁的可执行性来自生命周期事件:process_started与process_initialized是否出现、出现几次,都可以从 apps/daemon/src/runs/manager.ts 收集的appServerLifecycle数组精确断言。
11. 风险与未决问题
| 风险 | 处理 |
|---|---|
| 交互式进程凭证比运行级令牌存活更久 | 凭证只绑定单个子进程;每轮通过活动上下文恢复 run/thread 归属;进程退出立即撤销;无活动 run 时拒绝 |
| 串行执行可能限制多会话并发 | 第一版保持简单和事件隔离;只有出现明确并发需求与数据后再设计路由 |
| app-server 内部状态长期运行后可能异常 | 进程退出和协议错误会清空状态;下一轮自动重建;真实连续运行纳入验收 |
| 保留常驻与一次性两条路径增加测试面 | 两条路径按createdBy明确分工,一次性路径不修改核心语义 |
方案文档同时记载了一个值得注意的独立审核事件:Reviewer 初始结论为 BLOCKED,原因是兼容 Reviewer 已到达 completed 终态,但其运行环境没有本地文件读取或 CodeGraph 工具,在只读且不启动子进程的约束下无法读取方案正文与代码证据,因此未完成实质性独立审核(REV-BLOCK-001)。该问题经用户风险披露后知情批准("没问题,继续")进入 Plan,并明确 D/FR/BR/NFR/DEC/AC 未获得独立 Reviewer 的逐项代码事实核对,权限凭证和失败语义必须在实现与验收中重点验证。这一记录本身也体现了仓库对方案评审透明性的要求。
12. 小结:常驻化的核心方法论
回顾整份方案,OpenCreator 的 app-server 常驻化遵循了几条可复用的工程原则:
- 性能收益聚焦用户感知链路:只常驻用户手动对话,后台任务保持一次性进程,权限边界不因优化而模糊;
- 用串行换取简单与正确:第一版只允许一个活动 turn,用
activeRun直接承接事件与审批,避免全局路由和并发串线; - 进程级凭证替代运行级令牌:针对 app-server 环境变量启动后固定的约束,用不可预测的进程凭证 + 每轮激活/清空的活动上下文实现同等强度的授权,且无活动 run 一律拒绝;
- 失败只伤当前轮,绝不重放:崩溃、中断、凭证失效都只终止当前 run,下一轮自动重建,杜绝重复副作用;
- 可观测性内建于设计:从
process_started到run_cleared的完整生命周期事件与关键时间点记录,让性能门禁与故障排查都有结构事实可依。
如果你想继续深入,建议按以下顺序阅读源码:先从 apps/daemon/src/runs/persistent-app-server-executor-2026-07-28.ts 理解执行器入口,再到 apps/daemon/src/codex/app-server-runtime-manager.ts 看 scope 复用、指纹与空闲回收,接着对照 apps/daemon/src/agent-tools/capability-token.ts 的进程租约机制,最后用 apps/daemon/test/unit/persistent-app-server-executor-2026-07-28.test.ts 的用例逐条验证本文提到的行为。整体设计文档见 docs/specs/opencreator-app-server常驻化方案-2026-07-28.md,实施计划见 docs/plans/opencreator-app-server常驻化-实施计划-2026-07-28.md。
【免费下载链接】OpenCreatorFormerly KrillinAI. Open-source AI workspace for creators, powered by Codex. Create videos, images, voice, avatars, translations, and edits with Agents in one place.项目地址: https://gitcode.com/GitHub_Trending/kr/OpenCreator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考