# Agent Execution API - 前端对接文档

> 版本：v4.0
> 更新日期：2026-02-04

---

## 概览

本 API 提供 Agent 执行过程的实时可视化能力：
- **REST API** - 查询 Trace 和 GoalTree
- **WebSocket** - 实时推送 Goal/Message 更新（支持断线续传）

**核心概念**：
- **Trace** - 一次完整的 Agent 执行（主 Agent 和 Sub-Agent 都是 Trace）
- **GoalTree** - 每个 Trace 的目标树
- **Goal** - 一个目标节点，包含 self_stats（自身统计）和 cumulative_stats（含后代统计）
- **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 采用层级命名：
  - 主 Trace: UUID (如 2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d)
  - Sub-Trace: {parent}@{mode}-{timestamp}-{seq} (如 2f8d3a1c...@explore-20260204220012-001)
```

**数据结构**：
```
后端存储：
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 完成后的里程碑
- 边 = 相邻节点之间的执行过程
- Sub-Trace 可以折叠（显示为单个节点）或展开（显示内部 Goals）
- 边的统计数据从 target Goal 的 stats 获取

---

## REST API

### 基础信息

- **Base URL**: `http://43.106.118.91:8000`
- **Content-Type**: `application/json`

---

### 1. 列出 Traces

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

**查询参数**：
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `status` | string | 否 | 过滤状态：`running` / `completed` / `failed` |
| `mode` | string | 否 | 过滤模式：`call` / `agent` |
| `limit` | int | 否 | 返回数量（默认 50，最大 100）|

**响应示例**：
```json
{
  "traces": [
    {
      "trace_id": "abc123",
      "mode": "agent",
      "task": "实现用户认证功能",
      "status": "running",
      "total_messages": 15,
      "total_tokens": 5000,
      "total_cost": 0.05,
      "current_goal_id": "2.1",
      "created_at": "2026-02-04T15:30:00"
    }
  ],
  "total": 1
}
```

---

### 2. 获取 Trace + GoalTree

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

**响应示例 1**（主 Trace，explore 进行中）：
```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,
  "created_at": "2026-02-04T15:30:00",
  "completed_at": null,
  "goal_tree": {
    "mission": "实现用户认证功能",
    "current_id": "2",
    "goals": [
      {
        "id": "1",
        "parent_id": null,
        "type": "normal",
        "description": "分析代码",
        "reason": "了解现有结构",
        "status": "completed",
        "summary": "用户模型在 models/user.py",
        "self_stats": { "message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2" },
        "cumulative_stats": { "message_count": 5, "total_tokens": 2300, "total_cost": 0.03, "preview": "glob → read × 2" }
      },
      {
        "id": "2",
        "parent_id": null,
        "type": "agent_call",
        "description": "并行探索认证方案",
        "reason": "评估不同技术选型",
        "status": "in_progress",
        "agent_call_mode": "explore",
        "sub_trace_ids": [
          {"trace_id": "abc123.A", "mission": "JWT 方案"},
          {"trace_id": "abc123.B", "mission": "Session 方案"}
        ],
        "sub_trace_metadata": 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 }
      },
      {
        "id": "3",
        "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
    }
  }
}
```

