context-management.md 30 KB

Context 管理与执行计划

本文档描述 Agent 的 Context 管理、执行计划和探索机制。


设计目标

  1. 自主长程执行:Agent 能独立执行复杂任务,无需人工频繁干预
  2. 有效的 Context 管理:长任务中保持关键信息,压缩次要细节
  3. 支持探索和回溯:能尝试多种方案,失败时能有效回溯
  4. 简单的工具接口:LLM 只需理解少量简单工具,复杂逻辑由系统处理

参考方案:OpenCode 的 Context 管理

核心架构

┌─────────────────┐
│   plan.md       │  ← 文本格式的计划(TODO 列表)
└─────────────────┘
         ↓
┌─────────────────┐
│  线性 Message   │  ← 对话历史
│     List        │
└─────────────────┘
         ↓
┌─────────────────┐
│  Prune + Full   │  ← 两阶段压缩
│   Compaction    │
└─────────────────┘
         ↓
┌─────────────────┐
│   Sub-Agent     │  ← 隔离大任务
└─────────────────┘

1. Message 管理

数据结构

  • User Message: 用户输入,包含 TextPart, FilePart, CompactionPart, SubtaskPart 等
  • Assistant Message: LLM 输出,包含 TextPart, ToolPart, ReasoningPart 等
  • 每个 Message 包含多个 Part,支持流式处理

存储

Storage Key:
["message", sessionID, messageID] -> MessageV2.Info
["part", messageID, partID] -> MessageV2.Part

2. Context 压缩机制

两阶段压缩

阶段 1: Prune(清理旧工具输出)

参数:
- PRUNE_MINIMUM = 20,000 tokens(最少删除量)
- PRUNE_PROTECT = 40,000 tokens(保护阈值)
- PRUNE_PROTECTED_TOOLS = ["skill"](不删除的工具)

流程:
1. 从后向前遍历 messages
2. 跳过最后 2 轮 turns(保护最近交互)
3. 跳过已有 summary 标记的 assistant 消息
4. 收集已完成工具调用的输出
5. 当累计 > PRUNE_PROTECT 时,标记为已 compacted
6. 当删除量 > PRUNE_MINIMUM 时,执行删除

阶段 2: Full Compaction(上下文总结)

流程:
1. 创建新的 assistant 消息(summary=true)
2. 调用 "compaction" 专用 agent
3. 提示词: "Provide a detailed prompt for continuing our conversation..."
4. 返回 "continue" 时自动创建新的 user 消息继续

3. Plan/Todo 机制

数据结构

Todo.Info = {
  id: string
  content: string      // 任务描述
  status: string       // pending | in_progress | completed | cancelled
  priority: string     // high | medium | low
}

存储:文件系统(.opencode/plans/xxx.md)或 Storage

4. Sub-Agent 机制

Agent Mode

  • primary: 主代理,执行工具
  • subagent: 子代理,独立 context,结果汇总回主会话

内置 Sub-Agents

  • general: 通用代理,可并行执行多个任务
  • explore: 代码探索专用,仅允许查询工具
  • compaction: 上下文总结专用

Subtask 执行

  1. 创建 SubtaskPart
  2. 子代理独立处理(独立 message list)
  3. 结果通过 "The following tool was executed by the user" 汇总

5. 优缺点分析

优点

  • 简单成熟,经过大量验证
  • Plan 和执行分离,用户可直接编辑 plan.md
  • Sub-agent 有效隔离大任务的 context

局限

  • Plan 是纯文本,与执行记录无结构化关联
  • 压缩是"事后"的,等满了再压缩
  • 回溯能力有限,无法精确回到某个状态
  • 不支持并行探索-合并的模式

我们的方案

核心思路

基于 OpenCode 方案,增强三个能力:
1. 结构化 Plan(goal 工具)
2. 并行探索-合并(explore 工具)
3. 精确回溯(abandon + context 压缩)

架构

┌─────────────────────────────────────────────┐
│              GoalTree (嵌套 JSON)             │
│  层级目标,LLM 通过 goal 工具维护              │
│  注入 LLM 时过滤废弃目标,重新生成连续显示序号   │
└─────────────────────────────────────────────┘
                      │
         ┌────────────┴────────────┐
         ↓                         ↓
