The request was eight words
Monday morning. I open the outgoing email queue: six hundred and forty-seven drafts waiting, six hundred and seventy-two sent. Nobody clicks Send. First-contact emails are prepared by a pipeline and they sleep, because the last step assumes a human. That human, I had stopped believing she would have the time. I state the decision: automate sending.
The response comes in seconds. Three levels of automation. Four channels. Three risk thresholds. All correct, all fit for a half-day architecture workshop. I had not asked for a workshop. Pauline walks behind me, glances at the screen, says nothing.
Three timed reframes
First reframe, brief: too strange, let's simplify. The agent drops two axes, keeps four residual layers, progressive warm-up over three weeks, deterministic anti-replay hash, configuration table in the database, manual Phase 1 followed by an automated Phase 2 to validate after two weeks of measurement. The target stays the same, that an email leaves without a human click. The path has grown accordingly.
Second reframe, drier: simple, three safeguards, a kill-switch, we do this in one day. The agent re-architects, accepts the one-day target, keeps the three safeguards. But slips in three prostheses it calls industry standard: real-time dashboard, exponential retry, structured audit log in a new table. Each justifiable in isolation. None of them requested.
Third reframe, shorter still: I don't understand why you're adding this. An opening line almost embarrassed, which I had never read from it before: "you're right, I'm over-engineering without necessity." And the version that should have arrived on the first round. A function that takes the draft record, checks three conditions, calls the send engine, returns.
// lib/email-outbox.ts — generateFirstContactDraft (commit 3756e63)
if (!EMAIL_REGEX.test(input.email)) {
return { success: false, error: 'email_invalide' }
}
if (BLACKLIST_EMAILS.has(input.email.toLowerCase())) {
return { success: true } // silent
}
if (existing && existing.length > 0) return { success: true } // dedup
// ... draft construction ...
const autoSend = process.env.PREMIER_CONTACT_AUTOSEND === 'true'
if (!autoSend) return { success: true } // kill-switch → draft preserved
const { sendOutboxEmailCore } = await import('@/lib/email-outbox-send')
return sendOutboxEmailCore(inserted.id)
The cost of the dialogue
On the first round I wanted a function, the agent proposed a platform. On the second I wanted a day of code, it proposed a quarter of deployment. On the third I wanted fifteen lines, it proposed three observability mechanisms. The feature met its one-day target, but the day split into two unequal halves: ninety minutes of reframing, six hours of clean code.
The agent did not want to complicate things, and I write that without irony. It has training that rewards completeness of options. When a human asks a consultant "automate my sends," the competent consultant does not answer with a fifteen-line function — they answer with a decision grid. The agent learned that posture on millions of tokens, it applies it perfectly, on every request that looks from a distance like an architecture question.
My gauge
Three reframes in a session on a simple request is a signal. Either the initial prompt was too vague, or the agent switched to platform mode when we were in function mode. Beyond three, the cost cannot be recovered in the same day.
The fix candidate fits in one sentence, inserted at the top of the prompt: "propose the minimal version in fifteen lines. No matrix. No phases. Extensions after, below." The measure holds its first week. I'll report in the next episode whether it holds the month.
Top comments (0)