Talegorithm před 1 měsícem
rodič
revize
aec9a870a1

+ 10 - 118
agent/execution/fs_store.py

@@ -6,18 +6,18 @@ FileSystem Trace Store - 文件系统存储实现
 目录结构:
 .trace/{trace_id}/
 ├── meta.json           # Trace 元数据
-├── goal.json           # 主线 GoalTree(扁平 JSON,通过 parent_id 构建层级)
-├── messages/           # 主线 Messages(每条独立文件)
+├── goal.json           # GoalTree(扁平 JSON,通过 parent_id 构建层级)
+├── messages/           # Messages(每条独立文件)
 │   ├── {message_id}.json
 │   └── ...
-├── branches/           # 分支数据(独立存储)
-│   ├── A/
-│   │   ├── meta.json   # BranchContext 元数据
-│   │   ├── goal.json   # 分支 A 的 GoalTree
-│   │   └── messages/   # 分支 A 的 Messages
-│   └── B/
-│       └── ...
 └── events.jsonl        # 事件流(WebSocket 续传)
+
+Sub-Trace 是完全独立的 Trace,有自己的目录:
+.trace/{parent_id}@{mode}-{timestamp}-{seq}/
+├── meta.json           # parent_trace_id 指向父 Trace
+├── goal.json
+├── messages/
+└── events.jsonl
 """
 
 import json
@@ -27,7 +27,7 @@ from typing import Dict, List, Optional, Any
 from datetime import datetime
 
 from agent.execution.models import Trace, Message
-from agent.goal.models import GoalTree, Goal, BranchContext, GoalStats
+from agent.goal.models import GoalTree, Goal, GoalStats
 
 
 class FileSystemTraceStore:
@@ -57,26 +57,6 @@ class FileSystemTraceStore:
         """获取 message 文件路径"""
         return self._get_messages_dir(trace_id) / f"{message_id}.json"
 
-    def _get_branches_dir(self, trace_id: str) -> Path:
-        """获取 branches 目录"""
-        return self._get_trace_dir(trace_id) / "branches"
-
-    def _get_branch_dir(self, trace_id: str, branch_id: str) -> Path:
-        """获取分支目录"""
-        return self._get_branches_dir(trace_id) / branch_id
-
-    def _get_branch_meta_file(self, trace_id: str, branch_id: str) -> Path:
-        """获取分支 meta.json 文件路径"""
-        return self._get_branch_dir(trace_id, branch_id) / "meta.json"
-
-    def _get_branch_goal_file(self, trace_id: str, branch_id: str) -> Path:
-        """获取分支 goal.json 文件路径"""
-        return self._get_branch_dir(trace_id, branch_id) / "goal.json"
-
-    def _get_branch_messages_dir(self, trace_id: str, branch_id: str) -> Path:
-        """获取分支 messages 目录"""
-        return self._get_branch_dir(trace_id, branch_id) / "messages"
-
     def _get_events_file(self, trace_id: str) -> Path:
         """获取 events.jsonl 文件路径"""
         return self._get_trace_dir(trace_id) / "events.jsonl"
@@ -92,10 +72,6 @@ class FileSystemTraceStore:
         messages_dir = self._get_messages_dir(trace.trace_id)
         messages_dir.mkdir(exist_ok=True)
 
-        # 创建 branches 目录
-        branches_dir = self._get_branches_dir(trace.trace_id)
-        branches_dir.mkdir(exist_ok=True)
-
         # 写入 meta.json
         meta_file = self._get_meta_file(trace.trace_id)
         meta_file.write_text(json.dumps(trace.to_dict(), indent=2, ensure_ascii=False))
@@ -237,90 +213,6 @@ class FileSystemTraceStore:
 
     # ===== Branch 操作 =====
 
-    async def create_branch(self, trace_id: str, branch: BranchContext) -> None:
-        """创建分支上下文"""
-        branch_dir = self._get_branch_dir(trace_id, branch.id)
-        branch_dir.mkdir(parents=True, exist_ok=True)
-
-        # 创建分支 messages 目录
-        messages_dir = self._get_branch_messages_dir(trace_id, branch.id)
-        messages_dir.mkdir(exist_ok=True)
-
-        # 写入 meta.json
-        meta_file = self._get_branch_meta_file(trace_id, branch.id)
-        meta_file.write_text(json.dumps(branch.to_dict(), indent=2, ensure_ascii=False))
-
-    async def get_branch(self, trace_id: str, branch_id: str) -> Optional[BranchContext]:
-        """获取分支元数据"""
-        meta_file = self._get_branch_meta_file(trace_id, branch_id)
-        if not meta_file.exists():
-            return None
-
-        try:
-            data = json.loads(meta_file.read_text())
-            return BranchContext.from_dict(data)
-        except Exception:
-            return None
-
-    async def get_branch_goal_tree(self, trace_id: str, branch_id: str) -> Optional[GoalTree]:
-        """获取分支的 GoalTree"""
-        goal_file = self._get_branch_goal_file(trace_id, branch_id)
-        if not goal_file.exists():
-            return None
-
-        try:
-            data = json.loads(goal_file.read_text())
-            return GoalTree.from_dict(data)
-        except Exception:
-            return None
-
-    async def update_branch_goal_tree(self, trace_id: str, branch_id: str, tree: GoalTree) -> None:
-        """更新分支的 GoalTree"""
-        goal_file = self._get_branch_goal_file(trace_id, branch_id)
-        goal_file.write_text(json.dumps(tree.to_dict(), indent=2, ensure_ascii=False))
-
-    async def update_branch(self, trace_id: str, branch_id: str, **updates) -> None:
-        """更新分支元数据"""
-        branch = await self.get_branch(trace_id, branch_id)
-        if not branch:
-            return
-
-        # 更新字段
-        for key, value in updates.items():
-            if hasattr(branch, key):
-                # 特殊处理 stats 字段
-                if key == "cumulative_stats" and isinstance(value, dict):
-                    value = GoalStats.from_dict(value)
-                setattr(branch, key, value)
-
-        # 写回文件
-        meta_file = self._get_branch_meta_file(trace_id, branch_id)
-        meta_file.write_text(json.dumps(branch.to_dict(), indent=2, ensure_ascii=False))
-
-    async def list_branches(self, trace_id: str) -> Dict[str, BranchContext]:
-        """列出所有分支元数据"""
-        branches_dir = self._get_branches_dir(trace_id)
-        if not branches_dir.exists():
-            return {}
-
-        branches = {}
-        for branch_dir in branches_dir.iterdir():
-            if not branch_dir.is_dir():
-                continue
-
-            meta_file = branch_dir / "meta.json"
-            if not meta_file.exists():
-                continue
-
-            try:
-                data = json.loads(meta_file.read_text())
-                branch = BranchContext.from_dict(data)
-                branches[branch.id] = branch
-            except Exception:
-                continue
-
-        return branches
-
     # ===== Message 操作 =====
 
     async def add_message(self, message: Message) -> str:

+ 10 - 5
agent/execution/models.py

@@ -18,6 +18,9 @@ class Trace:
 
     单次调用: mode="call"
     Agent 模式: mode="agent"
+
+    主 Trace 和 Sub-Trace 使用相同的数据结构。
+    Sub-Trace 通过 parent_trace_id 和 parent_goal_id 关联父 Trace。
     """
     trace_id: str
     mode: Literal["call", "agent"]
