---
title: Session Tree 运行时
created: 2026-04-21
updated: 2026-04-26
type: concept
tags: [runtime, agent, backend, database]
sources: [packages/backend/src/modules/netaclaw/session-tree/provider.ts, packages/backend/src/modules/netaclaw/session-tree/types.ts, packages/backend/src/modules/netaclaw/session-tree/snapshot.ts, packages/backend/src/modules/netaclaw/session-tree/context_builder.ts, packages/backend/src/modules/netaclaw/session-tree/mysql_provider.ts, packages/backend/src/modules/netaclaw/session-tree/file_provider.ts, packages/backend/src/modules/netaclaw/gateway/session.ts, packages/frontend/src/modules/agent/store/chat.ts]
---

# Session Tree 运行时

Session Tree 是 NetaClaw 新 Agent runtime 的会话状态模型。它把对话从线性消息列表升级为“节点树 + 当前叶子 + 活动路径 + 运行时上下文”的结构，使刷新恢复、分支、压缩、子 Agent 批次、标签、模型切换都能进入同一套持久化和投影机制。

它是 [[agent-runtime]]、[[context-compaction]]、[[subagent-session]] 和 [[frontend-architecture]] 的共同底座。

## Provider 抽象

`SessionTreeProvider` 定义统一接口，当前有 file 和 mysql 两种 provider。

主要能力包括：

- `createSession`
- `getSession`
- `updateSession`
- `deleteSession`
- `listEntries`
- `appendEntry`
- `appendMessage`
- `appendThinkingLevelChange`
- `appendModelChange`
- `appendCompaction`
- `appendBranchSummary`
- `appendLabelChange`
- `appendSessionInfo`
- `updateEntry`
- `switchLeaf`
- `resetLeaf`
- `createBranchedSession`
- `getActivePath`
- `getSnapshot`

这层抽象让运行时不直接绑定 MySQL 或本地文件，也为未来多 workspace、本地优先存储、导入导出保留空间。

## Snapshot 结构

`SessionTreeSnapshot` 是前后端对齐的核心投影：

| 字段 | 含义 |
| --- | --- |
| `session` | 会话元信息，包括 provider、rootEntryId、leafEntryId、cwd、agentId 等 |
| `entries` | 当前会话所有树节点 |
| `activePath` | 从 root 到 leaf 的当前上下文路径 |
| `childrenByParentId` | 前端重建树结构所需的父子索引 |
| `labelsByEntryId` | 节点标签状态 |
| `runtimeContext` | 供模型调用使用的消息、thinking level、模型引用 |

前端刷新恢复应优先加载 snapshot，而不是依赖 localStorage 保存完整聊天记录。

## Entry 类型

当前树节点类型包括：

| 类型 | 用途 |
| --- | --- |
| `message` | system/user/assistant/tool 模型消息 |
| `thinking_level_change` | 会话内 thinking level 切换 |
| `model_change` | 会话内模型切换 |
| `compaction` | 压缩摘要节点，详见 [[context-compaction]] |
| `branch_summary` | 分支摘要 |
| `custom` | 不直接展示的自定义数据 |
| `custom_message` | 可展示的自定义消息 |
| `label` | 对目标节点设置或清除标签 |
| `session_info` | 会话信息变更 |
| `subagent_batch` | 子 Agent 批次节点，详见 [[subagent-session]] |
| `subagent_result` | 子 Agent 批次结果节点 |

## Active Path 与上下文构建

模型上下文不再等于“所有历史消息”。运行时会从当前 `leafEntryId` 回溯得到 `activePath`，再由 `context_builder.ts` 转成 `runtimeContext.messages`。

这带来几个结果：

- 分支切换只需要切换 leaf。
- 压缩摘要可以替代被压缩的历史段。
- 子 Agent、分支摘要、custom message 可以参与展示，但不一定进入模型上下文。
- thinking/model 变更可以沿路径生效。

## 持久化位置

Session Tree 支持两类持久化：

- MySQL provider：适合生产和多端共享。
- File provider：适合本地优先、单机 workspace、调试和导入导出。

浏览器 localStorage 只保存最近 session/agent 之类的轻量指针，不是对话历史主存储。

## 删除语义

Session Tree 删除必须按会话实际所属的 provider 执行：

- 前端 `chat.ts` 删除请求优先携带会话列表中该 session 的 `agentId`。
- 后端 `gateway/session.ts` 在删除前先从 `netaclaw_agent_session` 反查 `sessionId` 所属 `agentId`，再解析该 Agent 的 session backend。
- MySQL provider 删除 `netaclaw_agent_session_entry` 和 `netaclaw_agent_session`；file provider 删除对应 JSONL 文件。
- 随后后端再清理 legacy session/message、subagent_session 和 session-tree 兼容表记录。

这避免了 MySQL 会话在当前选中 Agent 不一致、或前端缺少 `agentId` 时走默认 file provider，导致“点击删除没有效果”。

## 前端消费

`packages/frontend/src/modules/agent/store/chat.ts` 负责消费 snapshot：

- `sessionMeta` 保存会话元信息。
- `entries` 和 `entryById` 保存节点。
- `childrenByParentId` 支持树结构恢复。
- `activePathIds` 控制当前可见路径。
- `visibleEntries` 将树节点投影成对话 UI。
- `subagentRuntimeByBatchId` 和 `toolRuntimeRoutesByBatchId` 记录子 Agent 工具路由展示数据。

对话页滚动、刷新恢复、历史批次展示都应该围绕这些状态实现。

## 相关页面

- [[agent-runtime]]
- [[subagent-session]]
- [[context-compaction]]
- [[frontend-architecture]]
- [[websocket-gateway]]
- [[netaclaw-module]]
- [[frontend-architecture]]

## 2026-04-22 Subagent Result Metadata

`subagent_result` carries durable metadata needed to replay subagent execution after refresh.

- `metadata.processEvents`: normalized worker event timeline for UI replay.
- `metadata.evidenceSummaries`: structured evidence projection built from subagent tool results.
- `metadata.toolRuntimeRoutes`: tool routing diagnostics for the delegated batch.

The frontend store should project these fields into dedicated maps keyed by entry id, rather than letting view components parse raw metadata directly.

## 2026-04-23 Continue-From-Entry And Projection Contract

Session Tree 现在不仅负责“存什么”，还决定“从哪里继续对话”：

- 前端 `chat.ts` 维护 `selectedEntryId`、`switchingLeafEntryId`、`pendingLeafConfirmation`，允许用户选中任意节点后继续发送，而不是只能沿当前 leaf 末尾追加。
- 如果选中的是普通 user 节点，继续发送会基于它的父节点重放该条 user message，形成新的分支。
- 如果选中的是 `branch_summary`、`compaction` 或非当前 leaf 节点，前端会先切换 leaf，再沿该路径继续。
- 这些交互都以 `session.leafEntryId`、`activePath` 和 snapshot 中的树结构为依据，不依赖前端自行缓存一份线性历史。

对子 Agent 相关节点，当前 snapshot 契约也更明确：

- `subagent_result.metadata.subagentProjection` 是会话树边界输出的 canonical projection。
- projection 内的 `diagnostics.selectedSource`、`inputSources`、`fallbackUsed` 用来说明当前展示究竟来自 `subagent_result`、旧 tool message，还是历史 metadata fallback。
- 这让 Session Tree 不再只是原始数据容器，而是把“兼容历史记录”和“为当前 UI 供给稳定形态”一起纳入运行时模型。
