从一次36分钟的翻盘学习DSH是怎么做 Loop Engineering的——智能体基建系列

从一次36分钟的翻盘学习DSH是怎么做 Loop Engineering的------智能体基建系列

如果你只关心「哪个模型更聪明」,这篇可以先划走。

我想聊的,是一个被很多人忽略、却决定一个 agent 能不能把活干成的能力,叫 loop engineering,循环工程。说直白点:当模型调一次工具失败、当任务跑到一半被叫停、当进程突然崩溃,agent 靠什么不白干、能续上、能复盘?

我拿 DeepSeek 刚开源的 agent 框架 DeepSeek Harness(dsh)当解剖样本。先用一个真实对比故事开场,同一个看似十秒的活,我的 agent 躺了,dsh 磨了 36 分钟翻盘,然后我会扒开它的源码,用最朴素的比喻把它的循环讲清楚。不爱看故事的技术读者,可以直接跳到「我翻了 DeepSeek Harness 的源码」那一节。

先交代个背景。

我最近在给自己的自媒体工作台产品文渡做智能体,代号阿文。说是智能体,其实是我养的一个小同事,替我跑腿干活写文章。为了让它真能上手办事(查阅资料、实践验证)而不是嘴上功夫,我一直在给它补执行层的底子,loop engineering,循环工程,就是我最近死磕的方向。

这个词听着玄,测起来特别朴素。

为了验证阿文的实践能力,我给阿文出了道真题。

请在本机电脑上运行nvm命令展示一共有多少个版本的nodejs

人干这活要多久?打开终端,敲 nvm ls,回车,数一数,十秒钟的事。

阿文上场了。

结果刚一动手就撞墙。Windows 突然弹出一个对话框,标题写着 Terminal Only,正文是「NVM for Windows should be run from a terminal such as CMD or PowerShell.」。翻译一下就是,我这玩意只认真终端,你在别处调我,免谈。

阿文试了一轮,换个姿势又试一轮,弹窗跟钉子户一样杵在那不动。几轮下来,它停了,任务失败。

就是下面这个画面。

说真的,我有点下不来台。一个十秒钟的活,我的智能体干砸了。

我不甘心,想找个参照系。正好 DeepSeek 前不久开源了一套 agent 底层框架,叫 DeepSeek Harness,简称 dsh。我把同一道题,一个字没改,丢了过去。

好家伙,开场一模一样。

同一个弹窗,连标点都不带差的,弹了出来。我盯着屏幕,心想也不过如此嘛。

然后我就闭嘴了。

它没停。这条路不通,换个路子再来。还不通,再换。中途它还上外网查资料,翻 nvm-windows 的官方 issue,一个坑一个坑往外爬。就这么磨,磨了整整 36 分钟,一轮任务里跑了 27 步,烧了 55 万多 token。

最后,它交卷了:本机装着 v14 和 v24 两个版本。它还顺手报了个数,Node.js 官方发布库里可下载的版本一共 868 个。

更骚的是它交卷还附赠了一份避坑报告。

它先发现沙箱有 ACL 故障,每条命令还没跑就被权限步骤卡死,于是切到宽权限模式绕过去。又查到 nvm-windows 有个官方挂了号的缺陷:nvm.exe 启动时会先探一下 stdout 是不是字符设备,不是的话就弹一个模态框拦人,挂起等你点 OK,没人点就一直挂死,就算点掉它也是 exit(0)、零输出。对应 issue 1189 和 1119。最后它干脆用 Win32 API 的 AttachConsole 加 ReadConsoleOutputCharacter,把 nvm 藏在真实隐藏控制台里的屏幕缓冲区整个抓了下来,才拿到完整输出。

我看着这份报告,一时间无语凝噎。

同一个坑,一边几轮躺平,一边死磕半小时翻盘。

可能你会说这不公平,模型不一样。还真不是,dsh 那场跑的是 glm-5.3-flash,市面上真算不上顶配,而阿文跑的模型还是gpt6-luna。

那答案就只剩一个了,循环不一样。

我让AI把 dsh 的源码翻了。就一个念头,看看它是如何实现loop engineering的。

下面这段我尽量说人话,不写代码的朋友也能跟上。我会用「办事窗口」这个比喻串起 dsh 的循环,再用三张图把它的骨架画清楚。

先把 agent 想成办事窗口

整个 agent 就是一个永远不下班的办事窗口。

