
我一直觉得给 Emacs 接 AI最没意思的做法就是装个补全插件。补全本质上还是“你问我答”快的代价是浅它对整个编辑流程的理解几乎为零。直到我开始折腾 agent-shell事情才变得有点意思编辑器里的 AI 不再是一个被动的打字助手而是一个能自己读文件、跑命令、改代码、甚至帮我重构配置的“协作者”。这篇就聊聊我的完整实践路径——从为什么选 agent-shell到怎么配、怎么用、怎么让它真的在 Emacs 里干活最后再分享一下我踩过的坑。如果你是一个在 Emacs 里写代码、长期维护配置、厌倦了“复制粘贴 ChatGPT 回答再手动应用”的人这篇文章应该能给你一套可以直接落地的方案。即使是刚接触 Emacs 的新手我也尽量把每步背后的意图讲清楚让你能跟着操作的同时不至于只是机械照做。1. 为什么要在 Emacs 里搭一个 AI 工作台1.1 从自动补全到 agent 式协作编辑器 AI 能力的三次进化我的 Emacs 配置从 2018 年用到现在补全方案从 auto-complete 换到 company再到 lsp-mode 内置的补全体验确实是越换越顺。但把时间线拉长就会发现这些工具本质都属于“下一次词”或“下一行”的粒度AI 没有参与我对项目的整体理解也没有办法执行任何操作。后来我陆续用过一些对话式工具在浏览器里聊完把代码贴回 Emacs 改来回切换窗口很割裂而且贴回来的代码经常和自己的代码风格冲突还得手动修。真正的转折点是 agent 这一类工具的出现它把大模型的“理解”和 shell 的“执行”绑在了一起AI 不再只是回答问题而是可以动手改文件、跑测试、读报错甚至根据反馈反复调整直到任务完成。1.2 agent-shell 的定位以及它和 Emacs 原生生态的关系Emacs 里有几条路线能实现类似效果你可以直接在 Eshell 里跑一个命令行的 agent 工具也可以在 Emacs 里装一个专门用 Shell 做交互桥接的包这类包通常扮演“调度者”的角色你把任务用自然语言描述给 agentagent 通过 Shell 命令去读文件、写文件、执行测试然后把结果反馈回来做下一步判断。我用的 agent-shell 就是这类思路的典型实现它本身不强绑定某一个大模型而是依赖底层模型接口来驱动整个循环。选择它的理由有三层。第一层它让 AI 的所有动作都能被 Emacs 原生捕获输出直接显示在 buffer 里中间状态都在 Emacs 的地盘上我能随时查看和干预。第二层它的思考过程是可见的agent 执行了什么命令、改了哪个文件、为什么这样改一切都有日志可查这比“黑箱式”的自动修改让我放心很多。第三层它没有把 Emacs 变成一个只能和特定服务对话的“终端”而是保留了 Elisp 的可编程性我可以为特定任务写辅助函数把自己的工作流做成一个可复用的函数库。提示如果你只是想要写代码时的行内补全agent-shell 不是最优解它更适合“我要完成一个任务”而不是“我要补全一个 token”。选型前先想清楚自己要的是哪种互动。2. 环境准备与工具选型2.1 从安装到跑通agent-shell 的最小可用配置agent-shell 本身是一个 Emacs 包安装方式有两种用 MELPA 的package-install直接装或者用straight.el管理。我用的是前者稳定、少折腾。初始配置可以非常精简只需要设置好模型服务商的 API Key并且在环境中让 Emacs 能找到对应的客户端命令。我在init.el里写的最简配置大概是这样的(use-package agent-shell :ensure t :defer t :custom (agent-shell-api-key (getenv AGENT_API_KEY)) (agent-shell-model deepseek-chat) :config (setq agent-shell-shell-buffer-name *agent-exec*))这一段的作用是让 agent-shell 在调用大模型时读取我已经导出的 API Key指定一个默认模型同时把执行命令的输出单独放到一个*agent-exec*buffer 里避免把当前的代码 buffer 搅乱。跑通这一步的关键不是配置本身而是先手动确认命令行环境下能正常调用模型接口。我踩过的一个典型坑是在 Emacs 里启动的 agent-shell 继承的是 Emacs 的 PATH而我通过 Homebrew 装的命令行工具在/opt/homebrew/bin下如果没有在 Emacs 启动前让它 load 到这个路径agent 会一直报“command not found”。2.2 模型选型与参数调优一套适合日常编码的通用参数值选模型这事不必盲目追求“最强”更重要的是稳定性和成本。我用过几个不同的模型实测下来对于 agent 这种需要多轮工具调用、频繁读取文件内容的场景推理能力强的大模型确实更不容易“绕圈子”。而像 DeepSeek 这类模型在当前环境下对中文场景支持也不错成本相对可控。关键在于上下文长度因为 agent 每次要读多个文件内容窗口太小会导致它“忘记”前面看过的代码经常需要你手动重复贴内容。我的建议是模型上下文至少支持 32K如果能到 128K 会更舒服。温度参数视模型而定我一般设成 0.2让 agent 的输出尽量稳定可控因为编码任务和创意写作不一样我不希望它每次都给你一套全新方案。2.3 工具链闭环Eshell、ripgrep 与 Git 的配合agent-shell 干活的底层依赖其实是 Shell 命令。我的实践中发现ripgrep 对项目全局搜索是最高效的git 用来查看 diff 和回滚Eshell 负责交互输出。把这些工具提前装好并且确认在 PATH 里agent 的工作效率会高很多。配好之后我习惯先给 agent 一个基础约定只允许在当前项目目录下创建或修改文件不允许触碰~/.emacs.d之外的系统路径。这个约束写在系统提示词里能避免很多意外操作。3. 核心工作流让 agent 真正“在 Emacs 里干活”3.1 把模糊需求拆成 agent 可执行的任务描述在 agent-shell 里一切工作的起点是一段自然语言指令。很多人觉得 agent 不好用其实不是模型不行而是给出的任务描述本身就是一团浆糊。你如果说“帮我优化一下项目”agent 无从下手。它不知道“优化”指性能还是可读性、不知道范围多大、也不知道验收标准。我吃了不少亏以后总结出一套任务描述的固定结构目标、上下文、约束、验收标准。举个实际例子我要让 agent 帮我的 Emacs 配置里增加一个“自动保存当前文件”的功能完整的描述大致是在 ~/.emacs.d/ 下的配置里增加一个功能 1. 当 Emacs 失去焦点时自动保存所有未保存的 buffer 2. 保存操作要有日志输出到 *Messages* 3. 不要修改现有配置的结构只在 end of file 之前新增一个 use-package 块 4. 完成后检查是否有语法错误并给出新增代码的说明。 验收标准执行后 Emacs 能无报错启动失焦时保存操作生效。这个描述里有明确的目标、范围限制、性能约束不修改现有结构、以及验收方式。实践证明这种任务格式能让 agent 的准确率提升一个档次因为它有了“检查点”和“停止条件”。3.2 Agent 改配置的完整循环读取、修改、验证、反馈跑通之后你才会真正体会到“循环”这两个字的重量。agent 并不是一次性生成所有的代码然后收工它会先读取目标的配置文件然后在理解结构的基础上逐步修改。我第一次让它改配置的时候观察到的流程是这样的它先执行cat ~/.emacs.d/init.el来了解全文然后搜索与auto-save相关的现有配置接着在文件的对应位置插入新代码再执行emacs --batch --eval做语法检查。检查的过程中它发现一个括号不平衡的错误于是读取了刚才的修改自己修正再跑一次语法检查直到通过。整个过程没有我介入它在几轮循环里完成了从“理解现状”到“动手修改”再到“验证结果”的闭环。3.3 多文件协作与上下文管理如何让 agent 跳出“单文件思维”单个文件的改动是入门多文件协作才是真正能提效的场景。但 agent 在多个文件之间跳跃时很容易丢失上下文甚至出现“改了 A 文件却忘了 B 文件里对它的依赖也改了”的情况。我在实践中找到了几个有效的措施。其中最重要的一条是项目级任务开始前先让 agent 生成一个项目结构树让它先把项目地图装进上下文里。比如一个 Emacs Lisp 的项目我通常会先运行tree -L 3 -I elpa或者直接让 agent 执行find . -type f -name *.el | sort。这样它至少知道有哪些文件、文件之间的物理关系。其次是要求它在修改文件前先显示 diff 预览我可以随时打断说“这里不对我不需要改动这段逻辑”避免出现不可控的连锁修改。最后我习惯把多文件任务拆成更小的子任务让 agent 每次专注在一个目标上比如“先完成配置文件分离再修改加载逻辑”这样它的上下文窗口始终是聚焦的效果比一次性铺开所有需求好很多。注意我发现agent 在多文件操作时如果遇到“找不到某段代码”的情况往往会脑补一个不存在的函数名去补全这是一种很危险的“幻觉”。应对办法是要求它在搜索无果时必须停下并说明“未找到”而不是自作主张地创建新函数。3.4 用 agent 反向驱动自己的学习和配置整理agent-shell 还有一个很有价值的用法让它解释你自己配置里的代码。维护了多年的 Emacs 配置里面很多代码是早年从别人的配置里抄来的时间一长自己都忘了那一段到底是干嘛的。我以前也想过写注释但一直拖延因为工作量太大。现在我会选中一段历史代码发给 agent让它给我解释这段代码的用途和潜在的优化点。这不仅仅是“帮我解释”聊完之后我会让它把解释整理成注释加入到代码里。坚持几周之后我的配置文件里多了很多“自己写的说明文档”很多历史遗留的无效配置也被顺手清理掉了。这个过程让我感觉自己不是在“用 AI 写代码”而是在“让 AI 帮我维护代码的秩序”。4. 实战记录三个比较典型的落地场景4.1 重构 init.el从 900 行到 400 行的减负过程先说我最有成就感的一次实战。我的init.el因为长期叠加配置积累到了 900 多行启动时间一直在 1.8 秒左右。我一直想精简但每次都担心“删错配置导致某个功能失效”于是一拖再拖。用了 agent-shell 后我下决心做了一次重构。我把任务拆成了三个阶段先让 agent 扫描全文把配置按功能分类包管理、UI、编辑行为、语言支持、快捷键形成一个清单再让它找出已经被注释掉的、或者加载了但没有实际生效的配置块最后逐项确认后把确定无用的部分删除。整个过程花费大约一个晚上比我之前手动整理要快好几倍。有一个细节值得说。agent 在分析有效配置时会用到use-package的diminish状态来判断一个包是否真的被加载。我发现它给出的判断列表里有些包标记是“可能启用”因为它无法确定某些require是否在其他地方被调用过。这其实暴露了 agent 的局限性它对 Emacs 这种“高度动态”的 Lisp 环境理解能力有限毕竟加载逻辑可能存在运行时分支里。我的处理方法是让 agent 先把不确定的包列出来由我逐一确认而不是让它直接删除。最后重构结果是从 900 行压到 400 行启动时间降到 1.1 秒功能没有任何明显退化。这个过程里agent 做的是最耗时的“整理、搜索、罗列”工作我做的是需要经验的判断工作配合得恰到好处。4.2 用 agent-shell 写一个小的 Elisp 工具从需求到落地的全流程另一个场景是让 agent 直接给我写一个 Elisp 工具。当时的需求很简单我经常在 Org 文件里写一些固定的文本模板想用一个快捷键插入。这个功能用yasnippet也能做但我想要一个更轻量、更容易定制的方案。我直接在 agent-shell 里描述需求它给了我一段 30 行的 Elisp 代码并附加了配置方式和使用说明。(defun my/insert-todo-template () (interactive) (insert (concat - [ ] (format-time-string %Y-%m-%d %H:%M) \n - 任务内容待补充\n))) (global-set-key (kbd C-c t) #my/insert-todo-template)这段代码当然不复杂但有两个细节体现了 agent 的价值一是在concat里嵌套了format-time-string保证插入时自动带上当前时间二是用interactive声明这是一个交互命令让我可以用快捷键绑定。放到以前我还得翻*Help*查一下interactive的写法和kbd的参数格式现在这些琐碎记忆细节不用我操心了。我只需要做最后的评审确定它的命名风格符合我的习惯确定缩进对齐然后就把它放进配置里了。这种事单独看不大但如果每周有四五次这种小需求积累下来释放的精力相当可观。4.3 agent-shell 在 Debug 中的用法让 AI 自己“读报错、跑测试”Debug 是 agent-shell 另一个高频场景。传统做法是我把一段报错信息贴到搜索引擎或者聊天工具里然后翻看各种答案自己试。现在我可以直接把报错信息原样给 agent让它顺着报错栈去读取对应的源码文件分析出错原因并尝试修复。我用一个具体的 Elisp 包调试示例来说明。有一次我装的一个第三方包在启动时报void-function: org-element-interpret-data这个错误我以前见过但忘了是版本不匹配还是加载顺序不对。我让 agent 去看这个包的源码和org-element.el里的函数定义位置它在对比之后发现这个包调用org-element-interpret-data的方式是旧的新版本 Org 里该函数被改名成了org-element--interpret-data。agent 随后在配置里给这个包加了一个:pin设置锁定了它依赖的旧版 Org然后再测报错消失。这类跨文件的排查工作如果全靠手动少说要花半小时而 agent 可以一边看源码一边交叉比对效率高很多。5. 常见问题与排查技巧实录5.1 你和 agent 之间最常见的 7 个问题我用 agent-shell 跑了几个月遇到的坑说多不多说少不少整理成一张速查表给后来的人省点时间。现象可能的原因解决办法agent 一直在循环同一个失败命令Shell 命令缺少必要的退出码检查设置超时机制或在任务描述中明确“如果命令执行失败超过 N 次就停止”agent 生成代码后语法错误频发模型对当前项目的代码风格理解不足提供项目已有的代码样例给 agent 一个参考风格片段agent 修改了无关文件没有在任务描述中限定文件范围增加明确的文件操作边界必要时用 git 操作来收窄路径agent 读文件时乱翻项目依赖目录没有限制搜索目录导致上下文污染用rg --glob !vendor/**之类的指令限制搜索范围输出中文夹杂奇怪字符终端编码不匹配检查 Emacs 和 shell 的 LANG 环境变量是否一致统一为 UTF-8agent 拒绝执行某些代码修改系统提示词或者安全限制设置过严适度放开执行权限但必须在沙箱目录里做测试修改后的代码影响整体稳定性缺少自动化测试保障为关键路径写基础测试agent 每次修改后跑一次回归5.2 应对 agent“自我发挥”超限的关键防线AI agent 最让人头疼的问题就是“自我发挥”。你说改 A它顺手把 B 也改了你觉得改得还行但完全不是这次任务该干的。这里有几个我从实操里总结的防线。第一每次任务开始前我会要求 agent 明确列出它计划修改的所有文件清单只准改清单内的文件。第二改完后要求显示 diff而不是只给一个“完成”的结论。第三重要项目里我坚持用 Git 分支管理让 agent 在分支上工作每次改动提交一条 commit我 review 后再合并回主分支。这样的流程虽然看起来多了一步但出了问题可以随时回滚不会把整个配置搞乱。我记得有一次 agent 在帮我添加某个快捷键绑定时发现我的init.el里有一个函数从未被调用于是自作主张把这个函数删除了。这个函数其实是我平时用M-x手动调用的删除后我当天下午发现一个功能失效还好我买了这个流程的保险——让我查出 diff 并恢复。从那以后我对 agent 的“主观能动性”有了更清醒的认识。5.3 给新手的三条避坑建议如果你刚接触 agent-shell我给你三条建议。第一条不要在正式环境里直接让 agent 改配置先复制一份配置到临时目录做实验跑通了再搬回正式环境。第二条不要一次塞给 agent 一个大而全的需求先从“它读文件、你复核”这种简单模式开始等熟悉了它的行为模式再慢慢加大任务粒度。第三条定期查看 agent 的历史记录理解它是如何思考的你越了解它的决策习惯就越知道怎么给它下指令它就越不容易跑偏。提示agent 不是人它没有长期记忆也不会“记住上次你纠正过它什么”。所以把你希望它遵守的规则显式地写在每轮任务描述里比指望它吸取教训靠谱得多。写在最后要让我总结这段时间用 agent-shell 的最大感受我会说它让我重新审视了自己和编辑器相处的方式。过去Emacs 是我的“手工工作台”每一块积木我都亲手搭过现在这个工作台开始长出一些自己会思考的部件而我的角色从“搭建者”慢慢变成了“架构师和监督者”。这个过程并不总是顺利但我享受这种“让 AI 干活、我在旁边看着、偶尔指出方向”的协作节奏。最后再分享一个小技巧如果你也希望 agent 更好地理解你的项目习惯可以在项目根目录放一个AGENTS.md文件把编码规范、命令用法、目录结构写进去让 agent 先读它再动手。这个小文件会明显提高后续任务的准确率值得一试。