从0打造编码Agent 岔路 阅读 + 实验 ≈ 25 分钟 页内实验 ×1
A0 工作流还是 Agent:什么时候需要 agent,什么时候一段代码就够? 岔路课

通用 agent 和编码 agent 差在哪?

通用 agent 与编码 agent
Hermes 是一个通用的个人 agent:发消息、管日程、写代码都做。它在代码仓库里会自动切到「编码姿态」——循环不换,换的是它看到的东西:一段约 580 token 的编码守则、一份工作区快照;只有打开 focus 模式,才会再收窄工具集、把非编码技能的说明降成只剩名字。把 pin 上的判定代码原样加载实跑:本课的任务仓单独拷出来被判成通用,放在项目目录里原地跑却被判成编码——因为往上三层有个 requirements.txt。
❓

本课一问:第 3 课说,编码任务有一样别处少有的东西——测试,每一轮都能拿到客观的反馈。除此之外,一个「编码 agent」和一个什么都做的通用 agent,到底差在哪?是另一套循环吗?

这一课先不碰:Hermes 的记忆(A6)、审批与沙箱(A3、A4);不同编码 agent 之间的差别(A1–A9 的对照带)。

从已有出发

本课的六个对照物里,只有 Hermes(pin 56dc01d9)不是编码 agent:它是 Nous Research 做的通用个人 agent,能接消息平台、管日程、调各种工具,写代码只是它会做的事之一。它的默认身份说明是这样写的:

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过
Hermes · 默认身份说明(prompt_builder.py) hermes-agent@56dc01d9:agent/prompt_builder.py:144-152需联网 MIT
144DEFAULT_AGENT_IDENTITY = (
145 "You are Hermes Agent, an intelligent AI assistant created by Nous Research. "
146 "You are helpful, knowledgeable, and direct. You assist users with a wide "
147 "range of tasks including answering questions, writing and editing code, "
148 "analyzing information, creative work, and executing actions via your tools. "
149 "You communicate clearly, admit uncertainty when appropriate, and prioritize "
150 "being genuinely useful over being verbose unless otherwise directed below. "
151 "Be targeted and efficient in your exploration and investigations."
152)
👆 带下划线的行可以点:点哪行,讲哪行。

贯穿任务在这里换个问法:如果在 parse_date 的任务仓里打开 Hermes,它会把自己当成编码 agent 吗?

先自己设计:看到 .git 就切

最直接的规则:当前目录是 git 仓库,就注入编码守则;不是,就当通用助手。拿本课构造的几个目录试一下(文件清单是实跑时的真实目录):

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案有 .git 就切到编码姿态
目录有 .git有 .py朴素规则判定
本课任务仓 parse_date 单独拷进一个空目录(没有 .git,没有清单文件)—✓general
同上,先 git init✓✓coding
同上,不 git init,只加一个 AGENTS.md—✓general
一个只有 notes.md 的笔记仓,git init 过✓—coding

它什么时候切到编码姿态

判定写在一个函数里:

