Qwen Code Daemon 会话维护写者租约(Session Maintenance Writer Lease)机制深度解析

Qwen Code Daemon 会话维护写者租约(Session Maintenance Writer Lease)机制深度解析 Qwen Code Daemon 会话维护写者租约Session Maintenance Writer Lease机制深度解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文围绕 Qwen Code 开源仓库中 daemon-session-maintenance-writer-lease.md 设计文档展开结合packages/core与packages/cli中的真实源码实现系统讲解 Daemon 在删除、归档、取消归档、孤儿清理与停机等维护场景下如何通过跨进程写者租约Writer Lease防止转录文件transcript被并发写入者竞态破坏以及运行时存储绑定、租约 API、维护协议、停机封存与兼容回滚等核心设计。读完本文你将掌握 Qwen Code Daemon 会话维护的并发安全模型并能准确理解相关配置项QWEN_RUNTIME_DIR、advanced.runtimeOutputDir与错误语义409/503、daemon_draining背后的实现原理。一、问题背景为什么 in-process 归档协调器还不够Qwen Code 的 Daemon 进程可以在某个会话的进程内 ACP 所有者关闭之后对该会话的持久化转录执行删除delete、归档archive或取消归档unarchive操作。原设计文档明确指出The daemon can delete, archive, or unarchive a persisted transcript after its in-process ACP owner has closed. A different daemon process can still own the same transcript, so the in-process archive coordinator alone does not prevent the daemon from racing an external writer.也就是说进程内的归档协调器只能约束本进程内的写者。一旦存在另一个 Daemon 进程、另一个 Qwen 交互进程或 ACP 子进程仍然持有同一个会话的写权限仅靠进程内协调就无法阻止跨进程的竞态一个进程可能正在向xxx.jsonl追加内容而 Daemon 同时把该文件删除或移动到了归档目录。此外设计文档还强调了另一个关键约束——路径解析必须在同一个运行时上下文内完成The transcript path and writer-lock path must also be resolved from the same workspace runtime. Falling back to the primary daemon runtime can mutate one workspace while checking a lock in another.如果转录路径来自工作区 A 的运行时而写者锁路径回退到了主 Daemon 运行时就会出现在一个工作区里改文件、在另一个工作区里查锁的错位锁检查完全失去意义。这正是本文第三节运行时存储绑定要解决的问题。二、覆盖范围Scope哪些维护操作纳入租约保护本设计覆盖 Daemon 所有权的维护操作包括REST 与 ACP 通道的 delete、archive、unarchive 请求断连disconnect与孤儿orphan清理定时任务scheduled-task的回滚与 keepalive 清理Daemon 在维护进行中触发停机shutdown时的有序退出。同时设计文档明确划定了本设计的边界不做什么不引入租约过期lease expiry、心跳heartbeat、基于主机名的恢复hostname-based recovery、自动夺取automatic steal、强制解锁force unlock或锁 schema 迁移不参与租约协议的写者仍然需要平台级的单写者围栏single-writer fencing来保证安全。这一点在源码中也有印证packages/core/src/services/session-writer-lease.ts中SessionWriterLease.acquire()的失败日志明确提示fence blocks access; it does not prove that a writer is still alive并建议先围住包括 ACP 子进程在内的所有写者再进行恢复。三、运行时存储绑定转录路径与锁路径必须同源3.1 运行时基目录的解析优先级每个WorkspaceRuntime在创建时解析一个绝对会话运行时基目录session runtime base directory解析优先级固定为环境变量QWEN_RUNTIME_DIRadvanced.runtimeOutputDir相对工作区解析常规的 Qwen 运行时目录默认路径。对应的配置解析逻辑可以参考 fast-path-settings.ts 中对advanced.runtimeOutputDir的校验与挑选pickedAdvanced.runtimeOutputDir以及 daemon-logger.ts 中getRuntimeBaseDir()的实现——它正是先读process.env[QWEN_RUNTIME_DIR]再回退到runtimeOutputDir相对于cwd的解析function getRuntimeBaseDir(runtimeOutputDir?: string, cwd?: string): string { const envDir process.env[QWEN_RUNTIME_DIR]; if (runtimeOutputDir) return resolveConfigPathLite(runtimeOutputDir, cwd); // ...fallback to the normal Qwen runtime directory }3.2 固定注入与不随环境重载解析得到的目录会存储在运行时对象上并以QWEN_RUNTIME_DIR注入到每个受管的 ACP 子进程环境中。设计文档特别强调Environment reload may update other values but preserves this pinned value because changingruntimeOutputDirrequires a runtime restart.即环境热重载可以刷新其他配置但不会改变这个已固定的运行时目录——因为修改runtimeOutputDir本身要求运行时重启。这一设计保证了同一会话的转录文件与写者锁文件始终落在同一个运行时根下。3.3 失败不回退Daemon 父进程中凡是涉及列出、读取、导出、整理或维护会话的操作都运行在所选运行时的存储上下文内运行时解析失败时不会回退到主运行时Runtime resolution failures do not fall back to the primary runtime从而避免前文所述的改 A 查 B错位。四、租约 APIacquireSessionWriterLease()4.1 接口定义SessionService.acquireSessionWriterLease()是维护场景获取跨进程写者租约的统一入口实现在 sessionService.tsasync acquireSessionWriterLease( sessionId: string, options: { processKind: SessionWriterProcessKind; qwenVersion?: string | null; reclaimPolicy: local | never; takeoverPolicy?: never | certified; }, ): PromiseSessionWriterLease { if (!SESSION_FILE_PATTERN.test(${sessionId}.jsonl)) { throw new SessionWriterUnavailableError(); } return SessionWriterLease.acquire({ runtimeBaseDir: this.storage.getRuntimeBaseDir(), sessionId, transcriptPath: this.getSessionFilePath(sessionId, active), ...options, }); }关键点调用方只提供会话 ID、进程类型processKind、版本qwenVersion和回收策略reclaimPolicy锁根目录与活动转录路径都从服务固定的Storage实例派生天然满足同源约束非法会话 ID 在触碰锁目录之前就被拒绝——通过SESSION_FILE_PATTERN正则校验不合法直接抛SessionWriterUnavailableError。此外 sessionService.ts 还提供了acquireSessionMaintenanceLease()区别在于转录路径带.maintenance后缀用于维护类独占操作。SessionWriterProcessKind在 session-writer-lease.ts 中定义为interactive | acp | daemon | unknown四种。Daemon 维护操作一律使用processKind: daemon as const, reclaimPolicy: never as const,这在 session-archive.ts 的维护流程中可以直接看到。4.2 锁记录与错误类型SessionWriterLease的锁记录schema v2包含schema_version、state: active、session_id、owner_idUUID、pid、process_start_identity、pid_namespace_id、hostname、process_kind、acquired_at、qwen_version等字段并且保留了对 v1 遗留 schema 的兼容判断。设计文档明确The existing lock schema, key, owner record, and acquire/release protocol remain unchanged.——即本次变更不修改既有锁协议只复用。租约相关的错误类型定义在同一文件中errorKindHTTP 状态含义session_writer_conflict409会话已在另一个 Qwen 进程中打开session_writer_lost409本会话的写所有权已丢失session_transcript_changed409转录文件在持有期间被修改session_writer_unavailable503锁不可用如锁目录异常、非法会话 ID4.3 获取协议要点acquireInternal()session-writer-lease.ts 起的核心流程为解析并创建锁目录mkdir -pmode: 0o700并拒绝符号链接目录组装lockRecord含随机owner_id、PID、进程启动身份、PID 命名空间、主机名等使用.claim守卫路径最多重试ACQUIRE_ATTEMPTS8 次安装锁记录对已存在锁进行分类missing重试、live抛SessionWriterConflictError、malformed抛不可用、sealed仅takeoverPolicy: certified时可接管、stale按reclaimPolicy决定是否回收由于 Daemon 维护使用reclaimPolicy: never一旦遇到非 stale 或不允许回收的锁直接抛SessionWriterConflictError——绝不自动夺取。五、维护协议七步单会话流程设计文档给出的维护协议为每个会话独立处理的七个步骤进入 Daemon 的每会话独占归档协调器per-session exclusive archive coordinator关闭本地所有者归档要求 agent close删除使用普通 fast close本地所有者缺失是允许的分类持久化状态保留既有的 not-found 与幂等结果不创建锁获取 Daemon 写者租约reclaimPolicy: never持有租约时重新分类reclassify确认状态在两次分类之间没有漂移校验所有权与转录指纹transcript fingerprint然后执行一次变更带 owner-token 校验释放租约。其中转录指纹在 session-writer-lease.ts 有对应实现基于文件 stat 生成TranscriptFingerprint用于验证转录文件在持有锁期间未被第三方改写释放时同样要求 owner-token 匹配防止误释放他人持有的锁。5.1 并发与批量约束批量请求可以并发处理互相独立的会话但一个 worker 最多同时持有一个跨进程租约且绝不在持有多个租约时等待never waits while holding multiple leases避免死锁与锁堆积。5.2 错误优先级与日志脱敏维护流程的错误上报遵循明确的优先级规则变更失败 释放成功→ 上报变更失败释放失败或所有权失败→ 这是对外安全的错误即使变更也失败了仍以释放/所有权错误为准日志记录工作区、会话、动作、错误类型以及转录变更是否落盘但绝不记录 owner token 与锁路径they never include owner tokens or lock paths定时任务对账scheduled-task reconciliation跟随实际的转录变更结果而不是跟随租约释放是否成功——避免因释放阶段的次级失败而错误回滚已落盘的维护结果。5.3 孤儿清理的特殊处理孤儿清理orphan cleanup先关闭本地所有者并尊重requireZeroAttaches在 session-archive.ts 中可见该标志若期间有新所有者 attach 上来则阻止删除新 attach 的所有者阻止删除发生迟到的清理任务late-spawn cleanup会先等待 close 完成再获取租约并删除转录保证不会与重新打开会话的进程竞争。六、停机ShutdownsealMaintenanceAndWait()Daemon 停机时通过SessionArchiveCoordinator.sealMaintenanceAndWait()实现见 session-archive.ts完成维护封存同步拒绝新的独占维护请求等待已准入的独占操作完成共享的转录读取不参与封存——一次长时间导出不会消耗停机预算termination budget。封存期间的对外错误语义REST 返回503 daemon_drainingACP 返回 JSON-RPC server error且data.errorKind daemon_draining。停机流程在子进程/进程拆除之前先封存维护并且只有已准入的维护租约全部释放后停机才算完成。七、兼容性与回滚Compatibility and Rollout批量响应形状不变既有的 archive / delete / unarchive 幂等性保持原样409 语义分层准入前由assertNotTransitioning抛出的本地session_archiving冲突仍以请求级409返回准入门admission gate内部产生的冲突则归档、取消归档、删除一致地在200响应体的errors[]数组中按会话逐个上报混合版本不安全不同版本的写者同时运行是危险组合因此部署与回滚必须先排空drain旧版 Daemon 与受管 ACP 进程再启动新版本。八、验证Verification设计文档要求的测试覆盖包括使用真实临时运行时根目录测试写者竞争writer contention与根目录隔离root isolation覆盖初始分类与持锁分类之间的状态变化state changes between the initial and locked classifications验证 close、变更、释放、定时任务对账与停机顺序单元测试覆盖非法 ID、重复 ID、active/archive 冲突、租约释放失败、孤儿重新挂载reattachment与日志脱敏合入前必须通过相关包测试、构建与类型检查。对应的测试文件包括 session-service-writer-lease.test.ts、session-archive.test.ts、standalone-session-service.test.ts 与 multi-workspace-sessions.test.ts 等可作为阅读与复现验证的入口。九、2026-09-06 Conversations-runtime 更新从never到硬化local恢复设计文档开篇的更新说明指出本设计原有的Daemon 维护一律reclaimPolicy: never规则已被后续演进部分取代2026-09-02-relaxed-standalone-daemon-ownership.md 描述的 Relaxed Standalone Daemon Ownership 通过强制写者围栏mandatory writer fences与全局所有者切换global-owner cutover实现对于Conversations 独立生命周期与维护获取现在使用硬化的local恢复hardenedlocalrecovery取代了原先的never规则而普通工作区及其他受管运行时的 Daemon 维护仍然保持never。也就是说reclaimPolicy的取值需要按运行场景区分local仅用于 Conversations 独立所有权场景的受控恢复普通工作区的维护依然坚持不回收never以最大程度避免跨进程误夺。十、阅读与排查路径小结关注点代码/文档位置租约获取 APIsessionService.ts锁协议与错误类型session-writer-lease.ts维护流程seal/acquire/requireZeroAttachessession-archive.ts运行时目录解析env → advanced → 默认daemon-logger.ts、fast-path-settings.ts相关测试session-service-writer-lease.test.ts、session-archive.test.ts、multi-workspace-sessions.test.ts核心结论Qwen Code 的 Daemon 会话维护通过运行时同源绑定 不自动夺取的写者租约 七步维护协议 停机封存四个层次在不引入心跳与租约过期的前提下把跨进程维护竞态压缩到可控范围对不参与租约协议的写者仍依赖平台级单写者围栏兜底。理解这套机制是安全运维多工作区 Daemon、排查409 session_writer_conflict与503 daemon_draining等错误的前提。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考