标签归档:DeepSeekHarness

开发同学在 vibe coding 后,面对代码黑盒应该做什么

最近半年,我们在代码库中看到越来越多这样的提交:一个功能完整的 PR,包含数千行代码,单测覆盖率达到 85% 以上,本地集成测试全绿。然而在提测演示或做方案复盘时,提交代码的工程师无法在不借助模型的情况下,准确描述出某条核心业务链路在异常分支下的具体状态扭转过程。

代码由大模型直接生成,工程师负责提供提示词、粘贴报错、运行命令以及验收功能。这种被称为 vibe coding 的开发模式正在研发团队中快速蔓延。开发人员正在迅速丧失对代码内部实现细节的掌握。

如果一个开发人员完全不需要知道代码内部发生了什么,只要在外部通过输入输出来验证系统,这项工作换成一个不懂技术的产品经理或业务人员,结果并没有本质区别。 当编写代码的动作被模型接管之后,技术人员本身的专业壁垒就会受到直接冲击。

在实际工程推进中,不懂底层原理的使用者在与模型交互时,会迅速遇到一道无法逾越的墙。当系统行为偏离预期,或者出现跨多个子系统的复合型偶发故障时,缺乏系统内部认知的人甚至无法组织出有效的排查提示词。他们只能把控制台的错误堆栈反复扔给模型,陷入尝试、失败、再尝试的低效死循环。

当然,这种情况在模型和 Harness 越来越强的情况下,越来越少了。

当前,判断开发工程师价值的关键指标,在于具体业务子领域是否还需要我们深入理解系统的内部实现,以及需要理解到何种程度。

复杂系统的黑盒沉降

我们原本设想黑盒开发只会停留在低风险的边缘地带。那些生命周期以周计的营销脚本、孤立的数据管道、或者一次性的内部报表工具,被开发者直接当成黑盒丢给模型演化。只要输入和输出符合预期,内部堆叠了多少冗余逻辑,人类完全不需要过问。

这种边界在过去半年里被全面打破。

在真实的研发场景中,复杂的长周期核心系统同样正在被迅速黑盒化。系统的实现逻辑已经开始脱离了我们的掌控。

有些系统已经没有任何一个工程师完整读过底层实现,所有人都在依靠行为验收来驱动迭代。

这一变化的驱动力来自工程吞吐量的极端失衡。过去由一个六人资深团队耗时三个月才能搭建完的复杂状态机,现在借助高阶推理模型,两天内就能生成数万行代码,并配套数千个通过的单元测试与端到端用例。当代码生成的速率超越人类视网膜与大脑工作记忆的物理极限时,人工逐行审查机制就失去了事实上的防御能力。

我们开始被迫接受这种现实。 在面对极其庞大的调用拓扑和复杂的上下文依赖时,我们已经放弃了对抽象语法树的微观控制,转而把整个复杂系统视作一个自组织的、不透明的动力学网络。

这种转变直接剥夺了传统工程学给我们带来的安全感。当不懂内部机理的团队开始掌控这种复杂黑盒系统时,表面上的研发效率提升与潜伏的系统性崩溃风险就绑定在了一起。

演化哲学的工程代价

把软件代码视同生物 DNA 序列,认为系统可以在目标函数的约束下自然演化,是黑盒派的核心立论。生物演化积累了大量的无用突变、历史包袱与内含子序列,依然能在残酷的自然选择中维持机体运转。在很多开发者的设想里,只要外部的行为约束足够严格,内部的代码哪怕堆积成山,系统也能通过模型的自我修剪持续向后演化。

在复杂系统中使用演化哲学,会遭遇物理层面的硬约束。

软件系统与生物体存在本质区别。生物体拥有物理世界施加的绝对法则限制,而软件的目标函数是由人类通过测试套件和契约规范手工定义的。人类工程师编写的测试用例,无论规模多么庞大,都只能覆盖有限的状态空间。

当模型在复杂的业务系统里自我演化时,它会不断寻找使测试用例全绿的阻力最小路径。在处理一个涉及分布式两阶段提交的业务分支时,模型为了修复一个并发竞争的偶发报错,可能会在关键路径上引入一段极其隐蔽的全局读写锁,或者擅自放宽事务隔离级别。从行为验收的角度看,那十几个报错的测试用例确实顺利通过了。

这种微小的局部优化在多轮提示词和版本迭代后,会引发全局架构的雪崩。并发瓶颈从数据库行锁被硬生生搬运到了应用层内存,系统的吞吐量在特定流量脉冲下出现断崖式下跌。由于团队没有人理解这段被模型演化出来的内部调度机制,监控指标只能呈现出 CPU 软中断升高与连接池耗尽,排查人员根本无法把这种宏观症状与某个被模型悄悄修改的同步原语对应起来。

生物演化历经数十亿年,其代价是无数个体的死亡与物种灭绝。在商业软件工程中,任何一次演化走入死胡同,换来的都是真实的资损、核心数据损坏与长达数小时的服务不可用。

重写的大坑与暗知识

当代码产出如此快速时,以前不敢做的重构操作,现在频频出现在现实之中。

并且对于黑盒的代码,当无法维护时,会有人开始直接让 AI 来重写了。

对于极度依赖隐性规则的复杂系统,重写会是一个大坑。

复杂系统的内部实现中,沉淀了海量的暗知识。这些暗知识由线上历史事故、冷门协议的缺陷规避、上下游陈旧系统的怪异行为,以及特定硬件环境下的性能妥协交织而成。在漫长的迭代周期里,这些细节几乎不可能被完整记录在需求文档或提示词工程的上下文中。

当一个承载核心业务的复杂黑盒系统遭遇架构死锁时,工程师试图命令模型从零重写一套全新的系统。模型根据现有的规范文档和接口契约,迅速生成了一个结构干净、抽象完美的新架构。但在接入真实生产流量的瞬间,系统会被各种意想不到的边缘异常彻底击穿。

老系统中那些看似丑陋的防重试逻辑、硬编码的延时等待以及反常的类型转换,恰恰是系统在生产环境存活多年的抗体。在黑盒演化过程中,这些抗体没有被人类工程师转化为显性的设计原则,而是散落在无法辨识的代码废墟中。

当模型完全接管了代码,人类不再阅读实现细节,这些暗知识就随着上下文窗口的更迭永久失传了。重写一个复杂黑盒系统,意味着要重新把过去五年踩过的所有生产故障从头经历一遍。这种重写代价根本不是代码生成速度能够弥补的。

工程师的剩余价值

