拆开 DeepSeek Harness,从 0 到 1 先看懂 Agent
从 DeepSeek Harness 的第 0 章出发,用普通人能理解的方式讲清 Agent、记忆、Token、工具调用、依赖和状态机。
页面准备好后会自动显示。
上一篇我试完 DeepSeek Harness,留下的判断其实很克制。
它现在还不是普通人打开就会舒服的产品。
但它值得观察。因为它想改的,不是某一个模型,而是模型外面那套工作系统。
看完以后,我自己紧接着冒出来的问题不是“那我要不要马上换过去”。
而是:如果它最特别的地方,是把 Harness 拆开给你改,那普通人到底该怎么学这一层?
这篇开始,我会按这个方向写一个系列。
不是再评一次 DSH 好不好用。
是顺着它把 Agent 外面那套东西拆开,看看一套 Harness 到底是怎么搭起来的。
今天只做第一件事。
先把一个 Agent 到底是什么,讲清楚。
很多人一听“我写了个 Agent”,第一反应是:这人是不是把模型调得很聪明。
拆开以后你会发现,答案挺扫兴的。
他写的那部分,基本没有 AI。
模型还是那个模型。
它只会接一段文字,再吐一段文字。
它不会自己读你电脑里的文件,不会真的删东西,也不会记住你上一句刚说完的话。
你平时觉得它“会干活”,是因为外面有一套程序,在替它搬东西、记笔记、动手、再把结果重新喂回去。
这套程序,才叫 Agent。
更准确一点:
Agent 不是一个模型,是一个能自己循环调用模型,并且能真的动手的程序。
Harness,就是承载这套程序的框架。
这个词我也不硬译。
最顺的理解是:模型是马,Harness 是马身上那套挽具。
马再强,没有挽具,它也拉不了车。
挽具再精致,没有马,它也只是一堆皮带和铁环。

所以,为什么要专门造一层 Harness?
因为模型本身只会判断和做决定。
其余那些看起来很智能的事,读文件、跑命令、记对话、控制权限、任务跑到一半被你按停止,全是程序在干。
这些活一个都不需要 AI。
但一个都躲不掉。
你后面会看到,一套 Harness 难的地方,往往不在“模型够不够聪明”,而在这些普通程序有没有被认真设计过。
这可能和很多人现在使用这些模型的体感可能不太一样,但它确实是事实。
大语言模型对外就是一个函数:你给它一段文字,它还你一段文字。
没有别的接口。
它不能读文件,不能上网,不能执行命令,也不能记住昨天你说过什么。
那为什么你在聊天软件里连聊十轮,会觉得它记得你?
不是它记住了。
是聊天软件在第十轮时,把前面九轮的全部内容,重新发了一遍。
但其实模型这一侧,根本没有“第十轮”这个概念。
它只是突然收到一大段文字,里面碰巧抄着前面九轮的对话,然后接着往下写。
你可以把它想成一个每天早上都会彻底失忆的专家。
你要他接着昨天的活干,唯一的办法,是每天早上把昨天的完整工作日志重新念一遍给他听。
他听完就能接上。
你少念一段,他就真的不知道。
这个比方还有一个更狠的地方:
失忆的人至少知道自己失忆了。
模型连这个都没有。
它不是忘了,是压根没有“上一次”。