┌─────────────────┐      ┌─────────────────┐
│   Messages      │      │  并行探索        │
│   (扁平列表,    │      │  (explore 工具)  │
│    goal_id 关联) │      │  多个独立分支    │
└─────────────────┘      └─────────────────┘
         │                         │
         ↓                         ↓
┌─────────────────┐      ┌─────────────────┐
│  完成/回溯       │      │  合并评估        │
│  done/abandon   │      │  返回主会话      │
│  触发 context   │      └─────────────────┘
│  压缩           │
└─────────────────┘
         │
         ↓
┌─────────────────────────────────────────────┐
│              DAG 可视化(派生视图)             │
│  从 GoalTree + Messages 生成                  │
│  节点 = 结果/里程碑,边 = 动作/执行过程         │
│  边可展开/折叠,对应目标的层级展开              │
└─────────────────────────────────────────────┘

数据结构

两层数据

后端存储两类数据,可视化的 DAG 是派生视图:

  1. GoalTree(嵌套 JSON):层级目标,注入 LLM
  2. Messages(扁平列表):执行记录,通过 goal_id 关联 Goal

不存在独立的"边"数据结构,边在可视化时从 Messages 聚合生成。

Goal

@dataclass
class GoalStats:
    message_count: int = 0               # 消息数量
    total_tokens: int = 0                # Token 总数
    total_cost: float = 0.0              # 总成本
    preview: Optional[str] = None        # 工具调用摘要,如 "read_file → edit_file → bash"

GoalStatus = Literal["pending", "in_progress", "completed", "abandoned"]
GoalType = Literal["normal", "explore_start", "explore_merge"]

@dataclass
class Goal:
    id: str                              # 内部唯一 ID,纯自增("1", "2", "3"...)
    parent_id: Optional[str] = None      # 父 Goal ID(层级关系)
    branch_id: Optional[str] = None      # 所属分支 ID(分支关系,null=主线)
    type: GoalType = "normal"            # Goal 类型

    description: str                     # 目标描述(做什么)
    reason: str                          # 创建理由(为什么做)
    status: GoalStatus                   # pending | in_progress | completed | abandoned
    summary: Optional[str] = None        # 完成/放弃时的总结

    # explore_start 特有
    branch_ids: Optional[List[str]] = None       # 关联的分支 ID 列表

    # explore_merge 特有
    explore_start_id: Optional[str] = None       # 关联的 explore_start Goal
    merge_summary: Optional[str] = None          # 各分支汇总结果
    selected_branch: Optional[str] = None        # 选中的分支(可选)

    # 统计(后端维护,用于可视化边的数据)
    self_stats: GoalStats                # 自身统计(仅直接关联的 messages)
    cumulative_stats: GoalStats          # 累计统计(自身 + 所有后代)

实现agent/goal/models.py:Goal

ID 设计

  • 内部 ID:纯自增数字("1", "2", "3", "4"...),不管层级、分支、废弃
  • 层级关系:通过 parent_id 字段维护
  • 分支关系:通过 branch_id 字段维护(null 表示主线,"A"/"B" 表示分支)
  • 显示序号to_prompt() 时动态生成连续有意义的编号("1", "2", "2.1", "2.2"...)

统计更新逻辑

  • 每次添加 Message 时,更新对应 Goal 的 self_stats,并沿祖先链向上更新所有祖先的 cumulative_stats
  • 可视化中,折叠边使用 target Goal 的 cumulative_stats,展开边使用 self_stats
@dataclass
class GoalTree:
    mission: str                         # 总任务描述
    current_id: Optional[str] = None     # 当前焦点(内部 ID)
    goals: List[Goal]                    # 顶层目标(扁平列表,通过 parent_id 构建层级)

实现agent/goal/models.py:GoalTree

Message

Message 对应 LLM API 的消息,加上元数据。每条 Message 通过 goal_idbranch_id 关联所属 Goal。