在复杂的长周期系统也逐步被黑盒代码吞噬的周期里,技术团队内充斥着工程师技能贬值的焦虑。天天面对自己看不懂或者懒得去看的自动化生成代码,开发人员的专业尊严受到了直接侵蚀。

开发人员的剩余价值非但没有消失,反而在系统复杂度失控的悬崖边缘被急速放大。只不过,这种价值的落点发生转移。

编写算法、拼装框架、修补语法的低阶体力劳动被彻底剥离。工程师的核心价值,收敛为以下几项不可替代的底层能力:

第一,是定义系统物理边界与失败容忍度的系统论设计能力。当实现细节彻底黑盒化,整个系统的生存完全取决于外部边界是否足够坚固。如何设计熔断逻辑,如何划分数据强一致性与最终一致性的边界,如何定义灾难恢复时的降级降速策略,这些决策直接关乎企业的商业生死,不可能托付给缺乏全局上下文责任感的模型。

第二,是穿透软件抽象层、理解物理基础设施底层的能力。不论上层代码被模型演化得多像一个不可名状的黑盒,当它最终被编译成汇编指令落到物理 CPU、内存条、网卡与固态硬盘上时,它依然必须服从物理世界的客观定律。

在跨机房专线延迟突增、底层存储硬件出现坏块、操作系统内核调度产生微秒级锁死等极端场景下,缺乏真实物理世界感知的黑盒系统会瞬间丧失全部应对能力。此时能够挽救全局的,永远是那些清楚 Linux 内核参数配置、深刻理解 TCP 拥塞控制算法、明白数据库 B+ 树与 LSM-Tree 存储引擎底层权衡的工程师。

第三,是决定何处必须保留白盒的战略决断力。在复杂的业务全景图中,我们必须划出一条绝对的红线。在这条红线之外,允许模型疯狂试错、野蛮生长、黑盒演化,以换取交付速度;而在红线之内,在涉及资产结算、核心密码学协议、权限鉴权核心以及数据持久化原子性的基石模块上,我们必须死守白盒阵地,每一个状态位、每一行锁逻辑、每一个并发屏障,都必须由人类大脑完全理解、推演与严格审计。

拥抱黑盒演化是应对代码生产力爆发的必然妥协,但盲目迷信黑盒则是工程理性的自杀。

白盒边界的落地

不过,把红线画出来,还远远不够。

如果白盒意味着所有代码都要由人逐行理解,那么几万行的关键模块很快就会耗尽团队的审查能力。如果白盒只意味着安排一位资深工程师签字,它又会退化成一种形式上的责任分配:代码已经合入,签字的人却无法解释失败路径。

从实际出发,白盒要求可以落实到几个可以检查的问题上:

这个模块维护哪些状态?哪些状态转换绝对不允许发生?一次操作在什么位置产生不可撤销的影响?执行中断以后,如何判断它究竟完成到了哪里?修改这里,会影响哪些调用方的既有假设?

负责的工程师需要能够独立回答这些问题,并且指出对应的实现位置。

这里仍然允许模型生成代码。我们限制的是未经理解的关键变更进入系统。生成与理解可以由不同的过程完成,但理解不能被一份自动生成的说明替代。

边界还需要沿着依赖关系检查。

假设一个关键模块本身经过了严格审查,但它依赖的公共组件被模型修改了失败处理方式,原有结论就可能失效。白盒边界如果只按目录划分,很容易漏掉这种变化。把关键模块依赖的行为约定一起纳入审查范围,尤其是超时、重试、错误返回和状态写入的语义。

这样做会拖慢部分公共组件的修改速度。我们需要接受这笔成本,也需要控制它的规模。如果一个普通改动总要召集半个团队评审,说明关键模块依赖了过多外部细节,应该考虑收缩接口和状态共享范围。

白盒区域也不必永久固定。一个模块的状态被拆出、接口变得稳定、替换方式得到验证之后,可以降低内部审查强度。一个原本独立的工具开始承接共享数据写入,就应该重新评估。

「核心业务」四个字无法覆盖整个代码库。范围画得太大,最后往往只能整体降低执行标准。

验证独立

黑盒能接受到什么程度,取决于我们能够从外部验证什么。

这里最容易出现的问题,是实现、测试和验收结论都来自同一条生成链路。模型先理解需求,再生成代码,随后根据代码补齐测试,最后告诉工程师所有测试已经通过。

如果最初的理解遗漏了一个条件,这个条件就可能同时从实现与测试中消失。

覆盖率无法自动发现这种遗漏。某一行代码被执行过,只能证明测试经过了那里。异常分支执行以后留下的状态是否正确,仍然需要另外检查。

因此,关键验收条件在实现之前确定。至少把正常结果、禁止出现的结果,以及失败后允许留下的状态写清楚。

尤其要检查那些跨越多个动作的过程:前一个动作已经完成,后一个动作失败,系统应该停在哪里?用户重新发起请求时,允许重新执行哪些部分?超时之后,调用方能否把结果当成失败?

这些问题没有答案时,继续增加测试数量并不能补上设计缺口。

模型可以帮助枚举场景、生成数据、执行验证。涉及业务承诺的条件,需要由了解系统的人确认。验收阶段还应保留独立于当前实现的依据,避免模型通过修改预期结果,让实现重新获得通过。

测试本身的变更也要审查。

删除一个失败用例,有时是因为旧需求确实失效,有时却只是当前实现暂时无法满足它。二者在测试报告上没有区别。对稳定约束的删除、放宽和跳过,我会要求单独解释,不能夹在几千行功能改动里一起合入。

验证强度当然有成本。复杂故障场景需要准备环境,大规模数据验证消耗资源,长时间运行的检查会增加反馈延迟。我的做法是分开执行频率:局部检查跟随每次修改,影响面较大的验证放在合入和发布环节,昂贵的故障验证集中覆盖关键路径。

目标是让不同风险都有对应的检查位置,同时避免每次修改都等待一套庞大的验证流程。

限制单次变化

代码生成速度上来以后,团队很容易把更大的任务直接交给 agent。

一个需求里同时包含接口调整、状态机修改、数据结构迁移和历史代码整理。模型可能一次完成,最终差异也显得相当统一。但人工审查时,很难再把每一处变化与最初的动机对应起来。

发生回归以后,定位范围同样会扩大。

我们要控制单次变更所包含的独立决策数量。功能增加、结构整理和历史兼容性清理,尽量分开进行。每一部分都需要有能够单独解释的目的,以及对应的验证结果。

