Every engineering team I have worked with has some version of “don’t assume, ask.” On this build it was written into the top-level instruction file that every Claude session reads before it does anything: blocked or unsure, write it down and flag it, never assume. That line was a client rule before it was ours. What I want to describe is what it took to make it work when the people doing the asking were five engineers each driving their own AI coding agent, on one codebase, with a delivery date that did not move.
The problem with “just ask”
A coding agent that hits an ambiguous spec has three options. It can guess, it can stop and ask the human sitting next to it, or it can write the question down somewhere the whole team can see it. Guessing is cheap and sometimes right. Asking the nearest human scales badly, because that human usually does not own the answer either. Writing it down only works if there is one place to write, one format, and someone whose job it is to turn the question into a decision.
We built that. It is not sophisticated. It is a folder, a naming convention, a role, and a habit.
The pipeline
The folder is waiting-confirmation/. By the end of the build it held 56 items. A separate rulings/ folder held 26 ruling files, and the status board carried dozens more as inline sections. The flow from one to the other looked like this every time:
- A machine hits ambiguity. It writes a waiting-confirmation item: the question, the options as the agent sees them, and a recommendation with the reasoning.
- The integrator (or, later, the contract steward) verifies the facts behind the question against the repo. The agent is explicitly told that it does not get to decide. The process bundle that defined this put it in one sentence: “The ruling is human; you prepare the decision surface, you do not decide.”
- A human rules. Sometimes the integrator’s human, sometimes the steward’s, during one week mine.
- The ruling is recorded, and every file it changes is listed in the same commit.
Step 2 is the one teams skip. An agent that is good at writing questions is also good at writing questions that are not actually open. Most of the verification work was reading the question against the code and the stories and finding out that half of it had already been answered somewhere.
Four rulings worth stealing
A handful of these decisions turned out to be general. I quote their names as they appear in the board because the letters became shorthand on the team.
R-A: act on the part both readings agree on, escalate only the delta. The client said one workstream had “priority” over another. Did that mean order (do this first) or exclusivity (do only this)? The two readings differ by about 62 story points of scope. But for the next day’s work they produce the same next action: build the prioritised workstream. The ruling was to act now on the reading-independent part and escalate the ambiguity to the client in parallel. Nobody waited.
R-D: “not applicable” is a fifth outcome. Our compliance checks could return pass, fail, warning or inconclusive. An agent proposed folding “this check does not apply to this request” into inconclusive. The ruling made NOT_APPLICABLE a distinct, closed outcome. Inconclusive means someone can still supply the missing input; not applicable means nobody should try. The module docstring later recorded that collapsing the two had “already broke twice.” Vocabulary is a design decision.
R-BF: code is the authority for “done”. Late in the build, 17 compliance checks were closed in the tracker on the strength of a register spreadsheet that said “Yes” or “Yes (with caveat).” The ruling reopened them. A check is done when code supports it and a reviewed pull request has merged, and the repo outranks both the tracker and the register. This sounds obvious. It was not obvious to the spreadsheet.
R-AP: the new machine is a full peer. When a fifth engineer joined mid-build, the integrator’s first draft of the onboarding note put restrictions on what the newcomer could claim. The human overruled it the same day: no work is pushed to him, but the permission to take work is standing, and helping him is a duty. The board records him as “FULL PEER, same rights as M1/M2/M3/M4.” The lesson is that agents tend to invent process for new people. Watch for it.
Under-claiming is also wrong
The most expensive bad number on this build was pessimistic, not optimistic. An early count said 7 of 19 compliance checks were viable. That figure was corrected to 13 of 19 within days, but by then “7 of 19” had survived in three places, including the summary the client sponsor was about to sign off. The plan file now carries a warning in capitals: do not quote the 7 figure, it is stale in the pessimistic direction.
We tend to treat over-claiming as the sin and under-claiming as caution. In a build with hard dates, a stale pessimistic number costs scope just as surely as a stale optimistic one costs trust. The fix was the same for both: every status claim links to a test name or a commit hash, and a number without a link is a lead, not a fact.
Chase by who must answer
The waiting-confirmation items that were blocked on people outside the team went into a chase list. The thing that made it useful was the grouping. Not by workstream, not by date: by the person who must answer. Each ask led with its fallback, meaning what we would build if nobody replied. And the file carried a warning at the top that the need-by dates were ours, back-calculated from the delivery date, not the client’s.
Running the arithmetic on that list changed the headline. The informal story was “we are blocked on the client.” The counted story was about 114 story points claimable immediately with no external input, about 61 blocked only on our own review queue, and only about 37 genuinely blocked on people outside the team. The blocker was mostly us.
The small habits that held it together
One builder’s first-day note asked whether the test bar for readiness was too loose, and ended “happy to be overruled if M1 wants a stricter bar.” The integrator’s reply was recorded in the same file: no stricter bar, and “M3’s judgement was right, and it was right for the reason M3 gave.” That exchange cost ten minutes and settled the bar for everyone.
Another item was filed by an agent that refused to invent a value for a commit-trailer field the rules had not defined. Its note argued that an undefined value was the one unacceptable outcome, “because it is resolved differently by each machine on each commit.” It was right. The vocabulary got defined.
A third was a seam between two modules that nobody owned. The agent that found it wrote at the top: “NOT a request for M2 to claim it. Flagged, not taken.”
Flagged, not taken is the whole discipline in three words.
What I’d tell you to do
- Give “never assume” a folder, a file format and an owner. Without those three it is a poster.
- Separate preparing the decision from making it. The agent writes options and a recommendation; a named human rules.
- When two readings of a spec produce the same next action, act and escalate in parallel. Only the delta waits.
- Treat pessimistic stale numbers as bugs with the same priority as optimistic ones.
- Group your blocked items by who must answer, lead each with its fallback, and count the story points. The result is usually less blocked than it feels.