← 返回教程库

给自己的 Agent 加记忆与技能积累——最小可用落地方案

最后更新 2026-06-25
你将学到
  • 理解文件式记忆(MEMORY.md + USER.md)的最小可用结构,知道各自存什么
  • 掌握跨会话加载记忆并注入上下文的方式,让 Agent 每次启动都"认识你"
  • 让 Agent 能把本次任务中学到的新方法沉淀成 Skill 文件,形成技能自积累
  • 能在自己的 Agent 主循环里接上读写记忆的代码,并知道怎么避开三个主要坑

你已经把前几节的记忆原理过了一遍——向量检索、FTS5 摘要、用户建模,每种思路听起来都有道理。但说到底,理论读完之后,坐在键盘前还是一脸懵:我自己的 Agent,该从哪里下手?

这节不再讲原理,只讲怎么动手。目标很具体:给你正在写(或者准备写)的 Agent,接上三样东西——

  1. 文件式记忆:用两个 Markdown 文件存事实和用户偏好
  2. 跨会话加载:每次启动把记忆读进来,塞进上下文
  3. 技能自积累:Agent 把本次学到的新方法写成 Skill 文件,下次直接用

这三样组合起来,就是 Hermes Agent 里那套思路的简化版。完整的 Hermes 实现以 官方仓库 为准,本文给你一个可以直接搬进自己项目的骨架。


要加的三样东西

第一样:文件式记忆

两个文件搞定大部分场景:

MEMORY.md——存"关于这个任务/这个项目的事实"

# 记忆

## 项目事实
- 仓库路径:/home/user/myproject
- 主要技术栈:Python 3.11 + FastAPI
- 测试命令:pytest tests/ -v

## 已完成的事
- 2026-06-25:完成了用户认证模块,JWT 方案,token 有效期 24h
- 2026-06-25:发现 /api/upload 接口有并发锁问题,已记录但未修

## 待跟进
- 上传接口的并发锁需要用队列重写

USER.md——存"关于你这个人的偏好"

# 用户偏好

- 回复风格:简洁直接,不要废话
- 代码风格:Python,类型注解,不用 type: ignore
- 不喜欢:Agent 自作主张改文件名、删代码
- 工作节奏:早上专注写代码,下午处理沟通

为什么分两个文件?因为更新频率不同MEMORY.md 每次任务后都可能追加新事实;USER.md 很少变,基本只在用户说"以后不要这样做"时才改。分开存,更新时不会互相污染,加载时也可以分别控制要不要读进来。

关于文件式记忆的更多背景,可以回看 用文件给 Agent 加记忆:Hermes 的思路 这节。

第二样:跨会话加载记忆

记忆文件有了,关键是每次启动 Agent 时要把它读进去,而且要放对位置。

最直接的办法:放进 system prompt

def load_memory(memory_path="MEMORY.md", user_path="USER.md") -> str:
    """读取记忆文件,拼成 system prompt 的一部分"""
    sections = []
    for path, label in [(user_path, "用户偏好"), (memory_path, "项目记忆")]:
        try:
            with open(path, "r", encoding="utf-8") as f:
                content = f.read().strip()
            if content:
                sections.append(f"## {label}\n{content}")
        except FileNotFoundError:
            pass  # 文件不存在就跳过,不报错
    return "\n\n".join(sections)

加载进来的内容会放在 system prompt 的靠前位置,让模型在整个会话里都能"看到"它。这和 跨会话汇总与用户建模 里讲的思路一脉相承——把需要持续生效的上下文放 system,把会话内容放 messages。

第三样:技能自积累

Agent 自写 Skills:Hermes 的自学习机制 里讲过,Agent 可以把"怎么做某类事"写成 SKILL_XXX.md,下次遇到类似任务时直接加载,不用重新摸索。

最简单的触发方式:在任务完成后,让 Agent 判断"有没有值得沉淀的方法"

