Talegorithm 1 месяц назад
Родитель
Сommit
e7c96855ad
5 измененных файлов с 872 добавлено и 533 удалено
  1. 9 2
      agent/core/runner.py
  2. 3 0
      agent/tools/builtin/goal.py
  3. 462 180
      docs/context-management.md
  4. 236 351
      frontend/API.md
  5. 162 0
      test_integration.py

+ 9 - 2
agent/core/runner.py

@@ -357,9 +357,16 @@ class AgentRunner:
                     for tc in tool_calls:
                         tool_name = tc["function"]["name"]
                         tool_args = tc["function"]["arguments"]
+
+                        # 解析参数
                         if isinstance(tool_args, str):
-                            import json
-                            tool_args = json.loads(tool_args)
+                            if tool_args.strip():  # 非空字符串
+                                import json
+                                tool_args = json.loads(tool_args)
+                            else:
+                                tool_args = {}  # 空字符串转换为空字典
+                        elif tool_args is None:
+                            tool_args = {}  # None 转换为空字典
 
                         # 拦截 goal 工具调用(需要保存更新后的 GoalTree)
                         if tool_name == "goal":

+ 3 - 0
agent/tools/builtin/goal.py

@@ -5,6 +5,7 @@ Goal 工具 - 执行计划管理
 """
 
 from typing import Optional
+from agent.tools import tool
 
 
 # 全局 GoalTree 引用(由 AgentRunner 注入)
@@ -22,11 +23,13 @@ def get_goal_tree():
     return _current_goal_tree
 
 
+@tool(description="管理执行计划,添加/完成/放弃目标,切换焦点")
 def goal(
     add: Optional[str] = None,
     done: Optional[str] = None,
     abandon: Optional[str] = None,
     focus: Optional[str] = None,
+    uid: str = ""
 ) -> str:
     """
     管理执行计划。

+ 462 - 180
docs/context-management.md

@@ -1,6 +1,6 @@
 # Context 管理与执行计划
 
-> 本文档描述 Agent 的 Context 管理、执行计划和探索机制。
+> 本文档描述 Agent 的 Context 管理、执行计划和 Sub-Agent 机制。
 
 ---
 
@@ -9,7 +9,8 @@
 1. **自主长程执行**:Agent 能独立执行复杂任务,无需人工频繁干预
 2. **有效的 Context 管理**:长任务中保持关键信息,压缩次要细节
 3. **支持探索和回溯**:能尝试多种方案,失败时能有效回溯
-4. **简单的工具接口**:LLM 只需理解少量简单工具,复杂逻辑由系统处理
+4. **统一的 Agent 模型**:主 Agent 和 Sub-Agent 使用相同的 Trace 结构
+5. **简单的工具接口**:LLM 只需理解少量简单工具,复杂逻辑由系统处理
 
 ---
 