Hermes · _detect_profile_name:通用还是编码 hermes-agent@56dc01d9:agent/coding_context.py:434-468需联网 MIT
434def _detect_profile_name(mode: str, platform: str, cwd_str: str) -> str:
435 """Resolve which profile applies.
436
437 ``auto``/``focus``: coding when the surface is interactive AND the cwd is a
438 code workspace (a git repo or a recognised project root). ``on``: always
439 coding. ``off``: always general.
440
441 A git repo rooted at ``$HOME`` (the dotfiles pattern) is NOT a workspace
442 signal — without the guard, every session anywhere under a dotfiles-managed
443 home directory would silently flip to the coding posture.
444
445 Detection is intentionally not memoized: it's a handful of ``stat`` calls,
446 and callers resolve the mode once per session anyway. Caching here would
447 risk a stale posture if a long-lived process (gateway/TUI) serves sessions
448 from different working directories.
449 """
450 if mode == "off":
451 return GENERAL_PROFILE.name
452 if mode == "on":
453 return CODING_PROFILE.name
454 if platform and platform.strip().lower() not in INTERACTIVE_CODING_PLATFORMS:
455 return GENERAL_PROFILE.name
456 cwd = Path(cwd_str)
457 # A recognized project root (manifest / AGENTS.md / .cursorrules) is a code
458 # workspace on its own — cheap stat checks, no scan.
459 if _marker_root(cwd) is not None:
460 return CODING_PROFILE.name
461 git_root = _git_root(cwd)
462 if git_root is not None and git_root == _home():
463 git_root = None # dotfiles repo at $HOME — not a code workspace
464 # A bare git repo only counts when it actually holds code, so `git init` on a
465 # notes/writing/research folder stays in the general posture.
466 if git_root is not None and _has_code_files(git_root):
467 return CODING_PROFILE.name
468 return GENERAL_PROFILE.name
👆 带下划线的行可以点:点哪行,讲哪行。
Hermes · 项目标记与交互式界面 hermes-agent@56dc01d9:agent/coding_context.py:67-83需联网 MIT
67CODING_TOOLSET = "coding"
68
69# Surfaces where a coding posture makes sense under ``auto``. Messaging
70# platforms (telegram, discord, slack, …) are intentionally absent — a chat bot
71# in a group is not pair-programming.
72INTERACTIVE_CODING_PLATFORMS = {"cli", "tui", "acp", "desktop", ""}
73
74# Project-root signals that mark a directory as a code workspace even when it
75# isn't (yet) a git repo. Cheap filename checks — no parsing.
76_PROJECT_MARKERS = (
77 "pyproject.toml", "setup.py", "setup.cfg", "requirements.txt",
78 "package.json", "tsconfig.json", "deno.json",
79 "Cargo.toml", "go.mod", "pom.xml", "build.gradle", "build.gradle.kts",
80 "Gemfile", "composer.json", "mix.exs", "pubspec.yaml",
81 "CMakeLists.txt", "Makefile", "Dockerfile",
82 "AGENTS.md", "CLAUDE.md", ".cursorrules",
83)
👆 带下划线的行可以点:点哪行,讲哪行。

把 pin 上的 agent/coding_context.py 原样加载(只把一个跑 git 的辅助函数换成等价的 subprocess 调用),在临时目录里复制本课的任务仓、按用例加 .git 或 AGENTS.md,逐个问它判成什么:

用例会话来源coding_context判定注入编码守则工作区快照收窄工具集降级的技能类别
本课任务仓 parse_date 单独拷进一个空目录(没有 .git,没有清单文件)cliautogeneral————
同一个任务仓,原地(在本课项目 04-实践项目/tasks/ 下面)cliautocoding✓✓——
同上,先 git initcliautocoding✓✓——
同上,不 git init,只加一个 AGENTS.mdcliautocoding✓✓——
同一个任务仓,但会话来自 telegramtelegramautogeneral————
一个只有 notes.md 的笔记仓,git init 过cliautogeneral————
任务仓 + git init,配置 coding_context: focusclifocuscoding✓✓coding18
任务仓(无 .git),配置 coding_context: onclioncoding✓———
任务仓 + AGENTS.md,配置 coding_context: offclioffgeneral————
🎯

读这张表的三个地方:① 前两行是同一个任务仓:单独拷进空目录,判成 general;原地放在本课项目里,判成 coding——判定函数往上走了 3 层,在项目根找到了 requirements.txt,于是工作区快照的根目录写的是项目根,不是任务仓本身(见下面的快照原文);② git init 过的笔记仓(只有 .md)仍是通用:光有 .git 不够,要有代码文件;③ 只有 focus 那一行收窄了工具集、降级了 18 个非编码技能类别——默认的 auto 只动 system prompt。

原地那一行实际注入的工作区快照(本机路径已换成占位符):

Workspace (snapshot at session start — re-check with `git` before acting on it):
- Root: <project>
- Project: requirements.txt

加了 AGENTS.md 的那一行:

Workspace (snapshot at session start — re-check with `git` before acting on it):
- Root: <repo>
- Context files: AGENTS.md

切过去,改了什么

模块开头的文档把设计说得很清楚:编码姿态是一份数据(profile),由 system prompt、工具集选择、子 agent 委派这几处各自读取;模式在会话开始时只解析一次、之后不变,因为中途改 system prompt 会让前缀缓存失效(A6、A8 会细讲)。