SKILL_PROMPT = """
你刚完成了这个任务。请判断:这次解决问题的过程中,有没有一个"通用方法"值得记下来,以后遇到类似情况可以直接用?

如果有,用下面格式输出(只输出这个,不要其他话):
---SKILL---
文件名:SKILL_<简短英文名>.md
内容:
# 技能:<技能名>
## 适用场景
<什么情况下用这个技能,一两句话>
## 步骤
<具体怎么做,3-5步>
## 注意
<坑或边界,可选>
---END---

如果没有值得沉淀的通用方法,只输出:NO_SKILL
"""

Agent 回复 ---SKILL--- 块时,你的代码解析出来写成文件;回复 NO_SKILL 时什么都不做。这是 Hermes 完整自写 Skill 机制的简化版——完整实现涉及版本管理、技能评分、自动归并等,那些以官方仓库为准,这里给你的是能跑的骨架。


可跑代码骨架:读写记忆接进主循环

把上面三样东西组合起来,这是一个完整可跑的主循环骨架(把它保存成 agent_with_memory.py,填入 ANTHROPIC_API_KEY 就能跑):

import anthropic
import json
import os
import re
from pathlib import Path

# ── 配置 ──────────────────────────────────────────────
MEMORY_FILE = "MEMORY.md"
USER_FILE   = "USER.md"
SKILLS_DIR  = "skills"   # Skill 文件存在这个目录里

client = anthropic.Anthropic()  # 自动读取 ANTHROPIC_API_KEY

# ── 1. 读取记忆文件 ────────────────────────────────────
def load_memory() -> str:
    sections = []
    for path, label in [(USER_FILE, "用户偏好"), (MEMORY_FILE, "项目记忆")]:
        try:
            content = Path(path).read_text(encoding="utf-8").strip()
            if content:
                sections.append(f"## {label}\n{content}")
        except FileNotFoundError:
            pass
    return "\n\n".join(sections)


def load_skills() -> str:
    """把 skills/ 目录里所有 SKILL_*.md 的内容拼起来"""
    skill_dir = Path(SKILLS_DIR)
    if not skill_dir.exists():
        return ""
    chunks = []
    for f in sorted(skill_dir.glob("SKILL_*.md")):
        try:
            chunks.append(f.read_text(encoding="utf-8").strip())
        except Exception:
            pass
    return "\n\n---\n\n".join(chunks)


# ── 2. 把新事实写回记忆文件 ────────────────────────────
def append_to_memory(new_fact: str) -> None:
    """把 Agent 总结的新事实追加到 MEMORY.md"""
    if not new_fact.strip():
        return
    with open(MEMORY_FILE, "a", encoding="utf-8") as f:
        f.write(f"\n- {new_fact.strip()}")
    print(f"[记忆] 已追加:{new_fact.strip()}")


# ── 3. 解析并保存 Skill ────────────────────────────────
def try_save_skill(model_output: str) -> None:
    """如果模型输出里有 ---SKILL--- 块,解析后写成文件"""
    m = re.search(r"---SKILL---\n文件名:(\S+)\n内容:\n(.*?)---END---", model_output, re.DOTALL)
    if not m:
        return
    filename, content = m.group(1), m.group(2).strip()
    Path(SKILLS_DIR).mkdir(exist_ok=True)
    skill_path = Path(SKILLS_DIR) / filename
    skill_path.write_text(content, encoding="utf-8")
    print(f"[技能] 已保存:{skill_path}")