**响应示例 2**（主 Trace，explore 已完成并合并）：
```json
{
  "trace_id": "abc123",
  "mode": "agent",
  "task": "实现用户认证功能",
  "status": "running",
  "goal_tree": {
    "mission": "实现用户认证功能",
    "current_id": "3",
    "goals": [
      {
        "id": "1",
        "type": "normal",
        "description": "分析代码",
        "status": "completed",
        "summary": "用户模型在 models/user.py"
      },
      {
        "id": "2",
        "type": "agent_call",
        "description": "并行探索认证方案",
        "status": "completed",
        "agent_call_mode": "explore",
        "sub_trace_ids": [
          {
            "trace_id": "abc123@explore-20260204220012-001",
            "mission": "JWT 方案"
          },
          {
            "trace_id": "abc123@explore-20260204220012-002",
            "mission": "Session 方案"
          }
        ],
        "sub_trace_metadata": {
          "abc123@explore-20260204220012-001": {
            "task": "JWT 方案",
            "status": "completed",
            "summary": "实现完成，使用 JWT token，无状态但 token 较大",
            "last_message": {
              "role": "assistant",
              "description": "生成 JWT token 并返回",
              "content": "JWT 方案实现完成。使用 jsonwebtoken 库生成 token，包含 user_id 和过期时间。优点：无状态，易扩展。缺点：token 较大（约 200 字节），无法主动失效。",
              "created_at": "2026-02-05T10:30:00"
            },
            "stats": {
              "message_count": 8,
              "total_tokens": 4000,
              "total_cost": 0.05
            }
          },
          "abc123@explore-20260204220012-002": {
            "task": "Session 方案",
            "status": "completed",
            "summary": "实现完成，使用 Redis 存储 session，支持主动失效",
            "last_message": {
              "role": "assistant",
              "description": "配置 Redis 并实现 session 管理",
              "content": "Session 方案实现完成。使用 Redis 存储 session，支持过期自动清理和主动失效。优点：token 小（约 32 字节），可主动失效。缺点：需要 Redis，有网络开销。",
              "created_at": "2026-02-05T10:32:00"
            },
            "stats": {
              "message_count": 12,
              "total_tokens": 4000,
              "total_cost": 0.05
            }
          }
        },
        "summary": "探索了 2 个方案，JWT 和 Session 均可行",
        "cumulative_stats": {
          "message_count": 20,
          "total_tokens": 8000,
          "total_cost": 0.10
        }
      },
      {
        "id": "3",
        "type": "normal",
        "description": "完善实现",
        "status": "in_progress"
      }
    ]
  },
  "sub_traces": {
    "abc123@explore-20260204220012-001": { /* ... */ },
    "abc123@explore-20260204220012-002": { /* ... */ }
  }
}
```

---

