I’ve spent a good chunk of this year helping teams put coding agents into real delivery pipelines. Not demos. Pipelines with on-call rotations attached.
The mental model that survived is one sentence: agents propose, policy accepts. An agent’s output is a change proposal. It merges when the same machinery that gates human changes says yes: automated checks, ownership rules, review, deploy approvals. The agent never gets to say yes to itself.
That sounds obvious. Almost every mess I’ve been called into started with a team quietly violating it, usually by giving an agent a token that could push to a protected branch “temporarily.”
Scope agents by path, and enforce it Link to heading
Every agent gets a job and a set of paths, and the paths are enforced in CI, not in the prompt. Prompts are suggestions. CI is a wall.
A dependency agent should be able to touch manifests and lockfiles and nothing else. So check:
# fail the proposal if the diff leaves the allowed paths
# note: shallow clones break the merge-base diff; fetch full history first
CHANGED=$(git diff --name-only origin/main...HEAD)
[ -z "$CHANGED" ] && exit 0
OUT_OF_SCOPE=$(echo "$CHANGED" \
| grep -vE '^(package(-lock)?\.json|go\.(mod|sum)|requirements[^/]*\.txt)$' || true)
if [ -n "$OUT_OF_SCOPE" ]; then
echo "out-of-scope changes:"
echo "$OUT_OF_SCOPE"
exit 1
fi
A dozen lines, and the failure mode changes completely. Without it, a
“dependency bump” can include a drive-by refactor the model felt like doing,
and your reviewer approves it because the title said chore: update lodash.
This version handles the empty diff and keeps subdirectory files out of the allowlist, but it’s still illustrative. Renames, deleted files, and nested manifests need handling before you trust it. The shape is the point: diff the proposal, compare against the agent’s allowed paths, fail loudly.
This is also what makes running agents in parallel survivable. Two agents with overlapping write paths don’t give you 2x throughput. They give you conflicting proposals and a merge order problem. Disjoint paths, no coordination needed.
Agents hand off artifacts, not chat Link to heading
Agent-to-agent conversation is state you can’t inspect after the fact. So we don’t do it. An agent’s output is a file with a schema, and the next stage consumes the file:
{
"task": "upgrade-openssl",
"proposal": "pr-4312",
"checks": ["build", "integration", "license-scan"],
"risk": "medium",
"rollback": "revert pr-4312, no data migration involved",
"needs_human": false
}
When something goes wrong three stages later, you diff the artifacts and find where reality diverged from the plan. When agents coordinate through a shared conversation, your incident review is “we think it misunderstood.” That’s not a root cause. A wrong field in a JSON file is a root cause.
Same reason the stages chain on pipeline events, not on an orchestrator agent’s judgment. Stage two runs when stage one’s checks pass. Boring, inspectable, already how CI works.
Bound the retries. Write the number down. Link to heading
An agent whose check fails will happily fix, push, fail, fix, push, fail. Each iteration looks like progress. Across a weekend it’s hundreds of pipeline runs and no convergence.
Now the rule is mechanical: same required check fails twice, the loop stops, a human gets the proposal link, the two failing runs, and the diff between attempts. Not a dashboard. A direct handoff with the three links you’d want.
Two failures is the right default because one failure is often the environment and three means the second fix didn’t work either, and the agent has started guessing. You can tell it’s guessing because attempt three touches files that attempt one didn’t.
State lives in the tracker, not the transcript Link to heading
If a decision only exists in a chat session, it isn’t state. It’s an anecdote. Nobody reviews a transcript.
So: acceptance criteria live on the ticket. Design decisions live on the proposal. Results live in pipeline runs and their artifacts. An agent resuming work reads those, in that order, and continues. It does not get yesterday’s transcript, because a transcript full of dead ends is worse context than a proposal description that was updated when direction changed.
Drift detection falls out of this for free. When a proposal’s diff no longer matches the ticket’s criteria, one of them is wrong, and it’s findable with your eyes. When the criteria live in a prompt someone pasted three sessions ago, nothing is findable.
One operational note that bites people: pipeline logs and artifacts expire. Defaults range from 30 to 90 days depending on the platform, and deletion is usually unrecoverable. If artifacts are your audit trail, export the ones attached to anything you might need to explain later.
When it breaks, sort before you fix Link to heading
Agent failures land in three buckets, and each has a different owner:
- Reasoning: the requirement was read wrong. Fix the instructions.
- Tooling: the pipeline was misconfigured, never triggered, or the agent’s permissions were wrong. Fix the config.
- Context: the state was stale or contradictory. Two documents disagreed, and the agent picked the wrong one. Fix the filing.
The distribution surprised me. Coming in, I expected bucket one. In practice, bucket three dominates. The model gets blamed, but when you pull the thread, the ticket said X, the proposal said Y, a doc said Z, and the agent chose the wrong letter. So would a new hire.
None of this is new Link to heading
Protected branches, least-privilege tokens, required reviews, path-based ownership: all of it predates agents, because humans also propose confident, wrong changes at volume.
That’s the actual takeaway. Agents don’t require a new governance philosophy. They punish the absence of the old one. Teams with working policy add agents and it’s mostly fine. Teams that ship to main on vibes discover that agents are a force multiplier on exactly the thing they were hoping nobody would notice.