From 8216f32280c944c453a5226457348902efbdb322 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:24:03 +0800 Subject: [PATCH 01/11] docs: add deep dive report on task management and swarm coordination --- .../qwen-code-improvement-report.md | 1 + docs/comparison/task-management-deep-dive.md | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/comparison/task-management-deep-dive.md diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 1388bf14..7c20fafa 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -73,6 +73,7 @@ | **P1** | 原子文件写入与事务回滚 — temp+rename 原子写 + 大结果persist to disk [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-12) | 直接 writeFileSync | 中 | — | | **P1** | 自动检查点默认启用 — 每轮工具执行后自动创建文件快照 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-13) | 检查点默认关闭 | 小 | — | | **P1** | Coordinator/Swarm 多 Agent编排 — Leader/Worker 团队 + 3 种执行后端 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-14) | 仅 Arena 竞赛 | 大 | — | +| **P1** | [Task Management 任务协同与跨进程并发调度](./task-management-deep-dive.md) — 支持 blocks/blockedBy 的任务拓扑、跨进程安全锁与 Swarm 集成 | 仅提供简易无状态 TodoWriteTool | 大 | — | | **P1** | Agent 工具细粒度访问控制 — 3 层allowlist/denylist + per-agent 限制 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-15) | 全部或指定列表 | 中 | — | | **P1** | InProcess 同进程多 Agent隔离 — AsyncLocalStorage 上下文隔离 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-16) | 全局状态可能泄漏 | 中 | — | | **P1** | Agent 记忆持久化 — user/project/local 3 级跨 session 记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-17) | 无跨 session 记忆 | 中 | — | diff --git a/docs/comparison/task-management-deep-dive.md b/docs/comparison/task-management-deep-dive.md new file mode 100644 index 00000000..01a0291e --- /dev/null +++ b/docs/comparison/task-management-deep-dive.md @@ -0,0 +1,60 @@ +# Qwen Code 改进建议 — 任务管理与多智能体协同 (Task Management System) + +> 核心洞察:Claude Code 拥有一个完整的、支持多进程和依赖关系图的后台任务管理系统(Task System),用于支撑其 Swarm(多 Agent)架构。而 Qwen Code 目前仅提供单机的、无状态的 `TodoWriteTool`。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构对比 + +### 1. Claude Code 的任务协同架构 +Claude Code 的任务系统(位于 `utils/tasks.ts` 和相关 Tools)专门为多 Agent 协同设计: +- **分布式锁与并发安全**:通过 `.highwatermark` 递增分配 ID,并通过文件锁(`lockfile.ts`)实现跨进程的任务并发安全读写,允许不同进程中的 Agent(Swarm)安全地认领和更新任务。 +- **任务依赖图谱 (Dependency Graph)**:任务模型包含 `blocks`(阻塞哪些任务)和 `blockedBy`(被哪些任务阻塞),Agent 可以精确推断任务执行顺序。 +- **归属与生命周期**:包含 `owner` 字段标记当前哪个 Agent 正在处理该任务,状态严格在 `pending`、`in_progress`、`completed` 之间流转。 +- **细粒度工具集**:提供了 6 个独立工具(`TaskCreateTool`、`TaskGetTool`、`TaskListTool`、`TaskOutputTool`、`TaskStopTool`、`TaskUpdateTool`),支持对任务树的增删改查。 +- **IPC 信号通知**:进程内使用 `tasksUpdated.emit()` 信号,UI 能够实时刷新任务列表和 Spinner 状态(`activeForm`)。 + +### 2. Qwen Code 的现状 +Qwen Code 目前的任务管理仅限于 `packages/core/src/tools/todoWrite.ts`(TodoWriteTool): +- 仅提供一个 `todo_write` 工具,每次操作需覆写整个 Todo 列表。 +- 数据结构简单:仅包含 `id`、`content`、`status`,缺乏依赖关系和归属者概念。 +- 不支持多智能体(Multi-Agent)协同认领任务,缺乏并发安全机制。 + +## 二、Qwen Code 的改进路径 (P1 优先级) + +为了支持真正意义上的多智能体协作(Coordinator/Swarm),Qwen Code 必须重构其任务系统。 + +### 阶段 1:数据模型与底层支持 (Data Model & IPC) +1. **持久化与并发控制**:在项目级的 `.qwen` 目录中建立任务注册表。引入跨进程文件锁,保障多个 Qwen Agent 进程可以安全读写任务状态。 +2. **丰富的任务 Schema**: + ```typescript + export interface Task { + id: string; + subject: string; + description: string; + activeForm?: string; // UI 渲染用的进行时动词,如 "Running tests" + owner?: string; // 执行该任务的 Agent ID + status: 'pending' | 'in_progress' | 'completed' | 'failed'; + blocks: string[]; // 阻塞的其他任务 ID + blockedBy: string[]; // 依赖的前置任务 ID + metadata?: Record; + } + ``` +3. **事件驱动 (Event-Driven)**:实现 `TaskUpdate` 事件流,主节点(Coordinator)能够实时侦听子 Agent 更新任务状态并更新 TUI。 + +### 阶段 2:原子化 Tool 拆分 (Granular Tools) +废弃单一的 `todo_write` 覆写模式,拆分为原子化的 CRUD 工具: +- `TaskCreateTool`: 创建新任务并指定前置依赖。 +- `TaskListTool`: 获取当前所有任务及状态拓扑。 +- `TaskUpdateTool`: 认领任务(设置 `owner`)或更改状态。 +- `TaskGetTool`: 获取单个任务的详细信息(包括 `metadata` 和长篇 `description`)。 + +### 阶段 3:Swarm 调度集成 (Coordinator Integration) +主控 Agent (Leader) 利用 `TaskCreateTool` 分解大任务并建立依赖关系,而后唤起(或等待)子 Agent (Worker)。子 Agent 读取 `pending` 且 `blockedBy` 为空的子任务,认领执行。执行完毕后,使用 `TaskUpdateTool` 标记完成,自动解锁下游任务。 + +## 三、改进收益评估 +- **实现成本**:涉及核心架构变动,约 1000 行代码,需 1-2 周开发时间(高难度)。 +- **直接收益**: + 1. **稳定性提升**:细粒度 Tool 减少了模型每次重写整个 Todo List 的 Token 开销和幻觉风险。 + 2. **多并发解锁**:从底层打通了真正能够并行工作的多 Agent 架构。 + 3. **可观测性**:UI 可以清晰渲染任务依赖树(Tree)及各个 Agent 的实时进度。 \ No newline at end of file From cb7fe4a0f11da33d97c10f11df2a288e539142d9 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:26:02 +0800 Subject: [PATCH 02/11] docs: add deep dive report on session crash recovery and interruption detection --- docs/comparison/crash-recovery-deep-dive.md | 79 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/crash-recovery-deep-dive.md diff --git a/docs/comparison/crash-recovery-deep-dive.md b/docs/comparison/crash-recovery-deep-dive.md new file mode 100644 index 00000000..7bd2660f --- /dev/null +++ b/docs/comparison/crash-recovery-deep-dive.md @@ -0,0 +1,79 @@ +# Qwen Code 改进建议 — 会话崩溃恢复与中断检测 (Crash Recovery & Interruption Detection) + +> 核心洞察:CLI Agent 进程在长时间运行(长任务、重构、多文件分析)时,极易因内存溢出 (OOM)、系统休眠、网络中断或用户误触 `Ctrl+C` 等意外退出。Claude Code 设计了极其完善的崩溃恢复(Crash Recovery)机制,能够精准识别上次中断的状态并无缝续行,而 Qwen Code 目前完全缺失此能力。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、问题背景:为什么直接恢复历史记录是不行的? + +假设我们将多轮对话持久化到了本地磁盘。当进程崩溃并重新启动时,如果简单地将最后保存的 Message 数组直接发给模型 API,经常会遭遇 `API 400 Bad Request` 错误。 + +这是因为 LLM API 要求严格的消息配对格式: +- `tool_use` 必须跟随 `tool_result`。 +- 不能连续出现两个相同角色(例如连续两个 `user`),或结尾状态异常。 + +如果崩溃发生在工具执行中途,历史记录里会存在一个"孤立的" `tool_use`;如果发生在刚处理完用户附件但还没生成回复时,尾部会留下不规范的边界。直接发送这些残缺的对话链会导致 API 拒绝服务,**最终导致哪怕有本地存档,用户也只能被迫新建会话,前功尽弃**。 + +## 二、Claude Code 的解决方案:三态中断检测与合成续行 + +在 Claude Code 的源码 `utils/conversationRecovery.ts` 中,设计了一个非常巧妙的 `deserializeMessagesWithInterruptDetection` 机制。 + +### 1. 数据清理与规范化 (Data Normalization) +每次从本地持久化文件读取历史消息时,Claude Code 会先执行一系列清理: +- **`filterUnresolvedToolUses`**: 扫描并移除所有只发出了 `tool_use` 请求但由于崩溃未能返回 `tool_result` 的孤立区块。 +- **`filterOrphanedThinkingOnlyMessages`**: 移除只有 `` 但没有实际行动块的未完成思考片段。 + +### 2. 三态中断检测 (detectTurnInterruption) +接着,它会检查最后一个有效消息的类型,判断崩溃具体发生在什么阶段,返回 3 种内部状态: + +1. **`none` (正常完成)** + - 最后一个消息是 `assistant`(由于 `stop_reason` 在持久化时处理的特性,只要最后一个是完整的 assistant 块且没有遗漏的 tool,就被认为是正常结束)。 + - 或者最后一个是类似于 `SendUserMessage` 的终端(Terminal) `tool_result`。 +2. **`interrupted_prompt` (提示词被中断)** + - 最后一个消息是纯文本的 `user` 提示,这意味着用户刚发完指令,Claude 还没开始思考(或没想完)就崩溃了。 +3. **`interrupted_turn` (执行轮次被中断)** + - 最后一个消息是 `tool_result` 或 `attachment`,这意味着 Claude 在工具执行间隙(Mid-Turn)被打断,它本来应该继续根据上一个工具的结果进行思考的。 + +### 3. 合成续行 (Synthetic Continuation) +对于 `interrupted_turn` 状态,Claude 采用了一种极其优雅的处理方式:**注入合成提示词**。 + +```typescript +// 源码摘录:将 interrupted_turn 转换为 interrupted_prompt +if (internalState.kind === 'interrupted_turn') { + const [continuationMessage] = normalizeMessages([ + createUserMessage({ + content: 'Continue from where you left off.', + isMeta: true, + }), + ]) + filteredMessages.push(continuationMessage!) + turnInterruptionState = { + kind: 'interrupted_prompt', + message: continuationMessage!, + } +} +``` +系统会强行在末尾追加一条虚拟的用户消息:`"Continue from where you left off."`。这样,不规范的结尾被抹平了(因为现在是 user 发起的新一轮),并且 LLM 被自然引导去阅读上文并在上次崩溃的点继续执行。 + +## 三、Qwen Code 的现状与改进路径 (P0 优先级) + +Qwen Code 当前缺乏这一套复杂的异常恢复机制。一旦 Node 进程退出,会话状态(除了偶尔残留在外部平台的上下文)在本地内存中即宣告丢失。 + +### 改进阶段: + +#### 阶段 1:增量式持久化存储 +- 为 Qwen Code 引入 Append-only JSONL 会话存储机制。每一轮交互实时写入 `.qwen/sessions/.jsonl`。 +- 采用追加写入避免崩溃瞬间的写入损坏。 + +#### 阶段 2:恢复与清洗模块 (Recovery Service) +- 编写 `packages/core/src/utils/conversationRecovery.ts`。 +- 实现 `filterUnresolvedToolUses`:如果最后一个消息里包含了 `tool_calls` 但是后续数组中没有紧跟的 `tool_messages` (结果),必须从 `assistant` 消息中将其切除。 + +#### 阶段 3:合成续行与自动启动 +- 在 CLI 启动入口(`cli.ts` 或 `agent.ts`)检测到未完成的会话恢复时,自动应用三态中断检测逻辑。 +- 对于 `interrupted_turn`,自动推入 `[{ role: "user", content: "Continue from where you left off." }]`,绕过等待用户输入的阶段,直接触发大模型推理,实现**断点续传**(无缝重启)。 + +## 四、改进收益 +- **用户体验**:解决了 "跑了 10 分钟的重构由于 OOM 突然终止,不得不重头再来" 的痛点。 +- **稳定性**:通过彻底剔除孤立的 `tool_use`,消除了长对话中隐秘且频繁的 API 400 格式错误。 +- **Agent 连续性**:配合未来可能引入的 Swarm 和后台代理模式,崩溃恢复将是代理持久存活的基石。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 7c20fafa..7f21a54c 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -40,7 +40,7 @@ | **P0** | [Mid-Turn Queue Drain](./input-queue-deep-dive.md) — Agent 执行中途注入用户输入,无需等整轮结束 [↓](./qwen-code-improvement-report-p0-p1-core.md#item-6) | 推理循环内无队列检查 | 中 | [PR#2854](https://github.com/QwenLM/qwen-code/pull/2854) | | **P0** | [多层上下文压缩](./context-compression-deep-dive.md) — 自动裁剪旧工具结果 + 摘要,用户无需手动 /compress [↓](./qwen-code-improvement-report-p0-p1-core.md#item-1) | 仅单一 70% 手动压缩 | 中 | — | | **P0** | [Fork Subagent](./fork-subagent-deep-dive.md) — Subagent 继承完整对话上下文,共享 prompt cache 省 80%+ 费用 [↓](./qwen-code-improvement-report-p0-p1-core.md#item-2) | Subagent 必须从零开始 | 中 | — | -| **P0** | 会话崩溃恢复与中断检测 — 3 种中断状态检测 + 合成续行 + 全量恢复 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-7) | 无崩溃恢复 | 大 | — | +| **P0** | [会话崩溃恢复与中断检测](./crash-recovery-deep-dive.md) — 3 种中断状态检测 + 合成续行 + 全量恢复 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-7) | 无崩溃恢复 | 大 | — | | **P1** | [Speculation](../tools/claude-code/10-prompt-suggestions.md) — 预测用户下一步并提前执行,Tab 接受零延迟 [↓](./qwen-code-improvement-report-p0-p1-core.md#item-3) | 已实现但默认关闭 | 小 | [PR#2525](https://github.com/QwenLM/qwen-code/pull/2525) ✓ | | **P1** | [会话记忆](./memory-system-deep-dive.md) — 关键决策/文件结构自动提取,新 session 自动注入 [↓](./qwen-code-improvement-report-p0-p1-core.md#item-4) | 仅简单笔记工具 | 大 | — | | **P1** | [Auto Dream](./memory-system-deep-dive.md) — 后台 agent 自动合并去重过时记忆 [↓](./qwen-code-improvement-report-p0-p1-core.md#item-5) | 缺失 | 中 | — | From da79ffbf4e9844ce43eafc3a648510432b7eb3d0 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:29:55 +0800 Subject: [PATCH 03/11] docs: add deep dive report on streaming tool execution pipeline --- .../qwen-code-improvement-report.md | 2 +- .../streaming-tool-execution-deep-dive.md | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/streaming-tool-execution-deep-dive.md diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 7f21a54c..3219b0c5 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -60,7 +60,7 @@ | **P1** | [Remote Control Bridge](./remote-control-bridge-deep-dive.md) — 从手机/浏览器驱动本地终端 session [↓](./qwen-code-improvement-report-p0-p1-platform.md#item-7) | 缺失 | 大 | — | | **P1** | /teleport — Web session → 终端 session 双向迁移 [↓](./qwen-code-improvement-report-p0-p1-platform.md#item-8) | 缺失 | 大 | — | | **P1** | GitLab CI/CD — 官方 GitLab pipeline 集成 [↓](./qwen-code-improvement-report-p0-p1-platform.md#item-9) | 缺失 | 中 | — | -| **P1** | 流式工具执行流水线 — API 流式返回 tool_use 时立即开始执行,不等完整响应 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-1) | 等完整响应后执行 | 中 | — | +| **P1** | [流式工具执行流水线](./streaming-tool-execution-deep-dive.md) — API 流式返回 tool_use 时立即开始执行,不等完整响应 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-1) | 等完整响应后执行 | 中 | — | | **P1** | 文件读取缓存 + 批量并行 I/O — 1000 条 LRU + mtime 失效 + 32 批并行 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-2) | 无缓存,顺序读取 | 小 | — | | **P1** | 记忆/附件异步prefetch — 工具执行期间并行搜索相关记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-3) | 无prefetch | 中 | — | | **P1** | Token Budget 续行与自动交接 — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | diff --git a/docs/comparison/streaming-tool-execution-deep-dive.md b/docs/comparison/streaming-tool-execution-deep-dive.md new file mode 100644 index 00000000..2c89fe57 --- /dev/null +++ b/docs/comparison/streaming-tool-execution-deep-dive.md @@ -0,0 +1,81 @@ +# Qwen Code 改进建议 — 流式工具执行流水线 (Streaming Tool Execution Pipeline) + +> 核心洞察:当大模型在一个回合中连续输出多个工具调用(Tool Calls)时,Qwen Code 必须等待模型将整个回合的所有文本和 JSON 全部生成完毕后,才开始串行或并行执行工具;而 Claude Code 实现了一个“流式工具执行流水线”(Streaming Tool Executor),在模型生成流的过程中,只要解析出一个工具调用,就立即异步启动执行,极大缩短了端到端延迟。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构对比与性能瓶颈 + +### 1. Qwen Code 的当前实现:阻塞式执行 +在 `packages/core/src/agents/runtime/agent-core.ts` 中,Qwen Code 的主运行循环 `runReasoningLoop` 是这样处理模型响应流的: + +```typescript +// Qwen Code: 等待完整流结束后再执行 +const functionCalls: FunctionCall[] = []; +for await (const streamEvent of responseStream) { + // ... 收集所有的 functionCalls + if (resp.functionCalls) functionCalls.push(...resp.functionCalls); +} + +// 整个响应生成完毕后,才开始批量处理 +if (functionCalls.length > 0) { + currentMessages = await this.processFunctionCalls(functionCalls, ...); +} +``` + +**性能损耗分析**: +假设模型生成 5 个工具调用(例如连续读取 5 个不同文件)需要 3 秒钟,而这 5 个文件的 I/O 操作总共需要 1 秒钟。 +在 Qwen Code 的架构下,总耗时为:`模型生成时间 (3s) + 工具执行时间 (1s) = 4s`。 +前几个解析完毕的工具在排队干等模型生成完最后的话语。 + +### 2. Claude Code 的解决方案:流水线并发 +在 `services/tools/StreamingToolExecutor.ts` 和 `query.ts` 中,Claude Code 通过引入异步并发流水线彻底消灭了这段干等的时间。 + +它在流式解析的过程中拦截工具块(Tool Use Block): +```typescript +// Claude Code 的处理逻辑简述 +const streamingToolExecutor = new StreamingToolExecutor(...); + +// 在 API Stream 回调中: +onStreamChunk(chunk => { + if (chunk.isToolUseBlockCompleted) { + // 立即加入队列并尝试触发执行! + streamingToolExecutor.addTool(toolBlock, message); + } +}); + +// 在外部并发获取已经完成的结果 +for (const result of streamingToolExecutor.getCompletedResults()) { + // 渲染结果或装填进下一轮 Prompt +} +``` +`StreamingToolExecutor` 维护了一个执行状态机(`queued` | `executing` | `completed`)。当判断一个工具是并发安全的(`isConcurrencySafe: true`,例如只读工具),它会在模型还在生成后续字符的同时,在后台发起子进程或异步 I/O 操作。 + +**性能优化结果**: +同样是生成 5 个工具耗时 3s,执行耗时 1s。 +在 Claude Code 的流水线架构下,前几个工具在第 0.5s、1.0s 时就已经开始执行了,总耗时被压缩到了约 `max(模型生成时间 3s, 首工具生成时间+所有执行时间) ≈ 3s ~ 3.5s`。 +**端到端延迟直接缩减了约 30% 到 50%(在多工具重度探索场景下尤为明显)。** + +## 二、Qwen Code 的改进路径 (P1 优先级) + +为了达到接近"零延迟"的丝滑体验,Qwen Code 需要重构 Agent 运行时的事件循环与工具调度器。 + +### 阶段 1:流式 JSON 拦截与触发器 +1. 修改 `agent-core.ts` 中的流处理逻辑(或者在更底层的流合并层)。不能只维护简单的 `functionCalls` 数组。 +2. 引入 `StreamingToolCallParser`,在 `streamEvent.type === 'chunk'` 期间,一旦某个 `functionCall` 的 JSON 参数对象闭合且有效,立即触发 `TOOL_CALL_READY` 事件。 + +### 阶段 2:重构 CoreToolScheduler 为动态队列 +1. 当前的 `CoreToolScheduler` 是在收集齐所有 Calls 后一次性初始化的(`const scheduler = new CoreToolScheduler(...)`)。 +2. 需要将其重构为长生命周期的动态队列(类似 Claude Code 的 `StreamingToolExecutor`)。它应当拥有 `.addTool(call)` 方法。 +3. 每当 `TOOL_CALL_READY` 被触发,立即通过 `.addTool` 压入调度器。调度器根据并发策略(是否为只读命令等)决定是立即执行还是挂起等待。 + +### 阶段 3:UI 渲染解耦 +1. CLI 与 TUI 组件需要能够响应增量抛出的 `TOOL_RESULT`,而不是等待整个 `processFunctionCalls` 返回。 +2. 这样用户可以看到 Agent 边写字(思考),状态栏里的 Spinner 边显示某个工具已经执行完毕并反馈了结果。 + +## 三、改进收益评估 +- **实现成本**:中。需要改造核心事件循环(异步生成器改造),约 300 - 500 行核心代码变动,对稳定性和竞态条件控制要求较高。 +- **直接收益**: + 1. **显著降低延迟感**:多工具调用的响应速度极大提升。 + 2. **提高吞吐量**:使得 Agent 探索代码库(如并行多次执行 `grep` 或 `read_file`)的速度达到物理极限。 + 3. **平滑的终端体验**:视觉上完全消除了"停顿-集中爆发现象"。 \ No newline at end of file From eeb56fc186d4683c8ac71e79847353512ad82106 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:32:32 +0800 Subject: [PATCH 04/11] docs: add deep dive report on file read cache and parallel IO --- docs/comparison/file-read-cache-deep-dive.md | 87 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/file-read-cache-deep-dive.md diff --git a/docs/comparison/file-read-cache-deep-dive.md b/docs/comparison/file-read-cache-deep-dive.md new file mode 100644 index 00000000..af1afb79 --- /dev/null +++ b/docs/comparison/file-read-cache-deep-dive.md @@ -0,0 +1,87 @@ +# Qwen Code 改进建议 — 文件读取缓存与批量并行 I/O (File Read Cache & Parallel I/O) + +> 核心洞察:代码 Agent 最频繁的底层操作就是文件 I/O。当 Agent 需要综合上下文分析 10 个文件,或者在一个回合中修改某个文件后立即重读该文件以验证时,缓慢的磁盘读取会极大拖慢响应速度。Claude Code 通过内存 LRU 缓存、mtime 自动失效机制以及 `Promise.all` 批量并发,将这部分耗时压缩到了极致;而 Qwen Code 目前使用的是无缓存的顺序(串行)读取。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、实现差异与性能分析 + +### 1. Qwen Code 的当前实现:串行、无缓存 +在 `packages/core/src/utils/readManyFiles.ts` 中,Qwen Code 处理多文件读取的核心逻辑如下: + +```typescript +// Qwen Code: 使用 for...of 串行等待每一个读取完成 +for (const rawPattern of inputPatterns) { + const fullPath = path.resolve(projectRoot, normalizedPattern); + const stats = fs.existsSync(fullPath) ? fs.statSync(fullPath) : null; + + // ... + if (stats?.isFile() && !seenFiles.has(fullPath)) { + seenFiles.add(fullPath); + // 阻塞式的串行 await + const readResult = await readFileContent(config, fullPath); + // ... + } +} +``` +**瓶颈**: +- **串行 I/O**:读取 30 个文件,总耗时为 `t1 + t2 + ... + t30`。如果遇到机械硬盘或远程挂载目录(如 WSL、NFS),延迟会被放大几十倍。 +- **热点文件无缓存**:在多轮对话中,Agent 可能反复查阅同一个核心文件(如 `package.json` 或 `agent-core.ts`),Qwen Code 每次都会重新触发完整的磁盘读取。 +- **主线程阻塞**:部分底层操作(如 `fs.statSync`)还在使用同步方法,会短暂阻塞 Node.js 事件循环。 + +### 2. Claude Code 的解决方案:三层优化 +Claude Code 在 `utils/fileReadCache.ts` 以及文件搜索相关逻辑中,打出了一套性能组合拳: + +#### 第一层:LRU 内存缓存与 Mtime 失效 (`FileReadCache`) +它在进程内维护了一个单例的 LRU Map(上限 1000 条),键为文件路径。 +```typescript +// Claude Code: fileReadCache.ts +const stats = fs.statSync(filePath); +const cachedData = this.cache.get(cacheKey); + +// 如果 mtime 没变,直接返回内存数据,磁盘开销降至 0 +if (cachedData && cachedData.mtime === stats.mtimeMs) { + return { content: cachedData.content, encoding: cachedData.encoding }; +} +``` +这保证了“一旦被修改(mtime变化),缓存立刻失效”,而“未修改的频繁读取,耗时为 0”。 + +#### 第二层:批量并发读取 +在需要读取多文件时,它采用了分批(Batching)加 `Promise.all` 并发: +```typescript +// Claude Code +const READ_BATCH_SIZE = 32; +// 对每批 32 个文件同时发起异步读取,耗时等于最慢的那个文件,而不是总和 +await Promise.all(batch.map(file => readFile(file))); +``` + +#### 第三层:并发获取元数据 +不仅是读内容,对于大目录扫描(需要获取几百个文件的 stat),也是用并发: +```typescript +// 并发 stat 检查修改时间 +await Promise.all(filePaths.map(lstat)); +``` + +## 二、Qwen Code 的改进路径 (P1 优先级) + +为了优化大中型代码库的探索速度和多轮交互的延迟,Qwen Code 需要重构底层文件系统交互层。 + +### 阶段 1:引入 FileReadCache (内存缓存层) +1. 在 `packages/core/src/utils/` 下新建 `fileReadCache.ts`。 +2. 实现基于 `mtimeMs`(修改时间戳)的缓存校验逻辑,最大缓存数限制在 1000 左右防止 OOM。 +3. 改造 `readFileContent` 优先走 `fileReadCache.readFile()`。 + +### 阶段 2:改造串行 I/O 为并发 (Concurrency) +1. 梳理 `readManyFiles.ts`。对于目录遍历可以保留顺序或队列,但对于明确指定的 `inputPatterns` 文件列表,应该先通过并发 `fs.promises.stat` 过滤出有效文件。 +2. 随后使用 `Promise.all(files.map(f => readFileContent(f)))` 进行并发提取。 +3. 建议设置合理的并发上限(如 `p-limit` 或固定批次 32),防止同时打开过多文件描述符抛出 `EMFILE` 错误。 + +### 阶段 3:解阻塞主线程 +1. 盘点整个项目中的 `fs.statSync`、`fs.readFileSync`(特别是在 `getFolderStructure.ts` 和 `workspaceContext.ts` 这类高频热点中)。 +2. 将非初始化阶段的 Sync 操作全部替换为异步 `promises` API,避免在文件 I/O 期间冻结终端 UI 的渲染或键盘事件接收。 + +## 三、改进收益评估 +- **实现成本**:低到中等。涉及部分底层工具类改动,风险可控(只需做好并发控制和缓存一致性)。 +- **直接收益**: + 1. **显著缩短等待**:阅读多文件或全目录时的速度理论上提升几倍至数十倍(取决于磁盘和并发数)。 + 2. **消除无谓 IO**:在反复 Edit-Read 的开发循环中,极大地减轻了磁盘压力,让交互响应像读内存一样迅速。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 3219b0c5..af7a8af2 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -61,7 +61,7 @@ | **P1** | /teleport — Web session → 终端 session 双向迁移 [↓](./qwen-code-improvement-report-p0-p1-platform.md#item-8) | 缺失 | 大 | — | | **P1** | GitLab CI/CD — 官方 GitLab pipeline 集成 [↓](./qwen-code-improvement-report-p0-p1-platform.md#item-9) | 缺失 | 中 | — | | **P1** | [流式工具执行流水线](./streaming-tool-execution-deep-dive.md) — API 流式返回 tool_use 时立即开始执行,不等完整响应 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-1) | 等完整响应后执行 | 中 | — | -| **P1** | 文件读取缓存 + 批量并行 I/O — 1000 条 LRU + mtime 失效 + 32 批并行 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-2) | 无缓存,顺序读取 | 小 | — | +| **P1** | [文件读取缓存 + 批量并行 I/O](./file-read-cache-deep-dive.md) — 1000 条 LRU + mtime 失效 + 32 批并行 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-2) | 无缓存,顺序读取 | 小 | — | | **P1** | 记忆/附件异步prefetch — 工具执行期间并行搜索相关记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-3) | 无prefetch | 中 | — | | **P1** | Token Budget 续行与自动交接 — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | | **P1** | 同步 I/O 异步化 — readFileSync/statSync 替换为 async,解阻塞事件循环 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-5) | 多处 readFileSync | 中 | — | From 2bd70bf285066acb595101ddcf18064e1188fa9b Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:35:18 +0800 Subject: [PATCH 05/11] docs: add deep dive report on token budget continuation and compaction --- .../qwen-code-improvement-report.md | 2 +- .../token-budget-continuation-deep-dive.md | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/token-budget-continuation-deep-dive.md diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index af7a8af2..e68d576b 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -63,7 +63,7 @@ | **P1** | [流式工具执行流水线](./streaming-tool-execution-deep-dive.md) — API 流式返回 tool_use 时立即开始执行,不等完整响应 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-1) | 等完整响应后执行 | 中 | — | | **P1** | [文件读取缓存 + 批量并行 I/O](./file-read-cache-deep-dive.md) — 1000 条 LRU + mtime 失效 + 32 批并行 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-2) | 无缓存,顺序读取 | 小 | — | | **P1** | 记忆/附件异步prefetch — 工具执行期间并行搜索相关记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-3) | 无prefetch | 中 | — | -| **P1** | Token Budget 续行与自动交接 — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | +| **P1** | [Token Budget 续行与自动交接](./token-budget-continuation-deep-dive.md) — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | | **P1** | 同步 I/O 异步化 — readFileSync/statSync 替换为 async,解阻塞事件循环 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-5) | 多处 readFileSync | 中 | — | | **P1** | Prompt Cache 分段与工具稳定排序 — static/dynamic 分界 + 内置工具前缀 + schema 锁定 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-6) | 无分段缓存 | 中 | — | | **P1** | API 指数退避与降级重试 — 10 次退避 + 529 模型降级 + 401 token 刷新 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-8) | 仅配置重试次数 | 中 | — | diff --git a/docs/comparison/token-budget-continuation-deep-dive.md b/docs/comparison/token-budget-continuation-deep-dive.md new file mode 100644 index 00000000..0c1c7b7b --- /dev/null +++ b/docs/comparison/token-budget-continuation-deep-dive.md @@ -0,0 +1,70 @@ +# Qwen Code 改进建议 — Token Budget 续行与分层压缩 (Token Budget Continuation & Compaction) + +> 核心洞察:对于自动执行复杂任务(如跨多文件重构)的 Agent,经常会遇到单次对话上下文(Context Window)爆满,或者单次输出(Output Tokens)被截断的情况。Claude Code 采用了一套极其成熟的“预算续行预警(Token Budget Continuation)+ 多级后备压缩(Layered Compaction)”机制,确保大任务能够不间断完成;而 Qwen Code 目前只有粗暴的 70% 一次性截断压缩,缺乏续行与降级策略。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构对比与断点分析 + +### 1. Qwen Code 的当前实现:一刀切的 70% 压缩 +在 `packages/core/src/services/chatCompressionService.ts` 中,Qwen Code 的策略非常简单: + +```typescript +export const COMPRESSION_TOKEN_THRESHOLD = 0.7; +// 当判断 tokens > max_tokens * 0.7 时: +// 1. 强行砍掉前 70% 的历史记录 +// 2. 用 LLM 对这 70% 记录生成一份 `` 摘要 +// 3. 带着摘要和后 30% 的记录继续 +``` + +**目前的痛点**: +- **无输出预警与续行**:Qwen Code 无法感知自身是否接近单次生成的输出上限(如 8K 限制)。如果 Agent 正在输出大量代码修改,到达 8K 截断时,任务会直接失败,没有任何“自动接力”机制。 +- **僵硬的压缩时机**:一旦触发 70% 阈值,立即发起一次昂贵的全局摘要请求。摘要会丢失精确的文件路径、工具调用的具体参数和执行细节,导致模型在压缩后立刻产生“失忆”性幻觉(例如忘记刚读过的文件内容)。 + +### 2. Claude Code 的解决方案:预算管理与递减检测 +Claude Code 在 `query/tokenBudget.ts` 和 `services/compact/` 中建立了一套名为 Token Budget 的系统: + +#### 第一道防线:90% 续行预警 (Continuation) +它不轻易丢弃上下文。系统会监控每轮交互消耗的 Token。当上下文接近上限的 90% (`COMPLETION_THRESHOLD = 0.9`) 时,引擎会在用户的最后一条消息背后,**悄悄注入一段合成的警告 Prompt**: +> "You have used 90% of your context budget. Please finish your current immediate thought and return a response. I will continue your execution in the next turn." + +模型收到这个预警后,会主动保存关键状态并优雅地结束当前轮次,然后在下一次被引擎自动唤醒继续,从而**将一个超大任务拆分为多个无缝衔接的子回合**,完美避开 Token 截断。 + +#### 第二道防线:收益递减断路器 (Diminishing Returns Detection) +如果 Agent 陷入死循环,不断触发续行,Claude Code 有一个断路器: +```typescript +const isDiminishing = + tracker.continuationCount >= 3 && + deltaSinceLastCheck < 500 && + tracker.lastDeltaTokens < 500 +``` +连续 3 次增量不足 500 Tokens 时,系统判定它卡住了,强制中断续行链。 + +#### 第三道防线:分层压缩回退 (Layered Compaction) +当真的必须压缩上下文时,Claude Code 不会立刻做全局摘要,而是分 3 层进行无损/有损降级: +1. **Micro Compact (微压缩)**:遍历历史记录,静默删除过去的所有临时工具输出(如大段的 `BashTool`、`GrepTool` 结果),只保留命令本身。这通常能瞬间释放 50% 的空间且无损! +2. **Session Memory Compact**:清理内存中的附件和不再需要的缓存结构。 +3. **Full Compact (全量压缩)**:只有前两步无效时,才回退到类似 Qwen Code 的历史摘要截断(最后手段)。 + +## 二、Qwen Code 的改进路径 (P1 优先级) + +Qwen Code 要支撑复杂的企业级代码生成,必须废弃单层的 70% 截断,重构为柔性的预算管理。 + +### 阶段 1:引入续行预警机制 (Nudge/Continuation) +1. 新建 `packages/core/src/services/tokenBudgetService.ts`。 +2. 在 `agent-core.ts` 的推理循环末尾增加拦截:在模型生成的 Token 总数达到 85%-90% 时,通过 `TOOL_RESULT` 或系统消息注入 `nudgeMessage`,引导模型进入下一轮。 + +### 阶段 2:实现 Micro Compact (无损瘦身) +1. 拦截并识别占用 Token 最多的部分(通常是长命令的输出 `stdout`、长文件读取)。 +2. 在触发全量 `compressContext` 之前,先执行一次清理循环:将旧回合(非当前回合)的 `tool_response` 里的长文本替换为 `[Output elided to save tokens]`。 +3. 往往执行完 Micro Compact,Token 水位就会降到 30%,根本不需要耗费时间去做不精准的全量摘要。 + +### 阶段 3:收益递减与无限死循环防护 +1. 追踪每次 Agent 自动续行的增量 Token (`delta_tokens`)。 +2. 设定阈值(例如连续 3 个回合都没推进实际进度),强制停止并交还控制权给人类用户,防止浪费 API 额度和费用。 + +## 三、改进收益评估 +- **实现成本**:中等。核心在于修改 Prompt 拦截和改造 `chatCompressionService.ts` 的执行流。 +- **直接收益**: + 1. **彻底解决大任务截断问题**:Agent 终于可以处理动辄几万行修改的巨型重构,不受单次 max_tokens 的物理限制。 + 2. **大幅降低压缩后幻觉**:优先采用清理工具日志的 Micro Compact,上下文的“精确记忆”得以保留,Agent 变聪明的直观感受极强。 \ No newline at end of file From 9feaf78c895a77388426c57f48c401d077193bb3 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:36:50 +0800 Subject: [PATCH 06/11] docs: add deep dive report on prompt cache optimization --- .../prompt-cache-optimization-deep-dive.md | 64 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/prompt-cache-optimization-deep-dive.md diff --git a/docs/comparison/prompt-cache-optimization-deep-dive.md b/docs/comparison/prompt-cache-optimization-deep-dive.md new file mode 100644 index 00000000..6cfecec1 --- /dev/null +++ b/docs/comparison/prompt-cache-optimization-deep-dive.md @@ -0,0 +1,64 @@ +# Qwen Code 改进建议 — Prompt Cache 分段与工具稳定排序 (Prompt Cache Optimization) + +> 核心洞察:现代大模型(如 Claude 3.5、Gemini 1.5 等)普遍支持 Prompt Caching(提示词缓存),能够为长且固定的前缀上下文节省高达 90% 的输入 Token 费用,并使首字响应时间减半。但 Prompt Cache 对前缀的**字节级一致性**要求极高。Claude Code 设计了极致的分段缓存、稳定排序与 Schema 锁定策略;而 Qwen Code 的 System Prompt 和工具列表容易发生微小抖动,导致极高的缓存未命中率。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、为什么 Prompt Cache 容易失效? + +在 API 调用时,Agent 的前缀通常由以下两部分构成(约占几万 Tokens): +1. **System Prompt (系统提示词)**:包含大段行为准则、项目规范、当前状态。 +2. **Tools Schema (工具定义)**:包含所有工具名称、描述和 JSON Schema。 + +**Qwen Code 的现状问题**: +在 `packages/core/src/core/client.ts` (`getMainSessionSystemInstruction`) 中,Qwen Code 会将动态状态(如当前工作目录、当前打开的文件、动态的内存片段)直接拼接在整个 System Prompt 中发送。 +此外,工具数组的顺序没有严格保证(如果加载了外部 MCP 工具,顺序可能会变化)。 +**后果**: +只要前缀中有一个字符发生变化(例如当前活跃的文件变了),整个数万 Token 的前缀缓存就会被击穿,API 每次都会重新收取全量输入费用,且响应迟缓。 + +## 二、Claude Code 的解决方案:分界与锁定 + +在 Claude Code 的源码中(`utils/api.ts`, `utils/toolSchemaCache.ts`, `services/api/promptCacheBreakDetection.ts`),它为极致的缓存命中率做了四层设计: + +### 1. Static / Dynamic 系统提示词分界 +Claude Code 显式地将系统提示词分为两半: +- **Static Section (静态区)**:包含永不改变的系统身份、核心能力、通用指令规则。 +- **Dynamic Section (动态区)**:包含当前时间、活跃文件、临时的用户偏好等。 + +它会在两者的交界处插入一个 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记,并且**仅对静态区域和其上方的工具定义**应用 `cache_control: { type: 'ephemeral' }` 缓存断点。 +这样,动态区的内容随意改变,都不会影响前面数万 Token 的缓存命中。 + +### 2. 工具列表的稳定排序 (Stable Sorting) +工具 Schema 是最长的前缀之一(十几 K Token)。 +Claude Code 在 `utils/toolPool.ts` 中强制所有**内置工具(Built-in Tools)**按照字母表顺序**固定排列**在数组最前面。 +对于动态加载的 MCP 工具或项目局部工具,则始终**追加**在内置工具之后。这样可以确保“内置工具前缀”永远命中缓存。 + +### 3. 工具 Schema 的首帧锁定 +大型 Agent 的工具 Schema 中可能会因为 A/B 测试(GrowthBook)或配置热更新而发生微小变动。 +Claude Code 设计了 `toolSchemaCache.ts`。在会话(Session)的第一次调用时,它会深度冻结(Snapshot)并缓存当前的 Tool Schema。即使后续环境变量或开关发生改变,只要不重启 Session,它发送给 API 的 Schema 就保证**绝对字节级一致**。 + +### 4. 自动缓存击穿检测 +Claude Code 包含一个 `promptCacheBreakDetection.ts` 服务。它会监控每次 API 响应中的 `cache_read_input_tokens` 指标。如果发现原本应该命中的缓存突然变成 0,它会在内部日志中记录警告,帮助开发者迅速定位是哪一段 Prompt 引入了抖动。 + +## 三、Qwen Code 的改进路径 (P1 优先级) + +为了将长会话的 API 成本砍掉一大半并加速响应,Qwen Code 必须重构 Prompt 组装逻辑。 + +### 阶段 1:拆分核心 System Prompt +1. 重构 `getCoreSystemPrompt()` (位于 `packages/core/src/core/prompts.ts`)。 +2. 将原本混合在一起的 `userMemory`、环境状态等剥离到数组的后半部分。 +3. 确保前半部分的指令字符串**完全纯净、静态**。 + +### 阶段 2:强制工具数组稳定排序 +1. 在 `client.ts` 或 `agent-core.ts` 组装 `toolsList` 时,增加 `sort()` 逻辑。 +2. 确保 `Qwen Code` 自带的基础工具(如 `read_file`, `shell`, `todo_write`)具有固定的索引位置。 + +### 阶段 3:注入 Cache Control 标记 +1. 对于支持 Prompt Caching 的模型 API(目前主要是 Anthropic Claude 系列,以及部分提供兼容 API 的国产模型)。 +2. 在组装 `messages` 数组发送前,对 Static System Prompt block 和 Tools block 附加底层协议要求的 Cache 断点标记(例如 Anthropic 的 `cache_control`)。 + +## 四、改进收益评估 +- **实现成本**:中等。需要深入了解不同 LLM 供应商的 Prompt Caching 协议。 +- **直接收益**: + 1. **巨额成本节约**:将平均 20K~50K Tokens 的全量输入费用转化为廉价的 Cache Read 费用,API 成本直降 50%-80%。 + 2. **响应速度起飞**:模型省略了庞大系统设定的 Context 阶段,首 Token 吐出延迟(TTFT)通常可缩短一半以上。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index e68d576b..4b95da3d 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -65,7 +65,7 @@ | **P1** | 记忆/附件异步prefetch — 工具执行期间并行搜索相关记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-3) | 无prefetch | 中 | — | | **P1** | [Token Budget 续行与自动交接](./token-budget-continuation-deep-dive.md) — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | | **P1** | 同步 I/O 异步化 — readFileSync/statSync 替换为 async,解阻塞事件循环 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-5) | 多处 readFileSync | 中 | — | -| **P1** | Prompt Cache 分段与工具稳定排序 — static/dynamic 分界 + 内置工具前缀 + schema 锁定 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-6) | 无分段缓存 | 中 | — | +| **P1** | [Prompt Cache 分段与工具稳定排序](./prompt-cache-optimization-deep-dive.md) — static/dynamic 分界 + 内置工具前缀 + schema 锁定 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-6) | 无分段缓存 | 中 | — | | **P1** | API 指数退避与降级重试 — 10 次退避 + 529 模型降级 + 401 token 刷新 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-8) | 仅配置重试次数 | 中 | — | | **P1** | 优雅关闭序列与信号处理 — SIGINT/SIGTERM + 清理注册 + 5s failsafe [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-9) | 无信号处理 | 中 | — | | **P1** | 反应式压缩 — prompt_too_long 自动裁剪最早消息 + 重试 3 次 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-10) | 无被动恢复 | 中 | — | From 3d044d239075554c9b46930c4f34f1437b60197e Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:38:13 +0800 Subject: [PATCH 07/11] docs: add deep dive report on in-process agent isolation --- .../in-process-agent-isolation-deep-dive.md | 86 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/in-process-agent-isolation-deep-dive.md diff --git a/docs/comparison/in-process-agent-isolation-deep-dive.md b/docs/comparison/in-process-agent-isolation-deep-dive.md new file mode 100644 index 00000000..c6a2ecb1 --- /dev/null +++ b/docs/comparison/in-process-agent-isolation-deep-dive.md @@ -0,0 +1,86 @@ +# Qwen Code 改进建议 — InProcess 同进程多 Agent 隔离 (InProcess Agent Isolation) + +> 核心洞察:当代码 Agent 发展到多 Agent(Swarm)协作架构时,主 Agent 往往需要拉起(Spawn)子 Agent(Worker)来并行处理子任务。最轻量、最快捷的拉起方式就是**同进程拉起(In-Process)**。然而,Node.js 的单进程模型和大量使用的全局变量(Global State)极易导致多个运行中的 Agent 上下文互相污染(例如:工作目录冲突、日志串台、Token 计数混乱)。Claude Code 通过 Node.js 的 `AsyncLocalStorage` 实现了优雅的“并发沙盒隔离”;而 Qwen Code 目前在状态管理上存在全局泄漏隐患,无法安全支持真正的同进程多 Agent 运行。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、为什么多 Agent 需要上下文隔离? + +想象以下场景: +你让 Qwen Code “重构后端 API 并同时更新前端调用”。 +主 Agent 将任务分为两半,并在同一个 Node 进程中拉起了两个后台子 Agent: +- Agent A 处理后端(需将 `cwd` 设为 `./backend`,并使用特定的系统提示词)。 +- Agent B 处理前端(需将 `cwd` 设为 `./frontend`)。 + +**未隔离的致命后果**: +由于 Qwen Code 的大部分配置(如 `Config` 单例、当前工作目录 `process.cwd()`、Telemetry Token 累加器)是全局共享的: +1. Agent A 刚刚把工作目录切到 `./backend`,Agent B 就来读取文件,导致 Agent B 找不到 `./frontend` 下的文件(目录污染)。 +2. Agent A 报错抛出的日志,可能会被记录成 Agent B 的失败(标识符混淆)。 +3. 多个 Agent 同时更新同一个对话历史文件,引发 JSON 解析崩溃(文件锁冲突)。 + +## 二、Claude Code 的隔离方案:AsyncLocalStorage + +在 Claude Code 的源码中,它实现了一个高度成熟的 `InProcessBackend`(位于 `utils/swarm/backends/InProcessBackend.ts` 和 `spawnInProcess.ts`)。 + +它的核心隔离武器是 Node.js 原生的 `AsyncLocalStorage` (ALS)。 + +### 1. 什么是 AsyncLocalStorage? +ALS 允许我们在异步调用链中存储“局部全局变量”(类似于 Java/Thread 中的 `ThreadLocal`)。在 Node 单线程中,它可以让并发的不同 Promise 链拥有自己独立的数据副本。 + +### 2. Claude Code 中的应用 +```typescript +// Claude Code: utils/swarm/inProcessRunner.ts +import { AsyncLocalStorage } from 'async_hooks'; + +// 创建 Agent 的上下文沙盒 +const agentContextStorage = new AsyncLocalStorage(); + +export function runWithTeammateContext(context: TeammateContext, fn: () => Promise) { + // 在这个 fn 执行的整个生命周期(以及它 await 产生的所有子异步调用)中, + // getTeammateContext() 都会返回专属于当前子 Agent 的 context,而不是全局的。 + return agentContextStorage.run(context, fn); +} +``` + +**被隔离的关键上下文:** +- `cwd` 工作目录覆盖:主 Agent 也许在 `/root`,但 ALS 内的子 Agent `getCwd()` 会返回 `/root/backend`。 +- `agentId` 和 `teamName`:用于独立路由 IPC 消息。 +- `Telemetry` 和 `Workload` 标签:在 `services/analytics/metadata.ts` 和 `utils/workloadContext.ts` 中,日志会自动带上 ALS 中的当前 Agent 身份,绝不串台。 + +通过这套机制,Claude Code 能在单进程里同时运行 10 个子 Agent 且丝毫不乱。 + +## 三、Qwen Code 的改进路径 (P1 优先级) + +Qwen Code 当前的 `Config`、`workspaceContext` 和 `debugLogger` 设计过于依赖单例或显式参数传递,要走向 Swarm 时代,必须重构状态管理。 + +### 阶段 1:引入 ALS 上下文管理器 +1. 新建 `packages/core/src/core/agentContext.ts`。 +2. 定义 `AgentContext` 接口,包含:`agentId`、`role`、`cwd`、`tokenBudget`。 +3. 实例化 `new AsyncLocalStorage()` 并导出获取方法 `getCurrentAgentContext()`。 + +### 阶段 2:消除全局状态污染 +梳理 Qwen Code 中所有危险的“全局方法”,让它们优先尝试从 ALS 中取值: +1. **工作目录获取**:封装一个统一的 `getCwd()`。如果 `getCurrentAgentContext().cwd` 有值,则返回覆盖值,否则返回全局的 `config.getProjectRoot()`。 +2. **日志打印**:修改 `debugLogger.ts`。在拼接日志前缀时,自动追加 `[Agent: ${getCurrentAgentContext()?.agentId ?? 'Main'}]`。 +3. **Telemetry 计数**:修改 `uiTelemetryService.ts`,确保 Token 消耗分别累加到主/子 Agent 账上。 + +### 阶段 3:提供 Spawn 包裹器 +在未来实现 `/subagent` 命令或 Agent 的工具能力时,使用 ALS 包裹执行: +```typescript +async function spawnInProcessWorker(taskPrompt: string, overrideCwd: string) { + const workerContext = { agentId: generateId(), cwd: overrideCwd }; + + // 安全拉起! + return agentContextStorage.run(workerContext, async () => { + const workerAgent = new AgentInteractive(...); + await workerAgent.runReasoningLoop(...); + }); +} +``` + +## 四、改进收益评估 +- **实现成本**:中。需要全盘审查全局单例的使用情况。 +- **直接收益**: + 1. **解锁 Swarm 能力**:彻底消灭多 Agent 同进程运行的并发 BUG,这是向智能体集群进化的基础。 + 2. **高内聚低耦合**:无需在成百上千的函数签名中层层传递 `agentId` 或 `cwd` 参数,代码更干净。 + 3. **易于调试**:多并发环境下的错误日志能准确归因到具体的后台 Worker。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 4b95da3d..097fc4f3 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -75,7 +75,7 @@ | **P1** | Coordinator/Swarm 多 Agent编排 — Leader/Worker 团队 + 3 种执行后端 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-14) | 仅 Arena 竞赛 | 大 | — | | **P1** | [Task Management 任务协同与跨进程并发调度](./task-management-deep-dive.md) — 支持 blocks/blockedBy 的任务拓扑、跨进程安全锁与 Swarm 集成 | 仅提供简易无状态 TodoWriteTool | 大 | — | | **P1** | Agent 工具细粒度访问控制 — 3 层allowlist/denylist + per-agent 限制 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-15) | 全部或指定列表 | 中 | — | -| **P1** | InProcess 同进程多 Agent隔离 — AsyncLocalStorage 上下文隔离 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-16) | 全局状态可能泄漏 | 中 | — | +| **P1** | [InProcess 同进程多 Agent隔离](./in-process-agent-isolation-deep-dive.md) — AsyncLocalStorage 上下文隔离 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-16) | 全局状态可能泄漏 | 中 | — | | **P1** | Agent 记忆持久化 — user/project/local 3 级跨 session 记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-17) | 无跨 session 记忆 | 中 | — | | **P1** | Agent 恢复与续行 — SendMessage 继续已完成代理 + transcript 重建 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-18) | 执行完即销毁 | 中 | — | | **P1** | 系统提示模块化组装 — sections 缓存 + dynamic boundary + uncached 标记 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-19) | 单一字符串 | 中 | — | From 80eb354e0bc3ab72e85e9fefa008f9e7660e9e55 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:39:30 +0800 Subject: [PATCH 08/11] docs: add deep dive report on nested memory discovery and include directives --- .../nested-memory-include-deep-dive.md | 59 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/nested-memory-include-deep-dive.md diff --git a/docs/comparison/nested-memory-include-deep-dive.md b/docs/comparison/nested-memory-include-deep-dive.md new file mode 100644 index 00000000..5ecb2955 --- /dev/null +++ b/docs/comparison/nested-memory-include-deep-dive.md @@ -0,0 +1,59 @@ +# Qwen Code 改进建议 — @include 指令与嵌套记忆自动发现 (Modular Memory & Auto-Discovery) + +> 核心洞察:在一个大型的 Monorepo(单体仓库)中,前端(React/TypeScript)、后端(Go/Python)和文档目录通常有着截然不同的代码风格和最佳实践。如果将所有规则全挤在一个根目录的 `QWEN.md` 里,不仅浪费 Token,还会因为规则冲突导致大模型产生幻觉(例如在 Go 代码里错误应用了 React 的规范)。Claude Code 通过引入 `@include` 指令与“递归向上遍历查找”的嵌套记忆自动发现机制,完美解决了这个痛点,而 Qwen Code 目前欠缺这一精细化的规则挂载能力。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构缺陷与使用痛点 + +### 1. Qwen Code 的现状:单体配置困境 +Qwen Code 目前通常只读取项目根目录下的少量特定配置文件。 +- **配置割裂与臃肿**:假设你在维护一个全栈项目。你需要告诉 Agent:“前端要使用 `camelCase`,并使用 TailwindCSS;后端要使用 `snake_case`,不要使用 ORM,手写 SQL”。在现有的单文件配置模式下,这些冲突的指令只能揉在一起,导致 System Prompt 急剧膨胀。 +- **无模块化能力**:开发者不能像写代码 `import` 那样复用规范,多项目之间共享的通用规范(如安全准则)必须复制粘贴多次。 + +### 2. Claude Code 的解决方案:多级挂载与引用 +在 `utils/claudemd.ts` 中,Claude Code 实现了一套灵活的文档加载器系统: + +#### 机制一:目录级别的记忆自动发现 (Directory Traversal) +当你让 Agent 编辑 `src/frontend/components/Button.tsx` 时,Claude Code 不仅会读取全局配置,还会沿着目标文件所在的路径“**逐级向上爬**”: +1. 查找全局系统配置 (`/etc/claude-code/CLAUDE.md`) +2. 查找用户根目录 (`~/.claude/CLAUDE.md`) +3. 查找项目根目录 (`/project/CLAUDE.md`) +4. 查找沿途的特定目录 (`/project/src/frontend/CLAUDE.md`, `.claude/rules/*.md` 等) + +并且,优先级是**越具体的越近(后加载覆盖前加载)**。这意味着在处理前端组件时,Agent 会优先看到针对前端的专项定制化指令,不会被后端的指令干扰。 + +#### 机制二:@include 指令的动态引入 +如果不想依赖隐式的目录查找,Claude Code 还提供显式的 `@include` 指令。 +开发者可以在 `CLAUDE.md` 的任何文本节点(不在代码块内)写下: +```markdown +# 我们的全栈项目规范 +@./src/frontend/frontend_rules.md +@~/global_security_rules.md +``` +加载器会拦截这些指令(利用 `marked` 进行 Lex 解析提取 Tokens),并将引用的文件内容平铺(Inline)注入到上下文中。系统还硬编码了 `MAX_INCLUDE_DEPTH = 5` 防止循环引用死锁。 + +## 二、Qwen Code 的改进路径 (P1 优先级) + +要实现智能体的工业级落地,Qwen Code 必须支持配置的去中心化与模块化。 + +### 阶段 1:支持目录爬升自动加载 (Directory Climbing) +1. 拦截文件修改操作(如在 Tool 层或更底层的 Context 组装阶段)。 +2. 每当涉及某个具体文件路径时,计算该路径的祖先目录数组。 +3. 从上到下,依次读取 `.qwen.md` 或 `.qwen/rules/*.md` 并注入系统提示。 + +### 阶段 2:解析与支持 @include (Include Directive) +1. 编写或引入一个小型的 Markdown 解析器/正则表达式层,在读取 `QWEN.md` 时拦截以 `@` 开头的独立行。 +2. 支持 `@./` (相对当前配置文件) 和 `@~/` (相对用户 Home 目录) 两种路径。 +3. 递归解析这些文件,并拼接最终的纯文本 System Prompt。 + +### 阶段 3:缓存去重与安全防御 +1. 引入解析树去重机制(避免被包含的文件内部出现交叉包含引发栈溢出)。 +2. 只允许包含纯文本后缀的文件(如 `.txt`, `.md`),拒绝 `.bin`, `.exe` 等二进制文件读取以防止奇怪的文件系统错误。 + +## 三、改进收益评估 +- **实现成本**:中。需要改造配置加载阶段的逻辑。 +- **直接收益**: + 1. **巨幅节省 Token 成本**:按需加载让大模型的系统上下文变得精简、相关,消除不必要的输入计费。 + 2. **消除规则冲突**:Monorepo 下前后端协作再也不用担心大模型混淆命名规范或架构约定。 + 3. **增强企业级复用能力**:团队可以通过 `@~/` 引入全局的安全指南,真正做到架构规范的“代码化(Config as Code)”。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 097fc4f3..fea6a87a 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -80,7 +80,7 @@ | **P1** | Agent 恢复与续行 — SendMessage 继续已完成代理 + transcript 重建 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-18) | 执行完即销毁 | 中 | — | | **P1** | 系统提示模块化组装 — sections 缓存 + dynamic boundary + uncached 标记 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-19) | 单一字符串 | 中 | — | | **P1** | 系统提示内容完善 — OWASP 安全 + prompt injection检测 + 代码风格约束 + 输出格式 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-24) | 缺少具体指导 | 中 | — | -| **P1** | @include 指令与嵌套记忆发现 — @path 递归引用 + 文件操作触发目录遍历 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-20) | 无 @include/嵌套发现 | 中 | — | +| **P1** | [@include 指令与嵌套记忆发现](./nested-memory-include-deep-dive.md) — @path 递归引用 + 文件操作触发目录遍历 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-20) | 无 @include/嵌套发现 | 中 | — | | **P1** | 附件类型协议与令牌预算 — 40+ 类型 + per-type 预算 + 3 阶段有序执行 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-21) | 字符串拼接/无预算 | 中 | — | | **P1** | Thinking 块跨轮保留与空闲清理 — 活跃保留 + 1h 空闲清理 + latch 防缓存破坏 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-22) | 每轮独立/无清理 | 中 | [PR#2897](https://github.com/QwenLM/qwen-code/pull/2897) | | **P1** | 输出 Token 自适应升级 — 8K 默认 + max_tokens 截断时自动 64K 重试 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-23) | 固定值/不重试 | 小 | [PR#2898](https://github.com/QwenLM/qwen-code/pull/2898) | From 2b9a75330c60e43bd73c5c85cc275be7ab22066b Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:41:48 +0800 Subject: [PATCH 09/11] docs: add deep dive report on API backoff and fallback retry --- .../api-retry-fallback-deep-dive.md | 63 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/api-retry-fallback-deep-dive.md diff --git a/docs/comparison/api-retry-fallback-deep-dive.md b/docs/comparison/api-retry-fallback-deep-dive.md new file mode 100644 index 00000000..6590bc87 --- /dev/null +++ b/docs/comparison/api-retry-fallback-deep-dive.md @@ -0,0 +1,63 @@ +# Qwen Code 改进建议 — API 指数退避与降级重试 (API Backoff & Fallback Retry) + +> 核心洞察:代码 Agent 执行复杂任务通常需要连续几十次 API 调用。任何一次瞬态网络故障(如 500、502)、服务限流(429)或模型过载(529)都不应该导致整个长任务立即失败并丢失进度。Claude Code 拥有极其精细的网络错误处理、指数退避、429 尊重和 529 模型降级机制,保证了极高的稳定性;而 Qwen Code 的重试机制相对单薄,缺乏降级与特殊错误码的智能处理。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构对比与稳定性痛点 + +### 1. Qwen Code 现状:基础重试 +在 `packages/core/src/utils/retry.ts` 中,Qwen Code 提供了一个基础的 `retryWithBackoff` 函数: +- **最大次数**:默认 `maxAttempts: 7`。 +- **触发条件**:仅在 HTTP 状态码为 429 或 5xx 时重试。 +- **退避策略**:简单的基础时间乘以 2,加上 `30%` 的抖动 (Jitter)。 + +**目前的痛点与缺失**: +1. **不尊重 Retry-After**:当遭遇 HTTP 429 Rate Limit 时,服务端通常会在 Header 中返回 `retry-after`(告诉客户端应该等多少秒后再试)。Qwen Code 忽略了该字段,直接盲目重试,容易被服务端彻底封禁。 +2. **无模型降级 (Fallback)**:在晚高峰等服务过载(HTTP 529 Overloaded)时,盲目重试大模型(如 `qwen-max`)往往徒劳无功。如果任务直接失败,用户体验极差。 +3. **网络层异常未覆盖**:未特殊处理底层的 `ECONNRESET` 或 `EPIPE` 等 TCP 连接被重置的错误(这类错误通常需要禁用 HTTP Keep-Alive 重建连接)。 +4. **Token 过期无法自愈**:在耗时数小时的长会话中,如果遭遇 401/403,Qwen Code 会直接抛错。而它本应挂起当前请求,触发 Auth 模块刷新 Token 后再无缝重试。 + +### 2. Claude Code 解决方案:高弹性网络层 +Claude Code 的重试网关(位于 `services/api/withRetry.ts`)具有企业级的异常容错能力: + +#### 机制一:智能读取 Retry-After +```typescript +// Claude Code: 解析 429 限流 +if (status === 429 && headers.has('retry-after')) { + // 优先遵循服务端指导的等待时间,而不是自己的指数退避 + const delayMs = parseRetryAfter(headers.get('retry-after')); + await sleep(delayMs); +} +``` + +#### 机制二:529 过载降级 (FallbackTriggeredError) +当检测到服务端严重过载(如连续 3 次返回 529 错误)时,抛出专门的 `FallbackTriggeredError`,并**自动无缝降级到备用的小型模型**(如从 `claude-3-5-sonnet` 降级到 `claude-3-haiku`)继续执行当前不那么重要的工具输出总结任务。 +这保证了“降级总比彻底宕机强”。 + +#### 机制三:网络底座容错与持久化重试 +针对底层的 `ECONNRESET` / `UND_ERR_SOCKET`,Claude Code 知道这是底层的 Keep-Alive 遇到了失效连接,它会在重试时自动带上特殊的标志,让底层网关禁用 Keep-Alive 重建纯净的 TCP 连接。 +另外,它还支持配置“持久化重试模式(Persistent Retry)”——在 CI/CD 或后台无人值守环境下,它可以每隔 5 分钟重试一次,永远不轻易放弃任务。 + +## 二、Qwen Code 的改进路径 (P1 优先级) + +为了确保 Qwen Code 在不稳定的网络环境下依然能像一台“不知疲倦的机器”一样完成任务,我们需要升级网络重试层。 + +### 阶段 1:增强基础 Retry 逻辑 +1. 升级 `utils/retry.ts`,提升 `maxAttempts` 至 10 次。 +2. 在捕获 Error 时,深度解析 Axios/Fetch 的 `response.headers`。如果遇到状态码 `429`,提取 `retry-after` 字段,作为本次 `delay` 的绝对时间(如果该字段存在)。 + +### 阶段 2:底层网络异常与 Token 自愈 +1. 捕获特定的系统级错误码(如 `ECONNRESET`, `ETIMEDOUT`, `ENOTFOUND`)加入重试白名单。 +2. 处理 401 Unauthorized:拦截到 401 时,不要立即失败,而是触发一个 `eventEmitter.emit('TOKEN_REFRESH_REQUIRED')`。等待(await)凭证刷新器获取到新的 Token 后,修改请求头并重新发起请求。 + +### 阶段 3:引入模型降级 (Fallback) +1. 在 `Config` 中允许用户配置主模型和备用模型(如 `model: qwen-max, fallbackModel: qwen-turbo`)。 +2. 在 `client.ts` 的 `generateContent` 包装器中:如果遇到连续 N 次 529 或 500 服务器错误,捕获并捕获并捕获后,自动将请求的 `model` 参数替换为 `fallbackModel`,并在 TUI 上打印一条黄色警告:“主模型过载,已自动降级为 qwen-turbo 以保证任务继续”。 + +## 三、改进收益评估 +- **实现成本**:中。需要改造底层 HTTP 调用的拦截器和重试函数,代码量约 200 - 300 行。 +- **直接收益**: + 1. **极高的任务完成率**:长任务(如几十步的大型重构)再也不会因为中间某一次微小的网络抖动或服务端限流而前功尽弃。 + 2. **平台合规性**:尊重 API 的 `Retry-After`,大幅降低因为野蛮重试被云厂商账号封禁的风险。 + 3. **出色的弱网/晚高峰体验**:在 API 调用最拥堵的时间段,通过自动降级依然能保持 Agent 活跃运行。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index fea6a87a..7eb43cfa 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -66,7 +66,7 @@ | **P1** | [Token Budget 续行与自动交接](./token-budget-continuation-deep-dive.md) — 90% 续行 + 递减检测 + 分层压缩回退 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-4) | 70% 一次性压缩 | 中 | — | | **P1** | 同步 I/O 异步化 — readFileSync/statSync 替换为 async,解阻塞事件循环 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-5) | 多处 readFileSync | 中 | — | | **P1** | [Prompt Cache 分段与工具稳定排序](./prompt-cache-optimization-deep-dive.md) — static/dynamic 分界 + 内置工具前缀 + schema 锁定 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-6) | 无分段缓存 | 中 | — | -| **P1** | API 指数退避与降级重试 — 10 次退避 + 529 模型降级 + 401 token 刷新 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-8) | 仅配置重试次数 | 中 | — | +| **P1** | [API 指数退避与降级重试](./api-retry-fallback-deep-dive.md) — 10 次退避 + 529 模型降级 + 401 token 刷新 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-8) | 仅配置重试次数 | 中 | — | | **P1** | 优雅关闭序列与信号处理 — SIGINT/SIGTERM + 清理注册 + 5s failsafe [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-9) | 无信号处理 | 中 | — | | **P1** | 反应式压缩 — prompt_too_long 自动裁剪最早消息 + 重试 3 次 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-10) | 无被动恢复 | 中 | — | | **P1** | 持久化重试模式 — CI/后台无限重试 + 5min 退避上限 + 30s 心跳 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-11) | 失败即退出 | 中 | — | From bf416f50a44b1973a385ef7642d80c8b6f577619 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:44:06 +0800 Subject: [PATCH 10/11] docs: add deep dive report on atomic file write and persistent storage --- .../comparison/atomic-file-write-deep-dive.md | 66 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/atomic-file-write-deep-dive.md diff --git a/docs/comparison/atomic-file-write-deep-dive.md b/docs/comparison/atomic-file-write-deep-dive.md new file mode 100644 index 00000000..21205974 --- /dev/null +++ b/docs/comparison/atomic-file-write-deep-dive.md @@ -0,0 +1,66 @@ +# Qwen Code 改进建议 — 原子文件写入与事务回滚 (Atomic File Write & Persistent Storage) + +> 核心洞察:CLI Agent 的长期稳定性很大程度上取决于其持久化存储(会话历史、记忆缓存、项目配置)的健壮性。如果因为电脑意外断电或 OOM 导致写入进程中断,直接使用 `fs.writeFile` 极易留下半写文件,导致整个会话 JSON 彻底损坏报废。Claude Code 全面采用了“`temp + rename` 原子写”以及对超大结果(如几十 MB 的命令输出)的“Persist to disk”降级策略;而 Qwen Code 的绝大部分模块依然在使用有风险的直接覆写机制。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、架构对比与数据损坏风险 + +### 1. Qwen Code 现状:直接写入与内存膨胀 +Qwen Code 中核心的配置存储和会话存档,大量使用了 `fs.writeFile` 或同步的 `fs.writeFileSync`: +- **半写风险(Torn Writes)**:当写入一个 5MB 的会话历史 `history.json` 时,如果写入中途 Node.js 崩溃或遭遇断电,文件中可能只包含了一半的 JSON 结构,导致该文件彻底损坏且无法恢复,用户丢失所有对话进度。 +- **历史内存膨胀**:如果一个工具(比如一次失败的 `npm build` 或者一个 `grep`)返回了 5 万行的日志(2MB 文本),这些数据会被直接原封不动地塞进 `history` 数组中持久化。这不仅导致下次启动读取缓慢,还会立刻撑爆大模型的输入上下文。 + +*注:虽然 Qwen Code 在 `packages/core/src/utils/atomicFileWrite.ts` 中实现了一个 `atomicWriteJSON`,但目前仅在实验性的 Arena 模块中使用,核心引擎仍处于裸奔状态。* + +### 2. Claude Code 的解决方案:原子事务与防膨胀脱水 +Claude Code 设计了两套底座机制来保证长对话的数据安全: + +#### 机制一:全局原子写 (Atomic Rename) +所有的关键持久化(无论是 Session、TeamMemory 还是 Config),一律强制走包装好的原子写函数: +```typescript +// 1. 写到与目标同一磁盘的隐藏临时文件 +await fs.writeFile(`${filePath}.tmp`, data); + +// 2. 利用 POSIX 系统的 fs.rename() 的原子性,瞬间替换目标文件 +await fs.rename(`${filePath}.tmp`, filePath); +``` +由于 `rename` 在操作系统层是原子的,即使在此刻拔断电源,用户也只会看到“全新的文件”或“完好无损的旧文件”,绝不存在被写破了一半的损坏状态。 + +#### 机制二:大结果持久化降级 (Persist to Disk) +当检测到工具调用的输出(如 `BashTool` 的标准输出或大文件的 `FileReadTool`)超过了 `50K chars`(约 8MB 内存上限)时: +1. Claude Code **不会** 把这段文本放进历史消息记录(Message Array)中。 +2. 而是调用 `persistToolResult` 函数,用 `SHA-256` 算个 Hash,将这 8MB 文本单独持久化到本地 `.claude/tool-results/` 目录下。 +3. 在真实的对话历史中,只保存一个极短的“脱水占位符(Stub)”: +```xml + + Preview (first 2KB): npm WARN deprecated... + Full output saved to: ~/.claude/tool-results/mcp-bash-1738... + +``` +这避免了 OOM 崩溃,也防止了无意义的海量垃圾日志污染下一次会话的大模型 Context。 + +## 二、Qwen Code 的改进路径 (P1 优先级) + +保证用户数据(尤其是耗时几小时的 Coding Session 存档)绝对不丢失,是 Agent 工具的及格线要求。 + +### 阶段 1:全域推行原子写 (Rollout Atomic Write) +1. 找出项目中所有的 `fs.writeFileSync`、`fs.writeFile`(尤其是针对 `.qwen/sessions/`、`.qwen/config.json` 等高价值资产的操作)。 +2. 全部替换为 `utils/atomicFileWrite.ts` 中提供的方案。 +3. 确保临时文件和目标文件处于同一挂载点(Volume),否则 `rename` 会退化为非原子的 `copy+delete`(此时仍需处理跨盘回滚逻辑)。 + +### 阶段 2:引入 ToolResult 脱水存储 +1. 新建 `utils/toolResultStorage.ts`,定义一个 `MAX_INLINE_RESULT_CHARS` 阈值(建议为 100,000,约 25K Tokens)。 +2. 在 `agent-core.ts` 收集 `TOOL_RESULT` 事件时,对 `result.length > 阈值` 的内容进行截断(保留前后各 10% 的摘要供大模型判断执行是否成功)。 +3. 将完整内容异步写入到磁盘的 `.qwen/tool-results/` 目录下。 + +### 阶段 3:会话恢复清理机制 +1. 当用户运行 `/clear` 清除缓存或退出长会话时,启动一个无阻塞的后台 Worker。 +2. 清理超过 7 天的 `tool-results` 脱水文件,防止长期运行占用过多硬盘空间。 + +## 三、改进收益评估 +- **实现成本**:低。Qwen Code 已有 `atomicWriteJSON` 基础工具,只需大规模重构替换。持久化降级逻辑只需几十行代码。 +- **直接收益**: + 1. **告别丢存档**:彻底根绝由于异常退出导致的 "Session JSON parse error" 致命 Bug。 + 2. **避免内存 OOM**:让 Agent 可以安全执行极长输出的打包或编译命令,无需担心缓冲区撑爆 Node.js 内存。 + 3. **降低 Token 费用**:只给大模型看长命令的头尾截断,阻止了无效日志刷屏带来的天价 Token 账单。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index 7eb43cfa..e61d6314 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -70,7 +70,7 @@ | **P1** | 优雅关闭序列与信号处理 — SIGINT/SIGTERM + 清理注册 + 5s failsafe [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-9) | 无信号处理 | 中 | — | | **P1** | 反应式压缩 — prompt_too_long 自动裁剪最早消息 + 重试 3 次 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-10) | 无被动恢复 | 中 | — | | **P1** | 持久化重试模式 — CI/后台无限重试 + 5min 退避上限 + 30s 心跳 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-11) | 失败即退出 | 中 | — | -| **P1** | 原子文件写入与事务回滚 — temp+rename 原子写 + 大结果persist to disk [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-12) | 直接 writeFileSync | 中 | — | +| **P1** | [原子文件写入与事务回滚](./atomic-file-write-deep-dive.md) — temp+rename 原子写 + 大结果persist to disk [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-12) | 直接 writeFileSync | 中 | — | | **P1** | 自动检查点默认启用 — 每轮工具执行后自动创建文件快照 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-13) | 检查点默认关闭 | 小 | — | | **P1** | Coordinator/Swarm 多 Agent编排 — Leader/Worker 团队 + 3 种执行后端 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-14) | 仅 Arena 竞赛 | 大 | — | | **P1** | [Task Management 任务协同与跨进程并发调度](./task-management-deep-dive.md) — 支持 blocks/blockedBy 的任务拓扑、跨进程安全锁与 Swarm 集成 | 仅提供简易无状态 TodoWriteTool | 大 | — | From 09d5604d26ce9da32891a90aa163eade5cdfae8e Mon Sep 17 00:00:00 2001 From: wenshao Date: Sun, 5 Apr 2026 13:46:34 +0800 Subject: [PATCH 11/11] docs: add deep dive report on agent tool fine-grained access control --- .../agent-tool-access-control-deep-dive.md | 76 +++++++++++++++++++ .../qwen-code-improvement-report.md | 2 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/comparison/agent-tool-access-control-deep-dive.md diff --git a/docs/comparison/agent-tool-access-control-deep-dive.md b/docs/comparison/agent-tool-access-control-deep-dive.md new file mode 100644 index 00000000..524ef33d --- /dev/null +++ b/docs/comparison/agent-tool-access-control-deep-dive.md @@ -0,0 +1,76 @@ +# Qwen Code 改进建议 — Agent 工具细粒度访问控制 (Fine-grained Tool Access Control) + +> 核心洞察:随着智能体系统(Agent System)支持后台执行(Async Agents)和多智能体协同(In-process Teammates),并非所有 Agent 都应该拥有相同的工具权限。如果后台执行的“只读代码探索” Agent 能够调用 `AskUserQuestionTool`,它将永远阻塞并在后台挂起等待用户回复;如果它能调用 `ExitPlanModeTool`,甚至会意外破坏主流程的状态机。Claude Code 实现了多层、白名单与黑名单组合的细粒度工具权限控制;而 Qwen Code 目前只能做到“全量赋予”或粗粒度的硬编码列表。 +> +> 返回 [改进建议总览](./qwen-code-improvement-report.md) + +## 一、为什么需要多层工具访问控制? + +在单体(Single Agent)架构下,主干交互 Agent 拥有全部工具权限是没有问题的。但是一旦引入后台子代理(Subagent)或多 Agent 协作网络(Swarm),权限隔离就变得生死攸关。 + +**Qwen Code 现状与潜在风险**: +假设你想衍生一个子 Agent 在后台自动整理和归纳过去一周的代码修改。 +在当前架构下,如果它获得了所有的基础工具: +1. **死锁风险**:它错误地调用了 `ask_user` 或 `agent_tool` (尝试再衍生子 Agent),会导致后台任务挂起或递归爆炸。 +2. **状态越权**:它调用了 `exit_plan_mode`,破坏了当前主进程(Coordinator)原本的阶段控制逻辑。 +3. **破坏最小权限原则**:本来只用搜索(grep)的 Agent,被赋予了修改文件(edit/write)或跑高危命令(shell)的能力。 + +## 二、Claude Code 的解决方案:三层门控 (Triple-Gate Control) + +Claude Code 在 `constants/tools.ts` 中精心设计了三层基于集合(Sets)的隔离墙。这决定了在运行时,底层 `filterToolsForAgent` 函数会给 Agent 装配何种能力: + +### 第一层:全局禁止名单 (ALL_AGENT_DISALLOWED_TOOLS) +这是不可逾越的红线。任何脱离主交互循环被独立 `spawn` 出来运行的 Agent(不论前后台),都**绝对禁止**使用这些工具: +- `TaskOutputTool`:它只能由调度主脑使用。 +- `ExitPlanModeTool` / `EnterPlanModeTool`:状态机专有。 +- `AskUserQuestionTool`:交互界面专有(后台无标准输入)。 +- `AgentTool` (spawn subagent):防止产生失控的递归子代理。 + +### 第二层:异步白名单 (ASYNC_AGENT_ALLOWED_TOOLS) +对于普通的后台异步任务,它实行的是**白名单制(Allowlist)**,只允许严格安全的、用于完成基本开发闭环的工具集: +- `FileReadTool`, `WebSearchTool`, `GrepTool`, `GlobTool` (只读搜集) +- `FileEditTool`, `FileWriteTool`, `BashTool` (操作执行) + +### 第三层:同进程协作增权 (IN_PROCESS_TEAMMATE_ALLOWED_TOOLS) +当后台 Agent 不是一个孤独的异步任务,而是通过 `AsyncLocalStorage` 拉起的“同进程协作队友 (Teammate)”时,它需要与其他 Agent 交流或分配任务。系统会为它**额外开放**特定协同工具: +- `TaskCreateTool`, `TaskGetTool`, `TaskListTool`, `TaskUpdateTool` (操作共享任务树) +- `SendMessageTool` (向调度主脑或其他队友发消息) + +不仅如此,Claude Code 还支持在 Agent 的配置文件(Frontmatter)中使用 `tools:` 列表手动缩小权限,或使用 `disallowedTools:` 显式排除某工具,实现了动态与静态结合的极强可配性。 + +## 三、Qwen Code 的改进路径 (P1 优先级) + +为了迎接真正安全的多 Agent 协作时代,Qwen Code 必须重写其工具下发逻辑。 + +### 阶段 1:定义常量约束层 +1. 新建 `packages/core/src/tools/toolAccessPolicies.ts`。 +2. 定义三个不可变的权限集:`GLOBAL_DISALLOWED_TOOLS`, `BACKGROUND_AGENT_ALLOWLIST`, `SWARM_AGENT_ALLOWLIST_EXTENSION`。 + +### 阶段 2:改造动态装配逻辑 +1. 在 `agent-core.ts` 初始化 `toolsList` 的阶段,引入一个类似 `filterToolsForAgent(agentConfig)` 的流水线: + ```typescript + let availableTools = allRegisteredTools; + + // 如果是后台 Agent,仅保留在 白名单 中的工具 + if (agentConfig.isBackground) { + availableTools = availableTools.filter(t => BACKGROUND_AGENT_ALLOWLIST.has(t.name)); + } + + // 全局剔除绝对禁止的工具 + availableTools = availableTools.filter(t => !GLOBAL_DISALLOWED_TOOLS.has(t.name)); + + // 叠加自定义配置排除 (Denylist) + if (agentConfig.disallowedTools) { + availableTools = availableTools.filter(t => !agentConfig.disallowedTools.includes(t.name)); + } + ``` + +### 阶段 3:工具层感知 (Context-Aware Tools) +1. 对于特定的交互工具(比如 `ask_user` 工具本身),它可以在其内部 `execute` 时,额外检查 `getCurrentAgentContext().role`,如果发现是后台角色则直接抛错,提供双保险。 + +## 四、改进收益评估 +- **实现成本**:低。无需引入新的库,只需要理顺工具注册表的分发逻辑,几百行代码即可实现。 +- **直接收益**: + 1. **避免后台假死**:根绝了后台异步代理滥用交互工具导致任务卡住的问题。 + 2. **多 Agent 状态解耦**:彻底分清“调度者(Manager)”与“执行者(Worker)”的工具边界。 + 3. **增强防御编程**:对开发者暴露了更清晰、更最小权限的插件/Agent 定义 Schema(通过开放 `disallowedTools`)。 \ No newline at end of file diff --git a/docs/comparison/qwen-code-improvement-report.md b/docs/comparison/qwen-code-improvement-report.md index e61d6314..1c8a45f0 100644 --- a/docs/comparison/qwen-code-improvement-report.md +++ b/docs/comparison/qwen-code-improvement-report.md @@ -74,7 +74,7 @@ | **P1** | 自动检查点默认启用 — 每轮工具执行后自动创建文件快照 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-13) | 检查点默认关闭 | 小 | — | | **P1** | Coordinator/Swarm 多 Agent编排 — Leader/Worker 团队 + 3 种执行后端 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-14) | 仅 Arena 竞赛 | 大 | — | | **P1** | [Task Management 任务协同与跨进程并发调度](./task-management-deep-dive.md) — 支持 blocks/blockedBy 的任务拓扑、跨进程安全锁与 Swarm 集成 | 仅提供简易无状态 TodoWriteTool | 大 | — | -| **P1** | Agent 工具细粒度访问控制 — 3 层allowlist/denylist + per-agent 限制 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-15) | 全部或指定列表 | 中 | — | +| **P1** | [Agent 工具细粒度访问控制](./agent-tool-access-control-deep-dive.md) — 3 层allowlist/denylist + per-agent 限制 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-15) | 全部或指定列表 | 中 | — | | **P1** | [InProcess 同进程多 Agent隔离](./in-process-agent-isolation-deep-dive.md) — AsyncLocalStorage 上下文隔离 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-16) | 全局状态可能泄漏 | 中 | — | | **P1** | Agent 记忆持久化 — user/project/local 3 级跨 session 记忆 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-17) | 无跨 session 记忆 | 中 | — | | **P1** | Agent 恢复与续行 — SendMessage 继续已完成代理 + transcript 重建 [↓](./qwen-code-improvement-report-p0-p1-engine.md#item-18) | 执行完即销毁 | 中 | — |