Hermes · coding_context.py 模块说明 hermes-agent@56dc01d9:agent/coding_context.py:10-49需联网 MIT
10The posture is modelled as a frozen :class:`RuntimeMode` selected from a small
11:class:`ContextProfile` registry (today: ``coding`` and ``general``). A profile
12is *data* — it declares the toolset to collapse to, the operating brief to
13inject, and hints for other domains (model routing, memory, subagents). Every
14domain reads the same resolved object instead of probing git/config itself:
15
16 * **System prompt** — ``RuntimeMode.system_blocks()`` → the operating brief +
17 a live git/workspace snapshot (``agent/system_prompt.py``).
18 * **Toolset** — ``RuntimeMode.toolset_selection()`` → the ``coding`` toolset
19 plus the user's enabled MCP servers (``cli.py`` / ``tui_gateway``). Only
20 under the opt-in ``focus`` mode: the default posture is prompt-only and
21 never touches the user's configured toolsets (toolsets like messaging /
22 smart-home / music are off-by-default anyway, and someone who explicitly
23 enabled image-gen or Spotify shouldn't lose it for being in a git repo).
24 * **Delegation** — subagents inherit the parent's toolset and run through the
25 same prompt builder, so the coding posture propagates to children for free.
26 * **Model / memory / compression** — declared on the profile
27 (``model_hint``, ``memory_policy``) as the extension seam; consumers read
28 ``mode.profile`` rather than re-deciding.
29
30Cache safety
31------------
32The mode is resolved **once** and is immutable. The workspace snapshot is built
33once at prompt-build time and baked into the *stable* system-prompt tier — never
34re-probed per turn (that would shatter the prompt cache). Branch and dirty state
35drift mid-session, so the brief tells the model to re-check with ``git`` before
36acting on the snapshot. A ``/coding`` flip therefore only takes effect next
37session (deferred), the same contract as ``/skills install`` vs ``--now``.
38
39Activation (config ``agent.coding_context``):
40
41 * ``auto`` (default) — posture (brief + snapshot) on an interactive coding
42 surface sitting in a code workspace (git repo or recognised project root).
43 Prompt-only; toolsets and the skill index untouched.
44 * ``focus`` — like ``auto``, but additionally collapses the toolset to the
45 ``coding`` set + enabled MCP servers and demotes non-coding skill
46 categories to names-only in the prompt's skill index (no skill is ever
47 hidden). Explicit opt-in for a lean schema.
48 * ``on`` — force the posture anywhere (incl. non-workspaces). Prompt-only.
49 * ``off`` — disable entirely.
👆 带下划线的行可以点:点哪行,讲哪行。

注入的那段编码守则有 2,626 个字符,用本课项目模型的 tokenizer 数约 577 token 。它写的是一个编码 agent 该有的规矩:

