本文档描述 Agent 的 Context 管理、执行计划和探索机制。
┌─────────────────┐
│ plan.md │ ← 文本格式的计划(TODO 列表)
└─────────────────┘
↓
┌─────────────────┐
│ 线性 Message │ ← 对话历史
│ List │
└─────────────────┘
↓
┌─────────────────┐
│ Prune + Full │ ← 两阶段压缩
│ Compaction │
└─────────────────┘
↓
┌─────────────────┐
│ Sub-Agent │ ← 隔离大任务
└─────────────────┘
数据结构:
存储:
Storage Key:
["message", sessionID, messageID] -> MessageV2.Info
["part", messageID, partID] -> MessageV2.Part
两阶段压缩:
阶段 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 消息继续
数据结构:
Todo.Info = {
id: string
content: string // 任务描述
status: string // pending | in_progress | completed | cancelled
priority: string // high | medium | low
}
存储:文件系统(.opencode/plans/xxx.md)或 Storage
Agent Mode:
primary: 主代理,执行工具subagent: 子代理,独立 context,结果汇总回主会话内置 Sub-Agents:
general: 通用代理,可并行执行多个任务explore: 代码探索专用,仅允许查询工具compaction: 上下文总结专用Subtask 执行:
优点:
局限:
基于 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 是派生视图:
goal_id 关联 Goal不存在独立的"边"数据结构,边在可视化时从 Messages 聚合生成。
@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 设计:
parent_id 字段维护branch_id 字段维护(null 表示主线,"A"/"B" 表示分支)to_prompt() 时动态生成连续有意义的编号("1", "2", "2.1", "2.2"...)统计更新逻辑:
self_stats,并沿祖先链向上更新所有祖先的 cumulative_statscumulative_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 对应 LLM API 的消息,加上元数据。每条 Message 通过 goal_id 和 branch_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_callsrole="tool":一个工具的执行结果,通过 tool_call_id 关联对应的 tool_call@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)
基于 sub-agent 机制实现。
@tool
def explore(
branches: List[str], # 探索方向列表
background: Optional[str] = None, # 背景概括(可选)
) -> str:
"""
并行探索多个方向,汇总结果。
- background 有值:用它初始化各分支的 context
- background 为空:继承主 message list
"""
每次 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
当调用 goal(done="...") 时:
两种模式:
模式 1:需要修改的计划还没有执行
直接修改计划并继续执行。Goal 状态为 pending 时,可以直接修改 description 或删除。
模式 2:需要修改的计划已经执行
abandoned(保留在 GoalTree 数据中,但 to_prompt() 不展示)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(有向无环图),不是树。
核心概念:
展开/折叠:对边操作,对应目标的层级展开。
折叠视图(只看顶层 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:测试] ← 正常
后端提供 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 生成逻辑(前端实现):
cumulative_stats,展开用 self_stats)实现:见 frontend/API.md
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 元数据
主线 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] 完善实现
核心原则:
explore_start 和 explore_merge 是主线 GoalTree 中的特殊 Goal 类型主线 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 # 事件流
折叠视图(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")。
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 |
@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 存储。
---
两种方案都已实现,请选择一种继续。
分支完成后的压缩策略:
| 方面 | 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 |
待调整 |