# ── 4. Agent 主循环 ────────────────────────────────────
def run_agent(user_message: str, tools: list = None) -> str:
    if tools is None:
        tools = []

    # 读取记忆和已有技能,塞进 system prompt
    memory_context = load_memory()
    skill_context  = load_skills()

    system_parts = ["你是一个有记忆、有积累的助手,每次任务后会把新方法沉淀成技能。"]
    if memory_context:
        system_parts.append(memory_context)
    if skill_context:
        system_parts.append(f"## 已有技能\n{skill_context}")

    system_prompt = "\n\n".join(system_parts)

    messages = [{"role": "user", "content": user_message}]
    print(f"\n用户:{user_message}")

    # ── 主循环(想→做→看) ──────────────────────────────
    max_rounds = 10
    for _ in range(max_rounds):
        kwargs = dict(
            model="claude-opus-4-5",   # 以官方文档为准(截稿 2026-06)
            max_tokens=2048,
            system=system_prompt,
            messages=messages,
        )
        if tools:
            kwargs["tools"] = tools

        response = client.messages.create(**kwargs)
        print(f"stop_reason: {response.stop_reason}")

        if response.stop_reason == "end_turn":
            final_text = "".join(
                b.text for b in response.content if hasattr(b, "text")
            )
            print(f"\nAgent:{final_text[:200]}{'...' if len(final_text) > 200 else ''}")

            # ── 5. 任务完成后:询问模型有没有新事实/新技能 ──────
            new_fact = ask_for_new_fact(final_text, user_message)
            if new_fact:
                append_to_memory(new_fact)

            try_save_skill(final_text)   # 如果回复里带了 SKILL 块就保存
            # 也可以再单独发一轮 SKILL_PROMPT,这里为简洁合并处理

            return final_text

        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": response.content})
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    print(f"  → 调用工具:{block.name},参数:{block.input}")
                    # 这里换成你真实的工具执行逻辑
                    result = f"(工具 {block.name} 的返回结果)"
                    print(f"  ← 返回:{result}")
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": str(result),
                    })
            messages.append({"role": "user", "content": tool_results})
        else:
            print(f"未预期的 stop_reason: {response.stop_reason}")
            break

    return "Agent 超出最大轮次,未能完成任务"


def ask_for_new_fact(agent_reply: str, original_task: str) -> str:
    """单独发一轮,让模型判断这次任务有没有值得记忆的新事实(一句话)"""
    prompt = f"""
原始任务:{original_task}

你的回复:{agent_reply[:500]}

请判断:这次任务里有没有一条值得记入 MEMORY.md 的新事实(例如"发现了一个新路径"/"确认了某个配置")?

如果有,只输出那一句话,不加任何前缀。如果没有,只输出 NO_FACT。
""".strip()

    r = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=200,
        messages=[{"role": "user", "content": prompt}],
    )
    result = r.content[0].text.strip()
    return "" if result == "NO_FACT" else result


# ── 跑起来 ────────────────────────────────────────────
if __name__ == "__main__":
    # 第一次跑:建空文件,让它感受到"没有记忆"
    run_agent("帮我整理一下这个项目用的是什么技术栈,用一句话总结。")

    # 再跑一次:这次它应该能看到上一轮写回来的记忆
    # run_agent("上次我们讨论了什么?")

你应该看到什么

第一次跑完,如果 Agent 认为回复里有值得记录的事实,MEMORY.md 里会多出一行。第二次跑同一个脚本,system prompt 里就会带上这条记忆——Agent 不再是"每次都忘光"的状态。

skills/ 目录里,如果 Agent 判断本次任务有可复用的方法,会出现一个 SKILL_xxx.md 文件。下次加载时它会读进 system prompt,不需要再重新摸索同类问题。

这就是最小可用的"记忆+技能积累"完整循环:读 → 干活 → 写回 → 下次读进来


怎么让它沉淀得更好

光有骨架还不够,沉淀质量决定这个机制有多大用。几个实践点:

写回时加时间戳

from datetime import date

def append_to_memory(new_fact: str) -> None:
    today = date.today().isoformat()
    with open(MEMORY_FILE, "a", encoding="utf-8") as f:
        f.write(f"\n- {today}:{new_fact.strip()}")

加了时间后,你知道哪些记忆"比较新",清理时可以按时间保留近 3 个月的。

Skill 文件给它分类

SKILLS_DIR 里可以按主题建子目录,比如 skills/git/skills/api/。加载时按需读,不需要每次把所有技能全塞进去。AI Agent 智能体阶梯 里后续章节会讲更细的按需检索。

USER.md 让用户自己写

第一次跑 Agent 之前,让用户手动填一遍 USER.md。模板给他,五分钟填完。比 Agent 自己猜用户偏好准多了,而且不会在错误的假设上越走越远。

技能不要让 Agent 自由命名

SKILL_XXX.mdXXX 如果让模型自由发挥,容易出现重复(SKILL_git_push.mdSKILL_git_commit.md 内容高度重叠)。可以在 SKILL_PROMPT 里加一行:「文件名格式:SKILL_<领域>_<操作>.md,领域和操作都用英文小写」,约束一下命名空间。


