Skip to content

feature: advanced memory 服务化 - #329

Closed
CongkeChen wants to merge 6 commits into
mainfrom
feature/advanced_memory_service
Closed

feature: advanced memory 服务化#329
CongkeChen wants to merge 6 commits into
mainfrom
feature/advanced_memory_service

Conversation

@CongkeChen

Copy link
Copy Markdown
Contributor

feature: advanced memory 服务化,支持 redis/sql

  • 优化框架 session compact 实现逻辑

  • 多用户实现

  • 组件支持 redis 存储

  • 组件支持 sql 存储

  • 组件支持简单自定义记忆

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

审查结论

不通过

审查范围:f05797d..eb29c64(93 个变更文件,+4796/-4591),计划为 "feature: advanced memory 服务化 / 记忆与上下文部分解耦",涉及三个新后端(local/redis/sql)的 AdvancedMemoryRuntime、Session Compact 新包(AutoCompact/SessionMemoryExtractor/ToolResultBudget 等)、SessionService 的 compact_events/patch_session_state、Runner 接线与示例。

计划符合性:数据面主体(作用域化、键隔离、compact/patch 幂等、Runner 接线)基本符合计划;但 SQL 与 Redis 后端存在两处会直接导致清理功能失效或后端不可用的死引用缺陷(SEVERE),另有 AutoCompact 持久化边界解析失败升级为硬阻断(MODERATE)与 Redis 去重键 TTL 语义缺口(MODERATE),以及一处同秒排序不确定性(LOW)。

主要风险:

  1. (SEVERE)SQL 后端:ScopedAdvancedMemoryRuntime.initialize() 引用不存在的 self.root._sql_cleanup,且 SqlAdvancedMemoryCleanup 从未被实例化——配置 memory_ttl_seconds/session_ttl_seconds 后过期行永不清除,TTL 保留语义失效、数据无界增长。
  2. (SEVERE)Redis 后端:_redis_stores.py 调用本次变更已删除的 AdvancedMemoryPaths.session_dir(),任何会话级写/读(tool-result、transcript 含去重)都会 AttributeError,Redis 后端会话数据无法存取。
  3. (MODERATE)AutoCompact._persist_session_compaction 在边界事件无法映射回活跃事件时直接抛 ValueError,apply 将其计为一次压缩失败并恢复未压缩内容——多轮压缩/上下文接近硬限时连续失败累积到 autocompact_max_failures,会话被硬阻断,压缩机制整体失效。
  4. (MODERATE)Redis 会话去重键 {stream}:seen:{unique_key} 以 ":transcript" 开头被 _refresh_session_ttl 的 skip_prefixes 跳过,session_ttl_delete_transcripts=False 时永不过期。
  5. (LOW)Session.compact_events 将摘要时间戳设回前缀最早时间,SQL 按 timestamp.desc 恢复并 reversed 时,与同秒保留事件的顺序依赖 DB 物理序,存在不确定排序。

测试充分性:新增测试覆盖 session 服务 patch/update/compact 与 compact_events 幂等、token budget、跨事件循环锁;但 _redis_stores.py 与 _sql_stores.py 两个新存储模块完全无测试,SQL 清理周期路径也无测试,正是 SEVERE-1/2 与 MODERATE-4 未被拦截的原因;AutoCompact 的边界映射失败路径(_resolve_boundary_event_id 返回 None)同样无测试覆盖。

门禁结论:本变更包含会直接导致 Redis 后端不可用与 SQL 清理功能失效的高置信缺陷,判定为 FAILED。

发现的问题

严重

trpc_agent_sdk/advanced_memory/_runtime.py:201-202

问题: ScopedAdvancedMemoryRuntime.initialize() 引用 self.root._sql_cleanup,但 AdvancedMemoryRuntime 从未定义或赋初值该属性(全仓 grep 仅有此处两行引用),SqlAdvancedMemoryCleanup 也从未在 create() 中被实例化;create() 仅为 sql 分支创建 _sql_storage、为 local 分支创建 _local_cleanup

触发条件: storage_backend="sql" 且配置了 memory_ttl_secondssession_ttl_seconds,任何会话调用 ScopedAdvancedMemoryRuntime.initialize()(例如 Advanced Memory 工具/记忆注入首次作用域化时)都会进入该分支。

实际影响: 条件成立时表现为死代码而非异常(_sql_cleanupNone,条件不满足),因此不报错但过期清理任务永远不会启动:SqlMemoryIndex/SqlMemoryTopic/SqlTranscript/SqlTranscriptSeen/SqlToolResult 中设置了 expires_at 的行永不被删除,SQL 后端 TTL 保留语义完全失效,表随使用无界增长。

修正方向:AdvancedMemoryRuntime.create()sql 分支实例化 SqlAdvancedMemoryCleanup(resolved_config, sql_storage) 并存入 _sql_cleanup 字段,或将 initialize() 改为在该分支直接创建并 start() 清理任务;同时补充 SQL 清理路径的单元/集成测试。

严重

trpc_agent_sdk/advanced_memory/_redis_stores.py:61

问题: _session_base() 调用 self._paths.session_dir(session_id),而 session_dir 已被本次变更从 AdvancedMemoryPaths 中删除(当前 _paths.py 仅含 memory_topic_path 等方法,session_dir 只存在于旧版 f05797d 基座中)。

触发条件: storage_backend="redis" 下任何一次会话级存取(RedisToolResultStore.write/readRedisTranscriptStore.append/append_unique/read_alldelete_session_refresh_session_ttl)都会先调用 _session_base() 构造键。

实际影响: 初次会话级写入/读取即抛 AttributeError: 'AdvancedMemoryPaths' object has no attribute 'session_dir',Redis 后端的会话数据(工具结果、去重转录)完全不可用;去重转录与大型工具结果正是本计划声称的 Redis 后端核心能力,现有消费者与其对接即失败。

修正方向: 为该键恢复一个等价的安全会话标识(例如在 _paths.py 重新实现 session_dir/安全组件化函数,或在 _redis_stores.py_collision_safe_component(session_id) 直接生成),并为 Redis 存储补充调用 _session_base() 的测试。

中等

trpc_agent_sdk/advanced_memory/_redis_stores.py:131-139

问题: _refresh_session_ttlsession_ttl_delete_transcripts=False 时设置 skip_prefixes=(f"{base}:transcript",)_refresh_ttl_groupstartswith 前缀匹配跳过 TTL 刷新;去重集合键 {stream}:seen:{unique_key}append_unique 中构造)同样以 :transcript 开头,被一并跳过。

