关于 inspeximus: agent memory with provenance, GDPR erasure and a tamper-evident audit trail
inspeximus 是面向 AI Agent 的长期记忆库及 MCP 服务,适用于需要修正记忆并保留审计依据的开发者。它以签名哈希链记录操作,并支持可验证的主体删除证明;证明范围仅限此存储。
主要功能
- 01按键更正并退役旧值
- 02恢复更正并记录决策
- 03主体删除及可验证证明
- 04签名哈希链操作日志
- 05适配多种 Agent 框架
适用场景
- 01避免已更正事实重新成为当前值
- 02回应主体删除请求并提供证明
- 03核查 Agent 行动时保存的记忆
- 04为 Agent 集成持久记忆
MCP 相关
MCP 服务向 Claude Code、Cursor、Windsurf、Codex 和 Cline 等客户端提供记忆能力。README 也列出 LangChain、LangGraph、ADK 等框架的适配器。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx inspeximusINSPEXIMUS_PATHPath to the JSON memory file the server persists to (defaults to ./inspeximus_memory.json).INSPEXIMUS_ECHO_GUARD1 (default) blocks a restated retired value from resurrecting a corrected fact; 0 = legacy keyed supersession.INSPEXIMUS_EMBED_URLOptional OpenAI-compatible /embeddings endpoint for SEMANTIC recall (e.g. http://localhost:11434/v1/embeddings for local Ollama). Unset = deterministic lexical recall.INSPEXIMUS_EMBED_MODELEmbedding model id used with INSPEXIMUS_EMBED_URL (default: text-embedding-3-small; for Ollama use e.g. nomic-embed-text).本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
135收录的工具列表 · 更新于 今天
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| key | any | 否 | |
| tags | any | 否 | |
| text | string | 是 | |
| mtype | any | 否 | |
| value | number | 否 | |
| object | any | 否 | |
| source | string | 否 | |
| user_id | any | 否 | |
| agent_id | any | 否 | |
| reaffirm | boolean | 否 | |
| session_id | any | 否 | |
| derived_from | any | 否 |
详细介绍
inspeximus
为 AI Agent 提供防篡改的长期记忆。只需更正一次事实,旧值就会永久停用;删除某个人并提供证明;向审计员展示 Agent 采取行动时掌握的信息。仅需一个零依赖的 Python 文件,另附 MCP 服务。
删除证明仅适用于此存储,不涵盖向量索引、提示词日志或备份;它是一种完整性原语,并非合规认证。
快速入门 · 文档 · 与 mem0 和 Graphiti 对比 · 从 mem0 迁移 · 欧盟《人工智能法案》与 GDPR 证据 · Claude Code,一行命令 · 透明度日志 · PyPI
pip install "inspeximus[crypto]"
inspeximus demo # a first result: offline, touches nothing of yours
from inspeximus import Inspeximus
m = Inspeximus("memory.json")
m.remember("The staging database is db-3.internal", key="staging-db")
m.remember("The staging database is db-7.internal", key="staging-db") # a correction
m.recall("which staging database")[0]["text"] # 'The staging database is db-7.internal'
m.revert("staging-db") # and it is reversible, on purpose
memory.json 只是一个名称,并非文件格式:新建的存储是一个 SQLite 文件。请通过 inspeximus 或
sqlite3 模块读取,切勿使用 json.load 或 cat。
为什么选择 inspeximus
当 Agent 会连续运行数天,而它持有的事实可能在运行过程中发生变化,并且日后有人可能会询问它当时掌握了什么、删除了什么,就可以使用它。这就是全部的设计目标。
| 你的情况 | 可使用 |
|---|---|
| Agent 一直信誓旦旦地重复你已经更正过的值 | remember(key=...):更正优先生效,重述不会让旧值重新生效,revert() 是一项记录在案的决定 |
| 收到删除权请求,或审计员询问 Agent 采取行动时掌握了什么 | 使用 forget_subject() 和 erasure_certificate();以及带有 matches() 的签名行动账本 |
| 使用 Claude Code、Cursor、Windsurf、Codex 或 Cline,且会话之间无法保留记忆 | MCP 服务,只需一行配置 |
| 使用某个框架(LangChain、LangGraph、ADK、Hermes、Haystack),但无法证明记忆写入确实发生过 | 适配器和回执链 |
agno 并非我们开发。 它在自己的指南中提供了官方集成示例:cookbook/11_memory/integrations/inspeximus_integration.py,该示例于 2026-09-20 合并至 agno#10146,与 mem0、zep、memori 和 dakera 并列。他们自己的 README 将其描述为 “让更正持续生效的 inspeximus”。该文件所在的仓库并非由我们编写或维护,这正是值得列出它的原因:读者无需相信我们的话,就能自行核实这项集成。agmi 也同样如此。这是一份 Agent 记忆完整性评分卡,其维护者在亲自复现后,将我们的三行适配器 agmi/adapters/inspeximus_rows.py 合并到了 agmi#1。
如果你需要带控制面板的托管服务、针对文档的知识图谱,或在对话回忆基准测试中取得最高分,那它就不是合适的工具。mem0、Zep 和 cognee 在这些方面表现出色,比较页面 说明了各自的优势与不足。
实测结果
我们测量了其他系统没有公布的一项指标:更正后的事实再次出现的频率。
每个系统都使用各自的原生配置运行,执行相同任务,共进行 30 次试验:
| 系统 | 保留更正 | 旧值再次出现 |
|---|---|---|
| inspeximus | 100% | 0% |
| Graphiti 0.x (Neo4j + OpenAI) | 86.7% | 13.3% 95% CI [3.3, 26.7] |
| mem0 2.0.11 (OpenAI 原生) | 53.3% | 46.7% 95% CI [30.0, 63.3] |
| inspeximus,已禁用防护机制 | 0% | — 对照组:这就是防护机制的作用 |
每个系统 n = 30。mem0 测试版本为 2.0.11(2026-07);mem0 目前已更新至 2.0.18,我们尚未重新测试——这里注明具体版本,而非将结论重述为当前结论。完整方法、原始数组和可重新运行的测试工具:
RAMR · echo_resistance_backends_result.json
请正确解读 Graphiti 这一行——它的回声防护并未失效。 我们自己的原始输出记录显示,在回声运行前正确提取的 26 条更正中,
echo_attributable_flips: 0。Graphiti 的双时态失效机制保住了每一条。上面的 13.3% 是四条回声出现前的提取遗漏——更正根本没有进入图中——这与本表关注的故障类型不同。若以机制而非标题来表述:在可归因于回声的旧值复现方面,Graphiti 得分为 0%,与我们相同,因为它在写入时保留了取代关系链接。这里真正的发现是:这些系统的区别在于是否记录了该链接,而不在于由谁记录。
三秒内即可核验的两个数字,无需 API 密钥
于 2026-08-25 对 Hindsight 0.9.2(vectorize-io,21k stars)和 mem0 进行测试,各自使用原生配置,n=20。这两项测试完全不需要评判模型——它们直接读取原始召回载荷,因此结果不受模型阅读能力影响:
| inspeximus 2.21.0 | Hindsight 0.9.2 | mem0 | |
|---|---|---|---|
| 更正后,召回返回新值而不是旧值 | 20 / 20 | 0 / 20 | 1 / 20 |
| 相同内容写入两次——存储状态是否相同? | 逐字节相同 | 20 / 20 不同 | — |
| 完成此测试所需的模型调用次数 | 0 | 60 | 60 |
两个竞品都会返回更正后的值和已废弃的值,并将选择权留给调用方。这是一种合理的设计——双时态存储通过有效性标记同时返回旧值和新值,确实做到了坦诚——但这与我们的承诺不同,差别在于由谁负责消除歧义。
第一行可以免费验证。无需密钥、服务或网络:
git clone https://github.com/DanceNitra/inspeximus && cd inspeximus
python probes/integrity_bench_store_resolves.py --systems inspeximus
测试会在几毫秒内完成,并打印 store-resolved=1.00 (resolved=20 both=0 stale=0 neither=0, n=20)。
添加 ,mem0 或 ,hindsight 即可复现它们对应的列,并产生它们各自的提取器调用成本。
方法、注意事项,以及我们未胜出的项目。
最重要的是最后一行。关闭我们的防护机制后,得分就是 零——这说明这个数字反映的是机制本身,而不是基准测试对我们手下留情。
内置符合《欧盟人工智能法案》和 GDPR 的证据记录
每次写入、修正、删除和 Agent 操作都会留下带签名、哈希链式记录。第三方无需 API 密钥,即可离线验证。只有当读取者固定运营方的公钥并见证锚点时,签名才能证明签署者身份。证据现在即可导出;《欧盟人工智能法案》的高风险义务将于 2027 年 12 月 2 日(附件 III 系统)和 2028 年 8 月 2 日(嵌入受监管产品的系统)起适用,而 GDPR 第 17 条自 2018 年 5 月 25 日起已适用。
| 义务 | inspeximus 保存的内容 | 读取者如何检查 |
|---|---|---|
| 《欧盟人工智能法案》第 12 条,自动事件记录 | 带签名的操作账本,记录 Agent 执行操作时记忆存储中的内容;matches() 将保留的转录与其条目绑定 | inspeximus actions verify,离线验证 |
| 第 19 条,日志保留 | 仅追加的回执链,其链头存放在存储之外,因此会报告尾部截断 | verify_writes() |
| GDPR 第 17 条,删除权 | forget_subject() 会删除此存储中可归因于某个人的所有记录,包括继承了该信息的摘要,并留下不含内容且带签名的墓碑。事件日志中已删除记录的租户和 Agent ID,以及墓碑上的租户标记,均使用加盐 HMAC 进行假名化。盐值位于密钥目录中,因此可根据已知 ID 重新导出假名。一个标识符会保留:异议记录会在 <store>.objections.json 中保留主体的 ID,以便继续阻止新记录 | erasure_certificate(),无需私钥即可检查;固定 expected_pubkey 并见证锚点以确认作者身份 |
| GDPR 第 15 条和第 16 条,访问权和更正权 | export_subject()、rectify(),并附有注明操作者和原因的回执 | 导出的清单哈希存放在账本中 |
| 第 26 条,部署者义务 | deployer_report,默认仅提供计数,报告中不含个人数据 | |
| 发生时间 | 来自第三方的 RFC 3161 时间戳;inspeximus timestamp qualified 会说明该机构在对应日期是否列入欧盟可信列表(eIDAS 第 41 条) | 离线可信列表缓存 |
| 提交给审计员 | 导出为 draft-sharif-agent-audit-trail-04 文件,并通过 action_detail 携带 Ed25519 签名 | 任何符合规范的验证器 |
inspeximus compliance 会按条款打印证据。完整映射及每一行的适用边界见**《欧盟人工智能法案》证据页面**和 docs/AI_ACT.md。范围一句话概括:在全新存储(3.12.0)上,inspeximus coverage 会逐条列出已覆盖的 36 项适用提供者和部署者义务;认证是由其他方单独执行的行为。
对于评估人员要审阅的文档,intake_form() 会请你一次性填写仅运营方能够填写的 66 个字段,之后 assessor_pack()(3.7.0)会生成附件 IV、附有 DPIA 和 FRIA 附录的部署者报告、附件 VIII 导出文件以及审计包。只要还有字段未答复,它就不会声称内容已完整。
至关重要的 30 秒
每个记忆库都能存储和检索。没人回答的问题是:存储的事实被证明错误时,会发生什么?
from inspeximus import Inspeximus
m = Inspeximus("correction.json")
m.remember("The staging database is db-3.internal", key="staging-db")
m.remember("The staging database is db-7.internal", key="staging-db") # a correction
m.recall("which staging database")[0]["text"]
# 'The staging database is db-7.internal' <- the correction wins, every time
m.revert("staging-db") # and it is reversible
m.recall("which staging database")[0]["text"]
# 'The staging database is db-3.internal'
没有嵌入漂移,也没有“LLM 通常会选更新的那个”。旧值会按键退役,而退役本身是一条可审计、可撤销、可验证的记录。
再说一次旧值,它也不会回来。 这是近期优先规则做不到的事:在同一个键下,第三次写入 db-3,当前值仍然是 db-7。回退是你有意做出的决定,要使用 remember(..., reaffirm=True)——防护机制不会自行撤销取代关系。
按键写入后,读取 m.last_write["blocked"];或者传入 raise_on_block=True,这样防护机制保留旧值时会抛出 WriteBlocked 错误,而不是返回 ID。
这个限制源于按键机制: 未指定键的语句是一个新事实,而不是更正,因此不受防护机制约束。如果你的流水线在重新摄取过时文档时不使用键,那么这段文本就会凭自身价值参与竞争。这两种行为都在
probes/does_a_restatement_take_the_key_back.py 中进行了测量,该脚本可在一秒内离线运行。
选择启用权威度,较弱的来源就无法覆盖较强的来源。 默认情况下,较晚的按键写入会胜出。使用 Inspeximus(path, supersession="authority") 时,如果按键写入的
source={"doc": ..., "authority": 0.3} 权威度低于当前值,该写入会在到达时退役,当前值保持不变;判定结果会记录在记录中,也会写入 m.last_write。摘要会通过 derived_from 继承其最弱父项的权威度,因此以完整权威度重述谣言也无法洗白它。只有双方都声明了权威度时,权威度才会参与判定;因此,在现有存储上启用此功能不会带来任何变化,直到写入方开始声明权威度。在 MemTX 语料库上重放存储数据时,默认模式在 318 个案例中有 278 个提供了带标签的信念,而权威度模式在 318 个案例中有 307 个提供了带标签的信念,没有任何案例出现反向结果。大多数过时写入案例已被回声防护机制捕获;它会让重述的旧值退役,无论其权威度如何。权威度机制新增的作用是阻止较弱来源写入该键从未持有过的值。它仍未捕获的 11 个案例是权威度相同的写入方之间的更新丢失,这不是任何权威度规则所能裁定的;而且有一种情况值得了解:系统以完整权威度写入的事实,永远无法由权威度较低的 Agent 更正。这两点都写在 _supersede_by_key 的文档字符串中。
在一项我们未编写的基准测试上(MemTX,318 个可重放案例),默认模式在 318 个案例中有 278 个提供了带标签的信念,而 supersession="authority" 在 318 个案例中有 307 个。
第一个数字已经包含回声防护机制;该机制在 1.87.0 版本发布时就已上线,早于任何人在此语料库上对它进行测量。
当有人要求你证明时
启用收据后,每次写入都会加入哈希链。仅凭值本身,无法判断是否有人在库背后修改了文件。哈希链可以。
from inspeximus import Inspeximus
m = Inspeximus("receipts.json", receipts=True)
m.remember("The staging database is db-3.internal", key="staging-db")
m.remember("The staging database is db-7.internal", key="staging-db")
m.verify_writes()[0] # nothing has been touched yet
# True
# now somebody edits the store directly, turning db-7 into db-9
from inspeximus import sqlite_store
items = sqlite_store.load("receipts.json")
before = sqlite_store.snapshot(items)
edited = next(r for r in items if "db-7" in r["text"])
edited["text"] = edited["text"].replace("db-7", "db-9")
sqlite_store.save("receipts.json", items, before)
Inspeximus("receipts.json", receipts=True).verify_writes()[1][0].split(": ", 1)[1]
# 'its TEXT or KEY no longer matches its write receipt (edited after write)'
如果存储中已有不带哈希链的记录,或者哈希链是从中途开始的,一次调用即可处理。哈希链未涵盖的每条记录,都会根据其当前状态生成一份收据;收据的哈希中会标记 backfill,并包含该批次的 Merkle 根。从这次调用开始,任何编辑都会像其他编辑一样导致 verify_writes() 失败;此前发生的情况,之后生成的收据都无法追溯。
from inspeximus import Inspeximus
m = Inspeximus("older.json") # a store written with receipts off
m.remember("The on-call rota is in the wiki", key="on-call")
m.verify_writes()[1][0].split(":", 1)[0]
# 'write receipts are DISABLED'
m.enable_receipts()["anchored_records"]
# 1
m.verify_writes()[0]
# True
也可以在 shell 中运行:inspeximus receipts enable --backfill。3.0.0 版本的 CHANGELOG 条目包含了我们对自有存储进行的测量结果。
不再适用的键应该用 retire 结束,而不是写入占位值:按键写入会替换当前值,因此占位值会成为该键新的活动值。retire 不会留下活动值,会在 history(key) 中保留所有值及其原因,并在收据链中声明自身。
from inspeximus import Inspeximus
m = Inspeximus("rota.json")
m.remember("The on-call rota is in the wiki", key="on-call")
m.retire("on-call", "the rota moved to the pager tool")
m.current("on-call")
# None
m.history("on-call")[0]["reason"]
# 'the rota moved to the pager tool'
多个进程,一个存储库
行写入器会在写入行的同一事务中,向 memory_events 追加一条不含内容的行,因此另一个进程可以按 seq 追踪该表,并在下一次调用时看到一次提交,无需重新加载,也无需消息代理。current(key) 会通过以(租户、Agent、key)为键的 L1 缓存响应重复读取,因此,一个 Agent 预热的缓存命中绝不会被提供给另一个 Agent。
擦除记录时,会在执行删除的同一事务中,从其日志行中移除记录的 key(这些行会保留,并标记为 key_redacted),因此,以某个人命名的键不会在擦除后继续留存。
from inspeximus import Inspeximus
lead = Inspeximus("crew.json")
worker = Inspeximus("crew.json")
tip = worker.events_tip()
lead.remember("The plan is: ship on Friday", key="plan")
lead.publish_event("plan.updated", {"to": "worker"}, agent_id="lead")
[e["type"] for e in worker.poll_events(since_seq=tip)]
# ['record.added', 'plan.updated']
worker.current("plan")["text"]
# 'The plan is: ship on Friday'
Agent 的操作与其当时掌握的信息绑定
审计追踪工具会为 Agent 的操作签名。操作账本也会这样做,并将每项操作与 Agent 在当时持有的记忆绑定:存储库的状态摘要,以及上次召回返回的 ID。如果某条事实在两次操作之间被更正,这两次操作就会有不同的摘要,因此读者可以从链中看出,第一次操作基于旧值执行,第二次基于新值执行,而不必依赖任何人的说法。
from inspeximus import Inspeximus
from inspeximus.actions import ActionLedger
m = Inspeximus("deploys.json", receipts=True)
m.remember("The staging database is db-3.internal", key="staging-db")
led = ActionLedger(m, actor="deploy-agent")
target = m.recall("staging database")[0]["text"]
with led.action("tool:deploy", inputs={"target": target}) as a:
a.output({"deployed_to": target})
m.remember("The staging database is db-7.internal", key="staging-db") # the correction
target = m.recall("staging database")[0]["text"]
with led.action("tool:deploy", inputs={"target": target}) as a:
a.output({"deployed_to": target})
led.what_it_knew(0)["recalled_now"][0]["current"]["status"] # 'superseded'
led.what_it_knew(1)["recalled_now"][0]["current"]["status"] # 'active'
led.verify() # (True, [])
默认不含内容:输入和输出会以加盐的 SHA-256 摘要形式存储。保留了对话记录的操作员仍可调用 led.matches(seq, inputs=prompt, output=answer),确认模型收到的内容和返回的内容是否与之相符;只要有一个字符不同,结果就是否,而且此项检查需要盐文件,因此其他人无法运行。若存储库有收据密钥,则会使用该密钥签名。inspeximus actions verify 可离线检查文件;若存储库存在,它还会检查每个条目的 last_receipt 是否仍存在于记忆链中,从而从操作侧发现被改写的记忆历史。设置 INSPEXIMUS_ACTIONS=1 后,MCP 服务会记录每次工具调用;inspeximus.integrations.langchain.InspeximusActionCallback 会记录 LangChain 工具调用和模型调用。探针:probes/what_the_agent_knew_when_it_acted.py,三个篡改控制,均未通过。
另外两种事件类型也会共用这条链。led.oversight("override", "ops-lead", refers_to=3, reason=...) 会记录针对某项操作的人类决策,并注明作出决策的人或角色(《法案》第 14 条和 GDPR 第 22 条所要求的记录);引用必须能够解析,验证器会重新检查。led.disclosure("s1", "You are chatting with an AI assistant.", channel="web") 会按会话记录一条第 50 条披露。inspeximus.subject_rights.export_subject(m, "crm/alice", ledger=led) 会响应 GDPR 第 15 条访问请求,导出来源可解析至该主体的所有记录,其解析方式与 forget_subject 完全相同;rectify(m, key=..., text=..., actor=..., reason=..., ledger=led) 则是第 16 条更正,并生成注明请求者身份的收据。led.incident("...", "serious", "dpo", refers_to=[3, 4]) 会创建一条第 73 条记录,并从知悉事件时开始计算 15 天时限;incident_report(seq) 会生成报告框架。inspeximus compliance 会通过其验证器读取账本中的所有内容,并生成 22 项按条款标注的控制措施。
根据同一组证据会生成两份文档。inspeximus technical-documentation --out annex_iv.md 会生成第 IV 附件框架(第 11 条),依据存储库和账本中的证据填写可填充的章节,其中包含第 13(3)(f) 条使用说明,并将 24 个提供方字段标记为 OPERATOR INPUT REQUIRED。inspeximus deployer-report --out deployer.md 则生成部署方报告(第 26 条):已记录的监督、事件及第 73 条时限、最早保留日志条目的时长与六个月最低期限的对比(在日志达到该时长之前,会报告为尚无法测试)、披露内容,以及 GDPR 第 35(7) 条 DPIA 附录和引用该附录的第 27(1) 条 FRIA 附录。两份文档都会列出所有无法填写的字段。inspeximus registration-export --section A 会以相同方式写入欧盟数据库所需的第 VIII 附件字段(第 49 条)。
保存多年的账本会进行轮换,而不是截断。inspeximus actions archive --keep-days 400 会将较早的条目移入账本旁边的归档文件,并以一条带签名的检查点作为当前文件的开头,其中注明归档文件、其哈希值和归档的尾部;链条不会中断,actions verify 会沿检查点继续验证归档文件,而仅验证当前文件时,会将归档范围报告为未验证,而不是跳过它。inspeximus actions attest --policy-days 183 --actor dpo 会追加一条带签名的声明,说明账本所涵盖的最早条目,以及是否遵守了 Art. 19 和 Art. 26(6) 规定的六个月底线。inspeximus actions timestamp --url https://freetsa.org/tsr 会请求 RFC 3161 机构为尾部加盖时间戳,并将令牌链接进账本链,这样审计员就能获得第三方对其之前所有内容的时间证明,并使用 openssl ts -verify 进行验证。
inspeximus actions timeline --session s1
会从链中重建一个工作流,不包含内容:每个步骤都列出 Agent 持有的记忆摘要、模型、参与者,以及与之相关的监督或事件。inspeximus actions export-trail
会以 IETF draft-sharif-agent-audit-trail-04 格式写出账本,按 RFC 8785 进行哈希链式链接,供读取该格式的工具使用;--verify 会验证任何此类文件。inspeximus actions lifecycle substantial_modification --actor cto --note "..." 会记录 Art. 3(23) 所指的变更,该变更会终止系统依据 Art. 111(2) 享有的祖父条款豁免;lifecycle decommission --disposition erased 则会记录系统生命周期结束时如何处置记忆。
记忆可以按 Agent 和流程进行分区,这正是 CNIL 2026 年关于智能体 AI 的说明所要求的形式:inspeximus partitions open triage-2026-09-16 --kind context --max-age-days 1 --max-records 200
会打开一个范围,其写入内容都会带上标签;partitions sweep 会为每个打开的分区应用过期时间和容量上限,并添加墓碑;partitions close NAME --actor 会结束流程(context 分区会在关闭时擦除其记录)。partitions report 会显示当前已过期的内容,以及分区外存放了多少记忆。
写入存储的位置
你无需选择存储格式。新存储会以行的形式写入 SQLite 文件,无论文件名是什么:memory.json 并不是 JSON,json.load 无法读取它。此版本首次打开现有 JSON 存储时会将其转换:转换过程会重新读取写入的数据,并且只有在记录数和 ID 顺序都保持不变时才会继续;原文件会作为 memory.json.pre-rows.bak 保留在存储旁边。加密存储仍然是单个加密数据块,因为静态数据加密覆盖整个文件。
采用行存储,是因为过去每次写入都会重写整个文件,而且重写无法像行写入那样合并并发写入者的记录。
一次持久化写入,两种格式,每种进行三次独立试验,每次写入 30 条记录
(probes/one_write_two_formats_across_store_sizes.py):
| 存储中的记录数 | 整个文件 | 单行 | |
|---|---|---|---|
| 1,000 | 0.0075 s | 0.0071 s | 行写入快约 1.1 倍 |
| 10,000 | 0.0818 s | 0.0422 s | 行写入快约 1.9 倍 |
| 30,000 | 0.2334 s | 0.1292 s | 行写入快约 1.8 倍 |
差距取决于文件大小:随着文件增大,重写文件的成本会增加,而写入一行的成本不会,因此记录越多,收益越明显。应将最小的一行视为最不可靠的结果。在 1,000 条记录时,两者足够接近,以至于该探针的不同运行结果各有胜负;而在生成此表的那次运行中,三次试验里仍有一次结果相反。因此,探针会报告每次试验的结果,而不是平均值,并在趋势不稳定时明确说明。上表由探针写入的回执生成(tools/sync_store_format_table.py),因此它反映的是某次运行的测量结果,而不是我们的记忆。
在并发写入者的情况下,调用方如果丢弃存储自身抛出的 StoreChangedOnDisk 错误,而不是重试,那么在 48 个进程的最差试验中,只写入了 384 条记录中的 199 条;而行存储在测试的每种并发规模下,4 次试验均写入了所有记录。差距源于调用方,而非格式:按照错误提示进行重试后,整文件存储可以跟上(probes/what_a_concurrent_writer_is_told_against_what_the_store_keeps.py)。
请参阅 probes/twelve_writers_and_the_one_that_stopped_writing.py。这两个探测都会在运行它们的机器上重新测量整文件基线,而不是引用我们的数据,因此较慢的机器会报告较小的差距,而非给出虚假的结果。
升级前有两件事需要了解:
- **使用此版本写入的存储无法由 2.26.1 或更早版本读取。**这些版本会将文件解码为 UTF-8,并抛出
UnicodeDecodeError。如需回退,请将memory.json.pre-rows.bak重命名并覆盖存储,然后固定使用较早版本。 - 首次擦除时会删除回滚副本。
forget、forget_subject和forget_pii都会将其移除,因为未经请求由此库创建的副本,不应成为个人数据在收到删除请求后仍得以留存的地方。erasure_certificate()会按文件名报告该文件的处理结果,因此回滚窗口的结束会留下记录,而非悄无声息。如需保留该副本,请在<key home>/inspeximus/config.json中设置{"store": {"keep_conversion_backup": true}}(而非设置环境变量):此时证书会声明该备份未被擦除操作触及,这就是你需要接受的取舍。
INSPEXIMUS_STORE_FORMAT=json 会保留旧格式,供其他工具直接读取存储使用。自 3.17.0 起,它适用于新存储和已经是 JSON 格式的存储;对于行存储,该设置会被忽略,并在标准错误输出中打印一行提示。在 <key home>/inspeximus/config.json 中设置 {"store": {"format": "json"}},则会固定任何存储的格式。
provenance(key=...) 一次调用即可提供其余信息:该键曾持有的每个值及使其失效的策略、当前值的来源(包括通过摘要继承的污染)、记录是否仍与其回执承诺的内容相符,以及一个说明这些信息均无法证明什么的 limits 字段。擦除也以相同方式运作。forget_subject() 会硬删除可归因于某个人的所有记忆,包括通过谱系继承了这些记忆的摘要,并留下一个经过签名且不含内容的墓碑,让后续读取者能够分辨这是有意擦除,而非篡改。erasure_certificate() 可让第三方在没有私钥的情况下验证这一点。为证书签名的密钥属于操作者,因此要验证作者身份,第三方需固定 expected_pubkey 并见证锚点;verify_erasure_certificate(..., require_signed=True) 会拒绝未签名的证书。它只涵盖此存储,不包括向量索引、提示词日志或备份。
inspeximus compliance 会按条款标注并打印相同的证据,同时附上其自身的范围说明:列出的义务是 inspeximus coverage 所涵盖的义务,而非认证。
证明时钟的时间,以及它是否归任何人所有
系统中的每个时钟都归被审计的运营方所有,因此 timestamp.py 会改为从第三方获取 RFC 3161 令牌。根据 eIDAS 第 41 条,合格时间戳对其显示的时间具有可反驳的推定效力,而普通时间戳则没有。令牌本身不会说明你拿到的是哪一种。
inspeximus timestamp trusted-lists 会构建欧盟可信列表的离线缓存,而
inspeximus timestamp qualified <token> --trusted-list <cache> --when <the date it was made>
则会针对单个令牌给出判断。退出代码会区分合格、不合格和无法判定。
传入令牌生成时的日期,而不是今天的日期。合格状态会随时间授予和撤销: 在 25 个地区公布的 1477 项合格时间戳服务中,有 570 项(39%)曾同时处于合格和不合格状态。某项真实的奥地利服务仅因日期不同,就会根据同一张证书返回四种不同的答案。
它只报告成员资格,不做其他检查。它不会检查可信列表上的签名,不会说明令牌是否真实(verify_with_openssl 会进行此项检查,而且两项检查都必须通过),并且对于列表中最早记录之前的日期,它会回答 UNKNOWN,而不是“否”。
范围。 上述各行是 inspeximus coverage 按条款列出的职责。该法案对独立 Annex III 系统的高风险义务自 2027 年 12 月 2 日起适用,对嵌入受监管产品的系统则自 2028 年 8 月 2 日起适用;相关义务要求提供的证据(第 12 条的事件日志、第 19 条的留存、第 15 条的准确性和稳健性),正是存储目前已保留并可导出的内容。
docs/AI_ACT.md 将各项职责映射到存储,并标明映射的边界。
读者无需向你索取任何内容,就能自行检查的日志
只要你诚实,上述内容就都成立。但这些内容无法阻止你保留两份历史记录,并向不同读者展示各自合意的那一份,因为答案由你提供,记录也是你写的。
因此,tools/publish_static_log.py 会将日志写成普通文件:头部、COSE 密钥集、每个叶子哈希、每份收据、每条记录的文本,以及一个仅依赖标准库运行的 verify.py。读者下载四个文件,就能根据叶子本身检查 Merkle 根。这正是证书透明度的发展方向,而非绕过它的捷径:C2SP 的
static-ct-api 将日志作为可缓存文件提供,因为这种方式运行成本更低,也比 API 更难作假。
我们的日志位于 dancenitra.github.io/inspeximus/transparency。 每条记录都是本项目发布的一个数字,并附有它出现的句子以及可复现该数字的命令。
日志是否涵盖所有内容,是由你来检查的事,而不是我们声称的事;而这一段过去曾声称它涵盖所有内容。python tools/seed_claims_log.py --log transparency/claims.log --check 会将登记表与日志进行对比,并指出尚未记录的内容;它不需要密钥,CI 会在每次推送时运行它,因此一旦出现缺漏,你和我们就会同时看到。现在确实存在缺漏:四项声明已登记但尚未写入日志,因为追加记录需要签名密钥,而密钥并不在执行登记的地方。日志落后且如实说明这一点,正是此项工作的意义;日志落后却被描述为完整,正是它要防止的失败。
这里先说明静态日志无法做到的事,以免日后才发现:没有任何端点会通过 HTTP 接受登记。记录只能在签名密钥所在之处写入。如需实时端点,scrapi.py 提供 draft-ietf-scitt-scrapi-11,而 deploy/ 中包含容器映像。
见证者是你无法亲自运行的那一方
日志只能告诉你它内部一致。它无法告诉你别人看到的是否是同一份日志,运营者签多少次名也无法解决这个问题。只有记得先前某个头部的参与方才能发现日志被改写,而且只有当这份记忆存放在运营者无法触及的地方时才行。
inspeximus witness watch 就是这样的参与方。它获取一份自己并不运营的日志,从叶子节点重新计算根,而不是直接读取头部中的根;然后通过根据当前发布的叶子节点重建上次接受的头部,与之进行比较。判定结果包括 EXTENDS、FIRST_CONTACT(会明确说明它目前还不能证明任何事情)、FORK、ROLLBACK,以及表示日志自相矛盾的 MALFORMED。拒绝接受时不会更新记忆,因为如果见证者忘了刚刚发现的问题,下次面对改写后的日志就会报告 EXTENDS。
整个设置只需两个命令,而且首次运行不会让你作出任何承诺:
pip install inspeximus
inspeximus witness watch --url https://dancenitra.github.io/inspeximus-log/log --state witness.json
deploy/witness-template.yml 会在任意公开仓库中每天免费运行同一条命令。外部人士在这里最有帮助的做法,就是针对我们的日志运行一次;这不会让你作出任何承诺:你并非为任何条目的真实性背书,只是在记录今天展示给你的历史是否延续了之前展示给你的历史。
离线检查 Bitcoin 锚定
每个已发布的头部都用 OpenTimestamps 加盖时间戳,回执是一个 .ots 文件。通常可以用 ots 命令进行检查,但它会引入 python-bitcoinlib;在 Windows 上,该导入会通过 ctypes 查找 libssl,并在读取证明的任何字节之前崩溃。因此,检查功能已内置。
inspeximus ots upgrade <the .ots receipt> # which block covers it? asks the calendars
inspeximus ots verify <the stamped file> --upgrade --block-header <the 80-byte header as hex> # ANCHORED, or MISMATCH
你需要提供区块头,可以来自你自己的节点,也可以来自任意区块浏览器。这就是离线检查的关键:区块来源由你选择,你的数据不会离开这台机器。只有 --upgrade 会使用网络,而且它只是向日历询问一个日历已经持有的摘要。
退出代码:0 表示 ANCHORED,1 表示 MISMATCH,3 表示 PENDING 或 INCOMPLETE。只有日历承诺的证明属于 PENDING,既不是错误,也不算通过,所以它有自己的代码,而不会被归入这两者之一。已锚定的证明表示这些确切的字节在该区块被挖出之前就已存在。它并不表示其中任何内容属实。
如果判定结果是 MISMATCH,且你的文件来自 Windows 上的 git 检出,请查看验证程序打印的关于行尾的提示:回执是针对 LF 字节加盖时间戳的,转换后的副本与回执不同,但这并不意味着有人篡改了任何内容。
你用来核验日志的密钥,发布了两份
要核验我们托管的日志,你需要我们的验证密钥。从你正在核验的主机获取密钥,等于让主机告诉你如何核验它自己:能够重写日志的运营者,也能重写旁边的密钥,而每个签名仍然都会通过验证。因此,这里也发布了该密钥。
现在,两份副本都托管在同一个 GitHub 账户下:这份 README,以及日志的公开镜像。只有在工作流使用固定在该仓库中的密钥验证快照后,镜像才会发布快照。这让这两份副本能够检查是否发生了隐蔽分叉,但无法防范 GitHub 或我们。独立于两者的参考是原始公钥:
9fb780dd72894867c6dac8e140cc78d755617147d20cfd26bfe557190f65ac49,它也以纸质形式离线保存。
92.5.74.17.sslip.io/log+41dfe27a+AZ+3gN1yiUhnxtrI4UDMeNdVYXFH0gz9Jr/lVxkPZaxJ
该行的 SHA-256:1017ff229193fa867e1f73758df24e440ebfad1710e1af49e296a5fe4f00783c
同一行也通过
/log/checkpoint.vkey 提供。两者必须逐字节完全一致。 如果不一致,请勿信任其中任何一个,并提交 issue。41dfe27a 是 c2sp.org/signed-note 中的四字节密钥 ID,用于选择要尝试的密钥,并非安全边界。
diff <(curl -s https://dancenitra.github.io/inspeximus-log/log/checkpoint.vkey) <(curl -s https://raw.githubusercontent.com/DanceNitra/inspeximus/main/README.md |
sed -n '/checkpoint-vkey:begin/,/checkpoint-vkey:end/p' | sed -n '3p')
tools/check_published_key.py 会执行此比较,CI 也会每天运行它,因此密钥轮换不会悄悄造成两份副本分叉。两份副本将悄悄替换密钥的成本从一个系统上的一次写入,提高到了两个系统上的两次写入。但这并不能让我们值得信任,因为两份副本都由我们掌控。独立性来自见证者,它会记住我们无法触及的一个头部。
接下来的五分钟
上面的演示在 revert() 处结束。接下来说明如何使用它。
将它接入真正的 Agent。 无需额外接线:在输入时调用 remember,在输出时调用 recall。关键在于密钥,因为它能让之后的更正落在同一条事实记录上,而不是变成第二条记录。
from inspeximus import Inspeximus
m = Inspeximus("memory.json")
user_id, choice, user_question = "u-1", "dark mode", "what does this user prefer"
m.remember(f"user prefers {choice}", key=f"pref::{user_id}") # correcting later needs the key
context = [hit["text"] for hit in m.recall(user_question, k=5)]
print(context[0])
# user prefers dark mode
如果你使用框架,我们为 LangChain、LangGraph、LlamaIndex、CrewAI、AutoGen、Haystack、Google ADK、OpenAI Agents 和 Pydantic-AI 提供适配器,并附有一份台账,记录哪些已通过实时安装验证,哪些已记录为无法运行,而不是只摆一墙 Logo: docs/INTEGRATIONS.md。
按顺序完成这些示例。 它们无需密钥即可离线运行,每个示例都会打印它执行的操作:
01_basics.py | 记忆、回忆、更正,以及读取某个密钥的历史记录 |
02_correction_and_erasure.py | 将更正和擦除作为独立通道处理,它们本来就是独立的 |
03_semantic_recall.py | 使用你自己的嵌入器 |
06_gdpr_erasure_receipt.py | 向不信任你的人证明删除确实发生过 |
熟悉代码结构。 docs/CORE_MAP.md 列出了每个公共方法及其起始行号,内容由 AST 生成,并在 CI 中重新检查。
然后再决定是否相信其中任何内容,请使用 不必信任我们,也能核验我们下的两个命令。
每个 Agent 都能使用同一份记忆
pip install inspeximus
inspeximus install --all
install --all(3.14.0)会查找你已安装的每个 Agent:Claude Code(包括 Claude Desktop 的 Code 选项卡运行的副本,3.14.4)、Codex CLI、Gemini CLI、Antigravity、Cursor、Windsurf(Devin Desktop)、Devin CLI、Cline 和 Hermes Agent。它会在每个 Agent 中注册 MCP 服务,并将它们指向同一个用户级存储,这样,一个 Agent 记录的决策就会保存在其他所有 Agent 都能读取的存储中。
- 每个 Agent 如何回忆。 Claude Code 和 Hermes Agent 通过各自的钩子和记忆提供程序来回忆。Gemini CLI 和 Codex 会读取服务的指令,在每项任务开始时进行回忆。对于 Cursor、Windsurf、Devin、Cline 和 Antigravity,
--all会在 Agent 的规则文件中提供一行配置(--rules yes或--rules no)。 - 保留现有配置。 它会合并现有条目,并在更改任何文件前先复制该文件。如果你的 Agent 已指向两个不同的存储,它会停止并询问你。
- 明确告知是否成功。 安装结束时会显示一个五行信息块:版本和
ARMED或NOT ARMED、存储及其记录数量、已接入的 Agent、需要重启的应用,以及校验封印。inspeximus install --check会再次打印该信息块,根据存储重新计算校验封印,并在校验不匹配时以非零状态码退出。
Claude Desktop 在 macOS 上的路径遵循 Windows 布局,尚未在 Mac 上测量。各 Agent 的安装步骤:docs/install/index.md。
在 Claude Code 中使用(单行命令)
在 Claude Code 中,无需 pip,也无需配置文件:
/plugin marketplace add DanceNitra/inspeximus
/plugin install inspeximus@inspeximus
或者在 Shell 中先运行 pip install inspeximus:
inspeximus install --ide claude # also: cursor, windsurf, codex, cline
这两种方式都会接入一个包含 135 个工具和相同钩子的 MCP 服务。从下一个会话开始,你的 Agent 就会知道上一个 Agent 做过什么决定——无需编辑 CLAUDE.md,也无需重新解释:
- SessionStart 会注入仍然有效的决策
- PostToolUse 会按文件记录实际发生的情况
- PreToolUse 会在操作运行之前展示与该操作相关的决策
已在干净配置下,通过 Claude Code CLI 验证这两种接入方式,测试跨越两个会话。在 Windows 上 Claude 桌面应用的 Code 选项卡中,3.16.2 版本的实时会话会将钩子捕获的内容写入存储、在每条提示词中显示回忆区块,并提供 MCP 工具。Code 选项卡尚未在 macOS 上测量。
钩子耗时根据我们自己的项目存储副本测量,并记录在 CHANGELOG 中:在包含 67,165 条记录的存储中,读取耗时从 4.96 秒降至 0.41 秒(3.15.1);在包含 71,772 条记录的存储中,运行 --archive 后,提示词钩子的耗时中位数从 6.39 秒降至 1.82 秒(3.16.1)。
项目存储不断增长时
钩子会记录捕获的每条命令,因此项目存储每周会增长数千行,而每个提示词都会读取所有内容。--archive 会将旧的命令捕获记录从存储中移出,按月存入旁边的单个文件。不会删除任何内容。
python -P -m inspeximus.claude_code --archive --older-than 7 # what would move; writes nothing
python -P -m inspeximus.claude_code --archive --older-than 7 --apply # move it
- 默认情况下,Recall 只读取存储。
recall(..., include_archive=True)也会搜索归档文件,MCPrecall工具接受include_archive。 - 擦除操作会覆盖每个月份。
forget、forget_subject、forget_pii和--scrub-secrets也会擦除归档文件中的内容;如果无法检查某些文件,则会拒绝操作并列出这些文件。擦除证书会列出每个文件,以及是否已确认其中不存在被擦除的记录。 - 这些文件足够小,可以存放在 git 仓库中,但擦除操作无法覆盖 git 历史。除非传入
--allow-git-tracked,否则会拒绝使用位于 git 工作树内的存储。将归档文件提交到任何位置之前,请先运行--scrub-secrets。 - 此版本的一项限制:加密存储和固定为 JSON 格式的存储会被拒绝,因为归档文件是纯行存储。加密存储会保持完整。要归档固定为 JSON 格式的存储,请取消设置
INSPEXIMUS_STORE_FORMAT并打开一次,这会将其转换为行存储。 - 存储会记录归档日志包含的条目数以及最后一条记录的哈希,因此,如果日志被截断或单独替换,就会无法通过验证,并阻止擦除,直到日志恢复为止。这可以捕捉意外情况或单个被编辑的文件。编辑存储和日志的人可以让两者重新匹配。第二份头部副本位于存储目录之外;自 2.38.0 起,收据链的头部一直保存在那里(在配置中设置
guards.heads: false可同时关闭两者)。除非同一个人也能写入该位置,否则这份副本可以捕捉这种编辑;对于这种情况,写入收据(签名日志)是检查手段。
你将获得
将更正作为一等操作。 remember(key=...) 会让该键的先前值失效。revert(key) 会将其恢复。history(key) 会显示链条。所有操作都是确定性的,且都可审计。
可验证的擦除。 forget_subject() 会硬删除可归因于某个主体的每条记忆——包括沿谱系继承了该主体信息的摘要——并留下经过签名、不含内容的墓碑记录,因此之后的审计可以区分主动擦除和篡改。
适用于任何存储、会读取字节内容的删除检查。 delete() 返回成功,只能说明某一行已从索引中移除。它并不能说明该值已从磁盘上消失;而对于擦除义务来说,真正重要的正是这一点。scan_residue(root, values) 会在目录中搜索应该已被清除的值,并区分通常会被混为一谈的三种结果:LIVE(某个表仍在一行中保存该值)、UNRECLAIMED(字节仍然存在,但不在任何活动行中,因为存储引擎尚未重新使用该页;这是引擎特性,不是供应商缺陷)以及 PLAIN(日志、跟踪记录或备份文件中仍包含该值)。这并非 inspeximus 专有功能:将其指向向量数据库、SQLite 历史记录、JSONL 跟踪文件或其他库的数据目录,它都能针对相应部署给出结果。
residue_certificate() 会将此类扫描结果转换成其他人可以检查的文档。它会记录所读取每个文件的 SHA-256,因此第三方可以使用 verify_residue_certificate() 重新遍历同一目录,确认搜索覆盖了其声称检查的字节,并确认这些字节此后未发生变化。签名可识别扫描器,但不能证明发现的内容属实;真正使其成为证据的,是任何人都可以重新运行扫描。从 shell 运行:inspeximus residue --root DIR --value SECRET --cert-out cert.json,然后运行 inspeximus residue-verify cert.json --root DIR。
在将干净结果视为一切正常之前,请先了解其范围。匹配是字面且区分大小写的,因此,将值改为小写或重新调整空格后得到的副本会按设计被漏掉;扫描无法读取的文件会被报告,并使判定保持为否定,因为“干净”绝不能意味着“我们没检查那里”。这两项限制都会写入签名证书。
可核查的来源,而不只是存储来源。 check_sources() 会重新读取每条记录的来源,并返回
FRESH / DRIFTED / ORPHANED / UNCHECKABLE,以及四个刻意分开统计的覆盖率数字——因为 source 字段的填充率达到 98.3%,但可重新获取率只有 0.01%,这只是一个 schema,而非保证。(这两个数字是我们在自有生产存储上测得的。)
对当前状态的适用性。 evaluate_applicability() 回答的是一个不同于“这条记忆是否属实”的问题:它现在能否在此处驱动操作? 历史证据可能完全有效,却不再获得授权——分支已变更、策略已更改、租户不同、有效期已过。该功能实现了与供应商无关的 CML 契约;两个独立实现对其冻结测试样例的结果一致。
多租户隔离。 for_tenant("acme") 会为一个共享存储提供限定范围的视图,并将租户绑定到签名消息中,因此记录无法在租户之间转移后仍通过验证。
审计人员熟悉的格式中的审计轨迹。 哈希链可以证明记录未被修改,但无法告诉第三方是谁写入了记录、记录内容是什么,或写入时间——而检查你系统的人实际会问的正是这三件事。四项 IETF 标准为此提供了解答,inspeximus 无需依赖项即可生成全部四种文件:
| 你想证明的内容 | 文件 | 标准 |
|---|---|---|
| 这条记录在日志中 | 包含性收据 | RFC 9942(COSE Receipts) |
| 这是我说的,且内容与此有关 | 签名声明 | RFC 9943(SCITT) |
| 遵循这些已发布的规则 | 注册策略,作为日志本身的第 0 条记录 | RFC 9943 s5.1.1 |
| 第三方证明的此时间点 | RFC 3161 时间戳 | RFC 3161 |
pip install "inspeximus[crypto]"
from inspeximus import Inspeximus, new_receipt_keypair, verify_transparent_statement
secret, public = new_receipt_keypair()
m = Inspeximus("memory.json", receipts=True, receipt_key=secret)
m.remember("The staging database is db-7.internal", key="staging-db")
doc = m.transparent_statement(0, issuer="did:web:your-company.example")
# -> a COSE_Sign1 carrying your claim AND its inclusion proof, checkable by anyone
inspeximus.transparency.TransparencyService 会在其自身日志中发布的策略下注册其他方的声明,而 python -m inspeximus.scrapi 会通过 SCITT 客户端使用的 HTTP 接口提供该服务(draft-ietf-scitt-scrapi-11),因此这里没人编写的工具也能使用它。
签名并不能带来什么,我们会提前说明。 收据能证明某条记录包含在某个日志中,却无法证明你向他人展示的只有这一个日志;这需要独立见证者,因此 witnessed_head() 会收集 k-of-n 份联署签名,并将拒绝视为警报,而非错误。时间戳表示第三方在某个时间看到了某个摘要;令牌的完整验证交由 openssl ts -verify 处理,而不是手写实现,因为只回答“有效”的简易 CMS 解析器会放过真实验证器会拒绝的令牌。而且这些都不代表合规:没有任何法规要求使用签名账本。这是用于证明履行义务的证据质量,全文均以此方式表述。
零依赖项。 核心仅需一个文件:将 inspeximus/core.py 复制到任意位置,即可在未安装任何软件的情况下导入并运行。语义召回是可选的(embed=your_model);词法回退不需要任何依赖项。MCP 服务、加密功能和框架适配器是独立模块,均可按需启用。签名(Ed25519)需要 pip install "inspeximus[crypto]";基础包始终保持零依赖项。
适配
langchain · langgraph-store · llamaindex · haystack · autogen · pydantic-ai ·
google-adk · memoryagentbench · hermes-agent
对于 Hermes Agent,请将 inspeximus 安装到 Hermes 运行所用的 venv 中,而不是安装到 shell 的 Python
中(方法)。请使用 Hermes
自带的安装程序安装 Hermes:PyPI 软件包 hermes-agent 的版本是 0.19.0,此版本永远不会加载该提供程序。
14 个中有 14 个已通过针对当前上游版本的验证,记录为故障的有 0 个。 一天前,其中 3 个还无法正常工作,
列表也如实记录了这一点——正因如此,你才可以相信这里的说法:openai-agents 缺少 SDK 会进行类型检查的一个
属性;langgraph-checkpointer 下,存储的单写入者保护机制错误地拦截了此进程自己的线程;而 CrewAI 完全替换了
其存储协议,因此需要新增一个类,而非修复原有类。声明审核会从
docs/integration_conformance.json 读取这些计数,因此这里的说法不会与运行器
最近一次测量结果不符。
只列出成功项的“适配”列表,不过是展示商标墙。这里会告诉你哪个适配器会出问题,让你在基于它构建之前就心中有数。
测试方式
2,600+ 项测试,以及一道让这些测试值得信赖的变异测试门槛:植入 175 个缺陷,全部 175 个均被发现,无一漏过。 仅仅通过测试套件并不能证明什么;能够捕获每一个故意引入的问题,才有说服力。
本页面上的每个数字都已登记在 docs/CLAIMS.md 中,并附有重新计算该数字的准确命令。如果某个数字 与你运行得到的结果不一致,请向我们报告,这是我们希望收到的错误报告。
针对 agmi 一致性测试套件的五种静态数据攻击(篡改、截断、删除中间条目、
重排、伪造),按照攻击者的方式对存储背后的 SQLite 文件进行测试:开启凭据并提供密钥时,5 种攻击全部被检测到,
且每种情况都有明确指出原因的说明。即使攻击者还持有凭据旁路文件(对存储目录有写入权限便可获得),并删除了其所
删除的每条记录对应的凭据,所有攻击仍会被检测到:存储每写入一条凭据,都会将链的头部写入用户的配置主目录,该目录
位于存储目录之外;verify_writes() 会报告链比该头部更短。若攻击者也持有配置主目录并删除了头部,则5 种攻击中有
4 种仍会被检测到:删除中间条目仍会破坏签名链,截断末尾则不会;这种情况下需要一个保存在机器之外的 anchor()
以及 verify_consistency()。检测通过 verify_writes() 这一审计调用完成;无论记录是否被篡改,recall() 都会返回
该记录。默认情况下,凭据处于关闭状态,验证器会拒绝为存储作出任何担保,无论存储是否被篡改;这种情况计为“无法验证”,
而非检测到攻击。探测脚本:
probes/five_at_rest_attacks_on_the_store_with_receipts_off_and_on.py。
不必信任我们,也能检查我们
只需运行两条命令。两条都不需要 API 密钥、服务或我们的任何数据。
python claims_audit.py
四十秒。它会读取我们在 README、文档和网站中发布的每个数字,并报告每个数字是否已登记、其固定版本是否仍可解析,以及是否有已提交的命令能够重新计算该数字。最后,它会输出问题列表,或者只输出一行:
every published number is registered, every pin resolves, every command names a real file
这里故意不引用统计数字。在审计会读取的文件中引用审计自身的总数,会导致文档每次变更时总数也随之变化;本节的第一个草稿就这么做了,并发布了过时的数据。运行它,查看当前数字。
运行结果会显示:有几行标记为 WITHDRAWN。这些是我们发布过、后来却无法复现的数字;我们将它们连同反驳它们的探针一起留在登记册中,而不是删掉。基准测试表是对竞争者的声明;登记册是对我们自己的声明,而我们更希望你先检查后者。
python probes/integrity_bench_revert.py --systems inspeximus --judge local --n 5
免费、离线、确定性,而且会自行说明:本地评估器无法与上表中由 OpenAI 评判的数据相提并论。诚实的测量工具和能让结果显得更好看的工具,不应是同一个工具。
文档
| 项目网站 → | 导览:基准测试、MCP 接口、治理说明 |
| 与 mem0 和 Graphiti 的测量对比 | 完整的恢复表,包含对照组和真实适用范围 |
| Claude Code 设置 | 一行 MCP 安装命令,以及三个钩子的作用 |
| 长篇详解 | 每种机制、每项测量,以及失败的项目 |
| 完整 API | 每个方法,以及它旨在防止的故障 |
| 擦除与 GDPR | 派生摘要中的删除权及相关凭证;擦除页面展示了完整的真实运行过程 |
| 从 mem0 迁移 | inspeximus import-mem0 export.json:每条记忆对应一条记录,以用户为主体,以 mem0 的时间戳为事件时间,可安全重复运行;包含 API 映射,以及导入无法恢复的内容 |
| 审计追踪页面 | Agent 采取行动时掌握的信息:签名账本条目、转录匹配,以及 IETF 草案导出,以真实运行示例展示 |
| 欧盟 AI 法案证据页面 | 第 12 条日志记录和第 17 条擦除要求,以及它们与存储中现有内容的对应关系;对应说明见 docs/AI_ACT.md |
| MCP 工具 | 全部 134 个工具及其用途 |
| 声明账本 | 每个已发布的数字,以及重新计算该数字的命令 |
| core.py 映射 | 每个公共方法及其所在位置,由 AST 生成并在 CI 中检查 |
| 可运行示例 | 可运行的脚本,而非代码片段 |
| 框架适配器 | 哪些已通过实时安装验证,哪些被记录为已损坏 |
| 变更日志 | 更改内容及原因,包括我们出错之处 |
适用对象
你正在构建一个持续运行数周、而非数分钟的 Agent。它会学到一些东西,而后那些东西会发生变化——配置值、策略、某个人的偏好,或一条事实。让你付出代价的故障,不是 Agent 忘记了,而是它信心满满地记住了旧答案。
这正是这个库所要解决的问题,也是我们唯一用来进行自我基准测试的问题。这个领域的大多数演示展示的是写入,而这个演示展示的是撤回,因为 Agent 将因这项操作而受到评判。
名称
这个名称源自中世纪的特许状。国王、主教、修道院院长或市议会会以 inspeximus 开篇,
意为“我们已查验”,并全文复述一份较早的文书,以记录他们已审阅该文书,通常还会予以确认,
再为结果加盖印章,以便后来的读者核查。这证明副本忠实地对应原件,而非证明原件属实。这里
提供的也是同样的保证,provenance() 会在 limits 字段中说明这一点,而不会让你自行发现。
引用
已存档于 Zenodo,并提供与版本无关的 DOI — 10.5281/zenodo.21708778。 机器可读的元数据位于 CITATION.cff,因此 GitHub 的“引用此仓库”按钮 可直接为你提供 BibTeX 和 APA 格式。
采用 MIT 许可。由 Agora 构建。Agora 是一个自主研究 组织,会将失败的复现与成功的复现一同发布。
mcp-name: io.github.DanceNitra/inspeximus
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。