背景
s09 的 agent_loop(s09_memory/code.py)用整数下标 memory_turn 标记“把记忆内容注入到哪条 user turn”:
memory_turn = len(messages) - 1 if messages and isinstance(messages[-1].get("content"), str) else None
但这个下标是在 s08的compact运行之前一次性算好的。如果进行snip_compact会原地改短/重建消息数组,使之后面的所有下标都偏移:
messages[:] = tool_result_budget(messages)
messages[:] = snip_compact(messages) # len>50 时砍掉中间,数组变短
messages[:] = micro_compact(messages)
if estimate_size(messages) > CONTEXT_LIMIT:
messages[:] = compact_history(messages) # 整个数组换成 1 条摘要
注入处只有一道“越界保护”:
if memories_content and memory_turn is not None and memory_turn < len(messages):
request_messages[memory_turn] = { ... 注入 memory ... }
压缩后 memory_turn 已不再指向那条 user query,于是 memory_turn < len(messages) 为False,记忆注入被整段跳过——这一轮模型看不到已检索到的记忆。这恰好发生在“对话变长、压缩触发”时,与 s09 “压缩会丢细节,要有一层不丢的 / 跨压缩、跨会话”的核心命题直接矛盾。
修复建议
根因是“先算下标、后压缩”。修复只改 agent_loop(不碰 compact_history / reactive_compact):把“记下标”改成“记消息对象”,压缩后每次用身份比较重新定位;若 query 被
compact_history 摘要掉,则回退到“最后一条 str 内容的 user turn”(即摘要消息),保证记忆仍注入、不会静默丢失。
- 在 agent_loop 开头改为捕获消息对象:
memories_content = load_memories(messages)
# 捕获当前 user turn 的*对象*,而非下标。压缩管线会改短数组、偏移下标,
# 预计算的下标会漂移(记忆被静默丢弃)。改为压缩后按身份重新定位。
memory_msg = messages[-1] if (messages
and isinstance(messages[-1].get("content"), str)) else None
system = build_system()
- 新增一个按身份重新定位的辅助函数(放在 agent_loop 之前):
def _find_memory_turn(messages, memory_msg):
"""按对象身份定位要注入记忆的 user turn。
s09 bug fix:压缩管线会改短/重建 messages,预计算的下标会漂移并静默丢弃
记忆注入。每次压缩后按身份重定位。只返回 str 内容的 user turn,调用方可以
安全地用字符串拼接把记忆接到 content 上。
"""
if memory_msg is not None:
for i, m in enumerate(messages):
if m is memory_msg:
if isinstance(m.get("content"), str):
return i
break # 对象还在,但已不是纯文本 turn
for i in range(len(messages) - 1, -1, -1):
if messages[i].get("role") == "user" and isinstance(messages[i].get("content"), str):
return i
return None
- 循环内、压缩之后重新定位,再注入:
# 压缩后按*身份*重定位 user turn —— 压缩可能已偏移其下标
memory_turn = _find_memory_turn(messages, memory_msg)
try:
request_messages = messages
if memories_content and memory_turn is not None:
request_messages = messages.copy()
request_messages[memory_turn] = {
**messages[memory_turn],
"content": memories_content + "\n\n" + messages[memory_turn]["content"],
}
背景
s09 的 agent_loop(s09_memory/code.py)用整数下标 memory_turn 标记“把记忆内容注入到哪条 user turn”:
但这个下标是在 s08的compact运行之前一次性算好的。如果进行snip_compact会原地改短/重建消息数组,使之后面的所有下标都偏移:
注入处只有一道“越界保护”:
压缩后 memory_turn 已不再指向那条 user query,于是 memory_turn < len(messages) 为False,记忆注入被整段跳过——这一轮模型看不到已检索到的记忆。这恰好发生在“对话变长、压缩触发”时,与 s09 “压缩会丢细节,要有一层不丢的 / 跨压缩、跨会话”的核心命题直接矛盾。
修复建议
根因是“先算下标、后压缩”。修复只改 agent_loop(不碰 compact_history / reactive_compact):把“记下标”改成“记消息对象”,压缩后每次用身份比较重新定位;若 query 被
compact_history 摘要掉,则回退到“最后一条 str 内容的 user turn”(即摘要消息),保证记忆仍注入、不会静默丢失。