@@ -29,6 +32,10 @@ class Trace:
     task: Optional[str] = None
     agent_type: Optional[str] = None
 
+    # 父子关系(Sub-Trace 特有)
+    parent_trace_id: Optional[str] = None     # 父 Trace ID
+    parent_goal_id: Optional[str] = None      # 哪个 Goal 启动的
+
     # 状态
     status: Literal["running", "completed", "failed"] = "running"
 
@@ -74,6 +81,8 @@ class Trace:
             "prompt_name": self.prompt_name,
             "task": self.task,
             "agent_type": self.agent_type,
+            "parent_trace_id": self.parent_trace_id,
+            "parent_goal_id": self.parent_goal_id,
             "status": self.status,
             "total_messages": self.total_messages,
             "total_tokens": self.total_tokens,
@@ -94,7 +103,7 @@ class Message:
     """
     执行消息 - Trace 中的 LLM 消息
 
-    对应 LLM API 消息格式(assistant/tool),通过 goal_id 和 branch_id 关联 Goal。
+    对应 LLM API 消息格式(assistant/tool),通过 goal_id 关联 Goal。
 
     description 字段自动生成规则:
     - assistant: 优先取 content,若无 content 则生成 "tool call: XX, XX"
@@ -106,7 +115,6 @@ class Message:
     sequence: int                        # 全局顺序
     goal_id: str                         # 关联的 Goal 内部 ID
     description: str = ""                # 消息描述(系统自动生成)
-    branch_id: Optional[str] = None      # 所属分支(null=主线, "A"/"B"=分支)
     tool_call_id: Optional[str] = None   # tool 消息关联对应的 tool_call
     content: Any = None                  # 消息内容(和 LLM API 格式一致)
 
@@ -124,7 +132,6 @@ class Message:
         sequence: int,
         goal_id: str,
         content: Any = None,
-        branch_id: Optional[str] = None,
         tool_call_id: Optional[str] = None,
         tokens: Optional[int] = None,
         cost: Optional[float] = None,
@@ -141,7 +148,6 @@ class Message:
             goal_id=goal_id,
             content=content,
             description=description,
-            branch_id=branch_id,
             tool_call_id=tool_call_id,
             tokens=tokens,
             cost=cost,
@@ -201,7 +207,6 @@ class Message:
         return {
             "message_id": self.message_id,
             "trace_id": self.trace_id,
-            "branch_id": self.branch_id,
             "role": self.role,
             "sequence": self.sequence,
             "goal_id": self.goal_id,

+ 147 - 0
agent/execution/trace_id.py

@@ -0,0 +1,147 @@
+"""
+Trace ID 生成和解析工具
+
+提供 Trace ID 的生成、解析等功能。
+
+Trace ID 格式:
+- 主 Trace: {uuid} (标准 UUID)
+- Sub-Trace: {parent_id}@{mode}-{timestamp}-{seq}
+  例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
+"""
+
+import uuid
+from datetime import datetime
+from threading import Lock
+from typing import Dict, Optional
+
+
+# 全局计数器(线程安全)
+_seq_lock = Lock()
+_seq_counter: Dict[str, int] = {}  # key: "{parent_id}@{mode}-{timestamp}"
+
+
+def generate_trace_id() -> str:
+    """
+    生成主 Trace ID
+
+    Returns:
+        标准 UUID 字符串
+    """
+    return str(uuid.uuid4())
+
+
+def generate_sub_trace_id(parent_id: str, mode: str) -> str:
+    """
+    生成 Sub-Trace ID
+
+    格式: {parent_id}@{mode}-{timestamp}-{seq}
+
+    使用完整的 parent_id(不截断),避免 ID 冲突风险。
+    同一秒内多次调用会递增序号。
+
+    Args:
+        parent_id: 父 Trace ID(完整 UUID)
+        mode: 运行模式(explore, delegate, compaction 等)
+
+    Returns:
+        Sub-Trace ID
+
+    Examples:
+        >>> generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "explore")
+        '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001'
+
+        >>> generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "delegate")
+        '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001'
+    """
+    # 直接使用完整 UUID,不截断
+    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
+
+    # 生成序号(同一秒内递增)
+    prefix = f"{parent_id}@{mode}-{timestamp}"
+    with _seq_lock:
+        seq = _seq_counter.get(prefix, 0) + 1
+        _seq_counter[prefix] = seq
+
+    return f"{prefix}-{seq:03d}"
+
+
+def parse_parent_trace_id(trace_id: str) -> Optional[str]:
+    """
+    从 trace_id 解析出 parent_trace_id
+
+    Args:
+        trace_id: Trace ID
+
+    Returns:
+        父 Trace ID,如果是主 Trace 则返回 None
+
+    Examples:
+        >>> parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
+        '2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d'
+
+        >>> parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
+        None
+    """
+    if '@' in trace_id:
+        return trace_id.split('@')[0]
+    return None
+
+
+def is_sub_trace(trace_id: str) -> bool:
+    """
+    判断是否为 Sub-Trace
+
+    Args:
+        trace_id: Trace ID
+
+    Returns:
+        True 表示是 Sub-Trace,False 表示是主 Trace
+
+    Examples:
+        >>> is_sub_trace("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
+        True
+
+        >>> is_sub_trace("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
+        False
+    """
+    return '@' in trace_id
+
+
+def extract_mode(trace_id: str) -> Optional[str]:
+    """
+    从 Sub-Trace ID 中提取运行模式
+
+    Args:
+        trace_id: Trace ID
+
+    Returns:
+        运行模式(explore, delegate 等),如果是主 Trace 则返回 None
+
+    Examples:
+        >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
+        'explore'
+
+        >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001")
+        'delegate'
+
+        >>> extract_mode("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
+        None
+    """
+    if '@' not in trace_id:
+        return None
+
+    # 格式: parent@mode-timestamp-seq
+    parts = trace_id.split('@')[1]  # "mode-timestamp-seq"
+    mode = parts.split('-')[0]
+    return mode
+
+
+def reset_seq_counter():
+    """
+    重置序号计数器
+
+    主要用于测试,生产环境不应调用此函数。
+    """
+    global _seq_counter
+    with _seq_lock:
+        _seq_counter.clear()

+ 9 - 67
agent/goal/models.py

@@ -4,7 +4,6 @@ Goal 数据模型
 Goal: 执行计划中的目标节点
 GoalTree: 目标树,管理整个执行计划
 GoalStats: 目标统计信息
-BranchContext: 分支执行上下文
 """
 
 from dataclasses import dataclass, field
@@ -17,10 +16,7 @@ import json
 GoalStatus = Literal["pending", "in_progress", "completed", "abandoned"]
 
 # Goal 类型
-GoalType = Literal["normal", "explore_start", "explore_merge"]
-
-# Branch 状态
-BranchStatus = Literal["exploring", "completed", "abandoned"]
+GoalType = Literal["normal", "agent_call"]
 
 
 @dataclass
@@ -55,23 +51,19 @@ class Goal:
     执行目标
 
     使用扁平列表 + parent_id 构建层级结构。
+    agent_call 类型用于标记启动了 Sub-Trace 的 Goal。
     """
     id: str                                  # 内部唯一 ID,纯自增("1", "2", "3"...)
     description: str                         # 目标描述
     reason: str = ""                         # 创建理由(为什么做)
     parent_id: Optional[str] = None          # 父 Goal ID(层级关系)
-    branch_id: Optional[str] = None          # 所属分支 ID(分支关系,null=主线)
     type: GoalType = "normal"                # Goal 类型
     status: GoalStatus = "pending"           # 状态
     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 = field(default_factory=GoalStats)          # 自身统计(仅直接关联的 messages)
@@ -86,14 +78,11 @@ class Goal:
             "description": self.description,
             "reason": self.reason,
             "parent_id": self.parent_id,
-            "branch_id": self.branch_id,
             "type": self.type,
             "status": self.status,
             "summary": self.summary,
-            "branch_ids": self.branch_ids,
-            "explore_start_id": self.explore_start_id,
-            "merge_summary": self.merge_summary,
-            "selected_branch": self.selected_branch,
+            "sub_trace_ids": self.sub_trace_ids,
+            "agent_call_mode": self.agent_call_mode,
             "self_stats": self.self_stats.to_dict(),
             "cumulative_stats": self.cumulative_stats.to_dict(),
             "created_at": self.created_at.isoformat() if self.created_at else None,
@@ -119,64 +108,17 @@ class Goal:
             description=data["description"],
             reason=data.get("reason", ""),
             parent_id=data.get("parent_id"),
-            branch_id=data.get("branch_id"),
             type=data.get("type", "normal"),
             status=data.get("status", "pending"),
             summary=data.get("summary"),
-            branch_ids=data.get("branch_ids"),
-            explore_start_id=data.get("explore_start_id"),
-            merge_summary=data.get("merge_summary"),
-            selected_branch=data.get("selected_branch"),
+            sub_trace_ids=data.get("sub_trace_ids"),
+            agent_call_mode=data.get("agent_call_mode"),
             self_stats=self_stats,
             cumulative_stats=cumulative_stats,
             created_at=created_at or datetime.now(),
         )
 
 
-@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
-
-    summary: Optional[str] = None           # 完成时的总结
-    cumulative_stats: GoalStats = field(default_factory=GoalStats)  # 累计统计
-    created_at: datetime = field(default_factory=datetime.now)
-
-    def to_dict(self) -> Dict[str, Any]:
-        return {
-            "id": self.id,
-            "explore_start_id": self.explore_start_id,
-            "description": self.description,
-            "status": self.status,
-            "summary": self.summary,
-            "cumulative_stats": self.cumulative_stats.to_dict(),
-            "created_at": self.created_at.isoformat() if self.created_at else None,
-        }
-
-    @classmethod
-    def from_dict(cls, data: Dict[str, Any]) -> "BranchContext":
-        created_at = data.get("created_at")
-        if isinstance(created_at, str):
-            created_at = datetime.fromisoformat(created_at)
-
-        cumulative_stats = data.get("cumulative_stats", {})
-        if isinstance(cumulative_stats, dict):
-            cumulative_stats = GoalStats.from_dict(cumulative_stats)
-
-        return cls(
-            id=data["id"],
-            explore_start_id=data["explore_start_id"],
-            description=data["description"],
-            status=data.get("status", "exploring"),
-            summary=data.get("summary"),
-            cumulative_stats=cumulative_stats,
-            created_at=created_at or datetime.now(),
-        )
-
-
 @dataclass
 class GoalTree:
     """

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

@@ -18,6 +18,9 @@ DEFAULT_SKILLS_DIRS = [
     "./agent/skills"                           # 框架内置 skills
 ]
 
+# 默认单一目录(用于 list_skills)
+DEFAULT_SKILLS_DIR = DEFAULT_SKILLS_DIRS[0]
+
 
 def _check_skill_setup(skill_name: str) -> Optional[str]:
     """

+ 136 - 61
docs/context-management.md

@@ -176,14 +176,14 @@ Todo.Info = {
 
 ```python
 # 主 Agent
-Trace(trace_id="abc123", mode="agent", task="实现用户认证")
+Trace(trace_id="2f8d3a1c", 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 方案")
+Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="explore", task="JWT 方案")
+Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="explore", task="Session 方案")
 
 # Sub-Agent(单线委托)
-Trace(trace_id="abc123.task1", parent_trace_id="abc123", agent_type="delegate", task="实现具体功能")
+Trace(trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001", parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", agent_type="delegate", task="实现具体功能")
 ```
 
 **优点**:
@@ -191,7 +191,7 @@ Trace(trace_id="abc123.task1", parent_trace_id="abc123", agent_type="delegate",
 - **ID 简洁**:每个 Trace 内部独立编号(1, 2, 3),不需要前缀
 - **完全隔离**:每个 Trace 有独立的 GoalTree、Message List、LLM Context
 - **自然分布式**:每个 Trace 可以独立运行、迁移、存储
-- **层级清晰**:从 trace_id 可以直接解析出父子关系(`abc123.A` 的父是 `abc123`
+- **层级清晰**:从 trace_id 可以直接解析出父子关系(`@` 前是父 ID
 
 ### 数据结构
 
@@ -212,7 +212,7 @@ class Trace:
 
     主 Agent 和 Sub-Agent 使用相同的 Trace 结构
     """
-    trace_id: str                        # 层级化 ID:"abc123", "abc123.A", "abc123.A.1"
+    trace_id: str                        # 主 Trace: UUID,Sub-Trace: parent@mode-timestamp-seq
     mode: Literal["call", "agent"]
     task: Optional[str] = None
 
@@ -245,21 +245,88 @@ class Trace:
 **实现**:`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(用户直接触发):
+  {uuid}
+  例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
+
+Sub-Trace(Agent 启动的子任务):
+  {parent_id}@{mode}-{timestamp}-{seq}
+  例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
+  例如: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220015-002
+```
+
+**格式说明**:
+- `parent_id`:父 Trace 的**完整 UUID**(不截断,避免冲突)
+- `@`:父子关系分隔符
+- `mode`:运行模式(`explore`, `delegate`, `compaction` 等)
+- `timestamp`:创建时间戳(`YYYYMMDDHHmmss`)
+- `seq`:同一秒内的序号(`001`, `002`, ...)
+
+**优点**:
+1. **零碰撞风险**:使用完整 UUID,完全避免 ID 冲突
+2. **可精确追溯**:从 Sub-Trace ID 直接看到完整父 Trace ID
+3. **无需冲突检测**:实现简单,不依赖外部状态
+4. **信息完整**:一眼看出触发者、模式、时间
+5. **层级清晰**:`@` 分隔符明确表示父子关系
 
 **从 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
+    if '@' in trace_id:
+        return trace_id.split('@')[0]
+    return None
+
+# 示例
+parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001")
+# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
+
+parse_parent_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d")
+# → None
+```
+
+**生成 Sub-Trace ID**:
+```python
+from datetime import datetime
+from threading import Lock
+from typing import Dict
+
+# 全局计数器(线程安全)
+_seq_lock = Lock()
+_seq_counter: Dict[str, int] = {}  # key: "{parent_id}@{mode}-{timestamp}"
+
+def generate_sub_trace_id(parent_id: str, mode: str) -> str:
+    """
+    生成 Sub-Trace ID
+
+    格式: {parent_id}@{mode}-{timestamp}-{seq}
+
+    Args:
+        parent_id: 父 Trace ID(完整 UUID,不截断)
+        mode: 运行模式(explore, delegate, compaction)
+
+    Returns:
+        Sub-Trace ID(完整格式,无碰撞风险)
+    """
+    # 直接使用完整 UUID,不截断
+    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
+
+    # 生成序号(同一秒内递增)
+    prefix = f"{parent_id}@{mode}-{timestamp}"
+    with _seq_lock:
+        seq = _seq_counter.get(prefix, 0) + 1
+        _seq_counter[prefix] = seq
+
+    return f"{prefix}-{seq:03d}"
 
 # 示例
-parse_parent_trace_id("abc123.A")    # → "abc123"
-parse_parent_trace_id("abc123.A.1")  # → "abc123.A"
-parse_parent_trace_id("abc123")      # → None
+generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "explore")
+# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001"
+
+generate_sub_trace_id("2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d", "delegate")
+# → "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001"
 ```
 
 #### Goal
@@ -336,6 +403,7 @@ class Message:
     # 元数据
     tokens: Optional[int] = None
     cost: Optional[float] = None
+    duration_ms: Optional[int] = None
     created_at: datetime
 ```
 
@@ -352,10 +420,10 @@ class Message:
 **查询 Message**:
 ```python
 # 查询主 Trace 的 Messages
-GET /api/traces/abc123/messages?goal_id=2
+GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/messages?goal_id=2
 
 # 查询 Sub-Trace 的 Messages
-GET /api/traces/abc123.A/messages?goal_id=1
+GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/messages?goal_id=1
 ```
 
 ### 工具设计
@@ -837,24 +905,24 @@ ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0
 
 ```
 .trace/
-├── abc123/                    # 主 Trace
-│   ├── meta.json              # Trace 元数据
-│   ├── goal.json              # GoalTree
-│   ├── messages/              # Messages
+├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/                             # 主 Trace
+│   ├── meta.json                # Trace 元数据
+│   ├── goal.json                # GoalTree
+│   ├── messages/                # Messages
 │   │   ├── {message_id}.json
 │   │   └── ...
-│   └── events.jsonl           # 事件流
+│   └── events.jsonl             # 事件流
-├── abc123.A/                  # Sub-Trace A(并行探索)
-│   ├── meta.json              # parent_trace_id: "abc123"
-│   ├── goal.json              # 独立的 GoalTree
+├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/ # Sub-Trace A(并行探索)
+│   ├── meta.json                # parent_trace_id: "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
+│   ├── goal.json                # 独立的 GoalTree
 │   ├── messages/
 │   └── events.jsonl
-├── abc123.B/                  # Sub-Trace B(并行探索)
+├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002/ # Sub-Trace B(并行探索)
 │   └── ...
-└── abc123.task1/              # Sub-Trace task1(单线委托)
+└── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@delegate-20260204220030-001/ # Sub-Trace task1(单线委托)
     └── ...
 ```
 
@@ -870,18 +938,18 @@ ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0
 ### 场景
 
 ```
-主 Trace (abc123):
+主 Trace (2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d):
   [1] 分析问题
   [2] 并行探索认证方案 (type=agent_call, mode=explore)
-      → 启动 Sub-Traces: abc123.A, abc123.B
+      → 启动 Sub-Traces(完整 ID)
   [3] 完善实现
 
-Sub-Trace A (abc123.A):
+Sub-Trace A (2f8d3a1c...@explore-20260204220012-001):
   [1] JWT 设计
   [2] JWT 实现
   → 返回摘要:"JWT 方案实现完成"
 
-Sub-Trace B (abc123.B):
+Sub-Trace B (2f8d3a1c...@explore-20260204220012-002):
   [1] Session 设计
   [2] Session 实现
   [3] Session 测试
@@ -903,7 +971,7 @@ explore 工具返回:
 **主 Trace 的 GoalTree**:
 
 ```python
-# Trace: abc123
+# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
 goals = [
     Goal(id="1", type="normal", description="分析问题"),
     Goal(
@@ -911,7 +979,10 @@ goals = [
         type="agent_call",
         description="并行探索认证方案",
         agent_call_mode="explore",
-        sub_trace_ids=["abc123.A", "abc123.B"],
+        sub_trace_ids=[
+            "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
+            "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002"
+        ],
     ),
     Goal(id="3", type="normal", description="完善实现"),
 ]
@@ -920,7 +991,7 @@ goals = [
 **Sub-Trace A 的 GoalTree**(独立编号):
 
 ```python
-# Trace: abc123.A
+# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
 goals = [
     Goal(id="1", type="normal", description="JWT 设计"),
     Goal(id="2", type="normal", description="JWT 实现"),
@@ -930,7 +1001,7 @@ goals = [
 **Sub-Trace B 的 GoalTree**(独立编号):
 
 ```python
-# Trace: abc123.B
+# Trace: 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002
 goals = [
     Goal(id="1", type="normal", description="Session 设计"),
     Goal(id="2", type="normal", description="Session 实现"),
@@ -942,17 +1013,17 @@ goals = [
 
 ```
 .trace/
-├── abc123/                # 主 Trace
+├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/                             # 主 Trace
 │   ├── meta.json
 │   ├── goal.json
 │   └── messages/
-├── abc123.A/              # Sub-Trace A
-│   ├── meta.json          # parent_trace_id: "abc123", parent_goal_id: "2"
-│   ├── goal.json          # 独立的 GoalTree
+├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/ # Sub-Trace A
+│   ├── meta.json            # parent_trace_id: "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"
+│   ├── goal.json            # 独立的 GoalTree
 │   └── messages/
-└── abc123.B/              # Sub-Trace B
+└── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002/ # Sub-Trace B
     └── ...
 ```
 
@@ -972,7 +1043,7 @@ goals = [
                   └──→ [B:Session方案] ┘
 ```
 
-**继续展开分支 A 内部**(加载 Sub-Trace abc123.A 的 GoalTree):
+**继续展开分支 A 内部**(加载 Sub-Trace 的 GoalTree):
 ```
                   ┌──→ [A.1:JWT设计] → [A.2:JWT实现] ──┐
 [1:分析] ──→ [2] ─┤                                    ├──→ [3:完善]
@@ -980,22 +1051,23 @@ goals = [
 ```
 
 **注意**:
-- `[A:JWT方案]` 是折叠视图,代表整个 Sub-Trace abc123.A
+- `[A:JWT方案]` 是折叠视图,代表整个 Sub-Trace
 - `[A.1]`, `[A.2]` 是展开后显示 Sub-Trace 内部的 Goals
-- 前端显示为 "A.1",但后端查询是 `GET /api/traces/abc123.A/messages?goal_id=1`
+- 前端显示为 "A.1",但后端查询使用完整 trace_id:
+  `GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001/messages?goal_id=1`
 
 ### 前端 API
 
 **REST**:返回主 Trace 的 GoalTree + Sub-Trace 列表(元数据)。
 
 ```http
-GET /api/traces/abc123
+GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d
 ```
 
 响应:
 ```json
 {
-  "trace_id": "abc123",
+  "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
   "status": "running",
   "goal_tree": {
     "goals": [
@@ -1005,15 +1077,18 @@ GET /api/traces/abc123
         "type": "agent_call",
         "description": "并行探索认证方案",
         "agent_call_mode": "explore",
-        "sub_trace_ids": ["abc123.A", "abc123.B"]
+        "sub_trace_ids": [
+          "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
+          "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002"
+        ]
       },
       {"id": "3", "type": "normal", "description": "完善实现"}
     ]
   },
   "sub_traces": {
-    "abc123.A": {
-      "trace_id": "abc123.A",
-      "parent_trace_id": "abc123",
+    "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001": {
+      "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
+      "parent_trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
       "parent_goal_id": "2",
       "agent_type": "explore",
       "task": "JWT 方案",
@@ -1022,8 +1097,8 @@ GET /api/traces/abc123
       "total_tokens": 4000,
       "total_cost": 0.05
     },
-    "abc123.B": {
-      "trace_id": "abc123.B",
+    "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002": {
+      "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-002",
       "agent_type": "explore",
       "task": "Session 方案",
       "status": "completed",
@@ -1036,14 +1111,14 @@ GET /api/traces/abc123
 **按需加载 Sub-Trace 详情**:
 
 ```http
-GET /api/traces/abc123.A
+GET /api/traces/2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001
 ```
 
 响应:
 ```json
 {
-  "trace_id": "abc123.A",
-  "parent_trace_id": "abc123",
+  "trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001",
+  "parent_trace_id": "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
   "parent_goal_id": "2",
   "agent_type": "explore",
   "task": "JWT 方案",
@@ -1090,9 +1165,8 @@ async def explore_tool(branches: List[str]) -> str:
     # 2. 创建多个 Sub-Traces
     sub_traces = []
     for i, desc in enumerate(branches):
-        suffix = chr(ord('A') + i)
         sub_trace = Trace.create(
-            trace_id=f"{current_trace_id}.{suffix}",
+            trace_id=generate_sub_trace_id(current_trace_id, "explore"),
             parent_trace_id=current_trace_id,
             parent_goal_id=current_goal_id,
             agent_type="explore",
@@ -1115,10 +1189,10 @@ async def explore_tool(branches: List[str]) -> str:
 ```markdown
 ## 探索结果
 
-### 方案 A (abc123.A): JWT 方案
+### 方案 A: JWT 方案
 实现完成。优点:无状态,易扩展。缺点:token 较大,无法主动失效。
 
-### 方案 B (abc123.B): Session 方案
+### 方案 B: Session 方案
 实现完成。优点:token 小,可主动失效。缺点:需要 Redis 存储。
 
 ---
@@ -1157,14 +1231,15 @@ Sub-Trace 完成后的压缩策略:
 
 | 功能 | 文件路径 | 状态 |
 |------|---------|------|
-| Trace 数据模型 | `agent/execution/models.py` | 待调整(增加父子关系、context) |
-| Goal 数据模型 | `agent/goal/models.py` | 待调整(简化,移除 branch_id) |
+| Trace 数据模型 | `agent/execution/models.py` | 待调整(增加父子关系、移除 branch_id) |
+| Goal 数据模型 | `agent/goal/models.py` | 待调整(移除 branch_id、BranchContext) |
+| Trace ID 生成 | `agent/execution/trace_id.py` | 待实现(generate_sub_trace_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` | 待调整 |
-| TraceStore 协议 | `agent/execution/protocols.py` | 待调整(支持嵌套 Trace) |
+| TraceStore 协议 | `agent/execution/protocols.py` | 待调整(移除 branch 相关接口) |
+| FileSystem Store | `agent/execution/fs_store.py` | 待调整(移除 branches/ 目录) |
 | DAG 可视化 API | `agent/execution/api.py` | 待调整(支持 Sub-Trace) |
 | WebSocket 推送 | `agent/execution/websocket.py` | 待调整(统一事件格式) |
 | Plan 注入 | `agent/core/runner.py` | 待调整 |

+ 174 - 0
docs/refactor-plan.md

@@ -0,0 +1,174 @@
+# 重构计划:统一 Trace 模型
+
+> 基于更新后的 context-management.md 设计,移除 branch 概念,采用独立 Trace 架构
+
+## 目标
+
+将当前基于 branch 的实现重构为统一的 Trace 模型,每个 Sub-Agent 都是独立的 Trace。
+
+## Phase 1: 核心数据结构调整
+
+### 1.1 Trace ID 生成器
+- [ ] 实现 `agent/execution/trace_id.py`
+  - [ ] `generate_sub_trace_id(parent_id, mode)` 函数
+  - [ ] `parse_parent_trace_id(trace_id)` 函数
+  - [ ] 线程安全的序号计数器
+  - [ ] 单元测试
+
+### 1.2 Trace 模型更新
+- [ ] 更新 `agent/execution/models.py:Trace`
+  - [ ] 添加 `parent_trace_id: Optional[str]` 字段
+  - [ ] 添加 `parent_goal_id: Optional[str]` 字段
+  - [ ] 确保 `context: Dict[str, Any]` 字段存在
+  - [ ] 更新 `to_dict()` 和 `from_dict()` 方法
+  - [ ] 移除任何 branch 相关字段(如果有)
+
+### 1.3 Message 模型更新
+- [ ] 更新 `agent/execution/models.py:Message`
+  - [ ] **移除** `branch_id` 字段
+  - [ ] 确保 `duration_ms` 字段存在
+  - [ ] 更新 `to_dict()` 方法
+  - [ ] 更新 `create()` 方法
+
+### 1.4 Goal 模型更新
+- [ ] 更新 `agent/goal/models.py:Goal`
+  - [ ] **移除** `branch_id` 字段
+  - [ ] **移除** `branch_ids` 字段
+  - [ ] 确保 `sub_trace_ids` 字段存在
+  - [ ] 更新 `to_dict()` 和 `from_dict()` 方法
+
+### 1.5 移除 BranchContext
+- [ ] 删除 `agent/goal/models.py:BranchContext` 类
+  - [ ] 检查所有引用
+  - [ ] 移除相关导入
+
+## Phase 2: 存储层重构
+
+### 2.1 FileSystem Store 更新
+- [ ] 重构 `agent/execution/fs_store.py`
+  - [ ] **移除** `_get_branches_dir()` 方法
+  - [ ] **移除** `_get_branch_dir()` 方法
+  - [ ] **移除** `_get_branch_meta_file()` 方法
+  - [ ] **移除** `_get_branch_goal_file()` 方法
+  - [ ] **移除** `_get_branch_messages_dir()` 方法
+  - [ ] **移除** `create_branch()` 方法
+  - [ ] **移除** `get_branch()` 方法
+  - [ ] **移除** `get_branch_goal_tree()` 方法
+  - [ ] **移除** `update_branch_goal_tree()` 方法
+  - [ ] **移除** `update_branch()` 方法
+  - [ ] **移除** `list_branches()` 方法
+  - [ ] 更新 `create_trace()` - 不再创建 branches/ 目录
+  - [ ] 更新 `add_message()` - 移除 branch_id 逻辑
+  - [ ] 更新 `get_trace_messages()` - 移除 branch_id 参数
+  - [ ] 更新 `get_messages_by_goal()` - 移除 branch_id 参数
+  - [ ] 更新 `_update_goal_stats()` - 移除 branch_id 逻辑
+  - [ ] 更新 `_get_affected_goals()` - 移除 branch_id 逻辑
+
+### 2.2 TraceStore 协议更新
+- [ ] 更新 `agent/execution/protocols.py:TraceStore`
+  - [ ] **移除** branch 相关方法签名
+  - [ ] 更新方法签名(移除 branch_id 参数)
+  - [ ] 添加查询 Sub-Trace 的方法(如果需要)
+
+### 2.3 数据迁移脚本
+- [ ] 创建 `scripts/migrate_traces.py`
+  - [ ] 读取旧的 `.trace/*/branches/` 结构
+  - [ ] 为每个 branch 生成新的 trace_id
+  - [ ] 创建独立的 Trace 目录
+  - [ ] 更新 Goal 的 sub_trace_ids
+  - [ ] 移除 branch_id 字段
+  - [ ] 备份原始数据
+
+## Phase 3: 工具实现
+
+### 3.1 explore 工具
+- [ ] 实现 `agent/goal/explore.py`
+  - [ ] `explore_tool(branches, background)` 函数
+  - [ ] 使用 `generate_sub_trace_id()` 生成 Sub-Trace ID
+  - [ ] 创建独立的 Trace 实例
+  - [ ] 并行执行 Sub-Traces
+  - [ ] 汇总结果
+  - [ ] 单元测试
+
+### 3.2 delegate 工具
+- [ ] 实现 `agent/goal/delegate.py`
+  - [ ] `delegate_tool(task)` 函数
+  - [ ] 使用 `generate_sub_trace_id()` 生成 Sub-Trace ID
+  - [ ] 创建独立的 Trace 实例
+  - [ ] 执行并返回结果
+  - [ ] 单元测试
+
+### 3.3 goal 工具更新
+- [ ] 更新 `agent/goal/tool.py`
+  - [ ] 移除任何 branch 相关逻辑
+  - [ ] 确保与新的 Goal 模型兼容
+
+## Phase 4: API 层更新
+
+### 4.1 REST API
+- [ ] 更新 `agent/execution/api.py`
+  - [ ] `GET /api/traces/{trace_id}` - 支持新的 trace_id 格式
+  - [ ] 添加查询 Sub-Traces 的端点
+  - [ ] 移除 branch 相关端点
+  - [ ] 更新响应格式(包含 sub_traces)
+
+### 4.2 WebSocket
+- [ ] 更新 `agent/execution/websocket.py`
+  - [ ] 移除 branch 相关事件
+  - [ ] 添加 `sub_trace_started` 事件
+  - [ ] 添加 `sub_trace_completed` 事件
+  - [ ] 更新事件格式
+
+## Phase 5: 清理和测试
+
+### 5.1 清理
+- [ ] 搜索代码库中所有 "branch" 引用
+  - [ ] 移除或更新相关代码
+- [ ] 删除 `.trace/*/branches/` 目录(旧数据)
+- [ ] 更新所有文档引用
+
+### 5.2 测试
+- [ ] 单元测试
+  - [ ] Trace ID 生成器
+  - [ ] explore 工具
+  - [ ] delegate 工具
+  - [ ] FileSystem Store
+- [ ] 集成测试
+  - [ ] 创建主 Trace
+  - [ ] 启动 Sub-Trace
+  - [ ] 查询跨 Trace 数据
+- [ ] 端到端测试
+  - [ ] 完整的 explore 流程
+  - [ ] 完整的 delegate 流程
+
+## Phase 6: 文档更新
+
+- [ ] 更新 README
+- [ ] 更新 API 文档
+- [ ] 添加迁移指南
+- [ ] 更新示例代码
+
+## 实施顺序建议
+
+1. **Phase 1** (数据结构) - 最重要,先打好基础
+2. **Phase 2.1-2.2** (存储层) - 核心存储逻辑
+3. **Phase 3** (工具实现) - 业务功能
+4. **Phase 4** (API 层) - 对外接口
+5. **Phase 2.3** (数据迁移) - 处理历史数据
+6. **Phase 5** (清理测试) - 确保质量
+7. **Phase 6** (文档) - 最后完善
+
+## 风险和注意事项
+
+1. **兼容性**:旧的 traces 数据需要迁移
+2. **并发安全**:`generate_sub_trace_id` 的序号生成器需要线程安全
+3. **性能**:独立 Trace 可能增加文件系统操作,需要监控
+4. **调试**:新的 trace_id 格式较长,需要确保日志和 UI 能正确显示
+
+## 完成标准
+
+- [ ] 所有测试通过
+- [ ] 文档更新完成
+- [ ] 示例代码可运行
+- [ ] 没有 branch 相关代码残留
+- [ ] 旧数据可以迁移

+ 0 - 162
test_integration.py

@@ -1,162 +0,0 @@
-"""
-测试新的 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())