DOCS

Start with the local service, add memory, recall with source quotes, and verify governed-action receipts with a pinned public key.

Shomei Quickstart

Shomei Governed Memory is a memory layer for AI agents with provable, governed deletion: every delete, restrict, and subject-erasure operation emits a content-free, ed25519-signed receipt that you can verify offline with a pinned signer key.

Under the hood, Shomei crypto-erases memory rows per-tenant with your own key (BYOK); deletion destroys the per-row key, and every receipt is a signature the operator cannot forge — proving the operator authored the erasure attestation (not that every copy everywhere was destroyed).

Setup (5 minutes)

Start the local service (Python 3.10+; no AWS required). A bare editable install is enough to run it; the engine installs its audited crypto dependency plus exact-pinned temporal parser dependencies:

python3.12 -m venv .venv
. .venv/bin/activate
pip install -e .

SHOMEI_API_KEYS="devkey:acme" \
SHOMEI_KEK_PROVIDER=file \
SHOMEI_ALLOW_DEV_CUSTODY=1 \
SHOMEI_COMMIT_SECRET="local-dev-commit-secret-32-bytes" \
SHOMEI_DATA_DIR=./mochi-data \
SHOMEI_EMBEDDER=hash \
PYTHONPATH=$PWD python3.12 -m service.app

On boot the service prints its bind address and the per-tenant signer public key (pin this for offline verification):

[shomei] governed-memory service on http://127.0.0.1:8088  kek=file  custody=DEV-OPT-IN  embedder=hash  tenants=1  ...
[shomei] receipt signer (per-tenant KEK (custody v2-kek-hkdf))  tenant=acme  public_key=f898d38d...

SHOMEI_ALLOW_DEV_CUSTODY=1 is required for the file KEK provider and the dev commit secret — without it the service refuses to start (fail-closed). This is for local testing only; see Honest Boundaries.

In another shell, export the client environment. The default service port is 8088:

export SHOMEI_BASE_URL="http://127.0.0.1:8088"
export SHOMEI_API_KEY="devkey"

Core Verbs: Add, Search, Delete, Restrict, Verify

Add a memory and search it back

from mochi_memory import RemoteMemory

m = RemoteMemory.from_env()

# Add a memory
result = m.add("Dana prefers email contact", user_id="u1")
memory_id = result["results"][0]["id"]  # e.g., lm_abc123...
print(f"Added memory: {memory_id}")

# Search for it (semantic search; demo uses the hash embedder, so matching is keyword-ish)
hits = m.search("how to contact Dana", user_id="u1")
print(f"Found {len(hits['results'])} hit(s)")
for hit in hits["results"]:
    print(f"  - {hit['id']}: {hit['memory'][:50]}...")

Each search result carries id, memory (the stored text), user_id, and score; the top-level response also includes recall_label and degraded flags.

Delete with a verifiable receipt

# Delete a memory (governed crypto-erasure)
deletion = m.delete(memory_id)
print(f"Deleted: {deletion['deleted']}")  # True if successful

# The signed receipt proves deletion (no content, just a signature)
if deletion.get("signed_receipt"):
    receipt = deletion["signed_receipt"]
    print(f"Receipt hash: {receipt['receipt_hash'][:32]}...")

A successful delete() returns {message, deleted, receipt, receipt_hash, key_custody, retirement_hashes, signed_receipt}. The signed_receipt envelope has exactly these keys: schema, receipt_hash, signer_id, public_key_hex, signature_hex, body.

Verify a deletion receipt (offline, pinned-key verification)

# Fetch (or pin out-of-band) the signer's public key
pin = m.signer_key()["public_key_hex"]
print(f"Signer key: {pin[:32]}...")

# Verify the receipt client-side against the pinned key
result = m.verify_receipt(deletion["signed_receipt"], expected_public_key_hex=pin)
print(f"Receipt authenticated: {result.authenticated}")  # True if signature + pin match
print(f"Receipt valid: {result.valid}")                  # True if well-formed + signature consistent