@@ -130,57 +131,137 @@ Todo.Info = {
 ### 核心思路
 
 ```
-基于 OpenCode 方案,增强三个能力
+基于 OpenCode 方案,但采用更统一的架构
 1. 结构化 Plan(goal 工具)
-2. 并行探索-合并(explore 工具)
-3. 精确回溯(abandon + context 压缩)
+2. 统一的 Trace 模型(主 Agent 和 Sub-Agent 都是 Trace)
+3. 并行探索-合并(explore 工具,启动独立的 Sub-Traces)
+4. 精确回溯(abandon + context 压缩)
 ```
 
 ### 架构
 
 ```
 ┌─────────────────────────────────────────────┐
-│              GoalTree (嵌套 JSON)             │
-│  层级目标,LLM 通过 goal 工具维护              │
-│  注入 LLM 时过滤废弃目标,重新生成连续显示序号   │
+│            每个 Agent = 一个 Trace            │
+│  主 Trace 和 Sub-Trace 使用相同的数据结构      │
 └─────────────────────────────────────────────┘
          ┌────────────┴────────────┐
          ↓                         ↓
 ┌─────────────────┐      ┌─────────────────┐
-│   Messages      │      │  并行探索  
-│   (扁平列表,    │      │  (explore 工具)
-│    goal_id 关联) │      │  多个独立分支 
+│   GoalTree      │      │   Messages
+│   (层级目标)     │      │   (扁平列表)   
+│   goal 工具维护  │      │   goal_id 关联
 └─────────────────┘      └─────────────────┘
          │                         │
          ↓                         ↓
 ┌─────────────────┐      ┌─────────────────┐
-│  完成/回溯       │      │  合并评估        │
-│  done/abandon   │      │  返回主会话      │
-│  触发 context   │      └─────────────────┘
-│  压缩           │
-└─────────────────┘
+│  工具调用        │      │  Sub-Traces     │
+│  explore/       │      │  完全独立的      │
+│  delegate       │──────▶  Trace 实例      │
+└─────────────────┘      └─────────────────┘
 ┌─────────────────────────────────────────────┐
 │              DAG 可视化(派生视图)             │
 │  从 GoalTree + Messages 生成                  │
 │  节点 = 结果/里程碑,边 = 动作/执行过程         │
-│  边可展开/折叠,对应目标的层级展开      
+│  边可展开/折叠,Sub-Trace 作为折叠边显示
 └─────────────────────────────────────────────┘
 ```
 
-### 数据结构
+### 核心概念:统一的 Trace 模型
 
-#### 两层数据
+**关键设计**:每个 Agent(主 Agent 或 Sub-Agent)都是一个完整的 Trace。
+
+```python
+# 主 Agent
+Trace(trace_id="abc123", mode="agent", task="实现用户认证")
+
+# Sub-Agent(并行探索)
+Trace(trace_id="abc123.A", parent_trace_id="abc123", agent_type="explore", task="JWT 方案")
+Trace(trace_id="abc123.B", parent_trace_id="abc123", agent_type="explore", task="Session 方案")
+
+# Sub-Agent(单线委托)
+Trace(trace_id="abc123.task1", parent_trace_id="abc123", agent_type="delegate", task="实现具体功能")
+```
+
+**优点**:
+- **概念统一**:不需要额外的 SubAgentContext/BranchContext 概念
+- **ID 简洁**:每个 Trace 内部独立编号(1, 2, 3),不需要前缀
+- **完全隔离**:每个 Trace 有独立的 GoalTree、Message List、LLM Context
+- **自然分布式**:每个 Trace 可以独立运行、迁移、存储
+- **层级清晰**:从 trace_id 可以直接解析出父子关系(`abc123.A` 的父是 `abc123`)
+
+### 数据结构
 
 后端存储两类数据,可视化的 DAG 是派生视图:
 
-1. **GoalTree**(嵌套 JSON):层级目标,注入 LLM
-2. **Messages**(扁平列表):执行记录,通过 `goal_id` 关联 Goal
+1. **GoalTree**:层级目标,注入 LLM
+2. **Messages**:执行记录,通过 `goal_id` 关联 Goal
 
 不存在独立的"边"数据结构,边在可视化时从 Messages 聚合生成。
 
+#### Trace
+
+```python
+@dataclass
+class Trace:
+    """
+    执行轨迹 - 一次完整的 Agent 运行
+
+    主 Agent 和 Sub-Agent 使用相同的 Trace 结构
+    """
+    trace_id: str                        # 层级化 ID:"abc123", "abc123.A", "abc123.A.1"
+    mode: Literal["call", "agent"]
+    task: Optional[str] = None
+
+    # 父子关系
+    parent_trace_id: Optional[str] = None     # 父 Trace ID
+    parent_goal_id: Optional[str] = None      # 哪个 Goal 启动的
+
+    # 类型标记(语义)
+    agent_type: Optional[str] = None          # "main", "explore", "delegate", "compaction"
+
+    # 权限和配置
+    context: Dict[str, Any] = field(default_factory=dict)
+    # 可以包含:
+    # - allowed_tools: ["read", "grep"]    # 工具白名单
+    # - denied_tools: ["bash"]             # 工具黑名单
+    # - max_turns: 10                      # 最大对话轮数
+    # - timeout: 300                       # 超时(秒)
+
+    status: Literal["running", "completed", "failed"] = "running"
+
+    # 统计
+    total_messages: int = 0
+    total_tokens: int = 0
+    total_cost: float = 0.0
+
+    created_at: datetime
+    completed_at: Optional[datetime] = None
+```
+
+**实现**:`agent/execution/models.py:Trace`
+
+**Trace ID 命名规则**:
+- **主 Trace**:短随机 ID(如 `"abc123"`)
+- **Sub-Trace**:`parent_id.suffix`(如 `"abc123.A"`, `"abc123.task1"`)
+- **嵌套 Sub-Trace**:继续追加(如 `"abc123.A.1"`, `"abc123.A.task2"`)
+
+**从 trace_id 解析父子关系**:
+```python
+def parse_parent_trace_id(trace_id: str) -> Optional[str]:
+    """从 trace_id 解析出 parent_trace_id"""
+    parts = trace_id.rsplit('.', 1)
+    return parts[0] if len(parts) > 1 else None
+
+# 示例
+parse_parent_trace_id("abc123.A")    # → "abc123"
+parse_parent_trace_id("abc123.A.1")  # → "abc123.A"
+parse_parent_trace_id("abc123")      # → None
+```
+
 #### Goal
 
 ```python
@@ -192,13 +273,12 @@ class GoalStats:
     preview: Optional[str] = None        # 工具调用摘要,如 "read_file → edit_file → bash"
 
 GoalStatus = Literal["pending", "in_progress", "completed", "abandoned"]
-GoalType = Literal["normal", "explore_start", "explore_merge"]
+GoalType = Literal["normal", "agent_call"]  # agent_call: 启动了 Sub-Agent
 
 @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                     # 目标描述(做什么)
@@ -206,13 +286,9 @@ class Goal:
     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        # 选中的分支(可选)
+    # agent_call 特有
+    sub_trace_ids: Optional[List[str]] = None  # 启动的 Sub-Trace IDs
+    agent_call_mode: Optional[str] = None      # "explore" | "delegate" | "sequential"
 
     # 统计(后端维护,用于可视化边的数据)
     self_stats: GoalStats                # 自身统计(仅直接关联的 messages)
@@ -222,9 +298,8 @@ class Goal:
 **实现**:`agent/goal/models.py:Goal`
 
 **ID 设计**:
-- **内部 ID**:纯自增数字("1", "2", "3", "4"...),不管层级、分支、废弃
+- **内部 ID**:每个 Trace 独立编号("1", "2", "3"),简单自增
 - **层级关系**:通过 `parent_id` 字段维护
-- **分支关系**:通过 `branch_id` 字段维护(null 表示主线,"A"/"B" 表示分支)
 - **显示序号**:`to_prompt()` 时动态生成连续有意义的编号("1", "2", "2.1", "2.2"...)
 
 **统计更新逻辑**:
@@ -236,24 +311,24 @@ class Goal:
 class GoalTree:
     mission: str                         # 总任务描述
     current_id: Optional[str] = None     # 当前焦点(内部 ID)
-    goals: List[Goal]                    # 顶层目标(扁平列表,通过 parent_id 构建层级)
+    goals: List[Goal]                    # 扁平列表,通过 parent_id 构建层级
+    _next_id: int = 1                    # 内部 ID 计数器
 ```
 
 **实现**:`agent/goal/models.py:GoalTree`
 
 #### Message
 
-Message 对应 LLM API 的消息,加上元数据。每条 Message 通过 `goal_id` 和 `branch_id` 关联所属 Goal。
+Message 对应 LLM API 的消息,加上元数据。每条 Message 通过 `goal_id` 关联所属 Goal。
 
 ```python
 @dataclass
 class Message:
     message_id: str
-    trace_id: str
-    branch_id: Optional[str] = None      # 所属分支(null=主线, "A"/"B"=分支)
+    trace_id: str                        # 所属 Trace ID
     role: Literal["assistant", "tool"]   # 和 LLM API 一致
-    sequence: int                        # 全局顺序
-    goal_id: str                         # 关联的 Goal 内部 ID
+    sequence: int                        # 当前 Trace 内的顺序
+    goal_id: str                         # 关联的 Goal 内部 ID(如 "1", "2")
     tool_call_id: Optional[str]          # tool 消息关联对应的 tool_call
     content: Any                         # 消息内容(和 LLM API 格式一致)
     description: str                     # 消息描述(系统自动生成)
@@ -274,6 +349,15 @@ class Message:
 - `role="assistant"`:模型的一次返回,可能同时包含文本和多个 tool_calls
 - `role="tool"`:一个工具的执行结果,通过 `tool_call_id` 关联对应的 tool_call
 
+**查询 Message**:
+```python
+# 查询主 Trace 的 Messages
+GET /api/traces/abc123/messages?goal_id=2
+
+# 查询 Sub-Trace 的 Messages
+GET /api/traces/abc123.A/messages?goal_id=1
+```
+
 ### 工具设计
 
 #### goal 工具:计划管理
@@ -328,7 +412,7 @@ pending ──focus──→ in_progress ──done──→ completed
 
 #### explore 工具:并行探索
 
-基于 sub-agent 机制实现
+启动多个独立的 Sub-Traces 并行执行
 
 ```python
 @tool
@@ -339,11 +423,152 @@ def explore(
     """
     并行探索多个方向,汇总结果。
 
-    - background 有值:用它初始化各分支的 context
-    - background 为空:继承主 message list
+    每个方向会启动一个独立的 Sub-Trace(agent_type="explore")。
+
+    - background 有值:用它初始化各 Sub-Trace 的 context
+    - background 为空:继承主 Trace 的 message list
+
+    返回:各分支的汇总结果
     """
 ```
 
+**内部实现**:
+```python
+async def explore_tool(branches: List[str], background: Optional[str] = None) -> str:
+    current_trace_id = get_current_trace_id()
+    current_goal_id = get_current_goal_id()
+
+    # 1. 创建 agent_call Goal
+    goal = Goal(
+        id=next_id(),
+        type="agent_call",
+        description=f"并行探索 {len(branches)} 个方案",
+        agent_call_mode="explore",
+        sub_trace_ids=[],
+    )
+
+    # 2. 为每个分支创建 Sub-Trace
+    sub_traces = []
+    for i, desc in enumerate(branches):
+        suffix = chr(ord('A') + i)  # A, B, C...
+        sub_trace = Trace.create(
+            trace_id=f"{current_trace_id}.{suffix}",
+            parent_trace_id=current_trace_id,
+            parent_goal_id=current_goal_id,
+            agent_type="explore",
+            task=desc,
+            context=get_explore_context(),  # 从配置获取权限设置
+        )
+        sub_traces.append(sub_trace)
+        goal.sub_trace_ids.append(sub_trace.trace_id)
+
+    # 3. 并行执行
+    results = await asyncio.gather(
+        *[run_agent(st, background) for st in sub_traces]
+    )
+
+    # 4. 汇总返回
+    summary = format_explore_results(results)
+    return summary
+```
+
+**权限配置**:
+```python
+# 示例:系统配置中定义 Sub-Trace 的默认权限
+# 可以根据 agent_type 或具体场景调整
+
+def get_explore_context() -> Dict[str, Any]:
+    """获取 explore 模式的 context 配置"""
+    return {
+        # 工具权限:根据实际需求配置
+        # 选项 1:受限权限(只读,快速探索)
+        # "allowed_tools": ["read", "grep", "glob"],
+
+        # 选项 2:完整权限(需要实际实现来评估)
+        # "allowed_tools": None,  # None = 所有工具
+
+        # 步数限制
+        "max_turns": 20,
+    }
+```
+
+#### delegate 工具:单线委托
+
+将大任务委托给独立的 Sub-Trace 执行。
+
+```python
+@tool
+def delegate(task: str) -> str:
+    """
+    将大任务委托给独立的 sub-agent。
+
+    创建一个独立的 Sub-Trace(agent_type="delegate"),
+    有独立的 context,权限由配置决定。
+
+    返回:任务执行结果摘要
+    """
+```
+
+**内部实现**:
+```python
+async def delegate_tool(task: str) -> str:
+    current_trace_id = get_current_trace_id()
+    current_goal_id = get_current_goal_id()
+
+    # 1. 创建 agent_call Goal
+    goal = Goal(
+        id=next_id(),
+        type="agent_call",
+        description=f"委托任务: {task}",
+        agent_call_mode="delegate",
+        sub_trace_ids=[],
+    )
+
+    # 2. 创建 Sub-Trace
+    suffix = f"task{next_task_num()}"  # task1, task2...
+    sub_trace = Trace.create(
+        trace_id=f"{current_trace_id}.{suffix}",
+        parent_trace_id=current_trace_id,
+        parent_goal_id=current_goal_id,
+        agent_type="delegate",
+        task=task,
+        context=get_delegate_context(),  # 从配置获取权限设置
+    )
+    goal.sub_trace_ids.append(sub_trace.trace_id)
+
+    # 3. 执行
+    result = await run_agent(sub_trace)
+
+    # 4. 返回摘要
+    return result.summary
+```
+
+**权限配置**:
+```python
+def get_delegate_context() -> Dict[str, Any]:
+    """获取 delegate 模式的 context 配置"""
+    return {
+        # 工具权限:根据实际需求配置
+        # 通常 delegate 用于完整任务,给完整权限
+        "allowed_tools": None,  # None = 所有工具
+
+        # 或者也可以限制:
+        # "allowed_tools": ["read", "write", "edit", "bash"],
+        # "denied_tools": ["危险工具"],
+
+        # 步数限制
+        "max_turns": 50,
+    }
+```
+
+**注意**:
+- `explore` 和 `delegate` 的权限配置是独立的,可以根据需求调整
+- 不是工具本身决定权限,而是通过 `Trace.context` 配置
+- 典型配置:
+  - `explore`:可能受限(快速探索)或完整(需要实现验证)
+  - `delegate`:通常完整权限(执行完整任务)
+- 但这些都不是固定的,可以根据具体场景调整
+
 ### Context 管理
 
 #### 1. Plan 注入
@@ -611,174 +836,226 @@ ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0
 ### 存储结构
 
 ```
-.trace/{trace_id}/
-├── goal.json          # GoalTree(嵌套 JSON,含 abandoned 目标)
-├── messages/          # Messages(每条独立文件)
-│   ├── {message_id}.json
+.trace/
+├── abc123/                    # 主 Trace
+│   ├── meta.json              # Trace 元数据
+│   ├── goal.json              # GoalTree
+│   ├── messages/              # Messages
+│   │   ├── {message_id}.json
+│   │   └── ...
+│   └── events.jsonl           # 事件流
+│
+├── abc123.A/                  # Sub-Trace A(并行探索)
+│   ├── meta.json              # parent_trace_id: "abc123"
+│   ├── goal.json              # 独立的 GoalTree
+│   ├── messages/
+│   └── events.jsonl
+│
+├── abc123.B/                  # Sub-Trace B(并行探索)
 │   └── ...
-├── events.jsonl       # 事件流(WebSocket 断线续传)
-└── meta.json          # Trace 元数据
+│
+└── abc123.task1/              # Sub-Trace task1(单线委托)
+    └── ...
 ```
 
+**关键点**:
+- 每个 Trace(主/子)都是完全独立的目录
+- 从 trace_id 可以直接找到对应目录
+- 通过 `parent_trace_id` 可以追溯父子关系
+
 ---
 
-## 分支-合并设计(explore 工具)
+## 并行探索设计(explore 工具)
 
 ### 场景
 
 ```
-主线 Agent:
+主 Trace (abc123):
   [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] 完善实现
+  [2] 并行探索认证方案 (type=agent_call, mode=explore)
+      → 启动 Sub-Traces: abc123.A, abc123.B
+  [3] 完善实现
+
+Sub-Trace A (abc123.A):
+  [1] JWT 设计
+  [2] JWT 实现
+  → 返回摘要:"JWT 方案实现完成"
+
+Sub-Trace B (abc123.B):
+  [1] Session 设计
+  [2] Session 实现
+  [3] Session 测试
+  → 返回摘要:"Session 方案实现完成"
+
+explore 工具返回:
+  汇总两个分支的结果,主 Agent 继续决策
 ```
 
 **核心原则**:
-- 每个分支是独立的 sub-agent,有自己的 GoalTree 和 Message List
-- 模型在分支内看到的是简单的连续编号 "1", "2", "3"(独立于主线)
-- `explore_start` 和 `explore_merge` 是主线 GoalTree 中的特殊 Goal 类型
-- 分支数据独立存储,不直接嵌入主线 GoalTree
-- explore 工具返回时自动汇总各分支 summary
+- 每个分支是独立的 Trace,有自己的 GoalTree 和 Message List
+- 分支内的 Goal ID 简单编号("1", "2", "3"),独立于主 Trace
+- 主 Trace 的 Goal 通过 `sub_trace_ids` 字段关联分支
+- 分支完全独立存储,可以并行运行
+- explore 工具自动汇总各分支 summary
 
 ### 数据结构
 
-**主线 GoalTree**(不含分支内部 Goals)
+**主 Trace 的 GoalTree**
 
 ```python
-# Goal 类型在前面已定义,这里展示主线 GoalTree 示例
+# Trace: abc123
 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="完善实现", ...),
+    Goal(id="1", type="normal", description="分析问题"),
+    Goal(
+        id="2",
+        type="agent_call",
+        description="并行探索认证方案",
+        agent_call_mode="explore",
+        sub_trace_ids=["abc123.A", "abc123.B"],
+    ),
+    Goal(id="3", type="normal", description="完善实现"),
 ]
 ```
 
-**分支上下文**(独立存储):
+**Sub-Trace A 的 GoalTree**(独立编号):
 
 ```python
