开发同学在 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 执行中的关键状态显式化:输入有队列,执行有边界,请求有可见历史,工具有调度与审批,失败有独立的处理路径。

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

以上。

带团队的进阶:从解决问题到发现问题

好久没有写团队管理的文章了。今天写一下最近在思考到的一个问题。

我有一个习惯是会经常检查自己的日程,看看时间花在哪里了。

我会发现很多的时间都是在解决问题,不管是架构的问题,还是需求的问题,还是人的问题。你会发现自己很忙,也很充实,觉得自己发挥了作用。

我们一直讲 XXX左移,如测试左移,需求左移。问题也需要左移。

如果我们正在忙的这些事情下个月还会以相似的形式发生,思考一下我今天到底解决了什么问题?

从接到一个明确任务开始,延伸到问题尚未被准确描述的时候。

题目从哪里来

工程师接到一个任务,通常会先考虑约束。

吞吐量要达到多少,延迟要控制到什么范围,需要兼容哪些调用方,什么时候上线。约束越清楚,技术讨论越容易收敛。方案可以比较,工作可以拆分,结果也有验收标准。

进入管理岗位以后,麻烦在于:这些约束本身,也需要被检查。

假设业务提出,要把某个接口的响应时间减半。团队可以研究索引、缓存、并发和计算过程,也可以重新分配机器资源。这些工作都有工程价值。

但我会先追问:用户究竟在等待什么?

如果用户完成任务要经过多个环节,这个接口只占很小一部分时间,那么局部优化可能无法改变使用体验。如果等待主要发生在人工审核阶段,继续压缩服务端耗时,投入方向就需要调整。如果响应已经足够快,用户仍然反复提交,我们还需要检查状态反馈和操作流程。

这里没有贬低性能优化的重要性。问题在于,团队拿到的技术指标,是否足以代表想要改善的结果。

我经常和业务同学、商务同学以及其它提需求的人,你讲需求,不要讲方案。 因为很多人来把自己的方案当需求。

要求他们描述:谁遇到了什么困难,发生在什么条件下,造成了什么损失,现有证据是什么。

先不要写方案。

管理者需要对任务的来源负责。 方案评审只能检查给定题目下的解法,无法自动纠正题目本身。

扁鹊的难题

《鹖冠子·世贤》里,扁鹊评价兄弟三人的医术,将长兄排在最前,中兄其次,自己居后。长兄在疾病尚未显形时处理,中兄在病情轻微时处理,扁鹊的治疗动作更显著,名声也传得更远。

引用这个故事,我想表达的是其中的评价问题。

问题严重以后,损失是可见的,处理动作也是可见的。系统恢复了,业务继续运行了,参与者很容易理解这项工作的价值。

提前处理则有一个问题:我们无法直接观察那个没有发生的结果。

一个团队说,经过架构调整,避免了一次严重故障。这个说法可能成立,也可能只是把一种担忧包装成了成绩。没有发生故障,可能与改造有关,也可能因为流量没有达到预期,或者相关路径根本没有被触发。

「预防很重要」,但不会接受任何预防性投入。

假设有人提出,某项依赖存在单点风险,需要增加冗余。我希望看到的是:这个依赖失效后会影响哪些功能,现有替代路径是否可用,恢复需要什么条件,团队通过什么方式验证过。

随后才讨论增加冗余的成本,包括建设、切换、演练和持续维护。

故事中的医者似乎能够准确识别尚未显形的疾病。技术管理没有这种确定性。我们面对的是不完整的信息、会变化的业务,以及随时可能被新证据推翻的判断。

因此,观察到了什么,推测了什么,准备如何验证,判断错误时损失有多大。

发现得早,只有在判断质量和处理成本都合适的时候,才值得投入。

把症状写清

我们常常看到当一个问题刚被提出时,责任归属就已经确定了。这是不对的。

项目延期,于是执行力不足;线上出错,于是质量意识不够;评审反复,于是技术能力欠缺。这些解释很容易进入管理讨论,因为它们足够简短,也容易对应到熟悉的动作:催进度、加审核、做培训。

但它们经常缺少可以验证的内容。

以延期为例,我会先把工作过程拆开:需求什么时候达到可开发状态,编码用了多久,等待联调用了多久,返工发生在哪个阶段,验收条件是否改变过。

