Notes

实战项目:Mini Cursor(CLI 版)

摘要:用最少的工程复杂度,做出一个“能读写代码”的小 Agent。目标不是造一个完整 IDE,而是把 Tool Use + 安全护栏 + 可迭代改文件 这条链路跑通。


0. 目标与非目标

目标

  • 允许用户在终端输入需求:例如“把某个函数改成支持传入数组”
  • Agent 能:
    • 列目录 / 搜索文件
    • 读取目标文件
    • 生成补丁(patch)
    • 写回文件
    • 重新读取并自检

非目标

  • 不做 LSP、不做 AST 精确改写、不做全量测试编排(后续可加)

1. 工具设计(最小集)

建议第一版只做 4 个工具:

  1. list_dir(path):列出目录(用于定位文件)
  2. search(pattern, path):grep 搜索(用于定位符号)
  3. read_file(path, offset?, limit?):分段读文件(控 token)
  4. write_file(path, content):覆盖写入(第一版简单粗暴)

后续升级:把 write_file 变成 apply_patch(path, patch)(更安全)


2. 最小 Agent Loop(CLI 驱动)

你可以把它拆成三个层:

  • LLM 层:负责对话与 tool_calls
  • Tools 层:真实执行文件系统/命令
  • Loop 层:不断“问模型 -> 执行工具 -> 回传结果”直到完成

伪代码:

const tools = [list_dir, search, read_file, write_file]

const system = `你是一个代码修改助手。
规则:
- 修改前必须 read_file
- 尽量只修改必要最小范围
- 修改后必须再次 read_file 自检
- 不允许访问工作目录之外的路径
- 输出最终答复时,说明改了哪些文件、改动点是什么
`

messages = [{ role: "system", content: system }]

while (true) {
  const resp = await llm({ messages, tools })
  const msg = resp.choices[0].message

  if (!msg.tool_calls?.length) {
    print(msg.content)
    break
  }

  messages.push(msg)

  for (const call of msg.tool_calls) {
    const result = await runTool(call)
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      name: call.function.name,
      content: JSON.stringify(result)
    })
  }
}

3. 关键难点 ①:Token 消耗怎么控

3.1 只读必要片段

  • read_file 支持 offset/limit
  • 先通过 search 找到函数附近,再读局部

3.2 让模型“引用行号”

你可以让 read_file 返回带行号的内容:

120| function foo() {
121|   ...

这样模型更容易做精确修改(尤其是后续 patch 化)。

3.3 限制上下文

  • 只保留最近 N 轮 tool 结果
  • 或把“工具输出”做摘要再放回 messages

4. 关键难点 ②:修改准确性怎么保证

第一版“覆盖写入”很危险,建议至少加三层护栏:

  1. 工作区沙箱:只能操作指定目录(例如 repo 根目录)
  2. 变更最小化约束:system prompt 强约束“只改必要范围”
  3. 自检回读:写完必须 read_file 校验自己改了什么

如果你要进一步提升可靠性:

  • 让模型输出 unified diff
  • 由你的程序做 patch apply(失败就回传错误让模型修正)

5. 推荐的输出格式(让结果可审计)

最终回答建议固定结构:

  • ✅ 修改了哪些文件:
    • path/to/file.ts
  • ✅ 改了什么:
    • 改动点 1
    • 改动点 2
  • ✅ 如何验证:
    • 运行哪些命令/测试

6. 下一步

Mini Cursor 跑通后,你就具备了构建复杂 Agent 的基础“手脚”。接下来要解决的是:

  • Memory:对话长了会撞 context window,怎么让它“像记得一样”?
  • Planning:任务复杂时,怎么让它先计划再执行,并能自我纠错?
cd ..