<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DiaryVault</title>
    <description>The latest articles on DEV Community by DiaryVault (@diaryvault).</description>
    <link>https://dev.to/diaryvault</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3759172%2Fd5a83bd6-4446-4a60-a916-848159ed0e60.png</url>
      <title>DEV Community: DiaryVault</title>
      <link>https://dev.to/diaryvault</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diaryvault"/>
    <language>en</language>
    <item>
      <title>Personal AI Should Ask Before It Remembers</title>
      <dc:creator>DiaryVault</dc:creator>
      <pubDate>Sun, 19 Jul 2026 10:58:21 +0000</pubDate>
      <link>https://dev.to/diaryvault/personal-ai-should-ask-before-it-remembers-20h2</link>
      <guid>https://dev.to/diaryvault/personal-ai-should-ask-before-it-remembers-20h2</guid>
      <description>&lt;p&gt;How I built an open source Python memory layer with persistent drafts, explicit human approval, tamper evident finalization, revision history, and provenance aware AI exports.&lt;/p&gt;

&lt;p&gt;Personal AI systems are becoming increasingly capable of remembering.&lt;/p&gt;

&lt;p&gt;They can summarize conversations, identify recurring people, infer locations from photos, detect emotional patterns, and connect events across years.&lt;/p&gt;

&lt;p&gt;But that creates a deeper question:&lt;/p&gt;

&lt;p&gt;When does an AI suggestion become part of someone’s personal history?&lt;/p&gt;

&lt;p&gt;A model might suggest that a photograph was taken in Seoul. It might identify who was present, infer how someone felt, or create a title for the moment.&lt;/p&gt;

&lt;p&gt;The suggestion might be helpful.&lt;/p&gt;

&lt;p&gt;It might also be wrong.&lt;/p&gt;

&lt;p&gt;Either way, it should not silently become fact.&lt;/p&gt;

&lt;p&gt;That is the principle behind version 0.4.0 of the open source DiaryVault Memory Layer:&lt;/p&gt;

&lt;p&gt;AI may suggest. People confirm.&lt;/p&gt;

&lt;p&gt;This release introduces an end to end review workflow for personal memory records, from initial capture through explicit approval, cryptographic finalization, and provenance aware export.&lt;/p&gt;

&lt;p&gt;What is DiaryVault Memory Layer?&lt;/p&gt;

&lt;p&gt;DiaryVault Memory Layer is an open source Python SDK for portable, tamper evident personal memory records.&lt;/p&gt;

&lt;p&gt;Before this release, the SDK already supported:&lt;/p&gt;

&lt;p&gt;Local memory storage&lt;br&gt;
SHA 256 content hashing&lt;br&gt;
AES 256 GCM encryption&lt;br&gt;
HMAC SHA 256 integrity signatures&lt;br&gt;
Tamper detection&lt;br&gt;
Selective context sharing&lt;br&gt;
JSONL exports&lt;br&gt;
RAG ready chunks&lt;br&gt;
Conversation history exports&lt;br&gt;
Personal knowledge graphs&lt;br&gt;
Portable .dvmem records&lt;/p&gt;

&lt;p&gt;Version 0.4.0 adds the missing layer between capture and permanent memory:&lt;/p&gt;

&lt;p&gt;human review.&lt;/p&gt;

&lt;p&gt;Instead of allowing AI generated information to flow directly into a permanent record, applications can now create a draft, attach suggestions, record explicit decisions, approve the result, and finalize it as a verified memory.&lt;/p&gt;

&lt;p&gt;The review workflow&lt;/p&gt;

&lt;p&gt;The lifecycle is intentionally explicit:&lt;/p&gt;

&lt;p&gt;Capture&lt;br&gt;
    ↓&lt;br&gt;
ReviewDraft&lt;br&gt;
    ↓&lt;br&gt;
AI suggestions&lt;br&gt;
    ↓&lt;br&gt;
Accept, edit, or reject&lt;br&gt;
    ↓&lt;br&gt;
Explicit approval&lt;br&gt;
    ↓&lt;br&gt;
