我在一个智能体框架里,看到了老朋友
前几天我又把 DeepSeek Harness(下面简称 DSH)的文档翻了一遍。上一篇博客里,我把它类比成「智能体的 VSCode」,核心是插件生态。可这次换个角度再看,我脑子里冒出来的却是另一个老朋友:Spring Boot。
这个感觉有点奇怪。一个是大模型周围的智能体运行时,一个是 Java 后端里被写了无数年的框架,看起来八竿子打不着。但当我看到 DSH 的 Cordis 内核时,脑子里立刻浮现出 Spring 的 ApplicationContext、依赖注入和自动装配。它们都在做同一件事:不让能力散落在代码堆里,而是把能力交给一个运行时去组装、替换和管理。
这件事让我重新想了一个老问题:AI 能大量生成代码以后,程序员的竞争力到底在哪里?我的答案越来越明确——不是放弃写代码,而是架构设计的权重变高了。
Spring Boot 早就在教我们这件事
先说 Spring Boot。你写的当然还是 Java 代码,但一个 Spring 应用能跑起来,靠的不只是那些类和方法,还有背后的容器。
Spring 会把许多对象注册成 Bean,再根据声明好的依赖把它们装配起来。官方文档里的推荐方式也很直白:用构造器注入把依赖交给 Spring 管理。到了 Spring Boot,又多了一层自动配置——它会根据 classpath 里有哪些 jar 包来帮你装配默认行为。比如 HSQLDB 出现在依赖里,而你又没有手动定义数据库连接 Bean,Spring Boot 就会自动配置一个内存数据库。
这里最关键的不是「少写配置文件」,而是它划出了边界:
- 你可以提供自己的
DataSource,默认的内存数据库支持就会退后; @SpringBootApplication只是打开自动配置、组件扫描等一组机制,你也可以不用它;- Starter 把某个能力需要的依赖打包成一个约定,让你按能力引入,而不是到处复制粘贴依赖。
Martin Fowler 解释控制反转时有句话特别好:「Don't call us, we'll call you。」你不是随时冲进容器里指挥一切,而是把组件放进去,由框架在合适的时机调用你。这就是框架的力量:它先给你一套生命周期和扩展点,再让你的代码有地方安放。
所以,熟练的 Spring 开发者不会只问「这个类怎么写」,而会问:
- 这是领域逻辑,还是框架适配层?
- 这个依赖应该注入进来,还是自己 new 出来?
- 哪些地方允许替换?哪些默认行为必须能退场?
- 数据库、Web、事务、消息这些关注点,边界画在哪里?
这些问题,比某一行代码漂亮不漂亮更影响项目的命运。
一个小例子:换掉通知服务时才知道疼不疼
假设你在做一个课程管理系统。学生提交作业后,系统要发一条通知。最直觉的写法,是在 AssignmentService 里直接调用邮件客户端:
@Service
public class AssignmentService {
private final EmailClient emailClient = new EmailClient();
public void submit(Assignment assignment) {
// 保存作业、校验格式、计算提交状态……
emailClient.send(assignment.studentEmail(), "作业已提交");
}
}
这段代码能跑,但它的边界很糟糕。邮件客户端在业务服务里被写死了;想改成短信、站内信、企业微信,就得打开业务代码动刀;想在测试里不发真实邮件,也只能靠魔法或者注释代码。
更好的做法是先把「通知」这个能力抽象出来:
public interface NotificationSender {
void send(User user, String message);
}
@Service
public class EmailNotificationSender implements NotificationSender {
public void send(User user, String message) {
// 调用邮件服务
}
}
@Service
public class AssignmentService {
private final NotificationSender sender;
public AssignmentService(NotificationSender sender) {
this.sender = sender;
}
}
这时 AssignmentService 只知道「有一个能发通知的东西」,不知道它到底是邮件、短信还是测试里用的内存实现。Spring 容器负责把具体实现装进来。明天你加一个 FeishuNotificationSender,只要调整配置或条件装配,业务服务可以不动。
这个例子里真正值钱的,不是那几行接口声明,而是你做了一个取舍:业务流程依赖稳定契约,不依赖易变渠道。 AI 可以很快帮你补出三个 Sender 实现,但如果它一开始就让你把邮件 API 散落在二十个类里,后面每一次改通知渠道都是一次小型拆迁。
Spring Boot 的自动配置也在处理同类问题。它根据 classpath 判断你可能需要什么默认能力;当你显式给出自己的 Bean 时,很多默认实现会退后。这不是偷懒,而是一种可退出性:框架给默认值,但不霸占决定权。
DSH 只是把同一套思想搬到了智能体上
再看 DeepSeek Harness,你会发现它的文档几乎就是这套思想的另一种语言版本。
DSH 是一个开源智能体运行时,目前还是 Developer Preview,MIT 协议,底层用了 Cordis 插件框架。官方架构文档写得非常狠:模型适配器、工具注册表、会话日志、甚至 agent loop 本身都是插件。也就是说,连「AI 怎么一轮一轮思考、调用工具、继续任务」这个看起来最核心的东西,也不是焊死的内核,而是可以被配置替换的一部分。
Cordis 的几个概念也很像 Spring:
| Spring Boot | DeepSeek Harness |
|---|---|
ApplicationContext | Cordis Context |
| Bean | Plugin / Service |
构造器注入、@ComponentScan | inject 声明服务依赖 |
| Auto Configuration | Profile、Bundle、Patch 组合 |
| Application Event | Typed Event |
| 用自定义 Bean 替换默认配置 | 用插件或 patch 替换某一行配置 |
在 Cordis 里,一个 context 是服务的仓库。服务会占据类似 ctx.tools、ctx.llm、ctx.sessions 这样的稳定位置;别的插件通过 key 找能力,而不是直接导入某个具体实现。插件还可以用 inject 声明依赖,等依赖就绪后再加载。注册出去的能力也是可逆的 effect,插件卸载时可以按预期回收。
这跟 Spring 的思路几乎同构:组件不认识彼此的实现细节,只认识契约;容器负责发现、装配、生命周期和回收。
更有意思的是 DSH 的基础层 dsh-base。里面不仅有模型适配器和工具,还有持久化、沙箱、审批策略、设置、凭证、遥测。换句话说,智能体系统的关键早就不是「模型有多聪明」这一项,而是围绕模型的一整套工程约束。
LangChain 那篇讲 agent harness 的文章给过一个简洁公式:Agent = Model + Harness。模型负责智能,Harness 负责状态、工具执行、反馈循环和可执行的约束。Cloudflare 的文档也说得类似:harness 是让一次模型调用变成智能体的循环,它要处理提示词构建、记忆、工具选择、工具结果、消息持久化、流式响应,以及该继续还是停止。
你看,这不就是 Spring 容器干过的事吗?只是过去被组装的是 Controller、Service、Repository,现在被组装的是模型、工具、沙箱、会话和审批策略。
Cordis 不是概念游戏,它把边界写进了机制
如果只看「插件化」三个字,容易觉得这是营销词。Cordis 有意思的地方在于,它把依赖、事件和生命周期都变成了运行时规则。
官方文档里的思路可以简化成这样:一个插件声明自己需要哪些服务;等服务就绪后,它拿到一个 context,再把自己的能力注册进去。伪代码大概长这个样子:
// 只是为了表达结构,不是完整可复制代码
export const inject = ['llm'];
export function apply(ctx) {
ctx.tools.register({
name: 'search_notes',
schema: { /* 参数说明 */ },
async run(input) {
const llm = ctx.llm;
// 调用检索能力、整理结果
},
});
}
这里的重点是:工具不直接 import 某个模型 SDK,也不关心另一个插件怎么实现会话存储。它只认识 ctx.tools 和 ctx.llm 这些稳定位置。这很像 Spring 里组件通过接口和容器协作,而不是互相 new 出对方。
Cordis 的事件系统也很讲究。事件不是随便字符串乱飞,而是有类型的契约,并且分成几种派发方式:有的只是广播给监听者;有的会把结果沿着监听链传下去;有的并行执行;有的按顺序执行。放到智能体场景里,这件事非常关键。比如「模型请求发出前」「工具执行前」「回合即将结束」这些时机,都需要有人能观察、改写或者拦截。
想象一个审批插件。它不需要重写 agent loop,只需要挂到工具执行前的事件上:
- 如果是读本地文档,直接放行;
- 如果是执行 shell 命令,检查沙箱策略;
- 如果要访问外网,弹出人工确认;
- 如果命令里带了密钥,直接拒绝并留下审计日志。
安全策略没有糊在业务循环里,而是挂在明确的接缝上。想换一套更严格的公司内部策略,换插件就行。这就是架构带来的自由:变化频繁的地方,被隔离到了可替换的位置。
DSH 的 profile 和 bundle 也值得看。官方文档把一次运行的 DSH 描述成启动时组合出来的插件树。profile 是一份命名组合,列出要叠哪些 bundle,还能带用户自己的 patch;bundle 是配置行和代码的分发格式;dsh-base 是第一层基础能力,里面有模型适配器、工具、持久化、沙箱、审批策略、设置、凭证和遥测。Web UI 和 headless 运行器则作为不同模板叠上去。
你可以把它理解成 Spring Boot Starter 的更细粒度版本:Starter 把一组相关依赖和默认行为打包好,profile/bundle/patch 则决定这次智能体运行到底装载哪些能力。想看实际生效的组合,不必逐行猜源码,可以用类似 dsh --profile web --dump-config 的方式查看配置树。这一点对复杂系统特别重要——装配关系必须可见,否则出了问题就只能靠玄学排查。
还有一个设计我特别喜欢:session log。DSH 的会话日志是 append-only 的,上下文派生、回放、fork、遥测和持久化都从这条流来。文档甚至给了硬约束:凡是能进入模型请求的内容,都必须能从日志重建出来。
这句话听起来朴素,做起来很难。很多 AI 应用的状态散落在前端组件、后端内存、数据库表和第三方服务里,用户看到一段回答,却没人说得清当时模型到底看见了什么。等你要调试、审计、复现事故,就会发现每个模块都有自己的记忆方式。DSH 把「模型可见即留痕」变成架构不变量,等于提前堵住了这类混乱。
为什么 AI 时代架构更重要了
有人说 AI 会让人人都能写代码。这句话对了一半。AI 确实让「写出一段能跑的代码」变得便宜了,但它没有让下面这些问题消失:
- 这个模块该对谁暴露能力?
- 哪个组件有权决定模型能看到什么?
- 工具调用失败后,谁负责重试、回滚或者停下来?
- 沙箱、审批、凭证、日志放在哪一层?
- 换掉一个模型供应商时,会不会牵一发动全身?
这些都是架构问题。AI 可以帮你补齐实现细节,但如果边界画错了,它会很快生产出一堆正确却无法共存的零件。
Fowler 讲软件质量时有个观点:用户看得见界面和缺陷,却看不见内部架构;但内部质量决定了未来修改成本。好的模块划分能让你只理解几百行相关代码,而不是每次都啃五十万行。vFunction 的文章也提到,一段代码的返工成本往往取决于它牵连了多少依赖;如果 GenAI 让代码产量变大,架构质量只会变得更关键。
这不是空话。放到智能体系统里,架构直接决定产品能不能安全长大。
比如 DSH 的 session log 有一个很强的原则:model-visible means logged。凡是能进入模型请求的内容,都必须能从日志重建出来。这就不是一句口号,而是架构约束。它让调试、回放、fork、遥测和持久化都从同一条事件流派生出来。没有这条边界,后面每个功能都会长出自己的日志格式,最后谁也不敢动。
所以我说的「架构比写代码更重要」,不是轻视代码。恰恰相反,好的架构最终也要落到代码、测试和重构里。Uncle Bob 甚至提醒过:快速、可信的测试能让架构持续保持干净,因为没有测试,人们就不敢改架构。我想强调的是另一件事——当代码的生产成本下降,判断力就成了稀缺资源。
你要判断的不再是「这一行怎么写」,而是:
- 哪些变化必须隔离?
- 哪些决定不能提前锁死?
- 哪些默认值要能被替换?
- 哪些动作必须留下痕迹?
- 哪些权限永远不能交给模型自主决定?
这五问,Spring Boot 回答过一遍,DeepSeek Harness 又在新场景里回答了一遍。
智能体系统里,架构决策到底长什么样
把话题拉回更具体的场景。假如你要做一个「帮我整理课程资料」的智能体,Demo 版本可能很简单:用户上传文件,模型读一遍,输出摘要。可一旦它要被同学反复使用,问题马上就变了。
第一层是上下文架构。
模型不可能永远记得上个月的对话,也不能一次吞下整个课程目录。你得决定:哪些内容放进系统提示?哪些放到会话历史?哪些只作为工具按需检索?上下文快满时怎么压缩?哪些中间结果应该写到文件或数据库?
这不是 prompt 技巧,而是状态管理。Spring 应用里你不会把所有数据都塞进 HTTP Session;智能体系统也不该把所有信息都塞进 context window。谁负责短期记忆、长期记忆和工作区,必须提前分清。
第二层是工具契约。
「搜索笔记」「读取 PDF」「创建复习计划」「提交作业」都可以变成工具。但每个工具的入参、出参、错误语义、权限要求、幂等性,都要写清楚。模型调用一个工具时,本质上是在依赖一份契约。
如果契约模糊,模型可能会猜。猜对了叫聪明,猜错了就叫事故。比如同一个 delete 工具,有的地方意味着删除草稿,有的地方意味着删除数据库记录,这种设计迟早会出问题。工具注册表的价值就在这里:能力先登记,再暴露给模型;执行前有统一管道检查,而不是让模型直接摸到系统内脏。
第三层是安全边界。
普通 Web 应用的信任边界比较清楚:请求从浏览器来,经过鉴权、业务规则和数据库权限。智能体系统复杂得多,因为模型会生成下一步动作。它可以请求执行命令、访问文件、调用外部 API,甚至生成一段看起来合理的代码。
所以沙箱不能只是可选配件,审批策略也不能写成一句 system prompt 就完事。你需要回答:哪些动作允许自动执行?哪些需要人工确认?命令能不能访问网络?文件系统暴露多大范围?凭证放在哪里?用户点了取消以后,正在跑的子进程会不会真的停?
这些问题在 Spring 里也有对应物:事务边界、权限校验、参数校验、审计日志。区别在于,智能体系统的动作空间更大,出错方式更难预测,所以边界必须更明确。
第四层是验证循环。
人类开发者写完代码会编译、跑测试、看日志。智能体也需要类似的反馈回路。一个可靠的 coding agent 不该只是「看起来写完了」,而应该运行格式化工具、单元测试、类型检查,甚至启动应用做冒烟测试。
这些验证器属于 harness 的一部分。它们给模型提供客观信号:测试红了,就把失败信息交回去修;lint 不过,就继续改。没有验证循环的智能体,就像没有编译器的开发团队,全靠自信交付。
第五层是多智能体协作。
任务一复杂,你可能想让主 agent 拆任务,再让几个子 agent 分别查资料、写代码、跑测试。这时新的架构问题出现了:子 agent 能看到多少上下文?它们往哪个共享工作区写文件?谁负责合并结果?两个 agent 同时改一个文件怎么办?父任务失败时,子任务要不要取消?
这些问题很像微服务里的服务拆分。拆得太粗,还是一坨;拆得太细,通信成本爆炸。真正重要的不是「有几个 agent」,而是它们的职责、状态和通信协议是否清楚。
你会发现,每一层都不太像传统意义上的「写算法」,倒更像老老实实的系统工程。模型越强,这些层的收益越大;模型越弱,这些层越是救命绳。
给正在学编程的同学
如果你还在学校,我觉得没必要焦虑「AI 会不会取代程序员」。真正值得焦虑的是,你是否只在练「把需求翻译成语法」的能力。
语法和 API 当然重要,但你可以开始多问一层:
- 我这个项目里,哪部分变化最快?把它隔离出来了吗?
- 如果明天换数据库、换模型、换存储,需要改多少文件?
- 一个第三方能力挂了,我的系统会优雅降级,还是直接崩掉?
- 用户数据、密钥、执行命令的权限,有没有清晰边界?
做课程项目时也可以故意给自己设一点约束:不要只写一个大 main.py 或一个巨型 Service 类;试着把「业务规则」「外部服务适配」「入口协议」「持久化」分开;再想想哪些地方可以用接口或配置替换。哪怕项目很小,这种训练也会比多背十个面试题有用。
Spring Boot 花了多年时间证明:好的框架不是替你思考所有问题,而是把重复的装配工作收走,让你专注于真正的业务差异。DeepSeek Harness 正在做类似的事,只不过这次被组织起来的对象变成了模型、工具、沙箱、会话和策略。
代码是材料,架构是承重墙。AI 能很快搬来更多材料,但承重墙画错了,房子盖得越快,拆起来越痛。
下次看到一个炫酷的 AI 框架,别急着只问它接入了哪个模型。先看看它怎么定义边界、契约和替换点。那里才藏着一个系统未来能不能长大的答案。
参考资料
- DeepSeek Harness Architecture
- Cordis Primer | DeepSeek Harness
- DeepSeek Harness GitHub 仓库
- Spring Boot Auto-configuration
- Spring Beans and Dependency Injection
- The Anatomy of an Agent Harness - LangChain Blog
- Harnesses - Cloudflare Agents Docs
- Inversion Of Control - Martin Fowler
- Is High Quality Software Worth the Cost? - Martin Fowler
- The true measure of software quality: Architecture or code? - vFunction
评论交流
欢迎留下你的想法