← back

The field I left readable on purpose

#privacy-by-design#client-side-encryption#ai-product#trade-offs

Why I kept one AI-generated field as readable text while the rest of each entry is encrypted, and how I filtered it to earn that choice.

The reflex on an app like this is to encrypt everything and call it done. I spent this stretch building a feature that does the opposite on one specific field, on purpose, and I think the deliberate exception is the more honest design than a blanket rule would have been.

Pick two

Three things I wanted, and I could have any two. A companion that remembers enough about your past entries to feel like continuity instead of a stranger every night. Storage where the full text of what you wrote stays encrypted at rest. And full fidelity, the memory working off the complete entry, every word.

Pick all three and they collide. The part that builds the companion's memory runs on the server. The full entries are encrypted with a key that stays on your device, and that key never travels to the server, so a server-side process has ciphertext and nothing to open it with. If I wanted the memory to work off the complete text, I'd have to keep the complete text readable on the server, which gives up the encryption that was the point.

So something had to go, and I gave up full fidelity. The full entry stays encrypted. What the memory runs on instead is a short, deliberately lossy summary, generated when you save and kept as readable text the server can use. Smart memory, encrypted entries, lower fidelity on the one field that has to be readable. Two of three, chosen out loud, rather than pretending I could have all three.

What the AI actually needs

Why keep any readable field at all? Because the alternative is a companion with no memory, and that's a worse product.

Before this, the memory leaned on a single-line summary the model already produced. Functional, but thin. The new field is a fuller picture of an entry's themes, eighty to a hundred and twenty words, enough that the companion can pick up a thread from a few nights ago instead of greeting you cold every time. That continuity is a real part of why the app works for the people using it. Throwing it out to hit a cleaner privacy number would be optimizing the number over the person.

The point of minimizing data isn't to store as little as physically possible. It's to store as little as the product genuinely needs, and to be deliberate about the line. This field is me drawing that line, not pretending it doesn't exist.

Filtering before the model sees it

Keeping a field readable raised the obvious next question. Readable text can carry names, places, the specifics of someone's life. If I'm storing a summary the server can use, I don't want it ferrying the identifying details out of the encrypted entry and into the readable one.

So the summary gets filtered, and the filtering happens before the text reaches the model that writes it, not after. Identifying details are stripped on the way in, which means the model generating the summary never handles them in the first place. What lands in the readable field is the shape of what someone wrote, the themes and the feeling, with the specifics that would tie it to a real person taken out. The full, specific version stays where it belongs, in the encrypted entry behind the device key.

Checking it with a planted name

A filter you haven't watched work is a hope. So I tested it the blunt way. I wrote a journal entry on my own account and planted specifics in it, a person's name and where they worked, the kind of detail you'd actually write at night and not want wandering off.

Then I looked at both summary fields for that entry. The old thin summary, the one without the filter, had the name and the employer sitting right there in the open. The new filtered field had stripped them out while keeping the gist of what the entry was about. Same source text, two paths, and only the unfiltered path leaked. Watching the planted details survive in one field and vanish in the other is the gap between believing the filter works and knowing it does.

Being straight about what it is

I want to be plain about this field. It's readable text, linked to an account, that a server-side process uses. It is not the encrypted part of the entry, and I don't describe it as if it were. The work that earns it is the filtering and the trade-off being made on purpose, not a claim that it's something it isn't.

Encrypting everything would have been the easier story to tell. One rule, no exceptions, nothing to explain. Leaving a single field readable, filtered, and labeled for what it is took more thought and more honesty, and it's the version I'll actually stand behind.

Mental models

You usually get two of three. Smart memory, encrypted-at-rest storage, and full fidelity don't all come for free at once. Name the three, pick the two that serve the person, and don't pretend the third quietly tagged along.

Encrypting everything can be the lazy answer. A blanket rule skips the harder work of deciding what the product actually needs to read and minimizing exactly that. Sometimes the responsible call is a narrow, deliberate exception you can defend.

Filter before the model, not after. If a generated field has to be readable, strip the identifying details on the way in, so the thing that writes it never touches the specifics at all.

Watch the control work before you trust it. Plant the exact data you're afraid of leaking and confirm it's gone. Reasoning that a filter should work is not the same as seeing it strip a planted name out of real text.