### 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": "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,
        "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" }
      }
    ]
  },
  "sub_traces": {}
}
```

### 4. 获取 Messages（边详情）

查询指定 Trace 的 Messages（用于查看边的详细执行内容）。

```http
GET /api/traces/abc123/messages?goal_id=1
GET /api/traces/abc123.A/messages?goal_id=2
```

**查询参数**：
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `goal_id` | string | 否 | 过滤指定 Goal 的 Messages（内部 ID，如 "1", "2"）|

**响应示例**：
```json
{
  "trace_id": "abc123",
  "messages": [
    {
      "message_id": "msg-001",
      "trace_id": "abc123",
      "role": "assistant",
      "sequence": 1,
      "goal_id": "1",
      "content": {
        "text": "让我先读取现有的 API 设计...",
        "tool_calls": [
          {
            "id": "call_abc",
            "name": "read_file",
            "arguments": { "path": "api/routes.py" }
          }
        ]
      },
      "description": "让我先读取现有的 API 设计...",
      "tokens": 150,
      "cost": 0.002,
      "created_at": "2026-02-04T15:31:00"
    },
    {
      "message_id": "msg-002",
      "trace_id": "abc123",
      "role": "tool",
      "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"
    }
  ],
  "total": 2
}
```

---

## WebSocket API

### 连接

```javascript
const ws = new WebSocket(
  'ws://43.106.118.91:8000/api/traces/{trace_id}/watch?since_event_id=0'
)
```

**查询参数**：
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `since_event_id` | int | `0` | 从哪个事件 ID 开始。`0` = 补发所有历史 |

---

### 事件类型

#### 1. connected（连接成功）

连接后推送完整 Trace 信息，前端据此初始化 DAG。

```json
{
  "event": "connected",
  "trace_id": "abc123",
  "current_event_id": 15,
  "trace": {
    "trace_id": "abc123",
    "status": "running",
    "goal_tree": {
      "mission": "实现用户认证功能",
      "current_id": "2",
      "goals": [...]
    },
    "sub_traces": {}
  }
}
```

**前端处理**：
```javascript
if (data.event === 'connected') {
  initDAG(data.goal_tree)
  localStorage.setItem('last_event_id', data.current_event_id)
}
```

---

#### 2. goal_added（新增 Goal）

```json
{
  "event": "goal_added",
  "event_id": 16,
  "goal": {
    "id": "6",
    "parent_id": "2",
    "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 }
  },
  "parent_id": "2"
}
```

**前端处理**：
```javascript
if (data.event === 'goal_added') {
  insertGoal(data.goal, data.parent_id)
  regenerateDAG()
}
```

---

#### 3. goal_updated（Goal 状态变化）

包含级联完成场景：当所有子 Goal 完成时，父 Goal 自动 completed。

```json
{
  "event": "goal_updated",
  "event_id": 17,
  "goal_id": "3",
  "updates": {
    "status": "completed",
    "summary": "接口设计完成"
  },
  "affected_goals": [
    {
      "goal_id": "3",
      "cumulative_stats": { "message_count": 3, "total_tokens": 1500, "total_cost": 0.02, "preview": "read → edit" }
    },
    {
      "goal_id": "2",
      "status": "completed",
      "summary": "功能实现完成",
      "cumulative_stats": { "message_count": 8, "total_tokens": 4200, "total_cost": 0.05, "preview": "..." }
    }
  ]
}
```

**前端处理**：
```javascript
if (data.event === 'goal_updated') {
  updateGoal(data.goal_id, data.updates)
  for (const g of data.affected_goals) {
    updateGoalStats(g.goal_id, g)
  }
  regenerateDAG()
}
```

---

#### 4. message_added（新 Message）

后端更新统计后推送，包含受影响的所有 Goals。

```json
{
  "event": "message_added",
  "event_id": 18,
  "message": {
    "message_id": "msg-018",
    "role": "assistant",
    "goal_id": "4",
    "content": { "text": "...", "tool_calls": [...] },
    "tokens": 500,
    "cost": 0.005
  },
  "affected_goals": [
    {
      "goal_id": "4",
      "self_stats": { "message_count": 6, "total_tokens": 3200, "total_cost": 0.035, "preview": "edit × 3 → bash × 2" },
      "cumulative_stats": { "message_count": 6, "total_tokens": 3200, "total_cost": 0.035, "preview": "edit × 3 → bash × 2" }
    },
    {
      "goal_id": "2",
      "cumulative_stats": { "message_count": 9, "total_tokens": 4700, "total_cost": 0.055, "preview": "read → edit × 4 → bash × 2" }
    }
  ]
}
```

**说明**：
- `affected_goals[0]` 是直接关联的 Goal，更新 `self_stats` + `cumulative_stats`
- 后续是所有祖先 Goal，仅更新 `cumulative_stats`

**前端处理**：
```javascript
if (data.event === 'message_added') {
  for (const g of data.affected_goals) {
    updateGoalStats(g.goal_id, g)
  }
  // 根据当前展开状态更新对应边
  rerenderEdge(data.message.goal_id)
}
```

---

#### 5. trace_completed（任务完成）

```json
{
  "event": "trace_completed",
  "event_id": 50,
  "trace_id": "abc123",
  "total_messages": 50,
  "total_tokens": 25000,
  "total_cost": 0.25
}
```

**前端处理**：
```javascript
if (data.event === 'trace_completed') {
  markTraceCompleted()
  ws.close()
}
```

---

#### 6. sub_trace_started（Sub-Trace 开始）

explore 或 delegate 工具启动 Sub-Trace 时触发。

```json
{
  "event": "sub_trace_started",
  "event_id": 20,
  "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 === 'sub_trace_started') {
  insertSubTrace(data.parent_trace_id, data.sub_trace)
  regenerateDAG()
}
```

---

#### 7. sub_trace_completed（Sub-Trace 完成）

Sub-Trace 执行完成后触发。

```json
{
  "event": "sub_trace_completed",
  "event_id": 35,
  "trace_id": "abc123.A",
  "parent_trace_id": "abc123",
  "parent_goal_id": "2",
  "summary": "JWT 方案实现完成，无状态但 token 较大",
  "total_messages": 8,
  "total_tokens": 4000,
  "total_cost": 0.05
}
```

**前端处理**：
```javascript
if (data.event === 'sub_trace_completed') {
  updateSubTrace(data.trace_id, {
    status: 'completed',
    summary: data.summary,
    total_messages: data.total_messages,
    total_tokens: data.total_tokens,
    total_cost: data.total_cost
  })
  regenerateDAG()
}
```

---

## 数据模型

### Trace

| 字段 | 类型 | 说明 |
|------|------|------|
| `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 | 成本总和 |
| `created_at` | string | 创建时间（ISO 8601）|
| `completed_at` | string \| null | 完成时间 |