触发条件: Redis 后端、session_ttl_delete_transcripts=False(保留转录语义)且配置了 session_ttl_seconds,调用 append_unique() 写入新去重记录后刷新 TTL。

实际影响: 该去重集合键永不获得过期时间,同时 :transcript 流本身也被跳过(此为本意——保留转录),但 :seen: 集合内的唯一键值将永久留存:数据随会话唯一记录数无界增长,且陈旧值永久抑制相同 unique_key 记录的重新写入(去重语义与保留语义矛盾)。

修正方向: 让跳过匹配精确到转录流键而不是前缀——例如把 skip 逻辑改为 key == stream 或按 key.endswith(":transcript")(流键以 :transcript 结尾,而 seen 键以 :transcript:seen: 续接),从而仅跳过流、仍为 :seen: 键设置 TTL。

中等

trpc_agent_sdk/sessions/compact/_autocompact.py:476-488

问题: _persist_session_compactionboundary_event_id is None 时无条件 raise ValueError,而旧版实现(f05797d_persist_success)通过转录记录持久化失败并让电路熔断器重试;本次变更新增的 _legacy_boundary_event_id 在多轮压缩后(session memory 已存在、边界事件已被归档)会返回 None,直接把压缩路径变成异常路径。

触发条件: 启用 AutoCompact + Session Memory,会话发生多轮压缩且当前按 legacy 回退压缩,或记录边界签名/出现次数无法映射回活跃事件(_resolve_boundary_event_id 返回 None)时。

实际影响: 每次压缩尝试都抛出 ValueError: Cannot map the AutoCompact boundary to an active Session Eventapply 捕获后把 request.contents 恢复为未压缩内容并递增 consecutive_failures——接近硬限时直接进入阻断态(blocked=True),会话在上下文溢出边缘被硬阻断,甚至无法通过降级压缩延续。

修正方向: 使边界解析失败时可降级:在 _persist_session_compactionboundary_event_idNone 时改为调用 session.compact_events 的降级路径(例如按 _legacy_boundary_event_id 的索引语义回退),或将持久化失败与压缩回滚解耦,避免每次失败都计为一次压缩失败。

较低

trpc_agent_sdk/sessions/_session.py:178-183

问题: compact_events 将摘要事件时间戳设为被替换前缀的首个事件时间戳(replaced[0].timestamp),使摘要与保留事件可能出现完全相同的浮点时间戳;SQL 后端恢复时按 timestamp.desc 取回后 reversed() 还原,同秒内的并列序由数据库物理行序决定,无法保证与压缩前一致。

触发条件: SQL 会话服务下发生 Session Compact,且被压缩的前缀最后事件与保留的首个事件之间没有新增事件(时间戳相同或接近);此后重新加载会话。

实际影响: 重载的会话事件顺序在摘要与保留事件之间可能抖动(例如摘要排在保留事件之后),导致上下文注入顺序与用户实际对话顺序不符,压缩边界附近的历史呈现错位。

修正方向: 为摘要分配一个严格早于边界但大于前驱的时间戳(如 replaced[0].timestamp 与上一个可区分时间戳之间取微步长),或为 StorageSessionEvent 增加持久化序号列并按其排序,消除同秒并列的不确定性。

Comment on lines +201 to +202
if self.config.storage_backend == "sql" and self.root._sql_cleanup is not None:
await self.root._sql_cleanup.start()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: ScopedAdvancedMemoryRuntime.initialize() 引用 self.root._sql_cleanup,但 AdvancedMemoryRuntime 从未定义或赋初值该属性(全仓 grep 仅有此处两行引用),SqlAdvancedMemoryCleanup 也从未在 create() 中被实例化;create() 仅为 sql 分支创建 _sql_storage、为 local 分支创建 _local_cleanup

触发条件: storage_backend="sql" 且配置了 memory_ttl_secondssession_ttl_seconds,任何会话调用 ScopedAdvancedMemoryRuntime.initialize()(例如 Advanced Memory 工具/记忆注入首次作用域化时)都会进入该分支。

实际影响: 条件成立时表现为死代码而非异常(_sql_cleanupNone,条件不满足),因此不报错但过期清理任务永远不会启动:SqlMemoryIndex/SqlMemoryTopic/SqlTranscript/SqlTranscriptSeen/SqlToolResult 中设置了 expires_at 的行永不被删除,SQL 后端 TTL 保留语义完全失效,表随使用无界增长。

修正方向:AdvancedMemoryRuntime.create()sql 分支实例化 SqlAdvancedMemoryCleanup(resolved_config, sql_storage) 并存入 _sql_cleanup 字段,或将 initialize() 改为在该分支直接创建并 start() 清理任务;同时补充 SQL 清理路径的单元/集成测试。

)

def _session_base(self, session_id: str) -> str:
safe_session_id = self._paths.session_dir(session_id).name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: _session_base() 调用 self._paths.session_dir(session_id),而 session_dir 已被本次变更从 AdvancedMemoryPaths 中删除(当前 _paths.py 仅含 memory_topic_path 等方法,session_dir 只存在于旧版 f05797d 基座中)。

触发条件: storage_backend="redis" 下任何一次会话级存取(RedisToolResultStore.write/readRedisTranscriptStore.append/append_unique/read_alldelete_session_refresh_session_ttl)都会先调用 _session_base() 构造键。

实际影响: 初次会话级写入/读取即抛 AttributeError: 'AdvancedMemoryPaths' object has no attribute 'session_dir',Redis 后端的会话数据(工具结果、去重转录)完全不可用;去重转录与大型工具结果正是本计划声称的 Redis 后端核心能力,现有消费者与其对接即失败。

修正方向: 为该键恢复一个等价的安全会话标识(例如在 _paths.py 重新实现 session_dir/安全组件化函数,或在 _redis_stores.py_collision_safe_component(session_id) 直接生成),并为 Redis 存储补充调用 _session_base() 的测试。

Comment on lines +131 to +139
async def _refresh_session_ttl(self, session_id: str, *keys: str) -> None:
skip_prefixes: tuple[str, ...] = ()
if not self._config.session_ttl_delete_transcripts:
skip_prefixes = (f"{self._session_base(session_id)}:transcript", )
await self._refresh_ttl_group(
self._session_registry(session_id),
list(keys),
self._config.session_ttl_seconds,
skip_prefixes=skip_prefixes,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: _refresh_session_ttlsession_ttl_delete_transcripts=False 时设置 skip_prefixes=(f"{base}:transcript",)_refresh_ttl_groupstartswith 前缀匹配跳过 TTL 刷新;去重集合键 {stream}:seen:{unique_key}append_unique 中构造)同样以 :transcript 开头,被一并跳过。