这里不适合机械地规定 PR 行数。一次自动生成的重复性修改可能涉及很多文件,实际语义变化很少;一个几行的条件调整,也可能改变整个系统的状态约束。

这次只改变了哪些行为,哪些证据支持这些变化,以及如何撤销?

让 agent 先提交修改计划,也有帮助。人可以在生成大量代码之前检查它准备触及哪些模块,是否引入新的共享状态,是否扩大已有接口的职责。

当然,计划不能被当成执行事实。模型在修复过程中可能偏离原计划,最终仍然需要核对实际改动。尤其要检查那些为了让测试通过而新增的兼容分支、默认值和异常处理。

另外,权限应该跟着风险划分。修改实现、调整验收标准、操作生产数据,是三个不同等级的动作。不能因为 agent 已经获得代码仓库的写权限,就顺带允许它自行决定另外两件事。

保留接管能力

即便验证做得足够认真,团队仍然需要准备一种情况:系统出了问题,agent 连续几轮都没有找到原因。

这时工程师需要接管什么?

首先要接管的是故障判断和损失控制。当前还有哪些请求正在写入?哪些动作可以暂停?哪些状态已经无法直接撤销?继续重试是否会重复产生副作用?

如果这些问题无法回答,贸然修复代码可能让后续处理更困难。

因此,关键路径上的可观测信息需要围绕状态变化设计。只记录异常堆栈,通常不足以判断一个业务动作完成到了哪里。需要能够把一次操作的开始、关键写入和最终结果关联起来,同时区分「没有执行」与「执行完成但没有返回」。

信息越多,记录与检索的成本也越高。涉及数据内容时,还需要限制记录范围。我不会要求把所有局部变量都写进日志,而会优先保留能够判断执行阶段和副作用的信息。

恢复方案也要区分代码与数据。

代码版本退回以后,新版本已经写入的数据仍然存在。旧实现能否读取这些数据,正在执行的任务能否继续,外部已经发生的动作如何处理,都需要提前确认。一个发布平台上存在的回滚按钮,不能证明业务状态能够恢复。

团队可以让模型生成恢复方案,但需要通过演练检查其中的假设。演练时暴露出一个无法判断完成状态的步骤,就应该补充相应的记录或操作方式。

这些工作会占用原本可以拿来交付功能的时间。我们要优先安排给那些失败后难以恢复、影响范围又大的模块。对于可以直接丢弃并重新生成的结果,没有必要采用同样的投入标准。

我们也不应该假设所有故障最终都需要人手工解决。agent 能完成的排查可以继续交给它。人需要保留的是判断它是否仍在有效推进的能力,以及必要时改变排查方向、停止危险操作的权限。

把暗知识留下

关于暗知识,还有一个地方需要说得更准确。

只要代码、历史记录和运行证据仍然存在,很多知识就有机会被重新找回来。但随着版本累积,重新发现它们需要的时间会增加。故障发生时,团队未必有这个时间。

在每次关键修改中,顺手留下三类信息:这段特殊处理保护了什么约束,当初在什么条件下出现过问题,以及什么证据能够证明它将来可以被删除。

单独写一句「兼容历史逻辑」,对后来的维护者帮助很有限。模型也无法据此判断,这段逻辑究竟承担了必要的保护,还是早已失效的补丁。

记录需要贴近决策发生的位置。约束可以进入验收条件,历史问题可以转化为回归场景,难以自动验证的条件则保留在模块说明和变更记录里。没有必要把所有知识都塞进一份不断膨胀的架构文档。

尤其要防止另一种浪费:模型生成了大量说明,团队却没有检查其中哪些是事实,哪些只是根据代码推测出来的意图。

「当前代码这样执行」和「系统必须这样执行」需要分开记录。前者描述实现,后者约束未来修改。混在一起,模型可能把历史偶然行为永久固化,也可能把必须保留的规则当成可整理的细节删除。

重写之前,这些记录应当成为核对清单。找不到来源的特殊逻辑,需要进一步追踪,不能仅凭实现难看就判定它没有价值。

同样,也不能把所有历史补丁都当作不可触碰的要求。长期保留失效约束,会让新系统继续背负旧系统的复杂度。删除它们需要证据,保留它们也应该能够说明理由。

重新分配时间

这会改变工程师日常工作的时间分配。

一部分原本用于编写常规实现的时间,可以转移到约束定义、关键路径审查、失败恢复和演化设计上。另一部分确实可以节省下来,用于交付更多需求。我们没有必要为了证明专业价值,把节省的时间全部重新填满。

不要求所有工程师都深入内核、网络协议和存储引擎。基础设施知识在某些问题上非常关键,但大量故障仍然来自业务状态、依赖关系和错误的边界假设。团队需要根据系统的风险分布建立能力,不能用一套底层知识清单替代具体判断。

对于个人:能否识别模型没有回答的问题,能否发现验证结论依赖了未经确认的假设,能否在陌生实现中定位关键状态,以及能否解释一次技术选择会给后续修改增加什么成本。

这些能力也需要通过实际接触代码培养。如果年轻工程师长期只负责转发需求和粘贴错误,却没有机会追踪实现、分析失败、参与关键决策,团队几年后可能会出现知识传承的问题。

因此,我们需要让工程师对完整的局部问题负责:提出约束、使用模型实现、解释关键行为、验证异常路径,并参与上线后的问题处理。模型可以承担其中大量工作,但负责的人需要能够检查结果。

团队层面的评价也要变化。生成了多少代码、完成了多少轮对话,都不适合作为主要指标。我们需要观察一次需求从提出到稳定运行花了多久,评审与返工占用了多少时间,线上问题是否反复出现,以及一个模块是否越来越难由其他人接手。

如果编码时间缩短了,排查和返工时间却持续增长,就需要调整当前流程。交付数量暂时增加,也不能掩盖恢复能力和知识覆盖的下降。

回到最初那个数千行代码、测试全绿的 PR,我们不会因为提交人无法背出全部实现就否定它。

但如果他无法解释核心异常分支的状态变化,我们要要求补上这部分理解:追踪关键写入,核对失败后的状态,检查重复执行的后果,再决定是否需要增加验证或修改实现。

这个 PR 可以继续由模型完成修改。合入之前,负责的工程师需要知道哪些结论已经得到验证,哪些风险仍然存在,以及出问题以后应该从哪里开始处理。

以上。

DeepSeek Harness 的 Harness 到底做了什么