所以,所谓记忆,不在模型里。
在你每次重新塞给它的那一长串内容里。
写成更直白的公式,就是:
第 n 轮发给模型的内容
= 系统提示
+ 前面你说过的所有话
+ 前面它回过的所有话
+ 你这一轮新说的话
系统提示,就是每次都要重发的开场白:你是谁、能用哪些工具、有什么规矩。
前面它回过的话也得带回去。
因为它不知道自己上次说过什么。
对这一轮来说,它上次说的话,和你说的话一样,都只是输入文字的一部分。
少带一条,它就会前后矛盾。
很多人以为“这个模型能记住 128K 上下文”,意思是它自己存了 128K 的记忆。
不是。
那个数字只是单次输入的长度上限。
上限之内的东西,仍然要你每次原样重发。
这也是为什么一套正经的 Harness,一定要认真对待会话日志。
DeepSeek Harness 里有一句很硬的话:模型可见即已记录。
意思是,凡是送进模型的东西,都必须能从日志里重建出来。
模型没有记忆,所以程序必须记得滴水不漏。
模型不是按“字”算账的,它按 Token 算。
Token 就是文字被切碎之后的最小单位。
英文里,一个常见单词大概是 1 个 Token,长单词常被切成两三个。
中文里,一个汉字大概是 0.5 到 1 个 Token。
具体怎么切,由模型厂商自己的词表决定,别把它当成常数。
你需要建立的,只是量级
为什么要管这个?
因为两件事都按它算。
一是钱。API 按 Token 计费。
二是上限。每次调用能塞进去的文字量,有硬天花板。
现在把“没有记忆”和 Token 上限拼在一起,上下文为什么会炸,就很好懂了。
模型在第十轮时,会把前面九轮所有内容,全部塞进这一轮的输入里。
所以不是“聊着聊着变长了一点”。
是每多一轮,旧账都要整本重念一遍。
轮数翻一倍,重发的东西不是翻一倍,是越叠越厚。

这也是为什么 Agent 跑长任务时,账单会突然失控。
因为它每做一步,都要把前面做过的事重新搬出来给模型看。
为了解决这个问题,Harness 里通常会有压缩策略。
但你先记住边界:压缩不是让模型拥有了记忆,只是程序开始决定,哪些旧内容不再整段重发。
有东西被剪掉,模型就再也看不见。
所以压缩从来不是免费的。它是在拿准确性换空间。
到这里,模型还是只会说话。
让它能动手的机制,叫工具调用。
这一段是整篇文章的地基。
如果没有工具调用,Agent 就只能聊天。
你问它“这个目录里有几个文件”,它只能猜,或者编。
你说“帮我把这个文件删了”,它很可能会回你一句“好的,已删除”。
然后文件一个字节都没动。
模型可以调用工具
真实过程只有四步。
先由你的程序,在发请求时附上一张工具清单:每个工具叫什么、干什么、要哪些参数。
模型读完,如果觉得该动手,就吐出一段结构化的文字,大意是:我要调用 read_file,参数是这个路径。
注意,它只是说了这句话。什么都还没发生。
然后,你的程序解析这段文字,发现这是一个工具调用请求,才真的去打开文件、读出来、删掉、或执行命令。
这一步是纯粹的普通编程,一点 AI 都没有。
最后,程序再把结果包成一条新消息,接在对话后面,把整段对话再发一次给模型。
模型这才“看到”文件内容,或者“知道”删除有没有成功。

这张图你先看中间那个人。
左边是真实世界:文件、命令、网络。
右边是模型:它吃文字、吐文字,什么都碰不到。
中间的 Harness,才是唯一能伸手进真实世界的那一层。
真正改世界的那一步,没有 AI。
所以,你以后再看到“Agent 把文件删了”,更准确的说法是:
模型决定删,程序真的删,然后再把结果念给模型听。
没有中间这套程序,模型它也只是一个函数。
如果你看到这里已经有点晕,我们先理清几个概念。
一套 Harness 后面会用到一些程序概念,但普通人不必先去学一门语言。
你只要先把这几样东西认出来。
对象(Object):一只贴了名字的袋子。
里面装的不是一堆散着的内容,而是能按名字取出来的东西。
一次工具调用就是一个对象。袋子上写着:这次叫什么、要调哪个工具、参数是什么。
数组(Array):按顺序排好的一排东西。
整段对话在程序里,就是这样一排消息。
前面说的“每次把旧账整本重念”,念的就是这排东西。
函数(Function):一段能被唤起的程序。
你叫它一次,它就按规则干一次。
读文件、问模型、保存日志,落到程序里都是一次函数调用。
等待(await / Promise):先撕一张收据,结果还没到。
问模型要几秒,读一个大文件也要一会儿。程序遇到这种活,不会傻站着。它会先记下“回头来取”,然后继续往下走。
那张收据,专业名称叫 Promise。等它回来,叫 await。
你要是不等它回来,拿着这张空收据就当结果用,后面拿到的就会是空的。
所以等待不是客气,是你得承认:有些结果现在还没到。
回调(Callback):把一段活交给别人,让对方在对的时候帮你做。
你不用认识对方,你只负责说:等这件事发生了,请跑这段。
事件(Event):有人广播,有人收听。
有人喊“工具被调用了”,有人听见了就动手。
播出去叫 emit,收听叫 on。
播的人不用知道谁在听,听的人也不用认识播的人。
DeepSeek Harness 后来那句“一切皆插件”,落到最小形状上,其实就两样东西:
我需要什么。
给我现场,我就去登记自己能做什么。
你先有这些图像就够了。
后面所有复杂架构,都是从这几样东西长出来的。
依赖这个词很朴素。
A 要用 B 提供的东西,那就说 A 依赖 B。
问题出在启动顺序上。
如果 A 启动时就要用 B,那 B 必须先准备好。
两个模块时,你手写顺序就行。
五个也还行。
等模块多到你改一个依赖,就要重排整张表时,手写启动顺序就会变成灾难。
而且漏了还不一定马上报错。
它可能只在某台电脑、某个网速、某个罕见路径上突然炸。
典型症状是:你这边跑得好好的,换一台机器就变成“工具箱是空的”。
原因可能只是某个模块比工具注册表早启动了半毫秒。
DeepSeek Harness 底下那套框架,做法很有意思。
它不排顺序,只声明依赖。
插件先说清自己需要什么服务,然后等这些服务就绪了再启动。
加载顺序由依赖自己长出来,不靠人手工编排。
像做菜。
你不用规定先开火还是先切菜。
你只要写清:炒这一步需要切好的菜,和烧热的锅。
剩下的顺序,自己就定了。