**Trace ID 规则**：
- 主 Trace：标准 UUID（如 "2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d"）
- Sub-Trace：`{parent_uuid}@{mode}-{timestamp}-{seq}`
  - 例如："2f8d3a1c-4b6e-4f9a-8c2d-1e5b7a9f3c4d@explore-20260204220012-001"
  - 使用完整 UUID 避免 ID 冲突
  - timestamp: YYYYMMDDHHmmss 格式
  - seq: 3 位序号（001, 002...）

---

### GoalTree

| 字段 | 类型 | 说明 |
|------|------|------|
| `mission` | string | 总任务描述（来自 Trace.task）|
| `current_id` | string \| null | 当前焦点 Goal 的内部 ID |
| `goals` | Goal[] | 顶层目标列表 |

---

### Goal

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | string | 内部 ID，每个 Trace 独立编号（"1", "2", "3"...）|
| `parent_id` | string \| null | 父 Goal ID（层级关系）|
| `type` | string | `normal` / `agent_call` |
| `description` | string | 目标描述（做什么）|
| `reason` | string | 创建理由（为什么做）|
| `status` | string | `pending` / `in_progress` / `completed` / `abandoned` |
| `summary` | string \| null | 完成/放弃时的总结 |
| `sub_trace_ids` | Array<{trace_id: string, mission: string}> \| null | 启动的 Sub-Trace 信息（仅 agent_call）|
| `agent_call_mode` | string \| null | "explore" / "delegate" / "sequential"（仅 agent_call）|
| `sub_trace_metadata` | object \| null | Sub-Trace 元数据（仅 agent_call，包含最后消息等）|
| `self_stats` | GoalStats | 自身统计 |
| `cumulative_stats` | GoalStats | 累计统计 |

**Goal 类型说明**：

**`normal` Goal**：
- 普通执行目标，对应一系列顺序执行的 Messages
- 可以有子 Goal（通过 `parent_id` 构建层级）

**`agent_call` Goal**：
- 表示启动了 Sub-Agent 的特殊 Goal
- 不直接关联 Messages，而是启动一个或多个独立的 Sub-Trace
- 通过 `sub_trace_ids` 关联启动的所有 Sub-Traces
- 通过 `agent_call_mode` 标记执行模式：
  - `"explore"` - 并行探索：启动多个 Sub-Trace 并行执行，汇总结果后合并
  - `"delegate"` - 单线委托：将大任务委托给单个 Sub-Trace 执行
  - `"sequential"` - 顺序执行：按顺序启动多个 Sub-Trace

**分支探索与合并（`explore` 模式）**：
1. **分支开始**：`agent_call` Goal 同时启动多个 Sub-Trace（如 2-5 个）
2. **并行执行**：各 Sub-Trace 独立运行，有各自的 GoalTree 和 Messages
3. **收集元数据**：执行完成后，收集每个 Sub-Trace 的：
   - 最后一条 assistant 消息（`last_message`）
   - 执行总结（`summary`）
   - 统计信息（`stats`）
4. **合并节点**：`agent_call` Goal 完成时，表示所有分支已合并
   - explore 工具返回文本摘要（给主 Agent 的 LLM 查看）
   - `sub_trace_metadata` 保存详细元数据（给前端和后续决策使用）

**ID 设计**：
- 每个 Trace 内部独立编号（"1", "2", "3"）
- 层级关系通过 `parent_id` 维护
- 显示序号由前端/后端动态生成

---

### SubTraceMetadata（agent_call Goal 专用）

`agent_call` 类型的 Goal 会在完成时填充 `sub_trace_metadata` 字段，格式如下：

