作用
让 agent 能“记住跨会话的事实”,同时避免对话上下文无限增长。harness 把记忆拆成两层:高频低质量的“流水账” + 低频高质量的“策划后长期记忆”,并补上一套 FTS5 检索 + 后台维护。触发
关键逻辑
双层记忆模型
- 第一层·流水账
memory/YYYY-MM-DD.md:MemoryFlushManager专属,只追加,不去重;是“刚刚在说什么”的原始记录。 - 第二层·策划后长期记忆
MEMORY.md:MemoryConsolidator专属,整体重写;MemoryFlushManager 不会去动它。每次推理都走WorkspaceContextHook注入到 system prompt。 - 索引
MemoryIndex:启动时indexAllFromWorkspace一次;每次 flush 写今日流水账后增量重建该文件索引;SQLite 文件位于<workspace_parent>/memory_index.db。
对话压缩(ConversationCompactor)
TruncateArgsConfig—轻量预处理(可选)
在 LLM 摘要之前,可以先走一个不走 LLM 的干预:对老消息里不那么重要的 ToolUseBlock 参数做字符串截断(默认阈值 25 条 / 40k tokens,参数超 2000 字符被裁揉)。适合 write_file 这种入参体量大、后期不需要原貌的场景。
上下文溢出自动恢复
当模型返回context_length_exceeded / maximum context 之类错误,HarnessAgent.recoverFromOverflow → forceCompactAndRetry 会拼一个临时 triggerMessages=1 的 CompactionConfig 走一轮压缩,清空 Memory 后重试;前提是配置了 compaction(...),否则直接抛错。
记忆提取(MemoryFlushManager)
flushMemories(messages):拿当前MEMORY.md和今日流水账作为“去重参考”丢给 LLM,要求输出 仅新增的 bullet;“NO_REPLY” 表示什么都不写。- 写入位置固定是
memory/YYYY-MM-DD.md,不会动MEMORY.md(以防二层被一层覆写)。 - 写完后立刻
indexFromString重建该文件索引,并调MemoryMaintenanceScheduler.requestConsolidation()提示“能合并了合并下”。
二次合并(MemoryConsolidator)
- 读 mtime 超过 watermark 的日流水账 + 当前
MEMORY.md,调 LLM 合并、去重、裁剪。 - 输出限制:默认
maxMemoryTokens=4000(约 16k 字符),prompt 会以字符预算的形式告诉 LLM。 - 写后推进 watermark,存于
memory/.consolidation_state;下次只看 mtime 超 watermark 的日文件。 - 合并仅在后台 executor 跳:定期 tick 或
requestConsolidation()发起,永不阻塞推理循环。
后台维护(MemoryMaintenanceScheduler)
HarnessAgent.build() 中自动创建并 start(),每个 tick 顺序跑:
expireDailyFiles— 超过dailyFileRetentionDays的日文件归档到memory/archive/(默认 90 天)consolidateMemory— 调MemoryConsolidator.consolidate()pruneOldSessions— 删除 mtime 超sessionRetentionDays的会话文件(默认 180 天)reindex—MemoryIndex.indexAllFromWorkspace
Duration.ofHours(6);opportunistic 调用实际节流间隔 30 分钟,避免频繁 flush 打爆 LLM。
工具结果卸载(ToolResultEvictionConfig)
与压缩独立。某次 tool_call 返回的文本超过阈值时,全文写到 evictionPath 下的文件,原位置只留一个“首尾预览 + 路径”的占位符,agent 需要完整内容时走 read_file。