一、Agent演化简介
过去一段时间,基于LLM的Agent的演化路线经历以下几个关键阶段[参考资料: 1]:
- 第一阶段:提示词工程(Prompt Engineering)。通过调整提示词,让模型的单次输出更加稳定和符合预期,例如,系统提示词System Prompt、少样本提示Few-shot、思维链CoT等;
- 第二阶段:上下文工程(Context Engineering)。通过管理模型的上下文,让模型在多步行动中保留记忆和保持目标,例如,分层记忆、上下文管理(压缩/回滚/删减上下文)、ReAct、RAG都可以归为这一类。
- 第三阶段:驾驭工程(Harness Engineering)。通过提供完整的运行环境,让Agent长期、可控、可验证地完成任务,例如,沙箱隔离、状态管理、验证器、审批流等。
更简单地理解,三个阶段分别在解决LLM“听不听得懂、记不记得住、干不干得了”的问题,对应的任务分别为单次对话、多轮对话、复杂任务。
当前最火的Claude Code和Codex都可归为第三阶段的产物,虽然主打的是解决编程任务,但实际上现在都在往通用Agent的方向演进。

图1.1 Claude Code CLI、Codex CLI以及后面要介绍的Mini Coding Agent[参考资料: 2]
二、什么是Coding harness?
本文的大部分内容都来自[参考资料: 2],该文作者Sebastian Raschka同时也是《Build a large language model from scratch》的作者,感兴趣的读者可以关注他。
在开始之前,先区分几个概念(这也是在这个新概念层出不穷的大模型时代,不得不做的事):
- 大语言模型(LLM):原始的大模型;
- 推理模型(Reasoning LLM):一种经过优化的LLM,通过输出中间推理轨迹,进行更多的自我验证,有点类似人在回答问题前进行思考;
- 智能体(Agent):一个利用模型并结合工具、记忆和环境反馈进行运作的循环系统;
- Agent harness:围绕智能体构建的软件脚手架,负责管理上下文、工具使用、提示词、状态和控制流,如前段时间非常火的OpenClaw;
- Coding harness:Agent harness的一种,主要针对编程任务的特定架构,负责管理代码上下文、工具、执行以及迭代反馈,如Claude Code和Codex。
事实上,LLMs和推理模型本身也能够独立(无需复杂的harness)解决编程任务,但编程工作并不仅仅是下一个token的生成。很大一部分工作涉及代码仓库导航、搜索、函数查找、补丁应用、测试执行、错误检查,以及将所有相关信息保持在上下文中。而一个优秀的Coding harness能够让一个有(或无)推理能力的模型在使用体验上比单纯的聊天框交互强大得多,因为它提供了上下文管理、权限控制等多方面的功能优化。
一般来讲,当我们提到Coding harness时,如图所示,通常指LLM之外的软件层,该层负责组装提示、暴露工具、跟踪文件状态、应用编辑、运行命令、管理权限、缓存稳定前缀、存储记忆等多种功能。

图2.1 Coding harness由三层组成:模型家族、Agent循环和Runtime支持。模型提供“引擎”,Agent循环推动迭代问题求解,Runtime支持提供底层基础设施。在循环内部,“观察(observe)”从环境收集信息,“检查(inspect)”分析这些信息,“选择(choose)”决定下一步,“执行(act)”则将其实施。
在当前大模型能力越发接近的情况下,harness工程的好坏很大程度上影响了用户体验,比如当前常见的做法,将各种模型(包括DeepSeek、GLM、Kimi等)接入Claude Code中使用,往往体验更好。但同时,大家也发现相比Claude Code自带的Claude系列模型(如Sonnet、Opus)体验还是有一些差距,猜测Claude自家的模型可能在这套harness架构上进行了基于强化学习的后训练,以提升其效果。
三、Coding Harness的主要组成和实现
在该部分,我们会结合作者开源的Mini Coding Agent[参考资料: 3],来介绍Coding harness(这里混用了Coding Agent和Coding harness的概念)的六个主要组成部分:
- 实时的仓库上下文
智能体会预先收集稳定不变的工作区信息,比如仓库目录结构、说明文档以及git状态。
- 提示词构建与缓存复用
将稳定不变的提示词前缀,与不断变化的请求内容、会话记录、记忆区分开,这样重复调用模型时可以高效复用静态部分,减少开销。对应的技术称为Prefix Cache,是KV Cache的一种具体应用(KV cache可以参考之前的文章“大模型推理时的KV cache介绍和实践”)。
- 结构化工具、参数校验与权限控制
模型只能通过预定义的工具来完成操作,包括输入参数校验、工作区路径校验以及必要的权限审批,而不是执行任意的不受限制的操作。
- 上下文精简与输出管理
过长的输出会被截断,重复的读取操作会被去重,较早的会话记录会被压缩,从而将提示词的总长度控制在合理范围内。
- 会话记录、记忆与恢复能力
运行时既保留完整且持久化的会话记录,又维护一份更精简的工作记忆,因此会话可以被恢复,同时借助工作记忆保留关键状态。
- 任务委派与子智能体
可以把范围明确的子任务委派给辅助智能体,这些辅助智能体会继承足够的上下文来完成协助工作,但其行为始终被限制在一定范围内。

