从零开始理解 Agent(二):OpenClaw / Claude Code 如何实现记忆与规划,只需182 行

举报
AGENT魔方 发表于 2026/04/10 11:24:45 2026/04/10
【摘要】 欢迎阅读 「从零开始理解 Agent」系列 文章—— 我们将通过一个不到 300 行的开源项目 nanoAgent,逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。本文为该系列第二篇,聚焦记忆与规划。

专栏1.png

欢迎阅读 「从零开始理解 Agent」系列 文章—— 我们将通过一个不到 300 行的开源项目 nanoAgent,逐层拆解 OpenClaw / Claude Code 等 AI Agent 背后的全部核心概念。


项目地址:https://github.com/sanbuphy/nanoAgent

作者:十一

上一篇我们用 115 行代码理解了 Agent 的核心公式:LLM + 工具 + 循环。但在结尾我们也指出了它的致命缺陷——没有记忆、不会规划。

如果你用过 OpenClaw 或 Claude Code,你会发现它们可以在一个长对话中持续记住之前的操作,面对复杂需求时也会先制定计划再逐步执行。这些能力不是"魔法",而是可以用很少的代码实现的架构设计。

今天我们看 nanoAgent 的第二个版本 agent-plus.py(182 行),它只多了 67 行代码,却解决了两个根本问题:让 Agent 记住过去让 Agent 规划未来

▍一、从 agent.py 到 agent-plus.py:多了什么

1.png

工具层完全没变——新增的 67 行全部用来构建更高层的能力。这恰好印证了第一篇的结论:工具 + 循环只是地基,记忆和规划才是让 Agent 真正可用的关键。

二、记忆系统:最朴素但最本质的方案

2.1 记忆的存储:一个 Markdown 文件

MEMORY_FILE = "agent_memory.md"

def save_memory(task, result):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entry = f"\n## {timestamp}\n**Task:** {task}\n**Result:** {result}\n"
    try:
        with open(MEMORY_FILE, 'a') as f:
            f.write(entry)
    except:
        pass

agent-plus.py 用了一种极其朴素的方案——把历史任务和结果追加写入一个 Markdown 文件。没有数据库,没有向量索引,就是纯文本。每次任务执行完毕,Agent 把"什么时间、做了什么、得到什么结果"追加到文件末尾:

## 2026-03-12 14:30:00
**Task:** 统计当前目录下的 Python 文件数量
**Result:** 当前目录下共有 42 个 Python 文件。

## 2026-03-12 15:00:00
**Task:** 创建一个 hello.py
**Result:** 已创建 hello.py,内容为打印 Hello World。

2.2 记忆的加载:滑动窗口

def load_memory():
    ifnot os.path.exists(MEMORY_FILE):
        return""
    try:
        with open(MEMORY_FILE, 'r') as f:
            content = f.read()
        lines = content.split('\n')
        return'\n'.join(lines[-50:]) if len(lines) > 50else content
    except:
        return ""

加载记忆时有一个关键细节:只取最后 50 行。这是一个简单的滑动窗口策略——记忆文件可能无限增长,但 LLM 的 context window 是有限的。必须截断。

2.3 记忆的注入:塞进 System Prompt

def run_agent_plus(task, use_plan=False):
    memory = load_memory()
    system_prompt = "You are a helpful assistant that can interact with the system. Be concise."
    if memory:
        system_prompt += f"\n\nPrevious context:\n{memory}"
    messages = [{"role": "system", "content": system_prompt}]

加载出来的记忆被拼接到 system prompt 末尾,以 "Previous context" 的形式注入。LLM 在处理新任务时能"看到"之前的历史。

2.4 记忆流程全景

第 1 次运行                          第 2 次运行
───────────                        ───────────
用户: "创建 hello.py"               用户: "读取 hello.py 并加上注释"
        │                                  │
        ▼                                  ▼
  system prompt:                    system prompt:
  "You are a helpful               "You are a helpful
   assistant..."                    assistant...
                                    
                                    Previous context:
                                    ## 2026-03-12 14:30
                                    Task: 创建 hello.py
                                    Result: 已创建..."
        │                                  │
        ▼                                  ▼
  Agent 执行任务                     Agent 执行任务
        │                           (知道之前创建过 hello.py)
        ▼                                  │
  save_memory() ──写入──▶ agent_memory.md ◀─── save_memory()

2.5 记忆的本质

这个方案虽然原始,但揭示了一个根本原理:LLM 本身没有持久记忆,所有"记忆"都是通过在 prompt 中注入历史信息来实现的。

