155 lines
6.9 KiB
Markdown
155 lines
6.9 KiB
Markdown
|
|
---
|
|||
|
|
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 供给稳定形态”一起纳入运行时模型。
|