Interchange

Memory packs

A memory pack is a plaintext, human- and agent-readable Markdown rendering of a vault's cells. It is the second projection of the same data: the .keepsake vault is the encrypted, on-disk container, and the pack is the lossless interchange file you hand to a tool that cannot open one.

What a pack is

A vault (keepsake/v1) is one JSON file that holds the cells sealed with AES-256-GCM under a PBKDF2-SHA256 key. To read it, a tool must implement that container. A memory pack removes that requirement: keepsake pack decrypts a vault and writes a single Markdown document that carries the exact same cells. The spec describes a pack as a transport projection, not a vault.

The pack is lossless with respect to the cells. Every cell's id, text, source, role, createdAt, tags, and hash is written into a fenced JSON block as the cell array's canonical JSON, and the whole set is committed by a Merkle root recorded in the front matter. Importing a pack reconstructs the cells byte-for-byte; re-sealing them produces a vault with the same hashes and root.

Plaintext A memory pack is not encrypted. It exists so a human or an LLM can read it and so any tool can move it, which means anyone who has the file can read every cell. Share a pack only where that is intended, and encrypt or omit sensitive cells first.

Why it exists

The vault is the safe copy; the pack is the portable one. A pack lets you:

  • Read your memory without any keepsake tooling, because it is Markdown plus one JSON array.
  • Hand the memory to an agent or a service that can read text but cannot open an encrypted container.
  • Move memory into another implementation of the format, or into a different tool entirely.
  • Prove integrity after the move: the pack carries the per-cell hashes and the Merkle root, and keepsake unpack verifies both before it seals.

The pack is distinct from the encrypted vault and from a context pack. A context pack is a transient, token-budgeted digest printed for one task and never stored; a memory pack is a complete, persisted, re-importable copy of every cell.

Lifecycle

Two commands move data between the two projections. Both read the passphrase from KEEPSAKE_PASSPHRASE; neither takes it on the command line.

pack / unpack
# decrypt a vault into a plaintext memory pack$ keepsake pack --vault memory.keepsake --out memory.md# verify the pack's hashes and Merkle root, then seal a new vault$ keepsake unpack memory.md --out restored.keepsake
What each command does.
CommandBehaviour
keepsake packOpens --vault with the passphrase, sorts the cells by createdAt then id, and writes the pack. --out defaults to memory-pack.md. Prints packed N cell(s) into OUT (plaintext, not encrypted).
keepsake unpack <pack.md>Parses the pack, recomputes every cell hash and the Merkle root, and compares the root against the value recorded in the front matter. If both match, it seals the cells into a new vault with a fresh salt and IV. --out defaults to keepsake.keepsake. Exits 1 if the pack does not verify.

Verification is mandatory, not advisory. The spec requires an implementation to verify a pack's per-cell hashes and Merkle root before importing it, and to reject a pack that does not verify. A tampered data block, a wrong root, missing data markers, or invalid JSON all fail with the same message: keepsake: not a memory pack or the pack is corrupt.

The pack format

A pack is one Markdown document in four parts, in order:

  1. Front matter. A YAML-style block delimited by ---, with the keys keepsake, kind, createdAt, cells, and merkle.
  2. A human list. The heading # keepsake memory pack, a one-line description, then one numbered line per cell: N. [source · role · createdAt] text. Newlines inside a cell's text are collapsed to spaces so each memory stays on one line.
  3. The data block. The canonical JSON of the full cells array, fenced as json and wrapped in the markers <!-- keepsake:data --> and <!-- /keepsake:data -->. This is the lossless payload unpack reads.
  4. A closing line. A sentence restating the Merkle root and the fact that the file is not encrypted.

Here is a real pack produced from three cells, reproduced exactly. The hashes and root are deterministic; only createdAt reflects the moment the pack was written.

---
keepsake: keepsake/v1
kind: memory-pack
createdAt: 2026-09-20T15:33:45.128Z
cells: 3
merkle: 54403841eec9ebc89ab27e796bea5a866cf44f9e99f496c0b653f149e70bbf6d
---

# keepsake memory pack

This file is a lossless, re-importable copy of your memory: every cell can be imported back without losing a byte. It is plaintext and not encrypted.

1. [notes · note · 2026-01-01T00:00:00.000Z] first memory
2. [chat · user · 2026-01-02T00:00:00.000Z] second memory
3. [json · assistant · 2026-01-03T00:00:00.000Z] third memory

<!-- keepsake:data -->
```json
[{"createdAt":"2026-01-01T00:00:00.000Z","hash":"61af453b2f8bd5500014c983161986d781a29607da3faf305cfe5accb372b269","id":"a","role":"note","source":"notes","tags":[],"text":"first\nmemory"},{"createdAt":"2026-01-02T00:00:00.000Z","hash":"42a4548b4dfad53bcfa54bbebcdf276708c24b4ef2d62caecf9aa40b30620d62","id":"b","role":"user","source":"chat","tags":[],"text":"second memory"},{"createdAt":"2026-01-03T00:00:00.000Z","hash":"2af3de1f1858e0a521bb0d56855a5a574248212109b4e2e50b6897250ef9400f","id":"c","role":"assistant","source":"json","tags":[],"text":"third memory"}]
```
<!-- /keepsake:data -->

The Merkle root of these cells is 54403841eec9ebc89ab27e796bea5a866cf44f9e99f496c0b653f149e70bbf6d. This file is safe to paste anywhere, but it is not encrypted: anyone who has the file can read it.

Two details matter for round-tripping. First, the JSON is canonical JSON: object keys are sorted recursively and there is no insignificant whitespace, so the same cells always produce the same block. Second, each cell in the block still carries its hash field, which is the SHA-256 of that cell's canonical payload, so a single altered byte in the block no longer matches its hash.

What is preserved

A pack carries the cells and the integrity data needed to trust them. It does not carry the container, the key, or anything the format never stored.

Preserved in a pack versus not.
PreservedNot preserved
Every cell: id, text, source, role, createdAt, and tags.Confidentiality. The pack is plaintext Markdown; there is no encryption anywhere in it.
Each cell's hash, the SHA-256 of its canonical payload.Embedding vectors. The format stores no embeddings, so none are in the vault or the pack.
The Merkle root over the cells, recorded in the front matter and re-checked on import.The vault container fields: the KDF parameters and salt, the cipher IV, the ciphertext, and the vault's own createdAt.
The full cells array as canonical JSON, in the data block.Attachments, images, and tool calls. Import records text and metadata by design, and the pack inherits that.
A human-readable numbered list of every memory, for reading without tooling.Any key material or recovery path. There is no passphrase, escrow, or key in a pack.

Trust model

A pack is integrity-checked, not confidential. Its hashes and Merkle root detect any change to the plaintext, and unpack refuses a pack that does not verify, but they are not a signature: anyone who can edit the file can also edit the hashes and recompute the root. Use a pack to move and read memory, and keep the encrypted .keepsake vault as the copy you protect. See the spec for the normative rules and the usage page for the full command reference.