From af2e085bea9a891c5acf40b11fded36b08549c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Wed, 1 Jul 2026 21:26:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0:=20=E5=A4=9AAgent=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E6=9C=BA=E5=88=B6=E6=94=B9=E7=94=A8Git=20worktree?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=E8=87=AA=E5=BB=BA=E6=9A=82=E5=AD=98=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 隔离:git worktree天然分支隔离,复用diff/merge/conflict能力 - 合并:git merge-tree三方合并预检+git merge实际合并到plan分支 - 冲突:四层防线(层间串行/worktree隔离/编译检查/命令互斥) - 展示:合并产出落回主对话采用可展开单条(方案C) - 类型:SubTaskRecord加branch字段,ConflictRecord加conflict_type - 降级:非Git工程串行执行(无worktree隔离) - 实施分批同步更新(含git_worktree.rs+编译检查+命令互斥锁) --- .../多Agent并行执行与仲裁合并设计-2026-07-01.md | 192 ++++++++++++++---- 1 file changed, 153 insertions(+), 39 deletions(-) diff --git a/docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md b/docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md index 86c6b45..d09343e 100644 --- a/docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md +++ b/docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md @@ -91,8 +91,16 @@ ALTER TABLE ai_tool_executions ADD COLUMN subtask_id TEXT; -- 新建 3 张表(见 ER 图字段) CREATE TABLE ai_plans (...); -CREATE TABLE ai_subtasks (...); -CREATE TABLE ai_conflicts (...); +CREATE TABLE ai_subtasks ( + ... + -- worktree 分支名(git worktree 隔离机制,见 §4.2) + branch TEXT -- 如 "subtask/{plan_id}/A" +); +CREATE TABLE ai_conflicts ( + ... + -- 冲突类型(见 §4.2 四层防线) + conflict_type TEXT DEFAULT 'file' -- 'file'(同文件) / 'semantic'(编译失败) +); ``` ### 2.4 三层关联查询示例 @@ -162,38 +170,114 @@ Plan.to_layers() → [[subtask_0, subtask_1], [subtask_2], [subtask_3, subtask_4 | 规则 | 说明 | |------|------| -| 层间串行 | 上层全部 done 才进下一层(DAG 依赖保证) | -| 层内并行 | 同层 SubTask 用 JoinSet 并发执行 | -| **写 SubTask 降级串行** | 同层内如有 `write_file`/`patch_file` 工具的 SubTask,降级为串行执行,防 concurrent write | -| 只读 SubTask 并行 | `read_file`/`search_files`/`list_directory` 等只读工具的 SubTask 可安全并行 | +| 层间串行 | 上层全部 done + **Git merge 到 plan 分支后** 才创建下一层 worktree(fork 天然包含上层改动) | +| 层内并行 | 同层 SubTask 各自创建 Git worktree,JoinSet 并发执行 | +| 写工具天然隔离 | 每个 SubTask 在独立 worktree 内工作,write_file/patch_file 写到 worktree 目录(不重定向) | +| 只读工具天然可见 | read_file 读 worktree 内文件(含本 worktree 改动 + fork 基点的全部历史) | +| 层内隔离 | 同层并行 SubTask 互相看不到对方的 worktree 改动(独立分支,完全隔离) | -### 4.2 Token 预算管控 +### 4.2 Git worktree 隔离机制 + +**核心思路**:用 Git worktree 替代自建暂存区,复用 Git 原生的分支隔离/diff/merge/conflict 能力。 + +#### 优势(vs 自建暂存区) + +| 维度 | 自建暂存区 | Git worktree | +|------|-----------|-------------| +| 隔离 | 手动 cp 文件 | `git worktree add` 天然隔离 | +| diff | 自己实现比对 | `git diff` 原生(含上下文/行级/二进制) | +| 合并 | 手动 cp 或自写 merge | `git merge` 三方合并 | +| 冲突解决 | 自建 diff 展示 | `git merge-tree` 预检 + conflict markers | +| 回滚 | 手动备份/恢复 | `git reset` / `git checkout` | +| 审计 | 自建日志 | commit history 天然审计链 | +| AI 工具兼容 | write_file 需重定向路径 | worktree 就是普通目录,所有工具原样工作 | +| PR 集成 | 需额外实现 | plan 分支直接创建 PR | + +#### 数据流 + +``` +Plan 启动: + git worktree add .devflow/wt/{plan_id} -b plan/{plan_id} + (基于工程当前分支创建 plan 工作分支) + +Layer 0 并行: + SubTask A → git worktree add .devflow/wt/{plan_id}/A -b subtask/{plan_id}/A + (基于 plan 分支创建,A 的所有工具操作在此 worktree 内,天然隔离) + SubTask B → git worktree add .devflow/wt/{plan_id}/B -b subtask/{plan_id}/B + (同上,B 完全隔离,A 看不到 B 的改动,反之亦然) + SubTask A/B done 后各自 git add + git commit(改动落入各自分支) + +Layer 0 merge(本层全部 done 后): + ① cd .devflow/wt/{plan_id} (plan 分支) + ② git diff plan..subtask/{plan_id}/A → A 的改动集 + ③ git diff plan..subtask/{plan_id}/B → B 的改动集 + ④ 同文件交集 → git merge-tree (三方合并预检) + 无冲突 → git merge subtask/{plan_id}/A && git merge subtask/{plan_id}/B + 有冲突 → Conflict 表记录 + Reviewer 仲裁 / 用户选择 + ⑤ 合并完成 → plan 分支已含 Layer 0 全部改动 + +Layer 1 fork: + 基于 plan 分支最新 commit 创建新 worktree(天然包含 Layer 0 改动) + +Plan 完成: + ① 编译检查:在 plan worktree 跑 cargo check / tsc --noEmit + 失败 → semantic 冲突标记 + ② plan 分支创建 PR(可选,或直接 merge 回主分支) + ③ git worktree remove 清理所有子 worktree + plan worktree +``` + +#### 读工具兼容规则 + +| 场景 | 行为 | +|------|------| +| 同层并行 SubTask 互读 | 不可见(独立 worktree + 独立分支) | +| 下一层读上一层产出 | 可见(上一层已 merge 到 plan 分支,新 worktree 基于此创建) | +| SubTask 读自己 worktree 内的写入 | 可见(worktree 是真实目录,写完即读) | + +#### 非 Git 工程的降级 + +未绑定 Git 的工程(无 .git 目录)降级为串行执行(单 SubTask 逐个跑,无 worktree 隔离)。 +此时 ai_subtasks.branch 为 NULL,Coordinator.dispatch 走原串行路径。 + +### 4.3 冲突防护四层防线 + +| 防线 | 机制 | 覆盖冲突类型 | +|------|------|------------| +| **第一层:层间串行 + merge 后才 fork** | 上一层 merge 到 plan 分支后,下一层 worktree 基于此创建 | 跨层同文件覆盖 | +| **第二层:同层 worktree 隔离 + merge-tree 预检** | 独立分支 + git 三方合并检测 | 同文件并发写 | +| **第三层:编译检查** | merge 完成后在 plan worktree 跑 cargo check / tsc --noEmit,失败 → 标记语义冲突 | 跨文件语义冲突(删函数/改签名) | +| **第四层:命令互斥锁** | run_command 对同目录的 npm/cargo 加 mutex | 资源竞争(npm install 并发) | + +### 4.4 Token 预算管控 ```rust struct TokenBudgetPool { - total: AtomicU64, // 全局预算(来自设置项) + total: AtomicU64, // 全局预算(来自设置项) consumed: AtomicU64, // 已消耗 } impl TokenBudgetPool { fn try_reserve(&self, estimate: u64) -> bool { - // CAS 循环:consumed + estimate <= total + // CAS 循环:consumed + estimate <= total } } ``` - 每个 SubTask 启动前向预算池申请估算额度 -- 超限时 Coordinator 拒绝启动新 SubTask(降级为串行顺序执行剩余任务) -- 预算来源:设置项 `df-ai-plan-token-budget`(默认 100k tokens) +- 超限时 Coordinator 拒绝启动新 SubTask(降级为串行顺序执行剩余任务) +- 预算来源:设置项 `df-ai-plan-token-budget`(默认 100k tokens) -### 4.3 错误传播 +### 4.5 错误传播 | 场景 | 策略 | |------|------| -| SubTask 执行失败 | 记录 error 状态,**不中断其他同层 SubTask**(容错) | -| 全部 SubTask 失败 | Coordinator 标记 Plan 状态为 error,前端展示错误 | -| 部分 SubTask 失败 | merge 时跳过失败 SubTask 的产出,只合并成功的 | -| 子 Agent 超时 | 单 SubTask 超时(默认 120s)→ 标记 error,不影响其他 | +| SubTask 执行失败 | 记录 error 状态,**不中断其他同层 SubTask**(容错) | +| 全部 SubTask 失败 | Coordinator 标记 Plan 状态为 error,前端展示错误 | +| 部分 SubTask 失败 | merge 时跳过失败 SubTask 的分支(不 merge),只合并成功的 | +| 子 Agent 超时 | 单 SubTask 超时(默认 120s)→ 标记 error,不影响其他 | +| Git merge 冲突 | conflict markers 保留在 plan worktree,Reviewer Agent 仲裁或用户手动解决 | +| 编译检查失败 | 标记 semantic 冲突,Reviewer Agent 尝试修复或标记给用户 | +| worktree 创建失败 | 降级为串行(无 worktree 隔离,逐 SubTask 在主目录执行) | --- @@ -274,38 +358,52 @@ impl TokenBudgetPool { ## 七、实施分批 -### Batch 37 — 数据层 + 并行执行 +### 数据层 + Git worktree 隔离 + 并行执行 | # | 任务 | 文件 | |---|------|------| -| 1 | V36 迁移(3 新表 + 2 ALTER) | migrations.rs | +| 1 | V36 迁移(3 新表 + 2 ALTER + subtasks.branch + conflicts.conflict_type) | migrations.rs | | 2 | PlanRepo / SubTaskRepo / ConflictRepo CRUD | 新 repo 文件 | -| 3 | Coordinator.dispatch JoinSet 层内并行 | coordinator.rs | -| 4 | 写 SubTask 降级串行 + token 预算池 | coordinator.rs | -| 5 | 4 个新事件类型 | AiChatEvent | -| 6 | PlanProgress 接入真实状态 | PlanProgress.vue | -| 7 | 工具卡按 subtask_id 分组 | MessageList.vue | +| 3 | Git worktree 生命周期管理(create/commit/merge/remove) | 新 git_worktree.rs | +| 4 | Coordinator.dispatch JoinSet 层内并行(每 SubTask 绑 worktree) | coordinator.rs | +| 5 | Token 预算池(AtomicU64 CAS,超限降级串行) | coordinator.rs | +| 6 | 子 Agent 独立 ContextManager + fork 快照 + worktree_path | coordinator.rs | +| 7 | 层间 merge 到 plan 分支 + 下一层基于 plan 创建 worktree | coordinator.rs | +| 8 | 4 个新事件 + 事件双写(emit + publish_event) | AiChatEvent | +| 9 | 前端类型定义(PlanRecord/SubTaskRecord/ConflictRecord) | api/types.ts | +| 10 | PlanProgress 接入真实状态 + 发送即展示 | PlanProgress.vue | +| 11 | 工具卡按 subtask_id 折叠分组 + persona 徽章 | MessageList.vue | +| 12 | 编译警告清理(coordinator_plan unused / audit 子模块 unused imports) | 各文件 | -### Batch 38 — 仲裁合并 + UI 完善 +### 仲裁合并 + 冲突 UI + 编译检查 | # | 任务 | 文件 | |---|------|------| -| 1 | Coordinator.merge 冲突检测(按文件路径) | coordinator.rs | -| 2 | Reviewer Agent 仲裁(persona.rs 扩展) | persona.rs + coordinator.rs | -| 3 | 冲突 diff 展示 + resolution 按钮 | 新 ConflictResolver.vue | -| 4 | AiConflictResolved 事件闭环 | AiChatEvent | -| 5 | 技术债扫尾(编译警告 / unused import) | 各文件 | +| 1 | Coordinator.merge: git merge-tree 三方合并预检 | coordinator.rs + git_worktree.rs | +| 2 | 冲突检测:同文件路径(file 类型) + 编译失败(semantic 类型) | coordinator.rs | +| 3 | Reviewer Agent 仲裁(persona 扩展:读 diff → 推荐 resolution + 理由) | persona.rs | +| 4 | 合并产出落回主对话(方案 C:可展开单条) | coordinator.rs + MessageList.vue | +| 5 | ConflictResolver.vue(git diff 展示 + resolution 按钮) | 新组件 | +| 6 | resolve_conflict IPC + AiConflictResolved 事件闭环 | AiChatEvent | +| 7 | 命令互斥锁(run_command 对同目录 npm/cargo 加 mutex) | audit/approval.rs | +| 8 | agentic/mod.rs 拆分(2222 行 → 提取 compress/title/knowledge_inject 独立模块) | agentic/*.rs | +| 9 | AiChat 进度条补全(后端 AiAgentRound 补 max_rounds + 前端 completed 计数) | AiChat.vue | +| 10 | context.rs 拆分(1956 行 → 提取 sanitize/budget/compress 独立模块) | context/*.rs | --- ## 八、子 Agent 上下文隔离 -### 8.1 问题 +### 8.1 双重隔离:上下文 + 文件系统 -多 Agent 并行执行时,每个 SubTask 需要独立的上下文(messages / currentText / tool_results)。 -如果共享主对话的 ContextManager,多个 SubTask 的产出会交织污染。 +多 Agent 并行执行时需要两层隔离: -### 8.2 方案:子对话快照 + 独立 ContextManager +| 层 | 隔离对象 | 机制 | +|---|---------|------| +| 上下文隔离 | messages / currentText / tool_results / pending_approvals | 独立 ContextManager 实例 | +| 文件隔离 | 工程文件(worktree 内的源码) | Git worktree(见 §4.2) | + +### 8.2 上下文隔离 ```rust struct SubTaskContext { @@ -317,31 +415,37 @@ struct SubTaskContext { persona: AgentPersona, /// 子任务 id(产出消息标记 subtask_id) subtask_id: String, + /// Git worktree 路径(工具操作的工作目录,见 §4.2) + worktree_path: Option, // None = 非 Git 工程降级串行 } ``` **隔离规则**: | 维度 | 主对话 | 子 Agent | -|------|--------|---------| +|------|--------|----------| | messages | 用户消息 + 合并产出 | fork 快照 + 独立执行轨迹 | | ContextManager | 主对话的 | 每个 SubTask 独立实例 | | pending_approvals | 主对话的 | 各 SubTask 独立(无并发写竞争) | | tool_results | 写入子 Agent 的 messages | 合并时提取摘要写入主对话 | +| **文件系统** | 主工程目录 | **Git worktree 隔离**(§4.2) | +| **工作目录** | 主工程路径 | **worktree_path**(工具的 cwd 重定向到此) | -### 8.3 合并产出落回主对话 +### 8.3 合并产出落回主对话(方案 C:可展开单条) ``` SubTask 完成 → ExecutionResult { output, success } ↓ Coordinator.merge(results) → MergeResult { merged_output, conflicts } ↓ -主对话 push 一条 assistant 消息(merged_output) + N 条冲突消息(conflicts[i]) +主对话 push 单条可展开 assistant 消息 ``` -- **合并输出 = 单条 assistant 消息** — 不是把所有子 Agent 的消息都堆进主对话 -- **冲突 = 单独消息** — 每个冲突一条,带 `conflict_id` 供前端定位 -- **子 Agent 执行轨迹不落主对话** — 只在 ai_messages 表中保留(带 subtask_id),前端按需展开查看 +**展示策略**: +- **默认折叠**:主对话只展示一条 assistant 消息(merged_output 摘要) +- **点击展开**:展开后按 SubTask 分组,每组显示完整执行轨迹(从 ai_messages WHERE subtask_id=? 拉取) +- **冲突内联**:合并消息底部内联冲突列表(点击展开 diff + resolution 按钮) +- **子 Agent 执行轨迹不堆入主对话流**:只在 ai_messages 表保留(带 subtask_id),展开时按需加载 --- @@ -379,18 +483,28 @@ export interface SubTaskRecord { status: SubTaskStatus layer: number deps: string[] // JSON 解析后的数组 + /** Git worktree 分支名(NULL=非 Git 工程降级串行) */ + branch: string | null created_at: string completed_at: string | null } +/** 冲突类型 */ +export type ConflictType = 'file' | 'semantic' + /** 冲突记录 */ export interface ConflictRecord { id: string plan_id: string + /** 冲突文件路径(semantic 类型时为触发编译失败的入口文件) */ file_path: string + /** 冲突类型:file=同文件路径 / semantic=编译失败 */ + conflict_type: ConflictType subtask_a: string subtask_b: string + /** git diff 输出(A 的改动) */ diff_a: string + /** git diff 输出(B 的改动) */ diff_b: string resolution: ConflictResolution resolved_by: 'reviewer' | 'user' | null