ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent记忆管理实战:记忆海关架构设计与代码实现

Agent记忆管理实战:记忆海关架构设计与代码实现 做Agent开发做得时间久了你会发现最头疼的往往不是模型选型也不是提示词怎么写而是记忆这件事。以一个多轮对话Agent为例刚开始聊得好好的几十轮之后开始前后矛盾问它三天前你给它交代过什么事项它丢三落四更麻烦的是用户随口提过的手机号、银行卡尾号、项目代码片段统统被塞进长期记忆下次对话时又原样出现在上下文里。这就是典型的记忆失控。我一直在用自己手搓的Agent项目做实验尝试过各种记忆方案后沉淀出一套个人比较认可的架构记忆海关。简单说就是在记忆进入Agent的“身体”之前先过一道关卡完成三件事——用AST柔性扫描做内容体检用双池隔离把短期和长期记忆分开存放再用审计日志确保任何一次写操作都可以回滚追溯。这篇文章打算把整套方案的思路和核心代码拆开讲清楚适合正在做Agent应用、不想完全依赖现成框架、想从底层理解记忆链路的人。代码基于Python 3.10标准库在Python 3.14上跑也没有兼容问题。1. 为什么Agent需要“记忆海关”拆解核心设计思路1.1 记忆失控的三个典型症状先聊聊痛点。我在实际项目里观察到的Agent记忆问题基本逃不出下面三种。第一种是记忆污染。临时性的工具返回、闲聊废话、不完整的中间推理结果全被写进了长期记忆。结果就是用户最关心的那几条关键信息被淹没在噪音里检索时怎么排都排不到前面Agent的回答越来越“散”。第二种是敏感信息泄漏。很多人没意识到Agent的记忆和普通数据库不一样它不是存储后“躺”在库里而是会被定期加载进上下文喂给大模型。一旦用户说过密码、验证码、API Key、身份证这类信息它们就会反复进入模型视野。先不说合规问题单从产品安全角度就够让人睡不着的。第三种是事实漂移。同一个项目指标星期一是1.3星期二的对话里变成了1.8两条记忆都在库中新旧版本混乱Agent只能随机挑一条用。更糟糕的是你根本查不到这个错误是什么时候写进去的。这三个问题指向同一个根源记忆链路缺少一个“入关检查分类存放操作留痕”的治理层。这也是我做记忆海关的初衷。1.2 海关架构的三个模块怎么协同整套系统拆成三个模块对应入境海关的三个环节AST柔性扫描相当于安检员。记忆要入库先过扫描识别敏感字段、危险代码、低质量内容并打上标签。双池隔离相当于分流通道。通过安检的行李被分到不同的仓库短期工作池放临时物品长期价值池放重要物品。可回滚审计相当于档案室。每一件入关物品都有记录出了差错可以查原始单据一键退回之前的状态。三个模块是串行关系。记忆先进入扫描器产生一份扫描报告海关根据报告内容决定写入哪个池子每次写入、晋升、降级、删除都会生成一条审计记录。这个顺序很重要顺序反了就会出现“先入库后安检”的漏洞违规内容已经躺在库里了才被拦下来。1.3 一点设计原则这半年反复调整我总结出三条在设计上应该始终坚持的原则。先检查后入库。所有记忆写入操作都必须经过扫描器不能因为图快就跳过。哪怕你认为内容百分之百安全也走一遍流程因为规则是不断增补的。分层不混存。短期和长期记忆在物理上隔离短期池更像高速公路吞吐大但容量有限长期池更像档案馆写入门槛高、保存周期长。可回滚不覆盖。记忆不是“被更新”的而是“被新增版本”的。一旦现场被覆盖事后想查原始记录就没办法了。审计日志只追加不删除然后通过回滚机制把“现状”逆转成“历史态”。2. AST柔性扫描让记忆“入关”前先体检2.1 为什么选AST而不是正则很多人第一反应是给记忆内容做敏感检测拿正则匹配关键词不就结束了我以前也这么干后来发现正则的致命问题——它只能做线性匹配捕捉不了结构关系。举个例子你定义一条规则匹配“告诉我你的验证码”。但用户实际说的话可能是“验证码是多少我这边收到的短信里那个”。关键词“验证码”命中了但“用户的手机收到了一条验证码”这件事的整体语义正则抓不住。你只能用更多正则去堆最后变成一个长满if分支的怪物。AST的思路是不管输入是自然语言、代码片段还是结构化数据先解析成一棵树。扫描规则不再面对“字符串”而是面对“树的节点”。规则可以挂在任意节点类型上想要细粒度就往下钻想要泛化就往上提。这就是“柔性”的核心——规则的绑定位置是灵活的解析结构变化时只影响树的局部不会让整条规则链报废。2.2 把记忆变成一棵树实现上我并不直接调用Python的ast模块去解析中文对话那会变成一堆SyntaxError而是借用AST的核心设计——用树形结构去组织信息。先定义节点类型# ast_scanner.py from dataclasses import dataclass, field from typing import List, Optional dataclass class MemoryNode: node_type: str # root / block / sentence / entity / action / sensitive / code content: str # 原始文本片段 children: List[MemoryNode] field(default_factorylist) meta: dict field(default_factorydict) # 额外附带的标签比如置信度、来源然后写一个宽松的分层解析器。它对格式不做任何先验假设能识别多少就识别多少识别不了的整段退化成text节点保证不会因为“解析失败”把记忆拒之门外。class MemoryParser: 把记忆文本解析成 MemoryNode 树。风格刻意保持宽松——宁可少切不可切错。 def parse(self, text: str) - MemoryNode: root MemoryNode(node_typeroot, contenttext) # 1. 按轮次/段落切分 block blocks [b.strip() for b in text.split(\n\n) if b.strip()] for block_text in blocks: block_node MemoryNode(node_typeblock, contentblock_text) # 2. 初步识别代码块 sentences self._split_sentences(block_text) for sent in sentences: node MemoryNode(node_typesentence, contentsent) # 3. 附加实体/动作标记实际项目中可以接NER模型或规则 self._attach_entity_tags(node) root.children.append(node) return root这里有个容易被忽略的关键点我不追求一次解析把记忆里所有信息都结构化而是只做到“够规则用”。比如_attach_entity_tags里用正则标记出人名、商品名、金额、手机号候选然后让后续规则在树节点上复核。好处是扫描器的扩展成本极低——想加一个新种类的检测就多注册一条规则不用动解析器。2.3 扫描器和规则引擎扫描器本身非常薄核心就像一个事件分发器遍历树把节点分发给匹配的规则。class BaseRule: node_types: set set() def accept(self, node: MemoryNode) - bool: return node.node_type in self.node_types def handle(self, node: MemoryNode, report: ScanReport) - None: raise NotImplementedError class ScanReport: def __init__(self): self.hits [] self.risk_score 0.0 self.tags set() def add_hit(self, rule_name: str, node: MemoryNode, reason: str, risk: float): self.hits.append({ rule: rule_name, content: node.content, reason: reason, risk: risk, }) self.risk_score risk self.tags.add(rule_name) def is_blocked(self, threshold: float 0.8) - bool: return self.risk_score threshold写一条实际能用的敏感信息规则import re class SensitivePIIRule(BaseRule): 检测手机号、身份证、验证码。只负责在 sentence 节点上做标记。 注意这里的正则只是候选标记真正拦截要结合上下文判断。 node_types {sentence, entity} PII_PATTERNS [ (r\b1[3-9]\d{9}\b, 手机号, 0.9), (r\b\d{6}\b(?:.*?验证码)?, 验证码/短码, 0.7), (r\b\d{17}[\dXx]\b, 身份证号, 0.95), ] def handle(self, node, report): for pattern, label, risk in self.PII_PATTERNS: if re.search(pattern, node.content): report.add_hit(SensitivePIIRule, node, f疑似{label}, risk)扫描器本身就是一个遍历器class MemoryScanner: def __init__(self): self._rules [] def add_rule(self, rule: BaseRule): self._rules.append(rule) def scan(self, root: MemoryNode, text: str ) - ScanReport: report ScanReport() for rule in self._rules: for node in self._walk(root): if rule.accept(node): rule.handle(node, report) if text: report.text_preview text[:200] return report def _walk(self, node: MemoryNode): yield node for child in node.children: yield from self._walk(child)这套规则架构的“柔性”体现在哪儿我举个例子。一开始我只有敏感信息规则后来Agent项目里需要拦截“用户要求执行危险代码”的指令我不需要改扫描器只需要加一条规则class CodeExecutionRule(BaseRule): node_types {sentence, code} DANGEROUS {eval, exec, os.system, subprocess, pickle.loads} def handle(self, node, report): if node.node_type code: # 代码块里做简单AST解析 try: import ast as py_ast tree py_ast.parse(node.content) for item in ast_walk(tree): if isinstance(item, py_ast.Call) and getattr(item.func, id, ) in self.DANGEROUS: report.add_hit(CodeExecutionRule, node, f危险调用: {item.func.id}, 0.85) except SyntaxError: return else: for kw in [执行, 运行, 调用, eval]: if kw in node.content: report.add_hit(CodeExecutionRule, node, f疑似指令: {kw}, 0.4)到这里AST柔性扫描的基本盘就出来了。总结一下解析器负责把记忆变成灵活的树规则负责在树上自由绑定扫描器只做一个分发动作。规则之间互相独立不会因为新增规则影响到旧规则也不会因为某一次解析失败让整个链路崩溃。3. 双池隔离短期工作池与长期价值池3.1 为什么一定要分池我最初做记忆系统时是“一锅烩”——所有记忆都写进同一个池子检索时按相关度排序。运行一周后问题非常明显长期偏好和临时对话碎片混在一起临时信息因为时间新、频率高总是排在前面真正重要的用户画像反而沉到了底部。后来我改成“两仓制”。划分依据不是记忆的“来源”比如工具返回还是用户消息而是记忆的“生命周期”——这条记忆是给当前会话用的还是要长期陪伴Agent工作的。3.2 两个池子的定义先看一眼两个池子的定位差异维度短期工作池Working Pool长期价值池Vault Pool写入门槛低所有对话/工具返回都可写高必须通过“晋升评审”容量策略小默认200条溢出按LRU淘汰大按业务需要保留持久化频率低内存为主周期落盘高每次写入强制落盘读取优先级当前会话优先查长期任务/画像查询优先查审计粒度记录写操作即可记录写、晋升、降级、删除全链路核心差异是短期池允许“脏”早期你无法判断某条信息有没有价值先放进来几轮对话后根据使用情况再决定去留长期池不允许“脏”进去之前必须经过一轮评审。数据模型可以这样定义# memory_vault.py import json import time import uuid from dataclasses import dataclass, asdict from pathlib import Path from typing import Optional dataclass class MemoryItem: item_id: str pool: str # working / vault content: str source: str # user / tool / agent hit_count: int 0 created_at: float 0.0 last_access_at: float 0.0 class DualPoolVault: def __init__(self, working_store: Path, vault_store: Path): self._working_store working_store self._vault_store vault_store self._working: dict[str, MemoryItem] {} self._vault: dict[str, MemoryItem] {} self._load() def write_working(self, content: str, source: str) - MemoryItem: item MemoryItem( item_iduuid.uuid4().hex[:16], poolworking, contentcontent, sourcesource, created_attime.time(), last_access_attime.time(), ) self._working[item.item_id] item self._evict_if_needed() return item def promote(self, item_id: str) - Optional[MemoryItem]: 从工作池晋升到价值池。晋升前必须做深拷贝避免外部引用修改互相污染。 item self._working.get(item_id) if not item: return None new_item MemoryItem( item_iditem.item_id, poolvault, contentitem.content, # 字符串是不可变对象浅拷贝足够 sourceitem.source, created_atitem.created_at, last_access_attime.time(), ) self._vault[new_item.item_id] new_item return new_item def demote(self, item_id: str) - Optional[MemoryItem]: 从价值池降级回工作池常用于内存治理。 item self._vault.get(item_id) if not item: return None item.pool working self._working[item.item_id] item del self._vault[item.item_id] return item注意promote方法里的深拷贝处理这是双池隔离里一个特别容易踩的坑。如果直接把item对象塞进两个池子后续改工作池里的引用时价值池的数据也会跟着变——两个池明明想隔离最终却成了一对双胞胎那还不如不分池。3.3 转正与降级策略“什么样的记忆值得晋升”这是双池隔离的灵魂问题。我目前的规则是三元复合判断同一个用户实体用户ID或会话ID在三轮及以上的对话中被重复提及。这是稳定偏好的信号。用户明确表达了偏好或指令比如“以后都按XX方式处理”“我喜欢XX风格”。Agent在工具返回中拿到了结构化业务数据比如订单状态、配置参数、项目里程碑。晋升时机也很有讲究。我建议不要每轮对话都兴致勃勃地做晋升判断而是放在两个时间点对话轮次切换时以及Agent开始执行一个“重要任务”之前。前者用总结做沉淀后者用必要性做筛选。降级策略相对简单长期池里的记忆连续N次检索零命中我默认取5次就自动降级回工作池再观察一轮。如果降级后还是无人问津直接进入归档区不再参与上下文加载。3.4 隔离失效的经典翻车现场双池设计在纸面上很清晰但实际跑起来我发现三个特别容易翻车的地方。第一两个池子的存储路径靠得太近。有人图省事把working和vault放在同一个JSON文件里用字段区分结果一次清空脚本把文件删了两个池一起完蛋。物理隔离不是做做样子至少是独立文件最好是不同目录写脚本时也要分开处理。第二检索时只查长期池。有些开发者觉得“记忆都在长期池里”干脆不走工作池。结果就是当前会话的上下文完全无法被短期记忆关联聊到一半的临时状态全部丢失。正确做法是两级检索先查工作池拿到当前上下文再用长期池补全背景知识。第三晋升后旧记忆没有清除。promote方法里我只在vault里新增了一条没有把working里的旧引用删掉。这会造成同一条记忆在两个池子都存在检索时出现重复。我后来在promote调用处增加了删除逻辑如果你参照代码自己实现记得处理这一步。4. 可回滚审计每一次记忆操作都留痕4.1 审计日志的数据结构回滚审计的本质是一个不可篡改的操作账本。我在项目中用SQLite存审计日志每条记录包含这些字段CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, seq INTEGER NOT NULL, ts REAL NOT NULL, action TEXT NOT NULL, -- write_working / promote / demote / delete / rollback pool TEXT NOT NULL, item_id TEXT NOT NULL, content_snapshot TEXT NOT NULL, checksum TEXT NOT NULL, operator TEXT NOT NULL, reason TEXT );几个设计细节值得展开。content_snapshot存的是该条记忆的完整快照。可能有人会问为什么不只存diff理由是记忆项通常很小几百字以内全量快照的磁盘成本可以接受但回滚时的还原逻辑会简单得多。如果某天你真的需要存超大记忆块可以折中超过10KB的项只存diff摘要。checksum很重要它是防止审计链被篡改的关键。每次写入时计算sha256(content_snapshot str(prev_checksum))形成一条链式校验。回滚前先校验所有涉及记录的checksum一旦发现链路断裂立即中止回滚并告警。4.2 回滚的两种粒度我实现过两种回滚方式分别应对不同场景。全量时间点回滚给定目标时间点T把所有T之后发生的写操作按时间逆序撤销。这个操作通常用于上线事故或数据严重污染后发现需要快速恢复现场。class AuditJournal: def __init__(self, db_path: Path): self._conn sqlite3.connect(db_path) self._conn.execute( CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, seq INTEGER NOT NULL, ts REAL NOT NULL, action TEXT NOT NULL, pool TEXT NOT NULL, item_id TEXT NOT NULL, content_snapshot TEXT NOT NULL, checksum TEXT NOT NULL, operator TEXT NOT NULL, reason TEXT ) ) self._conn.commit() def append(self, action: str, pool: str, item_id: str, content: str, operator: str, reason: str ): seq self._next_seq() record (seq, time.time(), action, pool, item_id, content, self._checksum(content, seq), operator, reason) self._conn.execute( INSERT INTO audit_log (seq, ts, action, pool, item_id, content_snapshot, checksum, operator, reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?), record) self._conn.commit() def rollback_to(self, target_ts: float, vault: DualPoolVault, scanner_only: bool True) - int: 回滚到目标时间点。核心思路是逆序执行事件返回处理条数。 rows self._conn.execute( SELECT * FROM audit_log WHERE ts ? ORDER BY seq DESC, (target_ts,) ).fetchall() count 0 for row in rows: seq, ts, action, pool, item_id, snapshot, checksum, operator, reason row[1:] # 回滚前校验checksum完整性 expect self._checksum(snapshot, seq) if expect ! checksum: raise RuntimeError(f审计链校验失败: seq{seq}) self._apply_inverse(action, pool, item_id, snapshot, vault) self.append(rollback, pool, item_id, snapshot, system, frollback_to {target_ts:.2f}, undo {action}) count 1 return count靶向条件回滚不按时间回滚而是按条件只处理一批特定记忆。比如“回滚所有来源为tool的写入”或“撤销某一次promote操作”。实现方式是在上面SQL查询里加WHERE条件逆操作逻辑完全复用。4.3 逆操作怎么执行回滚的本质是把操作“反过来做一遍”。我在_apply_inverse里实现了四种逆操作原操作逆操作write_working从working池删除对应item_idpromote从vault池删除对应item_id并恢复working池原记录demote从working池删除对应item_id并恢复vault池原记录delete用快照重新插回原池这里有个容易出错的地方promote的逆操作不仅仅是把记忆从vault里删掉还要恢复working池的原记录。因为promote在业务层会从working池移除原条目如果逆操作里少了一步回滚后记忆就会“凭空消失”。回到第3章的代码去看我在promote实现里提到“新增了一条但没有删除working里的旧引用”你在自己的项目里一定要处理好这个对应关系否则回滚的操作链会断裂。4.4 审计与回滚的代价优化“代价”是我在真实项目里最关心的事情。审计没问题但如果每轮对话都同步写SQLite延迟直接爆炸。分享一下我的实测优化路径。同步写改成异步批量写。对话热路径只把审计记录压入内存队列后台消费者每500ms批量落盘一次。系统崩溃最多丢500ms日志对Agent场景来说可接受。快照要压缩。我加了gzip把content_snapshot压缩后存储。记忆文本的压缩率很高实测能省一半以上空间。回滚操作要加事务保护。整个回滚过程必须放在一个数据库事务里任何一步失败就整体回滚避免“回滚到一半系统处于中间态”的灾难。5. 实操演示从零手写一个最小可用的记忆海关5.1 模块划分为了让这套东西“真的能跑”我把前面所有代码拼成一个最小可用的项目。项目结构如下mem_customs/ ├── ast_scanner.py # MemoryNode、解析器、扫描器、规则 ├── memory_vault.py # MemoryItem、双池 ├── audit_trail.py # AuditJournal、回滚 ├── customs.py # MemoryCustoms 门面 └── demo.py # 演示入口门面类把三个模块串起来对外暴露两个核心方法entry(text, source)处理一条新的记忆写入rollback(ts)执行时间点回滚。# customs.py class MemoryCustoms: def __init__(self, scanner, vault, journal): self.scanner scanner self.vault vault self.journal journal def entry(self, text: str, source: str, operator: str user): # 一、入关扫描 parsed parser.parse(text) report self.scanner.scan(parsed, text) if report.is_blocked(threshold0.9): self.journal.append(blocked, none, -, text, operator, frisk{report.risk_score:.2f}, tags{report.tags}) return {decision: blocked, report: report} # 二、分流默认写工作池满足晋升条件时直接写价值池 item self.vault.write_working(text, source) self.journal.append(write_working, working, item.item_id, item.content, operator, frisk{report.risk_score:.2f}) if self._should_promote(report): self.vault.promote(item.item_id) self.journal.append(promote, vault, item.item_id, item.content, operator, auto-promote) return {decision: accepted, item_id: item.item_id, report: report}5.2 完整跑一遍演示流程demo.py里我模拟一个真实场景展示整套链路的效果。# demo.py def run_demo(): customs build_customs() # 用户闲聊 customs.entry(今天天气真不错午饭吃了牛肉面, user) # 用户留下手机号 customs.entry(我的手机号是13812345678有进展联系我, user) # 工具返回了一段代码 code import os\nos.system(ping 8.8.8.8) customs.entry(工具执行结果: code, tool) # 用户表达清晰偏好 customs.entry(以后生成报表都用条形图不要用饼图, user) # 重要业务事实 customs.entry(项目上线时间定为6月30日验收标准以需求文档V3为准, tool)执行过程里会发生三件值得注意的事第一第二条带手机号的记忆会被SensitivePIIRule命中risk达到0.9直接拒绝入池。审计日志里多一条blocked记录。第二工具返回的代码片段会被CodeExecutionRule标记为危险调用虽然不至于整个拦截因为Agent可能只是在分析代码但风险分被打到0.85这条记忆只会进工作池永远不会被晋升到价值池——哪怕它被引用了很多次。第三用户表达偏好和业务事实两条因为语义清晰、来源可信在_should_promote的判断下进入价值池。后续对话再检索时Agent只会加载价值池里的这两条不会把“吃了牛肉面”拉进上下文。回滚演示可以直接调用old_time time.time() customs.entry(临时任务A完成, tool) customs.entry(临时任务B完成, tool) customs.rollback(old_time)回滚后工作池和审计日志里多出两条rollback记录而记忆状态恢复到old_time之前的现场。如果你想直接验证可以打印vault._working和vault._vault里的内容对比。5.3 怎么接到现有Agent框架上很多场景下你不是从零开发Agent而是给LangChain、LlamaIndex这类框架加记忆治理。以LangChain为例最简单的接入方式是在Memory模块外面包一层。class CustomsMemoryWrapper: 包在 LangChain ConversationBufferMemory 外面的记忆海关代理。 def __init__(self, inner_memory, customs: MemoryCustoms): self.inner_memory inner_memory self.customs customs def save_context(self, inputs, outputs): # 写入前先过海关 for k, v in inputs.items(): if isinstance(v, str): decision self.customs.entry(v, user, operatorlangchain) if decision[decision] blocked: # 被拦截的内容可以脱敏后再存或直接不存 v self.customs.scrub(v) self.inner_memory.save_context(inputs, outputs)工具调用的接入点在after_tool_call这类hook里。核心思路是一致的任何要进入记忆系统的东西先过entry()再放行。这套设计不绑定具体框架你的接入成本主要就是找到框架里“记忆写入”的唯一入口然后做替换。6. 经验与避坑实战中用出来的几个教训6.1 常见问题速查表问题根因排查方法AST解析对话文本报SyntaxError直接把自然语言传给了Python的ast.parse不要把自然语言当代码解析用自定义MemoryParser做分层切分两个池子里出现同一条记忆promote后忘了清理工作池原记录在promote调用处补删除逻辑回滚后新写入的记忆也消失了回滚目标时间点过于靠前回滚前先确认目标时间点是否晚于要保留的记忆的写入时间审计日志以每天上百MB增长每条记录都存全量快照超过10KB的大块记忆只存diff日志定期归档压缩敏感规则误杀大量正常记忆阈值设太低比如0.5根据实际误杀率将阈值调到0.85-0.9之间并在blocked前加人工复核6.2 我踩过的性能坑性能问题集中出现在“高频写入”和“大文本扫描”两个场景。先聊高频写入。刚开始我把审计日志做成每轮对话同步写库结果一轮4轮对话的Agent任务光审计写入就耗时800ms严重影响体验。后来改成批量异步落盘单次对话开销降到10ms以内代价是系统异常退出最多丢最后半秒日志。对Agent记忆这种非金融级场景我认为完全可以接受。再说大文本扫描。当一份工具返回的结果有几十KB时全文解析规则扫描可能到几十毫秒级别。我的优化方法是分层策略先用轻量正则做“预扫描”只有预扫描命中敏感候选时才构建完整AST做深度结构分析。预扫描成本可以忽略不计深度分析虽然慢但它只在真正可疑的内容上触发整体平均耗时就被拉下来了。6.3 这套方案的边界与后续扩展说句实在话记忆海关不是万能的它解决的是“结构治理”问题也就是记忆该不该进、进哪个池、出了事怎么退。但它替代不了两件事一是语义去重两条意思相同但表述不同的记忆海关识别不了需要LLM或embedding去重工具配合二是记忆总结长期对话的原始记录不能永远原样保存该压缩的要压缩该提炼的要提炼。后续我计划在这套架构上叠加三个扩展一是接一个轻量LLM评分器对候选晋升的记忆做语义质量打分和规则评审形成双保险二是把审计中心独立成服务多个Agent共用一套审计账本实现跨会话的记忆行为追踪三是给长期池加记忆衰减调度器按真实业务生命周期而不是简单的LRU来淘汰记忆。说到底记忆治理不是一次做完的工程而是持续迭代的演进过程。以我现在项目的运行情况看最满意的不是某个具体模块有多强而是整套链路一旦出问题我能立刻定位到“哪条记忆在哪个时间点进入系统、经过了什么判断、最后被谁修改过”这种掌控感对手搓Agent项目来说是非常重要的定心丸。
返回列表