I asked an agent to work out why a pipeline was failing, and explicitly not to fix it. I wanted the diagnosis.
It fixed it. Then it wrote a commit message saying it had added a config file that was already sitting in the repository root, and that was the only thing it told me it had done.
The first failure is the boring one
Acting without leave is annoying, and it isn't what actually worries me. A human catches it on review, the commit gets reverted, everyone moves on slightly crosser than before (me, mostly). I've had to say some version of stop committing things I did not ask you to commit more than once, and it's a scope problem with a scope-shaped fix. The commit message is a different animal altogether.
When an agent has been working unattended, its commit messages stop being documentation and turn into the instrument panel: the whole channel through which the work reports what it did, and I read them the way you read a dial, quickly, trusting them, using them to decide where to look closely and where not to bother.
A commit message describing work the agent didn't do is a dial reading a number that was never true, on the only instrument I had.
So when should it interrupt?
I'm not sure there's a clean answer, and both extremes go wrong in ways I've managed to underrate myself.
An agent that asks about everything is a denial-of-service attack aimed at its own operator. You delegated the work to stop making these decisions; if every one comes back to you with a prompt attached you have gained a slower version of doing it yourself, and you'll start clicking through them without reading, which is worse than not being asked.
An agent that never asks guesses instead, and it doesn't guess evenly either... it guesses hardest on the things that were ambiguous, which, I suspect, are the very things a person would have wanted a say in, since the ambiguity is what made the question worth asking in the first place.
What that looks like as a tool
go-tool-base has a query_user tool, and its description is where the judgement actually lives:
Ask the human a clarifying question and receive their answer. Use this when the request is ambiguous, a design decision could reasonably go more than one way, or a security-sensitive choice needs human judgement, rather than guessing. Provide a concise question and 2-4 suggested answers; the user can choose one or write their own.
Three named conditions, and the "rather than guessing" is doing real work. The suggestions matter too: two to four options plus free text turns an interruption into something you can clear in a second, which is what stops it becoming the denial-of-service version.
Where the design decision actually sits
Underneath all of this sits a snag: this agent also has to run unattended in CI, where I'm not around to answer anything.
The obvious implementation is to keep the tool and have it fail, or return a default, when no human's about. Both feel wrong to me, and for one reason: the model still sees the tool, still reasons about asking, and now has to handle a refusal it can't do anything useful with. You have given it a button that isn't connected (and then asked it to reason carefully about when to press).
So in non-interactive runs the tool gets left out of the toolset entirely, rather than switched off (internal/agent/query.go). The caller supplies an interactive prompter or the tool doesn't exist, and an agent with no way to ask proceeds and makes the best call it can, which is the correct behaviour when there's nobody to ask.
It reads as a small plumbing choice and it's most of the design! The capability to interrupt is granted by context rather than requested by the model, so whether a human is available is never the agent's call to make.
The corrections that weren't about code
I came at this from the other end on an extraction branch, and what struck me afterwards was that almost nothing I had to correct was code.
No AI attribution in commits or merge requests. No breaking-change syntax before v1. Push each commit so I can review it rather than arriving with nine. Lint before committing. Keep commits scoped to one package. Don't reshape a public constructor behind my back to make something compile today.
Each of those is a boundary where the work was fine and the process went past me. An agent that stops at those isn't what I'd call timid. It's leaving me able to see what happened afterwards, which is about the only thing that makes handing work over survivable.
One of them degrades gracefully
The reason I care more about the commit message than the unauthorised fix is that the overstep degrades gracefully and the misreport doesn't. An agent that oversteps and says so leaves me a clean revert and a lesson, and I've had plenty of both. An agent that behaves perfectly and describes it wrongly leaves me a repository I believe I understand, and nothing on the panel is going to tell me otherwise.
Originally published at phpboyscout.uk on 2 November 2026.
Top comments (0)