无论是 Claude 的 Memory、ChatGPT 的记忆功能,还是更复杂的 RAG 系统,底层都遵循这个模式——只是在"存在哪、怎么找、搬多少"上做得更精细。我们会在文末讨论这些进化方向。

三、规划系统:让 Agent 学会"先想再做"

3.1 为什么需要规划?

回忆第一篇中 agent.py 的工作方式:把整个任务丢给 LLM,让它在循环中自行摸索。简单任务没问题,但面对复杂任务(比如"重构整个项目的目录结构"),LLM 容易迷失在细节中,忘记全局目标。

agent-plus.py 引入了一个可选的规划阶段:

def create_plan(task):
    print("[Planning] Breaking down task...")
    response = client.chat.completions.create(
        model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
        messages=[
            {"role": "system", "content": "Break down the task into 3-5 simple, actionable steps. Return as JSON array of strings."},
            {"role": "user", "content": f"Task: {task}"}
        ],
        response_format={"type": "json_object"}
    )
    try:
        plan_data = json.loads(response.choices[0].message.content)
        steps = plan_data.get("steps", [task])
        print(f"[Plan] {len(steps)} steps created")
        for i, step in enumerate(steps, 1):
            print(f"  {i}. {step}")
        return steps
    except:
        return [task]

3.2 规划的设计细节

这段代码有几个值得细品的地方:

用 LLM 来做规划。 规划本身也是一次 LLM 调用,但不带任何工具——纯粹的"思考"。system prompt 要求 LLM 把任务拆解为 3-5 个可执行的步骤,以 JSON 格式返回。

结构化输出。 通过 response_format={"type": "json_object"} 强制 LLM 返回 JSON,避免格式解析问题。

优雅降级。 如果 JSON 解析失败,except 分支回退到 [task]——即不拆解,整个任务当作一步执行。这种防御性编程在 Agent 开发中非常重要。

3.3 两种执行范式的对比

agent.py 和 agent-plus.py 代表了 Agent 领域的两种经典范式:

agent.py (ReAct)                  agent-plus.py (Plan-then-Execute)

思考 → 行动 → 观察                规划(全局思考)
  ↑         │                         │
  └─────────┘                      步骤1 → 步骤2 → 步骤3
                                   (每步内部仍是 ReAct)

ReAct 灵活但容易迷失,Plan-then-Execute 有全局视角但规划可能不准确。agent-plus.py 通过 --plan 参数让用户自行选择——这种"默认简单,按需复杂"的设计在工程上很实用。

四、多步执行:步骤之间的上下文传递

4.1 从 run_agent 到 run_agent_step 的关键变化

对比第一篇中 agent.py 的 run_agent 函数,agent-plus.py 的 run_agent_step 有两个关键变化:

def run_agent_step(task, messages, max_iterations=5):
    messages.append({"role": "user", "content": task})
    actions = []

    for _ in range(max_iterations):
        response = client.chat.completions.create(
            model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
            messages=messages,
            tools=tools
        )
        message = response.choices[0].message
        messages.append(message)

        ifnot message.tool_calls:
            return message.content, actions, messages

        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            print(f"[Tool] {function_name}({function_args})")
            function_response = available_functions[function_name](**function_args "function_name")
            actions.append({"tool": function_name, "args": function_args})
            messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": function_response})

    return "Max iterations reached", actions, messages

变化一:messages 从内部创建变为外部传入。 这意味着多个步骤可以共享同一个对话历史。步骤 1 执行 grep 搜索到的结果,在步骤 2 中仍然可见。

变化二:返回值包含 messages 函数把更新后的消息列表返回给调用方,供下一步继续使用。

4.2 编排层:把步骤串起来

all_results = []
for i, step in enumerate(steps, 1):
    if len(steps) > 1:
        print(f"\n[Step {i}/{len(steps)}] {step}")
    result, actions, messages = run_agent_step(step, messages)
    all_results.append(result)

final_result = "\n".join(all_results)
save_memory(task, final_result)

messages 变量在整个步骤循环中是同一个列表对象。 步骤 1 执行过程中产生的所有工具调用和结果,都会累积在这个列表中,步骤 2 的 LLM 调用能看到步骤 1 的完整执行轨迹。

这引出了一个重要的概念区分——短期记忆与长期记忆

2.png

五、完整运行时序示例

以 python agent-plus.py --plan "找到所有 TODO 并整理到 todo.md" 为例:

[Planning] Breaking down task...
[Plan] 3 steps created
  1. 使用 grep 递归搜索所有 TODO 注释
  2. 整理搜索结果为 Markdown 清单格式
  3. 将清单写入 todo.md