你递一张条子,那是任务进来了。窗口后面坐着的柜员,是模型(LLM)。柜员要查系统、要盖章、要跑流程,那是调工具(tool call)。这个窗口最难的,从来不是「怎么调一次模型」,而是活干砸了怎么办、干到一半被喊停怎么办、能不能不白干。这套调度,就是 loop engineering,循环工程。

dsh 给这个窗口定了一堆很「轴」的规矩,轴在每一条都为了同一件事,可恢复,可复盘。(下面出现的 agent.ts、inbox.ts、tool-calls.ts,都在 packages/core/agent-loop/src/ 下。)

一、两层循环:turn 包着 step

柜员一天分两层节奏:

  • 一轮(turn) :接待一位客人,从「开始办」到「办完闲下来」。turn/start → ... → turn/end。
  • 一步(step):这一轮里柜员「想一次 + 做一次」。想,就是调一次模型;做,就是跑模型要的工具。

代码里是两层 while:外层 kick() 问「还有客人排队吗,有就开新一轮」,内层 turn() 问「这一步干完,还要再想一步吗」。一句话,一轮对话里柜员会「想→做→想→做」地循环,直到没事可干。dsh 那 36 分钟,就是 27 个 step 一步一步连出来的。

看着平平无奇对吧,阿文的循环也长这样。真正拉开差距的,是下面几样。

二、三种状态:怎么摆平「活儿啥时候来」

柜员不是简单地「干 / 不干」,而是三种状态(agent.ts 里的 Phase):

状态 类比 说明
idle 闲着,灯灭 没活,不占资源
running 正在给客人办 手里有一轮对话在跑
maintenance 盘点 / 打扫中 跑后台维护,不能接客

烧脑点在竞态 :客人可能在柜员「正在盘点」或「正被喊停」时递条子。dsh 的解法是给每个 running / maintenance 配一个独立的「中止开关」(AbortController),再贴一张**「结束后叫我」的便利贴**(wakeRequested 闩锁):

  • 盘点时来活 → 贴便利贴,盘点完自动补叫号;
  • 正被叫停时来活 → 直接算「下一位客人」,开新的一轮,绝不混进已作废的那单。

这样不管活儿什么时候来,窗口都不会乱、不会重复办、也不会漏办。

三、收件箱:两种便签,还是账本式的

窗口前只有一个收件箱(inbox.ts),但便签分两类:

  • next-turn(下一位客人):下一轮对话才用,相当于「取新号」。
  • next-step(插话):影响当前这步,相当于「客人凑到窗口说:等等,先顺手把这也办了」。

关键是:这个收件箱不是写在便签纸上,是写进「总账本」的 (agent/inbox/spliced 事件)。所以柜员中途崩溃重启,翻账本就知道「还有谁在排队、插了什么话」------这就是它能断点续传的底子。

四、最反直觉的:柜员不靠脑子记,靠账本

这是 dsh 跟很多框架最大的不同(agent.ts 的 buildRequest):

铁律:模型能看见的任何东西,都必须先写进账本(session log)。

每一步要发给模型的内容,柜员不拿内存里的草稿 ,而是:① 先把这一步发生的事(说了啥、调了啥工具、结果啥)写进账本;② 再从账本里重新读一遍 (session.deriveMessages()),冻结,再发出去。

你品品这个反直觉。正常人写 agent,上下文垛在内存里,程序一崩全没了。dsh 偏不,它每一步都当自己下一秒会失忆,只信账本。这就像飞机的黑匣子------不存当下姿态,存一整串飞行记录,出事把记录拖出来一帧帧重放,过程就还原了。

这条铁律落到 nvm 那一局,效果特别直观。沙箱 ACL 挂了,账本记一笔;弹窗把进程挂死,账本再记一笔。每记一笔,模型下一步就多知道一条此路不通。走到第 27 步,它脑子里装着前面每一步的尸体,所以一翻到 issue 1189,立刻明白「重定向会触发弹窗把进程挂住,那我不重定向,直接把屏幕缓冲区抓下来」。前面 26 步,一步没浪费,全是垫脚石。

五、工具怎么排:独享 vs 并行,但必须保序

柜员要办的事分两种调度(tool-calls.ts):

  • exclusive(独享):像「进金库」,必须单独办、办完再下一个(barrier,独木桥)。
  • parallel(并行):像「同时盖几个章」,进有上限的并行池(bounded rolling pool)。