假设一个任务总共经历了两周,其中多数时间在等待依赖方确认接口。此时要求开发人员提高编码效率,无法覆盖主要损失。继续增加进度会议,还会占用原本可以用于协调和实现的时间。

不过,看到等待时间很长,也不能立即宣布「跨团队协作有问题」。

等待可能来自接口定义不清,也可能来自对方没有资源,还可能因为我们过早启动了一个条件尚未具备的任务。几种情况需要不同的处理方式。统一加一个协调角色,可能只会多出一次信息转述。

可以使用如下的逻辑:已经观察到的事实,基于事实形成的解释,以及准备采取的动作。

例如,几个任务都在同一个依赖环节停留较久,这是事实;对方资源不足,这是待验证的解释;调整双方排期,是一种候选动作。把它们写在一起,会让后面的讨论默认接受前面的推测。

问题描述越接近可观察的行为,团队越容易讨论具体改动。把大量精力花在解释一个人「为什么不够负责」上,通常得不到能够验收的方案。

寻找提前信号

等到事故发生再分析,证据往往比较集中。寻找早期问题,面对的信号要模糊得多。

我会优先关注那些暂时没有形成损失,却持续依赖额外劳动才能维持的环节。

例如,告警频繁发生需要开发介入排查;每次发布都需要某个人手工确认;某类数据每天都要补一次;一个模块只有固定的人敢修改;项目能按时交付,但前提是临时取消测试或压缩验收。

这些现象还不足以证明系统即将失控,但值得进一步检查。因为当前结果里,包含了没有被正式记录的人工投入。

如果只看交付是否完成、服务是否可用,我就无法知道团队为维持这些结果付出了多少补偿性劳动。

我会让团队用较低成本记录几类信息:重复的人工操作、等待外部决定的时间、计划外的工作,以及必须依赖特定人员才能完成的事项。记录的目的,是找到重复出现的位置,不追求把每个人的一天切成几十个时间片。

粒度过细,记录会变成负担,数据也容易围绕考核要求失真。

技术系统的观察也需要类似的取舍。我不会因为担心遗漏,就要求所有路径全量记录详细日志。采样比例、保存时间、字段数量和查询需求,都需要对应到具体诊断问题,并给采集开销和存储费用设预算。

如果新增一组数据无法回答任何决策问题,我倾向于先不采集。

告警同样需要说明后续动作。某个指标越界以后,谁要介入,要判断什么,允许等待多久?回答不了这些问题的告警,我会先把它放进观察范围,避免直接打断值班人员。

提前发现需要足够的信息,却不能靠无限增加信息量完成。可以从几个高损失场景倒推:在结果恶化之前,我们有没有机会看到变化?现有信息能否区分不同原因?缺少的那部分,怎样以最小代价补上?

追问到哪一层

发现重复问题以后,很容易进入另一个极端:不停追问原因,直到得到一个足够宏大的解释。

一次发布失败,可以追到测试不足;测试不足,可以追到排期紧张;排期紧张,可以追到业务压力;最后写成「组织需要加强质量文化」。

讨论完成了,下一次发布怎么做,仍然没有答案。

建议把分析停在当前能够改变、能够验证的层次,同时记录更上层的约束。

假设某次故障涉及配置变更。恢复服务后,我希望继续弄清楚:配置是否经过校验,发布前能否发现异常,变更是否可以独立回退,为什么已有检查没有覆盖这条路径。

如果缺少校验,就讨论怎样增加校验。如果具备校验能力,却因为发布时间紧张被跳过,就需要检查绕过条件和批准权限。再往上,若每次承诺交付时都默认压缩验证时间,排期方式也要进入改进范围。

这几个层次可以同时存在。没有必要强行选出唯一的「根因」。

我更关心哪一项改动能切断已知的失败路径,哪一项能缩短恢复时间,以及哪些约束暂时只能接受。

增加发布审核,是一种常见选择,但我会谨慎使用。它需要持续占用审核者时间,也会让发布等待另一个人的判断。如果某类错误可以通过确定性检查识别,我会优先评估自动校验;如果必须依赖业务背景判断,人工审核才有对应价值。

自动化是一种常见的策略,但是要考虑成本。一个发生频率很低、每次处理只需少量时间的例外,未必值得建设复杂流程。反过来,高频且规则稳定的操作,长期依赖人工检查,我会要求说明理由。