DeepSeek Harness 解决的核心问题,是多轮模型调用、工具执行与会话状态之间的协调:输入何时进入请求,工具结果如何写回历史,请求失败后从哪里重试,上下文压缩后保留哪些信息,以及任务中断后能够恢复到什么状态。

围绕这些问题,框架将运行过程拆分为插件装配、回合驱动、会话日志、工具管道和异常处理。分析其架构的关键,在于各部分的状态边界,以及这些边界提供的保证与限制。

本篇文章限定在提交 477b4f4205(2026 年 9 月 25 日)对应的实现。

1. 插件装配

插件是 DeepSeek Harness 的核心能力,插件的配置决定 Agent 的运行能力。

DeepSeek Harness 通过 Cordis 插件组织运行能力。Cordis 提供 Context、Service、Plugin 和 Event 等抽象,插件通过依赖注入与事件系统协作,并由框架管理生命周期。

LLM 服务、Session、Agent、AgentLoop 和工具注册表等基础组件,由 bundle 统一挂载。启动时,dsh 以 profile 为入口加载配置,按 id 定位配置行,并替换对应的整段 config。

这里的配置语义是整段替换,而非字段级合并。覆盖某个组件的配置时,原配置中未被保留的字段也可能随之消失。

插件装配因此直接影响 Agent 的实际行为:模型服务是否可用、哪些工具被注册、哪些事件处理器参与执行,都取决于最终加载的插件与配置。仓库包含某项能力,并不等于当前运行实例已经启用该能力。

这种设计便于替换组件和组合能力,但也让执行逻辑分布在多个插件中。分析一次请求的行为,需要同时查看核心循环、插件注册关系和最终生效的配置。

2. 核心循环

Agent 循环是 Agent 的核心逻辑,其主要区分回合、步骤与请求重试。

从实际的工作逻辑上来看,agent-loop 负责驱动模型与工具之间的循环。其主要执行路径包括:

  1. 准备模型请求,解析配置并选择适配器。
  2. 投影系统提示词,提交本次需要进入历史的用户输入。
  3. 从会话构造请求,接收并累积流式响应。
  4. 将 assistant 消息写入 Session。
  5. 执行模型提出的工具调用,并将结果交给后续模型请求。
  6. 在没有工具调用或满足结束条件时,结束本轮执行。

理解这条路径,需要分三个层次:

  • turn:由用户输入驱动的回合。
  • step:回合内部的执行边界。
  • 请求尝试:一次实际发往模型的调用,失败后可以在同一个 step 内重试。

因此,step 不能简单等同于一次网络请求。重试可能产生多次请求尝试,但不应重复消费输入或重复提交用户消息。

Agent 维护 next-turn 和 next-step 两个队列,分别承接下一回合与下一步的输入。队列修改以 agent/inbox/spliced 事件写入 Session,待处理输入可以从日志投影恢复。

这一设计区分了「系统已经收到消息」和「模型已经消费消息」。

执行过程中到达的补充要求,需要在相应边界进入后续请求。它不能自动改变已经发出的模型请求,也不能撤销已经发生的工具副作用。输入排队与执行取消因此属于不同机制。

将队列变化写入日志,还能避免恢复时只找回聊天记录,却丢失尚未处理的输入。

3. Session

Session 机制实现了运行记录与模型历史分离。

Session 被设计为只追加的事件日志,事件通过递增序列号确定顺序,以 type 区分类型,以 body 保存内容。

日志不仅记录用户与 assistant 消息,也记录请求失败、输入队列变化、工具执行和压缩过程等运行事实。

但日志中存在的事件,不一定进入模型上下文。

真正发送给模型的 messages,由 Session 的 surface 通过 deriveMessages() 重建,再冻结为不可变请求。只有进入 surface 的消息事件参与历史投影;assistant/attempt 等运行记录不会自动成为模型消息。

这一分离有两个作用:

  • 保留排障信息:请求失败与恢复过程可以完整记录,而不必全部占用上下文窗口。
  • 允许调整可见历史:压缩或替换模型上下文时,仍能保留原始事件。

Session 的 append 还会检查 JSON 可序列化性、事件规则及 surface 替换的合法性,以减少无法持久化或无法正确投影的数据进入日志。

日志恢复不等于外部动作回滚

只追加日志为审计与状态重建提供了基础,但不能单独保证任意中断点都能无损恢复。

例如,工具已经修改文件,但结果尚未写入 Session 时进程退出,外部环境与日志之间就可能出现状态差异。恢复日志可以还原已提交的记录,却不能仅凭「缺少结果」判断工具没有执行。

因此,恢复能力需要区分:

  • 已提交事件及其投影的恢复;
  • 外部副作用的确认与处理。

后者还依赖工具的幂等性、状态检查能力或额外的事务机制,不能仅由 Session 的追加式设计推导出来。

4. 工具管道

工具管道实现了从模型意图到受控执行。

工具注册表只向模型暴露 name、description、parameters 等白名单字段。执行函数、超时设置和并发属性保留在运行时。

这将模型侧的工具描述与运行时的执行控制分开:模型负责提出调用,框架负责判断调用是否可执行,以及如何执行。

一次工具调用依次经过:

  1. 解析模型生成的参数。
  2. 查找当前 Agent 可见的工具。
  3. 通过 tools/pre-execute 进行执行前检查,允许或拒绝调用。
  4. 进入 tools/execute 包装链。
  5. 通过 tools/post-execute 处理结果。
  6. 将结果写回 Session,供后续模型请求使用。

权限检查、执行包装和结果处理因而拥有独立的介入位置,不必全部写进工具函数。

并发执行与顺序提交分离

同一条 assistant 消息可能提出多个工具调用。调度器通过工具的 isConcurrencySafe 属性分类:

  • 明确返回 true 的调用进入并行池。
  • 其他调用作为独占屏障处理。

并发是显式声明的能力,未声明安全的工具不会默认并行执行。

工具本体可以并发,但准备、后处理、tool/result 写入及附加 context 入队,按模型调用顺序提交。这样可以避免工具完成时间的随机性直接改变会话中的结果顺序。

代价是可能出现等待:后面的工具即使先完成,也需要等待前面的调用提交。并发能够缩短部分执行时间,却不保证结果可以立即进入历史;提前完成的结果还可能需要暂存。

此外,提交顺序稳定不等于外部副作用确定。并发工具是否真正安全,仍取决于它们是否访问共享状态、是否存在执行依赖,以及并发声明是否准确。

5. 安全隔离

DSH 在实现上让审批与执行限制分别生效,以实现安全隔离。

Harness 的沙箱模式包括:

