30秒速览
- 全自主编程Agent的内核不是更强的LLM,而是把LLM嵌入一个有状态的操作系统进程,让它可以持续执行命令、读取反馈并记住上下文。<br>
- Devin很可能用Playwright和异步Shell组成“感官系统”,能自动截图、解析终端输出,甚至维持交互式REPL对话来调试验证。<br>
- 当Agent能自己跑完整开发闭环,我们得从写代码转向定义验收标准和架构约束——你的价值变成了“决定什么是正确的事”而不是“亲自做每件事”。
全自主编程Agent的内核根本不是“对话补全”,而是一个带状态的操作系统进程
上个月我花了两周时间把Cognition AI公开的论文、视频和SWE-bench上的提交记录翻了个遍,就想弄清楚Devin到底是怎么把一句“给我做一个能爬取Hacker News的网页”变成跑在沙箱里的React应用的。在这之前我已经用了快一年的Cursor和Copilot,说实话我一直以为所谓“自主编程”不过就是更长的上下文加更多的工具调用。直到我自己试着复现一个类似的自主Agent,才发现这东西的内核根本不是“对话补全”,而是一个带着完整状态的操作系统进程。
我来给你重现一下我当时的认知颠覆。年初我用Claude 4最新版的API搭了一个简单的编程助手,流程是这样的:用户提需求,我把需求扔给LLM,它输出代码,我再把代码写进文件,执行测试,如果有报错就把报错信息喂回给LLM修正。这个循环听起来跟Devin宣传片里一模一样。但第一轮尝试就给了我一个响亮的耳光——模型在执行第三步的时候完全不记得第一步自己干了什么。它在一个临时目录里写了一堆文件,第二次调用时却去读原始路径,因为会话是无状态的,每次调用都是一个全新的上下文。我当时就想,妈的,Devin肯定不是这么做的。
真正让我想通的是SWE-agent和OpenDevin这两个开源项目的架构。它们把整个Agent拆成了三块:一个LLM规划器负责把自然语言拆成可执行步骤,一个代码执行器在隔离的沙箱里跑命令,还有一个长期记忆模块把文件系统的状态、命令输出的摘要、以及每一步的决策理由都存下来。这三块合在一起,实际上就是模拟了一个开发者的操作系统环境。Devin的沙箱本质上是一个Docker容器,里面装着完整的Ubuntu、Node.js、Python、Git,甚至还有浏览器。这个容器是有状态的,它不会因为一次LLM调用结束就被销毁,而是持续运行直到任务真正完成。Devin的“记忆”不是全塞进上下文窗口,而是分层存储的:热数据放在长上下文中(比如最近20轮交互的详细记录),温数据用嵌入向量存在向量数据库里供检索,冷数据则是Git的commit历史和文件diff。我后来按这个思路重构了自己的Agent,最明显的变化是它能处理跨天的长任务了——上次我在周五下班前给它派了个“把后端从Express迁移到Fastify”的活,周一早上来看它已经开了三个PR,中间还自己修了两个依赖冲突。
这里我要特别强调一个经常被误解的点:Devin并不是“一个模型”,而是一个以LLM为大脑的智能体系统。LLM在这里的作用是生成命令和解释反馈,但真正执行动作的是沙箱里的Shell和浏览器。这种架构跟Cursor那种补全模式有本质区别。Cursor的Agent模式虽然也能跑命令,但它的上下文是你本机的IDE状态,是依附于你的编辑器的;而Devin是一个独立的进程,有自己的CPU、内存、文件系统和网络栈。我举个场景你就明白了:假如你要同时改前端React组件和后端API,还要确保端到端测试通过,你用Cursor的话得手动切换文件、手动触发测试,Agent只是在你编辑的间隙给你建议;而Devin可以并行启动前端开发服务器和后端进程,用Playwright打开浏览器自动点击,然后根据页面报错定位到是哪个API返回了500。这种自主性不是靠更强的模型做到的,而是靠把Agent封装成一个完整的操作系统进程。这是我在推演Devin架构时收获的最重要的一条认知。
import asyncio
async def execute_in_sandbox(command: str, cwd: str = "/workspace"):
"""在持久化沙箱中执行命令,返回完整输出和退出码"""
proc = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd
)
stdout, stderr = await proc.communicate()
return {
"exit_code": proc.returncode,
"stdout": stdout.decode(),
"stderr": stderr.decode()
}
上面这段代码是我在自己Agent里实现的命令执行核心,极其简单,但真正难的是如何把输出解析成LLM能理解的“观察”。Devin大概率在输出里做了结构化包装,比如用JSON把退出码、关键错误行、文件变更摘要打包后再塞进对话历史,而不是直接把几百行日志全丢进上下文。我在复现时就因为没做这种摘要,导致模型经常被冗长的npm install日志搞晕,完全忽略了真正致命的类型错误。后来我加了一个截断和关键错误提取的中间层,任务成功率直接从40%跳到了接近70%。这个小细节让我意识到,全自主Agent的竞争力不在模型本身,而在工程化的状态管理和信息过滤。
Devin的沙箱可能是Chromium和Shell的缝合怪,但它学会“看”终端输出是我最想偷师的技巧
在研究Devin的过程中,让我最着迷的其实是它怎么跟浏览器和Shell交互的。Cognition AI几乎没有透露技术细节,但根据他们在SWE-bench上的表现,我能推测出一些端倪。Devin能做的事情不止是写代码,它还能直接打开浏览器测试自己写的网页,这在修复前端Bug的时候简直是作弊器。我猜他们在沙箱里内置了Playwright或者Puppeteer这类浏览器自动化工具,并且给LLM暴露了几个高层API:打开页面、截图、获取DOM快照、执行JavaScript、监听网络请求。这样一来,当Devin发现自己写的React组件渲染异常时,它可以主动截个图,用多模态模型(多半是GPT-4.1或者Claude 4的视觉能力)分析截图,然后对比预期的设计稿,再回去改CSS。这个闭环我试过复现,但效果只能说差强人意,主要是因为截图到文本的分析链路延迟太高,再加上多模态模型对像素级布局的还原能力实在有限。不过Devin似乎更多依赖的是DOM结构和控制台报错,视觉只是辅助确认。
Shell交互的部分更有意思。Devin在视频里展示过它执行npm install并等待安装完成,然后自动根据错误信息修正依赖版本。这里的关键不是执行命令,而是“看懂”命令的输出。我自己在搭建Agent时踩过大坑:用subprocess获取输出很简单,但模型往往会误读警告信息,比如把npm的funding提示当成错误。Devin多半对命令输出做了预处理,把输出分类为成功、警告、错误,并且只把错误部分高亮给LLM。更高级的是,它能解析一些常见工具的JSON输出,比如ESLint的JSON格式报错、TypeScript编译器的结构化错误列表。这样规划器就能精确知道哪个文件的哪一行出了什么问题,从而生成有针对性的修复命令。我后来在Agent的“观察器”里加了一个插件机制,针对不同命令(tsc、pytest、eslint)使用不同的解析器,错误定位准确率提升了一大截。
还有一个容易被忽略的点是交互式命令的处理。Devin在演示中运行过python manage.py shell这样的交互式REPL,它能持续跟进程对话。这意味着他们的执行器不是简单的“执行-等待-返回”,而是可以维持一个长期运行的子进程,并且异步地读写标准输入输出。实现这个我用的是pexpect库,但为了适应异步环境后来换成了asyncio的Process交互。下面是我写的一段原型代码,展示了如何跟Node.js REPL对话——先启动,然后逐条发送表达式并等待提示符。Devin大概率用了类似的机制来调试验证一些逻辑,否则它不可能在写代码的同时去运行一个数据库控制台检查迁移结果。
import asyncio
async def interactive_node(expressions: list[str]):
proc = await asyncio.create_subprocess_exec(
'node', '-i',
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
results = []
async for line in proc.stdout:
if b'> ' in line: # node REPL提示符
if expressions:
expr = expressions.pop(0)
proc.stdin.write(f'{expr}n'.encode())
await proc.stdin.drain()
else:
proc.stdin.write(b'.exitn')
await proc.stdin.drain()
else:
results.append(line.decode())
await proc.wait()
return ''.join(results)
浏览器和Shell这两层的结合,让Devin有了类似人类开发者的“感知-行动”循环。它不只是生成静态代码,而是真的把代码运行起来,看结果,再修正。这跟Copilot那种“只管写不管跑”的模式完全是两个时代的东西。我前段时间在项目中需要把一个复杂的遗留jQuery页面改成React组件,用Copilot的话,它只能给我写出静态的JSX,至于运行后是不是跟原来长得一样,它完全不管。而我把任务交给自己的Devin-like Agent后,它自己用Playwright截取了旧页面的DOM快照,又截取了新组件的渲染结果,逐块对比差异,然后花了一个下午把十几个细小的样式错位全修了。那一刻我真的有种被掏空的感觉——但同时也觉得,这东西确实把“看终端输出并迭代”这件事学到了骨头里。
任务拆分不是简单地让LLM列步骤,你得逼着模型像工程师一样写Todo并检查自己的进度
很多人以为自主Agent的任务规划就是让LLM把大任务分解成小步骤,然后一个接一个执行。这么说原理上没错,但实际做起来你会被各种意外抽得晕头转向。我最初的做法特别愣:直接在系统提示里写“请将用户需求分解为一系列的Shell命令,每行一个”,然后按顺序执行。结果模型经常列出一些天马行空的步骤,比如第一步是“初始化项目”,第二步就跳到“部署到生产环境”,中间根本没有实际的开发工作。还有时候它分解出的步骤之间完全没有依赖检查,上一个命令失败了它仍然傻傻地往下跑,最后整个任务乱成一锅粥。后来我才意识到,真正的任务规划必须逼着模型像工程师一样写Todo,并且每完成一步都要对照预期结果,发现偏差就重新规划后续步骤。Devin内部几乎可以确定有一个专门的任务管理模块,维护着一张动态的依赖图。
我推测Devin的规划器采用的是Plan-and-Execute模式的一个变种,类似LangChain的PlanAndExecute或者AutoGPT的核心逻辑,但做得更工程化。首先它会接收自然语言需求,让LLM输出一个结构化的初始计划。这个计划不是简单的字符串列表,而是一个JSON数组,每个元素包含任务ID、任务描述、预期结果、依赖的前置任务ID、以及一个执行类型的标签(比如shell、file-edit、browser-action)。下面是一个示例提示词,用来生成这种格式的计划——我在自己的系统里就是这样做的,效果比自由文本好得多。
{
"task": "创建一个FastAPI应用,提供一个返回JSON的/hello接口",
"plan": [
{
"id": "1",
"desc": "初始化Python项目,安装FastAPI和Uvicorn",
"expected": "pip install成功,可以看到包已安装",
"depends_on": [],
"type": "shell"
},
{
"id": "2",
"desc": "创建main.py,编写FastAPI应用并定义/hello路由",
"expected": "文件存在且语法正确,uvicorn能够启动",
"depends_on": ["1"],
"type": "file-edit"
},
{
"id": "3",
"desc": "使用Uvicorn启动应用,并用curl测试/hello接口",
"expected": "curl返回{"message":"Hello World"}",
"depends_on": ["2"],
"type": "shell"
}
]
}
这个规划输出会被存进长期记忆,执行器则不停地循环:取出下一个可执行的任务(依赖已全部满足),在沙箱里执行对应动作,收集反馈,然后调用一个校验LLM来判断实际结果是否符合预期。如果符合,就把这个任务标记为完成,从依赖图中移除,激活后续任务。如果不符合,校验LLM会生成一个简单的分析,比如“FastAPI未安装,预期pip install成功,但实际返回错误码1”,这时系统会触发一个重规划流程:把当前任务以及所有依赖它的未执行任务回收到待规划池,调用LLM基于最新状态重新生成一个修正计划。整个过程就像是把敏捷开发里的看板搬到了沙箱里——任务卡片在上面自动流转,出了bug就自动退回“In Progress”并附上注释。
这里面最核心的难点是任务粒度的控制。如果任务拆得太粗,比如“实现整个用户管理模块”,那中间出错时上下文很容易丢失细节。如果拆得太细,比如“创建User model文件”、“添加username字段”、“编写序列化器”,那LLM的规划开销会爆炸,而且各个步骤之间的耦合会导致大量回滚重来。我在自己的Agent上反复调试了很久,最后发现一个比较舒服的粒度是每个任务大约对应3到5个Shell命令或者一次编辑器级别的代码改动。Devin很可能有一套动态调节粒度的策略:遇到简单任务(比如修改一个配置)就直接执行,遇到复杂任务(比如实现一个新服务)就先生成一个高层计划,然后在执行过程中由执行器根据反馈自动细化。比如它规划“实现登录API”时,会自动分解为“创建路由文件”、“编写JWT生成逻辑”、“连接数据库”、“编写测试脚本”这四个子任务,每个子任务再根据实际情况决定是否进一步细化。这种分层规划的能力,才是Devin能处理跨文件、跨服务的复杂开发任务的根本原因。
当Agent学会了自己修bug,我发现自己真正值钱的地方在‘定义什么是bug’
如果说任务规划是Devin的骨骼,那自我纠正循环就是它的免疫系统。我花在自己Agent上的时间,至少有一半是在打磨错误修复和回滚策略。一个全自主Agent真正跟辅助工具拉开代差的地方,就在于它能不能在没人看着的情况下,自己把搞砸的事情救回来。Cognition AI在Devin的介绍里特别强调它可以“自动检测并修复错误”,但没细说怎么实现的。我通过观察SWE-agent的成功案例和自己的实验,推测出Devin的自我纠正循环是一个多层防御的体系:从最轻量级的语法检查,到运行时的异常捕获,再到功能性的回归测试,最后是回滚到已知良好状态并重新规划。
最底层的是即时反馈修复。Devin在生成代码或执行命令后,几乎立刻就在沙箱里运行对应的语法检查和类型检查。比如写完一个Python文件后,它会自动执行python -m py_compile来捕获SyntaxError;写完TypeScript后跑tsc –noEmit。这些检查几乎是零成本的,失败之后Devin会把错误信息直接嵌进LLM的下一条指令里,让模型立刻修正。第二层是运行时错误修复。当Devin启动了服务或运行测试时,如果进程异常退出或者测试失败,它会提取错误堆栈和日志,结合git diff定位到自己刚刚修改的代码段,然后用LLM分析出最可能的根因并尝试修复。这个过程中有一个我特别推崇的小技巧:Devin很可能限制了每次修复的范围,只允许在最近修改的几个文件上做更改,并且强制生成一个最小化的补丁,防止模型把原本正常的部分搞乱。第三层就是功能回滚。如果连续修复几次都失败,或者错误影响范围太大,Devin会直接执行git reset –hard回到上一个成功的commit,再把最近这几步失败尝试写入一个“经验教训”文档存进记忆,下次规划类似任务时能主动避开同样的坑。
我举个例子,有一次我让Agent给一个Django项目添加用户头像上传功能。它顺利地安装了Pillow、写了模型和视图,但第一次启动时报了“ImageField requires Pillow”的错误。Agent检查后发现自己忘了在settings.py里注册Installed Apps中的avatar模块,于是自动修改了配置,commit后重试,第二次测试通过。但紧接着CI脚本又报了一个静态文件404,这次Agent没有慌张,它根据日志定位到是自己忘了运行python manage.py collectstatic,执行并再次提交。整个过程三次失败三次自动修复,只用了不到五分钟。这个体验让我既欣慰又后怕——欣慰的是我终于不用半夜被PagerDuty叫起来修这种低级错误了,后怕是Agent在修bug的时候其实并不理解为什么collectstatic能解决问题,它只是模式匹配到了以往类似案例的解决方案。
这恰好引出了我对所有自主编程Agent的终极思考:当机器能自己完成“写代码-跑测试-修bug”的完整闭环后,我们高级工程师的价值到底在哪里?我的答案是:定义什么是bug。机器擅长的是在给定验收标准下追求零错误,但它不知道什么样的行为才算是正确的。比如上面那个头像上传功能,如果产品经理后来希望头像必须是正方形且小于2MB,这些需求是机器无法自己推断出来的。DevOps、架构设计、领域建模、性能权衡、安全性考量——所有这些需要权衡多方利益、需要深入理解业务上下文的决策,仍然是人类的专属领地。我现在的日常已经从“写React组件”变成了“给Agent写验收标准”,从“调试死锁”变成了“设计并发模型然后让Agent实现”,从“修CSS错位”变成了“制定设计令牌系统并教会Agent如何使用”。
Devin这类全自主Agent对团队最大的挑战,不是抢走我们的饭碗,而是逼着每一个人向上迁移一个抽象层次。以前你觉得懂Spring Boot源码就够吃一辈子了,现在不行了,你得懂怎么把业务约束翻译成Agent能理解的规范,懂怎么审查Agent生成的几千行代码里有没有隐藏的安全漏洞,懂怎么在Agent搞出生产事故之前掐断它的操作。如果你还停留在“我比AI写CURD快”的层面,那确实危险了。但如果你能做那个“定义验收条件、设计测试策略、裁决技术选型”的人,全自主Agent只会让你如虎添翼。至少目前,我还没见过哪个Agent能独立决定一个电商系统的库存扣减应该用乐观锁还是悲观锁——它们只会两种都实现一遍,然后问你要哪一个。那一刻,发号施令的快感,说实话还挺上头的。