@dataclass
class Message:
    message_id: str
    trace_id: str
    branch_id: Optional[str] = None      # 所属分支(null=主线, "A"/"B"=分支)
    role: Literal["assistant", "tool"]   # 和 LLM API 一致
    sequence: int                        # 全局顺序
    goal_id: str                         # 关联的 Goal 内部 ID
    tool_call_id: Optional[str]          # tool 消息关联对应的 tool_call
    content: Any                         # 消息内容(和 LLM API 格式一致)
    description: str                     # 消息描述(系统自动生成)

    # 元数据
    tokens: Optional[int] = None
    cost: Optional[float] = None
    created_at: datetime

description 字段(系统自动生成):

  • assistant 消息:优先取 content 中的 text,若无 text 则生成 "tool call: XX, XX"
  • tool 消息:使用 tool name

实现agent/execution/models.py:Message

Message 类型说明

  • role="assistant":模型的一次返回,可能同时包含文本和多个 tool_calls
  • role="tool":一个工具的执行结果,通过 tool_call_id 关联对应的 tool_call

工具设计

goal 工具:计划管理

@tool
def goal(
    add: Optional[str] = None,       # 添加目标(逗号分隔多个)
    done: Optional[str] = None,      # 完成当前目标,值为 summary
    abandon: Optional[str] = None,   # 放弃当前目标,值为原因
    focus: Optional[str] = None,     # 切换焦点到指定显示序号
) -> str:
    """管理执行计划。"""

实现agent/goal/tool.py:goal_tool

层级支持add 添加到当前 focus 的 goal 下作为子目标。

# 没有 focus 时,添加到顶层
goal(add="分析代码, 实现功能, 测试")
# 结果:
# [ ] 1. 分析代码
# [ ] 2. 实现功能
# [ ] 3. 测试

# focus 到某个 goal 后,add 添加为其子目标
goal(focus="2")
goal(add="设计接口, 实现代码")
# 结果:
# [ ] 1. 分析代码
# [→] 2. 实现功能
#     [ ] 2.1 设计接口
#     [ ] 2.2 实现代码
# [ ] 3. 测试

状态流转

pending ──focus──→ in_progress ──done──→ completed
                        │                    ↓
                        │              (压缩 context)
                        │              (级联:若所有兄弟都 completed,父 goal 自动 completed)
                        │
                     abandon
                        ↓
                   abandoned
                        ↓
                  (压缩 context)

explore 工具:并行探索

基于 sub-agent 机制实现。

@tool
def explore(
    branches: List[str],                  # 探索方向列表
    background: Optional[str] = None,     # 背景概括(可选)
) -> str:
    """
    并行探索多个方向,汇总结果。

    - background 有值:用它初始化各分支的 context
    - background 为空:继承主 message list
    """

Context 管理

1. Plan 注入

每次 LLM 调用时,在 system prompt 末尾注入当前计划状态。注入时过滤掉 abandoned 目标,使用连续的显示序号:

## Current Plan

**Mission**: 实现用户认证功能
**Current**: 2.2 实现登录接口

**Progress**:
[✓] 1. 分析代码
    → 用户模型在 models/user.py,使用 bcrypt 加密
[→] 2. 实现功能
    [✓] 2.1 设计接口
    [→] 2.2 实现登录接口  ← current
    [ ] 2.3 实现注册接口
[ ] 3. 测试

实现agent/goal/models.py:GoalTree.to_prompt

2. 完成时压缩

当调用 goal(done="...") 时:

  1. 找到该 goal 关联的所有 messages(通过 goal_id)
  2. 将详细 messages 替换为一条 summary message
  3. 更新 goal 状态为 completed

3. 回溯(Abandon)

两种模式:

模式 1:需要修改的计划还没有执行

直接修改计划并继续执行。Goal 状态为 pending 时,可以直接修改 description 或删除。

模式 2:需要修改的计划已经执行

  1. 将原 Goal 标记为 abandoned(保留在 GoalTree 数据中,但 to_prompt() 不展示)
  2. 将废弃分支关联的 messages 做 summary
  3. 将 summary 累积到新分支的第一条消息中(供 LLM 参考历史失败原因)
  4. 创建新的 Goal 继续执行