硬规矩:结果必须按模型说的顺序记到账本(model-order commit)。可以几个章同时盖,落账时老老实实排队写,不能乱序。中途被叫停,已开始盖的章照常记结果,还没轮到的补一条「已取消」占位记录------保证账本永远完整、可重放。

工具执行失败,也不许抛异常把程序弄死,而是往账本记一条带错误标记的结果,下一眼模型就能看到。这点太关键:agent 干长任务,死法从来不是一步摔死的,而是摔了一跤却不知道自己摔了,揣着错误认知继续走,走进沟里。弹窗就是阿文那几轮的坑,但它没能变成一条阿文读得懂的记录,试了几轮始终没搞明白自己怎么死的,自然翻不了盘。

六、出错和被叫停:都关在一步里

  • 模型调用失败:先走「能不能重试」的裁决,能重试就重来,且重试不重做前面的准备。
  • 被取消:把原因(客人撤单 / 上级喊停 / 系统退出)写进账本,算这一轮正当结束,不算崩溃。
  • 任何报错:都收在「当前这一步」的边界里,不许炸穿整个窗口。

通俗 ↔ 术语对照

通俗说法 代码真名 位置(相对 packages/core/agent-loop/)
办事窗口 / 柜员 ReactLoopAgent(Driver) src/agent.ts
一轮接待 turn src/agent.ts
想一次 + 做一次 step src/agent.ts
窗口三态 Phase 状态机 src/agent.ts
中止开关 AbortController src/agent.ts
「结束后叫我」便利贴 wakeRequested 闩锁 src/agent.ts
收件箱 ReactLoopInbox src/inbox.ts
下一位客人 / 插话 next-turn / next-step src/agent.ts
总账本 session log src/agent.ts
从账本重读请求 deriveMessages() src/agent.ts
金库独享 / 并行盖章 barrier / rolling pool src/tool-calls.ts

当然,这套窗口的规矩远不止这些。三种状态怎么仲裁、插话怎么排队、工具怎么限流,每件单拎出来都够写一篇。今天先按下不表,捡最要命的聊。

一句话总结,模型负责聪明,循环负责让聪明活到最后。

我后来一想,这套东西根本不是新发明。软件工程里有个老概念,叫事件溯源,event sourcing。别存当前状态,存一串不可变的操作日志,任何时候想回到过去,重放日志就行。dsh 就是把事件溯源焊进了 agent 的每一次呼吸。

你发现没有,越底层的工程,越像在跟不确定性较劲。模型会超时,工具会挂,系统会弹窗,进程会死。dsh 的态度很明确,这些我都控制不了,但我让每一刻都可被记录,可被重来。

想通这一层,我反而兴奋了。

因为这说明 agent 的智能,不只长在模型里,也长在循环里。同一颗脑子,套上不同的循环,就是两种生物。这也正是我在文渡里死磕 loop engineering 的原因,这口气必须争。

同一个弹窗,两种结局。差的不是智商,是摔倒之后,有没有一本写满教训的账本,和一个把你扶起来接着跑的循环。

以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标。

谢谢你看我的文章,我们,下次再见。

相关推荐
java资料站1 小时前
十五、Spring AI Alibaba · 高级 ·Graph
java·人工智能·spring
明志数科1 小时前
具身智能机器人从演示验证到常态化部署的工程差距分析
人工智能·机器人
晨间晓1 小时前
图片边缘主要朝哪里?用华为云码道开发 OpenCV 梯度方向分析工具
人工智能·opencv·华为云
果霸大叔1 小时前
AI网关第8篇:AI 代理与负载均衡--多模型实例如何调度?
人工智能·算法·自然语言处理·负载均衡·ai-native
东离与糖宝1 小时前
Kafka流算湖一体化重磅发布,AI实时数据链路迎来大变革
人工智能
释厄6231 小时前
胶球实验本体论解码··力直接构成物质悖论·切割 A让B=B
人工智能
IvorySQL1 小时前
IvorySQL 校园行走进山东大学:从 PostgreSQL 到 AI,连接技术实践与职业成长
数据库·人工智能·postgresql
传人once1 小时前
从 PPT 到可交互原型:用 AI 做小程序原型的实践 | 深度实践版
人工智能·powerpoint·交互
元岳数字人小元1 小时前
数字人私有化,如何搭建全链路内网推理体系
运维·人工智能·开源·人机交互·交互