图3.1 Coding harness的主要组成部分
3.1 实时的仓库上下文
当用户说出类似“修复测试用例”或“实现某个功能”这样的指令时,LLM应当能够判断当前是否处于Git仓库中、处于哪个分支上,以及哪些项目文档中可能包含相关说明等信息,如图所示。这是因为这些细节会影响正确的操作和实现方式,例如,
- “修复测试用例”并非一条完整的指令——如果智能体能够看到AGENTS.md或项目README.md文件,它就能知道该运行哪个命令进行测试;
- LLM一旦掌握了代码仓库的根目录结构,它就能在正确的地方查找,而不需要盲目猜测;
- 此外,Git分支、状态和提交记录也能提供更多上下文,帮助LLM了解当前正在进行哪些更改以及重点应放在哪里。
一般来讲,Coding Agent在执行任何任务之前,会先收集信息(作为工作区摘要的“稳定事实”),从而避免每次提示都从零开始、或缺乏上下文。

图3.2 harness首先构建一个简短的工作区摘要,该摘要会与用户请求结合,以提供额外的项目上下文。
代码实现
在开始介绍代码实现前,先简单介绍一下项目中的类和其相互关系,如下图所示:
- MiniAgent是核心协调者,负责把模型客户端、工作区上下文、会话存储、工具表和主循环串起来。
- WorkspaceContext用于管理项目的上下文,会在启动时采集cwd、repo root、git状态、最近提交信息和项目说明文件的摘要等;
- SessionStore实现会话的管理,包括保存、加载和查找会话;
- 三个模型客户端遵守complete(prompt, max_new_tokens) -> str这个方法约定,可以被MiniAgent以同一种方式调用。