Before 回溯

GoalTree 数据:
  [✓] 1. 分析代码               (内部ID: 1)
  [→] 2. 实现方案 A              (内部ID: 2)
  [ ] 3. 测试                    (内部ID: 3)

Messages:
  [分析代码的 20 条 message...]
  [实现方案 A 的 30 条 message...]
  [测试失败的 message...]

After 回溯

GoalTree 数据(含废弃):
  [✓] 1. 分析代码               (内部ID: 1)
  [✗] 2. 实现方案 A              (内部ID: 2, abandoned)
  [→] 3. 实现方案 B              (内部ID: 4, 新建)
  [ ] 4. 测试                    (内部ID: 3)

to_prompt() 输出(给 LLM,连续编号):
  [✓] 1. 分析代码
  [→] 2. 实现方案 B  ← current
  [ ] 3. 测试

Messages:
  [分析代码的 20 条 message...]
  [Summary: "尝试方案 A,因依赖问题失败"]     ← 原 messages 压缩为 1 条
  [方案 B 第一条消息,包含废弃分支的 summary]  ← 供 LLM 参考
  [方案 B 的后续 message...]

实现agent/goal/compaction.py

可视化

DAG 模型

可视化展示为 DAG(有向无环图),不是树。

核心概念

  • 节点 = Goal 完成后的结果/里程碑
  • = 从一个结果到下一个结果的执行过程(动作/策略)
  • 每个节点对应一条入边,入边的数据从该 Goal 关联的 Messages 聚合

展开/折叠:对边操作,对应目标的层级展开。

折叠视图(只看顶层 Goals):
[START] ──→ [1:分析完成] ──→ [2:实现完成] ──→ [3:测试完成]
                              逻辑边

展开 [1]→[2] 的边(显示 Goal 2 的子目标):
[START] ──→ [1:分析完成] ──→ [2.1:设计完成] ──→ [2.2:代码完成] ──→ [3:测试完成]
                              执行边             执行边

展开时,父节点 [2] 被子节点 [2.1], [2.2] 替代。 折叠时,子节点合并回父节点 [2]。

嵌套展开:如果 2.1 也有子目标,可以继续展开 [1]→[2.1] 的边。

废弃分支:在可视化中以灰色样式展示废弃分支。

[1:分析完成] ──→ [2:方案A(废弃)] ──→ ...     ← 灰色
             ──→ [4:方案B] ──→ [3:测试]       ← 正常

API

后端提供 GoalTree 数据,前端负责生成 DAG 视图。

REST 端点

GET /api/traces/{trace_id}           # 获取 Trace + GoalTree
GET /api/traces/{trace_id}/messages?goal_id=2.1  # 获取 Messages(边详情)

响应(GoalTree 部分):

{
  "goal_tree": {
    "mission": "实现用户认证功能",
    "current_id": "4",
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "branch_id": null,
        "type": "normal",
        "description": "分析代码",
        "reason": "了解现有结构",
        "status": "completed",
        "summary": "用户模型在 models/user.py",
        "self_stats": {"message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2"},
        "cumulative_stats": {"message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2"}
      },
      {
        "id": "2",
        "parent_id": null,
        "branch_id": null,
        "type": "normal",
        "description": "实现功能",
        "reason": "核心任务",
        "status": "in_progress",
        "self_stats": {"message_count": 0, ...},
        "cumulative_stats": {"message_count": 11, "total_tokens": 5700, ...}
      },
      {
        "id": "3",
        "parent_id": "2",
        "branch_id": null,
        "type": "normal",
        "description": "设计接口",
        "status": "completed",
        "self_stats": {...}
      },
      {
        "id": "4",
        "parent_id": "2",
        "branch_id": null,
        "type": "normal",
        "description": "实现代码",
        "status": "in_progress",
        "self_stats": {...}
      },
      {
        "id": "5",
        "parent_id": null,
        "branch_id": null,
        "type": "normal",
        "description": "测试",
        "status": "pending",
        ...
      }
    ]
  }
}

