Essays on personal AI

Read on Request

The default architecture for reaching your data is a copy held by someone else. Folder access here was built twice in one evening — and the version that survived reads from your own disk at the moment of the question, and holds nothing in between.


21 May 2026·8 min read

An assistant is worth more the more of your material it can reach, and the default architecture for reaching anything is a copy: authorize, sync, index, and from then on the product serves you from its own mirror of your data. The reasons are real — a copy is what makes instant search possible, what background intelligence runs over, what survives your laptop being closed.

And a copy is custody. It outlives the moment of trust that created it. It has to be secured, refreshed, audited and — hardest of all — genuinely deleted. It converts "this assistant may look at my files" into "this company now has my files", which is a materially different sentence, and agreeing to the first is not agreeing to the second. For a product whose pitch is trust it with your working life, which architecture you pick here is not an implementation detail. It is most of the promise.

Last week we built folder access twice in one evening — once each way — and the deletion is the decision this entry records.

Built as a sync, un-built the same evening

Connecting a folder on your own computer was the feature: point the assistant at a directory of project files, notes, exports — the material that never made it into any tool — and have it usable in conversation. The first working version did the obvious thing: it copied. Connected files landed in directories inside the user's vault store, where all the existing machinery — listing, reading, search — worked on them for free. It ran. And looking at it, the sentence it implied was wrong: a user who "connected" a folder had, in fact, uploaded one, and would keep re-uploading it as it changed. The word on the button and the architecture behind it disagreed.

So the copy went. The synced directories were deleted, and what "connect a folder" now means is a permission, not a pipeline: the browser grants the page the right to read inside that folder, and nothing — nothing — is read until the user asks a question that needs it. In the moment of "what's in the licensing doc?", the file is read from the user's own disk, used to answer, and that is the whole event. Between asks, the folder is untouched. There is no background process, no mirror, no watcher.

The privacy properties fall out of the structure rather than the policy, which is where we prefer them. The mechanism is the browser's File System Access API: the folder's access handle lives in IndexedDB on the user's machine — our servers do not hold it and cannot reach the folder, ever, by construction; the server-side system learns which folders exist only when the app chooses to mention them in a message's context. The API itself refuses to reveal the folder's path — a page is told the folder's leaf name and nothing above it, because the shape of your disk is nobody's business. Even the boundary's failure mode is honest: the API exists only in Chromium-family browsers, so elsewhere the feature is absent rather than degraded into an upload.

The bill arrives as engineering

Refusing the copy does not make the problems go away; it moves them, and the week's real work was paying for the move. Three items on the bill are worth anyone's time.

The conversation that ate itself. A model has no memory between turns — whatever it "read" must be re-sent as history on every subsequent turn. So when a user had the assistant read a fifty-file folder inline, those file bodies entered the conversation record, consumed about a third of the context window, and were then replayed on every following turn until the conversation hit the window's ceiling and died. The no-copy contract had collided with the statelessness of the model: the file exists on the user's disk, but the conversation was carrying it forever. The fix strips file bodies from replayed history, keeping only a reference header naming what was read — which immediately creates the next problem: when the model reaches back for that file three turns later, where does it re-read from? Not the user's disk — the moment has passed, the browser may not even be open to that page.

The answer is a loan, not a donation. Files the user has read inline are cached in a hidden, session-keyed area of the document store — invisible to the vault's tree, search and index — and the tool layer enforces that a cached file is readable only by the conversation it was lent to: any other chat, any other agent, any background process asking for that path is told it does not exist. The product's own help copy found the right words and we have not improved on them: it is lending a book, not handing over the shelf. The book stays on a private shelf belonging to that one conversation, and no other reader can pull it.

Every project has a folder called vault. The browser's leaf-name-only rule, correct as privacy, creates a naming collision as UX: connect the working folders of three projects and you may hold three folders all named the same word, with no path to tell them apart — the system literally cannot know more. The only thing that can tell them apart is the person, so the shape of the answer is a name the user assigns. What that name then has to become — the one identifier the agent is told, echoes back, and every surface renders — is the next build, and the part most likely to be got wrong twice.

What this costs

Every read is paid at ask-time — the latency of opening files sits inside the answer rather than being prepaid at sync-time. There is no content index over material we do not hold, so "find the file that mentions the January renewal" across an unread folder means reading, not looking up. And nothing can happen in the background, because the capability itself is alive only while the user's browser session is: the assistant cannot watch, tidy, or act on your local folders while you are away — which is simultaneously the guarantee (it cannot) and the limitation (it cannot), and both halves are true at once. Freshness is the one axis where the ledger runs the other way: a copy is stale from the second it is made, and an on-demand read never is.

The principle we kept, transferable to any integration surface: prefer agency to custody. Reach for data at the moment of the question, in the smallest scope the question needs; hold a copy only where storing is explicitly the point — where the user's intent was "keep this", not "look at this". The copy-based design is not wrong for every product. It is wrong as a default, because it is the maximally convenient architecture for the builder and the maximally consequential one for the user, and defaults should not be set by whoever isn't paying.


Postscript, August 2026. The contract held, and grew a spine we did not have in May: every reach the assistant makes into sensitive data — reads included, not just writes — now leaves a content-free audit mark at the single dispatcher every execution path funnels through, recording which tool touched what class of resource on whose behalf, never the content itself, in a retention-locked Cloud Logging bucket the pod's own service account cannot quietly erase. "What has my assistant looked at?" has a complete answer precisely because looking is an explicit event in this design rather than an ambient property of a mirror. Two loose ends. The session-cache cleanup sweep this entry deferred is still deferred. The naming collision resolved the way the bill predicted, over two passes in June: a user-assigned label, defaulting to the folder name, auto-suffixed on collision, editable in place — and what made it stick was that the label became the identifier everywhere, what the agent is told, what it echoes back, what every surface renders, with disambiguation computed in one place in the data layer so no two surfaces can disagree about which "vault" is which. The folder line now also fires when a message names a connected folder by its label, a word-boundary, case-insensitive match skipping labels under three characters, and the prompt itself now says that the list's absence means nothing off a file-related message. And the synchronising copy was built — which looks like a reversal and is the closing rule being applied: a connected folder can now be pulled into the vault, and every property of how that works is the storing is explicitly the point clause made operational. It runs when somebody asks for it and not otherwise — a button, never a watcher; the destination is chosen rather than assumed; the local side stays read-only, enforced by a test that permits exactly one module in the codebase to open a file for writing, and only for the small marker that gives a folder a durable identity. Two-way sync was designed in full and declined rather than deferred, on the ground that the threshold for a feature whose failure destroys data you do not own is not can we make this reliable but what happens the one time we are not. Connecting still grants a permission; keeping became a separate act with its own consent. What that cost to build, and the identity problem underneath it, is a later entry.

One letter a month, when there is something worth saying.

What we shipped, what we learned, and the occasional thing that did not work. No drip sequence, no launch countdowns.

Unsubscribe any time. Or take the RSS feed instead.