触发条件: Redis 后端、session_ttl_delete_transcripts=False(保留转录语义)且配置了 session_ttl_seconds,调用 append_unique() 写入新去重记录后刷新 TTL。

实际影响: 该去重集合键永不获得过期时间,同时 :transcript 流本身也被跳过(此为本意——保留转录),但 :seen: 集合内的唯一键值将永久留存:数据随会话唯一记录数无界增长,且陈旧值永久抑制相同 unique_key 记录的重新写入(去重语义与保留语义矛盾)。

修正方向: 让跳过匹配精确到转录流键而不是前缀——例如把 skip 逻辑改为 key == stream 或按 key.endswith(":transcript")(流键以 :transcript 结尾,而 seen 键以 :transcript:seen: 续接),从而仅跳过流、仍为 :seen: 键设置 TTL。

Comment on lines +476 to +488
compact_events = getattr(ctx.session, "compact_events", None)
if not callable(compact_events):
# AutoCompact remains usable as a request-only primitive in unit
# tests and custom integrations. The standard Manager supplies
# the framework Session and persists the compacted window.
return

boundary_event_id = record.boundary_event_id or self._resolve_boundary_event_id(
ctx,
record.boundary_signature,
record.boundary_occurrence,
)
if boundary_event_id is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: _persist_session_compactionboundary_event_id is None 时无条件 raise ValueError,而旧版实现(f05797d_persist_success)通过转录记录持久化失败并让电路熔断器重试;本次变更新增的 _legacy_boundary_event_id 在多轮压缩后(session memory 已存在、边界事件已被归档)会返回 None,直接把压缩路径变成异常路径。

触发条件: 启用 AutoCompact + Session Memory,会话发生多轮压缩且当前按 legacy 回退压缩,或记录边界签名/出现次数无法映射回活跃事件(_resolve_boundary_event_id 返回 None)时。

实际影响: 每次压缩尝试都抛出 ValueError: Cannot map the AutoCompact boundary to an active Session Eventapply 捕获后把 request.contents 恢复为未压缩内容并递增 consecutive_failures——接近硬限时直接进入阻断态(blocked=True),会话在上下文溢出边缘被硬阻断,甚至无法通过降级压缩延续。

修正方向: 使边界解析失败时可降级:在 _persist_session_compactionboundary_event_idNone 时改为调用 session.compact_events 的降级路径(例如按 _legacy_boundary_event_id 的索引语义回退),或将持久化失败与压缩回滚解耦,避免每次失败都计为一次压缩失败。

Comment on lines +178 to +183
summary_event.set_summary_event(True)
# SQL backends restore active Events in timestamp order. Give the
# replacement summary the prefix's timestamp so it remains the anchor
# before every retained Event after persistence.
summary_event.timestamp = replaced[0].timestamp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: compact_events 将摘要事件时间戳设为被替换前缀的首个事件时间戳(replaced[0].timestamp),使摘要与保留事件可能出现完全相同的浮点时间戳;SQL 后端恢复时按 timestamp.desc 取回后 reversed() 还原,同秒内的并列序由数据库物理行序决定,无法保证与压缩前一致。

触发条件: SQL 会话服务下发生 Session Compact,且被压缩的前缀最后事件与保留的首个事件之间没有新增事件(时间戳相同或接近);此后重新加载会话。

实际影响: 重载的会话事件顺序在摘要与保留事件之间可能抖动(例如摘要排在保留事件之后),导致上下文注入顺序与用户实际对话顺序不符,压缩边界附近的历史呈现错位。

修正方向: 为摘要分配一个严格早于边界但大于前驱的时间戳(如 replaced[0].timestamp 与上一个可区分时间戳之间取微步长),或为 StorageSessionEvent 增加持久化序号列并按其排序,消除同秒并列的不确定性。

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

审查结论

不通过

审查范围与结论

审查范围:f05797d9f9dff2461922b5985aeccc1b636b7c8d..4116be24b3d1f373418963317d5e355fd5ab0b6b(5 个提交:advanced memory 服务化重构),共 115 个文件、+5231/−6985。计划意图为「feature: advanced memory 服务化」:删除 trpc_agent_sdk/advanced_memory 包与 AdvancedMemoryService/AdvancedMemorySessionService facade,以标准 SessionService + compact 子包(sessions/compact/{advanced,default})的 CompactSummarizerABC/CompactSummarizerManagerABC 模型、AdvancedAutoCompactSummarizerManager(BEFORE_MODEL 组合:ToolResultBudget→HistorySnip→MicroCompact→AutoCompact)与 SessionMemoryExtractor_trpc_agent:summary)为替代,并新增 Redis/SQL/InMemory 的 update_session_state 持久化路径。核心实现(compact_events 幂等、boundary 解析、SQL get_for_update 行锁、Redis cjson 修复、EvalSessionService 兼容、server/openclaw 别名延续)经逐一追踪验证,与计划一致。

主要风险

  1. :删除 API 的残留引用。tests/advanced_memory/ 下 3 个测试文件(test_memory_context.py、test_preload_memory.py、test_advanced_memory_tools.py)仍导入已删除的 trpc_agent_sdk.advanced_memorysessions.compact._callbacks.install_staged_callbackmemory.AdvancedMemoryServicetools.AdvancedMemoryToolsexamples/memory_service_with_advanced_memory_redis|sql/run_agent.py 仍导入并实例化已删除的 AdvancedMemoryServiceConfig/AdvancedMemoryService。均必然触发 ModuleNotFoundError/ImportError,破坏 CI 的 pytest --cov-fail-under=80 tests/ 门禁与示例可运行性。
  2. examples/memory_service_with_advanced_memory/README.md 仍以流程图为 AdvancedSessionCompactManager/BaseSessionCompactManager/AdvancedCompactConfig/session_compact_manager= 等已删除 API,且同目录 run_agent.py 已被清空为 0 字节、目录结构与其他示例不一致。
  3. :Redis update_session_state(新增核心持久化路径)为非原子 GET/SET 读改写,并发 checkpoint 写会静默丢失 state 增量,一致性弱于同变更中加 FOR UPDATE 行锁的 SQL 路径。
  4. TokenContextTracker.record_request_contextsession.state 写入 advanced_memory_pending_request_context_fingerprint,全仓无任何读取方(usage baseline 消费的是 Event 侧指标,无写入端),state 随轮次无意义增长。

测试充分性

变更新增了 tests/sessions/test_*_session_service.py 的 compaction/state 持久化测试与 tests/sessions/compact/ 单测,覆盖了 SQL/InMemory 主路径;但 Redis update_session_state 的并发竞态无测试,删除 API 的三包残留测试文件会使整套测试收集报错,属于重构清理遗漏而非“无测试”。