DAG 生成逻辑(前端实现):

  1. 根据用户展开状态,确定可见 Goal 序列
  2. 相邻 Goal 之间形成边
  3. 边的统计数据从 target Goal 的 stats 获取(折叠用 cumulative_stats,展开用 self_stats
  4. 边的详细内容通过 Messages API 查询

实现:见 frontend/API.md

WebSocket 实时推送

ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0

事件类型

事件 触发时机 payload
connected WebSocket 连接成功 trace_id, current_event_id, goal_tree(完整 GoalTree)
goal_added 新增 Goal goal 完整数据(含 self_stats, cumulative_stats)
goal_updated Goal 状态变化(含级联完成) goal_id, updates(含 cumulative_stats),affected_goals
message_added 新 Message message 数据(含 goal_id),affected_goals
trace_completed 执行完成 统计信息

事件详情

connected - 连接时推送完整 GoalTree,前端据此初始化 DAG:

{
  "event": "connected",
  "trace_id": "xxx",
  "current_event_id": 42,
  "goal_tree": { "mission": "...", "goals": [...] }
}

message_added - 新 Message 时,后端更新统计并推送受影响的 Goals:

{
  "event": "message_added",
  "message": { "message_id": "...", "role": "assistant", "goal_id": "2.1", "..." : "..." },
  "affected_goals": [
    { "goal_id": "2.1", "self_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"}, "cumulative_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"} },
    { "goal_id": "2", "cumulative_stats": {"message_count": 12, "total_tokens": 6800, "total_cost": 0.08, "preview": "glob → read → edit × 5 → bash"} }
  ]
}

affected_goals 包含该 Message 直接关联的 Goal(更新 self_stats + cumulative_stats)以及所有祖先 Goal(仅更新 cumulative_stats)。前端根据当前展开状态选择使用哪个 stats 渲染边。

goal_updated - Goal 状态变化时推送,包含级联完成场景:

{
  "event": "goal_updated",
  "goal_id": "2.1",
  "updates": { "status": "completed", "summary": "接口设计完成" },
  "affected_goals": [
    { "goal_id": "2.1", "cumulative_stats": {"message_count": 4, "total_tokens": 2000, "total_cost": 0.03, "preview": "read → edit × 2"} },
    { "goal_id": "2", "status": "completed", "summary": "功能实现完成", "cumulative_stats": {"message_count": 12, "total_tokens": 6800, "total_cost": 0.08, "preview": "..."} }
  ]
}

当所有子 Goal 完成时,后端自动级联完成父 Goal,并在 affected_goals 中包含所有状态变更的祖先。前端收到后直接更新对应节点,无需自行计算。

goal_added - 新增 Goal,携带完整 Goal 数据:

{
  "event": "goal_added",
  "goal": { "id": "2.1", "description": "设计接口", "reason": "需要先定义 API", "status": "pending", "self_stats": {}, "cumulative_stats": {} },
  "parent_id": "2"
}

实现agent/execution/websocket.py

存储结构

.trace/{trace_id}/
├── goal.json          # GoalTree(嵌套 JSON,含 abandoned 目标)
├── messages/          # Messages(每条独立文件)
│   ├── {message_id}.json
│   └── ...
├── events.jsonl       # 事件流(WebSocket 断线续传)
└── meta.json          # Trace 元数据

分支-合并设计(explore 工具)

场景

主线 Agent:
  [1] 分析问题
  [2] explore_start: 启动并行探索 (type=explore_start)
      │
      ├── 分支A (sub-agent): [1]设计 → [2]实现 → 完成
      └── 分支B (sub-agent): [1]设计 → [2]实现 → [3]测试 → 完成
      │
      ↓ (工具返回汇总结果)
  [3] explore_merge: 评估选择JWT (type=explore_merge)
  [4] 完善实现

核心原则

  • 每个分支是独立的 sub-agent,有自己的 GoalTree 和 Message List
  • 模型在分支内看到的是简单的连续编号 "1", "2", "3"(独立于主线)
  • explore_startexplore_merge 是主线 GoalTree 中的特殊 Goal 类型
  • 分支数据独立存储,不直接嵌入主线 GoalTree
  • explore 工具返回时自动汇总各分支 summary