type SandboxMode =
  | 'read-only'
  | 'workspace-write'
  | 'danger-full-access'

策略涉及文件路径权限、命令执行限制和权限提升。工具可以通过 justification 与 sandbox_permissions 请求提升权限,再由 approval 机制处理审批。

基础 bundle 默认挂载 workspace-write 文件沙箱与 ask 审批策略;sandbox-policy 将当前文件操作模式解析给实际后端。

这一结构包含三个不同层次:

  • 工具可见性:模型能够提出哪些调用。
  • 执行审批:某次调用是否获得许可。
  • 后端隔离:获准执行后,实际能够访问哪些资源。

三者不能相互替代。审批通过只说明动作获得许可,不代表执行环境具有无限权限;模型看不到某个工具,也不能据此证明其他工具无法访问相同资源。

安全边界最终取决于策略配置与实际后端的共同约束,而不只是沙箱模式的名称。

6. 异常收敛

异常是应用中很常见的东西,也是 Harness 的重点处理对象。

DSH 通过重试、压缩与取消各自处理不同问题。

6.1 请求重试

通过请求重试来保留失败记录,避免重复提交输入。

模型请求失败后,系统先写入 assistant/attempt,再交给 agent/request-error 瀑布链处理。

llm-retry 根据适配器提供的 retry policy,判断错误类型、重试次数与退避方式。计划重试与实际启动分别记录为 llm/retry 和 llm/retry-started。

重试在同一个 step 中重新准备请求,不重复提交首次用户消息。这一边界避免了网络层重试演变成会话层的重复输入。

分别记录重试计划与启动,也使日志能够区分“决定重试”与“已经开始下一次尝试”。

6.2 上下文压缩

上下文压缩实现了替换可见历史,保留原始事件。

自动压缩在 agent/pre-step 检测上下文压力,并记录:

  • compaction/start
  • compaction/summary
  • compaction/end

随后,系统将选中的连续历史区间替换为一个 checkpoint 用户消息。

这里发生的是 surface replace:模型可见历史缩短,原始日志仍然保留。因此,压缩减少的是请求上下文,不等于同步减少持久化存储。

压缩也引入了信息损失的可能。原始约束即使仍在日志中,只要没有进入摘要或剩余 surface,后续模型就无法直接使用。压缩是否有效,除了看上下文长度,还取决于任务目标、限制条件和未完成事项是否得到保留。

6.3 取消

通过取消来传递停止信号,而非撤销既有结果。

AbortSignal 贯穿回合、请求和工具,使取消信号能够沿执行链传递。

但信号传递不等于立即停止所有动作。工具是否及时退出,取决于其是否检查并响应取消;已经完成的文件写入或远端操作,也不会因 AbortSignal 自动回滚。

取消机制提供的是停止继续执行的控制路径,外部副作用的撤销仍需要独立实现。

7. 小结

DSH 的 Harness 实际上是一种架构的取舍,其取舍考量的是可追溯性与运行成本。

如下的机制共同形成了 DeepSeek Harness 的主要工程取舍:

机制 提供的能力 成本或边界
Cordis 插件装配 组件替换与能力组合 行为分布在配置和多个处理器中
turn / step 与输入队列 明确输入归属和执行边界 增加队列状态与事件维护
追加式 Session 审计与已提交状态重建 日志持续增长,不保证外部动作恰好执行一次
surface 投影 分离运行记录与模型上下文 需要维护投影一致性与替换规则
并发执行、顺序提交 利用并发,同时稳定历史顺序 可能产生提交等待与结果暂存
沙箱与审批 分层控制动作权限 实际保证依赖配置和执行后端
自动压缩 降低上下文窗口压力 增加摘要成本,并可能丢失任务信息
AbortSignal 统一传递取消意图 依赖工具配合,不能自动回滚副作用

DeepSeek Harness 的核心价值,是将 Agent 执行中的关键状态显式化:输入有队列,执行有边界,请求有可见历史,工具有调度与审批,失败有独立的处理路径。

这些机制使多轮任务能够被追踪、检查和恢复,同时也明确了框架的能力边界:日志恢复不能替代副作用确认,顺序提交不能替代并发安全,权限审批不能替代环境隔离,上下文压缩也不能保证信息无损。

以上。

DeepSeek Harness 的上下文管理、记忆和知识库剖析

DeepSeek Harness 里,上下文、记忆和知识库并没有被设计成三个彼此独立的重型系统。它们围绕 Session Log 协同工作。

用三句话概括:

  • 上下文管理负责决定当前这一轮模型能看到什么。系统提示词、动态环境、工具定义和会话消息,会在模型调用前完成装配。
  • 记忆机制负责在上下文窗口不足时折叠旧历史。原始会话日志继续保留,模型当前可见的历史被替换成结构化摘要。
  • 知识获取负责把仓库文件和外部信息送入模型。文件通过路径引用和读取工具进入,网络信息通过搜索、抓取工具进入,所有结果最终写回 Session Log。

三个模块共享一条约束:

模型看到的内容,需要拥有可追踪的来源,并且能够从会话记录中重新构造。

这条约束决定了 DeepSeek Harness 的整体形态。上下文不能由控制器随手拼接,工具结果不能停留在进程内存,压缩不能覆盖原始记录,检索结果也不能绕过会话日志直接塞给模型。

它由此形成了一条完整链路:

SystemPrompt.assemble() 组装当前上下文,ReactLoopAgent.preStep() 决定动态内容是否进入会话,Session.append() 保存事件,Session.deriveMessages() 生成模型历史,buildRequest() 构建最终请求,llm.stream() 完成模型调用。

理解了这条链路,就大概能了解 DeepSeek Harness 的知识和记忆相关的逻辑了。

上下文管理

上下文管理解决的是一个具体问题:每次调用模型时,系统应该选择、组织并发送哪些内容。

现在大多数 Agent 系统不会简单维护一个持续增长的字符串,而是使用结构化消息列表管理上下文。较成熟的系统还会根据任务状态,动态选择系统提示词、历史消息、工具定义、运行环境和检索结果。

真正的难的不是 「是否使用消息列表」,而在于:

  • 哪些内容应该进入本轮请求;
  • 不同内容由哪个模块提供;
  • 动态状态发生变化后如何更新;
  • 重复信息是否需要再次发送;
  • 长会话中哪些历史应该保留或压缩;
  • 服务重启后能否恢复模型当时看到的内容;
  • 最终请求能否被追踪、审计和重放。

