# Trace 模块 - 执行记录存储

> 执行轨迹记录和存储的后端实现

---

## 架构概览

**职责定位**：`agent/trace` 模块负责所有 Trace/Message 相关功能

```
agent/trace/
├── models.py          # Trace/Message 数据模型
├── goal_models.py     # Goal/GoalTree 数据模型
├── protocols.py       # TraceStore 存储接口
├── store.py           # 文件系统存储实现
├── trace_id.py        # Trace ID 生成工具
├── api.py             # RESTful 查询 API
├── run_api.py         # 控制 API（run/stop/reflect）
├── websocket.py       # WebSocket 实时推送
├── goal_tool.py       # goal 工具（计划管理）
└── compaction.py      # Context 压缩
```

**设计原则**：
- **高内聚**：所有 Trace 相关代码在一个模块
- **松耦合**：核心模型不依赖 FastAPI
- **可扩展**：易于添加 PostgreSQL 等存储实现
- **统一模型**：主 Agent 和 Sub-Agent 使用相同的 Trace 结构

---

## 核心模型

### Trace - 执行轨迹

一次完整的 LLM 交互（单次调用或 Agent 任务）。每个 Sub-Agent 都是独立的 Trace。

```python
# 主 Trace
main_trace = Trace.create(mode="agent", task="探索代码库")

# Sub-Trace（由 delegate 或 explore 工具创建）
sub_trace = Trace(
    trace_id="2f8d3a1c...@explore-20260204220012-001",
    mode="agent",
    task="探索 JWT 认证方案",
    parent_trace_id="2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d",
    parent_goal_id="3",
    agent_type="explore",
    status="running"
)

# 字段说明
trace.trace_id        # UUID（主 Trace）或 {parent}@{mode}-{timestamp}-{seq}（Sub-Trace）
trace.mode            # "call" | "agent"
trace.task            # 任务描述
trace.parent_trace_id # 父 Trace ID（Sub-Trace 专用）
trace.parent_goal_id  # 触发的父 Goal ID（Sub-Trace 专用）
trace.agent_type      # Agent 类型：explore, delegate 等
trace.status          # "running" | "completed" | "failed" | "stopped"
trace.total_messages  # Message 总数
trace.total_tokens    # Token 总数
trace.total_cost      # 总成本
trace.current_goal_id # 当前焦点 goal
trace.head_sequence   # 当前主路径头节点 sequence（用于 build_llm_messages）
```

**Trace ID 格式**：
- **主 Trace**：标准 UUID，例如 `2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d`
- **Sub-Trace**：`{parent_uuid}@{mode}-{timestamp}-{seq}`，例如 `2f8d3a1c...@explore-20260204220012-001`

**实现**：`agent/trace/models.py:Trace`

### Message - 执行消息

对应 LLM API 消息，加上元数据。通过 `goal_id` 关联 GoalTree 中的目标。通过 `parent_sequence` 形成消息树。

```python
# assistant 消息（模型返回，可能含 text + tool_calls）
assistant_msg = Message.create(
    trace_id=trace.trace_id,
    role="assistant",
    goal_id="3",                    # Goal ID（Trace 内部自增）
    content={"text": "...", "tool_calls": [...]},
    parent_sequence=5,              # 父消息的 sequence
)

# tool 消息
tool_msg = Message.create(
    trace_id=trace.trace_id,
    role="tool",
    goal_id="5",
    tool_call_id="call_abc123",
    content="工具执行结果",
    parent_sequence=6,
)
```

**parent_sequence**：指向父消息的 sequence，构成消息树。主路径 = 从 `trace.head_sequence` 沿 parent chain 回溯到 root。

**description 字段**（系统自动生成）：
- `assistant` 消息：优先取 content 中的 text，若无 text 则生成 "tool call: XX, XX"
- `tool` 消息：使用 tool name

**实现**：`agent/trace/models.py:Message`

---

## 存储接口

### TraceStore Protocol

