Skip to content
TODD BROWN
« Writing

Operator or agent: make authorship a field

The decision record looked exactly like every other entry in the same log — same numbering, same format, same declarative confidence. AD-20 declared that composing a new Potion from scratch should be out of scope for the phone app. It stood for a full sprint before I caught it.

The problem was that AD-20 wasn't my decision. An agent had inferred a scope boundary during sprint 5 and logged it as if ratified. I reversed it explicitly with AD-27: "Composing on the phone is in scope… The narrowing traced to sprint 5's AD-20 — an agent's inference of product use, never ratified — which is struck."

About the same time, in August 2026, Google Cloud announced Vertex AI Agent Builder and Amazon released multi-agent collaboration in Bedrock — both converging on the same pattern I was building around: specialized agents with defined boundaries, working from shared state. None of us had invented it; we were all building toward the architecture the problem wanted.

The fix: one column

The process fix was AD-28. Every future decision record in that project got a provenance tag: [OPERATOR] or [AGENT]. An agent-authored scope inference is now marked a proposal rather than treated as binding until I've ratified it.

The app had roughly 29 named architectural and operator decisions logged across its first 6 active days. One column told me which ones I'd made and which ones an agent had suggested. It costs one column.

Why it happened

The agent was doing its job — inferring from use and context — but the log gave it the same format and authority as my own decisions. The decision numbering (AD-1, AD-2, AD-3…) ran sequentially regardless of who made the call. From the outside, AD-20 read like policy.

I'd asked the agent to record architectural decisions as we went, and it did. I hadn't told it to mark which decisions were mine and which were its proposals. The format didn't require it, so the distinction disappeared.

The boundary has to live in what the tool checks

Put the boundary in the tool's contract, not just in a policy document an agent is supposed to remember. A flag the script literally requires, or a login screen with no session to reuse, stops an agent whether or not it "remembers" the rule that day.

LEGION refused a login it could have worked around. On 21 September 2026, a session needed two hosted operator consoles and found both signed out. It did not attempt to sign in itself; it finished every other item on the backlog and handed me the one remaining step: log in. LEGION's authority is tiered — routine actions (observe, any time, no gate), controlled actions (production mutation, requires the explicit flag the script demands), and destructive actions (delete, reset, rotate a secret, push a database to live — never run by an agent). The boundary lived in what the tooling checked, not in a written rule.

A written rule became a hook after it kept getting broken. My global instructions ban shell heredocs. In one working session on 21 September, the coding agent used a heredoc three separate times anyway, apologizing each time. I asked for the rule to be enforced on the machine; the agent wrote a 40-line pre-execution hook that denies any shell command containing a heredoc, and the next deliberate heredoc was refused before it ran.

The rule

When an agent keeps breaking a written rule, stop rewriting the rule. Move it into a check that runs before the action and cannot be talked out of.

Make authorship a field. If a decision log, an action log, a commit message or a change record doesn't say who decided it, you're one inference away from losing track of what you approved and what the agent proposed.

The provenance tag — [OPERATOR] or [AGENT] — is the simplest version. A login gate, a required flag or a pre-execution hook is the version that works when memory isn't enough. Either way, the boundary has to live in what the system actually checks, not in a paragraph an agent reads once and may or may not apply the next time.

The audio app shipped. It's on my phone. It landed 187 commits across 6 active days in mid-August 2026, with 1,211 test functions total (671 Rust, 540 Dart). AD-28 has held ever since: every decision record says who made it, and I know which ones need my review before they're policy.