DeepSeek Harness 同样以结构化消息为基础,但它没有让 AgentLoop 直接维护所有上下文,而是将上下文拆分为不同来源,在每个 step 开始前统一装配。

1. 分层组织上下文

DeepSeek Harness 中的上下文主要分为四类:

  • 系统规则:角色设定、行为约束和工具使用规范;
  • 动态状态:当前工作目录、运行环境和终端状态;
  • 会话历史:用户消息、模型回复和工具调用结果;
  • 工具定义:当前允许模型调用的工具及其参数结构。

这些内容分别由 SystemPrompt、Runtime Context、Session 和 ToolRuntime 管理,最后在模型调用前合并。

这种设计的重点不是改变消息格式,而是明确上下文的来源和职责。AgentLoop 只负责控制执行流程,不直接承担提示词拼接、历史管理和工具注册等工作。

2. 动态装配本轮内容

每个 step 开始前,ReactLoopAgent.preStep() 会调用 SystemPrompt.assemble(),收集本轮所需的系统提示、动态上下文和工具定义。

其中:

  • sections 保存相对稳定的系统提示;
  • contexts 提供会随运行状态变化的环境信息;
  • tools 描述当前可用的工具;
  • variables 提供提示词渲染需要的变量。

各插件只负责注册自己的内容,最终由 SystemPrompt 统一生成本轮快照。

这意味着上下文并不是固定不变的。例如,插件启停、工作目录变化或工具权限调整后,下一次模型调用可以获得最新状态,不需要由 AgentLoop 编写额外的业务分支。

3. 避免重复注入动态状态

动态上下文并不需要每个 step 都重复写入。

RuntimeContextProjection 会比较本轮快照和上一轮快照:

  • 如果内容没有变化,就继续沿用已有信息;
  • 如果内容发生变化,就生成新的上下文消息并写入 Session。

这种机制有两个作用。

第一,减少重复内容带来的 token 消耗。工作目录和运行环境可能连续多个 step 保持不变,没有必要反复加入会话历史。

第二,保留状态变化轨迹。当环境发生变化时,新快照会进入 Session,后续可以追踪模型从哪一轮开始看到了新的状态。

投影状态还可以从已有 Session 中恢复。因此,即使服务重启,系统也能判断某段动态上下文是否已经注入,避免因为进程内状态丢失而重复写入。

4. 统一派生会话历史

用户消息、模型回复和工具结果不会由控制器临时拼入请求,而是先写入 Session Log,再由 Session.deriveMessages() 派生为模型协议需要的消息列表。

这使系统能够区分三个层次:

  • Log:完整保存原始会话事件;
  • Surface:决定当前哪些事件参与模型上下文;
  • Messages:最终发送给模型的结构化消息。

例如,工具执行结果先作为 tool/result 写入 Session,下一轮再由 deriveMessages() 转换成模型可以读取的消息。上下文压缩也只调整 Surface,不直接删除原始事件。

因此,模型输入不是由多个模块临时修改的消息数组,而是由统一的会话记录派生出来的。

5. 构建并记录最终请求

上下文装配完成后,buildRequest() 会生成最终的模型请求,主要包括:

  • system:系统提示;
  • messages:会话历史和动态上下文;
  • tools:当前可用的工具定义;
  • sessionId:当前会话标识;
  • signal:调用控制信号。

同时,系统会记录 request/header 和 request/context,保存本次调用使用的关键上下文信息。

这样可以回答几个重要问题:

  • 模型当时看到了哪些历史;
  • 使用了哪一版系统提示;
  • 当时有哪些可用工具;
  • 动态环境是否已经发生变化;
  • 某次异常是模型推理问题,还是输入上下文问题。

DeepSeek Harness 的上下文管理并不是简单地将内容放进消息列表,而是围绕分层管理、动态装配、变化检测和统一派生建立完整流程。

其核心链路可以概括为:

各模块提供上下文 → SystemPrompt 动态装配 → RuntimeContextProjection 检测变化 → Session 派生历史消息 → buildRequest 构建并记录最终请求。

这样,每段上下文都有明确来源,动态信息不会被无意义地重复发送,模型输入可以从 Session 中恢复和追踪,AgentLoop 也不需要承载复杂的提示词与历史管理逻辑。

记忆压缩

DeepSeek Harness 当前的记忆能力主要由 Compaction 提供。

这里需要控制术语。Compaction 服务于当前会话的连续运行,它会把旧历史折叠成结构化检查点。跨 Session 的用户偏好、项目经验和长期事实存储,目前没有形成完整系统。

因此,我更愿意把它称为「会话记忆」。

1. 压缩对象

Session 内部存在三个层次:

  • Log:所有原始事件组成的追加日志;
  • Surface:当前参与模型消息派生的事件视图;
  • Messages:发送给模型的协议消息。

Compaction 修改的是 surface。

原始事件仍然保留在 Log 中,模型当前看到的历史则由摘要节点替代。这个结构同时满足了两个要求:

  • 模型输入得到缩短;
  • 原始会话记录没有被覆盖。

它比直接删除前 N 条消息安全很多。

删除历史以后,调试系统无法知道模型之前看过什么。线上出现问题时,只剩下截断后的残缺记录。Compaction 保留原始事件,后续仍可审计压缩范围和摘要来源。

2. 触发条件

BasicCompactionEngine 会通过 tokenMeter.measure(session) 估算当前会话的 token 压力,再与模型的 contextWindow 比较。

自动压缩拥有两个主要触发入口:

  • agent/pre-step 阶段的常规压力检查;
  • 模型返回上下文溢出错误后的重试处理。

常规检查负责提前处理窗口压力,错误重试负责兜底。

压缩区域由 selectCompactableRange() 选择。它会优先折叠较旧历史,并保留近期上下文。近期消息通常包含当前修改进度、最新工具结果和下一步操作,保留它们可以减少摘要对短期推理的影响。

区域选择还需要保护工具调用链。

Assistant 发出的 tool-call 和对应的 tool-result 属于一组完整语义。若压缩边界将它们切开,后续模型可能看到孤立的调用或孤立的结果。部分模型适配器甚至会直接拒绝这种消息结构。

validateSurfaceRegion() 会检查压缩区域,避免破坏工具调用与结果的配对关系。

这类结构校验比简单的「保留最近 N 条消息」可靠。Agent 历史已经超出普通聊天记录的范畴,消息之间存在协议级关联,切割时必须理解这些关联。

3. 摘要生成

压缩区域选定后,buildSummarizationInput() 会恢复对应的模型消息,并取回当时的 system 和 tools。summarizeWithLlm() 随后调用 LLM 生成摘要。

