Skip to content

feat: signed, verifiable memory export/import bundle - #48

Merged
stormer78 merged 1 commit into
mainfrom
feat/export-bundle
Nov 11, 2026
Merged

stormer78 merged 1 commit into
mainfrom
feat/export-bundle

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Closes #36.

What it does

  • vta-agent-memory export [--out FILE] [--type …] [--basis …] [--status …] writes a signed JSON bundle of the context's memories. Each record is included exactly as stored, with its author's own proof. The exporter's Data Integrity proof (eddsa-jcs-2022) covers the whole bundle: the record list, the source context and the filter. Records that are refused or unattributed are left out and listed with the reason. The file is written with mode 0600.
  • vta-agent-memory import FILE|- [--replace] [--dry-run] [filters] checks the bundle's proof and every record's own proof (author, contextId = sourceContext, and the key) before connecting. It is all or nothing: a bundle with one bad record imports nothing. Without --replace, a memory that already exists under the same key is left alone (create-only expectedVersion: 0). With --replace, the import becomes a new version and the old one goes to history.
  • docs/memory-bundle.md documents the format and the checks, so a non-VTA reader can verify a bundle.

Keeping the original author

A record's signature names the context it was written in, so an imported record can't keep that signature as its own: the relocation check from #31 would rightly refuse it. Re-signing as the importer would lose who wrote it. Instead, the importer signs a record for its new location and carries the original verbatim in importedFrom. authorship::verify then checks:

  1. the outer proof, for where the record was found;
  2. the original's proof, author == signer, the same key, and no nested import;
  3. that the content (everything except authorship, location, supersedes and importedFrom) is the same in both.

If all three hold, the memory is attributed to the original author, with importedBy and importedFrom shown alongside (author JSON block, and the note "imported from context home"). Re-importing flattens to the first original. Any later save or status change drops importedFrom, so the editor becomes the author and the imported version stays in history.

Tests (tests/bundle.rs, 7)

  • Export, then import into a fresh context, preserves name, body, updatedAt, basis, confidence, evidence, status, reason and reviewAfter, each attributed to its original author (agent or operator).
  • A tampered bundle is refused when:
    • a record was edited;
    • a record was dropped;
    • it claims another exporter;
    • a record was edited and the bundle re-signed by a forger (the bundle proof holds, the record's does not);
    • it was re-sealed under a different source context;
    • it isn't a bundle at all.
  • A planted import whose content differs from the original it carries is refused. A mutation check confirmed this test fails if the content comparison is removed.
  • Editing an imported memory makes the editor its author.
  • Re-exporting an import keeps the original author.
  • Import doesn't overwrite without --replace, and keeps history with it.
  • A filtered export carries only what it selects, says so in filter, and lists unattributed records it left out.

Also checked: cargo clippy --all-targets is clean, every existing test passes, and the eval's hard checks all pass. Nothing that affects ranking changed.

Notes

  • A bundle is a snapshot. Importing a memory the original author has since retracted brings back the pre-retraction version; the importedFrom context and timestamps say where it came from and when.
  • History isn't exported.
  • import writes memories one at a time once the bundle has been checked, so a VTA failure partway through can leave a partial import. Re-running without --replace picks up the rest.
  • The README's "Six MCP tools" line, stale since Memory lifecycle: status, expiry, supersede and dispute instead of overwrite #33, now lists all nine.

Version 0.6.1 → 0.7.0.

`export` writes a signed bundle of a context's memories, optionally
narrowed by --type/--basis/--status. Each record keeps its author's own
proof verbatim; the exporter signs the bundle as a whole, so a record
added, dropped or edited fails it. `import` checks both, all or nothing,
before connecting, and writes each memory attributed to its original
author: signed by the importer for where it now is, carrying the
original in `importedFrom`, which a reader checks for the same content.

Format documented in docs/memory-bundle.md.

Closes #36

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 6e4c8cf into main Nov 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signed, verifiable memory export/import bundle

1 participant