故障排查表

症状 原因 解法
记忆文件越来越大,几周后 system prompt 超 token 每次都追加,从不清理,记忆无限膨胀 定期压缩:让 Agent 对 MEMORY.md 做一次「合并同类项+删过期条目」的汇总,再写回;或者用滑动窗口只保留最近 30 条
两个 Agent 进程同时写 MEMORY.md,内容混乱 文件级写回没有锁,并发写回冲突 加文件锁(fcntl.flock on Unix,msvcrt.locking on Windows);或改用 SQLite(自带行锁);单进程场景不会触发此坑
技能太多,每次全加载导致 system prompt 稀释,核心任务能力下降 加载太多无关技能,上下文被占满 按任务类型按需加载:先让模型判断「这次任务需要哪些技能」,只加载相关文件;其余技能不进 system prompt
Agent 每次都把没什么用的废话写进记忆 ask_for_new_fact 没有限制条件 在 prompt 里加约束:「只记录影响未来任务的新事实,聊天内容和已知信息不记」;同时人工周期性审查一下 MEMORY.md
Skill 文件写出来格式乱,无法解析 模型没有严格遵守输出格式 把 SKILL_PROMPT 里的格式约束写得更死,加一行「注意:---SKILL--- 和 ---END--- 必须单独成行,文件名行必须以"文件名:"开头」

常见问题

Q:文件式记忆和向量数据库比,差在哪?

差在检索粒度和规模。记忆条数不多(几十到几百条)时,文件式读进来全量塞进 context 没问题,简单、可靠、不依赖额外服务。条数上千、需要语义搜索时,文件方案就撑不住了——那时候才需要向量库。前几节讲的 FTS5、向量检索是那个规模的解法,你现在不需要上来就搞。先把文件式跑通,再升级。

Q:MEMORY.md 放哪比较好?同一个项目里放一份,还是全局放一份?

两份都放:~/.agent/USER.md 放全局用户偏好(所有项目共用),./MEMORY.md 放当前项目事实(每个项目独立)。加载时两个都读,拼成 system prompt 的不同区块。这样用户偏好不用每个项目重填,项目事实也不会互相污染。

Q:这个骨架能接进 Hermes 吗?还是得重写?

骨架里的文件格式(MEMORY.md / USER.md / SKILL_*.md)和 Hermes 的设计思路一致,接起来改动不大。Hermes 完整实现还包括技能版本管理、自动评分、合并去重等,这些骨架里没有。如果你之后想升级到完整 Hermes,建议直接看 官方仓库,文件格式兼容,不用重写已有的记忆文件。

Q:Agent 写回的新事实,我需要每次审查吗?

建议早期审查,跑了二三十次之后可以信任它。刚开始 ask_for_new_fact 的 prompt 可能调得不够准,会写进一些废话;审查几次后调整 prompt,质量就会稳定。或者加一个"确认模式":每次写回前先打印出来让你确认,确认后再写——这在骨架里加三行就能做到。


小结

这节给你的不是原理,是可以直接搬进项目的三样东西:

  • MEMORY.md + USER.md:按更新频率分开,一个存事实,一个存偏好
  • 跨会话加载:读进来放 system prompt 靠前位置,Agent 每次都能"记住"
  • 技能自积累:任务完成后让 Agent 判断值不值得写成 Skill,写了下次直接用

这三样的核心是读 → 干活 → 写回 → 下次读进来这个循环,一旦跑通了,你的 Agent 就从"每次都是新的"变成"越用越顺"。

下一步:把这个骨架接进你自己的 Agent,跑十次,看看 MEMORY.md 和 skills/ 目录里积累了什么,再决定哪里需要调。

👉 看看 AI 数字员工落地指南,或了解 数字员工搭建实战课。需要为企业落地方案,欢迎找我们聊 企业服务

📄 来源 / 自校链接

本文为学习整理,关键步骤与代码请结合下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为学习与落地整理,AI 工具与平台更新较快,关键步骤请结合官方最新资料验证。见免责声明