```python
class TraceStore(Protocol):
    # Trace 操作
    async def create_trace(self, trace: Trace) -> str: ...
    async def get_trace(self, trace_id: str) -> Optional[Trace]: ...
    async def update_trace(self, trace_id: str, **updates) -> None: ...
    async def list_traces(self, ...) -> List[Trace]: ...

    # GoalTree 操作（每个 Trace 有独立的 GoalTree）
    async def get_goal_tree(self, trace_id: str) -> Optional[GoalTree]: ...
    async def update_goal_tree(self, trace_id: str, tree: GoalTree) -> None: ...
    async def add_goal(self, trace_id: str, goal: Goal) -> None: ...
    async def update_goal(self, trace_id: str, goal_id: str, **updates) -> None: ...

    # Message 操作
    async def add_message(self, message: Message) -> str: ...
    async def get_message(self, message_id: str) -> Optional[Message]: ...
    async def get_trace_messages(self, trace_id: str) -> List[Message]: ...
    async def get_main_path_messages(self, trace_id: str, head_sequence: int) -> List[Message]: ...
    async def get_messages_by_goal(self, trace_id: str, goal_id: str) -> List[Message]: ...
    async def update_message(self, message_id: str, **updates) -> None: ...

    # 事件流（WebSocket 断线续传）
    async def get_events(self, trace_id: str, since_event_id: int) -> List[Dict]: ...
    async def append_event(self, trace_id: str, event_type: str, payload: Dict) -> int: ...
```

**实现**：`agent/trace/protocols.py`

### FileSystemTraceStore

```python
from agent.trace import FileSystemTraceStore

store = FileSystemTraceStore(base_path=".trace")
```

**目录结构**：
```
.trace/
├── 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d/           # 主 Trace
│   ├── meta.json                                   # Trace 元数据
│   ├── goal.json                                   # GoalTree（扁平 JSON）
│   ├── messages/                                   # Messages
│   │   ├── {message_id}.json
│   │   └── ...
│   └── events.jsonl                                # 事件流
│
├── 2f8d3a1c...@explore-20260204220012-001/        # Sub-Trace A
│   ├── meta.json                                   # parent_trace_id 指向主 Trace
│   ├── goal.json                                   # 独立的 GoalTree
│   ├── messages/
│   └── events.jsonl
│
└── 2f8d3a1c...@explore-20260204220012-002/        # Sub-Trace B
    └── ...
```

**关键变化**（相比旧设计）：
- ❌ 不再有 `branches/` 子目录
- ✅ 每个 Sub-Trace 是顶层独立目录
- ✅ Sub-Trace 有完整的 Trace 结构（meta + goal + messages + events）

**实现**：`agent/trace/store.py`

---

## REST API 端点

### 查询端点

#### 1. 列出 Traces

```http
GET /api/traces?mode=agent&status=running&limit=20
```

返回所有 Traces（包括主 Trace 和 Sub-Traces）。

#### 2. 获取 Trace + GoalTree + Sub-Traces

```http
GET /api/traces/{trace_id}
```

返回：
- Trace 元数据
- GoalTree（该 Trace 的完整 Goal 树）
- Sub-Traces 元数据（查询所有 `parent_trace_id == trace_id` 的 Traces）

#### 3. 获取 Messages

```http
GET /api/traces/{trace_id}/messages?mode=main_path&head=15&goal_id=3
```

返回指定 Trace 的 Messages。参数：
- `mode`: `main_path`（默认）| `all` — 返回主路径消息或全部消息
- `head`: 可选 sequence 值 — 指定主路径的 head（默认用 trace.head_sequence，仅 mode=main_path 有效）
- `goal_id`: 可选，按 Goal 过滤

**实现**：`agent/trace/api.py`

### 控制端点

需在 `api_server.py` 中配置 Runner。执行在后台异步进行，通过 WebSocket 监听进度。

#### 4. 新建 Trace 并执行

```http
POST /api/traces
Content-Type: application/json

{
  "messages": [
    {"role": "system", "content": "自定义 system prompt（可选，不传则从 skills 自动构建）"},
    {"role": "user", "content": "分析项目架构"}
  ],
  "model": "gpt-4o",
  "temperature": 0.3,
  "max_iterations": 200,
  "tools": null,
  "name": "任务名称",
  "uid": "user_id"
}
```

#### 5. 运行（统一续跑 + 回溯）

```http
POST /api/traces/{trace_id}/run
Content-Type: application/json

{
  "messages": [{"role": "user", "content": "..."}],
  "after_message_id": null
}
```

- `after_message_id: null`（或省略）→ 从末尾续跑
- `after_message_id: "<message_id>"`（主路径上且 < head）→ 回溯到该消息后运行
- `messages: []` + `after_message_id: "<message_id>"` → 重新生成

Runner 根据解析出的 sequence 与 `head_sequence` 的关系自动判断续跑/回溯行为。