数据结构

主线 GoalTree(不含分支内部 Goals):

# Goal 类型在前面已定义,这里展示主线 GoalTree 示例
goals = [
    Goal(id="1", type="normal", description="分析问题", ...),
    Goal(id="2", type="explore_start", description="探索认证方案",
         branch_ids=["A", "B"], ...),
    Goal(id="3", type="explore_merge", description="选择JWT方案",
         explore_start_id="2", merge_summary="...", selected_branch="A", ...),
    Goal(id="4", type="normal", description="完善实现", ...),
]

分支上下文(独立存储):

@dataclass
class BranchContext:
    """分支执行上下文(独立的 sub-agent 环境)"""
    id: str                          # 分支 ID,如 "A", "B"
    explore_start_id: str            # 关联的 explore_start Goal ID
    description: str                 # 探索方向描述
    status: BranchStatus             # exploring | completed | abandoned

    # 独立的执行环境
    goal_tree: GoalTree              # 分支自己的 GoalTree(简单编号 1, 2, 3...)

    summary: Optional[str]           # 完成时的总结
    cumulative_stats: GoalStats      # 累计统计
    last_message: Optional[Message]  # 最新消息(用于可视化预览)

存储结构

.trace/{trace_id}/
├── meta.json              # Trace 元数据
├── goal.json              # 主线 GoalTree
├── messages/              # 主线 Messages
│   └── ...
├── branches/              # 分支数据(独立存储)
│   ├── A/
│   │   ├── meta.json      # BranchContext 元数据
│   │   ├── goal.json      # 分支 A 的 GoalTree
│   │   └── messages/      # 分支 A 的 Messages
│   └── B/
│       └── ...
└── events.jsonl           # 事件流

DAG 可视化

折叠视图(explore 区域显示为 start → merge):

[1:分析] ──→ [2:explore_start] ──→ [3:explore_merge] ──→ [4:完善]
                    │                      │
               (启动2分支)            (汇总评估)

展开分支视图(显示并行路径):

                  ┌──→ [A:JWT方案] ────┐
[1:分析] ──→ [2] ─┤                    ├──→ [3:合并] ──→ [4:完善]
                  └──→ [B:Session方案] ┘

继续展开分支 A 内部

                  ┌──→ [A.1:设计] → [A.2:实现] ──┐
[1:分析] ──→ [2] ─┤                              ├──→ [3:合并] ──→ [4:完善]
                  └──→ [B:Session方案] ──────────┘

注意:[A.1], [A.2]前端显示格式,后端存储的是 (branch_id="A", goal_id="1")

前端 API

REST:返回主线 GoalTree + 分支元数据(不含分支内部 Goals),按需加载分支详情。

GET /api/traces/{trace_id}

响应:

{
  "goal_tree": {
    "goals": [
      {"id": "1", "type": "normal", "description": "分析问题", ...},
      {"id": "2", "type": "explore_start", "branch_ids": ["A", "B"], ...},
      {"id": "3", "type": "explore_merge", "explore_start_id": "2", ...},
      {"id": "4", "type": "normal", ...}
    ]
  },
  "branches": {
    "A": {
      "id": "A",
      "explore_start_id": "2",
      "description": "JWT方案",
      "status": "completed",
      "summary": "JWT方案实现完成,无状态但token较大",
      "cumulative_stats": {"message_count": 8, "total_tokens": 4000, ...},
      "goal_count": 2,
      "last_message": {"role": "assistant", "content": "JWT实现完成...", ...}
    },
    "B": {...}
  }
}

按需加载分支详情

GET /api/traces/{trace_id}/branches/{branch_id}

响应:

{
  "id": "A",
  "description": "JWT方案",
  "status": "completed",
  "summary": "...",
  "goal_tree": {
    "goals": [
      {"id": "1", "description": "JWT设计", ...},
      {"id": "2", "description": "JWT实现", ...}
    ]
  },
  "cumulative_stats": {...}
}

