7 月底写第一篇“随心”时,我在最后留了一句:后几个月想做一个 mini coding agent。那时候它还只是个方向。我先去读 Pi,把 Agent Loop、工具和事件弄清楚,然后真的从一个 while 循环开始写。一个月后,MiniCode 终于从“能跑的 demo”变成了一个我愿意放出来给别人看的小工具。
为什么还要自己做一个 Coding Agent
现在能写代码的 Agent 已经很多了。Claude Code、Codex、Aider 都比 MiniCode 完整得多,所以我一开始就没打算再做一个功能更多的替代品。
我真正想弄清楚的是:当我把一句“帮我修这个问题”交给模型以后,中间到底发生了什么?模型怎么找到文件,工具结果怎么回到下一轮对话,修改前谁来确认,测试失败后又该怎么收口?
这些事情在成熟产品里通常被包得很好,用起来很顺,但也正因为太顺了,我很容易只看到最后那份 diff。读完 Pi 源码后,我发现 Coding Agent 最里面的骨架其实没有想象中复杂:
模型请求工具
-> 本地查找并校验工具
-> 执行工具
-> 把结果写回模型上下文
-> 模型继续下一轮
核心确实可以写成一个 while 循环。真正难的,是这个循环旁边的东西:模型能看什么、能做什么,失败有没有终态,用户确认的到底是不是最后真正执行的那个动作。
MiniCode 就是从这里开始的。
第一版很笨,但每一步都能看见
最早的 MiniCode 甚至没有接真实模型。我先写了一个确定性的 FakeModel,只让它完成搜索、读取源码和返回结果这几件事。它不理解自由任务,也不会假装自己真的修好了代码。
这一版看起来有点笨,但很适合验证 Agent Loop。模型发出一个工具请求,本地注册表找到工具,参数通过校验后执行,结果再变成一条 ToolResultMessage 回到消息历史。未知工具、参数错误和策略拒绝也不能悄悄消失,都要留下完整的错误结果。
我后来又加了一个很小的 Working Ledger,只记录当前任务里已经被工具验证过的事实。模型说“文件已经修改”不算,补丁真正写入才算;模型说“测试应该能过”也不算,测试进程返回成功才算。
先把这条最小链路跑通以后,我才接入 DeepSeek 和通用的 OpenAI-compatible Profile。默认模型仍然是离线的 FakeModel,只有显式选择远程 Profile 时,模型请求才会联网。Profile 里也不保存 API Key,只记录去哪个环境变量里取。
它为什么总会停下来问我
在 --guided --mode edit 下,MiniCode 现在最完整的一条路径大概是这样:
用户任务
-> 生成计划,等待 CONTINUE
-> 搜索并读取代码
-> 展示补丁,等待 APPLY
-> 展示测试命令、目录和风险,等待 RUN
-> 必要时确认一次修复方向
-> 只读检查 Git status / diff
-> 给出最终回答
CONTINUE、APPLY、RUN、CANCEL 都是本地控制词,不会被当成普通消息发给模型。没有出现对应的待确认面板时,输入这些词也不会产生副作用。
我最开始也想过,会不会确认太多了,用起来很拖。后来真正把编辑、命令和失败修复串起来,反而觉得这些暂停很重要。计划确认的是“准备怎么做”,补丁确认的是“准备写什么”,命令确认的是“准备运行什么”。它们不是同一个决定,不能用开头的一次同意全部带过。
尤其是修改代码这件事。MiniCode 会要求模型先成功读取目标文件,再提出一次唯一、精确的文本替换;补丁完整展示出来以后,只有输入 APPLY 才会写入。写入成功也不能马上宣布完成,下一轮会被强制切到真实的 test。测试没有实际运行并通过,任务就不能被记成成功。
这套流程没有“全自动”那么爽,但它至少让我知道 Agent 此刻停在哪里,以及下一步按下去会发生什么。
真正花时间的,不是让它改文件
让模型生成一段新代码并不难,难的是别让这段能力顺手越过边界。
MiniCode 的文件工具只接受工作区相对路径,解析真实路径后还要确认没有跑出工作区。.git、.env*、常见凭据和密钥文件会直接拒绝,项目也可以用 .minicodeignore 继续缩小 Agent 能读取的范围。补丁确认前后还会重新检查文件和父目录,避免用户看到的是一个文件,真正写入时却已经被换成了另一个文件。
命令没有直接开放 Shell 字符串,当前只接受拆开的 program + args + cwd,而且第一版只允许受控的 Node/npm 子集。Git 则只开放 status、diff 和 staged_diff 三个固定只读动作,不会帮我暂存、提交或推送。模型在 prompt 里承诺“我只做安全操作”,并不能让这些入口自动放行。
做到这里,我对“提示词不是权限系统”这句话有了更具体的感觉。模型可以负责提出动作,但它不应该负责决定自己有没有权限。真正的边界必须留在本地代码、工具注册和状态机里。
后来它终于有点像一个工具了
Runtime 能跑以后,我又花了不少时间做 TUI。
MiniCode 使用 Pi 的终端 UI 组件,但没有照搬它的完整界面。顶部只保留模型、权限和工作区,中间是会话,底部显示当前路径和 Profile;工具细节默认收起来,需要时再用 Ctrl+O 或 /details 展开。远程模型的最终回答会流式显示,但如果这一轮后来变成工具调用、证据校验失败或者用户取消,临时文本会撤回,不会留下一段看起来像最终结论的半成品。
我还是更喜欢终端。Coding Agent 本来就围绕仓库、文件和命令工作,如果为了“像产品”马上套一个 Web 页面,反而会把精力花到登录、部署和页面状态上。现在这个 TUI 不算华丽,但任务、等待确认、正在执行和最后收口都能看清楚,也保留了终端原生的滚动历史。
默认的 FakeModel 可以离线体验固定演示和部分确认流程,但不理解自由任务,也不会自主修改代码。要让真实模型读取或修改项目,需要显式配置 Profile,并开启对应模式。
我不想只靠一次演示证明它能用
做到后面,测试数量慢慢涨到了 287 项。除了 Agent Loop 和工具本身,我还测了路径逃逸、符号链接、确认期间文件被替换、命令参数、Git 只读边界、终端控制字符、审计脱敏,以及“补丁成功后必须真实测试”这类状态流程。写这篇时,最新 CI 已经在 Windows 和 Ubuntu 上都跑通了。
我还用 DeepSeek V4 Flash 跑了一次固定评测:15 个任务、3 种配置、每种重复 3 次,一共 135 次。精简的 MiniCode 三工具配置通过率是 88.9%,完整产品配置反而只有 66.7%。看到这里,我第一反应是:怎么做得更多,反而跑得更差了?
这个结果有点尴尬,但我觉得比只放一段成功 GIF 更有用。它说明更完整的计划、验证和修复状态机,并不会自动换来更高的任务成功率;提示词、预算和失败修复流程还有不少可以调的地方。另一边,固定矩阵里的安全任务是 45/45,没有秘密泄漏,也没有越权工具成功。但这只代表这组任务和这次模型配置。
至少现在,我能把“它到底哪里有效、哪里还不够”拆成数据,而不是只凭一次很顺的演示下结论。
它现在还不是什么
MiniCode 目前没有通用 Shell,没有 Git 写操作,没有浏览器、多 Agent、远程沙箱,也不会自动帮我 commit 或提 PR。真实模型仍然可能理解错任务、浪费工具预算,或者在严格流程里没有及时收敛。
它也不是操作系统沙箱。用户确认运行的 npm 项目脚本,依然可能读取宿主进程有权限访问的文件、联网或者启动其他进程。更准确地说,MiniCode 是一个把 Coding Agent 的行动、授权和证据摊开来看的小项目:代码不算多,链路可以顺着读,关键副作用前会停下来,失败也能回到具体的终态。
接下来我更想先提高完整产品配置的任务成功率,补真实终端里的使用体验,再考虑增加更多能力。工具数量继续往上堆很容易,难的是每多一个工具,都还能说清楚它为什么存在、谁能批准、失败后留下什么。
最后
一个月前,我只是想证明自己能写出 Agent Loop。现在回头看,我更在意的已经不是 while 循环本身,而是四件很具体的事:模型不能自己扩大权限;用户确认要绑定真正执行的对象;工具拒绝和失败也要有终态;一次代码修改必须留下验证证据。
MiniCode 当然还没有做完,但它已经不只是第一篇随心末尾的一个想法了。
如果想自己跑一下,需要 Node.js 22.18 或以上:
git clone https://github.com/iuyup/minicode.git
cd minicode
npm install
npm run mini
项目地址:github.com/iuyup/minicode
作者:T | 汕头大学光电信息科学与工程 | AI Agent 方向
GitHub: github.com/iuyup