门禁结论

存在 4 条 SEVERE(全部为已删除 API 的 import 残留导致测试收集失败与示例不可运行),本次变更不满足合入门禁,建议修复后再合入。

发现的问题

严重

tests/advanced_memory/test_memory_context.py:10-17

问题: 本变更将 advanced memory 服务化并整体删除 trpc_agent_sdk/advanced_memory 包、trpc_agent_sdk/sessions/compact/_callbacks.py(含 install_staged_callback)以及 trpc_agent_sdk/memory 中的 AdvancedMemoryService_advanced_memory_service.py 已清空且不再导出),但本文件在变更中仍保留/修改了对这些已删除符号的导入,第 10-16 行依次导入 AdvancedMemoryServiceConfigAdvancedMemoryRuntimeLongTermMemoryContextLongTermMemoryContextCallbackMemoryIndexEntryAdvancedMemoryServiceinstall_staged_callback,这些模块在 HEAD 上均不存在。

触发条件: 任何一次 pytest 收集到 tests/(如 CI 的 pytest --cov=trpc_agent_sdk ... tests/pyproject.tomltestpaths = ["tests"])或单独运行该测试文件时,import 阶段必然抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: 该测试文件整文件无法导入,导致相关测试全部失败,CI(--cov-fail-under=80)无法通过;同时 advanced_memory 新能力(Session Memory、filter 安装等)没有任何存量测试覆盖,重构正确性无法被验证。

修正方向: 删除本文件(及同目录 test_preload_memory.pytest_advanced_memory_tools.py 等仍引用已删除 API 的测试),或按新 API 重写为针对 AdvancedAutoCompactSummarizerManager/AdvancedAutoCompactSummarizerFilter/SessionMemoryExtractor_trpc_agent:summary)的测试。

严重

tests/advanced_memory/test_preload_memory.py:7-12

问题: 本变更在 tests/advanced_memory/ 下保留了三个仍导入已删除符号的测试文件:本文件第 13-18 行导入 AdvancedMemoryServiceConfigAdvancedMemoryRuntimeMemoryDocumentMemoryPreloaderMemoryType(均属于已删除的 trpc_agent_sdk.advanced_memory 包),test_advanced_memory_tools.py 第 11-17 行还额外导入 from trpc_agent_sdk.tools import AdvancedMemoryTools, create_advanced_memory_tools——trpc_agent_sdk/tools/_advanced_memory_tool.py 已被本次变更清空,tools/__init__.py 中的懒加载 re-export 也被注释掉,运行时必然 AttributeError

触发条件:pyproject.tomltestpaths = ["tests"] 收集或直接运行这三个测试文件即触发 ModuleNotFoundError/ImportError

实际影响: 三个文件全部收集失败,tests/advanced_memory 套件整体报错,测试门禁失败;遗留的死测试给后续维护者以“advanced memory 已有测试覆盖”的错误信号,实际覆盖为零。

修正方向: 删除这三个残留测试文件,或将它们迁移到 tests/sessions/compact/ 下按新 API(AdvancedAutoCompactSummarizerManagerAdvancedAutoCompactSummarizerFilterSessionMemoryExtractor)重写;同时清理 tools/__init__.py 中已失效的注释代码块。

严重

examples/memory_service_with_advanced_memory_redis/run_agent.py:17-20

问题: 本变更将示例迁移到新架构(agent/ 子包 + create_agent),但脚本第 17 行仍 from trpc_agent_sdk.advanced_memory import AdvancedMemoryServiceConfig、第 19 行仍 from trpc_agent_sdk.memory import AdvancedMemoryService 并随后 AdvancedMemoryService(config=...)——trpc_agent_sdk.advanced_memory 已被本次变更删除,memory.AdvancedMemoryService 已清空不再导出,而 agent.agent.create_agent 实际使用的是标准 SessionService + AdvancedAutoCompactSummarizerFilter/advanced memory 的替代机制,与本脚本的组装方式不一致。

触发条件: 按 README 说明 python run_agent.py 运行 redis 示例时,import 阶段即抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: 该示例完全不可运行(连同 examples/memory_service_with_advanced_memory_sql/run_agent.py 第 17、19 行同样失败),与 README 中“运行本示例验证”的说明直接矛盾;重构后 Redis/SQL 持久化能力没有可运行的演示路径。

修正方向: 删除本脚本中已失效的 advanced_memory/AdvancedMemoryService 导入与实例化,改为使用 agent.agent.create_agent 内置的标准 SessionService(InMemory)+ advanced compaction 组合,或直接删除该目录。

严重

examples/memory_service_with_advanced_memory_sql/run_agent.py:17-20

问题: 本变更删除了 trpc_agent_sdk.advanced_memoryBaseSessionCompactManagerAdvancedSessionCompactManagerAdvancedCompactConfigsession_compact_manager= 接线,但本文件作为变更中新增的 SQL 示例脚本,第 17-19 行仍导入并实例化这些已删除 API(AdvancedMemoryServiceConfigAdvancedMemoryService(config=...)),与 agent.agent.create_agent 的新组装方式矛盾。

触发条件: 按 README 执行 python run_agent.py 时,import 阶段抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: SQL 持久化示例(含 .envagent/ 子包、README 全文)全部成为不可运行的死示例;对本变更宣称的“advanced memory 服务化,支持 redis/sql”能力形成负向宣传。

修正方向: 移除脚本对 AdvancedMemoryService/advanced_memory 的依赖,改用新架构下 create_agent 的组合方式,或删除该示例目录。

中等

examples/memory_service_with_advanced_memory/README.md:7-37

问题: 本变更删除了 AdvancedSessionCompactManagerBaseSessionCompactManagerAdvancedCompactConfigsession_compact_manager= 参数(最终在 4116be2 中整体移除),并将 run_agent.py 清空为 0 字节,但本 README 仍在第 7 行(类图)、第 23 行(“Advanced Compact 通过 BaseSessionCompactManager 生命周期接入”)、第 36-37 行(session_compact_manager=AdvancedSessionCompactManager(config=AdvancedCompactConfig()))展示这些不存在的 API,并指导用户“执行 python run_agent.py”,而该文件已为空。

触发条件: 任何用户按 README 组装代码或执行示例。

实际影响: 文档与实际 API 完全脱节,用户按文档操作会遇到 ImportErrorBaseSessionCompactManager/AdvancedSessionCompactManager 已不存在)和空脚本无输出,造成误导;也表明该示例目录是本次重构遗留的未清理残骸。

