0007 — The clipboard is the airgap
- Status: Proposed
- Date: 2026-07-29
Context
The agent bridge generates a prompt containing worksheet instructions and, optionally, prior answers. The user takes it to an assistant of their choosing, is interviewed by voice, and brings back structured output to import. The natural product instinct is to integrate directly: an API key, a connect button, a conversation inside the app.
Options
O1. Direct provider integration. Rejected. User supplies an API key; the app calls the provider and handles the conversation itself. Much better experience.
O2. Copy out, paste back. Chosen. No keys, no relay, no network. Transfer in both directions is by clipboard.
Decision
Integration would require punching a hole in connect-src. The moment that hole exists,
“the application transmits nothing” becomes unverifiable — it degrades into a promise
about what we do with a connection we opened, which is the ordinary posture of every app
on the internet and worth approximately nothing.
Copy-and-paste makes the application structurally incapable of sending anything anywhere. The user’s choice becomes real rather than nominal. The inconvenience is the mechanism, not a limitation to be engineered away in a later version.
Whether to hand this material to an assistant is the user’s decision to make. The corollary is that the tradeoff must be legible at the moment it is made:
- Show the literal payload. A scrollable preview of the exact text going to the clipboard, not a description of it. Nobody can meaningfully consent to “your relevant answers.”
- Prior answers default to off, opted in per section. Generating a prompt for one task should never quietly bundle four days of reflection. Amended by C6 — a question whose prose is built on an earlier one carries that answer too, on the same tick.
- One plain sentence at the copy control, not a link to a privacy page.
- Say it once. A confirmation dialog on every copy trains people to dismiss it, which is worse than not warning at all.
Consequences
C1. connect-src 'none' stays intact, and
0005 and
0006 stay true. These three decisions hold each
other up; weakening any one collapses the other two.
C2. More friction than an integration would have. Accepted deliberately, and it should not be quietly optimised away by a later change that has forgotten why it is there.
C3. Import needs a real reviewing surface: validate against the schema, show what is new versus changed versus unchanged, and accept field by field. Agent output must never silently overwrite something the user wrote themselves.
C4. The output contract needs a version field, so that a prompt generated by an older build produces something the importer can either recognise or reject cleanly — rather than half-parse.
C4a. Added 2026-08-07. C4 is discharged by 0015, which records the block’s shape, its version rule, and what an importer does with one it cannot read.
C5. This is the one place data leaves the device, and it does so because the user chose it. That is the correct place for that decision to sit.
C6. Added 2026-09-06 (#105). “Opted in per section” is a rule about the control, not about the boundary of what one tick may carry. A question whose own prose is built on an earlier one — “from your circled list”, “for each of your 5” — carries that earlier answer too, or the prompt names material the assistant cannot see and the reader is made to recite their own work aloud, which is the cost 0001 exists to remove.
What holds 2 together at the widened boundary is that the widening is declared, not inferred: a question carries only what its schema entry names, the build refuses an entry naming nothing, and a field may be named on its own so that Day 5’s five “one change” answers travel without the twelve answers beside them. What holds 1 together is that the carried words are in the preview like every other word, and that the tick says so.
Two things are deliberately not done. Nothing travels transitively — a prompt for the operationalising step carries the five, not the five and the ten and the fifty. And no carried answer travels with the instance identifiers 0015 · C3 sends for the question being asked about: those exist so a reply can name the entry it updates, and an identifier for a question the reader did not open is an invitation to overwrite it.