[Step 1/3] 使用 grep 递归搜索所有 TODO 注释
[Tool] execute_bash({"command": "grep -rn 'TODO' --include='*.py' ."})
  → ./app.py:23: # TODO: add error handling
  → ./utils.py:7: # TODO: refactor this function
  → ./main.py:45: # TODO: add logging

找到 3 处 TODO 注释。

[Step 2/3] 整理搜索结果为 Markdown 清单格式
(LLM 看到步骤 1 的 grep 结果,直接整理,无需再次搜索)

已整理为以下清单:
- app.py:23 - add error handling
- utils.py:7 - refactor this function
- main.py:45 - add logging

[Step 3/3] 将清单写入 todo.md
[Tool] write_file({"path": "todo.md", "content": "# TODO List\n\n..."})

已将 TODO 清单写入 todo.md。

注意步骤 2 没有调用任何工具——LLM 直接从 messages 中读取了步骤 1 的 grep 输出并整理。这就是上下文传递的威力。

六、记忆方案的局限与进化方向

nanoAgent 的记忆方案堪称"最小可行记忆",但它清晰地暴露了四个需要进化的方向:

记忆无差别截断 → 向量数据库 + 语义检索。只保留最后 50 行会丢失重要的早期信息。更好的方案是把记忆存入向量数据库(如 Chroma、Pinecone),每次只检索与当前任务语义相关的记忆。这就是 RAG 的核心思路。

无记忆压缩 → 记忆蒸馏。当记忆超过阈值时,用 LLM 自动压缩旧记忆——提取关键事实,丢弃细节。比如把"执行了 grep -rn TODO,找到 app.py:23、utils.py:7、main.py:45 三处"压缩为"项目中有 3 处 TODO 待处理"。

全量注入 prompt → 记忆作为工具。不把记忆塞进 system prompt,而是提供一个 search_memory 工具让 Agent 按需查询。Agent 自己决定什么时候需要回忆、回忆什么。

单层记忆 → 分层记忆架构。参考人类记忆的工作方式:工作记忆(当前 messages)→ 短期记忆(最近几次对话摘要)→ 长期记忆(压缩后的关键事实)。每层的信息密度和保留时间不同。

七、总结与下一篇预告

3.png

三个核心启示:

1. 记忆的本质是"信息搬运" LLM 没有真正的记忆,所有记忆都是把外部存储的信息搬运到 prompt 中。不同方案的区别只在于"存在哪、怎么找、搬多少"。

2. 规划让 Agent 有了全局视角 没有规划的 Agent 像是蒙着眼走迷宫;有规划的 Agent 至少先看了一眼地图。虽然地图可能不准确,但总比没有强。

3. 上下文传递是多步执行的关键messages 列表在步骤间的共享,让后续步骤能利用前面步骤的执行结果,避免重复工作。

现在我们的 Agent 有了记忆和规划能力,但还有三个问题没解决:

  • 工具是硬编码的——想接入 Slack、GitHub、数据库等外部服务怎么办?
  • 没有行为约束——不同项目、不同团队对 Agent 的要求完全不同,怎么定制?
  • 规划是被动触发的——用户必须手动加 --plan 参数,Agent 自己不知道什么时候该规划。

这三个问题,引出了 Agent 架构中最后也是最精彩的三个概念:MCP(工具协议)Rules(行为规则)Skills(技能注册)。如果你用过 OpenClaw 或 Claude Code,你一定见过 CLAUDE.md 规则文件和 MCP 工具配置——它们正是这三个概念的工程化产物。在 第三篇:Rules、Skills 与 MCP 中,我们将看到 agent-claudecode.py 如何用 265 行代码,把 nanoAgent 进化为一个接近 OpenClaw / Claude Code 的完整 Agent 框架。


*本文基于 sanbuphy/nanoAgent 的 agent-plus.py 分析。如果你还没有读过系列第一篇,建议先从 底层原理,只有 115行 开始。


相关链接

[1]  nanoAgenthttps://github.com/sanbuphy/nanoAgent

[2] agent-plus.pyhttps://github.com/GitHubxsy/nanoAgent/blob/main/agent-plus.py

[3] agent.pyhttps://github.com/GitHubxsy/nanoAgent/blob/main/agent.py

[4] 第一篇:底层原理,只有 115 行 —— 工具 + 循环https://mp.weixin.qq.com/s/gz_vPvgTdozh4FO6vEdF-Q


容器.jpg

关注 AGENT 魔方公众号,回复 Agent

免费领取「从零开始理解 Agent」全套资料包

加速入门和掌握 Agent:

资料二维码.jpg

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。