Case Study

What I Let the AI Agent Touch, and What I Didn't

Shipping a regulated AI app with an AI coding agent, one founder, and controls that live in permissions instead of prompts.

Technical PM: AI-agent development workflow, permission design, verification gates, release process·October 2026

TL;DR

Duskglow is built by one founder and an AI coding agent: 461 commits, 84 merges into the live app, 1,052 automated tests at the last merge, and more than 100 logged working sessions since June. The agent's controls sit in what it can touch: four permission tiers, a read-only database connection, checks triggered by the kind of claim, gates with a named moment to run, and a human on what only a human can judge. The rules that lived only in instructions were the ones that failed.

The Problem

I'm the only person working on Duskglow, an AI journaling companion regulated under three state AI companion laws. Most of its code was written by an AI coding agent. It started on a no-code builder, moved to Cursor, spent about a week as copy-paste from a chat window, and since April 19 it has been Claude Code, which reads and edits the repository directly and runs commands on my machine.

That last part is the whole problem. An agent that can run commands can push code or change a production database, and it does both at the same speed and with the same confidence whether the step can be undone or not. It doesn't get nervous before an irreversible step. I do, and I'm not an engineer, so I can't review every line it writes.

Most advice on working with coding agents is about the prompt: write better instructions, add rules, tell it to be careful. I tried that first. The rules that lived only in instructions are the ones that failed, and the controls that held were the ones built into what the agent can touch.

Approach

Four tiers, sorted by what can be undone.

Every action the agent might take falls into one of four tiers. I sorted them by one question: if this goes wrong, how hard is it to take back?

The permission tiers
TierWho actsExamples
AThe agent, without askingReading code, local builds, read-only database queries, logs
BThe agent, after I approve each oneEvery push, every merge, installing a package
CThe agent drafts, I do it by handDatabase structure changes, server function deploys, secrets, hosting settings
DNobodyRewriting shared history, force-pushing to the main branch, destructive database commands without confirmation

The rule I'd defend hardest is a small one. When the approval prompt offers “don't ask again,” I never take it, for anything in tier B. The friction is the safety mechanism. A push that needs my yes every time is a push I read every time.

That only works if the approvals stay rare, though. Early on, prompts for harmless reads and builds piled up, and a prompt you see constantly is one you stop reading. The tier-A actions went on an allow list, and the prompts that remain are ones that deserve attention. Approval fatigue is how a review step quietly becomes a rubber stamp, and it happens long before anyone decides to skip review.

Make the risky lines physical.

A rule in the agent's instructions says it must never change the production database. I don't rely on that rule. The database connection the agent uses is read-only at the connection itself, so a write fails even if the agent tries one. I kept the instruction too. It costs nothing, and it means the agent doesn't waste a turn trying.

I made the same call in the other direction. My database provider offers a GitHub integration that applies database changes to production automatically when code merges. It would have saved me a manual step on every structure change. I turned it down, because it would have moved the one irreversible step in my release process out of my hands and into a merge, where the agent already has a path.

Tier C exists for the same reason. For a database change, the agent writes the change and runs it first on a local copy of the database. When it's a fix, it also writes a test that fails before the change and passes after, so we know the problem was real and is gone. Then I paste it into production myself. A server function deploy works the same way: I paste the files, and afterwards the agent reads the deployed files back and compares their fingerprints (hashes) to the ones it wrote. If a byte is off, we know before a user does.

Rules that depend on noticing doubt can't catch confident mistakes.

By early May I had a rule that said, roughly, when you can't verify a technical claim, say so. It failed three times across two sessions, the same way each time. A word count that was wrong. A database feature described as free when it needed the paid plan. A security setting described as enforced when it was only configurable. None came with a hedge, and that's the problem. A rule that waits for the model to feel unsure cannot fire on a claim that feels obviously true.

So I stopped asking the agent to notice and started telling it which kinds of claims always get checked: current prices, what a plan includes, the steps in another company's dashboard, how a specific library version behaves, and any number about content it just wrote, which it has to measure instead of estimate. Inside those categories it checks first, however sure it feels. I wrote up the rule that kept breaking when it happened.

