资讯动态

Hermes Agent 记忆容量如何管理?从 SQLite 到上下文压缩的 TaoToken 实践

发布时间:2026/10/9 20:26:52 来源:尧图企业网站定制
1. 记忆膨胀的现场Hermes Agent 为什么会把上下文吃满Hermes Agent 的记忆容量管理说白了就是解决一个很现实的问题Agent 跑得越久记忆越多上下文窗口越容易被撑爆。它适合谁适合那些把 Agent 放在本地长期跑、每天几十上百轮对话、还希望它记住历史经验的开发者。能做什么它把记忆拆成分层结构让存和用分开避免所有历史都往上下文里塞。我最早接触 Hermes Agent 的时候遇到一个很典型的场景一个本地跑的编码助手连续用了两周MEMORY.md 从几百字符涨到接近上限每次会话启动的固定开销越来越重长会话到后半段开始出现响应变慢、工具调用错乱。查下来不是模型的问题是记忆层没有治理。Hermes 的记忆分四层理解这四层是治理容量的前提文件层是持久语义记忆MEMORY.md 和 USER.md 两份文件有硬字符上限。MEMORY.md 约 2200 字符折算下来大概 800 tokensUSER.md 约 1375 字符约 500 tokens。这两份文件在每次会话启动时以冻结快照的形式注入 system prompt也就是说不管你这轮对话多短这约 1300 tokens 的固定开销都要先付掉。情景记忆层走的是另一条路用 SQLite 加 FTS5 全文检索全量历史存档不设容量上限。关键在于它不直接注入上下文而是等 Agent 主动调用 session_search 工具按关键词或正则检索检索结果以摘要形式注入。存的时候不省注入的时候省。工作记忆层就是当前会话的窗口也是最容易溢出的地方。Hermes 的 ContextCompressor 用四阶段管道处理不是简单截断。自我进化层是每 15 个任务触发一次 nudgeAgent 回顾最近完成的任务提炼可复用经验尝试写入 MEMORY.md超限就触发精简。这四层里真正决定记忆容量体感的是文件层和工作层。文件层管固定开销的天花板工作层管单次会话的溢出。SQLite 那层虽然叫无容量限制但它决定了检索时的 Token 消耗——检索命中越多注入的摘要越多工作窗口压力越大。所以容量治理不是单点问题是三层联动。下面我会从 SQLite 表结构开始一路讲到上下文压缩阈值配置最后用 TaoToken 统一通道观察每次调用的 Token 开销。整个过程都可以在本地复现。2. TaoToken 前置统一 Key 与 API 通道让 Token 开销可观测在动手改记忆表结构和压缩阈值之前先把调用通道理顺。原因很简单你要治理 Token 消耗前提是能看见 Token 消耗。如果每次请求散落在不同的 Key、不同的 Base URL 上账单和用量对不上压缩阈值调了也不知道效果。TaoToken 在这里的角色是统一入口。它提供兼容主流协议风格的 API 通道你可以在一个控制台里管理 Key、查看调用记录、观察模型维度的用量。对 Hermes Agent 这种会频繁调用辅助模型做记忆精简和上下文压缩的场景统一通道的价值很直接压缩用的廉价模型和主对话模型走同一个出口用量能合并看。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。注册后在控制台创建 API Key地址是 https://taotoken.net/api注意这个 API 地址不带 UTM 参数配置的时候别把查询串带进去。你需要准备三样东西这三样在后面的配置里会反复出现Base URLhttps://taotoken.net/apiAPI Key在控制台 https://taotoken.net/console/api-keys 生成形如 sk- 开头的一串Model ID按你实际要用的模型填比如做记忆精简这种低价值任务选一个便宜的小模型就够主对话再选能力强的如果你只是想先验证通道通不通可以用模型对话页面直接发一条测试消息https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。这一步不写代码纯网页操作确认 Key 有效、模型能返回内容再往下做本地配置。有一点要提醒TaoToken 是 API 通道不是编辑器替代品也不是让你绕过什么限制的工具。它的定位就是统一管理模型调用方便你观察开销。这个认知摆正了后面的配置才不会跑偏。对于长期跑 Agent 的开发者如果调用量稳定可以看一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。它的意义在于把长期编码和 Agent 场景的调用成本固定下来避免按量计费在记忆精简这种高频小请求上失控。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content配置细节以文档为准。下面我给出的配置片段是通用形态你按文档核对字段名。3. 可复制配置SQLite 记忆表结构 压缩阈值 settings 片段这一节是全文最核心的部分全部可复制。分三块SQLite 记忆表结构、上下文压缩阈值配置、以及把 TaoToken 通道写进 settings 的片段。3.1 SQLite 记忆表结构情景记忆层用 SQLite 加 FTS5。下面这份表结构是我实测下来比较稳的版本包含主表、FTS5 虚拟表和触发器三部分。字段设计上把原始内容和摘要分开存检索时优先返回摘要避免原始全文直接进上下文。-- 主表情景记忆条目 CREATE TABLE IF NOT EXISTS memory_episodes ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, -- user / assistant / tool content TEXT NOT NULL, -- 原始内容 summary TEXT, -- 压缩后的摘要检索优先返回 token_count INTEGER DEFAULT 0, -- 该条目的 token 估算 importance INTEGER DEFAULT 0, -- 重要度打分0-10 created_at INTEGER NOT NULL, -- unix 时间戳 last_hit_at INTEGER, -- 最近一次被检索命中的时间 hit_count INTEGER DEFAULT 0 -- 被命中次数 ); -- 索引按会话和时间检索 CREATE INDEX IF NOT EXISTS idx_episodes_session ON memory_episodes(session_id, created_at DESC); -- 索引按重要度筛选用于精简时优先淘汰低价值条目 CREATE INDEX IF NOT EXISTS idx_episodes_importance ON memory_episodes(importance, last_hit_at); -- FTS5 虚拟表全文检索 CREATE VIRTUAL TABLE IF NOT EXISTS memory_episodes_fts USING fts5( content, summary, contentmemory_episodes, content_rowidid, tokenizeunicode61 ); -- 触发器插入时同步 FTS CREATE TRIGGER IF NOT EXISTS episodes_ai AFTER INSERT ON memory_episodes BEGIN INSERT INTO memory_episodes_fts(rowid, content, summary) VALUES (new.id, new.content, COALESCE(new.summary, )); END; -- 触发器更新时同步 FTS CREATE TRIGGER IF NOT EXISTS episodes_au AFTER UPDATE ON memory_episodes BEGIN INSERT INTO memory_episodes_fts(memory_episodes_fts, rowid, content, summary) VALUES (delete, old.id, old.content, COALESCE(old.summary, )); INSERT INTO memory_episodes_fts(rowid, content, summary) VALUES (new.id, new.content, COALESCE(new.summary, )); END; -- 触发器删除时同步 FTS CREATE TRIGGER IF NOT EXISTS episodes_ad AFTER DELETE ON memory_episodes BEGIN INSERT INTO memory_episodes_fts(memory_episodes_fts, rowid, content, summary) VALUES (delete, old.id, old.content, COALESCE(old.summary, )); END;这份结构的关键点在于 summary 字段和 importance 字段。summary 让检索结果以摘要形式注入而不是原始全文importance 让精简时有依据优先淘汰低分且长期没被命中的条目。检索时用这样的查询只取摘要控制注入量SELECT e.id, COALESCE(e.summary, substr(e.content, 1, 200)) AS inject_text, e.importance, e.created_at FROM memory_episodes_fts f JOIN memory_episodes e ON e.id f.rowid WHERE memory_episodes_fts MATCH ? ORDER BY e.importance DESC, e.last_hit_at DESC LIMIT 5;LIMIT 5 是刻意的检索命中越多注入越多工作窗口压力越大。控制在 5 条以内配合摘要单次检索注入通常能压在 1K tokens 以内。3.2 上下文压缩阈值配置工作记忆层的四阶段压缩阈值配置决定了什么时候触发、保护多少、压缩多少。下面这份 TOML 配置是我调过几轮之后的版本字段名按你的实际实现对齐。[context_compressor] # 触发压缩的上下文占用比例超过 0.75 开始压缩 trigger_ratio 0.75 # 头部保护消息条数系统提示 早期上下文 head_protect_messages 3 # 尾部按 token 预算动态保留保证当前任务细节不丢 tail_protect_tokens 20000 # 旧工具输出清理阈值超过该字符数的工具结果替换为占位符 tool_output_char_limit 200 # 压缩用的辅助模型走统一通道 compress_model your-cheap-model-id # 压缩后是否修复孤立的 tool_call / tool_result 配对 repair_tool_pairs true # 单次压缩生成摘要的最大 token summary_max_tokens 1500 [memory_files] # 文件层硬上限按字符计 memory_md_char_limit 2200 user_md_char_limit 1375 # 接近上限的触发比例超过则触发 LLM 自主精简 compact_trigger_ratio 0.9 [nudge] # 每 N 个任务触发一次自我进化回顾 interval_tasks 15trigger_ratio 0.75 意味着上下文用到 75% 就开始压缩留出余量。tail_protect_tokens 20000 是尾部保护预算最近约 20K tokens 的对话保持原始形态。tool_output_char_limit 200 对应四阶段管道的第一步超过 200 字符的旧工具输出直接替换占位符这一步纯规则处理不经过 LLM零延迟。3.3 TaoToken 通道写进 settings把统一通道写进配置让主对话和压缩辅助模型都走同一个出口。下面这份 JSON 是通用形态字段名以接入文档为准。{ api: { base_url: https://taotoken.net/api, api_key: sk-你的Key, timeout_seconds: 60 }, models: { main: { model_id: your-main-model-id, max_tokens: 4096 }, compressor: { model_id: your-cheap-model-id, max_tokens: 1500 } }, memory: { sqlite_path: ./data/memory_episodes.db, memory_md_path: ./data/MEMORY.md, user_md_path: ./data/USER.md } }如果你用的是 Claude Code 这类工具做 Agent 的编码侧配置形态是 settings 文件三件套同样是 Base URL、Key、Model ID{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: your-model-id } }注意 ANTHROPIC_BASE_URL 填的是 https://taotoken.net/api不要带任何查询参数。Key 从控制台生成Model ID 按你实际选的填。这三件套缺一不可少任何一个都会在请求阶段报错。4. 验证请求从一次调用看 Token 用量与压缩效果配置写完必须验证。验证分两步先确认通道通再确认压缩生效、Token 开销可观测。4.1 通道连通性验证先用 curl 发一条最小请求确认 Base URL、Key、Model ID 三件套正确。curl -s https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: sk-你的Key \ -H anthropic-version: 2023-06-01 \ -d { model: your-model-id, max_tokens: 64, messages: [ {role: user, content: 回复两个字收到} ] }返回里会带 usage 字段包含 input_tokens 和 output_tokens。这一步通了说明通道没问题。如果返回 401看第 5 节的排查。4.2 记忆写入与检索验证往 SQLite 里插几条测试数据验证 FTS5 检索和摘要注入。sqlite3 ./data/memory_episodes.db SQL INSERT INTO memory_episodes (session_id, role, content, summary, token_count, importance, created_at) VALUES (sess-001, user, 项目使用 Python 3.11依赖管理用 uv, Python 3.11 uv, 12, 8, strftime(%s,now)), (sess-001, assistant, 已记录项目环境Python 3.11包管理 uv, 环境已记录, 10, 6, strftime(%s,now)), (sess-002, user, 数据库用 SQLite路径 ./data/app.db, SQLite ./data/app.db, 11, 7, strftime(%s,now)); SQL然后跑检索查询确认只返回摘要、条数受 LIMIT 控制sqlite3 ./data/memory_episodes.db SQL SELECT e.id, COALESCE(e.summary, substr(e.content,1,200)) AS inject_text FROM memory_episodes_fts f JOIN memory_episodes e ON e.id f.rowid WHERE memory_episodes_fts MATCH Python ORDER BY e.importance DESC LIMIT 5; SQL预期返回一条inject_text 是 Python 3.11 uv而不是原始全文。这就是存的时候不省注入的时候省的落地效果。4.3 压缩触发验证构造一段长对话让上下文占用超过 trigger_ratio观察压缩是否触发。可以在 Agent 里加一行日志打印每次请求前的上下文 token 估算和压缩动作。def log_context_state(ctx_tokens, window_size, compressed): ratio ctx_tokens / window_size print(f[ctx] tokens{ctx_tokens} window{window_size} fratio{ratio:.2f} compressed{compressed})跑几轮长对话你会看到 ratio 爬到 0.75 附近时 compressed 变成 True之后 ratio 回落。回落幅度取决于中间区域被压缩掉多少。如果压缩后 ratio 还是很高说明 tail_protect_tokens 设大了或者检索注入的摘要太多需要回头调 LIMIT 和 tail 预算。4.4 Token 用量对照在 TaoToken 控制台的调用记录里按时间对齐你的日志。你会看到两类请求主对话请求 input_tokens 较大压缩辅助请求 input_tokens 中等但调用频繁。把这两类分开看就能算出记忆治理的实际开销。一个健康的比例是压缩辅助请求的 Token 总量不超过主对话的 20%。如果超过说明压缩触发太频繁或者摘要生成用了太贵的模型。前者调高 trigger_ratio后者把 compress_model 换成更便宜的。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth配置和验证过程中下面这几类报错出现频率最高。逐个对照。5.1 401 Unauthorized最常见的原因是 Key 没填对或者 Base URL 带了多余路径。检查三点Key 是不是从控制台 https://taotoken.net/console/api-keys 生成的完整串Base URL 是不是 https://taotoken.net/api没有多余的 /v1 或查询参数请求头字段名对不对有的协议用 x-api-key有的用 Authorization: Bearer。还有一种情况是 Key 复制时带了空格或换行。把 Key 放进环境变量再引用避免手抖export TAOTOKEN_API_KEYsk-你的Key5.2 local proxy failed这个报错通常出现在本地 Agent 配置了代理转发但转发目标不可达。检查你的 settings 里有没有残留的代理配置指向一个没启动的本地端口。把代理相关字段清掉直接用 Base URL 直连。如果你在配置里看到类似 proxy_url、http_proxy 的字段且值指向 127.0.0.1 的某个端口而那个端口没有服务在跑就会报这个错。删掉这些字段或者确认本地服务已启动。5.3 reading choices 相关报错这类报错一般出现在响应解析阶段提示读取 choices 字段失败。原因是请求发出去返回的不是预期的 JSON 结构可能是错误页、可能是协议不匹配。检查你的请求体格式和 Base URL 对应的协议是否一致。用 curl 先手动发一条看原始返回长什么样比在代码里猜快得多。如果返回的是 HTML 错误页说明请求根本没到 API检查 URL 拼写。如果返回 JSON 但没有 choices 字段说明协议形态不对对照接入文档调整请求体。5.4 OAuth 相关报错有的工具链默认走 OAuth 流程配置里如果同时存在 OAuth 和 API Key 两套凭证可能冲突。排查方法是把 OAuth 相关配置注释掉只保留 API Key 三件套。对于 Claude Code 这类工具确认 settings 里是 ANTHROPIC_API_KEY 而不是 OAuth token 字段。5.5 压缩后 Agent 循环崩溃这个不是请求报错是逻辑报错。压缩后如果 tool_call 和 tool_result 配对断裂Agent 会在下一轮解析消息时崩溃。这就是配置里 repair_tool_pairs true 的作用。如果你关掉了这个选项压缩后务必手动检查消息序列确保每个 tool_call 都有对应的 tool_result。排查时打印压缩前后的消息列表对比 tool_call_id 是否成对出现。缺失的那一侧就是问题所在。6. 把记忆治理变成日常从观察到调优的闭环走到这里你已经有了完整的可运行配置SQLite 表结构、压缩阈值、TaoToken 通道、验证脚本、排错清单。剩下的就是把它变成日常习惯。我的做法是每周看一次 TaoToken 控制台的用量重点看压缩辅助请求的占比。占比升高就说明记忆层在膨胀要么是 MEMORY.md 接近上限触发了频繁精简要么是情景检索命中太多导致注入膨胀。前者去精简 MEMORY.md后者去调检索的 LIMIT 和 importance 阈值。MEMORY.md 的精简不要等它撞上限。compact_trigger_ratio 0.9 是触发线但你可以在 0.8 的时候就手动过一遍把过时条目删掉把重复条目合并。LLM 自主精简是兜底不是主力。情景记忆的 importance 打分值得花点心思。检索时按 importance 排序高分条目优先注入。你可以给不同类型的信息定不同基准分环境配置类 8 分临时调试信息 3 分用户偏好 7 分。这样精简时低分的先淘汰高分的留住。上下文压缩的 tail_protect_tokens 是体感最明显的参数。设小了当前任务细节丢失Agent 会重复问已经说过的信息设大了压缩效果不明显窗口还是紧张。20000 是个起点按你的任务复杂度上下调。最后所有调优都要有数据支撑。每次改完阈值跑一轮标准测试对话记录压缩触发次数、检索注入 Token、主对话 Token对齐 TaoToken 控制台的用量。改一次看一次别凭感觉调。如果你还没开始从模型对话页面发一条测试消息开始确认通道通再按第 3 节的配置落地。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content配置字段以文档为准。长期跑 Agent 的话Coding Plan 能把成本固定下来避免记忆精简这种高频小请求把账单推高。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