修正方向: 将 README 重写为新的标准 SessionService + AdvancedAutoCompactSummarizerFilter/AdvancedAutoCompactSummarizerManager 组合方式(参照 redis/sql 示例的 agent/agent.py),恢复或删除 run_agent.py

中等

trpc_agent_sdk/sessions/_redis_session_service.py:264-283

问题: update_session_state_redis_session_service.py 第 264-283 行)是非原子的读-改-写:先 GET 整个存储 session,在内存中 storage_session.state.update(state_delta),再 SET 整个 JSON 文档。该方法是本次变更新增的核心持久化路径,被 SessionMemoryExtractor._persist_checkpoint 在每个 compaction checkpoint 时调用;两个并发调用(如并发 SDK 进程或多个 Session 的 checkpoint)之间没有锁/事务保护,后写者覆盖先写者的 state 增量。

触发条件: 同一 session 上有两个并发 checkpoint 写(或多进程中同一 session 被并发访问)时,后完成的 SET 会以自己读到的旧 state 为基底写回,覆盖对方刚写入的字段。

实际影响: 部分 session.state 字段(如 _trpc_agent:summaryadvanced_memory_pending_request_context_fingerprint 或自定义 state)丢失,属于静默数据丢失而非报错;对比同变更中 SQL 实现已采用 SELECT ... FOR UPDATE 行锁,Redis 路径的一致性明显低于 SQL 路径。

修正方向: 改用事务内 WATCH key/MULTI + 条件写入,或将 state 变更做 merge 后仅写变更字段(如使用 HMSET/JSON.SET 定点更新 state 子文档)以减小覆盖窗口;至少为 Redis 路径补充并发写入测试(现有测试的 mock 是 GET/SET 直写,未覆盖此竞态)。

较低

trpc_agent_sdk/sessions/compact/advanced/_token_budget.py:242-252

问题: TokenContextTracker.record_request_context_token_budget.py 第 242-252 行)每次在 before-model compaction 成功后向 session.state 写入 advanced_memory_pending_request_context_fingerprint,但全仓(trpc_agent_sdk/examples/tests/)没有任何读取该键的代码——SessionMemoryExtractor/_latest_usage_baseline 消费的是写在 Event custom_metadata 上的 advanced_memory_request_context_fingerprint_token_budget.py:178),且写入端只有测试里伪造的 event metadata,真正的响应 Event 从未写入该字段。

触发条件: 每次触发 auto-compact 的模型请求(_auto_compact.py:581 调用)都会执行该写入,且该 state 会随 update_session_state/update_session 持久化。

实际影响: 会话 state 中持续累积一个永远不被读取的指纹键,state(含 Redis/SQL 存储的完整 JSON)随对话轮次增长,且易让后续 Session Memory 读取方误以为该字段是受支持的契约;同时说明 usage-baseline 应对“模型与工具更新后指纹失效”的机制实际并未闭环。

修正方向: 删除 record_request_context 及其调用点,或将指纹写入响应 Event 的 custom_metadataadvanced_memory_request_context_fingerprint)以对应 _latest_usage_baseline 的读取逻辑,二选一并补充对应测试。

Comment on lines +10 to +17
from trpc_agent_sdk.advanced_memory import AdvancedMemoryServiceConfig
from trpc_agent_sdk.advanced_memory import AdvancedMemoryRuntime
from trpc_agent_sdk.advanced_memory import HistorySnipCallback
from trpc_agent_sdk.advanced_memory import LongTermMemoryContext
from trpc_agent_sdk.advanced_memory import LongTermMemoryContextCallback
from trpc_agent_sdk.advanced_memory import MemoryIndexEntry
from trpc_agent_sdk.advanced_memory import MicrocompactCallback
from trpc_agent_sdk.advanced_memory import setup_advanced_memory
from trpc_agent_sdk.advanced_memory import setup_context_management
from trpc_agent_sdk.advanced_memory import ToolResultBudgetCallback
from trpc_agent_sdk.advanced_memory import TranscriptSessionService
from trpc_agent_sdk.advanced_memory._callbacks import install_staged_callback
from trpc_agent_sdk.memory import AdvancedMemoryService
from trpc_agent_sdk.models import LlmRequest
from trpc_agent_sdk.sessions.compact._callbacks import install_staged_callback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 本变更将 advanced memory 服务化并整体删除 trpc_agent_sdk/advanced_memory 包、trpc_agent_sdk/sessions/compact/_callbacks.py(含 install_staged_callback)以及 trpc_agent_sdk/memory 中的 AdvancedMemoryService_advanced_memory_service.py 已清空且不再导出),但本文件在变更中仍保留/修改了对这些已删除符号的导入,第 10-16 行依次导入 AdvancedMemoryServiceConfigAdvancedMemoryRuntimeLongTermMemoryContextLongTermMemoryContextCallbackMemoryIndexEntryAdvancedMemoryServiceinstall_staged_callback,这些模块在 HEAD 上均不存在。

触发条件: 任何一次 pytest 收集到 tests/(如 CI 的 pytest --cov=trpc_agent_sdk ... tests/pyproject.tomltestpaths = ["tests"])或单独运行该测试文件时,import 阶段必然抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: 该测试文件整文件无法导入,导致相关测试全部失败,CI(--cov-fail-under=80)无法通过;同时 advanced_memory 新能力(Session Memory、filter 安装等)没有任何存量测试覆盖,重构正确性无法被验证。

修正方向: 删除本文件(及同目录 test_preload_memory.pytest_advanced_memory_tools.py 等仍引用已删除 API 的测试),或按新 API 重写为针对 AdvancedAutoCompactSummarizerManager/AdvancedAutoCompactSummarizerFilter/SessionMemoryExtractor_trpc_agent:summary)的测试。

Comment on lines +17 to +20
from trpc_agent_sdk.advanced_memory import AdvancedMemoryServiceConfig
from trpc_agent_sdk.memory import AdvancedMemoryService
from trpc_agent_sdk.runners import Runner
from trpc_agent_sdk.sessions import InMemorySessionService

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 本变更将示例迁移到新架构(agent/ 子包 + create_agent),但脚本第 17 行仍 from trpc_agent_sdk.advanced_memory import AdvancedMemoryServiceConfig、第 19 行仍 from trpc_agent_sdk.memory import AdvancedMemoryService 并随后 AdvancedMemoryService(config=...)——trpc_agent_sdk.advanced_memory 已被本次变更删除,memory.AdvancedMemoryService 已清空不再导出,而 agent.agent.create_agent 实际使用的是标准 SessionService + AdvancedAutoCompactSummarizerFilter/advanced memory 的替代机制,与本脚本的组装方式不一致。

