ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在Emacs中搭建AI Agent工作台:从Shell交互到自我进化

在Emacs中搭建AI Agent工作台:从Shell交互到自我进化 最近的Emacs工作流让我有点上头。以前用AI多半是开个网页聊天框或者在编辑器里装个补全插件但总感觉AI只是个“提建议的”真正动手的还是我自己。直到我搭了一套叫agent-shell的方案把AI agent直接接到了Emacs的shell交互层才算是让AI从“嘴替”变成了“能干活的手”它能自己跑命令、翻代码、改配置、跑测试甚至会自己改完Emacs配置再做验证。这套东西我用了几个月越用越觉得有意思今天就把我的完整折腾过程、设计思路和踩过的坑都写出来。先说清楚agent-shell是什么它是一个基于Emacs的AI工作台方案。核心思路是让大语言模型LLM通过shell命令、文件读写、测试运行器等工具直接在Emacs环境里执行真实任务而不是只停留在“生成文本”阶段。你在Emacs里发起一个任务agent自己拆解、执行、看结果、再调整直到完成。整个过程都记录在buffer里你想打断、确认、审查都行。如果你也是Emacs用户或者对AI agent、自动化编程、个人工作台感兴趣这篇内容应该能帮你省掉不少试错时间。1. 为什么要在Emacs里搭AI工作台agent-shell的项目缘起1.1 从“对话框里的AI”到“能动手干活的AI”我最早用AI编程基本是两条路一是用GitHub Copilot式的补全写代码时自动给下一行二是开个聊天窗口把报错贴进去等AI给个解决方案。这两个路子有个共同问题——AI只输出建议执行还是我来。比如AI说“这里应该加个空指针判断”我得自己找到文件、定位函数、改代码、跑测试。对于简单场景还好一遇到跨多个文件的改动就成了“AI动嘴我跑断腿”。后来我开始接触agent和function calling才意识到AI真正该干的事不是“说”而是“做”。一个agent如果能在沙盒或项目目录里执行命令、查看输出、自己决定下一步动作那才是真正的工作流自动化。agent-shell的目标就是把这个能力移植到Emacs里让我不用切换终端、编辑器、浏览器在一个地方完成“下达任务→AI执行→我审查”的闭环。1.2 为什么选Emacs当工作台底座有人问我为什么不直接用现成的AI IDE或者干脆命令行操作。我的答案很简单Emacs具备几个无法替代的优势尤其是对细粒度控制要求高的场景。第一Emacs是“可编程编辑器”。整个编辑器都是Lisp环境所有操作都可以用函数表达。这意味着agent能操作的“界面元素”不只是文本还包括buffer管理、窗口布局、命令执行、配置修改。第二Emacs天生带shell。comint-mode、eshell、term、vterm各种shell解决方案一应俱全。任何命令都可以被捕获输出、批量执行、异步运行。agent-shell的“shell”就是架在这个基础上的。第三Emacs里一切皆bufferbuffer天然就是AI与工作台之间的“接口”。AI读文件就是读buffer内容AI写配置就是修改buffer再保存。这种统一性让实现变得非常干净。另外还有一点很重要可观测性。Emacs的所有过程都能以buffer形式呈现agent执行了什么命令、输出是什么、改动了哪些文件全部有记录。这是我在其他工具里很难得到的“审计感”。1.3 agent-shell想解决的三个核心问题在动手写第一行代码之前我给agent-shell定了三个必须解决的问题后面所有设计都是围绕它们展开的。问题一AI执行环境与用户环境的统一。绝大多数AI编程工具喜欢跑在自己的沙盒里跟用户真实项目环境隔离开。但我觉得在个人工作台上AI应该直接跑在本地Shell里这样它能读真实文件、用真实的依赖、复现真实的报错。当然安全问题要单独处理我后面会讲。问题二命令执行的权限与安全性。让AI直接执行任意shell命令听起来就很危险。agent-shell必须在开放性和可控性之间做平衡。我的方案是分级普通命令直接跑危险命令需要我按y确认目标目录限定在项目范围内。问题三会话的连续性与“自省式改进”。AI不能做完一个任务就失忆它得能记住自己正在干什么、接下来该干什么。并且改进工作台本身也应该是agent的工作范围——包括修改它自己的配置、提示词、工具定义。这就是书标题里的“自我进化”。2. agent-shell的核心机制AI Agent与Shell的交互层2.1 整体架构三层各司其职agent-shell的整体架构很清晰认真画起来其实就三层展示层、决策层、执行层。展示层是Emacs buffer。这里展示两样东西一是用户和agent的对话记录二是工具调用的执行日志。我习惯用一个buffer显示对话另一个buffer显示shell输出用text-property把命令、输出、错误用不同颜色高亮看起来很像一个带日志的运维终端。决策层是LLM。我的实现里默认用的是OpenAI兼容接口模型随意切换。决策层不做实际操作它只负责“想”分析任务、拆解步骤、决定调用哪个工具、根据返回结果调整计划。执行层是Shell。这里跑的是真实的bash/zsh会话。所有命令都通过一个受控的接口执行输出被捕获后反馈给决策层。除shell外执行层还包含两个重要的工具文件读写和测试运行器它们本质上也走shell但做了封装。这三层不是单向流水线而是一个循环决策层决定调用工具→执行层跑完→结果回到决策层→决策层决定下一步。这个循环就是agent的基本工作模式也是它区别于“一问一答”聊天机器人的关键。2.2 工具调用Function Calling是关键很多人第一次看agent-shell的代码会困惑Agent怎么知道自己能跑哪些命令答案是通过Function Calling。LLM在推理时会被提供一份“工具清单”每个工具都有一段JSON Schema描述工具名字、功能说明、参数列表。模型根据任务需要从清单里挑选合适的工具并填充参数结构化工县调用请求。我的agent-shell最小工具集是这几个shell_exec执行一条shell命令返回标准输出和退出码file_read读取指定文件内容file_write写入内容到指定文件text_search在目录里做grep返回匹配文件与行号run_test运行项目测试命令返回摘要emacs_reload重新加载Emacs配置返回加载是否有错每个工具都封装成Elisp函数再转成JSON Schema传给LLM。比如shell_exec的schema写出来大概是这样的格式{ type: function, function: { name: shell_exec, description: 在当前项目目录内执行一条 shell 命令返回标准输出, parameters: { type: object, properties: { command: { type: string, description: 要执行的完整 shell 命令 }, timeout: { type: integer, description: 超时秒数默认 30 } }, required: [command] } } }我在init脚本里维护一个agent-shell-tools列表所有的工具都统一注册进去再递给LLM。新增一个工具只需要写一个函数然后注册非常轻量。别小看工具定义这一步工具描述写得好不好直接影响agent会不会正确调用。描述太含糊它就会乱传参数描述太啰嗦模型又会抓不住重点。2.3 Emacs侧的集成方式agent-shell在Emacs侧的代码并不复杂核心是一个agent-loop函数。我把它设计成异步的这样agent在执行长命令时Emacs界面不会卡死。基本的调用流程是这样的用户按快捷键发起任务agent-shell把任务描述加上系统提示词发给LLM。LLM返回一个响应可能是普通文本表示任务完成也可能是一个工具调用请求。如果是工具调用agent-shell就在项目目录里执行对应工具把结果拼进对话上下文再次发给LLM。如此循环直到LLM返回最终回答或达到最大步骤数。这个loop用Emacs的async-process实现最顺手。每轮工具调用的输出会追加到一个日志buffer里我随时可以打开看agent执行到哪了。(defun agent-shell--run (task optional max-steps) 执行 TASK最多循环 MAX-STEPS 次。 (let ((messages (list (list :role user :content task))) (step 0)) (while (and ( step (or max-steps 20))) (let* ((resp (agent-shell--call-llm messages agent-shell-tools)) (msg (agent-shell--parse-message resp))) (if-let ((tool-call (aget msg :tool-call))) (let ((result (agent-shell--execute-tool tool-call))) (push (list :role assistant :content nil :tool-call tool-call) messages) (push (list :role tool :tool-call-id (aget tool-call :id) :content result) messages) (agent-shell--log-tool tool-call result)) (setq messages (append messages (list msg))) (setq step (1 step)))))))代码看着简单实际调通却费了不少功夫。最大的坑在于上下文格式不同LLM的messages结构、tool_call_id的位置、role字段稍微差一点就会报错。我建议新手先用一个简单的同步版本跑通再改成异步不然debug会把自己绕晕。2.4 会话与上下文管理让AI记得自己在干什么LLM的上下文窗口是有限的而agent执行一个复杂任务会产生大量中间输出命令结果、文件内容、报错日志等等。如果一股脑全塞进去用不了几轮就超长模型开始“忘事”逻辑混乱。我的做法是“工作记忆长期记忆”两段式。工作记忆是当前任务相关的最近几轮工具输出始终保留在上下文里。长期记忆是项目级别的信息比如项目的README、TODO列表、常用命令说明只在会话开始时加载一次。运行中产生的中间日志如果太长就先让agent做个摘要再把摘要放进上下文完整日志留在磁盘上。这一步非常关键。我最初没有压缩上下文结果agent跑了十几轮后开始重复执行同一个命令输出越来越大最后上下文窗口直接炸掉。加了个“摘要压缩”机制后就稳定多了。具体的压缩策略是单次工具输出超过100行时再单独发一轮LLM请求只做摘要摘要控制在5行以内然后再作为上下文继续主循环。3. 从零搭建我的简易agent-shell实操记录3.1 初始化项目结构agent-shell我用的是“Emacs Lisp管交互、Python管LLM调用”的跨语言结构。为什么不都用Elisp因为Emacs Lisp对JSON的处理、HTTP请求的支持虽然都有但写起来没有Python顺手而且LLM生态里现成的客户端库基本都是Python的直接调用库能省掉很多底层脏活。目录结构很简单agent-shell.el主模块定义交互命令、工具封装、buffer管理agent-shell-api.pyLLM API封装接收Elisp传过来的messages和tools返回响应agent-shell-tools.el工具注册表新增工具时改这里agent-shell-vars.el配置项比如模型名称、API地址、项目目录、安全规则agent-shell-api.py负责和LLM服务通信。它的接口很简单接收JSON格式的messages和tools调用OpenAI兼容接口把响应原样返回给Emacs。这个设计让我在切换不同LLM提供商时只需要改Python里的base_url和model名。3.2 从写代码到跑通第一轮agent循环我搭好基础框架后做的第一个“实战”是让agent在一个示例Python项目里修一个简单的bug。项目里有个函数返回值算错了需求描述是“修复加法函数的错误并补一个测试”。我先给agent发了这条任务然后就在旁边看着它在日志buffer里折腾。agent的第一轮是读文件。它调用text_search找到calculator.py的位置然后用file_read读取整个文件内容。第二轮它发现了函数逻辑确实有问题于是用file_write写入了修改后的内容。第三轮它主动调用run_test跑了一遍pytest输出显示测试通过。整个过程没有我的干预一步到位。当时我的真实感受是事情成立。不是因为AI一次就成功而是因为它能自己发现问题、修改、验证形成了一个闭环。后来我又试了更复杂的任务比如“给这个项目加上命令行参数解析”它需要同时改动入口文件、测试文件、可能还要加依赖声明这种跨文件的改动才是agent真正发光的地方。3.3 安全性agent不是没有缰绳的野马让agent能执行任意命令等于给了它一把刀。不给规矩肯定不行。我在安全上设置了四道防线。第一道工作目录锁定。agent的所有命令都限定在项目根目录内执行。我用的是bash的cd命令把shell会话先切到项目目录同时用pwd校验。如果你发现agent试图写项目之外的文件应立即警觉。第二道危险命令拦截。我维护了一套黑名单规则比如rm -rf /、mkfs、dd if、shutdown等等。凡是匹配到危险模式的命令直接拒绝执行并返回错误。第三道确认机制。有些命令本身不危险但影响面大比如git push、pip install、make migrate。这类命令我设为“需要确认”agent执行前会弹一个minibuffer提示按y才放行。第四道超时与步数限制。每个shell命令默认30秒超时整个任务默认最多20步防止失控死循环。我之前被真实坑过一次agent想清理临时文件结果生成了rm -rf tmp_dir如果目录变量因为某种原因为空后果不堪设想。虽然最后因为路径保护没出问题但我立刻加上了黑名单拦截和目录校验。安全这个话题上宁可多疑不要太信任。3.4 配置与导入如何把agent-shell挂进日常要让agent-shell用起来顺手还得在Emacs里做几件事。我用use-package管理它几个关键的custom变量配置如下(use-package agent-shell :load-path ~/emacs-config/agent-shell/ :custom (agent-shell-api-endpoint http://localhost:8000/v1/chat/completions) (agent-shell-model qwen2.5-coder-32b) (agent-shell-default-directory ~/workspace/) (agent-shell-require-confirm (git push pip install git clean)))启动项目很简单M-x agent-shell开启agent主bufferC-c a会在当前项目目录下发起一个任务。如果你常在一些固定项目里跑agent建议用project.el集成agent自动识别当前项目根目录省得每次手动指定。我在日常使用中比较依赖快捷键C-c a s在当前项目发起会话C-c a r重新加载Emacs配置agent专用C-c a t给agent指定一个测试文件去跑C-c a l打开执行日志buffer这套快捷键随着工具增加还在扩充每次新加一个工具我都会顺手绑个键位慢慢就形成了肌肉记忆。4. 让AI工作台“自我进化”闭环反馈与自动改进4.1 “自我进化”到底是什么意思书标题里的“自我进化”不是指AI有了自我意识而是指工作台能力的循环迭代。传统的工作流是人发现效率问题→人改配置→人验证。agent-shell把人这个环节也给自动化了agent使用过程中发现问题自己读取配置、自己修改、自己重载验证等于把“改进工作台”这个元任务也让渡给了agent。这让我想起了编译器的“自举”概念。一个能编译自己源码的编译器才算真正完整的编译器。一个能修改自身配置和工具集并验证可用的AI工作台也才算真正自洽。当然这需要闭环里每一步都有验证和回滚机制不能让agent瞎改。4.2 闭环设计修改、重载、验证、回滚我的agent-shell里有一条专门的指令叫“改进配置”它比普通任务多了几步操作。过程是agent先读取当前配置文件分析问题生成修改计划然后在配置文件末尾追加一个patch块用带标记的形式包裹方便回滚接着执行emacs_reload工具去加载配置如果加载报错立即回滚到之前的版本再重载如果加载正常agent继续跑一个自检命令确认工具集正常。这个设计的关键是“验证自动化”。如果agent改了配置但没法验证那它无法判断改动是否有效闭环就断了。emacs --batch --eval (progn (load agent-shell.el) (message OK))这个验证命令我试下来最稳定加载错误会直接暴露出来还能避免加载整个init.el带来的副作用。我实际跑过一轮“自我进化”案例agent反馈说日志buffer里tool调用记录太啰嗦每条命令打印了完整上下文占用大量buffer空间。它提出了修改方案把日志改成只显示命令摘要和退出码然后自己改掉了agent-shell-log-tool函数重载后果然清爽了很多。整个过程只花了两分钟。4.3 多AI协作一个主agent多个专家agent说到多AI协作我在agent-shell里还尝试了一种模式一个主agent负责任务分解和决策多个专家agent分别负责代码审查、测试生成、文档编写。这些专家agent共享同一个shell执行层但各自的prompt不同有的聚焦代码质量有的只写测试有的只做文档。主agent在需要时把子任务分配出去收集回来后再汇总。用Emacs的多buffer管理这种协作正好合适每个专家agent有自己的对话buffer主agent有一个执行总览buffer。它们可以并行跑互不干扰。比如主agent让文档专家写README同时让测试专家生成test文件最后主agent自己负责审查和整合。这种“总包分包”的工作模式在稍大一点的项目里效率提升非常明显。4.4 我实测过的三个自我进化案例案例一改进工具Schema。有一次agent在完成“搜索项目中所有TODO注释”任务时发现text_search工具返回的匹配项没有文件路径信息导致它没法直接定位。它主动在工具说明里加上“返回值需包含文件名”然后修改了Python端帮它格式化返回结果的代码。第二次执行这个任务时输出就规范多了。案例二自动封装常用shell命令。我在一个项目里反复运行同一个长命令来启动测试agent注意到这个模式后提出可以把这个命令封装成一个Elisp函数并绑定快捷键。它自己写了个agent-shell-run-project-tests还改了use-package配置绑定到了C-c a t。效果立竿见影。案例三上下文压缩策略优化。最初我把压缩阈值设置成100行agent在跑一个大型代码库时频繁触发摘要导致它丢失了一些关键细节。它建议动态调整如果当前任务是“跨文件重构”阈值提高到500行给agent更多上下文如果任务只是“查一个报错”阈值降低到50行减少噪声。它把这个逻辑用config变量实现了我后来跑了一些任务对比确实更稳。5. 常见问题与排查技巧实录5.1 问题速查表我整理了一份在agent-shell使用过程中踩到的典型问题方便你遇到类似情况时快速定位。问题现象可能原因解决方案agent在循环执行同一条命令上轮输出未正确写入上下文模型看不到结果检查tools message的tool_call_id是否与assistant消息匹配工具调用一直报format错误messages里出现了None的content字段在消息组装时把content为空的情况统一设为空字符串shell命令卡住不返回命令带有交互提示或长时间运行给shell_exec加timeout参数默认30秒超时直接杀掉进程agent拒绝执行某些必要命令黑名单规则太严格检查危险命令黑名单把必要命令加白名单并保留确认机制修改配置后Emacs启动报错配置文件语法错误或依赖缺失用emacs --batch单独加载目标配置不要整个init.el一次加载agent频繁丢失前文信息上下文被过度压缩调整摘要压缩阈值必要时把关键文件内容作为长期记忆常驻gptel或copilot正常但agent-shell没反应API endpoint或模型参数没配对用curl先测一下API连通性确认返回格式与OpenAI兼容5.2 独家避坑经验第一个坑是系统提示词的长度。我给agent的system prompt刚开始写得很长事无巨细地规定该怎么做结果模型反而变得畏手畏脚每次执行前都要回复很长一段“分析”就是不干活。后来我把prompt精简成三句话“你是Emacs工作台里的agent你的任务是完成用户请求。可以调用工具执行真实的shell命令。执行前不要长篇分析直接做。”效果立竿见影。第二个坑是Elisp异步回调里的状态混乱。用make-process跑命令回调函数里的buffer上下文如果不做保护很容易出现“输出的内容写错了buffer”的情况。我的经验是每个异步进程都绑定一个专属buffer回调时用with-current-buffer切换同时用进程属性保存任务ID回调后按任务ID分发结果避免串台。第三个坑是工具集的“自我膨胀”。agent每发现一个好用的场景就想着加一个工具结果工具越加越多LLM在决策时反而会犹豫选哪个甚至误选。我的解决办法是给工具分类加前缀shell_、file_、test_、config_。同时在工具描述里写清楚“使用场景”和“不要用它做什么”。工具数量控制在15个以内超出就考虑合并。5.3 与现有Emacs AI生态的横向对比Emacs里其实已经有gptel、copilot-chat、llm.el这些AI包了agent-shell和它们有什么区别我用了一段时间后理解越来越清晰。gptel和copilot-chat本质上还是“聊天/补全”导向AI的输出是文本建议最终要不要落地、怎么落地还是人说了算。agent-shell则不同它把“落地”这一环也交给了agent通过工具调用来执行真实操作。相比起来gptel更像一个AI顾问agent-shell更像一个AI员工。不过这不代表两者互斥。我现在的使用习惯是日常问问题、快速翻译、写邮件用gptel需要干实事比如修复bug、增加功能、改配置就用agent-shell。两者各有分工并不冲突。如果拿agent-shell和IDE里的agent模式比如GitHub Copilot Workspace这种对比Emacs的优势在于可定制性——我可以随意修改工具集、控制命令权限、自定义验证流程这套东西在商业IDE里很难做到这种细颗粒度。5.4 给新手的三个起步建议如果你也想搭一套自己的agent-shell我的建议是先从最小可用版本开始不要一上来就搞多agent协作和self-evolution。第一步先跑通单工具单轮调用。只提供shell_exec这一个工具让agent执行一条命令比如ls或git status确认工具调用链路是通的。第二步再逐步增加工具。每增加一个工具你就用两三个真实任务去验证确保工具描述和实际行为一致。第三步在这两个步骤都稳定后才开始做上下文压缩、自动摘要、配置自愈这些高级功能。我自己就是贪多嚼不烂第一次就把所有工具都写上结果agent到处乱用工具定位一个bug能翻遍整个项目最后是我手动关了所有工具才排查清楚。还有一点想提醒不要完全撒手让agent自己跑。好的工作流是“人在回路”——agent去执行人负责看方向遇到关键节点按一下确认。我在agent要执行git push和pip install这类操作时一定会弹确认。这不是不信任agent而是防止“执行得太顺忘了确认后果”。用了这么长时间我对agent-shell最大的体会是它让我重新理解了“工具型AI”的意义。真正的AI工作台不应该是“等AI给答案”的被动工具而应该是“给AI搭台子”的主动环境。Emacs还是那个Emacs但接上shell、接上agent、接上验证闭环之后它变得能自己长出新的能力。后来我又往这个方向扩展了很多比如让agent把每次任务的执行日志自动汇总成项目笔记、用org-mode做长周期任务的进度追踪、把测试覆盖率结果反馈成下一次开发的改进建议。这些听起来都很“炫”但它们的根基其实还是那条最简单的循环AI决定干什么Shell执行什么Emacs记录一切。如果你手里也有一套爱折腾的Emacs我非常建议你也从最小闭环试起让AI在你的工作台里真的“上手干活”。
返回列表