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=1is required for thefileKEK 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.
Legal Hold (preserve-but-allow-use, distinct from restriction)
# 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 behindSHOMEI_ALLOW_DEV_CUSTODY=1and are for testing only. Production requires a real custody provider — setSHOMEI_KEK_PROVIDER=aws-kmsand supplySHOMEI_KMS_KEY_ID(plus a strongSHOMEI_COMMIT_SECRETif 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, setSHOMEI_EMBEDDER=ollama(orbedrock) and provide a model; the demo'shashembedder 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_verifypackage lets auditors and regulators verify receipts with public-key crypto alone:python -m shomei_memory_verify receipt.json --pin <key> --render.