图3.3 mini coding agent中类的相互关系
重点来看一下项目启动时的上下文构建,具体分析WorkspaceContext的build(cwd)的实现。
# WorkspaceContext 会优先读取这些常见项目说明文件。它们会被裁剪后塞进# prompt prefix,给模型提供 repo-local instructions,但不会让 prompt 过长。DOC_NAMES = ("AGENTS.md", "README.md", "pyproject.toml", "package.json")@classmethoddef build(cls, cwd): # 入口 cwd 可能是相对路径;先 resolve,后续 git 命令和 path guard 都基于 # 绝对路径,避免 prompt 中出现依赖调用位置的相对路径。 cwd = Path(cwd).resolve() def git(args, fallback=""): # Git metadata 对 agent 很有用,但这个脚本也应该能在非标准/损坏 repo # 中启动。因此每个 git call 都有 fallback,不让 context 采集失败拖垮 CLI。 try: result = subprocess.run( ["git", *args], cwd=cwd, capture_output=True, text=True, check=True, timeout=5, ) return result.stdout.strip() or fallback except Exception: return fallback # 如果当前目录不是 git repo,rev-parse 会走 fallback,此时 repo_root 就是 cwd。 repo_root = Path(git(["rev-parse", "--show-toplevel"], str(cwd))).resolve() docs = {} # 同时检查 repo root 和 cwd:用户可能从子目录启动 agent,该子目录也可能有 # 更局部的 README/AGENTS.md。用 relative path 作为 key 可以避免重复收录。 for base in (repo_root, cwd): for name in DOC_NAMES: path = base / name ifnot path.exists(): continue key = str(path.relative_to(repo_root)) if key in docs: continue # 文档只取短摘要,作为 prompt hint,而不是把整份 README 塞给模型。 docs[key] = clip(path.read_text(encoding="utf-8", errors="replace"), 1200)
可以发现,在Mini Coding Agent的实现上,仓库上下文信息主要包括项目的目录结构信息、项目的相关文档(例如"AGENTS.md", “README.md”, “pyproject.toml”, “package.json”)。这里的实现作者有意进行了简化,对文档的摘要构建是通过截断的方式实现的,显然我们也可以通过LLM进行文档的摘要生成,为了避免上下文污染,可以采用子Agent来实现。
3.2 提示词构建与缓存复用
当完成仓库上下文的构建后,下一个问题是如何将这些信息输入LLM。在图3.2中展示了该过程的简化版本(“组合提示词:前缀+请求”),但在实际应用中,每次用户请求都重新组合提示词并处理工作区摘要会相当低效。
在KV Cache的文章中我们已经知道,大模型是没有记忆,也就是说,在多轮请求中仓库的上下文会被反复地多次输入LLM。为了尽可能地复用缓存,在decoder-only的架构下,利胜因果注意力机制的特点,只要保证token及其前序的内容不变,则其计算出来的KV是不变的,也就可以复用。这也就要求我们在多轮会话的过程中,将相对稳定不变的上下文作为“稳定的提示前缀”,并在上下文的尾部增加新信息,同时不要修改之前的内容。
一般来讲,保持不变的内容有:人为定义的Agent规则(包含系统提示词)、工具描述、工作区摘要(大部分情况下维持原样),这部分适合作为“稳定的提示前缀”,提升缓存复用。主要变化的通常是最近的用户请求、历史的对话记录、(可能有的)短期记忆。

图3.4 Agent构建一个稳定的提示前缀,添加变化的会话状态,然后将组合后的提示词输入LLM
代码实现
################################################ 2) Prompt Shape And Cache Reuse ###################################################def build_prefix(self): # 每个工具行都包含 schema、risk 和 description。模型只看文本协议,因此这里的 # 描述越明确,parse/validate 阶段的错误越少。 tool_lines = [] for name, tool in self.tools.items(): fields = ", ".join(f"{key}: {value}"for key, value in tool["schema"].items()) risk = "approval required"if tool["risky"] else"safe" tool_lines.append(f"- {name}({fields}) [{risk}] {tool['description']}") tool_text = "\n".join(tool_lines) # examples 同时覆盖 JSON style 和 XML style。XML style 专门服务 multi-line # 文件内容,降低 JSON escaping 对小模型的压力。 examples = "\n".join( [ '<tool>{"name":"list_files","args":{"path":"."}}</tool>', '<tool>{"name":"read_file","args":{"path":"README.md","start":1,"end":80}}</tool>', '<tool name="write_file" path="binary_search.py"><content>def binary_search(nums, target):\n return -1\n</content></tool>', '<tool name="patch_file" path="binary_search.py"><old_text>return -1</old_text><new_text>return mid</new_text></tool>', '<tool>{"name":"run_shell","args":{"command":"uv run --with pytest python -m pytest -q","timeout":20}}</tool>', "<final>Done.</final>", ] ) # rules 是模型行为约束:一次只能返回 tool 或 final,并禁止空 args、 # 重复无效工具调用等常见 failure modes。 rules = "\n".join([ "- Use tools instead of guessing about the workspace.", "- Return exactly one <tool>...</tool> or one <final>...</final>.", "- Tool calls must look like:", ' <tool>{"name":"tool_name","args":{...}}</tool>', "- For write_file and patch_file with multi-line text, prefer XML style:", ' <tool name="write_file" path="file.py"><content>...</content></tool>', "- Final answers must look like:", " <final>your answer</final>", "- Never invent tool results.", "- Keep answers concise and concrete.", "- If the user asks you to create or update a specific file and the path is clear, use write_file or patch_file instead of repeatedly listing files.", "- Before writing tests for existing code, read the implementation first.", "- When writing tests, match the current implementation unless the user explicitly asked you to change the code.", "- New files should be complete and runnable, including obvious imports.", "- Do not repeat the same tool call with the same arguments if it did not help. Choose a different tool or return a final answer.", "- Required tool arguments must not be empty. Do not call read_file, write_file, patch_file, run_shell, or delegate with args={}.", ]) # prefix 的拼接顺序从身份/规则到工具再到 workspace,保持静态内容集中在前面。 return"\n\n".join([ "You are Mini-Coding-Agent, a small local coding agent running through Ollama.", "Rules:\n" + rules, "Tools:\n" + tool_text, "Valid response examples:\n" + examples, self.workspace.text(), ])def prompt(self, user_message): # 每轮 prompt = stable prefix + compact memory + compact transcript + 当前请求。 # 这种分层让 stable prefix 可复用,也让动态上下文的增长更可控。 return"\n\n".join([ self.prefix, self.memory_text(), "Transcript:\n" + self.history_text(), "Current user request:\n" + user_message, ])
从下述的代码实现中,可以发现前缀prefix,主要包含:
- 系统提示词;
- 人为定义的Agent规则;
- 工具描述和返回示例;
- 工作区摘要。
拼接的可变内容包含:
- 短期记忆;
- 历史的对话记录;
- 用户请求。
事实上,从这个实现中,我认为如果把“历史的对话记录”前置到“短期记忆”之前,应该能提升缓存复用。但也存在一些隐患,这会将用户的请求拆开,可能会影响实际的执行效果。
3.3 结构化工具、参数校验与权限控制
工具访问和工具使用,是让harness开始感觉起来不那么像聊天、而更像一个智能体的地方。
聊天框里的LLM可以用文字的方式建议用户执行命令,但一个处于Coding harness中的LLM应该能够直接执行命令并取回结果(而不是我们手动执行命令、再把结果粘贴回聊天里)。
工具调用通常不会让LLM即兴发挥任意语法,而是提供一份允许使用的、预先定义好的工具列表,每个工具都有明确的输入和输出(包括,像Python的subprocess.call这样的东西也可以作为其中一部分,这样智能体也就能执行一系列shell命令)。工具使用的流程如下图所示:

图3.5 模型发出一个结构化的动作,harness对其进行校验,视情况请求用户批准,执行它,然后把(经过限制的)结果反馈回循环中
以Mini Coding Agent中的操作为例,LLM必须选择一个harness能识别的动作(预定义的工具列表中的一个),比如列出文件、读取文件、搜索、运行shell命令、写文件等。它还必须以harness能够检查的形式提供参数,比如常用的json format。
因此,当模型请求执行某项操作时,需要先停下来运行一些程序化检查,例如:
- “这是一个已知的工具吗?”
- “参数合法吗?”
- “这需要用户批准吗?”
- “请求的路径究竟是否在工作区内?”
只有在这些检查通过后,任务才会实际执行。

图3.6 Mini Coding Agent 中工具调用批准请求的示意图
当然,运行Coding agents本身带有一定风险,但harness的这些检查也提升了可靠性,因为模型不会执行完全随意的命令。并且可以看到,除了拒绝格式错误的操作和审批管控外,还可以通过检查文件路径,把文件访问限制在仓库内部。从某种意义上说,harness在给模型更少自由的同时,也提升了可用性。
代码实现
在3.2的提示词构建中,我们会提前将可用的工具列表输入给LLM了,这里重点来看看具体的工具定义、权限控制和工具执行。
################################################################# 3) Structured Tools, Validation, And Permissions ####################################################################def build_tools(self): # 工具表是模型可调用能力的唯一来源:schema 给 prompt 看,risky 决定是否审批, # run 指向实际 Python 实现。 tools = { "list_files": { "schema": {"path": "str='.'"}, "risky": False, "description": "List files in the workspace.", "run": self.tool_list_files, }, "read_file": { "schema": {"path": "str", "start": "int=1", "end": "int=200"}, "risky": False, "description": "Read a UTF-8 file by line range.", "run": self.tool_read_file, }, "search": { "schema": {"pattern": "str", "path": "str='.'"}, "risky": False, "description": "Search the workspace with rg or a simple fallback.", "run": self.tool_search, }, "run_shell": { "schema": {"command": "str", "timeout": "int=20"}, "risky": True, "description": "Run a shell command in the repo root.", "run": self.tool_run_shell, }, "write_file": { "schema": {"path": "str", "content": "str"}, "risky": True, "description": "Write a text file.", "run": self.tool_write_file, }, "patch_file": { "schema": {"path": "str", "old_text": "str", "new_text": "str"}, "risky": True, "description": "Replace one exact text block in a file.", "run": self.tool_patch_file, }, } if self.depth < self.max_depth: # delegate 只在未达到深度上限时暴露给模型,避免工具 schema 暗示一个不可用能力。 tools["delegate"] = { "schema": {"task": "str", "max_steps": "int=3"}, "risky": False, "description": "Ask a bounded read-only child agent to investigate.", "run": self.tool_delegate, } return tools# 工具执行def run_tool(self, name, args): # run_tool 是所有工具调用的统一入口:先查表,再校验,再检查重复和审批, # 最后执行具体 tool implementation。 tool = self.tools.get(name) if tool isNone: returnf"error: unknown tool '{name}'" try: # validate_tool 在 approve 之前运行,避免无效 risky request 触发无意义的用户审批。 self.validate_tool(name, args) except Exception as exc: example = self.tool_example(name) message = f"error: invalid arguments for {name}: {exc}" if example: # 给模型返回一个正确示例,下一轮更容易自我修复格式。 message += f"\nexample: {example}" return message if self.repeated_tool_call(name, args): # 连续重复同一工具通常表示模型卡住了;直接返回错误,推动它换策略或结束。 returnf"error: repeated identical tool call for {name}; choose a different tool or return a final answer" if tool["risky"] andnot self.approve(name, args): # risky tool 被拒绝也作为 observation 返回,不直接抛出,让模型可以说明受限原因。 returnf"error: approval denied for {name}" try: return clip(tool["run"](args)) except Exception as exc: # Tool implementation 的异常被包装成文本 observation,保持 agent loop 不崩溃。 returnf"error: tool {name} failed: {exc}"# 参数校验def validate_tool(self, name, args): # 校验集中在这里,具体 tool implementation 仍保留关键检查作为 defense in depth。 args = args or {} if name == "list_files": # list_files 只接受目录;路径解析时已经包含 workspace escape 检查。 path = self.path(args.get("path", ".")) ifnot path.is_dir(): raise ValueError("path is not a directory") return if name == "read_file": # read_file 使用 1-based inclusive line range,符合用户查看文件时的直觉。 path = self.path(args["path"]) ifnot path.is_file(): raise ValueError("path is not a file") start = int(args.get("start", 1)) end = int(args.get("end", 200)) if start < 1or end < start: raise ValueError("invalid line range") return if name == "search": # 空 pattern 很容易造成全仓库噪声输出,因此直接拒绝。 pattern = str(args.get("pattern", "")).strip() ifnot pattern: raise ValueError("pattern must not be empty") self.path(args.get("path", ".")) return if name == "run_shell": # run_shell 是高风险能力:至少要求非空命令和有限 timeout。 command = str(args.get("command", "")).strip() ifnot command: raise ValueError("command must not be empty") timeout = int(args.get("timeout", 20)) if timeout < 1or timeout > 120: raise ValueError("timeout must be in [1, 120]") return if name == "write_file": # write_file 可以创建新文件,但不允许把目录当文件覆盖。 path = self.path(args["path"]) if path.exists() and path.is_dir(): raise ValueError("path is a directory") if"content"notin args: raise ValueError("missing content") return if name == "patch_file": # patch_file 是 exact replacement:old_text 必须唯一,避免模型误 patch 多处。 path = self.path(args["path"]) ifnot path.is_file(): raise ValueError("path is not a file") old_text = str(args.get("old_text", "")) ifnot old_text: raise ValueError("old_text must not be empty") if"new_text"notin args: raise ValueError("missing new_text") text = path.read_text(encoding="utf-8") count = text.count(old_text) if count != 1: raise ValueError(f"old_text must occur exactly once, found {count}") return if name == "delegate": # delegate 是安全读-only 子 agent 入口,但仍要限制深度和空任务。 if self.depth >= self.max_depth: raise ValueError("delegate depth exceeded") task = str(args.get("task", "")).strip() ifnot task: raise ValueError("task must not be empty") return# 权限审批def approve(self, name, args): # read_only 是最硬的保护层,优先于 approval_policy。 if self.read_only: returnFalse if self.approval_policy == "auto": # auto 模式给模型完整 risky tool 权限,适合受信环境。 returnTrue if self.approval_policy == "never": # never 模式可用于 CI、dry-run 或 delegated child agent。 returnFalse try: # ask 模式下使用 stdin prompt;ensure_ascii=True 避免终端编码问题影响展示。 answer = input(f"approve {name} {json.dumps(args, ensure_ascii=True)}? [y/N] ") except EOFError: # 非交互环境无法审批时默认拒绝。 returnFalse return answer.strip().lower() in {"y", "yes"}
- 工具定义
- 通过字典形式定义工具,包括工具名称、工具描述、输入参数、执行函数,以及是否需要审批的“risky”字段,代码中涉及到写文件的操作risky为True,意味着需要经过用户确认才能执行。
- 权限控制
- 主要通过“risky”字段进行控制。当触发审批的时候,会跳出输入框,仅当用户输入y/yes的时候,当前工具才被允许执行。
- 工具执行
- 在工具执行之前,会先进行参数校验(validate_tool),确保当前满足对应工具的执行条件。之后在需要的时候进行权限审批,然后调用对应的函数执行相应操作。
3.4 上下文精简与输出管理
上下文膨胀(context bloat)并不是Coding agnets独有的问题,而是LLM普遍存在的一个问题。当然,如今的LLM支持越来越长的上下文,但长上下文依然昂贵(一般支持更长上下文的模型,价格更高),而且还可能引入额外的噪声(如果里面有大量不相关的信息)。
在多轮聊天中,Coding agnets比普通LLM更容易遭遇上下文膨胀,因为存在反复的文件读取、冗长的工具输出、日志等。
如果运行时把所有这些内容都完整保留下来,很快就会耗尽可用的上下文token。所以,一个好的harness在处理上下文膨胀方面通常需要相当精细,远不止像普通聊天机器人那样只是简单地截断或总结信息。如下图所示,有几种可以采用的压缩手段:
- 截断:搜索结果、shell日志往往过长,可以直接进行截断;
- 去重:重新读取的文件内容、完整的会话记录,可以进行去重;
- 考虑时间的压缩:时间较久的历史信息可以适当压缩,近期内的信息则可以完整保留。