分析问题的深度,应当服务于干预选择。继续追问已经无法改变行动时,我会停止扩展讨论,把精力转向验证。

问题也要排期

发现问题的能力提高以后,待办事项很可能先变多。

代码有债,流程有缺口,人员有依赖,业务假设也有风险。如果每一个发现都必须立即解决,团队会同时启动大量改善工作,原本的交付计划也失去可信度。

管理者需要对「暂时不处理」承担责任。

判断一个问题是否进入排期,我会看几件事:已经造成了什么损失,未来损失可能如何扩大,判断依据有多可靠,处理成本是多少,以及推迟以后是否会失去调整空间。

简单来说就问一个问题:不做什么怎么样,做要多少成本? 这也算是两个问题吧。

假设两项改造都能减少一定维护工作。其中一项随时可以做,另一项涉及即将被多个团队依赖的接口约定。后者一旦扩散,迁移就需要更多参与者配合。我可能优先处理后者,即使它眼前节省的时间更少。

但我不会把这些因素全部塞进一个精确分数,然后按小数点排序。概率、影响和工作量里包含大量估计,算式不能消除不确定性。

对于信息不足的问题,我会单独安排验证工作。

例如,先花一个有限时间窗口检查实际调用情况、做一次容量实验,或者回放一批历史任务。验证结束后,再决定是否启动完整改造。验证任务需要有结束条件,不能以「持续调研」的形式长期占用人力。

我也会区分两种决定:允许风险暂时存在,以及承诺在某个时间解决。前者需要记录接受风险的人、接受范围和重新检查的条件。不能把所有暂缓事项都放进一个没有期限的技术债列表,等下一次出事再说「早就提过」。

有限的资源只能处理有限的问题。管理者的工作包含排序、延后和拒绝,不能只提供一份越来越长的风险清单。

先做有限验证

提前发现的问题,往往还没有足够证据支持大规模改造。我们需要优先寻找能改变判断的小实验。

假设团队认为,交付慢主要来自公共组件不好用。直接重建组件库,投入很大,验证周期也长。可以先选一类重复任务,调整其中一个高频接口,再比较接入步骤、返工次数和总耗时。

这个实验不需要证明新方案在所有场景都更好。它需要回答:当前识别出的障碍,是否确实影响了交付;移除障碍以后,预期变化有没有出现。

实验设计里,我们可以要特别检查比较条件。

两次任务的复杂度是否接近,参与者是否不同,是否有额外辅导,新方案是否获得了旧方案没有的资源。如果这些条件发生变化,就要缩小结论的范围。

如果测试任务都是规则清楚、信息完整的样本,结论也只能覆盖这部分任务。遇到资料缺失、规则冲突或无法判断的情况,系统怎样退出,谁来接手,都要进入验证范围。

有限实验也有局限。它可能遗漏低频问题,额外受到团队关注,或者无法覆盖规模扩大后的协作成本。因此,通过一次实验以后,我会继续限制推广范围,保留回退条件。

我愿意为这些步骤支付一些时间。相较于一次性更换完整流程,分阶段验证让我有机会在投入扩大之前修正判断。

授权与介入

发现问题容易让管理者产生一种冲动:既然我已经看到了,亲自处理应该最快。

在紧急情况下,这可能是合适的选择。但如果每一次复杂问题最后都回到我这里,我就需要检查自己的介入方式。

我会区分三个环节:识别风险、作出决定、执行处理。它们可以由不同的人负责。

管理者发现某个发布环节存在风险,可以要求补充证据、确定负责人和完成期限。除非风险已经超过团队当前能力,或者需要跨越现有权限,否则没有必要接管全部实现。

授权时,我需要交代的是目标、约束、可使用的资源,以及什么情况下必须升级。对于实现细节,我会留出空间。

这里存在实际代价。团队选出的方案可能与我的习惯不同,局部实现也可能没有我亲自处理得快。如果每次出现这种差异,我就收回任务,成员很难积累完整判断的经验。

但授权也不能变成不检查。

我会根据风险安排检查点。可以轻易撤销的局部改动,允许负责人快速推进;涉及数据破坏、跨团队承诺或难以回退的迁移,需要更早检查方案和前提。检查频率随风险变化,避免所有任务都接受相同强度的管理。

