Session 不是聊天记录:把执行单元工程化
上一节讨论了记忆工程,但记忆本身还不是完整答案。你可以把知识保存下来,却仍然缺少一个关键抽象:到底什么东西被保存、被唤醒、被中断、被迁移?如果这个问题没有回答,所谓"长期运行"最后还是退化成一堆零散的 transcript、临时文件和人工约定。
在 Harness Engineering 里,真正需要被工程化的不是"聊天记录",而是 Session。模型像 CPU,Harness 像操作系统,而 Session 更接近进程。它不是一组 messages 的数组,而是一个完整的执行单元:当前目标、上下文状态、正在使用的工具、绑定的文件、阶段性产出、运行中的约束,都应该被封装在这个单元里。
一个最小可用的 Session 至少要包含几类信息。第一类是上下文状态:system prompt、conversation history、working memory。第二类是关联资源:它正在读写哪些文件、依赖哪些数据库或 MCP 服务、可以调用哪些工具。第三类是元数据:名称、优先级、创建时间、最后活跃时间、所属 Agent。第四类是生命周期状态:running、suspended、sleeping、terminated。只有这些信息被放在一起,Session 才称得上是可以恢复、可以调度、可以治理的对象。
这样定义 Session,一个重要区别就清楚了:Session 不等于 Agent。Agent 是角色或身份,Session 是这个角色手上的具体工作单元。一个 HR Agent 可以同时拥有"每月算工资"、"招聘筛选"、"社保申报"三个 Session。它们属于同一个 Agent,却应该有完全不同的上下文和边界。把这些事混在一个超长会话里,只会让上下文互相污染;把它们拆成专用 Session,才有可能真正控制复杂度。
这也是为什么 Session 是解决上下文爆炸的关键抽象。很多团队的第一反应是给 Agent 更大的上下文窗口,把更多资料塞进去。但真正可扩展的做法不是无限扩容,而是把工作切成多个专用且独立的 Session。一个 Session 只服务一个清晰目标,只绑定完成这个目标所需的最小上下文。HR 的薪资 Session 绑定薪资表、社保基数、个税规则和上月结果,每个月一号被唤醒,算完挂起,等待下次激活。它不需要知道招聘面试安排,也不应该被这些信息干扰。
一旦把 Session 当成执行单元,围绕它就会自然长出一组操作原语。最基本的是 save、load、compress,让 Session 可以跨会话存活。更进一步是 activate、suspend、interrupt、cancel,让 Session 能被调度。再进一步是 fork、merge、send、migrate,让 Session 能分叉 side quest、跨模型迁移、彼此通信。这些操作不一定一开始就全部实现,但它们给 Harness 提供了一套清晰的系统调用表,而不是继续依赖 ad hoc 的脚本和人工手势。
Session 还有一个经常被忽略但很重要的维度:依赖关系。一个 Session 不只依赖 prompt,也依赖周边环境。如果它绑定的 spec、代码目录或配置文件发生变化,Session 可能就应该被重新激活。这样一来,Heartbeat 就不只是机械轮询,而是向事件驱动演进。一个 API 文档 Session 可以在 src/ 目录有新提交后被唤醒,一个薪资 Session 可以在社保基数表更新后自动重新计算。Session 从"被动存档"变成了"可响应的工作单元"。
当然,这个类比不是完美的。操作系统里的进程是确定性的,Session 不是;进程的状态可以精确快照,Session 的压缩和恢复往往是有损的;两个进程 merge 没有语义问题,两个 Session merge 却可能出现观点冲突。但这不构成放弃抽象的理由,只意味着 Session 的系统调用必须比 POSIX 更保守、更显式。第一版只做最有价值的操作,远比一开始就追求完整虚拟机式迁移更现实。
从这里往下,自然就进入下一章的主题了。多 Agent 协作并不只是"多开几个聊天窗口",而是多个 Agent 各自拥有一组 Session,再通过隔离、契约和集成机制协同工作。没有 Session 这个中间层,多 Agent 很快就会退化成一堆难以调度、难以恢复、难以治理的临时对话。