-@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
+# Trace: abc123.A
+goals = [
+    Goal(id="1", type="normal", description="JWT 设计"),
+    Goal(id="2", type="normal", description="JWT 实现"),
+]
+```
 
-    # 独立的执行环境
-    goal_tree: GoalTree              # 分支自己的 GoalTree(简单编号 1, 2, 3...)
+**Sub-Trace B 的 GoalTree**(独立编号):
 
-    summary: Optional[str]           # 完成时的总结
-    cumulative_stats: GoalStats      # 累计统计
-    last_message: Optional[Message]  # 最新消息(用于可视化预览)
+```python
+# Trace: abc123.B
+goals = [
+    Goal(id="1", type="normal", description="Session 设计"),
+    Goal(id="2", type="normal", description="Session 实现"),
+    Goal(id="3", type="normal", description="Session 测试"),
+]
 ```
 
 ### 存储结构
 
 ```
-.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           # 事件流
+.trace/
+├── abc123/                # 主 Trace
+│   ├── meta.json
+│   ├── goal.json
+│   └── messages/
+
+├── abc123.A/              # Sub-Trace A
+│   ├── meta.json          # parent_trace_id: "abc123", parent_goal_id: "2"
+│   ├── goal.json          # 独立的 GoalTree
+│   └── messages/
+│
+└── abc123.B/              # Sub-Trace B
+    └── ...
 ```
 
 ### DAG 可视化
 
-**折叠视图**(explore 区域显示为 start → merge):
+**折叠视图**(Sub-Trace 作为单个节点):
 ```
-[1:分析] ──→ [2:explore_start] ──→ [3:explore_merge] ──→ [4:完善]
-
-               (启动2分支)            (汇总评估)
+[1:分析] ──→ [2:并行探索] ──→ [3:完善]
+                 │
+          (启动2个Sub-Trace)
 ```
 
 **展开分支视图**(显示并行路径):
 ```
                   ┌──→ [A:JWT方案] ────┐
-[1:分析] ──→ [2] ─┤                    ├──→ [3:合并] ──→ [4:完善]
+[1:分析] ──→ [2] ─┤                    ├──→ [3:完善]
                   └──→ [B:Session方案] ┘
 ```
 
-**继续展开分支 A 内部**:
+**继续展开分支 A 内部**(加载 Sub-Trace abc123.A 的 GoalTree)
 ```
-                  ┌──→ [A.1:设计] → [A.2:实现] ──┐
-[1:分析] ──→ [2] ─┤                              ├──→ [3:合并] ──→ [4:完善]
-                  └──→ [B:Session方案] ──────────┘
+                  ┌──→ [A.1:JWT设计] → [A.2:JWT实现] ──┐
+[1:分析] ──→ [2] ─┤                                    ├──→ [3:完善]
+                  └──→ [B:Session方案] ────────────────
 ```
 
-注意:`[A.1]`, `[A.2]` 是**前端显示格式**,后端存储的是 `(branch_id="A", goal_id="1")`。
+**注意**:
+- `[A:JWT方案]` 是折叠视图,代表整个 Sub-Trace abc123.A
+- `[A.1]`, `[A.2]` 是展开后显示 Sub-Trace 内部的 Goals
+- 前端显示为 "A.1",但后端查询是 `GET /api/traces/abc123.A/messages?goal_id=1`
 
 ### 前端 API
 
-**REST**:返回主线 GoalTree + 分支元数据(不含分支内部 Goals),按需加载分支详情
+**REST**:返回主 Trace 的 GoalTree + Sub-Trace 列表(元数据)
 
 ```http
-GET /api/traces/{trace_id}
+GET /api/traces/abc123
 ```
 
 响应:
 ```json
 {
+  "trace_id": "abc123",
+  "status": "running",
   "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", ...}
+      {"id": "1", "type": "normal", "description": "分析问题"},
+      {
+        "id": "2",
+        "type": "agent_call",
+        "description": "并行探索认证方案",
+        "agent_call_mode": "explore",
+        "sub_trace_ids": ["abc123.A", "abc123.B"]
+      },
+      {"id": "3", "type": "normal", "description": "完善实现"}
     ]
   },