Finalized Memory&lt;br&gt;
    ↓&lt;br&gt;
Provenance aware exports&lt;/p&gt;

&lt;p&gt;Each stage has a distinct meaning.&lt;/p&gt;

&lt;p&gt;A suggestion is not a confirmed value.&lt;/p&gt;

&lt;p&gt;An open draft is not an approved memory.&lt;/p&gt;

&lt;p&gt;An approved draft is not permanent until it has been finalized.&lt;/p&gt;

&lt;p&gt;Downstream AI systems can determine whether a memory was reviewed instead of treating every stored value as equally authoritative.&lt;/p&gt;

&lt;p&gt;Creating and persisting a draft&lt;/p&gt;

&lt;p&gt;A review workflow begins inside MemoryVault:&lt;/p&gt;

&lt;p&gt;from diaryvault_memory import MemoryVault&lt;/p&gt;

&lt;p&gt;vault = MemoryVault(&lt;br&gt;
    encryption_key="replace-with-a-private-secret",&lt;br&gt;
    storage_dir="./memory-data",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;draft = vault.create_draft(&lt;br&gt;
    content="She laughed when the dog sneezed.",&lt;br&gt;
    tags=["family"],&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The draft is stored separately from finalized memories.&lt;/p&gt;

&lt;p&gt;That separation matters. Drafts can change during review, while finalized memories represent completed records.&lt;/p&gt;

&lt;p&gt;Applications can save, retrieve, list, and delete drafts:&lt;/p&gt;

&lt;p&gt;vault.save_draft(draft)&lt;/p&gt;

&lt;p&gt;restored = vault.get_draft(draft.draft_id)&lt;/p&gt;

&lt;p&gt;open_drafts = vault.list_drafts(state="open")&lt;/p&gt;

&lt;p&gt;vault.delete_draft(draft.draft_id)&lt;/p&gt;

&lt;p&gt;Draft records live in their own storage directory, so they do not appear in normal memory searches or exports before finalization.&lt;/p&gt;

&lt;p&gt;AI suggestions remain unconfirmed&lt;/p&gt;

&lt;p&gt;An AI system can attach a suggestion without changing the user’s confirmed record:&lt;/p&gt;

&lt;p&gt;draft = draft.add_suggestion(&lt;br&gt;
    field_name="location",&lt;br&gt;
    value="Seoul",&lt;br&gt;
    source="echo",&lt;br&gt;
    model="example-model",&lt;br&gt;
    process_version="memory-card-v1",&lt;br&gt;
    confidence=0.88,&lt;br&gt;
    suggestion_id="suggestion-location",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;draft = draft.add_suggestion(&lt;br&gt;
    field_name="title",&lt;br&gt;
    value="The first laugh",&lt;br&gt;
    source="echo",&lt;br&gt;
    suggestion_id="suggestion-title",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;vault.save_draft(draft)&lt;/p&gt;

&lt;p&gt;Each suggestion may include:&lt;/p&gt;

&lt;p&gt;The proposed field&lt;br&gt;
The proposed value&lt;br&gt;
Its source&lt;br&gt;
The model that created it&lt;br&gt;
A process or prompt version&lt;br&gt;
Confidence when available&lt;br&gt;
A creation timestamp&lt;/p&gt;

&lt;p&gt;None of those suggested values appears in resolved_fields() until a person accepts it.&lt;/p&gt;

&lt;p&gt;Accepting, editing, and rejecting suggestions&lt;/p&gt;

&lt;p&gt;The reviewer may accept the proposed value:&lt;/p&gt;

&lt;p&gt;draft = draft.accept(&lt;br&gt;
    "suggestion-location",&lt;br&gt;
    reviewer="parent",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;They may accept it with a correction:&lt;/p&gt;

&lt;p&gt;draft = draft.accept(&lt;br&gt;
    "suggestion-location",&lt;br&gt;
    reviewer="parent",&lt;br&gt;
    value="Seoul Forest",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Or reject it entirely:&lt;/p&gt;

&lt;p&gt;draft = draft.reject_suggestion(&lt;br&gt;
    "suggestion-title",&lt;br&gt;
    reviewer="parent",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;The corrected value becomes the confirmed value.&lt;/p&gt;

&lt;p&gt;The original AI suggestion remains preserved as provenance.&lt;/p&gt;

&lt;p&gt;This distinction is important. The system does not rewrite the suggestion to make it appear as though the model was correct.&lt;/p&gt;

&lt;p&gt;Approval is a real state transition&lt;/p&gt;

&lt;p&gt;A draft cannot be approved while suggestions remain undecided:&lt;/p&gt;

&lt;p&gt;draft = draft.approve(&lt;br&gt;
    reviewer="parent",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Approval records who completed the review and when.&lt;/p&gt;

&lt;p&gt;The review objects also validate their own invariants. Invalid states are rejected, including:&lt;/p&gt;

&lt;p&gt;Duplicate suggestion identifiers&lt;br&gt;
Decisions referencing unknown suggestions&lt;br&gt;
Multiple decisions for one suggestion&lt;br&gt;
Multiple accepted values for the same field&lt;br&gt;
Approved drafts with pending suggestions&lt;br&gt;
Open drafts carrying completion metadata&lt;br&gt;
Rejected decisions carrying accepted values&lt;/p&gt;

&lt;p&gt;Those checks also run during JSON deserialization.&lt;/p&gt;

&lt;p&gt;Loading a malformed record cannot bypass the same rules used during normal construction.&lt;/p&gt;

&lt;p&gt;Deep immutability&lt;/p&gt;

&lt;p&gt;The review domain uses frozen value objects, but freezing only the outer dataclass is not enough.&lt;/p&gt;

&lt;p&gt;A supposedly frozen suggestion could still contain a mutable dictionary or list.&lt;/p&gt;

&lt;p&gt;Version 0.4.0 recursively freezes JSON style values so callers cannot mutate nested data after creation.&lt;/p&gt;

&lt;p&gt;Conceptually, this is no longer possible:&lt;/p&gt;

&lt;p&gt;draft.suggestions[0].value["people"].append("Someone else")&lt;/p&gt;

&lt;p&gt;Serialization converts the frozen structures back into normal JSON compatible values.&lt;/p&gt;

&lt;p&gt;This gives the domain model immutability during execution without sacrificing portable storage.&lt;/p&gt;

&lt;p&gt;Finalizing an approved memory&lt;/p&gt;

&lt;p&gt;Once approved, the draft can be finalized:&lt;/p&gt;

&lt;p&gt;memory = vault.finalize_draft(draft)&lt;/p&gt;

&lt;p&gt;Finalization reuses the same processing pipeline as ordinary memory creation:&lt;/p&gt;

&lt;p&gt;Content&lt;br&gt;
    ↓&lt;br&gt;
SHA 256 hash&lt;br&gt;
    ↓&lt;br&gt;
AES 256 GCM encryption&lt;br&gt;
    ↓&lt;br&gt;
HMAC SHA 256 signature&lt;br&gt;
    ↓&lt;br&gt;
Local storage&lt;/p&gt;

&lt;p&gt;The resulting memory contains the complete review record under:&lt;/p&gt;

&lt;p&gt;memory.metadata.custom["review"]&lt;/p&gt;

&lt;p&gt;That record includes:&lt;/p&gt;

&lt;p&gt;Original content&lt;br&gt;
Suggestions&lt;br&gt;
Model provenance&lt;br&gt;
User decisions&lt;br&gt;
Accepted corrections&lt;br&gt;
Rejections&lt;br&gt;
Reviewer identity&lt;br&gt;
Approval time&lt;/p&gt;

&lt;p&gt;Other explicitly accepted fields remain available under confirmed metadata.&lt;/p&gt;

&lt;p&gt;For example, an accepted location can become:&lt;/p&gt;

&lt;p&gt;memory.metadata.location&lt;/p&gt;

&lt;p&gt;A finalized draft can be finalized only once.&lt;/p&gt;

&lt;p&gt;It can no longer be replaced or deleted because it has become provenance for the permanent memory.&lt;/p&gt;

&lt;p&gt;That rule also survives a process restart.&lt;/p&gt;

&lt;p&gt;memory_id = vault.finalized_memory_id(draft.draft_id)&lt;br&gt;
Revision history without duplicate state&lt;/p&gt;

&lt;p&gt;Review activity is available through a derived revision history:&lt;/p&gt;

&lt;p&gt;for revision in draft.revision_history():&lt;br&gt;
    print(&lt;br&gt;
        revision.occurred_at,&lt;br&gt;
        revision.action,&lt;br&gt;
        revision.actor,&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;Typical actions include:&lt;/p&gt;

&lt;p&gt;draft_created&lt;br&gt;
suggestion_added&lt;br&gt;
suggestion_accepted&lt;br&gt;
suggestion_rejected&lt;br&gt;
draft_approved&lt;/p&gt;

&lt;p&gt;The revision history is not stored as a second mutable event log.&lt;/p&gt;

&lt;p&gt;It is derived from the timestamps already present on the draft, suggestions, decisions, and terminal approval.&lt;/p&gt;

&lt;p&gt;This avoids two sources of truth.&lt;/p&gt;

&lt;p&gt;The history cannot drift away from the record it describes.&lt;/p&gt;

&lt;p&gt;The missing piece: provenance aware exports&lt;/p&gt;

&lt;p&gt;Preserving review information inside the vault is not enough.&lt;/p&gt;

&lt;p&gt;The distinction between an AI suggestion and a human confirmed value must survive when the data enters another system.&lt;/p&gt;

&lt;p&gt;Version 0.4.0 therefore carries review provenance into the export layer.&lt;/p&gt;

&lt;p&gt;RAG chunks&lt;br&gt;
from diaryvault_memory import VaultExporter&lt;/p&gt;

&lt;p&gt;exporter = VaultExporter(vault)&lt;/p&gt;

&lt;p&gt;chunks = exporter.to_rag_chunks()&lt;/p&gt;

&lt;p&gt;review = chunks[0].metadata["review"]&lt;/p&gt;

&lt;p&gt;A reviewed memory may expose a compact summary such as:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
    "reviewed": True,&lt;br&gt;
    "approved_by": "parent",&lt;br&gt;
    "approved_at": "2026-07-19T10:30:00+00:00",&lt;br&gt;
    "suggestion_count": 2,&lt;br&gt;
    "accepted_count": 1,&lt;br&gt;
    "confirmed_fields": ["location"],&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;An ordinary memory without a review record is marked clearly:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
    "reviewed": False,&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;A retrieval system can now rank, filter, or label memories based on whether a person explicitly reviewed them.&lt;/p&gt;

&lt;p&gt;Generic JSONL&lt;/p&gt;

&lt;p&gt;Review summaries are included in the generic JSONL format:&lt;/p&gt;

&lt;p&gt;examples = exporter.to_jsonl(&lt;br&gt;
    format="generic",&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;Each record can carry both the memory content and its confirmation status.&lt;/p&gt;

&lt;p&gt;Conversation history&lt;/p&gt;

&lt;p&gt;Conversation exports include the review summary in their metadata:&lt;/p&gt;

&lt;p&gt;history = exporter.to_conversation_history()&lt;/p&gt;

&lt;p&gt;review = history[0]["metadata"]["review"]&lt;/p&gt;

&lt;p&gt;This allows an assistant to distinguish reviewed personal history from unreviewed information when reconstructing context.&lt;/p&gt;

&lt;p&gt;Knowledge graphs&lt;/p&gt;

&lt;p&gt;Memory nodes in the personal knowledge graph also contain review metadata:&lt;/p&gt;

&lt;p&gt;graph = exporter.to_knowledge_graph()&lt;/p&gt;

&lt;p&gt;memory_nodes = [&lt;br&gt;
    node&lt;br&gt;
    for node in graph.nodes&lt;br&gt;
    if node.node_type == "memory"&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;The provenance therefore stays attached as memories become nodes and relationships.&lt;/p&gt;

&lt;p&gt;Schema clean fine tuning exports&lt;/p&gt;

&lt;p&gt;The OpenAI and Anthropic fine tuning formats intentionally remain schema clean.&lt;/p&gt;

&lt;p&gt;The SDK does not inject custom review fields into formats that expect a specific message structure.&lt;/p&gt;

&lt;p&gt;Applications that need the provenance can use the generic JSONL, RAG, conversation, or graph exports.&lt;/p&gt;

&lt;p&gt;Why this matters for personal AI&lt;/p&gt;

&lt;p&gt;Imagine a retrieval system containing these two values:&lt;/p&gt;

&lt;p&gt;Location: Seoul&lt;br&gt;
Location: Seoul Forest&lt;/p&gt;

&lt;p&gt;Without provenance, a downstream model cannot know:&lt;/p&gt;

&lt;p&gt;Which value came from the AI&lt;br&gt;
Which value the user corrected&lt;br&gt;
Whether either value was reviewed&lt;br&gt;
When the decision happened&lt;br&gt;
Who approved it&lt;/p&gt;

&lt;p&gt;With the review record, the system can understand that:&lt;/p&gt;

&lt;p&gt;AI suggested: Seoul&lt;br&gt;
User confirmed: Seoul Forest&lt;br&gt;
Reviewer: parent&lt;br&gt;
Status: approved&lt;/p&gt;

&lt;p&gt;That is not just more metadata.&lt;/p&gt;

&lt;p&gt;It changes the meaning of the record.&lt;/p&gt;

&lt;p&gt;A complete example&lt;br&gt;
from tempfile import TemporaryDirectory&lt;/p&gt;

&lt;p&gt;from diaryvault_memory import MemoryVault, VaultExporter&lt;/p&gt;

&lt;p&gt;with TemporaryDirectory() as storage_dir:&lt;br&gt;
    vault = MemoryVault(&lt;br&gt;
        encryption_key="synthetic-example-key",&lt;br&gt;
        storage_dir=storage_dir,&lt;br&gt;
    )&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;draft = vault.create_draft(
    content="She laughed when the dog sneezed.",
    tags=["family"],
)

draft = draft.add_suggestion(
    field_name="location",
    value="Seoul",
    source="echo",
    model="example-model",
    confidence=0.88,
    suggestion_id="suggestion-location",
)

draft = draft.add_suggestion(
    field_name="title",
    value="The first laugh",
    source="echo",
    suggestion_id="suggestion-title",
)

draft = draft.accept(
    "suggestion-location",
    reviewer="parent",
    value="Seoul Forest",
)

draft = draft.reject_suggestion(
    "suggestion-title",
    reviewer="parent",
)

draft = draft.approve(
    reviewer="parent",
)

memory = vault.finalize_draft(draft)

assert vault.verify(memory)
assert memory.metadata.location == "Seoul Forest"

for revision in draft.revision_history():
    print(revision.action)

exporter = VaultExporter(vault)

chunks = exporter.to_rag_chunks()

print(chunks[0].metadata["review"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Testing the trust boundary&lt;/p&gt;

&lt;p&gt;Version 0.4.0 is covered by 122 tests.&lt;/p&gt;

&lt;p&gt;The test suite includes:&lt;/p&gt;

&lt;p&gt;Review state transitions&lt;br&gt;
Deep nested immutability&lt;br&gt;
Invalid deserialization&lt;br&gt;
Draft persistence and reload&lt;br&gt;
Draft filtering and deletion&lt;br&gt;
Finalization requirements&lt;br&gt;
Single finalization guarantees&lt;br&gt;
Finalization after process restart&lt;br&gt;
Derived revision history&lt;br&gt;
Approval aware RAG exports&lt;br&gt;
Approval aware generic JSONL&lt;br&gt;
Conversation history provenance&lt;br&gt;
Knowledge graph provenance&lt;br&gt;
Reviewed memory export counts&lt;br&gt;
Compatibility with existing memory features&lt;/p&gt;

&lt;p&gt;The package passes Ruff checks, runs both public examples, and builds versioned source and wheel distributions.&lt;/p&gt;

&lt;p&gt;Honest boundaries&lt;/p&gt;

&lt;p&gt;This is still Alpha software.&lt;/p&gt;

&lt;p&gt;The SDK does not:&lt;/p&gt;

&lt;p&gt;Call an AI model&lt;br&gt;
Decide whether a suggestion is true&lt;br&gt;
Provide independently trusted timestamps&lt;br&gt;
Establish legal authorship&lt;br&gt;
Synchronize data through a cloud service&lt;br&gt;
Integrate directly with the production DiaryVault mobile application&lt;br&gt;
Guarantee that an exported copy can later be revoked&lt;/p&gt;

&lt;p&gt;Drafts are local application records, while finalized memories pass through the SDK’s hashing, encryption, signing, and storage pipeline.&lt;/p&gt;

&lt;p&gt;Applications should treat the entire storage directory as sensitive and require explicit approval before exporting personal data.&lt;/p&gt;

&lt;p&gt;Why I built this&lt;/p&gt;

&lt;p&gt;I am building DiaryVault around a simple idea:&lt;/p&gt;

&lt;p&gt;Personal AI should help people remember without quietly rewriting their history.&lt;/p&gt;

&lt;p&gt;AI can organize memories.&lt;/p&gt;

&lt;p&gt;It can suggest context.&lt;/p&gt;

&lt;p&gt;It can identify patterns that would otherwise disappear across thousands of photographs and journal entries.&lt;/p&gt;

&lt;p&gt;But the final authority should remain human.&lt;/p&gt;

&lt;p&gt;That principle should not live only in interface copy or a confirmation button.&lt;/p&gt;

&lt;p&gt;It should exist in the underlying data model, persistence rules, integrity checks, revision history, and export formats.&lt;/p&gt;

&lt;p&gt;That is what version 0.4.0 is designed to provide.&lt;/p&gt;

&lt;p&gt;Try the project&lt;/p&gt;

&lt;p&gt;The project is open source under the MIT license:&lt;/p&gt;

&lt;p&gt;DiaryVault Memory Layer on GitHub&lt;/p&gt;

&lt;p&gt;git clone &lt;a href="https://github.com/DiaryVault/diaryvault-memory-layer.git" rel="noopener noreferrer"&gt;https://github.com/DiaryVault/diaryvault-memory-layer.git&lt;/a&gt;&lt;br&gt;
cd diaryvault-memory-layer&lt;/p&gt;

&lt;p&gt;python3 -m venv .venv&lt;br&gt;
source .venv/bin/activate&lt;/p&gt;

&lt;p&gt;python -m pip install --upgrade pip&lt;br&gt;
python -m pip install -e ".[dev]"&lt;/p&gt;

&lt;p&gt;python -m ruff check sdk tests examples&lt;br&gt;
python -m pytest tests/ -q&lt;br&gt;
python examples/review_workflow.py&lt;/p&gt;

&lt;p&gt;Personal AI will remember more about us.&lt;/p&gt;

&lt;p&gt;The infrastructure underneath it should ensure that we remain the ones who decide what becomes part of the record.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>ai</category>
      <category>privacy</category>
    </item>
    <item>
      <title>I built a cryptographic memory layer for humans in Python tags: python, opensource, security, blockchain</title>
      <dc:creator>DiaryVault</dc:creator>
      <pubDate>Sat, 07 Feb 2026 22:43:51 +0000</pubDate>
      <link>https://dev.to/diaryvault/i-built-a-cryptographic-memory-layer-for-humans-in-python-tags-python-opensource-security-20jo</link>
      <guid>https://dev.to/diaryvault/i-built-a-cryptographic-memory-layer-for-humans-in-python-tags-python-opensource-security-20jo</guid>
      <description>&lt;p&gt;Planes have black boxes. Cars have dash cams. Companies have audit logs.&lt;br&gt;
Humans have... memory. And memory is terrible.&lt;br&gt;
You forget 70% of new information within 24 hours. Meanwhile, AI can now generate fake photos, voices, and text indistinguishable from reality. So I asked myself: what if there was a way to create a tamper-proof, encrypted, verifiable record of your life?&lt;br&gt;
I built it over a weekend and open sourced it. Here's how.&lt;br&gt;
What it does&lt;br&gt;
DiaryVault Memory Layer is a Python SDK that turns any text — journal entries, notes, decisions, thoughts — into cryptographically verified, encrypted, permanent memory records.&lt;br&gt;
You write → SHA-256 hashed → AES-256 encrypted → HMAC signed → optionally anchored on-chain&lt;br&gt;
Five lines to get started:&lt;br&gt;
pythonfrom diaryvault_memory import MemoryVault&lt;/p&gt;

&lt;p&gt;vault = MemoryVault(encryption_key="your-secret-key")&lt;br&gt;
memory = vault.create(&lt;br&gt;
    content="Today I decided to start a company.",&lt;br&gt;
    tags=["career", "milestone"]&lt;br&gt;
)&lt;br&gt;
print(memory.hash)      # a7f3b2c1d4e5...&lt;br&gt;
print(memory.verified)  # True&lt;br&gt;
That's it. Your memory is now hashed, encrypted, signed, and stored.&lt;br&gt;
The architecture&lt;br&gt;
The system has four layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture Layer — Gets data in. Manual entries now, AI agents later.&lt;/li&gt;
&lt;li&gt;Synthesis Layer — AI enrichment. Summarization, pattern detection, emotional analysis. (Coming in v0.2)&lt;/li&gt;
&lt;li&gt;Verification Layer — The cryptographic core. This is where it gets interesting.&lt;/li&gt;
&lt;li&gt;Permanence Layer — Where verified hashes get anchored. Local storage, Arweave, Ethereum L2, or IPFS.
The crypto decisions I made (and why)
This was the part I spent the most time thinking about. Every choice here matters because if the crypto is wrong, the whole project is meaningless.
Key derivation: HKDF, not raw SHA-256
My first implementation derived encryption and signing keys by doing SHA-256(master_key + purpose). It worked, but it's not how serious cryptographic systems do it.
I switched to HKDF (RFC 5869), which is the industry standard used by TLS 1.3 and the Signal Protocol:
pythonfrom cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;def _derive_key(self, purpose: bytes) -&amp;gt; bytes:&lt;br&gt;
    hkdf = HKDF(&lt;br&gt;
        algorithm=hashes.SHA256(),&lt;br&gt;
        length=32,&lt;br&gt;
        salt=None,&lt;br&gt;
        info=purpose,&lt;br&gt;
    )&lt;br&gt;
    return hkdf.derive(self._master_key)&lt;br&gt;
Why does this matter? HKDF properly separates the "extract" and "expand" phases of key derivation, making it resistant to related-key attacks. Raw SHA-256 concatenation can leak information about the master key if an attacker sees multiple derived keys.&lt;br&gt;
Encryption: AES-256-GCM&lt;br&gt;
I chose AES-256-GCM over alternatives like ChaCha20-Poly1305 for one reason: ubiquity. AES-GCM is hardware-accelerated on virtually every modern CPU, it's NIST-approved, and every security auditor on earth knows how to review it.&lt;br&gt;
GCM mode is critical — it provides both confidentiality (nobody can read it) AND authenticity (nobody can tamper with it without detection). A unique 96-bit nonce per encryption prevents pattern analysis:&lt;br&gt;
pythondef encrypt(self, plaintext: str) -&amp;gt; tuple[bytes, bytes]:&lt;br&gt;
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce = os.urandom(12)
aesgcm = AESGCM(self._enc_key)
ciphertext = aesgcm.encrypt(nonce, plaintext.encode("utf-8"), None)
return ciphertext, nonce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Signing: HMAC-SHA256&lt;br&gt;
Every memory hash gets signed with HMAC-SHA256. This proves that the holder of the key created the hash — not just that the hash exists.&lt;br&gt;
Batch verification: Merkle trees&lt;br&gt;
If you have 1,000 memories, you don't want to anchor 1,000 hashes on-chain. Merkle trees let you compute a single root hash that verifies the entire batch:&lt;br&gt;
         [Root Hash]        ← Anchor this ONE hash&lt;br&gt;
          /        \&lt;br&gt;
    [Hash AB]    [Hash CD]&lt;br&gt;
     /    \       /    \&lt;br&gt;
   [A]   [B]   [C]   [D]   ← Individual memories&lt;br&gt;
One hash on-chain. Thousands of memories verified. Cost: one transaction.&lt;br&gt;
Tamper detection in action&lt;br&gt;
This is my favorite part. If someone changes even one character, the verification fails:&lt;br&gt;
pythonmemory = vault.create(content="I said this.")&lt;br&gt;
vault.verify(memory)  # True&lt;/p&gt;

&lt;p&gt;memory.content = "I NEVER said this."&lt;br&gt;
vault.verify(memory)  # False — hash mismatch detected&lt;br&gt;
The SHA-256 hash acts as a fingerprint. Any modification, no matter how small, produces a completely different hash. Combined with the HMAC signature, you get proof of both content and authorship.&lt;br&gt;
The .dvmem open format&lt;br&gt;
I didn't want to lock anyone into a proprietary format. The .dvmem format is a documented JSON structure that any tool can read:&lt;br&gt;
json{&lt;br&gt;
  "dvmem_version": "1.0",&lt;br&gt;
  "encoding": "utf-8",&lt;br&gt;
  "payload": {&lt;br&gt;
    "id": "550e8400-...",&lt;br&gt;
    "content": "...",&lt;br&gt;
    "hash": "a1b2c3d4...",&lt;br&gt;
    "encrypted_content": "...",&lt;br&gt;
    "signature": "...",&lt;br&gt;
    "created_at": "2025-02-07T14:32:01+00:00",&lt;br&gt;
    "metadata": {&lt;br&gt;
      "tags": ["daily", "career"],&lt;br&gt;
      "mood": "optimistic",&lt;br&gt;
      "source": "manual"&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Export your data anytime. No lock-in. If this project disappears tomorrow, your memories survive.&lt;br&gt;
What I learned shipping my first open source project&lt;br&gt;
A few things surprised me:&lt;br&gt;
The README matters more than the code. I spent as much time on the README as on the SDK itself. If someone can't understand your project in 30 seconds, they leave.&lt;br&gt;
pip install has to work. Sounds obvious, but I almost launched without publishing to PyPI. A developer who can't install your package in 5 seconds will never try it.&lt;br&gt;
CI signals legitimacy. Adding GitHub Actions with a green badge took 10 minutes but immediately made the project look more real.&lt;br&gt;
Start small. The SDK does one thing: hash, encrypt, verify, store. I had grand visions of AI agents, blockchain anchoring, and a mobile SDK. All of that is on the roadmap, but none of it is in v0.1. Ship the core, see if anyone cares, then build what people ask for.&lt;br&gt;
What's next&lt;br&gt;
The roadmap is public, but honestly it depends on what the community wants:&lt;/p&gt;

&lt;p&gt;AI capture agents (v0.2)&lt;br&gt;
Arweave and Ethereum L2 anchoring (v0.3)&lt;br&gt;
Photo and voice capture (v0.4)&lt;br&gt;
Dead man's switch for digital legacy (v0.5)&lt;br&gt;
Personal AI training export (v0.6)&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
bashpip install diaryvault-memory&lt;/p&gt;

&lt;p&gt;GitHub: github.com/DiaryVault/diaryvault-memory-layer&lt;br&gt;
Landing page: memory.diaryvault.com&lt;br&gt;
PyPI: pypi.org/project/diaryvault-memory&lt;/p&gt;

&lt;p&gt;MIT licensed. 28 tests passing. No VC. No tokens. Just a thing I think should exist.&lt;br&gt;
Stars and feedback welcome. Especially on the crypto — I'd love eyes from security folks on the implementation.&lt;/p&gt;

&lt;p&gt;I'm Stephen — I build AI products including DiaryVault and Crene. This is my first open source project. You can find me on Twitter and GitHub.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>security</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