摘要受到固定模板约束,主要包含:

  • Primary Request and Intent
  • Files and Code
  • Errors and Fixes
  • Next Step
  • Critical Context

结构化模板可以防止摘要退化成普通的对话概述。

编程 Agent 的历史里,有用的信息通常集中在几类内容:

  • 用户最初要解决的问题;
  • 已经读取或修改的文件;
  • 执行过的命令;
  • 失败原因和修复过程;
  • 当前未完成步骤;
  • 不能违反的环境约束。

如果只要求模型「概括以上对话」,输出往往会省略文件名、错误信息和失败尝试。摘要读起来流畅,后续执行却接不上。

固定结构会提高这些信息的保留概率,但无法保证语义完整。

4. 替换事务

摘要生成以后,会被包装进 <compacted-summary>,随后作为新的 user/message 写入 Session,并通过 surfaceOp: replace 替换旧区域。

参考实现中的主流程如下:

// compaction-basic/region.ts 的逻辑简化
session.append('compaction/start', { ... })

const summary = await summarizeWithLlm(ctx, config, input, agent, signal)
const framed = frameSummary(summary.summary)

const summaryEvent = session.append('compaction/summary', { ... })

session.append(
  'user/message',
  createUserMessage({
    content: framed,
    source: compactCheckpointSource(...),
  }),
  {
    surfaceOp: { op: 'replace', start, end },
    sourceEventSeqs: [startEvent.seq, summaryEvent.seq, ...shadowedSeqs],
  },
)

session.append('compaction/end', { ... })

整个过程会记录:

  • 压缩开始;
  • 摘要内容;
  • replacement message;
  • 被覆盖的事件序列;
  • 压缩结束。

sourceEventSeqs 建立了检查点与原始历史之间的关联。排查错误摘要时,开发者可以反查它覆盖了哪些事件。

Session.deriveMessages() 检测到 replaceGeneration 发生变化后,会重建消息缓存。后续模型看到的是新检查点和保留下来的近期历史。

当前的设计保证了事务的完整性。压缩失败时,旧 surface 仍然可以继续使用。摘要生成成功且提交完成后,模型视图才发生变化。

5. 有损风险

Compaction 无法绕开有损问题。

假设旧历史中出现过一条约束:测试环境使用特定环境变量,变量为空时必须跳过某项操作。摘要模型漏掉这条信息以后,后续 Agent 可能执行错误命令。

原始事件虽然还在 Log 中,当前模型无法自动访问。对推理过程而言,这条信息已经离开热上下文。

因此,Session Log 的完整性解决了审计问题,没有完全解决信息恢复问题。

我会在现有 Compaction 上增加一层历史召回能力。摘要中保留关键事件引用,模型缺少细节时,可以调用类似 recall_history 的工具读取旧历史。

召回参数可以采用结构化维度:

  • turn 范围;
  • step 范围;
  • event seq;
  • tool call id;
  • 文件路径;
  • compaction id。

这种方式比单纯的语义搜索更稳定一些。Session 已经拥有完整事件顺序和来源关系,应优先利用现有结构。

召回结果也要写成 tool/result。这样系统可以知道模型恢复了哪段历史,以及这段历史怎样影响后续决策。

6. 调度延迟

当前压缩会调用一次 LLM。压缩发生在主流程上时,用户需要等待摘要完成。

会话较短时,这个延迟可以接受。长会话的摘要输入很大,调用时间会明显增加。编程 Agent 还可能连续执行多个工具,压缩恰好卡在下一步之前,交互体验会出现停顿。

可以引入两级水位:

  • 接近窗口上限时,后台生成候选 checkpoint;
  • 到达硬上限时,提交已有 checkpoint;
  • 候选结果过期时放弃;
  • 没有可用结果时回退到同步压缩。

异步压缩的难点集中在一致性。

生成摘要期间,Session 还会继续追加事件。提交前必须校验它对应的 surface generation,确保被压缩区域没有发生冲突。检查点过期后不能强行替换,否则可能覆盖新的工具结果或用户输入。

当前已有的压缩重入保护、surface generation 和事务事件,为异步化提供了基础。早期保持同步更稳,长任务比例上升以后,再逐步引入后台 checkpoint。

知识获取

DeepSeek Harness 没有传统意义上的统一向量知识库。

它的知识入口由三部分组成:

  • 文件路径引用;
  • Web 搜索与抓取;
  • 工具结果写入 Session。

这种组合适合编程 Agent。代码仓库里的信息具有路径、符号、定义和引用关系。统一切块并写入向量数据库,会损失一部分结构信息,还要承担索引更新成本。

1. 文件引用

file-reference-local 提供工作区文件和目录候选。

它会根据 session.header.cwd 创建 WorkspaceFileSearch,然后扫描当前工作区。结果只包含:

  • path
  • kind

用户在前端选择文件后,输入框里会插入 @path 或 @"path with spaces"。

文件内容不会在这个阶段进入模型。

系统提示会告诉模型,@ token 表示工作区路径。模型需要读取内容时,应调用 read 工具。

这个分层处理了两个不同问题:

  • file-reference-local 负责找到路径;
  • read 工具负责读取内容。

如果选择路径时就把整个文件自动塞进消息,系统会遇到几类麻烦。

第一,大文件会快速占满上下文。

第二,用户无法知道系统展开了多少内容。

第三,读取行为缺少独立记录。

第四,文件权限和读取范围可能绕过工具 guard。

第五,文件修改以后,很难判断模型当时看到的是哪个版本。

路径引用保留了用户意图,工具调用保留了实际读取行为。两类信息都会进入 Session,审计链更完整。

WorkspaceFileSearch 还会控制目录边界、排除路径、最大条目和候选数量,并拒绝通过 .. 或符号链接跳出 workspace。

这些限制属于安全边界。文件路径来自用户输入,也可能由模型生成,不能默认可信。

2. 精确检索

文件路径补全只能解决「大致知道文件在哪里」的问题。

大型仓库中,开发者和模型经常需要回答另一类问题:

  • 接口定义位于哪个文件;
  • 某个符号有哪些引用;
  • 哪些实现依赖当前类型;
  • 一次修改会影响哪些调用点;
  • 编译诊断关联到哪些符号。

这些查询适合交给 LSP。

项目已经存在 packages/lsp/,可以继续扩展为代码检索能力。优先提供:

  • Go to Definition;
  • Find References;
  • Workspace Symbols;
  • Diagnostics;
  • 调用关系。