触发条件: 按 README 说明 python run_agent.py 运行 redis 示例时,import 阶段即抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: 该示例完全不可运行(连同 examples/memory_service_with_advanced_memory_sql/run_agent.py 第 17、19 行同样失败),与 README 中“运行本示例验证”的说明直接矛盾;重构后 Redis/SQL 持久化能力没有可运行的演示路径。

修正方向: 删除本脚本中已失效的 advanced_memory/AdvancedMemoryService 导入与实例化,改为使用 agent.agent.create_agent 内置的标准 SessionService(InMemory)+ advanced compaction 组合,或直接删除该目录。

Comment on lines +17 to +20
from trpc_agent_sdk.advanced_memory import AdvancedMemoryServiceConfig
from trpc_agent_sdk.memory import AdvancedMemoryService
from trpc_agent_sdk.runners import Runner
from trpc_agent_sdk.sessions import InMemorySessionService

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 本变更删除了 trpc_agent_sdk.advanced_memoryBaseSessionCompactManagerAdvancedSessionCompactManagerAdvancedCompactConfigsession_compact_manager= 接线,但本文件作为变更中新增的 SQL 示例脚本,第 17-19 行仍导入并实例化这些已删除 API(AdvancedMemoryServiceConfigAdvancedMemoryService(config=...)),与 agent.agent.create_agent 的新组装方式矛盾。

触发条件: 按 README 执行 python run_agent.py 时,import 阶段抛出 ModuleNotFoundError: No module named 'trpc_agent_sdk.advanced_memory'

实际影响: SQL 持久化示例(含 .envagent/ 子包、README 全文)全部成为不可运行的死示例;对本变更宣称的“advanced memory 服务化,支持 redis/sql”能力形成负向宣传。

修正方向: 移除脚本对 AdvancedMemoryService/advanced_memory 的依赖,改用新架构下 create_agent 的组合方式,或删除该示例目录。

Comment on lines +7 to +37
└── AdvancedSessionCompactManager
├── Session Memory
├── Tool Result Budget
├── History Snip
├── Microcompact
└── AutoCompact

AdvancedMemoryService
├── save_memory
├── read_memory
├── list_memory_index
└── long-term memory injection
```

1. `session-1` 连续输入多轮 Python 开发偏好。
2. 当累计上下文和工具调用达到配置阈值后,系统会提取 session memory,并写入
`session_memory.md`。
3. `session-1` 请求总结已经学习到的开发偏好。
4. `session-2` 查询长期记忆,验证不同 Session 共享同一个 `MEMORY/`。
不再使用独立的 Advanced SessionService。Session 的创建、Event 保存和状态管理始终
由标准 `InMemorySessionService`、`RedisSessionService` 或 `SqlSessionService`
负责;Advanced Compact 通过 `BaseSessionCompactManager` 生命周期接入。

## 使用方式
## 核心组装

```python
from pathlib import Path

from trpc_agent_sdk.memory import AdvancedMemoryConfig
from trpc_agent_sdk.sessions import AdvancedMemorySessionService
from trpc_agent_sdk.runners import Runner
config = AdvancedMemoryServiceConfig(
root_dir=Path(__file__).resolve().parent,
)

session_service = AdvancedMemorySessionService(
config=AdvancedMemoryConfig(
root_dir=Path(__file__).resolve().parent,
)
session_service = InMemorySessionService(
session_config=SessionServiceConfig(
store_historical_events=True,
),
session_compact_manager=AdvancedSessionCompactManager(
config=AdvancedCompactConfig(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 本变更删除了 AdvancedSessionCompactManagerBaseSessionCompactManagerAdvancedCompactConfigsession_compact_manager= 参数(最终在 4116be2 中整体移除),并将 run_agent.py 清空为 0 字节,但本 README 仍在第 7 行(类图)、第 23 行(“Advanced Compact 通过 BaseSessionCompactManager 生命周期接入”)、第 36-37 行(session_compact_manager=AdvancedSessionCompactManager(config=AdvancedCompactConfig()))展示这些不存在的 API,并指导用户“执行 python run_agent.py”,而该文件已为空。

触发条件: 任何用户按 README 组装代码或执行示例。

实际影响: 文档与实际 API 完全脱节,用户按文档操作会遇到 ImportErrorBaseSessionCompactManager/AdvancedSessionCompactManager 已不存在)和空脚本无输出,造成误导;也表明该示例目录是本次重构遗留的未清理残骸。

修正方向: 将 README 重写为新的标准 SessionService + AdvancedAutoCompactSummarizerFilter/AdvancedAutoCompactSummarizerManager 组合方式(参照 redis/sql 示例的 agent/agent.py),恢复或删除 run_agent.py

Comment on lines +264 to +283
async def update_session_state(
self,
session: Session,
state_delta: dict[str, Any],
) -> None:
"""Persist session-scoped state without replacing the caller's Event window."""
if not state_delta:
return
session.state.update(state_delta)

async with self._redis_storage.create_db_session() as redis_session:
key = session_key(session.app_name, session.user_id, session.id)
storage_session = await self._get_session(redis_session, key)
if not storage_session:
logger.warning(
"Session %s not found in Redis while updating state",
session.id,
)
return
storage_session.state.update(state_delta)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: update_session_state_redis_session_service.py 第 264-283 行)是非原子的读-改-写:先 GET 整个存储 session,在内存中 storage_session.state.update(state_delta),再 SET 整个 JSON 文档。该方法是本次变更新增的核心持久化路径,被 SessionMemoryExtractor._persist_checkpoint 在每个 compaction checkpoint 时调用;两个并发调用(如并发 SDK 进程或多个 Session 的 checkpoint)之间没有锁/事务保护,后写者覆盖先写者的 state 增量。

触发条件: 同一 session 上有两个并发 checkpoint 写(或多进程中同一 session 被并发访问)时,后完成的 SET 会以自己读到的旧 state 为基底写回,覆盖对方刚写入的字段。

实际影响: 部分 session.state 字段(如 _trpc_agent:summaryadvanced_memory_pending_request_context_fingerprint 或自定义 state)丢失,属于静默数据丢失而非报错;对比同变更中 SQL 实现已采用 SELECT ... FOR UPDATE 行锁,Redis 路径的一致性明显低于 SQL 路径。

修正方向: 改用事务内 WATCH key/MULTI + 条件写入,或将 state 变更做 merge 后仅写变更字段(如使用 HMSET/JSON.SET 定点更新 state 子文档)以减小覆盖窗口;至少为 Redis 路径补充并发写入测试(现有测试的 mock 是 GET/SET 直写,未覆盖此竞态)。

Comment on lines 242 to 252
def record_request_context(
self,
request: "LlmRequest",
ctx: "InvocationContext | None",
cls,
request: LlmRequest,
ctx: InvocationContext,
) -> None:
"""Stage the final request fingerprint for persistence on the response Event."""
session = getattr(ctx, "session", None) if ctx is not None else None
state = getattr(session, "state", None)
session = ctx.session
state = session.state
if isinstance(state, dict):
state["advanced_memory_pending_request_context_fingerprint"] = _request_static_fingerprint(request)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: TokenContextTracker.record_request_context_token_budget.py 第 242-252 行)每次在 before-model compaction 成功后向 session.state 写入 advanced_memory_pending_request_context_fingerprint,但全仓(trpc_agent_sdk/examples/tests/)没有任何读取该键的代码——SessionMemoryExtractor/_latest_usage_baseline 消费的是写在 Event custom_metadata 上的 advanced_memory_request_context_fingerprint_token_budget.py:178),且写入端只有测试里伪造的 event metadata,真正的响应 Event 从未写入该字段。