-  "branches": {
-    "A": {
-      "id": "A",
-      "explore_start_id": "2",
-      "description": "JWT方案",
+  "sub_traces": {
+    "abc123.A": {
+      "trace_id": "abc123.A",
+      "parent_trace_id": "abc123",
+      "parent_goal_id": "2",
+      "agent_type": "explore",
+      "task": "JWT 方案",
       "status": "completed",
-      "summary": "JWT方案实现完成,无状态但token较大",
-      "cumulative_stats": {"message_count": 8, "total_tokens": 4000, ...},
-      "goal_count": 2,
-      "last_message": {"role": "assistant", "content": "JWT实现完成...", ...}
+      "total_messages": 8,
+      "total_tokens": 4000,
+      "total_cost": 0.05
     },
-    "B": {...}
+    "abc123.B": {
+      "trace_id": "abc123.B",
+      "agent_type": "explore",
+      "task": "Session 方案",
+      "status": "completed",
+      ...
+    }
   }
 }
 ```
 
-**按需加载分支详情**:
+**按需加载 Sub-Trace 详情**:
 
 ```http
-GET /api/traces/{trace_id}/branches/{branch_id}
+GET /api/traces/abc123.A
 ```
 
 响应:
 ```json
 {
-  "id": "A",
-  "description": "JWT方案",
+  "trace_id": "abc123.A",
+  "parent_trace_id": "abc123",
+  "parent_goal_id": "2",
+  "agent_type": "explore",
+  "task": "JWT 方案",
   "status": "completed",
-  "summary": "...",
   "goal_tree": {
     "goals": [
-      {"id": "1", "description": "JWT设计", ...},
-      {"id": "2", "description": "JWT实现", ...}
+      {"id": "1", "description": "JWT 设计", ...},
+      {"id": "2", "description": "JWT 实现", ...}
     ]
   },
-  "cumulative_stats": {...}
+  "total_tokens": 4000,
+  "total_cost": 0.05
 }
 ```
 
@@ -786,62 +1063,62 @@ GET /api/traces/{trace_id}/branches/{branch_id}
 
 | 事件 | 触发时机 | 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 |
+| `sub_trace_started` | Sub-Trace 开始 | trace_id, parent_trace_id, parent_goal_id, agent_type |
+| `goal_added` | 任何 Trace 新增 Goal | trace_id, goal |
+| `message_added` | 任何 Trace 新 Message | trace_id, message, affected_goals |
+| `sub_trace_completed` | Sub-Trace 完成 | trace_id, summary, stats |
+| `trace_completed` | 主 Trace 完成 | trace_id, stats |
 
-### explore 工具流程
+### explore 工具执行流程
 
-```python
-@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))],
+```python
+async def explore_tool(branches: List[str]) -> str:
+    current_trace_id = "abc123"
+    current_goal_id = "2"
+
+    # 1. 创建 agent_call Goal
+    goal = Goal(
+        id="2",
+        type="agent_call",
+        description=f"并行探索 {len(branches)} 个方案",
+        agent_call_mode="explore",
+        sub_trace_ids=[],
     )
 
-    # 2. 为每个方向创建 sub-agent
+    # 2. 创建多个 Sub-Traces
+    sub_traces = []
     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,
+        suffix = chr(ord('A') + i)
+        sub_trace = Trace.create(
+            trace_id=f"{current_trace_id}.{suffix}",
+            parent_trace_id=current_trace_id,
+            parent_goal_id=current_goal_id,
+            agent_type="explore",
+            task=desc,
+            context={"allowed_tools": ["read", "grep", "glob"]},
         )
-        spawn_sub_agent(branch_id)
-
-    # 3. 等待所有分支完成
-    results = await gather_branch_results()
+        sub_traces.append(sub_trace)
+        goal.sub_trace_ids.append(sub_trace.trace_id)
 
-    # 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,
+    # 3. 并行执行
+    results = await asyncio.gather(
+        *[run_agent(st) for st in sub_traces]
     )
 
-    # 5. 返回汇总给主线 Agent
-    return merge_summary
+    # 4. 汇总返回
+    return format_results(results)
 ```
 
-**汇总结果示例**(作为 explore 工具的返回值)
+**汇总结果示例**:
 ```markdown
 ## 探索结果
 
-### 分支 A: JWT 方案
+### 方案 A (abc123.A): JWT 方案
 实现完成。优点:无状态,易扩展。缺点:token 较大,无法主动失效。
 
-### 分支 B: Session 方案
+### 方案 B (abc123.B): Session 方案
 实现完成。优点:token 小,可主动失效。缺点:需要 Redis 存储。
 
 ---
@@ -850,11 +1127,11 @@ def explore(branches: List[str]) -> str:
 
 ### Context 压缩
 
-分支完成后的压缩策略:
+Sub-Trace 完成后的压缩策略:
 
-1. **分支完成时**:分支的详细 context 压缩为 summary,存储在 BranchContext.summary
-2. **explore 完成后**:所有分支的 summary 汇总为 merge_summary
-3. **主线 context**:explore 工具调用被压缩为一条包含 merge_summary 的消息
+1. **Sub-Trace 完成时**:Sub-Trace 的详细 context 压缩为 summary(存储在 Trace.summary)
+2. **explore 完成后**:所有 Sub-Traces 的 summary 汇总到主 Trace 的 tool result
+3. **主 Trace context**:explore 工具调用被压缩为一条包含汇总结果的 tool message
 
 ---
 
@@ -862,14 +1139,17 @@ def explore(branches: List[str]) -> str:
 
 | 方面 | OpenCode | 我们的方案 |
 |------|----------|-----------|
-| Plan 格式 | 纯文本 (plan.md) | 结构化 (GoalTree 嵌套 JSON) |
+| Plan 格式 | 纯文本 (plan.md) | 结构化 (GoalTree JSON) |
 | Plan 与执行关联 | 无 | 通过 goal_id 关联 |
 | 执行记录 | Message List | Message List(加 goal_id 元数据) |
+| Sub-Agent 模型 | SubagentPart(嵌入式) | 独立 Trace(统一模型) |
+| Sub-Agent ID | 复杂(需要前缀) | 简单(每个 Trace 独立编号) |
 | 压缩时机 | 事后(context 满时) | 增量(goal 完成/放弃时) |
 | 并行探索 | Sub-agent(手动管理) | explore 工具(自动汇总) |
+| 权限控制 | 在 Sub-Agent 类型上 | 在 Trace.context 上(更灵活) |
 | 回溯能力 | 有限 | 精确(基于 goal 压缩 + 废弃分支 summary) |
 | 可视化 | 无 | DAG(边可展开/折叠) |
-| 工具复杂度 | todoread/todowrite | goal/explore |
+| 分布式支持 | 困难 | 自然支持(每个 Trace 独立) |
 
 ---
 
@@ -877,14 +1157,16 @@ def explore(branches: List[str]) -> str:
 
 | 功能 | 文件路径 | 状态 |
 |------|---------|------|
-| Goal 数据模型 | `agent/goal/models.py` | 待调整(ID 映射) |
+| Trace 数据模型 | `agent/execution/models.py` | 待调整(增加父子关系、context) |
+| Goal 数据模型 | `agent/goal/models.py` | 待调整(简化,移除 branch_id) |
 | goal 工具 | `agent/goal/tool.py` | 待调整 |
 | explore 工具 | `agent/goal/explore.py` | 待实现 |
+| delegate 工具 | `agent/goal/delegate.py` | 待实现 |
+| Trace ID 生成 | `agent/execution/trace_id.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 事件) |
+| TraceStore 协议 | `agent/execution/protocols.py` | 待调整(支持嵌套 Trace) |
+| DAG 可视化 API | `agent/execution/api.py` | 待调整(支持 Sub-Trace) |
+| WebSocket 推送 | `agent/execution/websocket.py` | 待调整(统一事件格式) |
 | Plan 注入 | `agent/core/runner.py` | 待调整 |
 
 ---

+ 236 - 351
frontend/API.md

@@ -1,6 +1,6 @@
 # Agent Execution API - 前端对接文档
 
-> 版本:v3.0
+> 版本:v4.0
 > 更新日期:2026-02-04
 
 ---
@@ -12,26 +12,40 @@
 - **WebSocket** - 实时推送 Goal/Message 更新(支持断线续传)
 
 **核心概念**:
-- **Trace** - 一次完整的任务执行
-- **GoalTree** - 嵌套的目标树
+- **Trace** - 一次完整的 Agent 执行(主 Agent 和 Sub-Agent 都是 Trace)
+- **GoalTree** - 每个 Trace 的目标树
 - **Goal** - 一个目标节点,包含 self_stats(自身统计)和 cumulative_stats(含后代统计)