图3.7 大段输出被裁剪,较早的读取被去重,会话记录在重新放回提示词之前被压缩
这里实现的Mini Coding Agent使用了至少两种压缩策略来管理这个问题。
第一种是截断(clipping),它会缩短过长的文档片段、庞大的工具输出、记忆笔记和会话记录条目。换句话说,它防止任何一段文本仅仅因为内容冗长,就把整个提示词预算(上下文窗口)给占满。
第二种策略是记录缩减或总结信息(transcript reduction or summarization),它把完整的会话历史变成一份更精简的、可放入提示词的摘要。这里的一个关键技巧是:把近期事件保留得更丰富,因为它们更有可能与当前步骤相关;而对较早的事件压缩得更激进,因为它们很可能相关性较低。
此外,我们还会对较早的文件读取做去重,这样模型就不会仅仅因为某个文件在会话早期被读过多次,而一遍又一遍地看到相同的文件内容。
总的来说,我认为这是优秀编码智能体设计中被低估的、枯燥无味的一部分。很多看起来像是“模型质量”的东西,其实是上下文质量。
代码实现
# 单个 tool result 进入 transcript 前的最大字符数,防止一次 shell/read 输出# 把后续 prompt 全部挤掉。MAX_TOOL_OUTPUT = 4000# history_text() 最终输出给模型的 transcript 上限;这是全局 prompt budget 的# 最后一道保护。MAX_HISTORY = 12000######################################################### 4) Context Reduction And Output Management ##########################################################def history_text(self): # history 是完整 transcript 的 prompt 版本;这里负责压缩,而不是改变磁盘 session。 history = self.session["history"] ifnot history: return"- empty" lines = [] seen_reads = set() # 最近 6 条通常最影响下一步决策,因此给更高的 clip limit。 recent_start = max(0, len(history) - 6) for index, item in enumerate(history): recent = index >= recent_start if item["role"] == "tool"and item["name"] in ("write_file", "patch_file"): path = str(item["args"].get("path", "")) # 文件被写入/patch 后,旧 read_file 结果可能已经过期;从 seen_reads 中 # 移除,让后续历史里同路径的读取可以重新出现。 seen_reads.discard(path) if item["role"] == "tool"and item["name"] == "read_file"andnot recent: path = str(item["args"].get("path", "")) if path in seen_reads: # 对较旧的重复 read_file,只保留第一次出现,减少 prompt 中重复文件内容。 continue seen_reads.add(path) if item["role"] == "tool": # Tool event 保留参数和裁剪后的结果,方便模型基于真实观察继续行动。 limit = 900if recent else180 lines.append(f"[tool:{item['name']}] {json.dumps(item['args'], sort_keys=True)}") lines.append(clip(item["content"], limit)) else: # user/assistant 文本同样按新旧区分裁剪;旧对话只保留摘要信号。 limit = 900if recent else220 lines.append(f"[{item['role']}] {clip(item['content'], limit)}") # 即使每条都裁剪过,最终合并后仍可能过长,所以再做一次总量裁剪。 return clip("\n".join(lines), MAX_HISTORY)
在代码实现中,主要采用了截断处理:
- 近期(6次以内)的历史记录保留的文本更多,其它的内容则更激进进行压缩;
- 对工具调用结果和会话内容采用不同的压缩阈值,会保留比较完整的会话内容。
至于上文提到了对内容进行总结,其实完全可以通过LLM实现,但在代码中未体现。
3.5 会话记录、记忆与恢复能力
实际上,这里涵盖的所有6个核心概念在实际应用中高度交织,不同章节和图示从不同侧重点或缩放层级进行了阐述。
在上一节中,我们探讨了提示阶段的历史记录利用方式,以及如何构建紧凑的对话记录。其核心问题在于:在下一次交互时,应当将多少过往信息重新输入模型?因此,重点在于压缩、截断、去重与时效性。
现在,这一节讲的是结构化的会话记忆,也就是对话历史在存储时的组织结构。这里的关键问题是:agent随着时间的推移会保留哪些内容作为永久记录?所以重点在于,runtime环境会保存一份更完整的对话记录作为持久状态,同时还有一个更轻量的记忆层——它更小,会被修改和压缩,而不只是不断追加。
总结来说,一个编码智能体会把状态(至少)分成两层:
- 工作记忆(working memory):agent显式保留的那份小且经过提炼的状态;
- 完整会话记录(full transcript):涵盖所有用户请求、工具输出和LLM回复。

