mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
272 lines
8.0 KiB
Markdown
272 lines
8.0 KiB
Markdown
# 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(结构体定义和初始化),然后再处理复杂的执行逻辑。
|