A day later the new rule paid for itself. Before drafting a set of monitoring queries, one check ran against production first and found every row stamped with the same placeholder version number. The queries would have run cleanly and told me nothing. Nobody had doubted that column in the week since I wrote the spec around it, and the check ran anyway (the full story).

I should be honest about the limit, though. The category rule is still an instruction. On the day I wrote it, the agent made a wrong claim about its own output inside one of those categories, and I caught it because I read the change and asked why a file had grown. The rule cut the failure rate, and I still read every change.

Architecture

The agent does most of the work, and a short list of checks stays with me. Each one exists because the agent can't do it, or because getting it wrong would reach a real user.

Where the human still checks
CheckWhenWhy it can't be automated away
Real-phone testAny change to screens, copy, flows or sign-inTests pass on layouts that feel wrong at bedtime in a dark room with the keyboard up
Adversarial suite runAny change to the AI companion's prompt or modelSafety behavior is the product; I run the suite and read every failing row before the change merges
Manual deployDatabase changes and server functionsThe irreversible step stays with the person accountable for it, then gets read back by fingerprint
Session openEvery working sessionThe live system is compared against a recorded baseline before any work starts
Session closeEvery working sessionA handoff note for the next session, because the agent starts each one with no memory of the last

The session open matters more than it looks. The agent starts every session cold, and the project moves underneath it. So before any work, it compares the live database against a recorded fingerprint of its structure and checks a handful of things that must always hold, most of them about who can reach which data. A difference in usage numbers is normal and gets explained. A difference in structure, or a broken must-hold, stops the session until we know why.

The real-phone test caught more than I expected. It's where a passing suite met a bedtime screen, and more than once the suite's idea of done wasn't mine. What “done” means when an AI writes the code turned into its own post, and so did the time every test passed while users got half-finished replies.

What I'd Do Differently

I'd give every gate a named moment to run. One of my browser tests covered the account setup flow. It passed in June and nobody ran it again, because it sat outside the everyday test command. In September two new screens went in front of the steps it walked through, and it failed from then on with no one looking. I found it in October. A test that only runs when someone remembers proves the day it was written and nothing after. The fix was one line in the merge checklist that says when it runs, and it now runs before every merge.

I'd watch the process for weight sooner. I believe setup and structure often pass for progress when they're really avoidance, and that applies to me. Every session ended with an update to a running project record, and every so often a deeper cleanup pass trimmed it. In September I measured that cleanup: it removed about 6 to 7 KB a pass, while a normal working session added almost 8. It had stopped paying for itself and I hadn't noticed, because the ritual felt like diligence. I cut it and moved the design reasoning into one record per decision, which kept the record small without the ritual.

I'd retire workarounds with their reasons. For months I kept three full copies of my writing guidelines, plus a rule to resync them at every edit, because my old tools couldn't read across projects. When everything moved into one place that reason was gone, and the copies stayed anyway until they had drifted apart. A workaround doesn't announce that its reason is gone. Now each one carries the premise it rests on, so the premise can be checked.

Impact

  • 461 commits and 84 merges into a live, regulated AI app, built by one founder and an AI coding agent.
  • 1,052 automated tests at the most recent merge, plus a browser gate on three browser profiles and a founder-run adversarial suite for the AI companion.
  • More than 100 logged working sessions since mid-June, each opened against a live-state check and closed with a handoff.
  • Database structure changes by hand. The agent writes and rehearses them locally; I apply them to production.

The limit, stated plainly: this is one founder on one product. I haven't rolled this system out across a team, where the hard part is getting several people to keep the friction instead of routing around it. What I can show is that it held through six months of building a product where mistakes would reach people at their most vulnerable, and that each time it failed, the failure showed me a rule that needed to become structure.

Principles

Sort permissions by reversibility. Ask what happens if this step is wrong and whether you can take it back, before you ask how capable the agent is. Everything irreversible goes to a person.

Put the dangerous lines in the plumbing. An instruction is a request. A read-only connection is a fact. Where the cost of a mistake is high, make the mistake impossible rather than forbidden.

Check by category, not by confidence. Models are most dangerous when they're sure. Pick the kinds of claims that go stale or get estimated, and make them always-check.

Every gate needs a time to run, or it should go.

Keep approvals rare enough to read. Allow the harmless actions so the approvals that remain get real attention. A review step people click through is worse than none, because it still looks like control.