图3.8 新事件会被追加到完整会话记录中,并在工作记忆中进行总结。磁盘上的会话文件通常以JSON文件的形式存储。
上图展示了两个主要的会话文件——完整会话记录和工作记忆,它们通常以JSON文件的形式存储在磁盘上。如前所述,完整记录保存了全部历史信息,即使关闭agnet也能恢复。工作记忆更像是一个提炼版本,包含当前最重要的信息,这与精简记录有一些相关性。
但精简记录和工作记忆的职责略有不同。精简记录用于提示词重建,其任务是为LLM提供近期历史的压缩视图,使其无需每轮都查看完整记录即可继续对话。工作记忆则更侧重于任务连续性,其职责是维护一个小型、明确管理的摘要,记录跨轮次的重要信息,例如当前任务、关键文件以及近期笔记。
按照上图步骤4,最新的用户请求,连同LLM的响应和工具输出,将在下一轮中被记录为“新事件”,同时存入完整记录和工作记忆。图中没有画出。
代码实现
################################################### 5) Session Memory (Continued) ##########################################################def record(self, item): # 所有 user/tool/assistant event 都追加进 durable history,并立即落盘。 self.session["history"].append(item) self.session_path = self.session_store.save(self.session)def note_tool(self, name, args, result): # note_tool 把完整工具结果压缩成 working memory note,供后续 prompt 快速回忆。 memory = self.session["memory"] path = args.get("path") if name in {"read_file", "write_file", "patch_file"} and path: # 最近触碰的文件单独维护,帮助模型知道当前任务涉及哪些路径。 self.remember(memory["files"], str(path), 8) note = f"{name}: {clip(str(result).replace(chr(10), ' '), 220)}" self.remember(memory["notes"], note, 5)
结合代码,对上述提到的三个概念进行区分:
- 完整会话记录:保留完整的会话信息,用于会话恢复;
- 精简记录:对完整会话记录进行适当的压缩总结,用于提示词构建;
- 工作记忆:对会话中的重要信息进行记录,例如代码中保留的读取过的文件名、用户强调的约束、当前的任务目标等。事实上,记忆还分为长期记录和短期记忆,这里更多的是指短期记忆,长期记忆则更偏用户的偏好习惯相关的信息。
3.6 任务委派与子智能体
一旦智能体拥有了工具和状态,接下来一个有用的能力就是委派(delegation)。原因在于,它让我们能够通过子智能体(subagent)把某些工作并行地拆分成子任务,从而加速主任务。例如,主智能体可能正处在某个任务的中途,却仍然需要一个旁支答案,比如某个符号是在哪个文件里定义的、某个配置写了什么,或者某个测试为什么会失败。把这件事拆分成一个有边界的子任务,比强迫一个循环同时承载所有工作线索要有用得多。
一个子智能体,只有在它继承了足够的上下文以完成实际工作时,才是有用的。但如果我们不加约束,就会出现多个智能体重复劳动、修改相同文件,甚至派生出更多子智能体等问题。
所以,棘手的设计问题不仅仅是如何派生一个子智能体,还包括如何约束它。这里的诀窍是:子智能体继承足够的上下文以保证有用,但同时也受到约束(例如只读、限制递归深度)。