这个设计漂亮,也有它的阴暗面。
如果 A 需要 B,B 也需要 A,两边会互相等。
结果不是崩溃,是静默地什么都没发生。
屏幕上可能连一行红字都没有。
这比报错更难查。
所以你会理解,为什么这种框架必须提供“把当前配置树打印出来”的能力。
不是为了显得专业。
是因为卡住和坏掉,看起来太像了。
最后一个地基,叫状态机。
一样东西在任何时刻,只能处于有限个状态里的某一个,并且只能沿着规定好的箭头,从这个状态转到另一个。
像红绿灯。
红、黄、绿三个格子。
箭头是规定好的,不能红灯直接跳黄灯。
为什么程序也需要这个?
因为你如果不用状态,就会写下好几个开关:正在加载、已经完成、出错了、取消了。
四个开关能组合出十六种可能。
其中大概只有几种是合法的。
剩下那些,就是“怎么会同时又在加载又已经出错”的诡异 bug 发源地。
状态机的价值,就是把不该存在的组合直接拿掉。
DeepSeek Harness 里,一个插件实例大概会经过这些格子:
在等依赖,还没开始。
正在启动。
已经跑起来。
启动失败了。
正在拆。
被拿掉了,回不来了。
这里最值得普通人记住的,不是这些英文名。
是“在等”和“失败了”不是一回事。
在等,意思是条件还没满足,条件一到我就起。
失败了,意思是我试过了,我起不来。
如果只用一个“坏了”的开关,这两件事会混成一件。
你就永远搞不清,到底该去补一个依赖,还是该去修一个 bug。

这也解释了,为什么一个最小 Agent 看起来只是一个循环,一旦你加上“用户随时可以按停止”,它就不得不引入状态。
因为停止不是一瞬间完成的。
模型请求可能已经发出去了,某个命令可能已经跑起来了。
程序得先知道自己现在在干什么,才知道该怎么收尾。
没有状态,它就只是埋头往下跑,不知道自己走到哪一步了。
了解agent harness ,先要记住下面这三点。
Agent 不是模型,是围着模型转、并且能真的动手的程序。
模型负责判断和做决定,程序负责执行、记忆和收尾。
工具调用里,真正改世界的那一步,没有 AI。
有了这三句,你再看 DeepSeek Harness,就不会只看见“又一个聊天框”。
你会开始看见挽具。
下一篇,我会顺着这层理解往下拆:一个最小的 Agent 循环,到底是怎么转起来的。
还不写完整产品。
先把“说一步、做一步、再把结果喂回去”这件事,落到能看懂的结构上。
以上也是我个人学习中的一些笔记整理,如果你觉得对你有所帮助,感谢你的转发收藏,如果哪里有问题,欢迎指出,我及时改正~