This verification runs offline using public-key crypto and a pinned signer key — no network call. verify_receipt is a static method that delegates to the standalone shomei_memory_verify package and returns a VerifyResult. The authentication gate is .authenticated (signature valid and matches the key you pinned); .valid alone only means the receipt is self-consistent. In production, pin the signer key out-of-band rather than fetching it from the same service.

Restrict a memory (Art.18: out-of-recall, preserved, reversible)

Restriction is wrapped on both SDK surfaces (the routes require an admin-scoped key):

out = m.restrict(memory_id)          # POST /v1/memories/{id}/restrict
print(out["restricted"], out["changed"])   # True True
assert m.get(memory_id) is None      # out of recall, but preserved

m.release_restriction(memory_id)     # DELETE /v1/memories/{id}/restrict
assert m.get(memory_id) is not None  # back in normal recall

The in-process Memory(...).restrict(memory_id) / release_restriction(memory_id) are the same verbs without the network boundary — see the SDK reference; the raw routes are in the HTTP API reference.

A restricted memory is excluded from search/get but preserved for audit, and is fully reversible. The operation emits a signed restriction receipt (schema: shomei.restriction.v1) you verify exactly like a deletion receipt (above).

Subject erasure (GDPR Art.17): forget

# Erase ALL live memories for a subject (user_id)
forget_result = m.forget(user_id="u1")
print(f"Erased: {forget_result['erased_count']} memories")
print(f"Deferred (under hold): {forget_result['deferred_count']} memories")

# Memories under legal hold are deferred (restricted now, auto-erased on release)
for deferred_id in forget_result.get("deferred_held", []):
    print(f"  - {deferred_id} will be erased on hold release")

forget() is a server-side, durably journaled operation. Shomei snapshots opaque target ids before the first erase and replays any unfinished targets on workspace reopen. It is not literal cross-provider ACID; external cleanup can remain explicitly pending. The response is a rich evidence bundle: the subject is under subject, counts under erased_count / deferred_count, per-memory proofs under signed_receipts (a list), and — when a signer and commitment key are configured — a subject-level closure under closure_receipt, closure_receipt_hash, and signed_closure_receipt (schema: shomei.subject_closure.v1). There is no top-level signed_receipt on forget; use signed_closure_receipt for the subject-level proof.

# Place a hold: the memory stays readable, but erase/update are blocked
hold_result = m.hold(memory_id)
print(f"On hold: {hold_result['held']}")  # True, with changed=True

# Try to delete while held — it fails
delete_attempt = m.delete(memory_id)
print(f"Delete blocked: {delete_attempt['deleted']}")  # False
print(f"Held: {delete_attempt.get('held')}")           # True

# Release the hold
m.release(memory_id)

# Now delete works
deletion = m.delete(memory_id)
print(f"Deleted after release: {deletion['deleted']}")  # True

Legal hold preserves a memory but still allows it to be used in recall — it is distinct from Art.18 restriction (which takes a memory out of recall). hold() / release() return {id, held, changed}.

Subject Access Export (Art.15): signed evidence

# Export the evidence bundle for a subject (content-free by default)
export = m.export(user_id="u1")
print(f"Live governed memories: {export['count']}")
print(f"Total live records (incl. derived): {export['live_count']}")

# The export also discloses the subject's DERIVED-ARTIFACT set (content-free opaque ids)
for art in export["derived"]:
    print(f"  - {art['id']} (recallable={art['recallable']})")  # e.g. artifact:<hex>

# Opt in to content
export_with_content = m.export(user_id="u1", include_content=True)
for mem in export_with_content["memories"]:
    print(f"  - {mem['id']}: {mem.get('memory', '<omitted>')[:40]}...")

# The export carries a signed receipt you can verify offline
pin = m.signer_key()["public_key_hex"]
result = m.verify_receipt(export["signed_receipt"], expected_public_key_hex=pin)
print(f"Export receipt verified: {result.authenticated}")

