终端编程 Agent:从 CLI 到 PR 的全链路 本节摘要:到 2026 年,编程 Agent 的形态已经定型:一个 TUI 外壳(Harness)、一份有状态的计划、一个沙箱化的工具面、一个「计划-行动-观察-恢复」的循环。Claude Code、Cursor 3、OpenCode 从 50 英尺外看几乎一模一样。本节是 17 个产品级毕业项目中的第一个,要求你端到端构建一个编程 Agent——从命令行输入到 GitHub 拉取请求输出——并在 SWE-bench Pro 上对照 mini-swe-agent 与 Live-SWE-agent 度量。你会学到,真正的难点不是模型调用,而是工具循环的稳定性、沙箱隔离与 50 轮运行下的成本天花板。
本节摘要:到 2026 年,编程 Agent 的形态已经定型:一个 TUI 外壳(Harness)、一份有状态的计划、一个沙箱化的工具面、一个「计划-行动-观察-恢复」的循环。Claude Code、Cursor 3、OpenCode 从 50 英尺外看几乎一模一样。本节是 17 个产品级毕业项目中的第一个,要求你端到端构建一个编程 Agent——从命令行输入到 GitHub 拉取请求输出——并在 SWE-bench Pro 上对照 mini-swe-agent 与 Live-SWE-agent 度量。你会学到,真正的难点不是模型调用,而是工具循环的稳定性、沙箱隔离与 50 轮运行下的成本天花板。本节把整个工程拆成八个可落地步骤,并给出一个可复现的评分量表。
对应原课程:Phase 19 · Lesson 01 ·
terminal-native-coding-agent(原英文phases/19-capstone-projects/01-terminal-native-coding-agent/docs/en.md)。
阅读完本节,你应当能够:
agent run <repo> "<task>" 命令并展示三栏视图。git worktree 隔离任务,并实现八类 2026 生命周期钩子。编程 Agent 在 2026 年成了 AI 应用最大的品类。Claude Code(Anthropic)、Cursor 3 配合 Composer 2 与 Agent Tabs、Amp(Sourcegraph)、OpenCode(11 万 Star)、Factory Droids、Google Jules,都是同一架构的变体:一个终端外壳、一个受权限控制的工具面、一个沙箱、一个围绕前沿模型构建的「计划-行动-观察」循环。前沿很窄——Live-SWE-agent 用 Opus 4.5 在 SWE-bench Verified 上达到 79.2%——但工程很宽。多数失败模式不是模型出错,而是工具循环不稳定、上下文中毒、token 成本失控、文件系统破坏性操作。
你无法从外部理解这些 Agent,必须亲手造一个:亲眼看着循环在第 47 轮因 ripgrep 返回 8MB 匹配而崩溃,再重建截断层。这就是本节毕业项目的意义。
外壳有四个面。计划(Plan) 维护一个 TodoWrite 风格的状态对象,模型每轮重写它。行动(Act) 分发工具调用(读、改、运行、搜索、git)。观察(Observe) 捕获 stdout/stderr/退出码,截断后把摘要回喂。恢复(Recover) 处理工具错误,不让上下文窗口爆掉或无限循环。2026 年版还多一层:钩子(Hooks)——PreToolUse、PostToolUse、SessionStart、SessionEnd、UserPromptSubmit、Notification、Stop、PreCompact,运营者在这里注入策略、遥测与护栏。
计划状态的骨架(整体重写而非增量):
PLAN_SCHEMA = { "items": [ # 每项: {status, content, notes} {"status": "pending", "content": "定位 worker.rs 并枚举互斥锁用法", "notes": ""}, {"status": "in_progress", "content": "识别竞争下的共享状态", "notes": ""}, {"status": "done", "content": "提出修复并验证测试", "notes": "test pass"}, ] } # 模型每轮把整份 PLAN 作为一次工具调用的入参回吐,而非只发 diff
工具面的最小集合(每个工具输出截断到 4k token):
TOOLS = ["read_file", "edit_file", "ripgrep", "tree_sitter_symbols", "run_shell", # 带 timeout "git"] # status / diff / commit / push # 通过 MCP StreamableHTTP 暴露,让外壳与传输解耦
成本控制三层硬截断:50 轮、200k 上下文、单任务 5 美元;PreCompact 钩子在 150k 标记处把旧轮次总结成一个 prior-state 块,腾出空间给新观察而不丢计划。
user CLI -> harness (Bun + Ink TUI) | v plan / act / observe loop <---> Claude Sonnet 4.7 / GPT-5.4-Codex / Gemini 3 Pro | (经 OpenRouter,模型无关) v tool dispatcher (MCP StreamableHTTP client) | +------------+------------+----------+ v v v v read/edit ripgrep tree-sitter git/run | | | | +------------+------------+----------+ | v E2B / Daytona sandbox (worktree isolated) | v hooks: Pre/Post, Session, Prompt, Compact | v OpenTelemetry -> Langfuse (spans, tokens, $) | v PR via GitHub app
技术栈要点:外壳运行时用 Bun 1.2 + Ink 5(终端里的 React);代码搜索用 ripgrep 子进程 + 预编译的 17 语言 tree-sitter 解析器;隔离用 git worktree add 每任务一个分支,成败都清理;可观测性用 OpenTelemetry SDK 配 gen_ai.* 语义约定,发往自托管的 Langfuse;PR 发布用细粒度 token 的 GitHub App,作用域限定目标仓库。
本节产出一个可复用技能(原课程 outputs/skill-terminal-coding-agent.md):给定仓库路径与任务描述,在沙箱里跑完整个计划-行动-观察循环,返回 PR 链接与一份追踪包(Trace Bundle)。评分量表如下:
| 权重 | 标准 | 度量方式 |
|---|---|---|
| 25 | SWE-bench Pro pass@1 对基线 | 你的外壳 vs mini-swe-agent,30 个匹配的 Python 任务 |
| 20 | 架构清晰度 | 计划/行动/观察分离、钩子面、工具 schema,对照 Live-SWE-agent 布局评审 |
| 20 | 安全 | 沙箱逃逸测试、权限提示、破坏性命令护栏通过红队 |
| 20 | 可观测性 | 追踪完整性(100% 工具调用被 span 化)、每轮 token 计费 |
| 15 | 开发者体验 | 冷启动 < 2s、崩溃可恢复计划、Ctrl-C 干净取消 |
| 100 |
一次典型运行:
$ agent run ./my-repo "Fix the race condition in worker.rs" [plan] 1 locate worker.rs and enumerate mutex uses 2 identify shared state under contention 3 propose fix, verify tests [tool] ripgrep mutex.*lock -t rust (44 matches, truncated) [tool] read_file src/worker.rs 120..180 [tool] edit_file src/worker.rs (+8 -3) [tool] run_shell cargo test worker:: (passed) [plan] 1 done · 2 done · 3 done [done] PR opened: #482 turns=9 tokens=38k cost=$0.41
各家 2026 编程 Agent 底层都是同一个循环,差异在外围挂了什么。Claude Code 把钩子和计划状态做成一等公民,生态最成熟;Cursor 3 用 Composer 2 + Agent Tabs 强调多任务并行与编辑器内联;OpenCode 是开源标杆(11 万 Star),适合二次定制;mini-swe-agent 是极简基线,适合做对照实验。本节建议先照搬 Live-SWE-agent 的布局搭骨架,再把模型从 Claude Sonnet 4.7 换成经 OpenRouter 接的任意前沿模型——这正是「模型无关」外壳的价值。
curl 外部 URL,再写一个尝试写到 worktree 之外,确认两者都被 PreToolUse 钩子挡住,记录尝试。.agent/state.json 以便崩溃恢复。git worktree 每任务一分支,E2B/Daytona 提供容器,主机文件系统不可达。gen_ai.* 语义约定发往 Langfuse,100% 工具调用 span 化。下一节,我们从「写代码」转向「读代码」——构建一个跨仓库的语义检索系统(RAG over Codebase),支撑 Agent 的代码库问答能力。