Harness这个词我们叫“控制框架”或者“执行支架”,指的是包裹在模型外面那层普通代码。这层代码负责规划、执行工具、管理记忆和保证安全,模型就只管决定下一步干嘛。在人工智能编程、大模型开发、AI代理工程这些领域,搞明白这个结构,比换个更贵的模型管用多了。
Claude Code能跑得稳,靠的不是模型本身多厉害,是外面那层执行支架在兜底。这趟拆解的是Claude Code的:它背后那套让模型不乱跑、不乱改、不乱忘的控制系统,比模型本身更关键。
我们把它的核心循环、计划、分拆任务、沙盒、记忆和检查点,在一个叫CrewAI的开源框架里重建了一遍,最后对着一个有bug的银行账户类跑通了全部测试。
整套架构就靠这几层配置堆出来的。
拆解Harness控制框架的四大块
Harness架构可以分成四大块:
记忆系统负责喂给模型工作上下文,还有跨会话学到的知识点。
技能部分编码了代理该怎么操作,比如流程、限制条件这些。
协议层连接用户、工具和其他代理。
核心部分用子代理调度、沙盒环境、评估器、审批循环、可观测性和上下文压缩把这些串起来。
这就像造一个自动车间,模型就是那个出主意的车间主任,而控制框架就是传送带、机械臂和安全护栏。
Anthropic把这个拆分叫做大脑和双手。模型是大脑,负责选下一步动作,Harness控制框架是双手,负责执行动作并且让整个流程不跑偏。所以说,你的代理和Claude Code之间的差距不是模型,是模型外面那圈机械装置。
Claude Code是现在生产环境里最能打的控制框架之一,它用的就是那张图里一小部分层。为了搞清楚有多少东西你得自己动手,我用CrewAI这个开源编排框架重新搭了一遍。比我预想中更多的部分能直接映射到内置功能上,而映射不上的那部分,才是真正要下功夫的工程活。
核心循环就是那个死循环
任何编程助手最底下都是一个循环。你丢一句话进去,模型决定下一步,要么直接回答你,要么申请调用一个工具。要工具就给工具,工具跑完把结果塞回对话里,模型再决定下一轮。一直到它觉得活干完了,不再申请任何工具,循环才停。
每一步工具调用都给模型喂新信息,再驱动它做下一个决定。修个小问题可能一步就完事,重构整个代码库可能要折腾几十次,模型才攒够信息给出最终答案。
这代码短得吓人:
python
while True:
reply = model(messages, tools)
calls = [b for b in reply if b.type == "tool_use"]
if not calls: # 纯文本,没有工具调用:任务完成
return reply.text
messages += [reply, run_all(calls)]
用CrewAI这种开源框架,这个执行循环你根本不用自己写。你定义一个代理,分配一个任务,它自动就跑起来了。
循环里能干的事就是读文件、改代码、跑终端命令、执行测试。这些不是几种不同模式,只是循环里不同名字的工具调用。
光有个循环不够用。真正的代码库复杂得多,模型读着读着就走偏了。所以Claude Code在循环外面裹了好几层:计划、文件工具、子任务拆分、权限控制、沙盒隔离、记忆系统、检查点。这些层不替换循环,只是让它安全、可靠到能正经干活。
整个架构拆开就是四块:
记忆给模型喂上下文和跨会话学到的知识;
技能编码了代理该按什么流程、什么约束去操作;
协议连用户、工具和其他代理;
核心部分用子代理调度、沙盒、评估、审批、可观测性和上下文压缩把前面三块串起来。
Anthropic管这叫大脑和手的分工。模型是大脑,负责选下一步动作;控制系统是手,负责执行动作并且保证整次运行不跑偏。
所以你自己写的代理跟Claude Code之间的差距,不是模型,是模型周围那套机器。
执行循环不用自己写
CrewAI这套框架把那个无限循环内置了。你只要建一个代理,派一个任务,框架自动跑起来。
建个修bug的代理就这几行:
python
from crewai import LLM, Agent, Crew, Task
bug_fixer = Agent(
role="Bug Fixer",
goal="Find and describe the fix for the reported bug in the codebase.",
backstory="You read directories and files to build an accurate picture of the code.",
llm="claude-sonnet-4-6",
)
task = Task(
description="Find the fix for {objective}.",
expected_output="A short description of the fix and which file it belongs in.",
)
result = Crew(agents=[bug_fixer], tasks=[task]).kickoff(
inputs={"objective": "the overdraft bug in account.py"}
)
这里面三个概念:代理定义谁干活,通过角色、目标、大模型和工具来描述;任务描述要干什么活;Crew把代理和任务捏在一起,调用kickoff就启动循环。底层用Anthropic还是OpenAI还是Google的模型,循环本身没区别。
工具让模型长出手脚
模型本来只会生成文字。工具给它装上手脚,才能读文件、写文件、跑终端、调外部接口。
CrewAI直接带了文件系统工具:读文件、列目录、写文件。这些工具还有一个额外功能,充当外部记忆。代理可以把一大段搜索结果存进文件,上下文里只保留文件名,需要的时候再读回来。上下文窗口小了,模型就更专注。
python
from crewai_tools import DirectoryReadTool, FileReadTool, FileWriterTool
read_file = FileReadTool()
write_file = FileWriterTool()
list_dir = DirectoryReadTool()
filesystem_tools = [read_file, write_file, list_dir]
内置工具覆盖常见场景。要干点特殊的,就把一个Python函数用@tool装饰器暴露成工具。函数的文档字符串就是给模型看的说明书,告诉它这个工具做什么、什么时候用、要什么输入。
python
from crewai.tools import tool
import subprocess
@tool("run_tests")
def run_tests(path: str = "tests/") -> str:
"""Run the pytest suite at the given path and return the result."""
result = subprocess.run(
["pytest", path, "-q"], capture_output=True, text=True, timeout=120
)
output = result.stdout + result.stderr
return output[-4000:] if len(output) > 4000 else output
计划防止模型干着干着就忘了目标
任务一复杂,普通循环就开始丢失原始目标。读了十几个文件、跑了好几轮工具之后,上下文被中间结果塞满,最初要干什么已经被挤到角落里了。
这种慢慢退化叫上下文腐烂。
计划层直接解决这个问题。代理动手之前先列一个分步计划,执行过程中这个计划一直留在上下文里。计划不干活,它就是一张地图,让模型始终能看到终点。
CrewAI在Crew层面加一个planning=True就生成计划了。生成计划用默认的gpt-4o-mini,也可以换别的模型。
python
from crewai import Crew, LLM
crew = Crew(
agents=self.agents,
tasks=self.tasks,
planning=True,
planning_llm=LLM(model="gpt-4o-mini"),
)
单个代理还能打开推理模式。推理模式让代理在动手前自己先琢磨一遍:想执行计划、评估准备好了没有、不满意就再调整、直到满意或者达到最大尝试次数,最后把推理完的计划注入任务再执行。
计划和推理解决不同问题。计划给整个任务画高德地图,推理给单个代理在行动前多一步思考时间。
python
from crewai import Agent
bug_fixer = Agent(
role="Bug Fixer",
goal="Find and describe the fix for the reported bug in the codebase.",
backstory="You read directories and files to build an accurate picture of the code.",
tools=[FileReadTool()],
reasoning=True,
max_reasoning_attempts=3
)
两个一起用,代理在长任务上就不容易漂移。
拆活给小弟干
计划解决的是注意力问题,不是容量问题。大代码库里找一只虫子可能要读几十个文件,这些文件没必要全塞在主代理的上下文窗口里。
子代理通过分工来解决。主代理把一块具体任务派给帮手代理,帮手在自己的上下文里干活,最后只给主代理返回一个简短总结。主代理只看到结论,看不到中间的折腾过程。
CrewAI用层级流程实现这个模式。一个管理代理把活拆开分给几个专业代理,再把结果合起来。
之前一个修bug代理干所有活。现在拆成一个管理加三个专家:代码探索代理负责读目录找相关文件,软件工程师代理负责改代码,测试运行代理负责在沙盒里跑测试报告通过还是挂掉,工程主管代理负责盯着三个专家。
python
from crewai import Crew, Agent, Task, Process
explorer = Agent(
role="Codebase Explorer",
goal="Map the repository and surface the files relevant to the task.",
backstory="You read directories and files to build a picture of the code.",
tools=[read_file, list_dir],
llm=llm,
)
manager = Agent(
role="Engineering Lead",
goal="Break the request into steps and delegate each to the right specialist.",
backstory="You decide who does what, review tests, finish once change is done.",
llm=llm,
allow_delegation=True,
)
crew = Crew(
agents=[explorer, coder, tester],
tasks=[task],
manager_agent=manager,
process=Process.hierarchical,
)
注意allow_delegation默认是关的,管理代理上要显式打开。
沙盒保证模型不能把电脑炸了
一个有终端权限的代理可以跑毁灭性命令。跟模型说不要干坏事不是防护措施。
真正的防护来自两层:权限系统要求敏感操作需要批准;沙盒隔离执行环境,就算批准了的命令也碰不到宿主机。
Anthropic就用这套。把代码执行挪进沙盒,用户需要点批准的次数少了,宿主机也安全了。
CrewAI的E2B工具把代码放到一个临时虚拟机里跑,跑完就销毁。Shell命令和Python都在那个隔离环境里执行。
python
from crewai_tools import E2BExecTool, E2BPythonTool
sandbox_tools = [E2BExecTool(), E2BPythonTool()]
人机审批环节设在任务上。一个Task设human_input=True,代理生成答案后整个Crew就停住。你看完输出,批准或者打回去再跑一轮。如果Crew跑在网页应用或聊天界面而不是终端,CrewAI的webhook审批系统干同样的事。
python
from crewai import Task
task = Task(
description=(
"In the working directory ./workspace, {objective}. "
"Explore the code first, make the change, then run the tests and report."
),
expected_output="A summary of the files changed and the final test output.",
human_input=True,
)
记忆和检查点让代理不健忘
默认情况下代理跑完一次就忘光。明天回来修同一个项目的另一个bug,它又从头开始。
两种机制让信息跨会话传递。检查点保存一次运行中途的状态,中断了可以从断点续跑。持久记忆跨对话存储事实,比如记录下偏好:“每次结束前把代码格式化好”。
CrewAI的记忆接口不区分短时记忆、长时记忆、实体记忆、外部记忆这些类型。保存的时候用大模型找出重要细节,整理好,以后能检索到。Crew层设memory=True就给整个团队配了跨会话记忆。每次任务结束,CrewAI从输出里提取有用事实存起来,下次运行时把相关记忆捞出来塞进任务提示里。
python
from crewai import Crew
crew = Crew(
agents=[explorer, coder, tester],
tasks=[task],
memory=True,
)
同一个Crew里的所有代理共享这份记忆,除非某个代理自己单独配了一份。
检查点是代理进度的快照,包括配置、任务状态、记忆、中间结果、输入和执行历史。默认每个任务完成时存一个检查点,中断了可以从那里恢复。检查点可以存成JSON文件,方便人读;也可以存进SQLite数据库,频繁存的时候性能更好。
python
from crewai import Crew
crew = Crew(
agents=[explorer, coder, tester],
tasks=[task],
checkpoint=True,
)
Crew、Flow、Agent都接受checkpoint参数,子对象继承父对象的设置,除非自己重写。
一整个控制系统叠起来长这样
执行循环、工具、计划、子代理、沙盒、记忆、检查点全加在一起,就一个任务的配置:
python
from crewai import Agent, Crew, LLM, Process, Task
from crewai.tools import tool
from crewai_tools import (DirectoryReadTool, FileReadTool, FileWriterTool,
E2BExecTool, E2BPythonTool)
llm = LLM(model="anthropic/claude-sonnet-4.6")
list_dir = DirectoryReadTool(directory="./workspace")
filesystem_tools = [FileReadTool(), FileWriterTool(), list_dir]
sandbox_tools = [E2BExecTool(), E2BPythonTool()]
@tool("run_tests")
def run_tests(path: str = "tests/") -> str:
"""Sync ./workspace into the sandbox, then run pytest there."""
return E2BExecTool().run(command=sync_and_test_command(path))
explorer = Agent(role="Codebase Explorer", goal="Map repo, surface relevant files.",
tools=[FileReadTool(), list_dir], llm=llm)
coder = Agent(role="Software Engineer", goal="Implement requested change.",
tools=filesystem_tools, reasoning=True, llm=llm)
tester = Agent(role="Test Runner", goal="Run tests in sandbox, report pass/fail.",
tools=sandbox_tools + [FileReadTool()] + [run_tests], llm=llm)
manager = Agent(role="Engineering Lead", goal="Delegate steps, finish once tests pass.",
allow_delegation=True, llm=llm)
task = Task(
description="In ./workspace, {objective}. Explore, edit, test, report.",
expected_output="Summary of changes and test output.", human_input=True,
)
crew = Crew(
agents=[explorer, coder, tester], tasks=[task],
manager_agent=manager, process=Process.hierarchical,
planning=True, memory=True, checkpoint=True,
)
result = crew.kickoff(inputs={"objective": "fix failing tests in account.py"})
这套配置在一个银行账户类的小代码库上测过。代码里埋了两个真bug,五个测试里三个挂。规则是只能改实现代码,不能动测试代码。这一点模仿了Anthropic内部评估编程代理的方法。
最后结果从三挂两通过变成五个全通过。实现代码不改测试的规则堵死了删测试或者改测试来蒙混过关的捷径。
框架不替你干的那几样活
有些东西不是框架能替你建的。提示词。每个代理的行为来自它的角色、目标和背景故事。把这些写对需要反复测试和调整,没有一个配置开关能替代这个活。执行环境。沙盒不管是E2B还是自己管的虚拟机,得你自己搭起来接进去。工具选择。哪个代理配哪些工具、哪个代理有权访问什么,这是框架不替你做的设计决策。
控制系统本身也有开销。计划、子代理、循环都在消耗API调用。一个复杂的代理配置最后可能比直接一个模型调用解决任务更贵。
还有一个长期限制值得记住。模型在进步,有些脚手架以后可能就不需要了。现在塞进控制系统里的东西,有一部分是在给今天模型的局限性打补丁,不是永久需求。Anthropic曾经用上下文重置来防止Claude Sonnet 4.5过早结束任务,到了更能干的Claude Opus 4.5,这套就不需要了。
所以结论就这一句。编程代理的能力大部分在Harness控制系统里,而一个编排框架给你的现成控制层比你以为的要多。循环、计划、分拆、沙盒、记忆都是配置,提示词、执行环境、工具选择还是你的活。
真正的工程在框架结束的地方开始。