-- **Message** - 执行消息,对应 LLM API 的 assistant/tool 消息,是详细执行数据
+- **Message** - 执行消息,对应 LLM API 的 assistant/tool 消息
+- **Sub-Trace** - 子 Agent(通过 explore/delegate 工具启动)
 
-**数据结构**:
+**统一的 Trace 模型**:
+```
+主 Trace 和 Sub-Trace 使用相同的数据结构:
+- 每个 Trace 有独立的 GoalTree
+- 每个 Trace 有独立的 Message List
+- Sub-Trace 通过 parent_trace_id 和 parent_goal_id 关联父 Trace
+- Trace ID 采用层级命名(abc123, abc123.A, abc123.A.1)
 ```
-后端存储两类数据:
-1. GoalTree(嵌套 JSON)- 目标树结构 + 聚合统计(stats)
-2. Messages(扁平列表)- 详细执行记录,通过 goal_id 关联 Goal
 
-关系:Goal.stats 是从关联的 Messages 聚合计算出来的
+**数据结构**:
+```
+后端存储:
+1. 每个 Trace 独立存储(主 Trace 和 Sub-Traces)
+2. GoalTree - 目标树结构 + 聚合统计
+3. Messages - 执行记录,通过 goal_id 关联 Goal
+
+关系:
+- Goal.stats 从关联的 Messages 聚合计算
+- Sub-Trace 通过 parent_trace_id 关联
+- Goal 通过 sub_trace_ids 关联启动的 Sub-Traces
 ```
 
 **DAG 可视化**(前端负责):
 - 从 GoalTree 生成 DAG 视图
 - 节点 = Goal 完成后的里程碑
 - 边 = 相邻节点之间的执行过程
-- 边的统计数据从 target Goal 的 stats 获取(折叠用 `cumulative_stats`,展开用 `self_stats`)
-- 边的详细内容需要查询该 Goal 关联的 Messages
+- Sub-Trace 可以折叠(显示为单个节点)或展开(显示内部 Goals
+- 边的统计数据从 target Goal 的 stats 获取
 
 ---
 
@@ -85,13 +99,16 @@ GET /api/traces?status=running&limit=20
 GET /api/traces/{trace_id}
 ```
 
-**响应示例**:
+**响应示例**(主 Trace)
 ```json
 {
   "trace_id": "abc123",
   "mode": "agent",
   "task": "实现用户认证功能",
   "status": "running",
+  "parent_trace_id": null,
+  "parent_goal_id": null,
+  "agent_type": "main",
   "total_messages": 15,
   "total_tokens": 5000,
   "total_cost": 0.05,
@@ -99,12 +116,11 @@ GET /api/traces/{trace_id}
   "completed_at": null,
   "goal_tree": {
     "mission": "实现用户认证功能",
-    "current_id": "4",
+    "current_id": "2",
     "goals": [
       {
         "id": "1",
         "parent_id": null,
-        "branch_id": null,
         "type": "normal",
         "description": "分析代码",
         "reason": "了解现有结构",
@@ -116,84 +132,133 @@ GET /api/traces/{trace_id}
       {
         "id": "2",
         "parent_id": null,
-        "branch_id": null,
-        "type": "normal",
-        "description": "实现功能",
-        "reason": "核心任务",
+        "type": "agent_call",
+        "description": "并行探索认证方案",
+        "reason": "评估不同技术选型",
         "status": "in_progress",
-        "summary": null,
+        "agent_call_mode": "explore",
+        "sub_trace_ids": ["abc123.A", "abc123.B"],
         "self_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
-        "cumulative_stats": { "message_count": 8, "total_tokens": 4200, "total_cost": 0.05, "preview": "read → edit × 3 → bash" }
+        "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null }
       },
       {
         "id": "3",
-        "parent_id": "2",
-        "branch_id": null,
+        "parent_id": null,
+        "type": "normal",
+        "description": "完善实现",
+        "reason": "基于选定方案完成实现",
+        "status": "pending"
+      }
+    ]
+  },
+  "sub_traces": {
+    "abc123.A": {
+      "trace_id": "abc123.A",
+      "parent_trace_id": "abc123",
+      "parent_goal_id": "2",
+      "agent_type": "explore",
+      "task": "JWT 方案",
+      "status": "completed",
+      "total_messages": 8,
+      "total_tokens": 4000,
+      "total_cost": 0.05,
+      "created_at": "2026-02-04T15:31:00",
+      "completed_at": "2026-02-04T15:35:00"
+    },
+    "abc123.B": {
+      "trace_id": "abc123.B",
+      "parent_trace_id": "abc123",
+      "parent_goal_id": "2",
+      "agent_type": "explore",
+      "task": "Session 方案",
+      "status": "completed",
+      "total_messages": 10,
+      "total_tokens": 5000,
+      "total_cost": 0.06
+    }
+  }
+}
+```
+
+---
+
+### 3. 获取 Sub-Trace 详情
+
+获取 Sub-Trace 的完整信息(包括 GoalTree)。
+
+```http
+GET /api/traces/abc123.A
+```
+
+**响应示例**:
+```json
+{
+  "trace_id": "abc123.A",
+  "parent_trace_id": "abc123",
+  "parent_goal_id": "2",
+  "agent_type": "explore",
+  "task": "JWT 方案",
+  "status": "completed",
+  "total_messages": 8,
+  "total_tokens": 4000,
+  "total_cost": 0.05,
+  "created_at": "2026-02-04T15:31:00",
+  "completed_at": "2026-02-04T15:35:00",
+  "goal_tree": {
+    "mission": "JWT 方案",
+    "current_id": null,
+    "goals": [
+      {
+        "id": "1",
+        "parent_id": null,
         "type": "normal",
-        "description": "设计接口",
-        "reason": "先定义 API 契约",
+        "description": "JWT 设计",
         "status": "completed",
-        "summary": "RESTful API 设计完成",
+        "summary": "设计完成",
         "self_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" },
         "cumulative_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" }
       },
       {
-        "id": "4",
-        "parent_id": "2",
-        "branch_id": null,
-        "type": "normal",
-        "description": "实现代码",
-        "reason": "按设计实现",
-        "status": "in_progress",
-        "summary": null,
-        "self_stats": { "message_count": 5, "total_tokens": 2700, "total_cost": 0.03, "preview": "edit × 3 → bash" },
-        "cumulative_stats": { "message_count": 5, "total_tokens": 2700, "total_cost": 0.03, "preview": "edit × 3 → bash" }
-      },
-      {
-        "id": "5",
+        "id": "2",
         "parent_id": null,
-        "branch_id": null,
         "type": "normal",
-        "description": "测试",
-        "reason": "验证功能正确性",
-        "status": "pending",
-        "summary": null,
-        "self_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
-        "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null }
+        "description": "JWT 实现",
+        "status": "completed",
+        "summary": "实现完成",
+        "self_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" },
+        "cumulative_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" }
       }
     ]
   },
-  "branches": {}
+  "sub_traces": {}
 }
 ```
 
----
-
-### 3. 获取 Messages(边详情)
+### 4. 获取 Messages(边详情)
 
-查询 Goal 关联的所有 Messages(用于查看边的详细执行内容)。
+查询指定 Trace 的 Messages(用于查看边的详细执行内容)。
 
 ```http
-GET /api/traces/{trace_id}/messages?goal_id=3
-GET /api/traces/{trace_id}/messages?branch_id=A
+GET /api/traces/abc123/messages?goal_id=1
+GET /api/traces/abc123.A/messages?goal_id=2
 ```
 
 **查询参数**:
 | 参数 | 类型 | 必填 | 说明 |
 |------|------|------|------|
-| `goal_id` | string | 否 | 过滤指定 Goal 的 Messages(内部 ID)|
-| `branch_id` | string | 否 | 过滤指定分支的所有 Messages |
+| `goal_id` | string | 否 | 过滤指定 Goal 的 Messages(内部 ID,如 "1", "2")|
 
 **响应示例**:
 ```json
 {
+  "trace_id": "abc123",
   "messages": [
     {
       "message_id": "msg-001",
+      "trace_id": "abc123",
       "role": "assistant",
-      "sequence": 6,
-      "goal_id": "3",
-      "branch_id": null,
+      "sequence": 1,
+      "goal_id": "1",
       "content": {
         "text": "让我先读取现有的 API 设计...",
         "tool_calls": [
@@ -204,92 +269,26 @@ GET /api/traces/{trace_id}/messages?branch_id=A
           }
         ]
       },
+      "description": "让我先读取现有的 API 设计...",
       "tokens": 150,
       "cost": 0.002,
       "created_at": "2026-02-04T15:31:00"
     },
     {
       "message_id": "msg-002",
+      "trace_id": "abc123",
       "role": "tool",
-      "sequence": 7,
-      "goal_id": "3",
-      "branch_id": null,
+      "sequence": 2,
+      "goal_id": "1",
       "tool_call_id": "call_abc",
       "content": "# API Routes\n...",
+      "description": "read_file",
       "tokens": null,
       "cost": null,
       "created_at": "2026-02-04T15:31:01"
-    },
-    {
-      "message_id": "msg-003",
-      "role": "assistant",
-      "sequence": 8,
-      "goal_id": "3",
-      "branch_id": null,
-      "content": {
-        "text": "现有 API 使用 REST 风格,我来设计新的认证接口...",
-        "tool_calls": [
-          {
-            "id": "call_def",
-            "name": "edit_file",
-            "arguments": { "path": "api/auth.py", "content": "..." }
-          }
-        ]
-      },
-      "tokens": 300,
-      "cost": 0.004,
-      "created_at": "2026-02-04T15:31:30"
     }
   ],