图3.8 子智能体继承了足够的上下文以保证有用,但它运行在比主智能体更紧的边界之内。
Claude Code很早就支持子智能体,而Codex是较晚才加入这一能力的。Codex通常不会强制子智能体进入只读模式。相反,它们通常会继承主智能体的大部分沙箱和审批设置。因此,其边界更多体现在任务范围、上下文和深度方面。
代码实现
####################################################### 6) Delegation And Bounded Subagents #############################################################def tool_delegate(self, args): # delegate 创建一个只读 child MiniAgent,让它调查子任务并返回摘要。 if self.depth >= self.max_depth: raise ValueError("delegate depth exceeded") task = str(args.get("task", "")).strip() ifnot task: raise ValueError("task must not be empty") child = MiniAgent( model_client=self.model_client, workspace=self.workspace, session_store=self.session_store, approval_policy="never", max_steps=int(args.get("max_steps", 3)), max_new_tokens=self.max_new_tokens, depth=self.depth + 1, max_depth=self.max_depth, # 子 agent 即使遇到诱导,也不能执行 write_file/patch_file/run_shell。 read_only=True, ) # 给子 agent 一点父上下文,但只给压缩后的 history,避免递归 prompt 爆炸。 child.session["memory"]["task"] = task child.session["memory"]["notes"] = [clip(self.history_text(), 300)] return"delegate_result:\n" + child.ask(task)
从代码中的实现可以看到,对子agent进行了如下限制:
- 限制为只读模式;
- 不继承完整的上下文,仅给到完成子任务必要的上下文(这里仅给到了工作记忆)。
四、总结和后续计划
总结
通过对Mini Coding Agent的完整学习,我们可以认识harness的实现中集成了Agent发展早期的提示词工程、上下文工程的思想和实现:
- Prefix Prompt中的人为定义的Agent规则(对应系统提示词)、工具描述以及对应的调用示例(对应Few-shot);
- 上下文工程,则贯穿了整个harness,针对不同的会话信息采用不同的压缩技术,形成包含不同信息的上下文,包括完整会话记录、精简记录、工作记忆等。
在此之上,对harness来讲参数校验与权限控制对稳定、安全地运行任务也不可或缺。
同时,在整个学习的过程了,我们也发现了Mini Coding Agent有意简化了实现,在大大提高易学性时,也忽略了较多的细节。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】

122



被折叠的 条评论
为什么被折叠?