Hermes · CODING_AGENT_GUIDANCE(编码守则) hermes-agent@56dc01d9:agent/coding_context.py:214-265需联网 MIT
214# Operating brief for the coding posture. Tool names referenced here (read_file,
215# search_files, patch, write_file, terminal, todo) are in the coding toolset and
216# in _HERMES_CORE_TOOLS, so they're present on every surface this fires on.
217CODING_AGENT_GUIDANCE = (
218 "You are a coding agent pairing with the user inside their codebase. "
219 "Operate like a careful senior engineer.\n"
220 "\n"
221 "Gather context first:\n"
222 "- Read the relevant files with `read_file` and locate code with "
223 "`search_files` before changing anything. Trace a symbol to its definition "
224 "and usages rather than guessing its shape.\n"
225 "- Batch independent lookups: when several reads/searches don't depend on "
226 "each other, issue them together in one turn instead of one at a time.\n"
227 "- Never invent files, symbols, APIs, or imports. If you haven't seen it in "
228 "the repo, go look. Don't assume a library is available — check the project "
229 "manifest (pyproject.toml / package.json / Cargo.toml / go.mod) and how "
230 "neighbouring files import it.\n"
231 "\n"
232 "Make changes through the tools, not the chat:\n"
233 "- Edit with `patch`/`write_file`. Do NOT print code blocks to the user as "
234 "a substitute for editing — apply the change, then summarise it. Only show "
235 "code when the user explicitly asks to see it.\n"
236 "- Match the project's existing style and conventions; AGENTS.md / "
237 "CLAUDE.md / .cursorrules already in context win over your defaults. Touch "
238 "only what the task needs — no drive-by refactors, renames, or reformatting "
239 "— and add any imports/dependencies your code requires.\n"
240 "- If an edit fails to apply, re-read the file to get the current exact "
241 "contents before retrying — don't repeat a stale patch. If the same region "
242 "fails twice, rewrite the enclosing function or file with `write_file` "
243 "instead of attempting a third patch.\n"
244 "\n"
245 "Verify, and know when to stop:\n"
246 "- Use `terminal` for git, builds, tests, and inspection. Run the relevant "
247 "tests/linter/build and confirm they pass before claiming the work is done.\n"
248 "- Terminal state persists across calls: current directory and exported "
249 "environment variables carry forward. Activate a virtualenv or export setup "
250 "vars once, then reuse that state instead of re-sourcing it before every "
251 "test command.\n"
252 "- Fix root causes, not symptoms: when you find a bug, check sibling call "
253 "paths for the same flaw and fix the class, not just the reported site.\n"
254 "- When fixing linter/type errors on a file, stop after about three "
255 "attempts on the same file and ask the user rather than looping.\n"
256 "- Track multi-step work with `todo`. Reference code as `path:line` instead "
257 "of pasting whole files.\n"
258 "\n"
259 "Respect the user's repo: don't commit, push, or rewrite history unless "
260 "asked, and never read, print, or commit secrets — leave `.env` and "
261 "credential files alone unless the user explicitly asks. The Workspace "
262 "block below is a snapshot from session start — re-run `git status`/"
263 "`git branch` before relying on it. Be concise: lead with the change or "
264 "answer, not a preamble."
265)
👆 带下划线的行可以点:点哪行,讲哪行。
Hermes · 工具集只在 focus 模式下收窄 hermes-agent@56dc01d9:agent/coding_context.py:505-521需联网 MIT
505 def toolset_selection(self, config: Optional[dict[str, Any]] = None) -> Optional[list[str]]:
506 """Toolset list for this posture, or ``None`` to keep the platform default.
507
508 Non-``None`` only under the opt-in ``focus`` mode. The default posture
509 is prompt-only: most strippable toolsets are off-by-default anyway, and
510 a user who explicitly enabled one (image-gen for frontend/game assets,
511 messaging for build notifications, …) keeps it while coding.
512
513 Callers apply this only when the user hasn't pinned an explicit
514 selection (``--toolsets``, ``HERMES_TUI_TOOLSETS``, …); they never
515 override a pin. Returns the profile's toolset plus enabled MCP servers.
516 """
517 if self.config_mode != "focus":
518 return None
519 if self.profile.toolset is None:
520 return None
521 return [self.profile.toolset, *_enabled_mcp_servers(config)]
👆 带下划线的行可以点:点哪行,讲哪行。

所以,差在哪

通用姿态编码姿态(auto,默认)编码姿态(focus,可选)
循环同一个同一个同一个
system prompt默认身份说明+ 编码守则(约 577 token)+ 工作区快照同 auto
工具集平台默认平台默认(不动)收窄到 coding + 已启用的 MCP
技能索引全部全部18 个非编码类别只留名字
什么时候决定会话开始会话开始,之后不变会话开始,之后不变

「共享同一个循环」是从代码结构读出来的:姿态只是一份被各处读取的数据,循环代码里没有为编码另写一条路 。调研和设计卡原来写的是「只改三处:system prompt、工具集、技能索引」,对照 pin 源码要加一个限定:默认的 auto 只改 system prompt,后两处要用户显式打开 focus 才改 。

说到底,编码 agent 和通用 agent 的差别,主要不在循环里,而在它看到的东西:一份告诉它先读后改、用工具改、改完跑测试的守则;一份告诉它根目录在哪、分支是什么、有哪些清单与上下文文件的快照;以及编码这个领域本身提供的东西——能自动验证的测试。Anthropic 在《Building effective agents》的附录里给编码 agent 的理由也是这些:方案能用自动化测试验证、agent 能拿测试结果当反馈反复改、问题空间结构清楚、产出质量能客观衡量 。

⚠
常见误解:「Hermes Agent 是给 Nous 的 Hermes 3 / 4 模型配套设计的。」

两者同名、同一家公司,但没有技术血缘。Hermes Agent 的源码里还专门警告:Hermes 3 与 Hermes 4 模型不是 agentic 的,缺少 agent 工作流需要的工具调用能力,建议换用别的模型(hermes_cli/model_switch.py:206-211,原文见岔路 B1) 。

证据三问