我也需要亲自参与一部分现场工作,例如阅读一次故障时间线,跟进一个长期受阻的任务,或者检查一次改造的实际使用情况。

如果信息只来自层层整理后的汇报,我很难判断哪些成本被省略了。亲自接触现场,是为了校准判断;后续仍然要让明确的负责人完成处理和验收。

发现问题之后,我还欠团队一个决定:谁负责、投入多少、何时检查,以及哪些事情因此不做。

让风险能上来

如果我希望团队提前报告问题,就需要检查自己怎样回应坏消息。

成员提出风险以后,立刻被要求证明一定会出事;承认进度存在不确定性以后,被评价为缺乏担当;发现历史缺陷以后,首先被追问为什么之前没有发现。面对这些回应,一个人有理由等证据更充分以后再开口。

等到证据充分,调整空间可能已经缩小。

我会允许风险报告保留不确定性,但要求表达具体:观察到了什么,可能影响什么,还缺少哪些信息,希望获得什么支持。

「这个项目肯定要延期」和「关键依赖尚未确认,如果本周无法完成,我们需要调整后续联调安排」,包含的信息量不同。后一种表达让管理者可以作出决定,也允许后续事实修正判断。

对于善意提出、最终没有发生的风险,我不会仅凭结果认定提出者判断失误。我会回看当时的信息是否足以支持担忧,验证动作是否合理,以及是否因为提前处理才没有继续恶化。

同样,我也不会奖励没有证据的持续悲观。风险提出以后,需要有人补充信息、缩小范围,必要时撤回判断。只负责提醒所有可能性,却不参与验证,会把筛选成本转移给整个团队。

预防性工作的评价也需要证据。

我会看风险路径有没有被验证,改造是否覆盖了它,回退或恢复是否经过检查,后续维护责任是否落实。至于「避免了多少损失」,没有足够依据就不写一个夸张数字。

对于重复的人工劳动,可以比较前后的处理次数和时间;对于恢复能力,可以记录演练中实际完成的操作与耗时;对于协作改善,可以检查等待和返工有没有变化。评价应当尽量落在能够复查的记录上。

这样做会增加一些记录成本。我愿意保留与决策和验收有关的部分,不要求团队为每项改善制作完整汇报材料。

检查已经完成的

还有一类问题,很容易被技术管理忽略:任务完成了,但投入没有产生预期结果。

系统按时上线,功能通过验收,接口符合设计,团队也没有犯明显错误。几个月以后,使用量很低,原有人工流程仍在继续,新系统还多了一份维护责任。

如果我的检查到上线就结束,这类问题不会进入视野。

我会在立项时约定一次投入后的复查。具体检查什么,取决于项目要解决的问题:人工步骤是否减少,用户是否完成了原来难以完成的任务,业务是否真的采用了新流程,旧系统是否按计划退出。

不能用「已经上线」替代这些问题的回答。

如果结果没有出现,我需要重新检查原来的假设。也许我们识别错了障碍,也许方案只处理了一小部分,也许业务条件已经发生变化。继续增加功能之前,先确认追加投入还有没有依据。

停止项目在这里也是一种需要认真评估的选择。它涉及已有投入、人员安排和承诺调整,处理起来往往比批准下一期更麻烦。但维护一个缺少使用理由的系统,同样会持续消耗资源。

发现问题因此也包括检查团队正在解决的问题是否仍然存在,解决方式是否仍然适用,以及目标是否已经改变。

这会改变我安排时间的方式。

我仍然需要参与故障处理,理解技术细节,对交付结果负责。同时,我会固定留出时间检查重复劳动、尚未验证的判断和上线后的实际结果。对于已经暴露的问题,要求处理闭环;对于只有迹象的问题,安排有限验证;对于暂时接受的问题,留下重新检查的条件。

我也会定期回看自己提出的问题。有多少得到了证据支持,有多少被否定,哪些因为迟迟没有决定而继续消耗团队,哪些处理动作带来了新的负担。

否则,所谓发现问题,很容易变成管理者不断向团队追加任务。

从解决问题到发现问题,对我意味着工作范围扩大了:需要参与定义,需要作出取舍,还需要在结果出现以后检查当初的判断。问题提前进入视野,只是给了我们更多处理空间。这个空间有没有被用好,最终仍然要回到具体的行动和结果上。

以上。