#### 6. 停止运行中的 Trace

```http
POST /api/traces/{trace_id}/stop
```

设置取消信号，agent loop 在下一个检查点退出，Trace 状态置为 `stopped`。

#### 7. 列出正在运行的 Trace

```http
GET /api/traces/running
```

#### 8. 反思（提取经验）

```http
POST /api/traces/{trace_id}/reflect
Content-Type: application/json

{
  "focus": "可选，反思重点"
}
```

在 trace 末尾追加一条包含反思 prompt 的 user message，作为侧枝运行。
使用 `max_iterations=1, tools=[]` 进行单轮无工具 LLM 调用，生成经验总结，
结果自动追加到 `./.cache/experiences.md`。head_sequence 通过 try/finally 保证恢复。

### 经验端点

#### 9. 读取经验文件

```http
GET /api/experiences
```

返回 `./.cache/experiences.md` 的文件内容。

**实现**：`agent/trace/run_api.py`

---

## WebSocket 事件

### 连接

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

### 事件类型

| 事件 | 触发时机 | payload |
|------|---------|---------|
| `connected` | WebSocket 连接成功 | trace_id, current_event_id, goal_tree, sub_traces |
| `goal_added` | 新增 Goal | goal 完整数据（含 stats, parent_id, type） |
| `goal_updated` | Goal 状态变化（含级联完成） | goal_id, updates, affected_goals（含级联完成的父节点） |
| `message_added` | 新 Message | message 数据（含 goal_id），affected_goals |
| `sub_trace_started` | Sub-Trace 开始执行 | trace_id, parent_goal_id, agent_type, task |
| `sub_trace_completed` | Sub-Trace 完成 | trace_id, status, summary, stats |
| `rewind` | 回溯执行 | after_sequence, head_sequence, goal_tree_snapshot |
| `trace_completed` | 执行完成 | 统计信息 |

### Stats 更新逻辑

每次添加 Message 时，后端执行：
1. 更新对应 Goal 的 `self_stats`
2. 沿 `parent_id` 链向上更新所有祖先的 `cumulative_stats`
3. 在 `message_added` 事件的 `affected_goals` 中推送所有受影响的 Goal 及其最新 stats

### 级联完成（Cascade Completion）

当所有子 Goals 都完成时，自动完成父 Goal：
1. 检测子 Goals 全部 `status == "completed"`
2. 自动设置父 Goal 的 `status = "completed"`
3. 在 `goal_updated` 事件的 `affected_goals` 中包含级联完成的父节点

**实现**：`agent/trace/websocket.py`

---

## Sub-Trace 工具

### explore 工具

并行探索多个方向：

```python
from agent.goal.explore import explore_tool

result = await explore_tool(
    current_trace_id="main_trace_id",
    current_goal_id="3",
    branches=["JWT 方案", "Session 方案"],
    store=store,
    run_agent=run_agent_func
)
```

- 为每个 branch 创建独立的 Sub-Trace
- 并行执行所有 Sub-Traces
- 汇总结果返回

### delegate 工具

将大任务委托给独立 Sub-Agent：

```python
from agent.goal.delegate import delegate_tool

result = await delegate_tool(
    current_trace_id="main_trace_id",
    current_goal_id="3",
    task="实现用户登录功能",
    store=store,
    run_agent=run_agent_func
)
```

- 创建单个 Sub-Trace，拥有完整权限
- 执行任务并返回结果

---

## 使用场景

### Agent 执行时记录

```python
from agent import AgentRunner
from agent.trace import FileSystemTraceStore

store = FileSystemTraceStore(base_path=".trace")
runner = AgentRunner(trace_store=store, llm_call=my_llm_fn)

async for event in runner.run(task="探索代码库"):
    print(event)  # Trace 或 Message
```

### 查询 Sub-Traces

```python
# 获取主 Trace 的所有 Sub-Traces
all_traces = await store.list_traces(limit=1000)
sub_traces = [t for t in all_traces if t.parent_trace_id == main_trace_id]
```

---

## 相关文档

- [frontend/API.md](../frontend/API.md) - 前端对接 API 文档
- [docs/context-management.md](./context-management.md) - Context 管理完整设计
- [agent/goal/models.py](../agent/goal/models.py) - GoalTree 模型定义
- [docs/REFACTOR_SUMMARY.md](./REFACTOR_SUMMARY.md) - 重构总结
