1. 为什么长任务里 Memory 总是“断片”
如果你正在用 OpenHands 跑一个跨多轮、跨文件的 AI Agent 任务,大概率遇到过这种场景:第一轮它记住了项目结构,第三轮开始就忘了之前改过哪个文件;或者你明确告诉它“不要动 tests 目录”,下一轮它又把测试文件重写了。这不是模型变笨了,而是 Agent 的 Memory 机制没有把上下文正确地持久化和回注。
OpenHands 的 Memory 模块本质上解决三件事:把对话历史压缩成可检索的片段、把工作区状态(文件、命令输出)沉淀成结构化记忆、在每一轮推理前按相关性把记忆重新拼进 prompt。它不是一个简单的“聊天记录数组”,而是一套带配置骨架的读写链路。理解这套链路,你才能控制 Agent 在长任务里“记住什么、忘掉什么”。
这篇内容聚焦 OpenHands 的 Memory 机制在长任务上下文保持场景下的落地方式,从config.toml与settings.json两个骨架文件切入,梳理 Memory 相关配置项与调用链路,最后交付一段可复制的配置和一次 Memory 读写验证动作。适合已经在本地跑过 OpenHands、想进一步调优 Agent 记忆行为的开发者。读完之后,你应该能自己改配置、验证 Memory 是否生效,并定位常见的“记忆不写入/读不出”问题。
2. TaoToken 前置:给 OpenHands 接一个稳定的模型入口
OpenHands 本身是 Agent 框架,Memory 的压缩、摘要、检索这些动作都要调用 LLM。也就是说,Memory 模块能不能跑通,一半取决于你的模型接入是否稳定。我试过在本地直接填各家原生地址,切换模型时改配置很碎,后来统一走 TaoToken 的 API 入口,OpenHands 侧只需要改base_url和api_key两个字段。
TaoToken 官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数,配置里直接写这个即可。你需要先在控制台创建一个 API Key,OpenHands 的 Memory 摘要模型和主推理模型可以共用同一个 Key,也可以分开配。
这里要强调一点:OpenHands 的 Memory 模块在长任务里会频繁调用模型做摘要,如果模型入口不稳定,表现就是“记忆时有时无”,很容易被误判成 Memory 配置写错了。所以先把模型入口固定下来,再调 Memory,排障路径会清晰很多。
创建 Key 的入口在控制台的 API Keys 页面,模型对话调试可以用模型对话页面先确认 Key 可用,长期跑编码任务建议看 Coding Plan 的额度说明。这几个入口分别是:
- API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
3. config.toml 与 settings.json 的 Memory 配置骨架
OpenHands 的配置分两层:config.toml管运行时行为,settings.json管 LLM 与 Agent 级参数。Memory 相关项散落在两边,很多人只改了其中一边,结果 Memory 不生效。下面是我本地验证过的骨架,你可以直接复制后改路径和 Key。
3.1 config.toml 里的 Memory 段
[core] # 工作区根目录,Memory 会基于这个路径索引文件状态 workspace_base = "/home/yourname/openhands-workspace" # 缓存目录,Memory 的中间产物落在这里 cache_dir = "/home/yourname/.openhands/cache" [memory] # 开启 Memory 模块 enabled = true # 记忆存储后端,本地调试用 local 即可 backend = "local" # 单条记忆的最大 token,超过会被摘要压缩 max_token_per_memory = 2048 # 每轮推理前召回的记忆条数 recall_top_k = 5 # 摘要模型,建议和主模型分开配,省钱且稳定 summarizer_model = "gpt-4o-mini" # 记忆持久化文件 persist_path = "/home/yourname/.openhands/memory/store.json" [llm] # 统一走 TaoToken 入口 base_url = "https://taotoken.net/api" api_key = "sk-your-taotoken-key" model = "gpt-4o"这里几个参数值得单独说。max_token_per_memory控制单条记忆的粒度,设太小会导致记忆碎片化,检索时拼不出完整上下文;设太大则每轮 prompt 膨胀,长任务后期容易超上下文。recall_top_k是召回条数,长任务里可以适当调大到 8 到 10,但要注意和主模型的上下文窗口匹配。
3.2 settings.json 里的 Agent Memory 开关
{ "agent": { "memory_enabled": true, "memory_mode": "hybrid", "memory_write_strategy": "on_turn_end", "memory_read_strategy": "relevance", "condenser_enabled": true, "condenser_max_history": 40 }, "llm": { "base_url": "https://taotoken.net/api", "api_key": "sk-your-taotoken-key", "model": "gpt-4o" } }memory_mode有三个常见取值:conversation只记对话,workspace只记文件与命令状态,hybrid两者都记。长任务建议用hybrid,否则 Agent 会忘记自己改过哪些文件。memory_write_strategy设为on_turn_end表示每轮结束写入,避免中途写入导致状态不一致。condenser_enabled是历史压缩开关,长任务必须开,否则历史会无限增长。
3.3 两个文件的优先级关系
| 配置项 | config.toml | settings.json | 实际生效 |
|---|---|---|---|
| memory 开关 | [memory] enabled | agent.memory_enabled | 两者都为 true 才生效 |
| 召回条数 | recall_top_k | 无 | 以 config.toml 为准 |
| 写入时机 | 无 | memory_write_strategy | 以 settings.json 为准 |
| 模型入口 | [llm] base_url | llm.base_url | settings.json 覆盖 config.toml |
注意:如果你只改了 config.toml 的
[memory]段,但 settings.json 里memory_enabled是 false,Memory 依然不会写入。这是最常见的“配了没效果”原因。
4. 跑通一次 Memory 读写验证
配置写完,别急着跑长任务,先用一个最小动作验证 Memory 是否真的在读写。下面这套流程我在本地反复用过,能快速判断 Memory 链路是否通。
4.1 启动 OpenHands 并确认 Memory 加载
# 进入 OpenHands 项目目录 cd ~/openhands # 用指定配置启动,注意 --config 指向你的 config.toml python -m openhands.server --config ./config.toml --settings ./settings.json启动日志里应该能看到类似输出:
[Memory] backend=local enabled=True persist_path=/home/yourname/.openhands/memory/store.json [Memory] summarizer_model=gpt-4o-mini recall_top_k=5 [LLM] base_url=https://taotoken.net/api model=gpt-4o如果enabled=False,回去检查 settings.json 的memory_enabled。如果base_url不是你配的地址,说明 settings.json 没被加载,检查启动参数路径。
4.2 触发一次记忆写入
在 OpenHands 的对话里发一条带明确状态信息的指令,比如:
请在 workspace 下创建 notes.md,写入一行 "project=openhands-memory-test",然后告诉我文件路径。Agent 执行完后,查看持久化文件:
cat /home/yourname/.openhands/memory/store.json | python -m json.tool | head -40你应该能看到一条包含notes.md和project=openhands-memory-test的记忆条目。如果没有,说明写入策略没触发,检查memory_write_strategy是否为on_turn_end,以及memory_enabled是否为 true。
4.3 验证记忆召回
新开一轮对话,问一个依赖上一轮状态的问题:
刚才创建的 notes.md 里 project 的值是什么?如果 Memory 召回正常,Agent 会直接回答openhands-memory-test,而不是重新去读文件或说不知道。你也可以在日志里看到召回记录:
[Memory] recall top_k=5 selected=1 score=0.87 source=notes.md这一步能过,说明 Memory 的写入、持久化、召回三段链路都通了。接下来再跑长任务,上下文保持会明显稳定。
5. 本篇常见错排查
Memory 配了不生效,原因通常集中在几个点上,我按出现频率排一下。
第一类是配置文件没被加载。OpenHands 启动时如果没显式指定--config和--settings,会走默认路径,你改的文件可能根本没被读。排查方法是看启动日志里的persist_path和base_url是否和你写的一致。
第二类是memory_enabled与[memory] enabled不一致。前面表格里说过,两者必须同时为 true。很多人只改了 config.toml,settings.json 里还是默认 false。
第三类是摘要模型调用失败导致记忆写入中断。Memory 写入时会调用summarizer_model做压缩,如果这个模型的 Key 或地址不对,写入会静默失败。排查方法是单独用模型对话页面测一下summarizer_model是否可用,入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。
第四类是recall_top_k设得过大导致 prompt 超限。长任务后期历史本来就长,召回条数再拉满,主模型直接报上下文超限。建议从 5 开始,逐步加到 8,观察是否稳定。
第五类是持久化文件权限问题。persist_path指向的目录如果不可写,Memory 写入会失败但不一定报错。手动touch一下该路径确认可写。
如果排查到模型接入层的问题,比如 Key 无效、额度不足、地址写错,直接去 API Keys 页面重新生成并核对,接入文档里有完整的字段说明:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 和 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
6. 把 Memory 调稳之后,长任务才真正可用
Memory 模块调通之后,OpenHands 在长任务里的表现会有质的变化:跨轮次的文件状态能保持,之前明确说过的约束不会被遗忘,Agent 不会反复重读同一批文件。这时候你再去看 Coding Plan 的额度消耗,会发现摘要调用其实占了一部分,把summarizer_model换成更轻量的模型能省不少。
如果你准备把 OpenHands 用在持续编码或 Agent 编排场景,建议把 Memory 配置和模型入口一起固化下来,别每次启动都手改。Coding Plan 页面有长期任务的额度说明,可以先看再决定模型组合:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
最后留一个我踩过的坑:改完 Memory 配置后一定要重启 OpenHands 进程,热加载不会重新读config.toml的[memory]段,这个坑我排查了半小时才发现。