问本课的回答
① 它解决什么问题?一个通用 agent 进到代码仓库时,自动拿到编码需要的守则与工作区信息,不必另写一套编码 agent
② 为什么可能有效?编码任务的差别主要在上下文(守则、快照、测试),不在控制流;把差别做成会话开始时解析一次的数据,既不改循环,也不破坏前缀缓存
③ 实验上呢?判定逻辑在临时目录里原样实跑 9 个用例(上表);没有「开不开编码姿态」对任务成功率影响的公开数据

演进地图:你在这里

什么时候需要 agent:每一代都被上一代的某个真数字逼出来
  1. 朴素 单次调用:报错 + 源文件发给模型,要回整份新文件,覆写后重跑 本课项目 workflow.py · 5 步全由代码决定
    解决了只调一次模型,每一步都写在代码里:可预期、好测、便宜。
    卡在T2 workflow 轨 2 / 4 通过。parse_date 只改了报错指到的那一行,重跑才露出第二处(datetime.date(2024, 2, 5) != datetime.date(2024, 3, 5));slugify 的回复只有函数片段,兜底规则把 130 字符的片段当整份文件写回(原文件 365 字符),丢了 import re。
  2. 第一代 加步骤、加分支:五种 workflow 模式 prompt chaining · routing · parallelization · orchestrator-workers · evaluator-optimizer
    解决了每一类失败补一条预先写好的路径:测试没过就带着新报错再来一次(评估者是测试的退出码)、回复格式不对就拒绝写回。
    卡在分支要在设计时写出来,可真实路径是在运行时长出来的:T2 agent 轨 4 个任务走出 4 条不同的工具序列;同一配置重跑 4 次 parse_date(seed 1 / 2 / 3 / 1),走出 4 条。
  3. 第二代 把「下一步做什么」交给模型:while + 工具 = agent 本课项目 loop.py(A1 细讲)
    解决了边在运行时长出来:T2 agent 轨 3 / 4 通过,包括 workflow 没修好的 parse_date 与 slugify。
    卡在代价:workflow 已经能解决的任务上 token word_freq ×5.3、leap_year ×4.5,其中 word_freq 的 agent 反而没修好;同配置、同 seed 重跑,2 / 4 个任务在中途分叉。
  4. 第三代 先判别再选轨道:能用一段代码就不用 agent 三条判别标准 · 先直接调 API
    解决了录制前写下三条标准并逐个预测,录制后对照:预测对 3 / 4。
    卡在预测错的 slugify 不是任务本身需要 agent,而是 workflow 自己的写回规则出了错(回复没有 FILE: 标记时的兜底)。每条轨道每个任务只跑 1 次:只能当例子,不能当比率。
  5. 岔路 混合:把固定编排写成 agent 的一个工具 learn-claude-code s16 Workflow Runtime
    解决了编排形状固定的部分(并行审查、逐条验证、断点续跑)写进脚本;模型只决定「要不要调这个 workflow、传什么参数」。
    卡在同一个仓库里两种说法并存:README :11 与 :41 说 agency 来自训练、不是编排;:239 说编排形状固定就写进代码。界线画在哪,没有实验回答。
  6. 岔路 通用 agent 切换到编码姿态 Hermes coding_context.py 你在这里
    解决了同一个循环,识别出代码工作区后注入编码说明(约 577 token,Qwen3 tokenizer 计)与工作区快照;focus 模式才会收窄工具集与技能索引。
    卡在判定靠文件标记:本课任务仓单独拷出来(README.md、dateparse.py、test_dateparse.py,没有 .git)在默认 auto 下被判成 general;放在项目目录里原地跑,却因为往上 3 层有个 requirements.txt 被判成 coding。姿态在会话开始解析一次,中途不变。
数字口径:本课项目 T2 双轨录制(Qwen3-8B-MLX-4bit,本机 mlx_lm.server,seed 1,2026-09-26;4 个任务各跑 1 次,只说明现象会发生,不估计比率);孪生运行 = A1 里与 T2 agent 轨配置相同、seed 相同的那次运行;Hermes 一行是 pin 56dc01d9 的原实现在临时目录里实跑的判定。

一句话

📌
通用 agent 变成编码 agent,不换循环,换的是它看到的说明、工具与工作区——而且在会话开始时就定下。

回主线

模型决定下一步的那个循环,到底长什么样? → A1 循环

或者先做本章的实验课:4 个任务,先预测、再看两条轨道的真实结果。

你可能会问