WebSocket 事件

事件 触发时机 payload
branch_started 分支开始探索 explore_start_id, branch 元数据
branch_goal_added 分支内新增 Goal branch_id, goal
branch_message_added 分支内新 Message branch_id, message, affected_goals
branch_completed 分支完成 branch_id, summary, cumulative_stats, last_message
explore_completed 所有分支完成 explore_start_id, merge_summary

explore 工具流程

@tool
def explore(branches: List[str]) -> str:
    """并行探索多个方向"""

    # 1. 创建 explore_start Goal
    start_goal = Goal(
        id=next_id(),
        type="explore_start",
        description=f"探索 {len(branches)} 个方案",
        branch_ids=[chr(ord('A') + i) for i in range(len(branches))],
    )

    # 2. 为每个方向创建 sub-agent
    for i, desc in enumerate(branches):
        branch_id = chr(ord('A') + i)
        create_branch_context(
            branch_id=branch_id,
            explore_start_id=start_goal.id,
            description=desc,
        )
        spawn_sub_agent(branch_id)

    # 3. 等待所有分支完成
    results = await gather_branch_results()

    # 4. 创建 explore_merge Goal
    merge_summary = format_exploration_results(results)
    merge_goal = Goal(
        id=next_id(),
        type="explore_merge",
        description="评估探索结果",
        explore_start_id=start_goal.id,
        merge_summary=merge_summary,
    )

    # 5. 返回汇总给主线 Agent
    return merge_summary

汇总结果示例(作为 explore 工具的返回值):

## 探索结果

### 分支 A: JWT 方案
实现完成。优点:无状态,易扩展。缺点:token 较大,无法主动失效。

### 分支 B: Session 方案
实现完成。优点:token 小,可主动失效。缺点:需要 Redis 存储。

---
两种方案都已实现,请选择一种继续。

Context 压缩

分支完成后的压缩策略:

  1. 分支完成时:分支的详细 context 压缩为 summary,存储在 BranchContext.summary
  2. explore 完成后:所有分支的 summary 汇总为 merge_summary
  3. 主线 context:explore 工具调用被压缩为一条包含 merge_summary 的消息

与 OpenCode 方案的对比

方面 OpenCode 我们的方案
Plan 格式 纯文本 (plan.md) 结构化 (GoalTree 嵌套 JSON)
Plan 与执行关联 通过 goal_id 关联
执行记录 Message List Message List(加 goal_id 元数据)
压缩时机 事后(context 满时) 增量(goal 完成/放弃时)
并行探索 Sub-agent(手动管理) explore 工具(自动汇总)
回溯能力 有限 精确(基于 goal 压缩 + 废弃分支 summary)
可视化 DAG(边可展开/折叠)
工具复杂度 todoread/todowrite goal/explore

实现位置

功能 文件路径 状态
Goal 数据模型 agent/goal/models.py 待调整(ID 映射)
goal 工具 agent/goal/tool.py 待调整
explore 工具 agent/goal/explore.py 待实现
Context 压缩 agent/goal/compaction.py 待调整
Message 数据模型 agent/execution/models.py 待调整(Step→Message)
TraceStore 协议 agent/execution/protocols.py 待调整
DAG 可视化 API agent/execution/api.py 待调整(tree→DAG)
WebSocket 推送 agent/execution/websocket.py 待调整(新增 goal 事件)
Plan 注入 agent/core/runner.py 待调整

渐进式实现计划

Phase 1: 基础 goal 工具

  • GoalTree 数据结构(含 ID 映射)
  • goal 工具(add, done, focus)
  • Plan 注入到 system prompt(含显示序号生成)
  • Message 模型(替代 Step)

Phase 2: 回溯支持

  • abandon 操作(两种模式)
  • 废弃分支 summary 累积到新分支
  • 基于 goal 的 context 压缩

Phase 3: 可视化

  • DAG 视图 API
  • WebSocket goal/message 事件
  • 展开/折叠逻辑

Phase 4: 并行探索

  • explore 工具
  • 独立 message list 管理
  • 结果汇总机制