```json
{
  "sub_trace_metadata": {
    "abc123@explore-20260204220012-001": {
      "task": "JWT 方案",
      "status": "completed",
      "summary": "实现完成，使用 JWT token",
      "last_message": {
        "role": "assistant",
        "description": "生成 JWT token 并返回",
        "content": "JWT 方案实现完成。使用 jsonwebtoken 库生成 token...",
        "created_at": "2026-02-05T10:30:00"
      },
      "stats": {
        "message_count": 8,
        "total_tokens": 4000,
        "total_cost": 0.05
      }
    },
    "abc123@explore-20260204220012-002": {
      "task": "Session 方案",
      "status": "completed",
      "summary": "实现完成，使用 Redis 存储",
      "last_message": {
        "role": "assistant",
        "description": "配置 Redis 并实现 session 管理",
        "content": "Session 方案实现完成。使用 Redis 存储 session...",
        "created_at": "2026-02-05T10:32:00"
      },
      "stats": {
        "message_count": 12,
        "total_tokens": 4000,
        "total_cost": 0.05
      }
    }
  }
}
```

**字段说明**：
- `task` - Sub-Trace 的任务描述
- `status` - Sub-Trace 的最终状态（completed/failed）
- `summary` - Sub-Trace 的执行总结
  - 优先使用 `run_agent()` 返回的 `summary` 字段（如果有）
  - 否则使用最后一条 assistant 消息的内容（截断至 200 字符）
  - 如果都没有，默认为 "执行完成"
- `last_message` - 最后一条 assistant 消息（内容截断至 500 字符）
  - `role` - 固定为 "assistant"
  - `description` - 消息的简短描述
  - `content` - 消息内容（可能被截断）
  - `created_at` - 消息创建时间
- `stats` - 统计信息（message_count, total_tokens, total_cost）

**用途**：
1. **辅助决策**：主 Agent 可以基于各分支的最终输出，决定是否需要展开查看详细信息
2. **前端展示**：在折叠视图中显示每个分支的关键信息，用户快速判断是否需要展开
3. **调试追踪**：快速了解每个分支的执行结果，无需加载完整 Messages

---

### GoalStats

| 字段 | 类型 | 说明 |
|------|------|------|
| `message_count` | int | 消息数量 |
| `total_tokens` | int | Token 总数 |
| `total_cost` | float | 总成本 |
| `preview` | string \| null | 工具调用摘要（如 `"read → edit × 2"`）|

---

### Message

| 字段 | 类型 | 说明 |
|------|------|------|
| `message_id` | string | 唯一 ID |
| `trace_id` | string | 所属 Trace ID（可能是主 Trace 或 Sub-Trace）|
| `role` | string | `assistant` / `tool` |
| `sequence` | int | 当前 Trace 内的顺序 |
| `goal_id` | string \| null | 关联的 Goal 内部 ID（如 "1", "2"）。**可以为 `null`**（初始阶段）|
| `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 | 创建时间 |

**goal_id 说明**：
- 通常关联到某个 Goal 的内部 ID（如 "1", "2"）
- **可以为 `null`**：在 Trace 开始时，Agent 还没有创建任何 Goal
- 初始阶段的 messages 用于分析任务、创建第一批 Goals

**查询初始阶段 Messages**：
```http
GET /api/traces/{trace_id}/messages?goal_id=_init
```
- 使用特殊值 `_init` 或 `null` 查询 goal_id 为 null 的 messages
- 用于前端展示 START 节点的详情

---

## 前端展示：START 虚拟节点

**设计原则**：诚实反映数据结构，不在后端创建额外节点。

### 概念

在 Trace 开始阶段，Agent 还没有创建任何 Goal，这期间产生的 Messages 的 `goal_id` 为 `null`。前端应该创建一个虚拟的 **START 节点**来展示这些初始阶段的 messages。

### DAG 结构

```
[START] ────────→ [1:分析完成] ──→ [2:设计完成] ──→ [3:实现完成]
   │
   └─ goal_id=null 的 messages
      (分析任务、创建计划等)