触发条件: 每次触发 auto-compact 的模型请求(_auto_compact.py:581 调用)都会执行该写入,且该 state 会随 update_session_state/update_session 持久化。

实际影响: 会话 state 中持续累积一个永远不被读取的指纹键,state(含 Redis/SQL 存储的完整 JSON)随对话轮次增长,且易让后续 Session Memory 读取方误以为该字段是受支持的契约;同时说明 usage-baseline 应对“模型与工具更新后指纹失效”的机制实际并未闭环。

修正方向: 删除 record_request_context 及其调用点,或将指纹写入响应 Event 的 custom_metadataadvanced_memory_request_context_fingerprint)以对应 _latest_usage_baseline 的读取逻辑,二选一并补充对应测试。

@CongkeChen CongkeChen closed this Sep 11, 2026
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

审查结论

通过

审查范围:f05797d9f9f..cacda11(6 个提交,"advanced memory 服务化:重构 memory 为 service,支持 local/redis/sql 存储,重写 Session Compact(compact_events 幂等、state 持久化、新 token budget)",110 文件、+6061/-6315,含 23 个测试文件)。

计划符合性:总体按计划完成——advanced memory 已与上下文解耦(AdvancedMemoryService.bind 只安装 LongTermMemoryContext+tools,返回未修改的 session_service);local/redis/sql 三个后端齐全,读写与 TTL 清理路径完整;compact_events 幂等(compaction_id 检查)、update_session_state 三个后端实现正确;storage_reference、_collision_safe_component 路径防护合理;Lua 释放锁脚本、CrossLoopLock、SQL 行锁等并发机制经核查正确。

主要风险(高置信、由本次变更引入):

  1. (MODERATE) 请求指纹元数据链路断裂:重构删除了旧 _session_service.py 中把 state 指纹写入事件 custom_metadata 的唯一 writer 且未补偿。record_request_context 每天写 session.state 但无消费方;_latest_usage_baseline 读取的事件 metadata key 无任何写入方(grep 证实仅测试手工构造)。后果:指纹校验分支永不执行、usage 基线机制失效,token 估算 100% 回退启发式 estimated,token 模式下压缩触发阈值被系统性低估。
  2. (MODERATE) BEFORE_MODEL 阻塞路径存在持久化缺口:AdvancedAutoCompactSummarizerFilter 的 _before 阻塞仅靠 is_continue=False 短路 model filter 链;end_invocation=True 在 SDK 全部代码中无任何消费者(含 runner、cancel、dsl、server)。被短路的阻塞响应不 append_event、不触发 _schedule_post_turn_processing;而阻塞路径的 extract_if_needed(force=False) 与新 update_session_state 会写回 state,在 SQL/Redis 后端压缩事件不会落库、被替换历史不进 historical_events,且后续 update_session 全量覆盖可丢先写回的 state。

测试充分性:新增 test_session_compact.py/test_token_budget.py 覆盖 compact_events、session memory 写 state、tool_result budget、usage 基线 fallback 单元路径,但未覆盖 AdvancedAutoCompactSummarizerFilter 的 BEFORE_MODEL 阻塞集成路径(end_invocation 语义、被短路事件不持久化)、指纹 metadata 端到端读写链路,以及 redis/sql 后端 extract+compact+update_session_state 组合。

门禁结论:无 SEVERE 级阻断缺陷,存在 2 个 MODERATE 问题;建议合入前修复 fingerprint metadata 链路(补 writer 或删死代码)并评估 BEFORE_MODEL 阻塞路径的持久化缺口。

发现的问题

中等

trpc_agent_sdk/sessions/compact/advanced/_token_budget.py:243-249

问题: 重构拆分 advanced memory 时删除了把请求指纹写入事件 custom_metadata 的唯一 writer。旧实现 trpc_agent_sdk/advanced_memory/_session_service.pyappend_event 会把 session.state 中的 advanced_memory_pending_request_context_fingerprint 转写到事件 custom_metadata["advanced_memory_request_context_fingerprint"];新代码中没有任何模块再写这两个 key(全仓库 grep 仅 _token_budget.py 自身与测试手工构造)。此处 record_request_context(第 243-249 行)仍每天向 session.state 写入指纹,但无任何消费方;_latest_usage_baseline 读取的元数据 key 也没有写入方,指纹过滤分支(recorded_fingerprint != static_fingerprint 时 continue)永不执行。

触发条件: 启用 token 模式(token_context_tracker.model_context_window_tokens 已配置)且事件带 usage_metadata 的会话。由于事件元数据 key 永远为空,_latest_usage_baseline 无法按请求上下文过滤 usage 基线,只能按内容指纹匹配。

实际影响: usage 基线机制整体失效:token 估算 100% 回退到启发式 estimated,token 模式下 auto_compact_threshold_tokens/blocking_threshold_tokens 被系统性低估,压缩触发过晚;且当系统指令/工具配置变化时本应失效的 usage 基线(_latest_usage_baseline 中的指纹比较试图防护的场景)不会失效。record_request_context 写入的 state key 成为无消费方的死状态,随会话状态永久累积。

修正方向: 二选一:恢复 writer——在绑定/事件落库路径(如 AdvancedMemoryService 或 SessionService 后置钩子)中将 state["advanced_memory_pending_request_context_fingerprint"] 写入事件 custom_metadata["advanced_memory_request_context_fingerprint"](参考旧 _session_service.py 实现);或移除 record_request_context_latest_usage_baseline 中的指纹分支并删除 state 写入,明确该能力已废弃。补充写→读两端到端的测试覆盖匹配/不匹配两条路径。

中等

trpc_agent_sdk/sessions/compact/advanced/_filters.py:54