The Art.15 export mirrors the Art.17 deletion cascade: alongside memories (live governed rows, each carrying a recallable flag) and erased (content-free erasure evidence), it now discloses the subject's derived-artifact set under derived — a list of content-free opaque ids ({"id": "artifact:<hex>", "status": "active", "recallable": <bool>}). recallable is honest per node: true only when at least one supporting source is still served. live_count includes these derived ids, and they are bound into the signed witness (set-schema shomei.subject_export_set.v2). The signed receipt body schema is shomei.subject_export.v1. The witness proves the delivered set is untampered as of export time; it does not, on its own, prove the operator's enumeration was complete (that is the Tier-3 measured-enclave roadmap).

RemoteMemory.export(*, user_id, include_content=False) is the full HTTP surface — it does not take now or include_erased (those exist only on the in-process engine).

Full Example: Round-trip with verification

from mochi_memory import RemoteMemory

m = RemoteMemory.from_env()

# 1. Add
mem = m.add("Support ticket #42: customer in timezone PST", user_id="u_alice")
mid = mem["results"][0]["id"]

# 2. Search
hits = m.search("timezone", user_id="u_alice")
assert any(h["id"] == mid for h in hits["results"]), "Memory not found in search"

# 3. Delete with receipt
deletion = m.delete(mid)
assert deletion["deleted"] is True, "Delete failed"

# 4. Verify offline with a pinned signer key
pin = m.signer_key()["public_key_hex"]
result = m.verify_receipt(deletion["signed_receipt"], expected_public_key_hex=pin)
assert result.authenticated, "Receipt signature does not match pinned key"

# 5. Confirm: the memory is gone and cannot be retrieved
gone = m.get(mid)
assert gone is None, "Memory should be gone after deletion"

print("Governed crypto-erasure verified (signature + pinned key)")

Honest Boundaries

  • Tier 2 today: receipts are operator-verifiable, not yet independently attested. The receipt proves the operator authored a content-free statement you can check offline; TEE attestation (Tier 3) is on the roadmap, not shipped.
  • Key custody is the proof boundary: Shomei's guarantee is only as strong as your KMS/BYOK provider. The file-based local-dev keys (and the dev commit secret) are gated behind SHOMEI_ALLOW_DEV_CUSTODY=1 and are for testing only. Production requires a real custody provider — set SHOMEI_KEK_PROVIDER=aws-kms and supply SHOMEI_KMS_KEY_ID (plus a strong SHOMEI_COMMIT_SECRET if applicable).
  • At rest, payload and embedding cells are sealed with per-row DEK envelope encryption, not SQLCipher page encryption. Each tenant gets its own workspace KEK; deletion is a crypto-erasure of the per-row key.
  • Receipts are content-free. They prove the operator authored the deletion/restriction/export statement; they do not prove that copies or backups held elsewhere (outside this store) were also erased.
  • The embedder default is hash (non-semantic). For semantic search, set SHOMEI_EMBEDDER=ollama (or bedrock) and provide a model; the demo's hash embedder is functional but not semantic.

Environment Variables (client)

Variable Default Purpose
SHOMEI_BASE_URL (required) e.g., http://127.0.0.1:8088
SHOMEI_API_KEY (required) e.g., devkey (matched against SHOMEI_API_KEYS on the service)
SHOMEI_CA_CERT (none) Path to a CA cert for HTTPS (self-signed: pin it)
SHOMEI_TLS_INSECURE 0 Set to 1 to skip TLS verification (demo only; plaintext or MITM-able)

The legacy MOCHI_* names are still honored as a fallback, but SHOMEI_* is canonical and wins when both are set.

What's Next

  • Production deployment: See the deployment guide for customer-VPC hardening, KMS integration, and the full SHOMEI_* service configuration.
  • In-process SDK: Use mochi_memory.Memory.from_profile(...) for local agent memory without a network boundary, including the full verb surface (restrict(), export(), forget()). See the SDK reference.
  • Offline receipt verification: The standalone shomei_memory_verify package lets auditors and regulators verify receipts with public-key crypto alone: python -m shomei_memory_verify receipt.json --pin <key> --render.