向量检索依赖语义相似度。两个函数命名和注释很接近,并不能证明它们具有依赖关系。LSP 返回的是语言服务器维护的定义和引用关系,适合代码修改场景。

可以把仓库检索分成四层:

  1. 已知路径,直接读取文件;
  2. 已知文本,使用 grep;
  3. 已知符号,使用 LSP;
  4. 只有概念描述时,再考虑语义检索。

这个逻辑会优先消耗确定性信息。代码仓库已经提供路径和符号结构,没有必要先把问题转换成向量相似度。

LSP 也有运行成本。语言服务器需要启动和预热,多语言仓库要维护多个进程,大型 monorepo 的全局引用查询可能很慢。它适合作为工具按需调用,不适合把所有结果常驻上下文。

查询结果仍然要通过 ToolRuntime 写入 Session。这样模型读取过哪些定义、哪些引用,可以在后续回放中找到。

3. Web 检索

外部知识通过 web_search 和 web_fetch 进入模型。

整体分为三层:

  • WebRuntime 定义统一能力;
  • search/fetch provider 负责具体请求;
  • tool-web 把能力注册成模型可见工具。

这种分层使工具 schema 与供应商解耦。模型只需要理解 web_search 和 web_fetch。底层可以选择 DeepSeek、Exa、Perplexity 或 HTTP fetch provider。

Provider 选择规则保持严格:

  • 配置了 provider id,使用指定 provider;
  • 未配置时,系统要求只有一个可用 provider;
  • 多个 provider 同时可用时直接报错。

这可以避免插件加载顺序影响线上行为。

Web 搜索结果会被格式化成模型可见文本,并带有外部内容不可信和引用 URL 的提示。Web fetch 会把 HTML 转换成 Markdown,普通文本则直接进入结果。

提示只能影响模型行为,网络边界还要由 provider 控制。HTTP fetch provider 已经包含多项限制:

  • 只允许 HTTP(S);
  • 拒绝 URL credentials;
  • 拒绝私网和非公网地址;
  • 控制同源重定向;
  • 限制重定向次数;
  • 限制响应字节数;
  • 限制正文长度;
  • 固定经过校验的 DNS 地址;
  • timeout 由部署配置管理。

模型生成的 URL也属于不可信输入。网页中的 Prompt Injection 可能诱导模型请求内网地址、云元数据地址或带有凭证的 URL。网络策略需要在模型之外执行。

4. 工具入库

文件读取、Web 搜索和 Web 抓取获得的信息,都通过同一条工具链进入模型。

流程可以概括为:

  1. ToolRuntime 把工具 schema 注册到 SystemPrompt;
  2. 模型返回 tool-call;
  3. AgentLoop 调用 executeToolCalls();
  4. Session 写入 tool/call;
  5. ToolRuntime 执行工具;
  6. Session 写入 tool/result;
  7. 下一轮 deriveMessages() 把结果投影给模型。

参考实现中的调用路径如下:

// 模型返回 tool-call
const toolCalls = message.content.filter(block => block.type === 'tool-call')

// Agent 执行工具
const { concluded } = await executeToolCalls(
  this.loopCtx,
  turn,
  step,
  toolCalls,
  signal,
  context => this.inbox.splice('next-step', this.inbox.nextStep.length, 0, [context]),
)

// 工具结果进入 session log,下一 step 被 deriveMessages() 投影给模型

工具结果不会只停留在当前执行栈,也不会直接修改临时 messages 数组。

这对 Web 信息尤其关键。搜索结果和网页内容会随时间变化。如果系统只记录搜索参数,重放时重新请求一次,模型得到的内容可能已经不同。

文件读取也一样。Agent 可能在读取后修改文件。历史推理需要保留当时真正发送给模型的内容。

当然,完整保存所有工具输出会增加 Session 体积。工程上可以保存模型实际看到的渲染结果,同时在 meta 中记录:

  • 是否截断;
  • 原始内容长度;
  • 来源路径或 URL;
  • content type;
  • 工具调用参数;
  • provider 信息。

模型没有看到的正文,无须全部伪装成上下文历史。回放能力关注的是当时的模型输入。

三者关系

上下文、记忆和知识获取分别控制模型输入的三个阶段。

  • 上下文决定当前输入: SystemPrompt 收集系统约束、动态状态和工具 schema。preStep() 判断动态内容是否变化,buildRequest() 构建最终模型请求。它解决的是「这一轮应该携带什么」。
  • 记忆控制历史体积: Compaction 观察 token 压力,选择旧历史区域,生成结构化摘要,再替换当前 surface。它解决的是「历史太长以后保留什么」。
  • 知识补充外部信息:文件引用帮助定位路径,读取工具获得仓库内容,Web 工具获取外部信息,未来还可以用 LSP 提供符号级查询。它解决的是「当前会话缺少的信息从哪里获得」。

三者最终都回到 Session。

动态上下文通过 user/message 进入日志,工具信息通过 tool/result 进入日志,压缩通过 compaction/* 事件和 replacement message 改写 surface。

所以,DeepSeek Harness 的数据主线可以压缩为:

装配上下文,记录事件,派生消息,调用模型,执行工具,写回结果,必要时折叠 surface。

插件可以增加新的上下文来源、新的工具和新的压缩策略,但不能绕过这条主线。

直接向 GenerateOptions.messages 塞内容,会产生无法回放的隐形上下文。

检索模块绕过 ToolRuntime,会失去权限、审计和结果记录。

压缩模块覆盖原始事件,会破坏历史追踪。

这几条边界比具体使用哪种模型、搜索服务或向量数据库更影响系统的维护成本。

小结

DeepSeek Harness 的三个模块可以归纳为三句话:

  • 上下文管理负责装配当前输入。
  • Compaction 负责压缩当前会话历史。
  • 文件和 Web 工具负责补充当前缺失的信息。

三者共享 Session Log:

  • 模型看到的动态状态要进入日志;
  • 模型获得的工具结果要进入日志;
  • 压缩只调整 surface,原始事件继续保留;
  • 后续模型输入由 Session.deriveMessages() 统一派生。

现有设计的优势集中在可回放、可审计和模块边界。主要缺口也很具体:Compaction 存在语义损失,压缩调用会阻塞主流程,文件路径检索缺少符号级能力,跨 Session 长期记忆尚未形成独立体系。

Session Log 继续保存事实,surface 控制模型当前看到的历史,工具负责按需获取信息。三个模块沿着这条数据链协作,系统才能在上下文窗口、运行延迟和信息完整性之间保持可控。

以上。