Files
research-blog/public/articles/portable-agent-state-open-market/portable-state-probe.mjs
T
2026-08-22 23:13:07 +08:00

194 lines
9.8 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
function canonical(value) {
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
if (value && typeof value === "object") {
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
}
return JSON.stringify(value);
}
function digest(value) {
return crypto.createHash("sha256").update(canonical(value)).digest("hex");
}
function device(name) {
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
const publicPem = publicKey.export({ type: "spki", format: "pem" });
return { name, did: `did:key:${digest(publicPem).slice(0, 32)}`, publicKey, privateKey };
}
function createEvent(signer, body) {
const id = digest(body);
const signature = crypto.sign(null, Buffer.from(canonical(body)), signer.privateKey).toString("base64");
return { id, body, signature };
}
function verifyEvent(event, keys) {
if (digest(event.body) !== event.id) return false;
const key = keys.get(event.body.writer);
return Boolean(key && crypto.verify(null, Buffer.from(canonical(event.body)), key, Buffer.from(event.signature, "base64")));
}
function eventOrder(a, b) {
return a.body.clock - b.body.clock || a.id.localeCompare(b.id);
}
function reduce(events, keys, ownerDid) {
const unique = [...new Map(events.map((event) => [event.id, event])).values()].sort(eventOrder);
const rejected = [];
const grants = new Map();
const revocations = new Map();
for (const event of unique) {
if (!verifyEvent(event, keys)) {
rejected.push({ id: event.id, reason: "bad-signature-or-id" });
continue;
}
const { kind, writer, payload, clock } = event.body;
if (kind === "grant.issue") {
if (writer !== ownerDid) rejected.push({ id: event.id, reason: "non-owner-grant" });
else grants.set(payload.grant_id, payload);
}
if (kind === "grant.revoke") {
if (writer !== ownerDid) rejected.push({ id: event.id, reason: "non-owner-revocation" });
else revocations.set(payload.grant_id, clock);
}
}
const registers = new Map();
for (const event of unique) {
if (!verifyEvent(event, keys) || event.body.kind !== "state.put") continue;
const { writer, clock, payload, grant_id: grantId } = event.body;
const grant = grants.get(grantId);
const revokedAt = revocations.get(grantId);
let reason = null;
if (!grant) reason = "missing-grant";
else if (grant.grantee !== writer) reason = "wrong-grantee";
else if (!grant.actions.includes("state.put")) reason = "action-not-allowed";
else if (clock > grant.expires_at) reason = "expired-grant";
else if (revokedAt !== undefined && clock >= revokedAt) reason = "revoked-grant";
if (reason) {
rejected.push({ id: event.id, reason });
continue;
}
const current = registers.get(payload.path);
if (!current || eventOrder(current.event, event) < 0) registers.set(payload.path, { value: payload.value, event });
}
const state = Object.fromEntries([...registers.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([path, item]) => [path, item.value]));
return { state, state_hash: digest(state), accepted_events: unique.length - rejected.length, rejected };
}
function exportHarnessA(internal, signer, ownerDid, grantId, startClock) {
const events = [];
let clock = startClock;
const put = (path, value) => events.push(createEvent(signer, {
version: "probe-0.1", owner: ownerDid, writer: signer.did, clock: clock++, kind: "state.put", grant_id: grantId, payload: { path, value }
}));
for (const [key, value] of Object.entries(internal.user.preferences)) put(`profile.preferences.${key}`, value);
for (const memory of internal.memories) put(`memory.${memory.id}`, { text: memory.text, provenance: memory.source });
put("task.current", internal.task);
return {
events,
loss: ["tool_rules: target-independent execution semantics undefined"],
omitted: ["private_material: never serialized"]
};
}
function importHarnessB(state) {
const profile = { preferences: {} };
const recollections = {};
let work = null;
for (const [path, value] of Object.entries(state)) {
if (path.startsWith("profile.preferences.")) profile.preferences[path.split(".").at(-1)] = value;
else if (path.startsWith("memory.")) recollections[path.split(".").at(-1)] = value;
else if (path === "task.current") work = value;
}
return { profile, recollections, work, vault: "local-only" };
}
function auditAgentFile(path) {
if (!path) return null;
const file = JSON.parse(fs.readFileSync(path, "utf8"));
const agents = file.agents ?? [];
return {
format: "Letta Agent File",
agents: agents.length,
blocks: (file.blocks ?? []).length,
tools: (file.tools ?? []).length,
messages: agents.reduce((sum, agent) => sum + (agent.messages ?? []).length, 0),
contains_secret_values: agents.some((agent) => Object.values(agent.secrets ?? {}).some((value) => value !== null)),
known_portability_gaps: ["archival passages not represented", "framework-specific blocks/tool rules require adapters", "schema migration is roadmap work"]
};
}
const owner = device("owner");
const harnessA = device("harness-a");
const harnessB = device("harness-b");
const keys = new Map([[owner.did, owner.publicKey], [harnessA.did, harnessA.publicKey], [harnessB.did, harnessB.publicKey]]);
const grantA = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 1, kind: "grant.issue", payload: { grant_id: "grant-a", grantee: harnessA.did, actions: ["state.put"], expires_at: 100 } });
const grantB = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 2, kind: "grant.issue", payload: { grant_id: "grant-b", grantee: harnessB.did, actions: ["state.put"], expires_at: 100 } });
const internalA = {
user: { preferences: { language: "zh-CN", answer_style: "direct" } },
memories: [{ id: "m1", text: "User works in Beijing", source: "user-stated" }],
task: { id: "research-1", status: "active", title: "portable state research" },
tool_rules: [{ tool: "shell", allow: "workspace-only" }],
private_material: { demo_credential: "redacted-local-only" }
};
const exportedA = exportHarnessA(internalA, harnessA, owner.did, "grant-a", 10);
const base = reduce([grantA, grantB, ...exportedA.events], keys, owner.did);
const importedB = importHarnessB(base.state);
const concurrentA = createEvent(harnessA, { version: "probe-0.1", owner: owner.did, writer: harnessA.did, clock: 20, kind: "state.put", grant_id: "grant-a", payload: { path: "profile.preferences.theme", value: "dark" } });
const concurrentB = createEvent(harnessB, { version: "probe-0.1", owner: owner.did, writer: harnessB.did, clock: 20, kind: "state.put", grant_id: "grant-b", payload: { path: "profile.preferences.theme", value: "light" } });
const mergedAB = reduce([grantA, grantB, ...exportedA.events, concurrentA, concurrentB], keys, owner.did);
const mergedBA = reduce([concurrentB, ...exportedA.events, grantB, concurrentA, grantA], keys, owner.did);
const tampered = structuredClone(exportedA.events[0]);
tampered.body.payload.value = "tampered";
const tamperResult = reduce([grantA, tampered], keys, owner.did);
const revokeB = createEvent(owner, { version: "probe-0.1", owner: owner.did, writer: owner.did, clock: 30, kind: "grant.revoke", payload: { grant_id: "grant-b" } });
const postRevoke = createEvent(harnessB, { version: "probe-0.1", owner: owner.did, writer: harnessB.did, clock: 31, kind: "state.put", grant_id: "grant-b", payload: { path: "profile.preferences.language", value: "en-US" } });
const revokeResult = reduce([grantA, grantB, ...exportedA.events, revokeB, postRevoke], keys, owner.did);
const tests = [];
const test = (name, fn) => { fn(); tests.push({ name, status: "pass" }); };
test("cross-harness preference import", () => assert.equal(importedB.profile.preferences.language, "zh-CN"));
test("cross-harness memory import", () => assert.equal(importedB.recollections.m1.text, "User works in Beijing"));
test("cross-harness task import", () => assert.equal(importedB.work.status, "active"));
test("merge converges independent of delivery order", () => assert.equal(mergedAB.state_hash, mergedBA.state_hash));
test("concurrent conflict resolves deterministically", () => assert.equal(mergedAB.state["profile.preferences.theme"], mergedBA.state["profile.preferences.theme"]));
test("tampering is rejected", () => assert.equal(tamperResult.rejected[0].reason, "bad-signature-or-id"));
test("revoked grant blocks later write", () => assert.ok(revokeResult.rejected.some((item) => item.reason === "revoked-grant")));
test("pre-revocation state remains valid", () => assert.equal(revokeResult.state["profile.preferences.language"], "zh-CN"));
test("secrets are omitted", () => assert.ok(!canonical(exportedA).includes("redacted-local-only")));
test("semantic loss is explicit", () => assert.ok(exportedA.loss[0].includes("tool_rules")));
const result = {
experiment: "portable-state-probe",
runtime: process.version,
tests,
summary: {
passed: tests.length,
failed: 0,
canonical_state_fields: Object.keys(base.state).length,
converged_state_hash: mergedAB.state_hash,
tamper_rejections: tamperResult.rejected.length,
revocation_rejections: revokeResult.rejected.filter((item) => item.reason === "revoked-grant").length,
explicit_loss_items: exportedA.loss.length,
omitted_secret_classes: exportedA.omitted.length
},
interpretation: {
demonstrated: ["signed event portability", "deterministic merge", "capability revocation", "secret omission", "loss reporting"],
not_demonstrated: ["behavioral equivalence across real model runtimes", "large-scale sync performance", "key recovery", "multi-user policy composition"]
},
agent_file_audit: auditAgentFile(process.argv[2])
};
console.log(JSON.stringify(result, null, 2));