别让 Agent 的记忆越权:用 Verity 把检索权限前置到索引层
企业给 Agent 接上 CRM、工单、云盘和知识库后,最容易被低估的问题不是“能不能搜到”,而是“谁能搜到什么”。传统 RAG 往往先把资料切块、向量化,再让应用层在结果出来后做过滤;一旦调用方身份、组织关系或数据来源权限在链路中丢失,模型就可能在不该看见的上下文里生成答案。更麻烦的是,Agent 会把检索结果总结为新记忆:如果新记忆按写入 Agent 的宽权限保存,原本受限的资料可能通过摘要再次扩散。
Verity 是一个仍处于 v0.1 的 Apache-2.0 自托管项目,定位并不是通用“长期记忆”插件,而是带权限边界的共享上下文层。它把来源系统中的内容和访问控制列表一起镜像到本地存储,用权限图和查询前过滤约束检索,并通过 MCP 暴露给 Agent。这个思路特别适合客服、销售、运维等多角色 Agent 共用资料、却不能共享全部资料的场景。
把权限当成检索条件,而不是回答后的补丁
一个常见错误是:先做语义召回,再检查返回片段是否可见。这样不仅容易遗漏过滤分支,也会让“无权限数据是否进入召回候选集”变得难以解释。Verity 的核心路径反过来:它把调用方的权限范围编译为检索的强制预过滤条件;空权限范围应当只得到空结果,而不是退化成全库查询。
项目 README 将存储建模为双时态(bi-temporal)记忆:既保留事实何时有效,也记录系统何时知道这条事实。来源变更通过 webhook、CDC 或连接器进入写入路径,结构化更新以确定性的 keyed upsert 替换旧值,而不是依赖模型判断新旧内容。对“客户合同已经续期,但旧报价还被 Agent 引用”这类问题,时间维度和来源可追溯性同样重要。
权限边界还必须覆盖写入。若 Agent 从多条受限资料得出一条总结,正确的可见范围应是这些输入权限的交集,而不是写入者本身的最大权限。Verity 的 memory_remember 支持传入 derived_from,以便服务端按来源推导更窄的可见性;但这是调用方显式声明的谱系信息,并非自动推断。项目的 HONESTY 文档也明确指出:没有声明谱系的写入默认仍可能按写入者范围保存;需要更严格约束时,应启用 VERITY_REMEMBER_REQUIRE_LINEAGE=1 拒绝这类写入。
这条边界很关键:把“模型会不会遵守提示词”换成“服务端是否接受这次写入”。前者是行为期望,后者才是可测试的控制点。
最小可运行链路:本地服务、显式可见性、再接 MCP
Verity 的开发环境由 Docker Compose 拉起 Postgres/ParadeDB、SpiceDB 和 MinIO 等组件。官方 Quickstart 要求 Docker 约 8 GB 内存、约 20 GB 磁盘空间;首次从源码构建并下载本地嵌入模型可能需要约 15 分钟。不要把下面的命令当作生产部署清单:它用于在隔离环境先观察权限检索的行为。
git clone https://github.com/RunAlphaLoop/verity cd verity cargo run --release -p verity-cli -- dev cargo run --release -p verity-cli -- add ./docs --visibility 1 cargo run --release -p verity-cli -- query "what do we know about pricing?"
这里的重点不在命令本身,而在“--visibility 必填”的设计:文档没有来源 ACL 时,系统宁可要求操作者提供可见范围,也不应默认为所有人可读。验证阶段可以运行仓库提供的两 Agent 演示:一名位于嵌套组中的用户能召回共享文档,另一名不在组中的用户在提示注入尝试后仍应得到空结果。项目还提供 verity-bench srb,README 所述的结果是针对合成固定样本的 1,220 次对抗探针未发现跨实体泄漏;这是一套可复现实验,不是第三方生产审计,不能把它写成合规认证。
接入 Claude Code 等 MCP 客户端时,身份不要放进每次工具调用的参数中。Verity 的 MCP 进程从环境变量读取租户、主体和 Agent 标识,并提供 memory_open_scope、memory_recall、memory_get、memory_remember、memory_record_action 等工具。官方示例的形式如下:
claude mcp add verity \ -e VERITY_TENANT_ID=\ -e VERITY_PRINCIPALS=1 \ -e VERITY_ACTOR_SUB=user:you \ -e VERITY_ACTOR_AZP=agent:claude-code \ -- /path/to/target/release/verity-mcp
生产环境更值得关注的是主体解析模式:MCP 工具 schema 不让模型提交“我拥有哪些权限”,服务端从配置的 subject 及其组关系解析可见范围。这样即使提示词诱导模型换一个客户、换一个角色,工具调用也没有字段可用来扩大权限。
连接器不能只看“能连上”,还要看 ACL 的保真度
Verity 支持 Google Drive、Gmail、SharePoint/OneDrive、Slack、Zoom、HubSpot、Salesforce、Notion、Intercom 等来源,但它们不处于同一可信等级。Google Drive 与 Gmail 可使用来源的逐项 ACL;Slack 用频道成员关系作为可见范围;而 Notion 的公开 API 不提供逐页分享信息,只能使用管理员指定的可见性下限,因此会倾向于多隐藏,而不是假装有精确继承。
部署时应把这种差异写进接入决策。以 Slack 为例,官方连接器要求先通过 verity-cli connect slack 建立配置,再运行回填和增量同步:
export VERITY_URL=http://localhost:7717
export VERITY_API_KEY=${VERITY_API_KEY}
export VERITY_TENANT_ID=my-workspace
python -m verity_ingest.connectors.slack --backfill
python -m verity_ingest.connectors.slack --once
Slack 的可见性来自频道成员,因而不应附加一个自定义 --visibility 来覆盖它。对于 HubSpot、Salesforce、Notion、Intercom 这类没有逐记录受众或需要管理员定义范围的来源,应该先审阅各自的限制和过度隐藏行为,再决定是否允许进入 Agent 的自动化路径。
另一个现实边界是“权限正确”不等于“数据新鲜”。项目明确说明,来源连接器静默停止时,默认配置并不会自动阻止旧内容继续被召回;可通过 VERITY_SOURCE_FRESHNESS_MAX_SECS 或请求级最大陈旧时间启用来源新鲜度栅栏。该栅栏当前覆盖召回,不覆盖所有点读、简报和订阅路径。高风险业务应把连接器心跳、失败告警和回填策略视为安全设计的一部分,而不是运维附属项。
何时值得采用,何时先别上生产读路径
如果你的系统只有一个内部 Agent、所有资料同权限,先用简单的本地索引可能更轻。Verity 的价值出现在三个条件同时成立时:多个 Agent 或人机角色复用上下文;来源系统已有不同的权限边界;团队愿意为 ACL 同步、身份目录和可观测性付出运维成本。
它也不应被包装成已完成的企业级安全产品。项目自述没有托管服务、没有 SOC 2 等合规证明,服务核心仍是单节点;某些连接器仅做过夹具或一次开发验证。性能数据也不能只摘取“低于 50ms”的单点:官方说明在 100k chunks 的温缓存条件下,稠密/混合召回可低于 50ms p95;扩展到 1M chunks 后,会随 ACL 选择性和缓存状态上升。先用自己的数据规模、组织结构和故障注入场景复验,再决定是否放到敏感业务的读路径。
上线前可以把验证拆成四个可操作的问题。第一,使用两个真实但权限不同的测试主体,对同一客户、跨客户和已撤销授权的资料分别检索,记录“允许”和“拒绝”两侧的结果。第二,模拟来源同步停滞;确认新鲜度栅栏启用后,超过阈值的来源是否从召回中消失,同时监控是否出现误伤。第三,让 Agent 基于两份不同范围的资料写入总结,检查声明 derived_from 后的摘要是否只能被两者权限的交集读取。第四,故意省略谱系字段,验证严格模式是否拒绝写入。把这些用例接入持续集成,才能把权限模型从架构图变成可回归的行为。
还应区分两类失败:召回为空可能是“确实没有资料”,也可能是安全控制正确地拒绝了请求;连接器延迟则可能让系统返回旧事实,而不是越权事实。前者需要在工具响应、日志和运营界面中明确标示,避免开发者为了“修复空结果”而放宽过滤条件;后者需要通过来源心跳、陈旧度告警与人工回填处理。安全记忆系统的可用性,不是最大化答案数量,而是在权限、正确性和可解释性之间保留可观测的取舍。
真正值得借鉴的不是某个向量库组合,而是这套优先级:权限在检索前生效,派生记忆需要谱系,来源同步需要新鲜度边界,能力范围需要如实公开。对共享 Agent 记忆而言,这些约束比“再多一个记忆工具”更接近可长期维护的基础设施。