SmartMemory
ExamplesClinical trials

Export the audit trail

Export the whole workspace as a portable bundle, and write one audit line per study record.

Set SMARTMEMORY_API_KEY, SMARTMEMORY_WORKSPACE_ID and optionally SMARTMEMORY_BASE_URL (default https://api.smartmemory.ai). See the overview for setup.

What it uses

  • client.export_okf and client.exportOkf download the workspace as a gzipped OKF bundle. They need workspace admin.
  • client.explain and client.memories.explain return the audit answer for one memory: when it was recorded, when it was valid, its version history, and the decisions that cite it.
  • chain_verified is null when a record has no edits to verify. That is not a warning.

Python

"""Export the workspace: a portable OKF bundle, plus one audit line per memory."""

import io
import json
import os
import tarfile

from smartmemory_client import SmartMemoryClient

client = SmartMemoryClient(
    base_url=os.environ.get("SMARTMEMORY_BASE_URL", "https://api.smartmemory.ai"),
    api_key=os.environ["SMARTMEMORY_API_KEY"],
    workspace_id=os.environ["SMARTMEMORY_WORKSPACE_ID"],
)

# 1. The whole workspace as a gzipped bundle. Needs workspace admin.
bundle = client.export_okf()
with open("clinical-trial-demo.okf.tar.gz", "wb") as out:
    out.write(bundle)
with tarfile.open(fileobj=io.BytesIO(bundle)) as archive:
    print("bundle:", len(bundle), "bytes,", len(archive.getnames()), "files")

# 2. The audit answer for every study memory: when it was recorded, when it was valid,
#    its version history, and the decisions that cite it.
lines = 0
offset = 0
with open("clinical-trial-demo.audit.jsonl", "w") as out:
    while True:
        page = client.list_memories(
            metadata_key="study", metadata_value="KVR-417-201", limit=50, offset=offset
        )
        for item in page["items"]:
            audit = client.explain(item["item_id"])
            row = {
                "record_id": item["metadata"]["record_id"],
                "recorded_at": audit["item"]["transaction_time"],
                "valid_from": audit["item"]["valid_start_time"],
                "versions": len(audit["versions"]),
                "chain_verified": audit["chain_verified"],
                "cited_by": [d["decision_id"] for d in audit["decision_provenance"]["citing_decisions"]],
            }
            out.write(json.dumps(row) + "\n")
            lines += 1
        offset += len(page["items"])
        if not page["items"] or offset >= page["total"]:
            break
print("audit lines:", lines)

Output:

bundle: 37048 bytes, 75 files
audit lines: 61

JavaScript

// Export the workspace: a portable OKF bundle, plus one audit line per memory.
import { writeFileSync, createWriteStream } from "node:fs";
import { gunzipSync } from "node:zlib";

import { SmartMemoryClient } from "@smartmemory/sdk-js";

const client = new SmartMemoryClient({
  apiBaseUrl: process.env.SMARTMEMORY_BASE_URL ?? "https://api.smartmemory.ai",
  apiKey: process.env.SMARTMEMORY_API_KEY,
  storage: "memory", // a script has no browser storage
});
client.setWorkspaceId(process.env.SMARTMEMORY_WORKSPACE_ID);

// 1. The whole workspace as a gzipped bundle. Needs workspace admin.
const bundle = Buffer.from(await client.exportOkf());
writeFileSync("clinical-trial-demo.okf.tar.gz", bundle);
// A tar archive is 512-byte blocks: a header block, then the file body padded to a block.
// Extended-attribute headers (type x) describe the next entry, they are not files.
const tar = gunzipSync(bundle);
let files = 0;
for (let at = 0; at + 512 <= tar.length && tar[at] !== 0; ) {
  const size = parseInt(tar.toString("ascii", at + 124, at + 135).trim(), 8) || 0;
  if (tar[at + 156] !== 0x78) files += 1;
  at += 512 + Math.ceil(size / 512) * 512;
}
console.log("bundle:", bundle.length, "bytes,", files, "files");

// 2. The audit answer for every study memory: when it was recorded, when it was valid,
//    its version history, and the decisions that cite it.
const out = createWriteStream("clinical-trial-demo.audit.jsonl");
let lines = 0;
for (let offset = 0; ; ) {
  const page = await client.memories.list({
    metadataKey: "study", metadataValue: "KVR-417-201", limit: 50, offset,
  });
  for (const item of page.items) {
    const audit = await client.memories.explain(item.item_id);
    out.write(JSON.stringify({
      record_id: item.metadata.record_id,
      recorded_at: audit.item.transaction_time,
      valid_from: audit.item.valid_start_time,
      versions: audit.versions.length,
      chain_verified: audit.chain_verified,
      cited_by: audit.decision_provenance.citing_decisions.map((d) => d.decision_id),
    }) + "\n");
    lines += 1;
  }
  offset += page.items.length;
  if (!page.items.length || offset >= page.total) break;
}
out.end();
console.log("audit lines:", lines);

Output:

bundle: 40625 bytes, 75 files
audit lines: 61

Audit line

Both scripts write clinical-trial-demo.okf.tar.gz and clinical-trial-demo.audit.jsonl to the current directory. One audit line, from the local run:

{
  "record_id": "CT-DEC-02",
  "recorded_at": "2026-11-02T19:38:35.236241+00:00",
  "valid_from": "2026-02-10T00:00:00+00:00",
  "versions": 0,
  "chain_verified": null,
  "cited_by": [
    "dec_4df2ad5d51e2"
  ]
}

The local workspace had 61 study records, 18 of them cited by a decision.

The output below is from a real run against a local test stack seeded with the demo dataset. It shows record ids and dates only.

On this page