```

**START 节点**：
- 类型：虚拟节点（前端创建，后端不存在）
- ID：`"START"` 或 `null`
- 数据：Mission 信息、初始阶段统计

**START → Goal 1 的边**：
- source: `"START"`
- target: 第一个真实 Goal 的 ID
- 数据：goal_id=null 的所有 messages 的聚合统计

### 实现示例

```javascript
// 1. 创建 START 节点
function buildDAG(goalTree, messages) {
  const nodes = []
  const edges = []

  // 创建虚拟 START 节点
  nodes.push({
    id: 'START',
    type: 'start',
    data: {
      label: 'START',
      mission: goalTree.mission,
    }
  })

  // 计算初始阶段的统计
  const initMessages = messages.filter(m => m.goal_id === null)
  const initStats = {
    message_count: initMessages.length,
    total_tokens: initMessages.reduce((sum, m) => sum + (m.tokens || 0), 0),
    total_cost: initMessages.reduce((sum, m) => sum + (m.cost || 0), 0),
    preview: generatePreview(initMessages)
  }

  // 创建 START 到第一个 Goal 的边
  const firstGoal = goalTree.goals.find(g => g.parent_id === null)
  if (firstGoal) {
    edges.push({
      source: 'START',
      target: firstGoal.id,
      data: {
        messages: initMessages,
        stats: initStats
      }
    })
  }

  // 创建其他 goals 的节点和边...
  // ...

  return { nodes, edges }
}
```

### 用户交互

**悬停 START 节点**：
```
┌─────────────────────────────┐
│ START                       │
│ Mission: 实现用户认证系统   │
│                             │
│ 初始阶段统计：              │
│ • 2 messages               │
│ • 500 tokens               │
│ • $0.005                   │
└─────────────────────────────┘
```

**点击 START 节点**：
- 侧边栏显示 Mission 描述
- 显示初始阶段的元信息

**点击 START → Goal 1 边**：
- 展开详情，显示所有 goal_id=null 的 messages
- 内容示例：
  ```
  [assistant] 我需要先分析这个任务...
  [tool: goal] 创建了3个顶层目标
  ```

**查询 API**：
```javascript
// 获取初始阶段的 messages
async function getInitMessages(traceId) {
  const resp = await fetch(`/api/traces/${traceId}/messages?goal_id=_init`)
  const data = await resp.json()
  return data.messages  // goal_id 全部为 null
}
```

### 特殊情况

**情况 1：没有初始阶段 messages**
- 如果 Agent 一启动就创建了 Goal（没有 goal_id=null 的 messages）
- 仍然显示 START 节点，但边的统计为空

**情况 2：所有 messages 都是初始阶段**
- 如果 Agent 还没有创建任何 Goal
- 只显示 START 节点，没有其他节点

```
[START]
  (分析中，3 msgs)
```

### 与编辑功能的集成

用户可以在 START → Goal 1 的边上编辑第一次 goal() 调用的参数：

1. 点击 START → 1 边，展开 messages
2. 找到 goal() 工具调用（通常是第一个或第二个 message）
3. 点击 [编辑] 按钮
4. 修改 add 参数（如修改初始的任务列表）
5. 系统废弃 Goal 1, 2, 3...，重新执行

这样用户就能方便地修改整体计划。

---

## DAG 可视化（前端实现）

后端提供 Trace（包含 GoalTree 和 Sub-Traces），前端负责生成 DAG 视图。

### 核心逻辑

**普通 Goal（子目标）**：
1. 未展开：显示父 Goal
2. 已展开：父 Goal 被子 Goal **替代**（顺序）

**agent_call Goal（Sub-Trace）**：
1. 未展开：显示为单个节点（代表整个 Sub-Trace）
2. 已展开：加载 Sub-Trace 的 GoalTree，显示内部 Goals

### 边数据

从 target 节点的 stats 获取：
- 折叠边（父 Goal）→ `target.cumulative_stats`
- 展开边（子 Goal）→ `target.self_stats`
- Sub-Trace 折叠边 → Sub-Trace 的 total_tokens/total_cost
- Sub-Trace 展开边 → Sub-Trace 内部 Goal 的 stats

### 示例：普通展开（子目标）

```javascript
// GoalTree（扁平列表，通过 parent_id 构建层级）
const goalTree = {
  goals: [
    { id: "1", parent_id: null, description: "分析代码" },
    { id: "2", parent_id: null, description: "实现功能" },
    { id: "3", parent_id: "2", description: "设计接口" },  // 2 的子目标
    { id: "4", parent_id: "2", description: "实现代码" },  // 2 的子目标
    { id: "5", parent_id: null, description: "测试" }
  ]
}