问题: AdvancedAutoCompactSummarizerFilter._before(默认 compact_trigger=BEFORE_MODEL,也是官方示例 examples/memory_service_with_advanced_memory 唯一使用的配置)在阻断本次模型调用时设置 end_invocation=True(本行),但 end_invocation 在整个 SDK 中没有任何消费者(全仓库 grep 仅 _callback.py 两类回调过滤器设置它,runner、cancel、dsl、server、agents 均无读取方)。阻断实际只靠 rsp.is_continue=False 使 model filter 链短路;被短路的链不会把阻断响应事件 append_event 到 session service,也不会进入 runner 的 _schedule_post_turn_processing。而 _before 中紧接着调用的 extract_if_needed(force=False) 及随后的 update_session_state(InMemory/Redis/Sql 三个后端的新增覆写)仍会把 session memory 状态写回存储。

触发条件: 任意 compact 压力达到 blocker 阈值(token 模式下 request_tokens_before >= blocking_threshold_tokens 且连续失败数达 max_failures)使 create_session_summary_by_request 返回 blocked 响应的 BEFORE_MODEL 回合。

实际影响: 阻塞响应(如 ADVANCED_AUTOCOMPACT_BLOCKED_MESSAGE 或重放记录)不会持久化到会话事件流,调用方只看到被过滤掉的响应;同时坏局面被加固:每次 BEFORE_MODEL 会话都在 state 写 SESSION_MEMORY_STATE_KEY,但压缩永不落库——在 SQL/Redis 后端,被压缩删除的原始事件不会进入 historical_eventsupdate_session 全量写才持久化 events,阻塞时不走),且后续任意 update_session 全量覆盖会用内存态把先前 update_session_state 写回的 state 分层覆盖丢失,session memory 检查点与事件窗口在持久化后端不一致。

修正方向: 让阻塞路径走持久化链路:在 _before 中于 extract_if_needed 成功后通过 session_service 持久化阻塞事件(或保证调用方在短路后仍 append_event),并评估 create_session_summary_by_request 阻塞前先完成 update_session(session) 提交压缩窗口;同时为 end_invocation 补充真实消费者或移除该赋值以避免误导。

Comment on lines +243 to +249
cls,
request: LlmRequest,
ctx: InvocationContext,
) -> None:
"""Stage the final request fingerprint for persistence on the response Event."""
session = getattr(ctx, "session", None) if ctx is not None else None
state = getattr(session, "state", None)
session = ctx.session
state = session.state

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 重构拆分 advanced memory 时删除了把请求指纹写入事件 custom_metadata 的唯一 writer。旧实现 trpc_agent_sdk/advanced_memory/_session_service.pyappend_event 会把 session.state 中的 advanced_memory_pending_request_context_fingerprint 转写到事件 custom_metadata["advanced_memory_request_context_fingerprint"];新代码中没有任何模块再写这两个 key(全仓库 grep 仅 _token_budget.py 自身与测试手工构造)。此处 record_request_context(第 243-249 行)仍每天向 session.state 写入指纹,但无任何消费方;_latest_usage_baseline 读取的元数据 key 也没有写入方,指纹过滤分支(recorded_fingerprint != static_fingerprint 时 continue)永不执行。

触发条件: 启用 token 模式(token_context_tracker.model_context_window_tokens 已配置)且事件带 usage_metadata 的会话。由于事件元数据 key 永远为空,_latest_usage_baseline 无法按请求上下文过滤 usage 基线,只能按内容指纹匹配。

实际影响: usage 基线机制整体失效:token 估算 100% 回退到启发式 estimated,token 模式下 auto_compact_threshold_tokens/blocking_threshold_tokens 被系统性低估,压缩触发过晚;且当系统指令/工具配置变化时本应失效的 usage 基线(_latest_usage_baseline 中的指纹比较试图防护的场景)不会失效。record_request_context 写入的 state key 成为无消费方的死状态,随会话状态永久累积。

修正方向: 二选一:恢复 writer——在绑定/事件落库路径(如 AdvancedMemoryService 或 SessionService 后置钩子)中将 state["advanced_memory_pending_request_context_fingerprint"] 写入事件 custom_metadata["advanced_memory_request_context_fingerprint"](参考旧 _session_service.py 实现);或移除 record_request_context_latest_usage_baseline 中的指纹分支并删除 state 写入,明确该能力已废弃。补充写→读两端到端的测试覆盖匹配/不匹配两条路径。

)
if not result:
return None
invocation_ctx.end_invocation = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: AdvancedAutoCompactSummarizerFilter._before(默认 compact_trigger=BEFORE_MODEL,也是官方示例 examples/memory_service_with_advanced_memory 唯一使用的配置)在阻断本次模型调用时设置 end_invocation=True(本行),但 end_invocation 在整个 SDK 中没有任何消费者(全仓库 grep 仅 _callback.py 两类回调过滤器设置它,runner、cancel、dsl、server、agents 均无读取方)。阻断实际只靠 rsp.is_continue=False 使 model filter 链短路;被短路的链不会把阻断响应事件 append_event 到 session service,也不会进入 runner 的 _schedule_post_turn_processing。而 _before 中紧接着调用的 extract_if_needed(force=False) 及随后的 update_session_state(InMemory/Redis/Sql 三个后端的新增覆写)仍会把 session memory 状态写回存储。

触发条件: 任意 compact 压力达到 blocker 阈值(token 模式下 request_tokens_before >= blocking_threshold_tokens 且连续失败数达 max_failures)使 create_session_summary_by_request 返回 blocked 响应的 BEFORE_MODEL 回合。

实际影响: 阻塞响应(如 ADVANCED_AUTOCOMPACT_BLOCKED_MESSAGE 或重放记录)不会持久化到会话事件流,调用方只看到被过滤掉的响应;同时坏局面被加固:每次 BEFORE_MODEL 会话都在 state 写 SESSION_MEMORY_STATE_KEY,但压缩永不落库——在 SQL/Redis 后端,被压缩删除的原始事件不会进入 historical_eventsupdate_session 全量写才持久化 events,阻塞时不走),且后续任意 update_session 全量覆盖会用内存态把先前 update_session_state 写回的 state 分层覆盖丢失,session memory 检查点与事件窗口在持久化后端不一致。

修正方向: 让阻塞路径走持久化链路:在 _before 中于 extract_if_needed 成功后通过 session_service 持久化阻塞事件(或保证调用方在短路后仍 append_event),并评估 create_session_summary_by_request 阻塞前先完成 update_session(session) 提交压缩窗口;同时为 end_invocation 补充真实消费者或移除该赋值以避免误导。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants