I trusted AI agents after I stopped trusting their first draft

I trusted AI agents after I stopped trusting their first draft

~ 11 min read


I used to supervise coding agents closely. A task might take thirty minutes, and I wanted to see each decision as it happened. That limited me to the few tasks I could keep in my head at once.

I now let a manager agent inspect a set of Linear (or Jira) issues, work out their dependencies, schedule independent work in parallel, create stacked pull requests where order matters, and keep each pull request moving through review and CI. Some of that work runs for a day or continues across a week without me watching every step.

I reached that point by assuming the first attempt would be incomplete, then turning each failure I found into a test, script, review rule, or reusable skill. Overtime I have built up both global and project specific automated tools and tests which check for regressions and ensure the codebase stays healthy and PRs are narrow. This is key to letting AI agents work autonomously without human oversight.

One large task produced one large pull request

My old workflow encouraged me to put a complete change in one pull request. A database change could include application code, a migration, a backfill, and the removal of the old fields.

The pull request description then became a deployment checklist:

  1. Approve the pull request but do not merge it.
  2. Run the migration from the pull request branch against production.
  3. Run and verify the backfill.
  4. Deploy the branch.
  5. Complete the data change and merge.

The instructions could be correct and the rollout could still fail. Someone had to remember every step, run it against the right revision, and notice if the branch changed halfway through. That someone was usually me.

Giving the whole feature to one agent reproduced the same problem. It returned a large diff that mixed several kinds of risk. Reviewers had more code to reconstruct, deployment instructions were longer, and an apparently small correction could invalidate an earlier migration or verification step.

Stacks changed the unit of planning

GitHub’s native stacked pull requests changed the useful unit of work from “the feature” to “the next independently safe state”. GitHub records the dependency chain, shows each layer as a focused diff, and handles cascading rebases when a lower layer changes or merges. The feature is still in public preview as of 28 August 2026.

I could and occasionally did use to manually create stacked pull requests, but now it has native support in GitHub much of the management overhead is handled automatically. Lets face it good developers are lazy, they automate the boring stuff.

I have written a separate stacked pull request walkthrough. Being able to express deployment order without putting every step in one pull request changed my agent workflow.

A recent weather-storage change became three pull requests:

#700 expand the database schema, dual-write and add the backfill
  |
  v
#701 switch reads and application logic to canonical SI fields
  |
  v
#702 remove compatibility code and legacy table columns

The first layer kept the old and new representations working together. The second changed application behaviour only after the migration and backfill were ready. The final layer removed the old contract after the canonical application was live.

Breaking the work apart forced the zero-downtime design. New and legacy fields had to coexist. Writes updated both. Reads went through an abstraction that could prefer the new value and fall back to the old one. The destructive cleanup became a separate decision after deployment and observation, rather than the last few lines of a large feature diff.

This is temporary duplication, and it is worth paying for when the alternative is an atomic production change that is difficult to verify or reverse.

A quiet abstract grid showing dependent work moving through separate safe states

Parallel implementation still needs ordered deployment

The manager agent can query the issue tracker through its installed skill and CLI, read the acceptance criteria, inspect issue relationships, and compare the areas of the codebase each issue is likely to touch. It then decides which work can start together and which work must wait.

In my setup, the Linear skill and CLI provide issue context and allow the agent to update the task as work progresses. On the GitHub side, gh and the gh-stack extension expose pull request, review, check, and stack state. Higher-level skills then encode what the agent should do with that access. My yeet skill handles the path from a finished change to a pull request, while a custom review-loop skill controls how comments, fixes, reviews, and CI are repeated.

The CLI provides access. The skill provides the operating rules.

That is better than asking for an arbitrary number of subprocesses. I tell the manager to find the fastest schedule that respects the dependency tree. Two unrelated fixes can proceed at once. A read cutover cannot deploy before its schema and backfill, even if another agent can prepare and test that later layer in parallel.

The pull request stack records the linear part of that plan. Independent work remains independent. The manager agent is responsible for not turning “parallel” into several branches editing the same files with incompatible assumptions.

Native stacks remove much of the rebasing work that made this awkward before. GitHub can automatically rebase and retarget the remaining layers after a lower pull request merges. There are more pull requests, but each one has a smaller purpose, a shorter deployment note, and a more useful review boundary.

Local agents need isolated applications

GitHub Actions already gives each job an isolated runner. Parallel local agents need the same property before they push their branches.

I use Git worktrees and per-agent Docker Compose environments. A setup script creates ignored environment files from a known test template, assigns a unique Compose project name, allocates a block of host ports, and configures separate application and test databases. This also fixes a common worktree problem: ignored .env files do not appear in a new worktree.

The normal flow is deliberately short:

scripts/create-agent-env weather-cutover
scripts/agent-env-do weather-cutover up -d
scripts/agent-env-do weather-cutover database migrate:fresh --seed
scripts/agent-env-do weather-cutover test
scripts/agent-env-do weather-cutover down -v --remove-orphans

The wrapper makes one agent use ports such as 8081, while another gets 8082 and the rest of its assigned range. Compose creates separate containers, networks, and volumes. Good factories and seeders give each database a useful, repeatable state instead of making agents share a stale development dump.

This took real work to set up. It was also work an agent could help implement and test. I cover the pattern in more detail in Docker Compose setups for parallel AI agents.

Without this layer, the manager can schedule parallel code changes but the local environment will serialise them by accident, or worse, let them corrupt each other’s database and test state.

Tests let me stop watching

I used to think good coverage mattered, but that 100% coverage was an ideal with a poor return on the last few lines for manay codebases. My view has changed for agent-written code. I now want 100% coverage, and I want it to be deep rather than decorative.

The percentage does not prove correctness. A test can execute a line without checking anything useful. High line coverage closes the easy gaps, while different test layers check different claims:

  • unit tests prove the changed behaviour and its failure cases
  • integration tests check the wider application and its real boundaries
  • end-to-end tests prove the user flow through the running system
  • migration and backfill checks prove that production data can move safely between representations

Every behavioural review finding should produce a regression test where possible. The lasting improvement is making the same class of mistake harder for the next agent to ship.

This is how my supervision window grew from minutes to days. I gradually allowed agents to do more, found where they failed, added a quality gate, and tried again.

Test runtime then becomes an operational concern. Every pull request and every revision runs the full suite. Unit, integration, and browser tests have to stay fast and deterministic or the review loop spends most of its time waiting.

The first working version is still a draft

Agents are great at solving the stated problem. They are also very good at leaving behind a second helper that already existed, an abstraction in the wrong place, an inefficient query, or a block of code that works but is harder to maintain than it needs to be.

Humans do this too. A competent engineer gets the behaviour working, then reads the change again, and improves its shape. Rejecting AI code because its first version is messy makes little sense if the workflow accepts that version as final.

I encode a separate refactoring pass in a skill. After the tests pass, the agent must inspect the changed code and its immediate collaborators for duplication, existing helpers, unnecessary complexity, and missing abstractions. The useful order is:

search for an existing pattern
reuse it when it fits
extract a shared helper when the duplication is real
create something new only when the first three do not solve the problem

That pass must preserve behaviour and rerun the relevant tests. It is not permission to redesign unrelated code.

Two reviewers find different defects

The implementing agent should review its own work. I also request both Copilot and Codex review on every pull request. I prefer using different model providers because their blind spots differ, although independent context matters even when the provider is the same.

Looking across the previous month of my pull requests, the reviewers regularly found different defects in the same diff. In a MapLibre migration, Copilot found a missing glyph source and an API that ignored per-line dash settings. Codex found a second click handler that caused marker popups to open and immediately close.

In a webcam and map change, Copilot found that viewport bounds included markers hidden by active filters. Codex found that every webcam marker had the same accessible name.

The models also overlap in useful ways. On the weather cutover, both independently found that a missing wind speed could be converted into a real zero. Codex separately found that an audit mixed metres per second with miles per hour.

The final weather cleanup produced the most relevant finding for this workflow. Codex noticed that it removed the status command required to verify the backfill before dropping the legacy columns. The code change was internally plausible, but it made the production rollout less safe.

Small pull requests help both reviewers. A model can spend its context testing one claim instead of reconstructing a large feature before it can assess the diff.

A restrained abstract grid showing two independent review paths crossing the same change

CI and review form a loop

Review runs as a loop. A pull request starts CI and both adversarial reviews. The manager agent monitors all three.

push a commit
  |-- start CI
  |-- request Copilot review
  `-- request Codex review
          |
          v
actionable comment requiring code
          |
          v
cancel CI for the superseded commit
          |
          v
fix, test, push and request both reviews again

If a review comment requires a code change, there is little value in finishing an expensive CI run for the old commit. My review-loop skill verifies the exact head SHA, cancels queued or running jobs for that revision, applies the fix, runs local checks, pushes, resolves only the comments actually addressed, and requests another review.

The loop continues until CI is green and no unresolved actionable review remains. Conflicting feedback, repeated failures, missing credentials, and product decisions stop the loop for human input. The agent cannot simply reinterpret a failed gate as acceptable.

Cancelling obsolete runs saves time and compute, but it also makes the workflow responsive. A valid review comment gets handled when it arrives instead of waiting behind a test result that can no longer approve the pull request.

I earned autonomy one failure at a time

There is more machinery in this workflow. Stacks create more pull requests. Every layer runs CI and receives two reviews. Database changes temporarily maintain two representations. Local parallelism needs worktree, Docker, port, database, and cleanup scripts.

All of that machinery lets me hold less coordination in my head and work with smaller units of risk. The manager agent can schedule independent work, while the stack keeps dependent deployment steps ordered. Reviewers see targeted diffs. A destructive database step cannot hide at the end of a feature-sized pull request.

I still occasionally commit a tiny, well-covered change directly to main. That is exceptional. If a change deserves a pull request, I expect both automated reviewers and the complete CI loop.

I can’t even start a piece of work now without an associated Linear issue, an AI Skill enforeces creating the issue if a relevant one does not already exist, and logs the audit trail via the issue, even if the work never hits main there is a record if AI was used or assisted doing the work.

Start by strengthening the tests around changed behaviour, then widen the integration and end-to-end coverage. Put the checks behind commands an agent cannot waive. Add a required refactoring pass. Turn every repeatable failure into a test, script, or skill before installing another coding agent.

Once those controls can reject bad work without you watching, give the agent a little more autonomy and see where the system fails next.

all posts →