如果你被各种 AI Agent 概念折腾得够呛——什么 Multi-Agent、Plan-and-Execute、记忆机制、工具调用、ReAct 循环——建议你先别急着去啃那些架构图和论文。我教新手最快的路径是:先亲手写一个 100 行的最小 Agent,让它能跑、能干活、能出 bug,然后所有概念一下子就都有了落脚点。
这篇就干这个事:用 Python 写一个脚本,复刻一把标题里说的 OpenClaw 这类开源通用 Agent 的核心链路。这类 Agent 的共同点是:给大模型接上终端、文件读写等工具,让它能自己规划、执行、纠错,直到完成你布置的任务。我们不去复制它的工程外壳,只复刻最核心的“思考-行动-观察”循环。跑通之后,你会发现那些看起来很唬人的框架,本质就是今天这 100 行代码加上各种外围功能。
这篇适合谁?完全没写过 Agent 的新手,看完能独立写出第一个能干活的 Agent;写过但总觉得概念飘着的朋友,看完能把零散知识点串成一条线;想快速评估某个 Agent 框架靠不靠谱的人,也能用这篇文章里的最小内核做参照系。
1. Agent 不是 API 调用:先搞清楚它到底在循环什么
1.1 模型只是“大脑”,Agent 是“大脑 + 手脚 + 循环”
很多人第一次写 AI 程序,就是调一次大模型接口,输入 Prompt,拿到回答。那是纯聊天,不是 Agent。Agent 和聊天的本质区别在于三件事:模型能做决策、决策能触发真实动作、动作结果能再次被模型看到。
想象一下你让一个实习生去整理办公室。纯聊天模型像是你在电话里问他“东西怎么摆”,他只能给你建议,但没法动手。Agent 则是你给他配了一双手,他看了一眼柜子(观察),决定把文件放进抽屉(行动),拉开抽屉发现里面有旧文件(新的观察),于是先清理再放新的(下一步行动)。这个“看一眼→动一下→再看一眼”的闭环,就是 Agent 的全部秘密。
OpenClaw 这类项目之所以让人觉得“哇塞”,不是因为模型变聪明了,而是因为它把上面这个闭环做成了流畅的产品:模型可以调用终端命令、读写文件、抓取网页;每次调用的结果会被塞回对话历史,让模型继续推理。你说“帮我整理一下这个目录”,它就先列出目录内容,自己规划先删临时文件再看大文件,一步步完成。
1.2 最小复刻的三个组成部件
我们要复刻的最小内核,只有三个部件:
- 一个支持工具调用的大模型接口:它负责理解任务、决定调用哪个工具、分析工具返回的结果。不用关心具体是哪家的,只要是那种“会返回结构化工具调用指令”的模型就行。
- 一组真实可执行的工具:比如执行 shell 命令、读写文件。这些是 Agent 的“手脚”,没有它们模型只能纸上谈兵。
- 一个循环控制中枢:把模型的决策翻译成真实调用,把执行结果转回模型能读的消息,并控制最大步数,防止它跑飞。
我给新人讲这个结构时,常打一个比方:模型是快递员的大脑,工具是快递员的腿和手,循环中枢是调度系统。大脑说“去 3 号柜取件”,调度系统就派腿过去,取完回来告诉大脑“3 号柜是空的”,大脑再决定“那去 4 号柜看看”。没有调度系统,大脑和腿是断开的,什么也干不成。
1.3 为什么要“复刻”,直接看 OpenClaw 源码不行吗
这是一个很实在的问题。OpenClaw 这种项目的源码动不动几千上万行,里面有大量工程化细节:多模型适配、流式输出、并发控制、配置解析、插件机制、前端界面、打包部署。这些东西对一个想理解 Agent 原理的新手来说,不是财富,是噪音。你会被一堆类名和配置文件淹没,完全看不见主干。
复刻的好处是,我们只保留主干,所有工程细节都丢开。一旦你亲手写出这个最小循环,再回去看任何 Agent 框架的源码,你会自动在脑子里做减法:哦,这部分只是在外面套壳,那段代码干的事和我那 100 行里第 67 行是一样的。这也是为什么我一直推荐用“最小可运行系统”来入门,而不是从研究大型项目起步。
2. 100 行代码的骨架:把思考-行动-观察写成一个不会卡死的循环
2.1 先看整体流程,再写代码
我写最小 Agent 时,通常直接把这个流程画在脑子里,不画那些花哨的架构图:
- 把一个系统提示词和用户任务放入消息列表。
- 把整个消息列表发给模型,同时附上工具描述。
- 模型返回两种结果之一:要么是一段普通文本(意思是我干完了),要么是一个工具调用指令(意思是我要用某个工具,参数是这些)。
- 如果是工具调用指令,我们就在真实环境里执行它,把结果包装成一条“工具消息”追加到消息列表,然后回到第 2 步。
- 如果模型直接给了最终答案,循环结束,把它返回给用户。
这个流程有一个学名叫 ReAct,即 Reasoning + Acting 交替进行的模式。别被高深的名字吓到,它本质上就是:模型说一句“我要查一下当前目录”(推理),我们执行“ls”(行动),把输出给它看(观察),它再说下一步怎么做(再推理)。一句话总结:让模型像一个会用电脑的真人那样,边想边做。
2.2 核心循环代码逐段拆解
下面这段代码是整个复刻版 Agent 的心脏。我省略了具体的模型请求实现,因为各家接口细节不同,但消息结构和调用逻辑是通用的。你在自己的机器上跑的时候,把你用的模型服务和密钥代入call_llm即可。
import json SYSTEM_PROMPT = """你是一个运行在电脑上的通用型AI Agent。 你可以调用工具来完成用户任务,可选工具包括: - execute_shell(command): 在命令行执行一条命令,返回标准输出和标准错误 - read_file(path): 读取指定文本文件的内容 - write_file(path, content): 把文本内容写入指定文件 规则: 1. 先用工具收集必要的真实信息,不要凭空编造结果。 2. 如果工具执行出错,分析错误原因并换一种方式重试。 3. 任务完成时,用 final_answer 总结你做了什么、结果是什么。 """ def call_llm(messages, tools): # 这里对接你使用的模型服务,要求支持 function calling 机制。 # 把 messages 和 tools 传给模型,返回解析后的 assistant 消息字典, # 结构至少包含 role / content,可能包含 tool_calls。 # # tool_calls 的通用格式: # [ # { # "id": "call_xxx", # "function": { # "name": "execute_shell", # "arguments": "{\"command\": \"ls\"}" # } # } # ] raise NotImplementedError("请接入你的模型服务") def run_agent(task: str, max_steps: int = 10): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task}, ] for step in range(max_steps): print(f"[debug] step {step + 1}/{max_steps}") # 让模型基于当前完整对话历史做一次决策 assistant_msg = call_llm(messages, tools=TOOL_SCHEMAS) messages.append(assistant_msg) # 情况一:模型没有要求调用工具,说明它认为任务完成了 if not assistant_msg.get("tool_calls"): final_answer = assistant_msg.get("content") or "" print("[debug] 收到最终回复") return final_answer # 情况二:模型要求调用一个或多个工具 for tool_call in assistant_msg["tool_calls"]: fn_name = tool_call["function"]["name"] fn_args = json.loads(tool_call["function"]["arguments"]) print(f"[debug] 调用工具 {fn_name},参数:{fn_args}") # 在真实环境里执行工具 tool_result = execute_tool(fn_name, fn_args) # 把工具的执行结果作为一条 tool 消息回传给模型 messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "content": tool_result, }) # 超出最大步数,强制结束 print("[debug] 达到最大步数,任务被强制终止") return messages[-1].get("content") or "(没有拿到最终结果)"2.3 为什么非要把“步数”卡死
我见过不少第一次写 Agent 的朋友,很喜欢把循环写成while True,理由很单纯:“AI 需要一直尝试到成功为止啊。”这个想法很危险。
真实世界里,模型可能陷入死循环:反复调用同一个命令、在同一个错误上打转、或者面对一个不可能完成的任务时疯狂重试。不限制步数,你的终端就会像一个失控的实习生,一晚上反复“整理”同一个文件柜。max_steps这个参数看起来不起眼,实际上是最重要的安全阀。
另外,步数限制还能控制成本。每一次循环都是一次模型请求,都会产生真实消耗。设置 10 步、15 步,等于给预算上了个天花板。出问题时先结束,再人工看 debug 日志,比让程序无限烧钱理智得多。
2.4 为什么工具结果要“塞回对话”,而不是存到变量里
新手经常问我:工具结果拿到了,为什么不输出到一个变量,下次调用时直接传个参数?原因在于模型是“无状态”的,它每次决策都只能基于你给它的上下文。只有把工具结果作为一个新消息放回对话历史,模型下一次调用时才能“看到”这个结果,才知道自己上一步做了什么、接下来该怎么调整。
所以 Agent 的对话历史,本质上就是它的“工作记忆”。你把观察结果写进去,模型就知道了;你不写,它就两眼一抹黑。很多 Agent 表现“很蠢”,调用了工具之后好像忘了结果,十有八九是消息列表没有正确维护,工具返回没有回写。这是整个 Agent 编码中最容易出错、也最关键的一环。
3. 工具调用这层“三次握手”:从会聊天到会干活
3.1 工具描述:你是在教模型用你的 API
要让模型知道能调用什么工具,你得先把工具“介绍”给它。这不是随便在 Prompt 里写一句“你可以执行命令”就行,而是要遵循一套结构化的描述格式,告诉模型:工具叫什么、是干什么的、参数有哪些。模型会在这套描述的基础上,输出一个符合格式的调用请求。
我把这个过程叫作“三次握手”:第一次,我们把工具说明书交给模型;第二次,模型说“我要调用 execute_shell,参数是 command='ls'”;第三次,真实环境执行后,我们把执行结果作为回应交给模型,完成这次调用。任何一次握手断了,Agent 都会开始胡说八道。
一个标准的工具描述大概长这样:
TOOL_SCHEMAS = [ { "type": "function", "function": { "name": "execute_shell", "description": "在电脑上执行一条 shell 命令,并返回标准输出和标准错误。适合查看目录、运行脚本、启动程序、检查网络状态等操作。", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "要执行的完整命令,例如 ls -la" } }, "required": ["command"] } } }, { "type": "function", "function": { "name": "read_file", "description": "读取指定路径的文本文件内容,返回文件前 2000 个字符。适合查看代码和配置文件。", "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "文件绝对路径或相对路径" } }, "required": ["path"] } } }, { "type": "function", "function": { "name": "write_file", "description": "把指定的文本内容写入文件,如果文件所在目录不存在会自动创建。", "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "目标文件路径" }, "content": { "type": "string", "description": "要写入的文本内容" } }, "required": ["path", "content"] } } } ]有一点要注意:工具名称必须和代码里实现的函数名严格对应,参数名也不能改,比如 schema 里写的是command,实现里就不能用cmd。模型很死板,它只会按你给的描述输出参数名,参数对不上,程序解析时会直接报错。
3.2 三个最常用的工具实现:让 Agent 拥有读写和执行能力
光有描述还不够,我们得让 Agent 真的能动手。我选三个最通用的工具作为复刻版的标准配置,它们覆盖了绝大多数简单任务:
execute_shell:执行终端命令,这是 Agent 的“万能手”。read_file:读取文件内容,这是 Agent 的“眼睛”。write_file:写入文件内容,这是 Agent 的“手”。
import os import subprocess def execute_shell(command: str, timeout: int = 15) -> str: """执行 shell 命令,返回输出。timeout 防止命令卡死。""" try: proc = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=timeout, ) output = (proc.stdout + proc.stderr).strip() if not output: return "(命令执行成功,但没有任何输出)" # 截断过长输出,防止撑爆上下文 if len(output) > 2000: return output[-2000:] + "\n...(输出过长已截断)" return output except subprocess.TimeoutExpired: return f"(命令执行超时,超过 {timeout} 秒)" except Exception as e: return f"(命令执行出错: {e})" def read_file(path: str) -> str: """读取文本文件,限制返回长度。""" try: with open(path, "r", encoding="utf-8", errors="ignore") as f: content = f.read().strip() if len(content) > 2000: return content[-2000:] + "\n...(文件过长已截断)" return content or "(文件为空)" except Exception as e: return f"(读取文件失败: {e})" def write_file(path: str, content: str) -> str: """写入文本文件,自动创建目录。""" try: abs_path = os.path.abspath(path) os.makedirs(os.path.dirname(abs_path), exist_ok=True) with open(abs_path, "w", encoding="utf-8") as f: f.write(content) return f"成功写入 {len(content)} 字符到 {abs_path}" except Exception as e: return f"(写入文件失败: {e})" def execute_tool(name: str, args: dict) -> str: """工具分发器:根据工具名决定调用哪个函数。""" if name == "execute_shell": return execute_shell(args.get("command", "")) if name == "read_file": return read_file(args.get("path", "")) if name == "write_file": return write_file(args.get("path", ""), args.get("content", "")) return f"(未知工具: {name})"这里每个工具函数都返回字符串,是整个设计里最容易被忽略但很关键的点。为什么统一字符串?因为工具结果是往消息列表里塞的,消息内容必须是文本。无论命令输出、文件内容还是错误信息,全部转成字符串,模型才能继续处理。你要是直接返回一个subprocess.CompletedProcess对象,消息序列化就得炸。
另外,给execute_shell加timeout参数是我的习惯。真实环境里,一条ls可能很快,但一条curl可能卡几分钟。加上 15 秒的超时限制,至少能保证 Agent 不会因为一条命令被永久卡死,这是最小系统里最划算的保险。
3.3 为什么工具结果也必须“截断”
工具结果太长,是整个 Agent 工程化里最隐蔽的性能杀手。模型一次能处理的上下文是有限的,如果cat一个 10 万行的日志文件,直接把全部内容塞回对话,轻则模型开始忽略关键信息,重则直接超过上下文长度报错。
所以我在每个工具函数里都做了同样一件事:只保留最近 2000 个字符,并明确标注“已截断”。别小看这个动作,它是在告诉模型:完整的信息我给不了,但你需要的答案大概率在这段里,如果不够,你可以继续用指令去提取,比如用grep关键词。截断不是让模型“凑合”,而是引导它学会用更精准的查询代替大而全地读取。
当然,2000 不是硬性标准。如果任务比较简单,500 也够用;如果模型要对长文档做摘要,可能需要 5000 甚至更多。关键是你要有这个意识:不是所有内容都值得进上下文,Agent 的上下文是昂贵的“办公桌”,桌面上只能放当前最重要的材料。
4. 完整跑通:让复刻版 Agent 自己统计代码行数
4.1 一个真实的思考-行动-观察轨迹
纸上谈兵没意思,我们直接跑一个任务:“统计当前目录下所有 Python 文件的总行数,并把结果写入 result.txt。”这个任务对新手非常合适,它需要多个工具配合,也能展现 Agent 逐步推理的过程。
启动run_agent("统计当前目录下所有 python 文件的总行数,并写入 result.txt")后,一份典型的执行轨迹是这样的:
[debug] step 1/10 [debug] 调用工具 execute_shell,参数:{'command': 'ls'} (工具结果): agent.py result.txt tools_demo.py [debug] step 2/10 [debug] 调用工具 execute_shell,参数:{'command': "find . -name '*.py' | xargs wc -l"} (工具结果): 12 agent.py 89 tools_demo.py 101 总 [debug] step 3/10 [debug] 调用工具 write_file,参数:{'path': 'result.txt', 'content': 'Python 文件总行数: 101'} (工具结果): 成功写入 19 字符到 /home/user/project/result.txt [debug] step 4/10 [debug] 收到最终回复最终模型给出的回复大概是:
我已经统计了当前目录下所有 Python 文件的总行数,结果为 101 行,并写入到了 result.txt 中。其中 agent.py 为 12 行,tools_demo.py 为 89 行。整个过程看起来很自然,像一个人在干活。但在程序底层,每一次工具调用背后都隐藏着我前面说的“三次握手”:模型生成一个结构化的调用请求,程序解析并执行,结果作为一个新消息回写。你看到的那 4 步,其实就是循环中转了好几个来回。
4.2 每次“观察”如何影响下一步决策
注意看第二步,模型直接用了find和xargs wc -l的组合命令,而不是老老实实地一个文件一个文件地去读。为什么它能做出这么“聪明”的选择?因为在第一步,模型通过ls观察到了当前目录的文件名,它判断出“文件数量不多,可以用一行命令完成统计”。如果第一步的观察结果是“目录非常大”,模型可能会改用find过滤后逐个读取。
这说明观察结果不是简单的信息堆积,而是下一步决策的依据。这也是为什么我强调工具结果必须回写消息列表:模型每一次看到的新信息,都会直接改变它的下一步行动。如果你的程序把工具结果存在变量里却没有回写,你看到的 Agent 就会像一个失忆的人,反复问同一个问题。
第三步模型选择write_file而不是直接用 shell 的重定向echo > result.txt,这个选择挺有意思。对于模型来说,工具列表里有write_file这个专为写入而生的函数,使用它是更符合直觉的选择。这不是“最优解”,但它是合理且可用的方案。Agent 的“智能”不在于每一步都最优,而在于它能基于当前观察做出可执行的合理决策,并在出错时自我纠正。
4.3 “100 行”是怎么算出来的
你可能会好奇,代码真的是 100 行吗?我拆一下:
| 模块 | 作用 | 估计行数 |
|---|---|---|
| SYSTEM_PROMPT + run_agent 循环 | Agent 核心驱动 | 约 45 行 |
| 工具描述 TOOL_SCHEMAS | 模型与工具的接口说明 | 约 30 行 |
| execute_shell / read_file / write_file / execute_tool | 真实工具实现 | 约 35 行 |
| call_llm 占位函数 | 模型请求接入点 | 约 5 行 |
加起来确实在 100 行左右。当然,“100 行”不是一个严格的极限,而是一种信号:一个能干活的最小 Agent,真的不需要那么多代码。如果你看到某个框架动不动几万行,那是因为它要在生产环境里解决海量并发、多租户、权限隔离、企业集成这些问题。那些问题都很重要,但不是 Agent 的本体。
5. 稳定复现清单:环境、模型和容易翻车的细节
5.1 环境准备与模型接入
写完了代码,怎么把它跑起来?我列一个亲测有效的最小准备流程:
# 1. 创建虚拟环境,Python 3.10 以上比较稳 python3 -m venv agent-demo source agent-demo/bin/activate # 2. 安装模型服务的基础 SDK pip install <your-llm-sdk> # 3. 配置密钥,注意不要写进代码里 export LLM_API_KEY="你的密钥"然后你需要把第 2 节代码里的call_llm占位函数替换成真实调用。需要注意的接口字段,几乎都是通用的:
messages:直接传我们维护的那个消息列表。tools:传TOOL_SCHEMAS。response_format:如果服务支持,可以设成自动选择工具。- 返回结果:通常取
choices[0].message,再把它转成字典格式。
我第一次跑通时最深的体会是:模型服务的“工具调用”接口各家叫法不一样,有的叫 function calling,有的叫 tool use,但消息结构大同小异。关键是先拿一个极小的例子确认格式,再接入主循环,不要一上来就调试完整版,那样分不清问题出在模型还是出在代码。
5.2 小白最容易踩的五个坑
我把带新人时反复出现的翻车点整理成一个表,你写的时候对照着查:
| 现象 | 根本原因 | 解决办法 |
|---|---|---|
| 模型从来不调用工具,只会说“我建议你先执行 ls” | 模型本身不具备工具调用能力,或服务没开启该功能 | 换成支持 function calling 的模型;确认请求里传了 tools |
| 程序报 JSON 解析错误 | 模型生成的 arguments 不是合法 JSON | 打印原始内容排查;加上容错解析,比如清掉多余缩进;必要时换更强的模型 |
| 工具结果太长,模型开始胡说八道 | 上下文被无关信息填满,关键信息被淹没 | 工具输出统一截断到 2000 字符内,并加“已截断”提示 |
| Agent 反复调用同一个命令原地打转 | 模型看不到自己的历史调用记录,或看不到错误原因 | 检查 tool 消息是否真实回写;把错误信息写得清楚具体 |
| 命令行卡住不返回 | 某个命令执行时间过长 | 给 execute_shell 加上 timeout 参数,默认 15 秒 |
这里面最值得展开说的是 JSON 解析。模型偶尔会把参数写成带注释的 JSON,或者多一个逗号,直接json.loads会崩。一个简单且稳妥的兜底方案是,在解析失败时把原始字符串清理一下,比如去除首尾的花括号外噪音,再尝试json.loads。更省事的做法,是选用返回质量更稳定的新模型。记住:工具调用试错成本最低的方式,是把原始的tool_calls字段打印出来看,而不是靠猜。
5.3 安全边界:能执行 shell 的 Agent 绝不能裸奔
这节必须重点强调。一个能调用execute_shell的 Agent,等于把一台电脑的“遥控器”交到了模型手里。模型幻觉导致的错误命令,可能会删文件、装软件、改配置。本地演示还好,一旦部署到服务器或接入真实业务系统,风险是实打实的。
我的建议,按安全层级从低到高:
- 本地演示:务必在虚拟环境或容器里跑,不要用 root 用户,目录也限制在一个专用文件夹内。
- 工具白名单:只允许少数几条白名单命令,比如
ls、cat、grep、find,禁止rm、mkfs、curl | bash这类高风险操作。 - 人工审批:在工具分发器里加一步确认逻辑,高危命令执行前需要人工输入
y确认。 - 审计日志:把每一次工具调用记录下来,包括时间、参数、结果,出问题能回溯。
很多开源 Agent 项目在工程上做了大量安全设计,就是为了解决这类风险。复刻版虽然简单,但安全意识不能跟着一起“简化”。你可以先想清楚:如果你的 Agent 突然执行了一条可怕的命令,会发生什么?想清楚这一点,再决定要不要加防线。
6. OpenClaw 在内核之外还做了什么:从最小可用到好用可信
6.1 工程外壳:让 Agent 从“能跑”到“好用”
复刻完这个最小内核,你其实已经理解了 OpenClaw 这类项目的一半。剩下那一半,就是在这个循环外面不断加工程外壳。
第一层是模型接入的抽象化。最小版代码里call_llm是写死的,生产级项目会做成配置驱动,允许你换不同的模型服务商,甚至加一层容错:主力模型超时了自动切换备用模型。这一层不用改循环逻辑,纯粹是增强健壮性。
第二层是流的体验优化。当前实现里,模型思考期间用户只能干等。生产项目会做流式输出,让用户看到“正在调用工具”“命令执行中”“已获取结果”这些中间状态。我自己的习惯是先跑通同步版本再改流式,否则并发和消息队列的问题会把你淹没。
第三层是记忆的持久化。最小版的记忆只存在于消息列表里,程序一重启就全丢了。给 Agent 加一个长期记忆模块,让它能把之前的任务结论、常用配置、用户偏好存下来,下次启动时加载。这一步看似简单,但对 Agent 的“智能感”提升非常明显。
6.2 从“能用”到“可信”:人机协同与审计
我接触过的 Agent 项目里,真正走向生产环境的,几乎没有一个是完全自动、完全无人值守的。比较务实的路线是“人在回路”模式:Agent 自主执行低风险步骤,遇到高风险操作或不确定性大的分支时,暂停并请求用户确认。这比追求全自动靠谱得多。
审计也是生产环境的硬需求。每次 Agent 调用了什么工具、喂了什么参数、产出了什么结果、耗时多少、花费多少,都该有结构化日志。出了问题能查,花费超标能发现,模型行为异常能及时封禁。最小内核里加一个简单的日志打印很容易,生产级项目则会专门做一套事件流系统。
还有一种常见增强是“规划器”。最小版 Agent 是走一步看一步,面对复杂任务容易迷失。给它加一个规划模块,让模型先把任务拆成子目标,再逐个执行,这就是“Plan-and-Execute”模式的核心思路。很多人觉得这是高深技术,但本质上就是让模型在开工前写一个 to-do list,然后根据进展勾选和调整。
6.3 下篇预告:这个内核还能往哪长
写到这里,你会发现 Agent 的想象力其实是无限的,但一切都建立在今天这个循环上。你学会了控制循环,就等于拿到了所有 Agent 框架的“操作系统的内核源码”。
我在实际带新人的过程中体会最深的一件事是:动手复刻一遍,比读十篇架构分析都管用。这 100 行代码不是终点,只是一扇门。打开这扇门后,你会慢慢理解工具应该怎么设计、上下文应该怎么管理、模型应该怎么引导。
如果这篇你跑通了,下篇我们继续往里加东西:先给 Agent 装上“长期记忆”,再装一个“规划器”,让它能接住那种多步骤、跨多个文件的复杂任务,而不是像现在这样只适合一步到两步的简单活儿。到时候你会发现,从“最小可用”到“真正能干复杂的活”,差的不是更多的代码,而是你对这个循环的理解深度和扩展技巧。