-  "total": 3
-}
-```
-
----
-
-### 4. 获取分支详情(按需加载)
-
-```http
-GET /api/traces/{trace_id}/branches/{branch_id}
-```
-
-**响应示例**:
-```json
-{
-  "id": "A",
-  "explore_start_id": "6",
-  "description": "JWT 方案",
-  "status": "completed",
-  "summary": "JWT 方案实现完成,无状态但 token 较大",
-  "goal_tree": {
-    "mission": "探索 JWT 方案",
-    "current_id": null,
-    "goals": [
-      {
-        "id": "1",
-        "parent_id": null,
-        "branch_id": "A",
-        "type": "normal",
-        "description": "JWT 设计",
-        "status": "completed",
-        "summary": "设计完成",
-        "self_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" },
-        "cumulative_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" }
-      },
-      {
-        "id": "2",
-        "parent_id": null,
-        "branch_id": "A",
-        "type": "normal",
-        "description": "JWT 实现",
-        "status": "completed",
-        "summary": "实现完成",
-        "self_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" },
-        "cumulative_stats": { "message_count": 5, "total_tokens": 2500, "total_cost": 0.03, "preview": "edit × 3 → bash" }
-      }
-    ]
-  },
-  "cumulative_stats": { "message_count": 8, "total_tokens": 4000, "total_cost": 0.05, "preview": "read → edit × 4 → bash" }
+  "total": 2
 }
 ```
 
@@ -316,17 +315,22 @@ const ws = new WebSocket(
 
 #### 1. connected(连接成功)
 
-连接后推送完整 GoalTree,前端据此初始化 DAG。
+连接后推送完整 Trace 信息,前端据此初始化 DAG。
 
 ```json
 {
   "event": "connected",
   "trace_id": "abc123",
   "current_event_id": 15,
-  "goal_tree": {
-    "mission": "实现用户认证功能",
-    "current_id": "2.1",
-    "goals": [...]
+  "trace": {
+    "trace_id": "abc123",
+    "status": "running",
+    "goal_tree": {
+      "mission": "实现用户认证功能",
+      "current_id": "2",
+      "goals": [...]
+    },
+    "sub_traces": {}
   }
 }
 ```
@@ -485,62 +489,67 @@ if (data.event === 'trace_completed') {
 
 ---
 
-#### 6. branch_started(分支开始探索
+#### 6. sub_trace_started(Sub-Trace 开始
 
-explore 工具触发并行探索时,为每个分支发送此事件
+explore 或 delegate 工具启动 Sub-Trace 时触发
 
 ```json
 {
-  "event": "branch_started",
+  "event": "sub_trace_started",
   "event_id": 20,
-  "explore_start_id": "6",
-  "branch": {
-    "id": "A",
-    "explore_start_id": "6",
-    "description": "JWT 方案",
-    "status": "exploring",
-    "summary": null,
-    "cumulative_stats": { "message_count": 0, "total_tokens": 0, "total_cost": 0.0, "preview": null },
-    "goal_count": 0,
-    "last_message": null
+  "parent_trace_id": "abc123",
+  "parent_goal_id": "2",
+  "sub_trace": {
+    "trace_id": "abc123.A",
+    "parent_trace_id": "abc123",
+    "parent_goal_id": "2",
+    "agent_type": "explore",
+    "task": "JWT 方案",
+    "status": "running",
+    "total_messages": 0,
+    "total_tokens": 0,
+    "total_cost": 0.0
   }
 }
 ```
 
 **前端处理**:
 ```javascript
-if (data.event === 'branch_started') {
-  insertBranch(data.explore_start_id, data.branch)
+if (data.event === 'sub_trace_started') {
+  insertSubTrace(data.parent_trace_id, data.sub_trace)
   regenerateDAG()
 }
 ```
 
 ---
 
-#### 7. branch_completed(分支完成)
+#### 7. sub_trace_completed(Sub-Trace 完成)
 
-分支内所有 Goals 完成后触发。
+Sub-Trace 执行完成后触发。
 
 ```json
 {
-  "event": "branch_completed",
+  "event": "sub_trace_completed",
   "event_id": 35,
-  "explore_start_id": "6",
-  "branch_id": "A",
+  "trace_id": "abc123.A",
+  "parent_trace_id": "abc123",
+  "parent_goal_id": "2",
   "summary": "JWT 方案实现完成,无状态但 token 较大",
-  "cumulative_stats": { "message_count": 8, "total_tokens": 4000, "total_cost": 0.05, "preview": "read → edit × 3" },
-  "last_message": { "role": "assistant", "content": "JWT 实现完成...", "created_at": "..." }
+  "total_messages": 8,
+  "total_tokens": 4000,
+  "total_cost": 0.05
 }
 ```
 
 **前端处理**:
 ```javascript
-if (data.event === 'branch_completed') {
-  updateBranch(data.explore_start_id, data.branch_id, {
+if (data.event === 'sub_trace_completed') {
+  updateSubTrace(data.trace_id, {
     status: 'completed',
     summary: data.summary,
-    cumulative_stats: data.cumulative_stats,
-    last_message: data.last_message
+    total_messages: data.total_messages,
+    total_tokens: data.total_tokens,
+    total_cost: data.total_cost
   })
   regenerateDAG()
 }
@@ -548,67 +557,30 @@ if (data.event === 'branch_completed') {
 
 ---
 
-#### 8. message_added 扩展(分支内消息)
-
-分支内的 Message 会包含 `branch_id` 和 `affected_branches` 字段:
-
-```json
-{
-  "event": "message_added",
-  "event_id": 25,
-  "message": {
-    "message_id": "msg-025",
-    "role": "assistant",
-    "goal_id": "1",
-    "branch_id": "A",
-    "content": { "text": "...", "tool_calls": [...] },
-    "tokens": 300,
-    "cost": 0.004
-  },
-  "affected_goals": [
-    { "goal_id": "1", "self_stats": {...}, "cumulative_stats": {...} }
-  ],
-  "affected_branches": [
-    { "branch_id": "A", "explore_start_id": "6", "cumulative_stats": {...} }
-  ]
-}
-```
-
-**前端处理**:
-```javascript
-if (data.event === 'message_added') {
-  for (const g of data.affected_goals) {
-    updateGoalStats(g.goal_id, g)
-  }
-  // 分支内消息还需更新分支统计
-  if (data.affected_branches) {
-    for (const b of data.affected_branches) {
-      updateBranchStats(b.explore_start_id, b.branch_id, b.cumulative_stats)
-    }
-  }
-  rerenderEdge(data.message.goal_id, data.message.branch_id)
-}
-```
-
----
-
 ## 数据模型
 
 ### Trace
 
 | 字段 | 类型 | 说明 |
 |------|------|------|
-| `trace_id` | string | 唯一 ID |
+| `trace_id` | string | 层级化 ID(如 "abc123", "abc123.A", "abc123.A.1")|
 | `mode` | string | `call` - 单次调用 / `agent` - Agent 模式 |
 | `task` | string | 任务描述 |
+| `parent_trace_id` | string \| null | 父 Trace ID(Sub-Trace 才有)|
+| `parent_goal_id` | string \| null | 哪个 Goal 启动的(Sub-Trace 才有)|
+| `agent_type` | string \| null | "main" / "explore" / "delegate" / "compaction" |
 | `status` | string | `running` / `completed` / `failed` |
 | `total_messages` | int | Message 总数 |
 | `total_tokens` | int | Token 总消耗 |
 | `total_cost` | float | 成本总和 |
-| `current_goal_id` | string | 当前焦点 Goal 的内部 ID |
 | `created_at` | string | 创建时间(ISO 8601)|
 | `completed_at` | string \| null | 完成时间 |
 
+**Trace ID 规则**:
+- 主 Trace:短随机 ID(如 "abc123")
+- Sub-Trace:父 ID + 后缀(如 "abc123.A", "abc123.task1")
+- 嵌套:继续追加(如 "abc123.A.1")
+
 ---
 
 ### GoalTree
@@ -625,61 +597,25 @@ if (data.event === 'message_added') {
 
 | 字段 | 类型 | 说明 |
 |------|------|------|
-| `id` | string | 内部唯一 ID,纯自增("1", "2", "3"...)|
+| `id` | string | 内部 ID,每个 Trace 独立编号("1", "2", "3"...)|
 | `parent_id` | string \| null | 父 Goal ID(层级关系)|
-| `branch_id` | string \| null | 所属分支 ID(null=主线)|
-| `type` | string | `normal` / `explore_start` / `explore_merge` |
+| `type` | string | `normal` / `agent_call` |
 | `description` | string | 目标描述(做什么)|
 | `reason` | string | 创建理由(为什么做)|
 | `status` | string | `pending` / `in_progress` / `completed` / `abandoned` |
 | `summary` | string \| null | 完成/放弃时的总结 |
-| `branch_ids` | string[] \| null | 关联的分支 ID(仅 explore_start)|
-| `explore_start_id` | string \| null | 关联的 explore_start Goal(仅 explore_merge)|
-| `merge_summary` | string \| null | 各分支汇总(仅 explore_merge)|
-| `selected_branch` | string \| null | 选中的分支(仅 explore_merge)|
+| `sub_trace_ids` | string[] \| null | 启动的 Sub-Trace IDs(仅 agent_call)|
+| `agent_call_mode` | string \| null | "explore" / "delegate" / "sequential"(仅 agent_call)|
 | `self_stats` | GoalStats | 自身统计 |
 | `cumulative_stats` | GoalStats | 累计统计 |
 
 **ID 设计**:
-- 内部 ID 纯自增,不管层级、分支、废弃
+- 每个 Trace 内部独立编号("1", "2", "3")
 - 层级关系通过 `parent_id` 维护
-- 分支关系通过 `branch_id` 维护
 - 显示序号由前端/后端动态生成
 
 ---
 
-### BranchContext
-
-分支元数据(主请求返回,不含内部 GoalTree)
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `id` | string | 分支 ID(如 `"A"`, `"B"`)|
-| `explore_start_id` | string | 关联的 explore_start Goal ID |
-| `description` | string | 探索方向描述 |
-| `status` | string | `exploring` / `completed` / `abandoned` |
-| `summary` | string \| null | 完成时的总结 |
-| `cumulative_stats` | GoalStats | 累计统计 |
-| `goal_count` | int | 分支内 Goal 数量 |
-| `last_message` | Message \| null | 最新消息(用于预览)|
-
----
-
-### BranchDetail
-
-分支详情(按需加载)
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| `id` | string | 分支 ID |
-| `description` | string | 探索方向描述 |
-| `status` | string | 状态 |
-| `summary` | string \| null | 总结 |
-| `goal_tree` | GoalTree | 分支内的 GoalTree |
-| `cumulative_stats` | GoalStats | 累计统计 |
-
----
-
 ### GoalStats
 
 | 字段 | 类型 | 说明 |
@@ -696,13 +632,13 @@ if (data.event === 'message_added') {
 | 字段 | 类型 | 说明 |
 |------|------|------|
 | `message_id` | string | 唯一 ID |
-| `trace_id` | string | 所属 Trace ID |
-| `branch_id` | string \| null | 所属分支(null=主线)|
+| `trace_id` | string | 所属 Trace ID(可能是主 Trace 或 Sub-Trace)|
 | `role` | string | `assistant` / `tool` |
-| `sequence` | int | 全局顺序 |
-| `goal_id` | string | 关联的 Goal 内部 ID |
+| `sequence` | int | 当前 Trace 内的顺序 |
+| `goal_id` | string | 关联的 Goal 内部 ID(如 "1", "2")|
 | `tool_call_id` | string \| null | tool 消息关联的 tool_call ID |
 | `content` | any | 消息内容(和 LLM API 格式一致)|
+| `description` | string | 消息描述(系统自动生成)|
 | `tokens` | int \| null | Token 消耗 |
 | `cost` | float \| null | 成本 |
 | `created_at` | string | 创建时间 |
@@ -711,7 +647,7 @@ if (data.event === 'message_added') {
 
 ## DAG 可视化(前端实现)
 
-后端只提供 GoalTree(扁平列表 + parent_id),前端负责生成 DAG 视图。
+后端提供 Trace(包含 GoalTree 和 Sub-Traces),前端负责生成 DAG 视图。
 
 ### 核心逻辑
 
@@ -719,16 +655,17 @@ if (data.event === 'message_added') {
 1. 未展开:显示父 Goal
 2. 已展开:父 Goal 被子 Goal **替代**(顺序)
 
-**explore Goal(分支)**:
-1. 未展开:显示单个 explore_start → explore_merge 节点
-2. 已展开:显示**并行分支路径**(分叉-汇合)
+**agent_call Goal(Sub-Trace)**:
+1. 未展开:显示为单个节点(代表整个 Sub-Trace)
+2. 已展开:加载 Sub-Trace 的 GoalTree,显示内部 Goals
 
 ### 边数据
 
 从 target 节点的 stats 获取:
-- 折叠边 → `target.cumulative_stats`
-- 展开边 → `target.self_stats`
-- 分支边 → `branch.cumulative_stats`
+- 折叠边(父 Goal)→ `target.cumulative_stats`
+- 展开边(子 Goal)→ `target.self_stats`
+- Sub-Trace 折叠边 → Sub-Trace 的 total_tokens/total_cost
+- Sub-Trace 展开边 → Sub-Trace 内部 Goal 的 stats
 
 ### 示例:普通展开(子目标)
 
@@ -801,107 +738,55 @@ function generateEdges(visibleGoals) {
 }
 ```
 
-### 示例:分支展开(explore)
+### 示例:Sub-Trace 展开
 
 ```javascript
-// GoalTree 含 explore Goals + 分支元数据
-const goalTree = {
-  goals: [
-    { id: "1", parent_id: null, type: "normal", description: "分析问题" },
-    { id: "2", parent_id: null, type: "explore_start", branch_ids: ["A", "B"] },
-    { id: "3", parent_id: null, type: "explore_merge", explore_start_id: "2" },
-    { id: "4", parent_id: null, type: "normal", description: "完善实现" }
-  ]
-}
-
-const branches = {
-  "A": { id: "A", description: "JWT 方案", cumulative_stats: {...}, goal_count: 2 },
-  "B": { id: "B", description: "Session 方案", cumulative_stats: {...}, goal_count: 3 }
+// 主 Trace 的 GoalTree
+const mainTrace = {
+  trace_id: "abc123",
+  goal_tree: {
+    goals: [
+      { id: "1", type: "normal", description: "分析问题" },
+      { id: "2", type: "agent_call", agent_call_mode: "explore", sub_trace_ids: ["abc123.A", "abc123.B"] },
+      { id: "3", type: "normal", description: "完善实现" }
+    ]
+  },
+  sub_traces: {
+    "abc123.A": { trace_id: "abc123.A", task: "JWT 方案", status: "completed", total_tokens: 4000 },
+    "abc123.B": { trace_id: "abc123.B", task: "Session 方案", status: "completed", total_tokens: 5000 }
+  }
 }
 
-// 折叠视图: [1] → [2:explore_start] → [3:explore_merge] → [4]
+// 折叠视图:[1] → [2:并行探索] → [3]
 
-// 展开分支后的视图:
-//           ┌→ [A:JWT] ────┐
-// [1] ──────┼              ├──→ [3] ──→ [4]
-//           └→ [B:Session] ┘
+// 展开 Sub-Traces 后的视图:
+//           ┌→ [abc123.A] ────┐
+// [1] ──────┼                 ├──→ [3]
+//           └→ [abc123.B] ────
 
-// 分支展开时,需要按需加载分支详情
-async function loadBranchDetail(traceId, branchId) {
-  const resp = await fetch(`/api/traces/${traceId}/branches/${branchId}`)
-  return await resp.json()  // 返回 BranchDetail,含 goal_tree
+// 继续展开 Sub-Trace abc123.A 内部
+async function loadSubTrace(traceId) {
+  const resp = await fetch(`/api/traces/${traceId}`)
+  return await resp.json()  // 返回完整 Trace,含 goal_tree
 }
 
-// 分支展开时,生成分叉结构
-function generateBranchDAG(prevGoal, exploreStartGoal, exploreEndGoal, nextGoal, branches, expandedBranches) {
-  const nodes = []
-  const edges = []
-
-  for (const branchId of exploreStartGoal.branch_ids) {
-    const branch = branches[branchId]
-
-    if (expandedBranches.has(branchId) && branch.goalTree) {
-      // 分支已加载且展开:显示分支内的 Goals
-      const branchNodes = getVisibleGoals(buildHierarchy(branch.goalTree.goals), expandedBranches)
-      nodes.push(...branchNodes)
-
-      // 分叉边:前一个节点 → 分支第一个节点
-      if (branchNodes.length > 0) {
-        edges.push({
-          source: prevGoal?.id ?? null,
-          target: branchNodes[0].id,
-          stats: branchNodes[0].self_stats,
-          isBranchEdge: true,
-          branchId: branchId
-        })
-
-        // 分支内部边
-        for (let i = 1; i < branchNodes.length; i++) {
-          edges.push({
-            source: branchNodes[i - 1].id,
-            target: branchNodes[i].id,
-            stats: branchNodes[i].self_stats,
-            branchId: branchId
-          })
-        }
-
-        // 汇合边:分支最后一个节点 → explore_merge
-        edges.push({
-          source: branchNodes[branchNodes.length - 1].id,
-          target: exploreEndGoal.id,
-          stats: null,
-          isMergeEdge: true,
-          branchId: branchId
-        })
-      }
-    } else {
-      // 分支折叠:显示为单个分支节点
-      const branchNode = {
-        id: `branch_${branchId}`,
-        description: branch.description,
-        isBranchNode: true,
-        branchId: branchId
-      }
-      nodes.push(branchNode)
-
-      edges.push({
-        source: prevGoal?.id ?? null,
-        target: branchNode.id,
-        stats: branch.cumulative_stats,
-        isBranchEdge: true
-      })
-
-      edges.push({
-        source: branchNode.id,
-        target: exploreEndGoal.id,
-        stats: null,
-        isMergeEdge: true
-      })
-    }
-  }
-
-  return { nodes, edges }
-}
+const subTraceA = await loadSubTrace("abc123.A")
+// {
+//   trace_id: "abc123.A",
+//   goal_tree: {
+//     goals: [
+//       { id: "1", description: "JWT 设计" },
+//       { id: "2", description: "JWT 实现" }
+//     ]
+//   }
+// }
+
+// 展开后显示 Sub-Trace 内部 Goals:
+//           ┌→ [A.1:JWT设计] → [A.2:JWT实现] ──┐
+// [1] ──────┼                                  ├──→ [3]
+//           └→ [abc123.B] ─────────────────────┘
+
+// 注意:前端显示为 "A.1",实际查询是 GET /api/traces/abc123.A/messages?goal_id=1
 ```
 
 ### 视觉区分

+ 162 - 0
test_integration.py

@@ -0,0 +1,162 @@
+"""
+测试新的 Goal + Message 系统(不需要 LLM)
+
+验证 GoalTree 和工具注册是否正常工作
+"""
+
+import asyncio
+from agent.goal.models import GoalTree
+from agent.execution.models import Trace, Message
+from agent.execution.fs_store import FileSystemTraceStore
+from agent.tools import get_tool_registry
+
+
+async def test_goal_tree_and_messages():
+    """测试 GoalTree 和 Message 集成"""
+    print("=" * 60)
+    print("测试 Goal + Message 系统集成")
+    print("=" * 60)
+    print()
+
+    # 1. 验证 goal 工具已注册
+    print("1. 检查工具注册")
+    registry = get_tool_registry()
+    tools = registry.get_tool_names()
+    print(f"   已注册 {len(tools)} 个工具")
+
+    if "goal" in tools:
+        print("   ✓ goal 工具已注册")
+    else:
+        print("   ✗ goal 工具未注册")
+        return
+
+    # 获取 goal 工具的 schema
+    goal_schema = registry.get_schemas(["goal"])[0]
+    print(f"   - 工具名称: {goal_schema.get('name')}")
+    print(f"   - 描述: {goal_schema.get('description', '')[:60]}...")
+    print()
+
+    # 2. 创建 Trace 和 GoalTree
+    print("2. 创建 Trace 和 GoalTree")
+    store = FileSystemTraceStore(base_path=".trace_test")
+
+    trace = Trace.create(mode="agent", task="测试任务:实现用户认证")
+    await store.create_trace(trace)
+    print(f"   Trace ID: {trace.trace_id[:8]}...")
+
+    tree = GoalTree(mission="测试任务:实现用户认证")
+    await store.update_goal_tree(trace.trace_id, tree)
+    print(f"   GoalTree 创建成功")
+    print()
+
+    # 3. 测试 goal 工具调用(模拟 LLM 调用)
+    print("3. 测试 goal 工具调用")
+
+    # 设置 goal_tree
+    from agent.tools.builtin.goal import set_goal_tree
+    set_goal_tree(tree)
+
+    # 模拟调用 goal 工具添加目标
+    result1 = await registry.execute("goal", {"add": "分析代码, 设计方案, 实现功能"}, uid="test")
+    print("   调用: goal(add='分析代码, 设计方案, 实现功能')")
+    print(f"   结果: {result1[:200]}...")
+    print()
+
+    # 保存更新后的 tree
+    await store.update_goal_tree(trace.trace_id, tree)
+
+    # 模拟调用 goal 工具 focus
+    result2 = await registry.execute("goal", {"focus": "1"}, uid="test")
+    print("   调用: goal(focus='1')")
+    print(f"   结果: {result2[:150]}...")
+    print()
+
+    # 4. 添加 Messages
+    print("4. 添加 Messages 并自动更新 stats")
+
+    # 添加 assistant message(与 goal 1 关联)
+    msg1 = Message.create(
+        trace_id=trace.trace_id,
+        role="assistant",
+        sequence=1,
+        goal_id="1",
+        content={"text": "开始分析代码结构", "tool_calls": []},
+        tokens=50,
+        cost=0.001
+    )
+    await store.add_message(msg1)
+    print(f"   Message 1: {msg1.description}")
+
+    # 添加更多 messages
+    msg2 = Message.create(
+        trace_id=trace.trace_id,
+        role="assistant",
+        sequence=2,
+        goal_id="1",
+        content={"text": "", "tool_calls": [
+            {"id": "call1", "function": {"name": "read_file", "arguments": "{}"}}
+        ]},
+        tokens=30,
+        cost=0.0006
+    )
+    await store.add_message(msg2)
+    print(f"   Message 2: {msg2.description}")
+
+    msg3 = Message.create(
+        trace_id=trace.trace_id,
+        role="tool",
+        sequence=3,
+        goal_id="1",
+        tool_call_id="call1",
+        content={"tool_name": "read_file", "result": "文件内容..."},
+        tokens=100,
+        cost=0.002
+    )
+    await store.add_message(msg3)
+    print(f"   Message 3: {msg3.description}")
+    print()
+
+    # 5. 查看自动更新的 stats
+    print("5. 查看自动更新的 Goal stats")
+    updated_tree = await store.get_goal_tree(trace.trace_id)
+    goal1 = updated_tree.find("1")
+
+    print(f"   Goal 1: {goal1.description}")
+    print(f"   - status: {goal1.status}")
+    print(f"   - self_stats.message_count: {goal1.self_stats.message_count}")
+    print(f"   - self_stats.total_tokens: {goal1.self_stats.total_tokens}")
+    print(f"   - self_stats.total_cost: ${goal1.self_stats.total_cost:.4f}")
+    print()
+
+    # 6. 完成 goal
+    print("6. 完成 goal 并查看更新")
+    result3 = await registry.execute("goal", {"done": "代码分析完成,找到认证模块"}, uid="test")
+    await store.update_goal_tree(trace.trace_id, tree)
+
+    print(f"   调用: goal(done='...')")
+    print(f"   结果: {result3[:200]}...")
+    print()
+
+    # 7. 显示最终的 GoalTree
+    print("7. 最终 GoalTree 状态")
+    final_tree = await store.get_goal_tree(trace.trace_id)
+    print(final_tree.to_prompt())
+    print()
+
+    # 8. 查看 Trace 统计
+    print("8. Trace 统计")
+    final_trace = await store.get_trace(trace.trace_id)
+    print(f"   total_messages: {final_trace.total_messages}")
+    print(f"   total_tokens: {final_trace.total_tokens}")
+    print(f"   total_cost: ${final_trace.total_cost:.4f}")
+    print()
+
+    print("=" * 60)
+    print("✅ 测试完成!所有功能正常工作")
+    print("=" * 60)
+    print()
+    print(f"数据已保存到: .trace_test/{trace.trace_id[:8]}...")
+
+
+if __name__ == "__main__":
+    asyncio.run(test_goal_tree_and_messages())