Appearance
上下文记忆与会话状态
Agent 的“记忆”有四个不同层次:会话业务状态、LLM canonical messages、执行摘要/工具结果、JSONL/checkpoint 持久化。把它们都称为 history 会导致恢复、压缩和分支实现出错。
1. ChatSession:业务会话容器
ChatSession 固定以下身份和生命周期字段:
| 字段 | 说明 |
|---|---|
sessionId、agentId、tenantId、userId | 访问隔离和路由键 |
roomId、sandboxWorkspace | 文件房间及当前相对工作目录 |
currentBranchId、currentLeafEntryId | JSONL 事件树分支和叶子 |
status | ACTIVE、PAUSED、ENDED、EXPIRED |
messages、context | 页面/历史投影和业务上下文,不等同于 LoopRuntime 消息 |
loopCount、maxLoops | 会话级轮数保护 |
datasetList、datasetsLoaded | Dataset 缓存 |
lastActiveTime、expireMinutes | 过期判断 |
isActive() 同时检查状态为 ACTIVE 和时间未过期。暂停的 session 不能被普通用户输入直接当作新一轮运行;应通过 resume 或明确结束后新建分支。
2. TaskExecutionState:图状态边界
子图可序列化字段按四组理解:
| 组 | 关键字段 | 写入节点 |
|---|---|---|
| 输入 | agentConfig、llmModelConfig、datasetList、userInput、userIntent | 主图桥接/init |
| 计划 | taskPlanText、taskTodoList、currentTask | planning/acting |
| 循环 | iterationCount、finishReason、loopSignal、lastIterationResult | reasoning/refining |
| 保护 | consecutiveFailures、replanCount、contextCompressionCount、hitl*、error | refining/memory/acting |
| 上下文 | executionHistoryText、toolResultStore、messages、runtimeMessages | acting/refining/checkpoint |
其中 executionHistoryText、toolResultStore、messages 使用 appender reducer,节点返回增量会追加;runtimeMessages 是恢复时可重新构造 LangChain4j 消息的 canonical DTO 列表。新增字段要同步 SCHEMA、serializer、CheckpointStateCodec、恢复校验和测试。
3. LoopRuntime:不可直接持久化的运行对象
LoopInitNode 为当前 session 创建 LoopRuntime,包含 ChatModel、工具规格、List<ChatMessage>、TodoManager、ToolResultStore、VFS/Sandbox 句柄、active Skill 和 StreamHandler。模型实例、线程、网络连接和 provider client 不写入 checkpoint。
恢复过程先从 runtimeMessages 反序列化 User/System/AI/Tool 消息,再按配置重新创建 ChatModel、工具和 workspace。只恢复 executionHistoryText 会让模型缺少工具调用配对消息;只恢复内存对象又无法跨进程。
4. 多层持久化
ChatSessionManager 读取顺序受 agent.session.persistence.mode 影响:
| 模式 | 语义 |
|---|---|
MIRROR | 传统 DB/缓存为常规来源,同时镜像 JSONL |
PRIMARY | 优先从 JSONL 恢复,传统存储作为回退/投影 |
JSONL_ONLY | 不依赖传统会话表作为来源 |
L1 是 JVM 内存,L2 通常是 Redis,L3 是数据库或 JSONL 恢复。容器重建时必须挂载 agent.session.persistence.base-dir,否则会话、checkpoint、blob 和轨迹会丢失。
JSONL 事件包含 session header、用户 turn、节点/工具/模型事件、运行控制、分支、压缩和 Sandbox 生命周期。大 payload 按 inlinePayloadMaxBytes 等阈值写入 blob,事件只保存引用。
5. 检查点恢复
checkpoint 文档至少包含 schemaVersion、checkpointId、session/branch/turn/run、graphVersion、current/next node、TaskState snapshot、runtime snapshot 和配置摘要。恢复顺序:
text
读取 session header
-> SessionLease(当前实现为 JVM 内存租约)
-> 校验租户/Agent/branch/schema/graph
-> restore TaskExecutionState
-> 校验 runtime surface digest 和 toolset hash
-> restore canonical messages
-> 重建 LoopRuntime/Sandbox
-> 从 nextNode 继续 graph工具配置、权限、消息投影或图版本变化时进入 RECOVERY_BLOCKED,不能静默清空状态重跑。AgentCheckpointStore 按 session/run 目录写入,保留数量由 checkpoint keepLast 控制。
6. 上下文压缩
LoopReasoningNode 会使用真实 input token 预判压缩,LoopMemoryNode 也处理 FinishReason.LENGTH。策略分档:
| 使用率 | 处理 |
|---|---|
<50% | 不压缩 |
50%-80% | 保留最近三个工具全文,旧结果变指针+预览 |
80%-95% | 仅保留最近一个工具全文,其余占位 |
>=95% | LLM 硬压缩历史消息 |
硬压缩最多两次;耗尽后尝试重规划,仍失败则 FAILED。完整工具结果通过 READ_TOOL_RESULT(toolCallId, offset, limit) 延迟读取,而不是重新把全文塞回 prompt。压缩事件会记录原消息数、新消息数、摘要和被归档的工具调用 ID。
7. ChatMemory 与分支
页面对话历史由 AiChatHistoryChatMemoryStore、MySQL/MapDB 实现和 JSONL projection 协作;它解决的是多轮展示和下一轮输入,不等于 Agentic Loop 的工具消息列表。分支通过 JSONL entry tree 的 branchId/currentLeafEntryId 管理,创建分支只改变后续历史投影,不应修改原分支已经完成的工具结果。
8. 恢复排障
| 现象 | 首先检查 |
|---|---|
| session 找不到 | L1/L2/L3 读取顺序、tenant/user、JSONL header |
| 模型看到上下文不完整 | runtimeMessages、surface digest、压缩事件 |
| 工具重复执行 | ToolExecutionJournal、toolCallId、runId |
| 恢复被阻止 | schema/graph/toolset hash、分支和权限 |
| 文件结果消失 | Sandbox checkpoint、workspace binding、blob 卷 |
| 同一 session 并发恢复 | SessionLease;多实例需共享租约实现 |