交接架构设计
当一次会话结束、下一次会话启动,智能体如何“接住“上一次的工作?本文系统讲解跨会话 Handoff 的三组痛点、三种主流机制和混合式四层架构,并以 MANIFEST.json(清单文件) 为核心给出可落地的分阶段实施路径。
问题陈述:跨会话交接的三组核心痛点
智能体在单次会话内表现优秀,但跨会话协作时会出现三类问题。
痛点一:会话信息丢失。 OpenCode 已有 /handoff 命令和 DCP compress 工具,但交接依赖智能体自觉输出摘要,缺少强制 Schema(模式) 验证。新会话启动时,要么全量读取历史(Token(令牌) 爆炸),要么只读摘要(关键决策丢失)。某生产项目(代号 Scorpius,下文简称 Scorpius)的实践显示,文件式交接若缺少 JSON schema 验证和文件锁,current.md 会成为单点故障,并发会话还会互相覆盖。
痛点二:无持久化记忆,重复劳动。 当前会话管理依赖文件式 AGENTS.md 和上下文压缩,所有上下文都是临时的。一个 70K Token 的项目上下文,若不做记忆抽取,每次新会话都要重新加载,成本叠加严重——智能体反复理解项目结构、用户偏好、历史决策。
痛点三:上下文膨胀与检索矛盾。 随着对话轮次增加,上下文窗口快速膨胀,输出质量渐进退化。实践中观察到一个关键反差:被动上下文(AGENTS.md 等启动时加载的文件)几乎总被智能体使用,而主动工具(MCP(模型上下文协议) 记忆检索)的调用率明显偏低——智能体不会主动调用记忆工具,除非规则强制路由。这一观察与业界“被动注入优于主动调用“的共识一致。
这三组痛点层层递进:第一组是信息丢失(交接质量差),第二组是成本浪费(重复劳动),第三组是机制失效(该用的工具不用)。三者叠加的结果是——智能体单次会话很强,跨会话就“失忆“,团队不得不在每个新会话重新喂上下文。
这组数据决定了本文的核心设计取向:Handoff 必须设计为被动加载,而非依赖智能体自觉。
三种 Handoff 机制对比
业界主流的会话交接机制可归为三类。
文件式交接:以 Scorpius 的 .handoff/current.md 模式为代表,配合 archive/ 归档旧文件。本书 多 Agent(智能体)协作 中的 WORKFLOW_STATE.md 也属此类——通过文件而非对话历史在 Agent 间传递状态。优点是可审计、可提交 Git、跨会话可恢复;缺点是无 schema 约束时格式漂移,并发会话容易互相覆盖。文件式交接的另一个陷阱是“全量读取“——智能体为了不遗漏信息,倾向于把 archive/ 下所有历史都读进来,结果 Token 瞬间冲到 70K+,反而触发上下文压缩。
命令式交接:OpenCode 的 /handoff 命令配合 DCP compress 工具,在会话结束时压缩上下文、生成摘要,新会话基于摘要启动。优点是 OpenCode 内置、无需重建;缺点是依赖智能体合规调用,且摘要质量参差。/handoff 的本质是“让智能体自己写自己的离任交接“——如果智能体这一轮表现不好,交接质量也跟着差。DCP compress 工具的详细用法见 上下文压缩与Token 预算。
Hook 被动加载:Claude Code 的 SessionStart / PreCompact Hook 模式——会话启动或上下文即将压缩时,自动 cat 加载 CLAUDE.md 等“唤醒文件“。OpenCode 的等价物是 Plugin 生命周期的 session.created / experimental.session.compacting 钩子(详见 记忆系统设计)。优点是触发率 100%、不依赖智能体自觉;缺点是 Hook 机制本身需要宿主平台支持,且 Hook 触发的文件加载是“全量注入“——文件多大就吃多少 Token,缺少选择性阅读能力。
| 方案 | 核心机制 | Token 效率 | 可靠性 | 集成度 |
|---|---|---|---|---|
| 文件式 | current.md + archive/ | 中(全量读取) | 中(无 schema/锁) | 中 |
| 命令式 | /handoff + DCP 压缩 | 中 | 中(依赖合规) | 高(OpenCode 内置) |
| Hook 被动加载 | session.created / experimental.session.compacting(OpenCode)或 SessionStart / PreCompact(Claude Code) | 高(被动加载) | 高(自动触发) | 中(需 Hook 机制) |
关键洞察:被动上下文几乎总被使用,而主动工具调用率明显偏低。这意味着 Handoff 不能依赖智能体主动调用——必须设计为被动加载,把交接信息像 AGENTS.md 一样“钉“在启动上下文里。
混合式四层架构
吸取三种机制各自的长板,可构成混合式四层架构。设计规格详见 docs/planning/specs/agent-handoff-memory-spec.md。
| 层级 | 机制 | 存储位置 | 解决的问题 |
|---|---|---|---|
| L1 文件式交接 | .handoff/MANIFEST.json + current.md + archive/ | Git 仓库 | 结构化交接、版本可追溯 |
| L2 选择性阅读 | MANIFEST 声明每文件摘要 + Token 估算,按需读取 | 同 L1 | Token 爆炸(70K → 1.5K) |
| L3 Schema 验证 | JSON schema 强制校验 + 文件锁(flock)防并发 | pre-commit hook | 格式错误、并发覆盖 |
| L4 Hook 被动加载 | 会话启动自动 cat MANIFEST + 压缩前紧急 dump | OpenCode Plugin 钩子(session.created / experimental.session.compacting)或 Claude Code Hook(SessionStart / PreCompact) | 智能体不主动调用(被动加载覆盖) |
下图展示四层架构的数据流,从会话启动到结束的完整闭环:
flowchart TB
U[用户启动新会话] --> H1["L4: SessionStart Hook"]
H1 --> M["L1: MANIFEST.json"]
H2["L4: PreCompact Hook"] --> M
M --> V["L3: JSON schema 校验"]
V --> S["L2: 选择性阅读<br/>按 priority 读取"]
S --> C["L1: current.md"]
F["L3: flock 文件锁"] -.-> C
C --> Agent["Agent 携带 ≤1.5K Token 启动"]
Agent -->|会话结束| W["L1: /handoff 命令"]
W --> M
classDef agent fill:#4A90D9,stroke:#333,color:#fff
classDef workflow fill:#FF9F43,stroke:#333,color:#fff
classDef mcp fill:#A66CFF,stroke:#333,color:#fff
class M,C,S,V,F mcp
class H1,H2,W workflow
class Agent agent
图中紫色节点表示外部状态存储(含 MANIFEST.json、current.md 等文件与 schema 校验/文件锁机制),非严格意义上的 MCP 服务器。这些文件归类为“外部存储“是因为它们独立于智能体上下文存在,由 Hook 或 /handoff 命令被动读写。
四层各自独立可用,可逐步叠加:先有 L1 就能跑,加 L2 解决 Token 问题,加 L3 解决可靠性,加 L4 解决使用率。
四层如何协作:一次完整的会话交接闭环是这样的——用户启动新会话时,L4 的会话启动 Hook(OpenCode 的 session.created 或 Claude Code 的 SessionStart)被动触发,读取 L1 的 MANIFEST.json;L3 的 schema 校验拦截格式错误的 MANIFEST,flock 文件锁防止并发会话同时写入;L2 根据 MANIFEST 中每个文件的 priority 和 tokens 字段,只加载 required 文件,把启动 Token 控制在 1.5K 以内;智能体携带精简上下文开始工作。会话结束时,/handoff 命令生成新的 MANIFEST 和 current.md,旧文件归档到 archive/,完成一次 Epoch 切换。整个闭环中,智能体不需要“记得“调用交接工具——Hook 在启动和压缩两个时机自动介入,把被动加载的优势发挥到最大。
MANIFEST.json 核心结构
MANIFEST.json 是整个架构的“目录索引“——智能体启动时先读它,再决定读哪些子文件。核心结构如下:
{
"session_id": "uuid-2026-07-12-001",
"created_at": "2026-07-12T10:30:00Z",
"goal": "为登录模块添加 OAuth 支持",
"files": [
{ "path": "current.md", "summary": "当前进展:API 已实现,前端待联调", "tokens": 800, "priority": "required" },
{ "path": "decisions.md", "summary": "关键决策:选择 Session+Redis 而非 JWT", "tokens": 300, "priority": "optional" },
{ "path": "next-actions.md", "summary": "下一步:完成 OAuth 回调与错误处理", "tokens": 200, "priority": "required" }
],
"total_tokens": 1300,
"schema_version": "1.0"
}
字段要点:
priority分required/optional两档。L2 选择性阅读时只强制加载required,optional按需扩展。tokens是每文件的 Token 估算,用于 L2 决策“读了会不会爆“。total_tokens是所有required文件之和,目标控制在 ≤1.5K——这是会话启动的“过路费“。schema_version用于 L3 校验,schema 升级时旧 MANIFEST 会被识别并提示迁移。
Context Epoch:上下文纪元
Handoff 不仅是文件传递,更是上下文的“改朝换代“。引入 Context Epoch(上下文纪元) 概念,将每次交接视为一次受控的纪元切换,遵循三条规则:
- 不可变基线:每个 Epoch 的基线上下文(MANIFEST + required 文件)一旦写入就不可修改。需要更新时新开一个 Epoch,旧文件归档到
archive/,而非原地覆盖。 - 安全转换边界:Epoch 切换发生在
/handoff命令触发的那一刻——这是唯一允许重置上下文的时机。会话中途不切换 Epoch,避免中途丢失上下文。 - 显式状态替换:新会话启动时,Hook 加载的 MANIFEST 内容替换而非追加到上下文——避免新旧 Epoch 内容混淆。实现上可通过 Hook 在
session.created时清空旧 Epoch 文件再写入新 MANIFEST 来近似替换语义(OpenCode 当前上下文是累加模式,原生“替换“语义需通过文件管理间接实现)。
这三条规则把“会话交接“从模糊的“接着上次干“变成可审计的状态机:每个 Epoch 有明确 ID(session_id)、明确边界(created_at)、明确基线(files 列表)。
举个具体场景:会话 A 完成了 OAuth 登录的 API 层,触发 /handoff 生成 Epoch-001(current.md 记录“API 已实现“)。会话 B 启动时读取 Epoch-001,只加载 required 文件(约 800 Token),开始做前端联调。会话 B 结束时触发 /handoff 生成 Epoch-002,Epoch-001 的文件归档到 archive/epoch-001/。如果会话 B 出了问题需要回滚,只需让新会话读取 Epoch-001 而非 Epoch-002——这就是不可变基线的价值:历史不是负担,是安全网。
实施建议:分阶段落地
不要一次性上四层架构——按价值递进落地:
Phase 1:OpenCode /handoff 基础。直接用内置命令生成 current.md,验证交接流程能跑通。此时已是 L1 文件式交接的雏形。
Phase 2:引入 MANIFEST.json。在 /handoff 输出基础上加 MANIFEST 索引,智能体启动时先读 MANIFEST 再选择性加载。Token 占用从 70K 级降到 1.5K 级。
Phase 3:加 Schema 验证与文件锁。定义 JSON schema,pre-commit hook 强制校验;为 current.md 加 flock 文件锁,杜绝并发会话互相覆盖。
Phase 4:接入 Hook 被动加载。配置会话启动 Hook(OpenCode 的 session.created 或 Claude Code 的 SessionStart)自动 cat MANIFEST,压缩前 Hook(OpenCode 的 experimental.session.compacting 或 Claude Code 的 PreCompact)在上下文即将压缩时紧急 dump 当前进展。至此被动加载覆盖到启动和压缩两个关键时机。
每个 Phase 都可独立交付、可演示、可回滚。Phase 1 失败不影响现有流程,Phase 4 失败可降级回 Phase 3。这种渐进式落地避免了“一步到位“的陷阱——团队可以在每个 Phase 验证实际效果再决定是否继续投入,而不是把四层架构一次性堆上去才发现某一层水土不服。
常见反模式
依赖智能体自觉输出摘要
现象:/handoff 命令执行了,但生成的 current.md 内容空洞、丢失关键决策,新会话还是从零开始。
原因:把摘要质量交给智能体“自觉“——没有 schema 约束字段,没有 priority 标记,没有验证环节。
对策:L3 的 JSON schema 强制要求 goal / files / decisions 等字段非空;L1 的 archive/ 保留历史,可对比摘要质量并迭代提示词。
全量读取历史导致 Token 爆炸
现象:新会话启动时把 archive/ 下所有历史 current.md 都读进来“为了完整理解上下文“,Token 瞬间冲到 70K+。
原因:跳过了 L2 选择性阅读,把“可追溯“误用为“全加载“。
对策:MANIFEST 的 priority 字段是硬约束——required 文件必读,optional 文件按需。archive/ 仅供人工审计或显式检索,启动时一律不加载。
无 Schema 验证导致并发覆盖
现象:两个会话同时 /handoff,后写入的 current.md 覆盖了前一个,前一个会话的进展全部丢失。
原因:L1 文件式交接缺少 L3 的文件锁和 schema 校验,current.md 是单点故障。
对策:flock 文件锁串行化写入;schema 校验拦截格式错误的 MANIFEST;archive/ 冗余保留每次交接的快照,即使覆盖也能从归档恢复。
关联章节
- → 多 Agent(智能体)协作(WORKFLOW_STATE.md 文件交接模式,本文 L1 的同源实践)
- → 上下文压缩与Token 预算(DCP
compress工具,本文命令式交接的底层支撑) - → 记忆系统设计(MCP 记忆服务器方案,与 Handoff 互补的长效记忆)