// 构建层级视图
function buildHierarchy(goals) {
  const map = new Map(goals.map(g => [g.id, { ...g, children: [] }]))
  const roots = []
  for (const goal of goals) {
    const node = map.get(goal.id)
    if (goal.parent_id) {
      map.get(goal.parent_id)?.children.push(node)
    } else {
      roots.push(node)
    }
  }
  return roots
}

// 展开状态
const expanded = new Set()  // 空 = 全部折叠

// 生成可见节点序列
function getVisibleGoals(nodes, expanded) {
  const result = []
  for (const node of nodes) {
    if (expanded.has(node.id) && node.children.length > 0) {
      // 展开：递归处理子节点
      result.push(...getVisibleGoals(node.children, expanded))
    } else {
      // 折叠：显示自己
      result.push(node)
    }
  }
  return result
}

// 折叠视图: [1] → [2] → [5]
// 展开 "2" 后: [1] → [3] → [4] → [5]

// 生成边
function generateEdges(visibleGoals) {
  const edges = []
  for (let i = 0; i < visibleGoals.length; i++) {
    const source = i === 0 ? null : visibleGoals[i - 1]
    const target = visibleGoals[i]
    edges.push({
      source: source?.id ?? null,
      target: target.id,
      // 边数据来自 target 的 stats
      // 如果 target 有子节点且未展开，用 cumulative_stats
      // 否则用 self_stats
      stats: hasUnexpandedChildren(target, expanded)
        ? target.cumulative_stats
        : target.self_stats
    })
  }
  return edges
}
```

### 示例：Sub-Trace 展开

```javascript
// 主 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: [
        {"trace_id": "abc123.A", "mission": "JWT 方案"},
        {"trace_id": "abc123.B", "mission": "Session 方案"}
      ] },
      { 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:并行探索] → [3]

// 展开 Sub-Traces 后的视图：
//           ┌→ [abc123.A] ────┐
// [1] ──────┼                 ├──→ [3]
//           └→ [abc123.B] ────┘

// 继续展开 Sub-Trace abc123.A 内部
async function loadSubTrace(traceId) {
  const resp = await fetch(`/api/traces/${traceId}`)
  return await resp.json()  // 返回完整 Trace，含 goal_tree
}

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
```

### 视觉区分

| 边类型 | 说明 | 样式建议 |
|--------|------|---------|
| 普通边 | 顺序执行 | 实线 |
| 分叉边 | explore 分支开始 | 虚线或带标记 |
| 汇合边 | 分支合并到 merge 节点 | 虚线或带标记 |
| 废弃边 | abandoned 分支 | 灰色 |

---

## 断线续传

```javascript
let lastEventId = 0

function connect(traceId) {
  const ws = new WebSocket(
    `ws://43.106.118.91:8000/api/traces/${traceId}/watch?since_event_id=${lastEventId}`
  )

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data)
    if (data.event_id) {
      lastEventId = data.event_id
      localStorage.setItem(`trace_${traceId}_event_id`, lastEventId)
    }
    // 处理事件...
  }

  ws.onclose = () => {
    setTimeout(() => connect(traceId), 3000)
  }
}
```

---

## 错误处理

### HTTP 错误码

| 状态码 | 说明 |
|--------|------|
| 200 | 成功 |
| 404 | Trace 不存在 |
| 400 | 参数错误 |
| 500 | 服务器错误 |

### WebSocket 错误

```json
{
  "event": "error",
  "message": "Too many missed events, please reload via REST API"
}
```

---

## 相关文档

- [Context 管理设计](../docs/context-management.md) - Goal 机制完整设计
- [Trace 模块说明](../docs/trace-api.md) - 后端实现细节
