Files
picoclaw/loop_conflict_analysis.md
T
2026-03-22 19:21:58 +08:00

272 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# loop.go 冲突详细分析
## 概述
loop.go 有 11 处冲突,涉及核心架构差异:
- **HEAD (feat/subturn-poc)**: 基于 context 的 SubTurn 层级管理,使用 `activeTurnStates` map 支持并发
- **Incoming (refactor/agent)**: 事件驱动架构,使用 `EventBus``HookManager`,单个 `activeTurn` **不支持并发 turn**
## 关键发现:Incoming 的并发限制
**重要**: Incoming 分支的 `activeTurn` 设计**不支持并发 turn 执行**!
```go
// Incoming 的实现
func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState) (turnResult, error) {
al.registerActiveTurn(ts) // 设置 al.activeTurn = ts
defer al.clearActiveTurn(ts) // 清除 al.activeTurn = nil
// ...
}
func (al *AgentLoop) registerActiveTurn(ts *turnState) {
al.activeTurnMu.Lock()
defer al.activeTurnMu.Unlock()
al.activeTurn = ts // 单例!后面的会覆盖前面的
}
```
**问题**:
1. 如果两个 session 同时调用 `runAgentLoop`,第二个会覆盖第一个的 `activeTurn`
2. `GetActiveTurn()` 只能返回最后一个注册的 turn
3. 中断操作 (`InterruptGraceful`, `InterruptHard`) 只能影响当前的 `activeTurn`
**HEAD 的优势**:
```go
// HEAD 的实现
activeTurnStates sync.Map // 支持多个并发 turn
// key: sessionKey, value: *turnState
// 每个 session 有独立的 turnState
al.activeTurnStates.Store(opts.SessionKey, rootTS)
```
## 架构决策的影响
如果采用 Incoming 的架构(方案 B),我们会**失去并发 turn 的能力**!
### 选项分析
**选项 1: 完全采用 Incoming(会失去并发)**
- ✅ 获得事件驱动架构
- ✅ 获得 Hook 系统
-**失去并发 turn 支持**
-**失去 SubTurn 并发支持**
- ❌ 多个 session 无法同时处理
**选项 2: 混合方案(推荐)**
- ✅ 保留 HEAD 的 `activeTurnStates sync.Map`
- ✅ 采用 Incoming 的 `EventBus``HookManager`
- ✅ 保持并发能力
- ⚠️ 需要调整 `GetActiveTurn()` 等 API
**选项 3: 改造 Incoming 支持并发**
-`activeTurn *turnState` 改为 `activeTurns sync.Map`
- 修改所有相关方法支持 sessionKey 参数
- 工作量大,但架构更清晰
## 推荐方案:选项 2(混合方案)
### AgentLoop 结构体设计
```go
type AgentLoop struct {
// Incoming 的字段
bus *bus.MessageBus
cfg *config.Config
registry *AgentRegistry
state *state.Manager
eventBus *EventBus // ✅ 保留
hooks *HookManager // ✅ 保留
hookRuntime hookRuntime // ✅ 保留
running atomic.Bool
summarizing sync.Map
fallback *providers.FallbackChain
channelManager *channels.Manager
mediaStore media.MediaStore
transcriber voice.Transcriber
cmdRegistry *commands.Registry
mcp mcpRuntime
steering *steeringQueue
mu sync.RWMutex
// HEAD 的并发支持(保留)
activeTurnStates sync.Map // ✅ 保留:支持并发 turn
subTurnCounter atomic.Int64 // ✅ 保留:SubTurn ID 生成
// Incoming 的字段(调整)
turnSeq atomic.Uint64 // ✅ 保留:全局 turn 序列号
activeRequests sync.WaitGroup // ✅ 保留:请求跟踪
reloadFunc func() error
}
```
### 关键方法调整
1. **GetActiveTurn()**: 需要接受 sessionKey 参数
2. **InterruptGraceful/Hard()**: 需要接受 sessionKey 参数
3. **runAgentLoop()**: 使用 `activeTurnStates` 而不是单个 `activeTurn`
## 冲突详情
### 冲突 1: AgentLoop 结构体 (38-77 行)
**HEAD 新增字段**:
```go
activeTurnStates sync.Map // key: sessionKey (string), value: *turnState
subTurnCounter atomic.Int64 // Counter for generating unique SubTurn IDs
```
**Incoming 新增字段**:
```go
eventBus *EventBus
hooks *HookManager
hookRuntime hookRuntime
activeTurnMu sync.RWMutex
activeTurn *turnState
turnSeq atomic.Uint64
activeRequests sync.WaitGroup
```
**关键差异**:
- HEAD: 使用 `sync.Map` 管理多个并发 turn (`activeTurnStates`)
- Incoming: 使用单个 `activeTurn` + 锁 (`activeTurnMu`)
- HEAD: SubTurn 计数器 (`subTurnCounter`)
- Incoming: Turn 序列号 (`turnSeq`)
- Incoming: 新增事件系统 (`eventBus`, `hooks`, `hookRuntime`)
**解决方案**: 采用 Incoming 的结构,但需要考虑如何在新架构中实现 SubTurn 的并发管理。
---
### 冲突 2: processOptions 结构体 (92-112 行)
**HEAD**:
```go
SkipAddUserMessage bool // If true, skip adding UserMessage to session history
```
**Incoming**:
```go
InitialSteeringMessages []providers.Message
// 新增结构体
type continuationTarget struct {
SessionKey string
Channel string
ChatID string
}
```
**关键差异**:
- HEAD: 使用 `SkipAddUserMessage` 标志
- Incoming: 使用 `InitialSteeringMessages` 数组 + 新的 `continuationTarget` 结构体
**解决方案**: 采用 Incoming 的实现,`InitialSteeringMessages` 提供更灵活的 steering 消息处理。
---
### 冲突 3: runAgentLoop 函数 (1439-1581 行)
这是最大的冲突,涉及核心执行逻辑。
**HEAD 的实现**:
1. 检查是否在 SubTurn 中 (`turnStateFromContext`)
2. 如果是 SubTurn,复用现有 turnState
3. 如果是根 turn,创建新的 rootTS
4. 使用 `activeTurnStates.Store` 注册 turn
5. 调用 `runLLMIteration` 执行 LLM 循环
**Incoming 的实现**:
1. 记录 last channel
2. 调用 `newTurnState` 创建 turn state
3. 调用 `al.runTurn(ctx, ts)` 执行 turn
4. 处理 follow-up 消息
5. 发布响应
**关键差异**:
- HEAD: 复杂的 SubTurn 层级管理,支持嵌套
- Incoming: 简化的 turn 管理,通过 `newTurnState``runTurn`
- HEAD: 使用 `runLLMIteration` 函数
- Incoming: 使用 `runTurn` 函数
- Incoming: 新增 follow-up 消息处理机制
**解决方案**: 采用 Incoming 的简化架构,但需要在 `runTurn` 中添加 SubTurn 支持。
---
### 冲突 4: runLLMIteration vs runTurn (1672-1689 行)
**HEAD**: 有独立的 `runLLMIteration` 函数
**Incoming**: 使用 `runTurn` 函数
需要查看具体实现来决定如何合并。
---
### 冲突 5-11: 其他冲突点
剩余冲突主要涉及:
- 工具执行逻辑
- Steering 消息处理
- 中断处理
- 变量命名差异(`agent` vs `ts.agent`
## 架构决策
根据方案 B(采用重构架构),需要:
1. **采用 Incoming 的 AgentLoop 结构**
- 使用 `eventBus`, `hooks`, `hookRuntime`
- 使用单个 `activeTurn` + `activeTurnMu`
- 保留 `turnSeq`
2. **SubTurn 支持策略**
- 选项 A: 在 `turnState` 中添加父子关系字段
- 选项 B: 使用 context 传递 SubTurn 信息
- 选项 C: 在 EventBus 中管理 SubTurn 层级
3. **函数迁移顺序**
- 先采用 Incoming 的结构体定义
- 更新 `newTurnState` 函数
- 采用 `runTurn` 函数
-`runTurn` 中集成 SubTurn 逻辑
## 推荐实施步骤
### 步骤 1: 结构体定义 (30 分钟)
- 采用 Incoming 的 `AgentLoop` 结构体
- 采用 Incoming 的 `processOptions` 结构体
- 添加 `continuationTarget` 结构体
### 步骤 2: 辅助函数 (30 分钟)
- 更新 `NewAgentLoop` 初始化函数
- 确保 EventBus、Hook 正确初始化
### 步骤 3: runAgentLoop 函数 (1-2 小时)
- 采用 Incoming 的简化实现
- 保留 channel 记录逻辑
- 调用 `newTurnState``runTurn`
- 处理 follow-up 消息
### 步骤 4: runTurn 函数 (2-3 小时)
- 采用 Incoming 的 `runTurn` 实现
- 在其中添加 SubTurn 检测和处理逻辑
- 集成 SubTurn 结果回传机制
### 步骤 5: 其他冲突点 (1-2 小时)
- 逐个解决剩余 7 个冲突
- 确保变量命名一致
- 更新工具执行和 steering 逻辑
## 风险和注意事项
1. **SubTurn 语义变化**: 新架构中 SubTurn 的实现方式可能不同
2. **并发安全**: 从 `sync.Map` 迁移到单个 `activeTurn` + 锁
3. **事件系统集成**: 需要确保 SubTurn 事件正确触发
4. **测试覆盖**: 原有 SubTurn 测试需要更新
## 下一步
建议先实现步骤 1-2(结构体定义和初始化),然后再处理复杂的执行逻辑。