@agent-custody/state 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,32 @@ ledger.retract({ factId: a.fact.factId, actor: "user:admin", reason: "poisoned b
22
22
  ledger.asOf({ validAt: "2026-09-01T00:00:00Z", txAt: "2026-09-01T00:00:00Z" });
23
23
  ```
24
24
 
25
- Two runnable examples, both executed by the test suite. [01-ledger.ts](examples/01-ledger.ts) walks through a wrong write and its undo. [02-receipt-to-belief.ts](examples/02-receipt-to-belief.ts) runs the whole loop with the receipts package: a tool call gets a signed receipt, the receipt is verified, the belief taken from it is recorded citing the receipt, and later retracted. Run them with `node examples/<file>` from this directory, after `bun run build` at the repository root.
25
+ Three runnable examples, all executed by the test suite. [03-memory-behind-the-gateway.ts](examples/03-memory-behind-the-gateway.ts) runs the memory server as the gateway's upstream. [01-ledger.ts](examples/01-ledger.ts) walks through a wrong write and its undo. [02-receipt-to-belief.ts](examples/02-receipt-to-belief.ts) runs the whole loop with the receipts package: a tool call gets a signed receipt, the receipt is verified, the belief taken from it is recorded citing the receipt, and later retracted. Run them with `node examples/<file>` from this directory, after `bun run build` at the repository root.
26
+
27
+ ## The memory server
28
+
29
+ The ledger as MCP tools, meant to run as the upstream of the receipts gateway. Behind the gateway, every write and read is checked by the Cedar policy and gets a signed receipt, and two things reach this server in the call's `_meta` that no caller can supply: the receipt id, which becomes the fact's `source`, and the agent from the signed delegation grant, which becomes the fact's `actor`. A caller's own claims about either are ignored.
30
+
31
+ ```bash
32
+ agent-custody-memory serve --ledger ./ledger.jsonl # over stdio; refuses calls that did not come through the gateway
33
+ ```
34
+
35
+ In the gateway's config, the memory server is the upstream, and the grant names the memory tools as scopes:
36
+
37
+ ```json
38
+ { "upstream": { "command": "agent-custody-memory", "args": ["serve", "--ledger", "/abs/path/ledger.jsonl"] }, ... }
39
+ ```
40
+
41
+ | tool | does | policy sees |
42
+ | --- | --- | --- |
43
+ | `memory.write` | records a belief in a space, optionally superseding an earlier fact | `context.args.space`, `subject`, `predicate`, `value` |
44
+ | `memory.read` | the facts believed at a moment, by space, subject, predicate, valid time, transaction time | the query |
45
+ | `memory.retract` | undoes a belief, keeping it visible to questions about the past | `factId`, `reason` |
46
+ | `memory.history` | every event that touched a fact | `factId` |
47
+
48
+ Trust tiers are Cedar policies over the space: `permit(principal, action == Action::"memory.write", resource) when { context.args.space == "team:support" };` lets this agent write team memory and nothing else. A read's receipt carries, as `observed`, the exact facts returned, so the ids the agent relied on are already on the record.
49
+
50
+ `--allow-direct` lets the server take calls without a gateway; then `source.receiptId` is null and `actor` is whatever the caller said, recorded as such. [examples/03-memory-behind-the-gateway.ts](examples/03-memory-behind-the-gateway.ts) runs the whole loop, including a denied write and a retraction that cites its own receipt.
26
51
 
27
52
  ## The ledger
28
53
 
@@ -46,6 +71,8 @@ The ledger refuses to supersede a fact that is unknown, already superseded, or r
46
71
 
47
72
  ```
48
73
  src/ledger.ts the fact record, the two event kinds, as-of queries, supersession, retraction, JSONL persistence
74
+ src/server.ts the ledger as MCP tools; source and actor taken from the gateway's _meta
75
+ src/cli.ts agent-custody-memory serve
49
76
  src/index.ts public surface
50
77
  examples/ runnable walkthroughs, each ends with OK and is run by the test suite
51
78
  test/ one test per question a platform owner asks after a memory incident
@@ -57,12 +84,12 @@ tsconfig.build.json emits dist/ for consumers; the repo itself runs the .ts dir
57
84
  **Done**
58
85
 
59
86
  - Bitemporal fact ledger with supersession, retraction, as-of and history queries, persisted as JSONL.
87
+ - The memory server: the ledger as MCP tools behind the receipts gateway, with the source receipt id and the attested actor supplied by the gateway, policy over spaces, and a denial receipt for every refused write.
60
88
 
61
89
  **Next, in the order it pays off**
62
90
 
63
- 1. A memory MCP server exposing write, read, supersede, and forget as tools, run behind the receipts gateway so every call is receipted and policy-checked and the source receipt id is filled in by the gateway rather than the caller.
64
- 2. A consumed-facts field on receipts: the gateway records which fact ids a read returned, so later receipts in the session show what the agent relied on.
65
- 3. Blast radius: given a fact id, every downstream receipt and derived fact that cited it.
66
- 4. Trust tiers as Cedar policies over spaces and receipt provenance: a self-reported write cannot overwrite an org-space fact that was attested through the gateway.
67
- 5. Write-through adapters for existing memory stores, tested against the real packages.
68
- 6. Signed forget statements: a retention or deletion request produces a verifiable record of which facts were removed.
91
+ 1. A consumed-facts field on receipts: the gateway records which fact ids a read returned, so later receipts in the session show what the agent relied on.
92
+ 2. Blast radius: given a fact id, every downstream receipt and derived fact that cited it.
93
+ 3. Trust tiers as Cedar policies over spaces and receipt provenance: a self-reported write cannot overwrite an org-space fact that was attested through the gateway.
94
+ 4. Write-through adapters for existing memory stores, tested against the real packages.
95
+ 5. Signed forget statements: a retention or deletion request produces a verifiable record of which facts were removed.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { Ledger } from "./ledger.js";
4
+ import { createMemoryServer, serveStdio } from "./server.js";
5
+ const USAGE = `agent-custody-memory <command>
6
+
7
+ serve --ledger <ledger.jsonl> [--allow-direct] the memory server over stdio; run it as the receipts gateway's upstream.
8
+ By default it refuses calls that did not come through the gateway.
9
+ `;
10
+ async function main(argv) {
11
+ const [cmd, ...rest] = argv;
12
+ switch (cmd) {
13
+ case "serve": {
14
+ const { values } = parseArgs({ args: rest, options: { ledger: { type: "string" }, "allow-direct": { type: "boolean", default: false } } });
15
+ if (!values.ledger)
16
+ throw new Error("serve needs --ledger");
17
+ const ledger = new Ledger(values.ledger);
18
+ console.error(`agent-custody-memory: ledger=${values.ledger} events=${ledger.size} ${values["allow-direct"] ? "direct calls allowed" : "gateway calls only"}`);
19
+ await serveStdio(createMemoryServer(ledger, { requireGateway: !values["allow-direct"] }));
20
+ return 0;
21
+ }
22
+ default:
23
+ console.error(USAGE);
24
+ return cmd === undefined || cmd === "--help" || cmd === "-h" ? 0 : 2;
25
+ }
26
+ }
27
+ main(process.argv.slice(2)).then((code) => process.exit(code), (e) => {
28
+ console.error(`error: ${e instanceof Error ? e.message : e}`);
29
+ process.exit(1);
30
+ });
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { Ledger } from "./ledger.ts";
2
2
  export type { AsOf, AssertEvent, AssertInput, Fact, LedgerEvent, RetractEvent, RetractInput, Source } from "./ledger.ts";
3
+ export { AGENT_META_KEY, RECEIPT_META_KEY, SERVER_VERSION, TOOLS, createMemoryServer, serveStdio } from "./server.ts";
4
+ export type { MemoryServerOptions } from "./server.ts";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  // Public surface of @agent-custody/state.
2
2
  export { Ledger } from "./ledger.js";
3
+ export { AGENT_META_KEY, RECEIPT_META_KEY, SERVER_VERSION, TOOLS, createMemoryServer, serveStdio } from "./server.js";
@@ -0,0 +1,15 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { type Tool } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { Ledger } from "./ledger.ts";
4
+ export declare const SERVER_VERSION = "0.1.0";
5
+ /** The same keys the receipts gateway sets on the upstream call. Duplicated here so this package needs no runtime import from receipts. */
6
+ export declare const RECEIPT_META_KEY = "agent-custody/receipt";
7
+ export declare const AGENT_META_KEY = "agent-custody/agent";
8
+ export declare const TOOLS: Tool[];
9
+ export interface MemoryServerOptions {
10
+ /** Refuse calls that did not come through the gateway, i.e. carry no receipt id. On by default when served from the CLI. */
11
+ requireGateway?: boolean;
12
+ }
13
+ export declare function createMemoryServer(ledger: Ledger, opts?: MemoryServerOptions): Server;
14
+ /** Serves over stdio, the way the gateway spawns it. Diagnostics must go to stderr. */
15
+ export declare function serveStdio(server: Server): Promise<void>;
package/dist/server.js ADDED
@@ -0,0 +1,99 @@
1
+ // The memory server: the ledger as MCP tools, meant to run as an upstream of the receipts gateway.
2
+ // Behind the gateway every write and read is policy-checked and receipted, and the gateway tells this server, in the
3
+ // call's _meta, which receipt it is and who the attested grant says is calling. Those become the fact's source and
4
+ // actor; a caller cannot supply them. Run it directly and the source is null and the actor is whatever the caller
5
+ // claims, which is recorded as such.
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
+ import { z } from "zod";
10
+ export const SERVER_VERSION = "0.1.0";
11
+ /** The same keys the receipts gateway sets on the upstream call. Duplicated here so this package needs no runtime import from receipts. */
12
+ export const RECEIPT_META_KEY = "agent-custody/receipt";
13
+ export const AGENT_META_KEY = "agent-custody/agent";
14
+ const iso = z.string().datetime({ offset: true });
15
+ const Write = z.object({
16
+ subject: z.string().min(1),
17
+ predicate: z.string().min(1),
18
+ value: z.unknown(),
19
+ space: z.string().min(1),
20
+ /** ignored behind the gateway, which supplies the attested agent instead */
21
+ actor: z.string().min(1).optional(),
22
+ validFrom: iso.optional(),
23
+ confidence: z.number().min(0).max(1).optional(),
24
+ supersedes: z.string().min(1).optional(),
25
+ });
26
+ const Read = z.object({ subject: z.string().min(1).optional(), predicate: z.string().min(1).optional(), space: z.string().min(1).optional(), validAt: iso.optional(), txAt: iso.optional() });
27
+ const Retract = z.object({ factId: z.string().min(1), reason: z.string().min(1), actor: z.string().min(1).optional() });
28
+ const History = z.object({ factId: z.string().min(1) });
29
+ const str = { type: "string" };
30
+ export const TOOLS = [
31
+ {
32
+ name: "memory.write",
33
+ description: "Record a belief: subject, predicate, value, in a space. Optionally supersede an earlier fact. Returns the new fact with its id, transaction time, actor, and source receipt.",
34
+ inputSchema: { type: "object", properties: { subject: str, predicate: str, value: {}, space: str, actor: str, validFrom: str, confidence: { type: "number" }, supersedes: str }, required: ["subject", "predicate", "value", "space"] },
35
+ },
36
+ {
37
+ name: "memory.read",
38
+ description: "The facts believed at a moment. validAt asks whether a fact was true then; txAt asks whether the ledger knew it then. Both default to now. Filter by space, subject, predicate.",
39
+ inputSchema: { type: "object", properties: { subject: str, predicate: str, space: str, validAt: str, txAt: str } },
40
+ },
41
+ {
42
+ name: "memory.retract",
43
+ description: "Undo a belief: the fact leaves the present, stays visible to questions about the past, and whatever it superseded is believed again.",
44
+ inputSchema: { type: "object", properties: { factId: str, reason: str, actor: str }, required: ["factId", "reason"] },
45
+ },
46
+ {
47
+ name: "memory.history",
48
+ description: "Every event that touched a fact, oldest first.",
49
+ inputSchema: { type: "object", properties: { factId: str }, required: ["factId"] },
50
+ },
51
+ ];
52
+ const json = (v) => ({ content: [{ type: "text", text: JSON.stringify(v) }] });
53
+ const fail = (msg) => ({ isError: true, content: [{ type: "text", text: msg }] });
54
+ export function createMemoryServer(ledger, opts = {}) {
55
+ const server = new Server({ name: "agent-custody-memory", version: SERVER_VERSION }, { capabilities: { tools: {} } });
56
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
57
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
58
+ const meta = (req.params._meta ?? {});
59
+ const receiptId = typeof meta[RECEIPT_META_KEY] === "string" ? meta[RECEIPT_META_KEY] : null;
60
+ const gatewayAgent = typeof meta[AGENT_META_KEY] === "string" ? meta[AGENT_META_KEY] : null;
61
+ if (opts.requireGateway && !receiptId)
62
+ return fail("memory server accepts calls only through the receipts gateway; no receipt id on this call");
63
+ const args = req.params.arguments ?? {};
64
+ const actorFor = (claimed) => gatewayAgent ?? claimed ?? "anonymous";
65
+ try {
66
+ switch (req.params.name) {
67
+ case "memory.write": {
68
+ const a = Write.parse(args);
69
+ const ev = ledger.assert({ subject: a.subject, predicate: a.predicate, value: a.value ?? null, space: a.space, actor: actorFor(a.actor), source: { receiptId }, ...(a.validFrom ? { validFrom: a.validFrom } : {}), ...(a.confidence !== undefined ? { confidence: a.confidence } : {}), ...(a.supersedes ? { supersedes: a.supersedes } : {}) });
70
+ return json({ fact: ev.fact, eventId: ev.eventId, txTime: ev.txTime, supersedes: ev.supersedes });
71
+ }
72
+ case "memory.read": {
73
+ const q = Read.parse(args);
74
+ return json({ facts: ledger.asOf(q) });
75
+ }
76
+ case "memory.retract": {
77
+ const a = Retract.parse(args);
78
+ const ev = ledger.retract({ factId: a.factId, actor: actorFor(a.actor), reason: a.reason, source: { receiptId } });
79
+ return json({ eventId: ev.eventId, factId: ev.factId, txTime: ev.txTime, actor: ev.actor, reason: ev.reason, source: ev.source });
80
+ }
81
+ case "memory.history":
82
+ return json({ events: ledger.history(History.parse(args).factId) });
83
+ default:
84
+ return fail(`unknown tool ${req.params.name}`);
85
+ }
86
+ }
87
+ catch (e) {
88
+ return fail(e instanceof z.ZodError ? `invalid arguments: ${e.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}` : String(e instanceof Error ? e.message : e));
89
+ }
90
+ });
91
+ return server;
92
+ }
93
+ /** Serves over stdio, the way the gateway spawns it. Diagnostics must go to stderr. */
94
+ export async function serveStdio(server) {
95
+ await server.connect(new StdioServerTransport());
96
+ await new Promise((resolve) => {
97
+ server.onclose = resolve;
98
+ });
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-custody/state",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Governed memory for AI agents: a fact ledger with provenance, valid time, rollback, and lineage, built on @agent-custody/receipts",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -12,6 +12,9 @@
12
12
  "access": "public"
13
13
  },
14
14
  "type": "module",
15
+ "bin": {
16
+ "agent-custody-memory": "./dist/cli.js"
17
+ },
15
18
  "exports": {
16
19
  ".": {
17
20
  "types": "./dist/index.d.ts",
@@ -23,14 +26,16 @@
23
26
  ],
24
27
  "scripts": {
25
28
  "build": "tsc -p tsconfig.build.json",
26
- "typecheck": "tsc --noEmit",
29
+ "typecheck": "tsc -p ../receipts/tsconfig.build.json && tsc --noEmit",
27
30
  "test": "tsc -p ../receipts/tsconfig.build.json && vitest run"
28
31
  },
29
32
  "engines": {
30
33
  "node": ">=22"
31
34
  },
32
35
  "dependencies": {
33
- "@agent-custody/receipts": "0.1.1"
36
+ "@agent-custody/receipts": "0.1.3",
37
+ "@modelcontextprotocol/sdk": "^1.30.0",
38
+ "zod": "^4.5.4"
34
39
  },
35
40
  "devDependencies": {
36
41
  "@types/node": "^26.4.1",