@ agent-custody/ 状态
人工智能特工的内存 不是向量商店:是一份事实分类账,每份书面都引用产生该分类账的收据、有效时间和交易时间,可以被取代或收回,而不丧失代理人在任何早期所相信的东西.
检索保留在您已经使用的商店里 。 这个包拥有出处,时间,和取消.
开始
npm install @agent-custody/state # or bun add, pnpm add
Npm上公布为 @agent-custody/state:编译有类型声明的JavaScript,节点22或后,Apache-2.0.
import { Ledger } from "@agent-custody/state";
// a JSONL file, a .sqlite path, or "postgres://…" with the pg package installed; the ledger is asynchronous
const ledger = new Ledger("./state/ledger.jsonl");
const a = await ledger.assert({ subject: "acct:42", predicate: "plan", value: "pro", space: "org", actor: "agent:support", source: { receiptId } });
await ledger.assert({ subject: "acct:42", predicate: "plan", value: "enterprise", space: "org", actor: "agent:sales", supersedes: a.fact.factId });
await ledger.retract({ factId: a.fact.factId, actor: "user:admin", reason: "poisoned by a tool result" });
await ledger.asOf({ validAt: "2026-09-01T00:00:00Z", txAt: "2026-09-01T00:00:00Z" });
可运行的例子,全部由测试套件执行. 10个支助回收点 纠正陈旧的效益,在错误计划事件后用经核实的证据补偿模拟信用. 09-修理机 在商店拿走了书写后丢失分类账附件, 然后找到并删除孤儿条目 memory.repair。 。 。 . 08-审查-控制台 通过网关进行退款,通过交易和客户找到,并输出独立核实的离线文件. 07-不完整证据 使例06的退款按照捕获规则:没有客户密钥的爆炸半径将退款列为INCOMPLETE,而不是漏出,用密钥找到. 06-行动与信仰 将内存和付款API放在一个网关后,在信仰的爆炸半径内找到退款. 05-blast-radius.ts (英语) 从一个倒退的信念 走到了一切依赖它. 04-evals.ts (英语) 把分类账和一家天真店 评为同样的记忆事件. 03-纪念-后门道.ts 运行内存服务器作为网关的上游. 01-引导器(ts) 穿过错误的文字和它的倒数. 02 - 收受者对信仰 与收据包一起运行整个循环:一个工具呼叫得到一张签名的收据,收据得到核实,从中提取的信念被记录在引用收据后,然后收回. 跟着他们跑 node examples/<file> 从此目录中, 在 bun run build 在寄存器根处.
对于真正的模型选择自己的工具,运行选入 代码支持恢复演示。无头编码器处理针对不正确的CRM内存提出的支持请求;然后操作员审查并批准恢复。 它使用您的代码登录和模拟计费。 普通的测试套房仍然离线.
内存服务器
分类账作为MCP工具,意在作为收款网关的上游运行. 在网关后,每个书写和读取都由Cedar政策检查并获得签名的收据,两件东西在呼叫中到达这个服务器 _meta 任何来电者都无法提供:收据id,成为事实的 source和签过字的代表团的代理人 成为事实 actor来电者自己对两者的主张都被忽视.
agent-custody-memory serve --ledger ./ledger.jsonl # over stdio; refuses calls that did not come through the gateway
在网关的配置中,内存服务器是上游,授予工具将内存工具命名为范围:
{ "upstream": { "command": "agent-custody-memory", "args": ["serve", "--ledger", "/abs/path/ledger.jsonl"] }, ... }
| 工具 | 已经 | 政策见 |
|---|---|---|
memory.write | 记录对空间的信念,可选地取代先前的事实 | context.args.space, (中文(简体) ), subject, (中文(简体) ), predicate, (中文(简体) ), value |
memory.read | 按空间、主题、上游、有效时间、交易时间分列的在某一时刻相信的事实 | 查询 |
memory.confirm | 解除隔离事实以证实;仅通过网关接受 | factId |
memory.retract | 消除一种信念,让过去的问题清晰可见 | factId, (中文(简体) ), reason |
memory.forget | 清除分类账和每个存储的值,保存摘要;收据是证书 | factId, (中文(简体) ), reason |
pack (CLI) (英语) | 一个事实的历史,收据, 持有,爆炸半径, 和忘了证书 一个签名的人工活性,可核查的离线 | |
memory.hold, (中文(简体) ), memory.release | 合法持有权:事实虽然合法,但不能被要求或扫荡所遗忘 | factId, (中文(简体) ), reason |
memory.sweep | 保留:忘记在瞬间、空间或所有空间里学到的东西,跳过掌握的事实,到达每个商店 | before, (中文(简体) ), space, (中文(简体) ), reason |
memory.repair | 找到分类账上没有事实的商店条目,在切出之前储存,一旦它们比切出大,就删除 写单和修理 | reason, (中文(简体) ), before (仅提前截止) |
memory.get | 任何州都用身份证明一个事实 作为网关的政策检查 | factId |
memory.history | 每一件触动事实的事件 包括孤儿的修补 | factId |
取出出检疫. 每个事实都有来源: claimed, (中文(简体) ), attested,或 verified穿过网关的文字 attested:其演员为在人签名的赠款中被指名的代理人并存在其收受. 以其他方式到达的文字 claimed,并且声称的事实被隔离: memory.read 让他们出去 除非打电话的人要求 includeClaimed,政策可以拒绝. memory.confirm,只通过网关接受,举出一个声称的事实来证明它自己的收货和交易时间,所以"这个事实是否在星期二仍然处于隔离状态"是可以回答的. 一个只有SDK的特工写下来的工具结果 不能成为其他舰队所相信的东西 除非得到证实的一方这么说 服务器有1个客户端;在下方的HTTP上共享,网关和直接写手输入一个分类账,检疫完成它的工作.
价值级隔离. 经认证的书写仍然带有代理商选择写的任何价值. 当代理商可以说出一个值来自哪里时,由网关自己的观察决定. A 类 memory.write 可能名称 evidence: { fact, path }事实上,这个网关通过它的事实调查机制(如CRM记录)和其中可选的字段来获取这个电话。 内存服务器将值与观测值进行比较:相等,事实写作 verified, 第三级出处, 演员和值均由代理商以外的东西担保; 不同, 写作被拒绝. 一项政策可能需要一个空间的证据(context.args has evidence),以及 memory.read 与 requireVerified 返回只返回已核实的事实。 测试套件在与内存服务器相同的网关后进行CRM查询.
对事实的政策正在改变. 网关可以通过其事实调查机制和 memory.get 然后政策将事实的空间、行为者和来源视为观察到的事实。 这是信任等级的后半部分:在Org空间中自报的音符可以被赠款允许的任何人所取代,而一个经证实的Org事实不能被转移或收回,除非由政策名称的任何人取出。 在网关配置中 :
"facts": [
{ "name": "target", "tool": "memory.get", "args": { "factId": "$args.supersedes" }, "forTools": ["memory.write"], "optional": true },
{ "name": "target", "tool": "memory.get", "args": { "factId": "$args.factId" }, "forTools": ["memory.retract"] }
]
forbid(principal, action in [Action::"memory.write", Action::"memory.retract"], resource)
when { context.facts has target && context.facts.target.space == "org" && context.facts.target.provenance == "attested" };
对写作的查找是可选的,所以一个取代无所事事的写作不需要进行查找;需要收回的写作. 拒收记录了网关所观察到的政策。 测试套房运行的正是这种配置.
验过死刑. 开始于 --key memory.key,服务器在网关所签发的接收结果上签名。 给定的验证人 memory.pub 然后报告内存服务器所证实的每次内存呼叫的执行,不仅网关所观察到的;测试套件以这种方式验证一个写出.
共享于HTTP. agent-custody-memory serve --ledger ./ledger.jsonl --http --port 8790 --token-env MEMORY_TOKEN --allow-direct --direct-token-env MEMORY_DIRECT_TOKEN 服务于同一工具的超流式HTTP,因此有几个网关,每个代理主机一个,以及,与 --allow-directSDK的特工共用一个分类账 两种呼叫者持有不同的信使,信使决定服务器相信什么:一个网关的呼叫带有收据的ID,代理,以及在其中观察到的事实 _meta这就是证明或证实的笔迹 _meta 在服务器读到它之前就被丢弃了 所以它可以声称一个事实 并且从不证明一个事实 不论它的电话怎么说 永远不要给一个直接的作家 网关的标志。 这是部署隔离的设定:通过网关到达的书信被证实,直接到达的书信被声称并被隐藏,直到网关确认为止,分类账将他们分开。 一个网关到达它 "upstream": { "url": "http://127.0.0.1:8790/mcp", "tokenEnv": "MEMORY_TOKEN" } 在它的配置。 服务器不会在没有网关符的情况下从HTTP上启动,因为没有它,任何到达端口的呼叫器都可以作为网关通过. 测试套房运行的正是这个:一个网关作家,一个直接作家,一个分类账. SIGINT或SIGTERM关闭了服务器和分类账,所以一个容器运行时间或系统可以干净地阻止它;审查控制台也这样做. 服务器停止收听,立即关闭闲置连接,让飞行中的请求完成. 3秒后,它会切断留下的连接,例如一个身体从未到来的请求. 服务器不会打开GET流(它会用网关客户端接受的405来回答GET),所以连接的网关不会持有站点.
写作政策 在收据指南中,对内存工具有6个经过测试的政策,从限定代理人到其团队空间到完整的支持代理政策. 信任等级是锡达对空间的政策,并通过 includeClaimed,超过隔离: permit(principal, action == Action::"memory.write", resource) when { context.args.space == "team:support" }; 让这个特工写团队记忆 而不是别的 读取的收据,作为 observed所以特工所依赖的id已经记录下来了.
--allow-direct 让服务器在没有网关的情况下接听电话;然后 source.receiptId 无效, actor 无论打电话者说什么 都记录下来了 打电话者输入的 _meta 被忽略。 一个客户要么是网关 要么是直接的作家 --allow-direct 说: 网关绝不可以使服务器产生 --allow-direct。 。 。 . 例子/03-纪念-后门.ts 运行整个循环,包括拒绝的写作和引用自己收件的收回.
证明忘记
"已验证"指收据证明所作所为,而非该价值不存在. 被遗忘的,是分类账和有适配器的商店, 它报告了他们每个人的回答。 它没有到达缓存,一个模型的上下文窗口,应用程序日志,复制品,备份,由导出提供的仓库,或者没有适配器的任何商店. 在 Postgres 上,被抹去的行图像会留在桌子上直到商店 VACUUM FULL 运行,在每次忘记之后,除非被告知不要忘记(分类账) (中文(简体) ). 对依赖它的人说清楚点 读一下结果中的每个商店状况 然后再叫人走.
删除要求不同于更正。 重排保存记录; 忘记去除值 . memory.forget 从分类账文件中去除事实的值,停止相信事实,从服务器后方的每个商店去除. 它保存的值是一个选择,记录在事件上作为 digestKind: 一个平原 sha256 中,使分类账能够证明它所删除的、但可以猜测的短值,例如持有文件的任何人的电子邮件 hmac-sha256 服务器在文件外持有的忘却键下( e)serve --forget-key-env NAME),它向任何人证明这一点,向任何其他人展示钥匙和任何物品;或 none,与 keepDigest: false,对于律师不想从保留的价值中获得任何利益的情况。 使用按键格式, 除非您有理由不使用 。 结果完全说明了所发生的情况:从分类账上被去除,从哪些仓库被去除,仍然持有,连同摘要,演员,以及原因.
留与法持. memory.sweep 忘记了在某一刻之前,在一个或所有空间里所了解的每一个事实,并且从每个商店中移除每一个;它是作为收讫的呼叫保留下来的,以收讫作为被删除内容的记录. 保留窗口存在于服务器中 : serve --retention 'org=P365D,team:*=P90D,user:*=P30D', ISO 8601 按空间模式排列的持续时间, 和无扫描 before 每个空间使用它们。 在没有守护进程的情况下运行, agent-custody-memory sweep --via retention-gateway.json --reason "quarterly retention" 产卵网关和呼叫 memory.sweep 通过它,所以扫荡运行 作为主 命名在网关的赠款, 保留工作 单范围 memory.sweep及其收讫记录 当留存, 由谁,它抹去什么, 和持有什么。 任何命令,系统计时器,或CI调度 都会驱动这个命令. memory.hold 将一个事实放在法律上:虽然这个事实是存在的,但无论是删除请求还是扫描都不能忘记它 memory.release 举起. 持有和释放是与演员,理性,和收受相关的事件,因此一个事实的历史表明持有和写作一样的清晰. agent-custody-memory sweep --ledger ... --before ... --reason ... 仅在分类账文件中保留,用于没有库存的分类账.
清除是核实的,没有假定. 由 id 删除和搜索索引赶上是不同的时刻。 每次收回,忘记,或扫地后,服务器都会询问每个商店自己的搜索结果是否仍然浮出水面,几次有回放,并在结果中记录每个商店的结果等: verified (商店的搜索 找不到它), stillIndexed (仍然如此,在复试之后), unverified (无法询问商店,或适配器没有搜索),或 failed (删除本身失败). Mem0和Zep都通过他们的搜索API进行验证;这些测试驱动真实的客户端与一个可配置的搜索次数所落后的索引相对抗. 写着 stillIndexed 是一个诚实的证书; 以后再运行忘记, 或者读它作为商店的问题修复.
包头. agent-custody-memory pack --ledger ... --receipts ... --fact <id> --out pack.json --sign keys/pack.key 将所有关于一个事实的事物收集到一个签名的人工制品中:它的历史是每起事件产生的收据,它的持有和释放,它的爆炸半径是每起下游收据,如果它被遗忘,则它与每个商店所回答的东西一起被遗忘. pack --verify pack.json --key keys/pack.pub --issuer-key keys/gateway.pub --principal-key keys/principal.pub 检查包的签名和文摘, 里面的每张收据与网关的钥匙, 每个事件的收据都存在, 忘记的收据名称 这一事实并记录去除。 在里面碰一张收据 整包都失效了 这是一张罚单上的艺术律师,审查员需要两个公用钥匙,而你什么都不需要.
凭证为收据. 通过网关, memory.forget 是一个接收到的电话,其结果 网关观察, 所以签名,登录的收据记录 发生擦除, 谁要求的, 和商店的答复。 把收据交给要求删除的任何人;任何持有网关公用钥匙的人都可以核实.
收据是价值存在的另一个地方:记录书面记录的收据在其请求论据中附有,而读取的收据则在其结果中附有,在一份Merkle日志上签字并附上。 收据包裹 prune 命令是对日志的保留:它用花序来替换更老的叶子,因此,在内容消失时,每个根和每个证据仍然可以验证,并去掉被刷取的收据的捆绑文件。 和扫荡时间一样.
爆炸半径
当一个信念被证明是错误的时,下一个问题就是它依赖于什么. 两张唱片一起回答. 每个网关收据都携带 consumed: 通过网关显示代理的身份, 在此之前, 内存服务器在每次读取前都会声明。 分类账上的每一条事实 都带有写它的收据 通过这两个联系从一个事实中向前走,给后来的每一个呼唤和写在这些呼唤中的每一个信念,都是过渡性的,以及根是否被收回,哪些信仰仍然被相信.
agent-custody-memory blast --ledger ./ledger.jsonl --receipts ./receipts --fact <factId> [--capture-key-env NAME]
根据私捕规则签发的收据扣留了代理人显示的内容。 查询从未将这样的电话算作一无所有:它列出自分类账得知这一事实以来(按交易时间:从明天起有效的事实今天可以读取)所作的每一次询问 INCOMPLETE键,用于模式中的收款 encrypted让它看清楚.
fact 3f2a…: acct:42 plan = "enterprise" (space team:support, by support-agent, attested)
retracted at 2026-09-07T10:12:04.118Z by support-agent: CRM sync bug: account is on the free plan
3 call(s) made after the agent was shown it:
2026-09-07T10:12:03.902Z memory.write executed receipt 7c1e…
...
2 belief(s) written in those calls, 2 still believed:
acct:42 discount = "20%" fact 9b04… STILL BELIEVED
acct:42 support_tier = "priority" fact e77d… STILL BELIEVED
随着网关在几个上游,内存和代理使用的工具面前,半径也到达了动作:代理读取一个信仰后发放的退款带有该信仰的ID并被列出. 实例/06-对信仰采取行动 显示它.
它是设计上方的束缚:在代理人看到事实后打的电话是在半径内,无论代理人是否使用,因为任何收据都无法证明模型所处理的内容. 它从未错过的,是一件重要的事情, 下游的行动或信念,确实取决于事实. 实例/05-blast-radius.ts 运行整个循环.
解释一个动作
担保人首先提出的问题不是事实而是行动: 183722年的退款是什么 我能相信答案吗? agent-custody-memory explain --receipts receipts --receipt <id> --ledger ledger.sqlite --issuer-key keys/gateway.pub --principal-key keys/principal.pub 按要求顺序回答:
WHO support-agent (attested, named in a signed grant)
WHO AUTHORIZED IT user_456, grant signed by key bfa1c0d2e3f4, valid 2026-09-08T08:00:00.000Z to 2026-09-08T16:00:00.000Z
WHAT WAS ALLOWED tools customer.lookup, stripe.refund
policy 9c1d2e3f4a5b decided allow
WHAT THE AGENT SAW customer = {"verified":true,"plan":"pro"} (fetched by the gateway via customer.lookup)
1 belief(s) shown before this call:
acct:42 plan = "enterprise" [0b83d822]
WHAT IT DID stripe.refund {"customer_id":"cust_123","amount":845000} -> executed
WHY the policy permitted it
WHAT EVIDENCE receipt 7f2e…, leaf 41 of a log whose head is signed by 3a9b0c1d2e3f
authorization committed as leaf 40, before the call was forwarded
CAN I VERIFY IT VERIFIED, 23 checks
DID ANYTHING DEPEND ON THIS 1 belief(s) written in this call, 3 later call(s) made after seeing them, 2 belief(s) derived
WHAT NEEDS REVERSAL 2 belief(s) still believed, and 3 later call(s) to review
前八行单从收取而来,无分帐而活;后二行由分帐而答,无分帐而活 unknown without a ledger 而不是假装什么 取决于电话. --out action.json --sign keys/pack.key 写出与一个签名的动作包一样的答案,带收据和每一份下游收据 explain --verify action.json --key keys/pack.pub --issuer-key ... --principal-key ... 检查包的签名和文摘,收据,每一份下游收据,以及书面信仰引用的收据. 里面碰一个收据,包坏了。 下面的看管包 与事实所见的相同 而不是行动.
检阅台. agent-custody-memory review --receipts receipts --ledger ledger.sqlite --issuer-key keys/gateway.pub --principal-key keys/principal.pub 提供与回回路页相同的答案:一个带有交易和客户身份的索引,何时、工具、结果、代理、主、生产者及其判断。 按交易、客户、代理或包含的时间范围进行搜索;每份收据将打开10个答案、核查报告、发放的捆绑和可移植的大小写下载。 它运行在收据所在的地方,因为没有人持有这些收据;它没有自己的登录,所以它只暴露在你已经拥有的后面.
agent-custody-memory review --receipts receipts --issuer-key keys/gateway.pub --principal-key keys/principal.pub --transaction re_123 --customer cust_123 --agent support-agent --from 2026-10-01T00:00:00Z --to 2026-10-31T23:59:59Z
agent-custody-memory review --receipts receipts --issuer-key keys/gateway.pub --principal-key keys/principal.pub --out review-site/
agent-custody-memory review --receipts receipts --issuer-key keys/gateway.pub --principal-key keys/principal.pub --case <receiptId> --out refund.case.html
agent-custody-memory review --receipts receipts --issuer-key keys/gateway.pub --principal-key keys/principal.pub --case <receiptId> --out refund.case.json
agent-custody-memory review --verify-case refund.case.json --issuer-key keys/gateway.pub --principal-key keys/principal.pub --json
agent-custody-memory review --verify-case refund.case.html --issuer-key keys/gateway.pub --principal-key keys/principal.pub
过滤器与AND结合,匹配精确的对大小写敏感的标识,并只搜索公开的论证,结果和网关事实. 时间必须是 ISO 8601 日期 Z 页:1 无法搜索捕获规则所扣留的标识符 。 缺少所读页面部分内容(它没有通过核查者的结构检查)的记录与理由一起显示为NOT VERIFIED,并且不停止导出。 分类账或文件出错并不是记录的缺陷:它不能导出,活页回答500. 搜索停止了64级的筑巢,并忽略了JSON文本比64KiB更长,因此一个敌对的争论无法拖住索引. 一个含有搜索跳过值的收件被标记为"部分索引",一个id过滤器将它保存在列表中,因为ID可能在跳过值中. 索引从不放出记录:其收件ID与其名称不同的文件被列出为NOT VERIFIED("文件名与收件ID不符"),页眉会计算无法被读取的文件. --out <dir> 写索引、收件页和捆绑,以及每张收件 .case.html 财务报告和审定财务报表 .case.json;其浏览器搜索仅覆盖该导出中包含的收件. 添加 --approval-key (每个批准人)如果收据是给一个批准的人的电话的:没有这些钥匙,收据就不会核实。 添加 --log-key, (中文(简体) ), --log-id,以及 --upstream-key 当这些签名需要独立的密钥.
一个可移植的HTML大小写包含签名收据,嵌入签名授权,包含证明,签名日志头,公钥(签发者,主,批准,日志,和上游)及其指纹,十个答案,以及任何分类账快照. 本地打开:它的嵌入式WebCrypto验证器在没有外部请求的情况下对所签名的证据进行新鲜检查. 这个结果并不独立,因为脚本和数据在同一个文件中:无论谁编辑文件,也可以编辑脚本,页面没有将十个答案与收件进行比较. review --verify-case 是独立的检查。 它从文件名中取出格式:结束于 .json 被读作JSON, 和所有其他名称作为一个页面。 不匹配名称的内容失败 。 它读取了文件,从所签名的收件中重建出十个答案并打印出它们(一个保存的答案,不同之处在于"十个答案与所签名的收件和未签名的分类账快照相匹配"的检查失败了),并在被失败的核实上出出一. 对a .case.html 它也失败,除非该页面正是它的数据所构成的,因此一个已更改的可见答案或数据的第二个副本失败。 信任命令打印的答案,而不是页面 。 支票包括来自签名收据的答案。 信念,依赖,和倒行来自文件中的分类账快照,没有人会签名,所以一个伪造的快照与匹配的答案通过:这些行没有证明. 没有了密钥旗,它使用被捆绑的密钥;所提供的公钥取代了被捆绑的全部信任集. 通过可信来源确认捆绑的指纹 。 解释和分类账快照是未署名的出口商的断言;核查证实了记录的网关决定并检测到收据被篡改,而不是外部付款。 没有分类账,信仰依存 unknown without a ledger。抓取密钥、私钥、盐类和已恢复的预留值从不添加到案件档案中.
写入您已经使用的仓库
分类账不是检索库,也不是试图检索的. src/stores.ts 将其置于已经运行的团队之下:一个通过内存服务器写出的事实也落在了每一个被配置的商店中,里面有它的保管元数据(事实ID,空间,演员,出处,收据ID),商店自己的ID被记录在事实上,并被收回通过该ID到达商店. 经认证的遗忘将以此为基础:只有到达了为召回服务的商店,才真正删除. 对于召回的球队来说, pgvectorStore 写入事实的文本, 它从您已经使用的函数中嵌入, 并用事实 ID 键入它的守护元数据, 并用同一最近的邻居查询一个检索调用运行来验证删除 。 还有三个,每个都有自己的限制: lettaStore 将一段写入代理的档案内存,保管作为标记,因为Letta的通过没有免费元数据,并通过档案搜索核实去除; langgraphStore 将每个事实写入 LangGraph 商店, LangMem 保存记忆, 以您给出的命名空间下的事实 ID 键入, 并用它验证清除 get 和一个过滤器 search· ; cogneeStore cognee直接说出REST API,因为Cognee没有JavaScript客户端,通过列出数据集来将文本添加到存储器中并找到数据id,因为Cognee添加返回了一条管道运行而不是一个ID;它是针对Cognee的路由器而建的,并且测试的是其中的站点,而不是运行中的Cognee.
import { MemoryClient } from "mem0ai";
import { ZepClient } from "@getzep/zep-cloud";
import { Letta } from "@letta-ai/letta-client";
import { InMemoryStore } from "@langchain/langgraph";
import { Ledger, cogneeStore, createMemoryServer, langgraphStore, lettaStore, mem0Store, pgvectorStore, zepStore } from "@agent-custody/state";
const stores = [
mem0Store(new MemoryClient({ apiKey: process.env.MEM0_API_KEY! }), { userId: "user_42" }), // infer is off: the memory is the fact, verbatim
zepStore(new ZepClient({ apiKey: process.env.ZEP_API_KEY! }), { userId: "user_42" }), // or { graphId } for a shared graph
pgvectorStore(pool, { embed, dimensions: 1536, table: "agent_memories" }), // your Postgres, your embedding function
lettaStore(new Letta({ apiKey: process.env.LETTA_API_KEY! }), { agentId: "agent-…" }), // archival memory of one agent, custody as tags
langgraphStore(new InMemoryStore(), { namespace: ["memories", "user_42"] }), // or any BaseStore: LangMem's memories live here
cogneeStore({ url: "http://localhost:8000", datasetId: "…", apiKeyEnv: "COGNEE_API_KEY" }), // Cognee's REST API, no client package exists
];
createMemoryServer(new Ledger("./ledger.jsonl"), { stores });
这个 Store 界面很小。 自定义存储工具 name, (中文(简体) ), put,以及 delete,并可以添加 verifyDeleted 财务报告和审定财务报表 list数字 :
interface StoreEntry { externalId: string; factId: string; text: string; storedAt?: string } // one entry a store holds for a fact; storedAt is the ISO time of the store write
interface Store {
name: string;
put(fact): Promise<string>; // writes the fact, returns the store's own id
delete(entry: StoreEntry): Promise<void>;
verifyDeleted?(entry: StoreEntry): Promise<boolean>; // optional; without it a removal is "unverified"
list?(): Promise<StoreEntry[]>; // optional; every entry with custody metadata, for memory.repair
}
为自定义商店打破了变化. remove(externalId, fact) 现在 delete(entry),以及 verifyRemoved(externalId, fact) 现在 verifyDeleted(entry)。每个需要一个 StoreEntry。变化 remove(id, fact) 改为 delete({ externalId, factId, text })。名称随论据而变化: createMemoryServer 拒绝没有的商店 delete,因此为更早的接口所写的商店在启动时失败。 它从来没有用错误的参数调用它,它的旧检查永远不会报告一个条目被证实. 这个 text 字段是文本 factText(fact) 返回,所以商店不再需要全部事实。 这个 entryOf(fact, externalId) 函数从事实建立条目。 此包中的适配器已经使用新签名 。 每个适配器执行器 list: Pgvector 和 Cognee 总是,朗格拉夫 当商店有 search,还有Mem0,Zep,以及Letta 当他们的客户端有方法列出条目时.
适配器在结构上打字,不依赖任何一家供应商运行时间;测试驱动真实 mem0ai 财务报告和审定财务报表 @getzep/zep-cloud 客户反对假的终点,下线.
写单和修理
书写订单是固定的:验证,存储,分类账.
- 分类账的检查先运行。 一张书上写着 分类账拒绝到商店.
- 接下来是商店 服务器需要商店的ID来记录事实.
- 分类附件最后.
拒绝写作的商店失败了写作,没有记录到任何地方. 在其分类账附件之前,一个需要5分多分钟才能储存的书写被拒绝;参见下面的修理扫描. 收回命令是相反的:分类账,然后储存。 保管权不能取决于商店的成立 未移除的商店在错误中被命名,所以呼叫者知道召回仍然可以服务于该值.
订单有一个成本。 如果分类账附件在商店拿走了笔记后失败,商店会保留一个分类账没有事实的条目. 这个条目是一个孤儿:召回可以服务于它,没有事件可以解释它. 修行扫地取出孤儿.
正在写作的文字看起来也像孤儿:其条目在仓库里,尚未入帐。 有两条规则让扫荡远离. 每个条目记录 storedAt它的商店写作的时间。 扫描只删除在截断前存储的条目, 时间是十分钟前( 导出) REPAIR_MIN_AGE_MS) (中文(简体) ). 写个书 书店要花五分钟多 一半 REPAIR_MIN_AGE_MS,在其分类附件之前被拒绝。 因此,一个进行中写入的条目永远不会比截取时间长. 这在共享商店的服务器进程之间占据着主导地位。 如果一个分类账附件的摊位超过这个时间,则修复事件决定:无论哪个事实ID的记录到达分类账中先胜出,另一个则失败出错. 在一个服务器程序中,分类账使检查和两个记录的后附单步. 对于不同过程中的服务器,窗口是检查与一个附件的实现之间的时间. 要关闭它,支票和附件必须是共享分类账商店中的一项交易,例如使用每个事实标识的咨询锁. 这个版本不这样做。 一个独特的限制 fact_id 不起作用:收回、确认、持有、释放和忘记事件与事实相同,两个商店的孤儿每个商店有一个修理事件.
memory.repair { reason, before? } 运行扫描。 CLI通过一个网关运行,因此扫描有收据,并作为网关赠款中的本金运行:
agent-custody-memory repair --via repair-gateway.json --reason "ledger outage on 14 October" [--before <ISO instant>]
截断时间是 before 十分钟前. before 只能更早地移动断路口 永远不要晚.
对于每个商店,有 list,扫描读取每个条目 与监护元数据。 它只删除一个条目一次,如果一个列名持有两次. 它跳过一个条目 其事实ID分类账知道。 它跳过一个在截取前没有被存储的条目. 对彼此的条目,扫描先附加 a repair 事件,如收回其分类账事件。 该事件有条目元数据中的事实ID,演员,原因,收件ID,商店,商店的ID为条目. 之后"扫地"删除了该条目,并询问商店的搜索该条目是否已经消失(重复和结果与遗忘一样). 其结果是: verified, (中文(简体) ), stillIndexed, (中文(简体) ), unverified,或 failed。 。 。 . memory.history 孤儿身份证明 第一次记录一个事实id获胜。 分类账拒绝修复一个它知道的事实ID,它拒绝一个有修复事件的事实ID的断言,因此迟到的写作失败出错,分类账从来不相信没有商店持有的事实. 在一个服务器进程中,一个已经有修复事件的条目的修复不会增加第二个事件:一个失败去除后的重试使用第一个事件. 一次扫描在服务器过程中运行 。 如果一个商店未能删除并上市,则不再持有该条目,扫描时将删除处理如常,并验证该条目,所以结果为: verified, (中文(简体) ), stillIndexed,或 unverified 同样的规则.
结果是 { before, repaired, unlisted, stillHeld, unrecorded }数字 :
before是扫描所用的截断点, 作为 ISO 瞬间.repaired实值[{ store, externalId, factId, outcome, eventId }]。 。 。 .unlisted是没有list。 。 。 .stillHeld是失败的清除错误。 它也坚持<store>: cannot list: <error>是一个上市失败的商店 扫地会和其他商店一起进行.unrecorded实值[{ store, externalId, factId, error }]:扫取没有去掉的条目,因为分类账没有取去修复事件. 下一次扫荡再次尝试他们.
结果是在 stillHeld 或者说 unrecorded 未为空。 在修复商店或分类账后再进行扫荡.
限制 :
- 其条目
storedAt缺少或没有分析 。 扫地从来取去. 当元数据不存在时storedAt,适配器返回商店的创建时间:created_atpgvector 的列,以及客户端为Mem0、Zep、Letta和LangGraph报告的创建时间。 Cognee只有在其上市有创作时间时才有倒计时. 莱塔记录storedAt在标签中stored:<iso>。 。 。 . - 一个仓库范围属于一个分类账。 一个存储范围是一个pgvector表,一个Mem0用户,一个Zep用户或图表,一个带有精确标记集的Letta代理,一个LangGraph命名空间,或者一个Cognee数据集. 共用一个范围的两个分类账已经相互间漏出,一个分类账的修补可以去掉另一个的分录.
- 改变Letta商店的标签套件 将商店在旧标签下写的段落绑起 确切的标记集是范围,因为命名空间是给LangGraph的. 新店没有列出旧的通道,和
memory.repair无法消除其中的孤儿.lettaStore丢入标记为agent-custody或从fact:, (中文(简体) ),space:, (中文(简体) ),stored:,或receipt:。 。 。 . - 内存服务器的时钟必须在几分钟内同意.
storedAt取自于作家的时钟,取出取出取出取出自修行者的时钟. - 商店没有
list报告于unlisted而不扫地取地. Mem0, Zep, 和莱塔得到list当客户端有列表条目的方法时. - 扫描只找到带有保管元数据的条目(
source: "agent-custody"和事实id。 它不会碰商店里的其他条目. - Cognee 数据列表中没有文本, 因此其条目为空
text适配器通过ID验证,因此这对扫荡无关紧要.
分解内存变异
src/evals.ts 是一种在写作之后,而不是在回忆录上 给记忆系统评分的绳索。 一种情景是写作、读取、超级会话和收回的脚本,其中每个读取的系统返回一个正确的值。 分数计有:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数;分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为:分数为(分数为分数为:分数为分数;数为分数为数为数为数为数为数为数为:分数为分数为:分数为:分数为:分数为:分数为:分数为:分数(分数为分数为分数为:分数为分数为分数为:分数(分数为:分数为:分数为 任何系统背后的小 MemoryUnderTest 界面可以被打入; 此分类账和一个天真地覆盖商店的船作为两个参考点,以及 实例/04-evals.ts 并列打印两个报告.
import { Ledger, ledgerUnderTest, runAll, SCENARIOS, formatReport } from "@agent-custody/state";
console.log(formatReport(await runAll(ledgerUnderTest(new Ledger("./ledger.jsonl")), SCENARIOS)));
从外壳,按时间表:
agent-custody-memory eval --baseline # built-in scenarios, ledger and a naive store side by side
agent-custody-memory eval --scenarios incidents.json --sign keys/eval.key --out report.json # your own incidents, signed report
agent-custody-memory eval --verify report.json --key keys/eval.pub # what a reviewer runs
一个情景文件是 { "version": "0.1", "scenarios": [{ "name", "ops": [...] }] } 书写、读取和撤回操作与内置操作相同 expect毫无意义 这个文件被验证了, 一个坏的名字 冒犯行动。 署名的报告是一份记分表的逐个报表,以与收据相同的信封格式附在它所运行的情景摘要上; eval --verify 检查签名和摘要. eval 当分类账得分低于所给出的假设时退出非零,所以当一个变化倒退了内存行为时,一个运行它的克龙任务就会响亮地失败.
分类账
src/ledger.ts 保留仅附加事件日志。 它本身没有它:每个问题都是向商店查询,所以商店决定一个分类账可以有多大,谁分享. 一个商店回答四个问题,所有问题都按顺序排列(出口,审计),所有与过滤器相匹配的事实(已读到的字节),所有触及一个事实的事实(其历史及其检查),以及存在哪些空格(保留),它做三件事:附加,替换一个在位的断言(遗忘),以及去除后紧凑. 三艘仓库船, 和整个分类账套装 运行对每一个, 所以他们回答相同.
贾森勒 是默认: 每行一个事件, 任何人都可以读取, 您复制的文件供审计 。 从记忆到数以万计的事件. SQL 点,选择一个以 .sqlite 或者说 .db,是用于在一个机器上的耐久性: 交易用写- ahead 记录,一个在位者忘记了覆盖被擦除的值(安全删除,然后是一个切换的检查点,所以在写-ahead 日志中没有停留),并且每个查询都通过一个关于事实、时间、空间、主题、上游和超会话的索引回答。 节点将SQLite模块船出,所以没有本土依赖性;由更早的版本所写的分类账,在首次打开时从它的JSON中得到索引列被填充. 以百万事件分类账衡量:以0.2 s开出,一个读取约1 ms的题材,一个事实的历史在一毫秒以内,2 ms的扫描空间. 邮资,由一个 postgres:// URL,用于共享分类账:一个表格上的多个内存服务器,在您安全团队已经批准的数据库中,被推倒的查询与SQL相同. 这需要 pg 套件安放在这个旁边; ?table=custody.events 将表格命名并 ?vacuum=false 跳过下面描述的真空。 在代码中,通过一个 PostgresStore 在你身边 pg Pool, 随你使用什么 TLS 和证书。 适配器通过PGlite对真正的Postgres引擎在进程中进行测试,包括数据库文件中没有被遗忘的值,并与Postgres 16进行手动检查.
忘记Postgres是一个 UPDATE,而MVCC将旧行图像保存在桌子上直到真空. 所以在被遗忘或被扫荡之后 店铺会开着 VACUUM FULL 在桌子上,它重写 它没有旧的图像。 重写需要独家锁; 一个非常大的分类账可能设置 vacuum: false 并运行自己的时间表, 和遗忘证书 依该时间表。 写头日志,复制品,备份保存自己的复制品,只要保留下来的话;每个数据库都是如此,诚实地读取忘却证书就是活的分类账不再持有价值.
选择 JSONL 用于一个服务器进程和任何审计师应该读取的 cat。选择 SQLite,当一台机器在 HTTP 上服务分类账,当两个写入之间的崩溃不能花费一个事件,或者当删除要求不能在文件中留下任何值的痕迹时。 当多个服务器必须共享分类账时, 或者当分类账属于您已经运行的数据库时, 选择 Postgres 。 仓库不是商店:雪花和Databricks默认会保留被删除的行数日,并且是为扫描而建造的,不是每个代理呼叫一个小写. 给他们吃 agent-custody-memory export --ledger postgres://… --out ledger.jsonl,它从任何商店写出可审计的 JSONL, 并查询分类账 您的合规团队已经工作.
日志上有这样的事件.
- 主张 创建一个事实:主题,上游,值,它所居住的空间(一个人,一个团队,一个org),写作的演员,如果写作经过一个收件制作人,则来源收件id,以及有效时间间隔. 断言可能 替换 一个更早的事实, 结束了这个事实的有效性 当新的事实开始.
- 收回 说一个事实不应该被相信。 这是逆向。 记录保留下来,所以关于早期的询问仍然看到事实,任何被收回的事实被取代的,都再次被相信.
两个钟,故意分开:
| 问它答案 | 设定 | |
|---|---|---|
| 有效时间 | 这是真实的 在世界上在T | 作者, 默认为现在 |
| 交易时间 | 分类账在T知道吗 | 分类账,从不写 |
asOf({ validAt, txAt, space, subject, predicate }) 返回一个瞬间相信的事实。 将两种时间设定为相同的即时答案"特工星期二相信什么",包括后来收回的信仰. history(factId) 返回每个触及事实的事件.
分类账拒绝取代一个未知、已经取代或撤回的事实,并拒绝在其取代事实之前开始有效性的替换.
版式
src/ledger.ts the fact record, the event kinds, as-of queries, supersession, retraction, forget, holds, sweeps, repairs
src/storage.ts the store interface and its three stores: JSONL (default, auditable), SQLite (indexed, one machine), Postgres (shared); chosen by path or URL
src/server.ts the ledger as MCP tools; source and actor taken from the gateway's _meta
src/http.ts the memory server over Streamable HTTP with bearer auth, for a shared ledger
src/explain.ts one action explained: the ten answers from a receipt and the ledger, and the signed action pack
src/review.ts the searchable review console: explain pages and portable case downloads, served on loopback or written as files
src/search.ts exact transaction, customer, agent and time filters over the public receipt view
src/case-file.ts portable HTML/JSON evidence with public keys, fresh offline verification and the ten answers
src/verify-web.ts the browser verifier embedded in portable HTML case files
src/pack.ts the custody pack: build, sign, verify, format
src/cli.ts agent-custody-memory serve (stdio or --http, with --retention and --forget-key-env), sweep (ledger-only or --via a gateway), repair (--via a gateway), eval, explain, review, pack, export, blast
src/blast.ts blast radius: from receipts' consumed facts and the ledger's source receipts, forward
src/stores.ts write-through adapters: Mem0, Zep, pgvector, Letta, a LangGraph store (LangMem), and Cognee, and the Store interface (put, delete, verifyDeleted, list) for others
src/evals.ts the memory-mutation harness: scenarios, scoring, report
src/evals-ledger.ts the ledger and a naive overwrite store behind the harness interface
src/evals-file.ts scenario files, validated; signed eval reports and their verification
src/index.ts public surface
examples/ runnable walkthroughs, each ends with OK and is run by the test suite
test/ one test per question a platform owner asks after a memory incident
tsconfig.build.json emits dist/ for consumers; the repo itself runs the .ts directly
计划
完成
- 带有超会话,收回,作为和历史查询的Bitemporal fact 分类账,作为JSONL,SQLite,或Postgres 在一个商店接口后面持续存在,从一个索引中回答每个查询,导出回JSONL.
- 内存服务器:分类账作为收件网关后方的MCP工具,由网关提供出源收件ID和经认证的演员,政策凌驾于空格上,每个被拒绝的书写都有拒绝收件.
- 忘记摘要是在服务器持有的秘密下键入的,或请求时缺席,因此无法从文件中猜出被删除的值.
- 服务器中每个空间保留窗口,扫描默认窗口,并 a
sweep --via在任何定时器上作为指定主线通过网关运行的触发器. - 审查控制台:按交易、客户、代理或时间搜索公共接收字段,检查10个答案和核查报告,并导出一个便携式HTML/JSON案例,并进行新的离线核查;在回放或写成静态文件.
- 解释一个动作:从收据身份证明,谁,谁授权,允许什么,代理人看到什么,为什么,证据,它是否核实,取决于什么,需要什么倒置;与签过字的动作包一样,每个下游收据都携带.
- 保管包:一个事实的历史 收据,持有,爆炸半径, 和忘了证书作为一个签名的 人工, 核实整体.
- 删除验证: 收回后, 忘记, 或扫描服务器询问每个商店的搜索值是否在收件时仍会浮出水面和记录, 是否仍然被输入, 未验证, 或每个商店失败 .
- 修复失败的分类账附录 :
memory.repair财务报告和审定财务报表repair --via找到分类账没有事实的存储条目,删除并核实这些条目,并记录一个repair以该条目中的事实身份识别的事件。 Store 接口需要一个StoreEntry并有一个可选list。 。 。 . - 保留和合法持有:收受的扫荡忘记了在一瞬间之前学到的东西并到达了商店;一盘地拒绝忘记并扫荡直到释放,作为事实历史的事件.
- 值级隔离:写作中可以引用自己获取的网关的事实;值必须与它相匹配,然后是事实
verified,上面证明的出处,或拒绝写作. - 经测试的处决:使用密钥,内存服务器会为网关的收件在结果上签名,因此持有公钥的核查者会看到内存呼叫得到证明.
- HTTP上的内存服务器:一个分类账,由几个网关和直接写作者共享,是每类呼叫者的一个代号,所以相信只有网关是关于收款和代理,隔离直播的.
- 证明忘却:
memory.forget从保存其摘要的分类账文件中去除一个值,不再相信它,从每个商店中去掉它,并报告确切发生的情况;网关收到那通电话就是证书. - 政策优于出处:网关检查一个书写取代或撤回目标的事实,因此政策决定其空间、行为者和出处;声称的事实可以被取代,一个经证实的事实不能改变.
- 假设的事实和爆炸半径:内存服务器宣布它所服务的事实,网关在以后每收到一次记录这些事实,以及
blast从一个事实到每一个下游的呼唤 和产生的信仰, 过渡性地,它的撤回状态. - 写入到 pgvector : 事实的文本, 嵌入, 和保存元数据作为 Postgres 的一行, 一个团队已经运行, 由事实 id 键入; 删除由 同一 近邻 的 查询 召回 运行 进行验证; 与 Postgres 进行测试, 并在 Docker 中扩展 .
- Letta(存档段落,保管为标记)的写入适配器,LangGraph商店,LangMem保留其记忆(在命名空间下每个事实一个项目)和Cognee(它的REST API,通过列出数据集找到的数据ID);每个软件都验证自己的清除,每个软件都说明其限制.
- Mem0和Zep的写入适配器:每个写出存储中带有保管元数据的地方,存储id被记录在事实上,收回到存储处,并且命令失败,所以没有东西是半记录.
- Eval CLI:内置或自定义的情景文件,分类账旁的天真基线,一个经签名的审核员验证的报告,以及回归时的非零退出,用于克龙.
- 内存-模拟 eval 套接字: Stale read, 矛盾,爆炸半径, 正确读取对脚本事件, 对分类账和对同一界面背后的任何内容的分数相同.
- 检疫:事实随身携带
attested或者说claimed来源; 声称的事实被默认地隐藏在读取中, 并且仅是一个网关memory.confirm举起它们,作为记录的事件.
接下来,按顺序算
- 主机,早期访问背后:租入日志,再入内存,再上报和一控制平面,有SSO,SCIM,居住,SIEM输出. 问题6。 。 。 .