A workshop bench with tools laid out across it

Guide

A local dev environment an AI agent can verify in

Give the agent an isolated build directory, one command that returns a verdict, and a browser it can drive. Part two of three on environments built for agents.

Published August 1, 2026

Give the agent a way to check its own work: a verification command that exits non-zero, an isolated build directory and port so it never races your dev server, and a browser it can drive through an MCP server. An agent that cannot observe the result of a change is guessing, however good the diff looks.

The failure this prevents

Part two of three. The most expensive agent failures are not wrong code — they are confident reports about code that was never observed running. The change looks right in the diff, the summary says it works, and nobody finds out otherwise until a human opens the page. Everything in this article exists to close the gap between looks right and is right.

The fix is not a better model or a stricter prompt. It is an environment where checking is cheap enough that the agent does it every time, and where the check produces a fact rather than an opinion.

Isolate the build directory and the port

Start here, because everything downstream depends on it. In most frameworks the dev server and the production build write to the same directory. When you have a dev server running and an agent builds in the background, they race: assets get half-written, the served HTML references a CSS hash that no longer exists on disk, and every stylesheet request fails. What the agent then sees is an unstyled page it will try to “fix”.

Next.js reads the build directory from next.config.js, so wiring it to an environment variable costs one line and buys every agent session its own sandbox:

next.config.js
const nextConfig = {
  // Lets a session build and serve without colliding with the dev server.
  distDir: process.env.NEXT_DIST_DIR || ".next",
}
CLAUDE.md — the rule that goes with it
## Verifying your work

- **Never run a second dev server against this repo.** Two processes share
  `.next/` and race, serving unstyled or broken pages mid-check.
- To look at the site, build and serve into your own directory and port:

  ```bash
  NEXT_DIST_DIR=.next-agent npm run build
  NEXT_DIST_DIR=.next-agent npx next start -p 3999
  ```

- Delete the directory when you are done. `.next-*` is gitignored.
- Restart the server after every rebuild: `next start` keeps serving the
  previous build's prerendered HTML if the dist is rebuilt underneath it.

That last line is the kind of rule you only write after losing an hour. A stale server serving the previous build looks exactly like a change that did not take effect, and the natural response is to “fix” code that was already correct.

One command that returns a verdict

The agent needs a check whose output is a verdict, not a wall of text it has to interpret. Anything it must read and judge is something it can read and misjudge. Collapse the whole thing to an exit code and a few lines of pass or fail.

package.json
{
  "scripts": {
    "build": "next build",
    "verify": "npm run build && node scripts/check-budget.mjs && npm run lint"
  }
}

Then say so in the constraint file, in the imperative, with the threshold named. “Run npm run verify after any change; the homepage First Load JS budget is 115 kB and a number over it is a regression to fix, not a number to report” is executable. “Please test your changes” is not.

Give it eyes

A build passing tells you the code compiles. It says nothing about whether the page renders correctly, whether the console is clean, or whether text is legible over a background image. For that the agent needs a browser, which means an MCP server. Configure it at the project root so the whole team gets the same tools:

.mcp.json
{
  "mcpServers": {
    "chrome-devtools": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest"]
    }
  }
}

What changes with a browser attached is the category of bug you can catch. While auditing this knowledge base, the text in every article hero sat over a photograph. Lighthouse reported a perfect accessibility score — because axe skips contrast checks on elements over background images. Driving a real browser made it measurable: hide the text layer, screenshot the hero, read the brightest pixel behind each text run, and composite the text colour over it.

Sampling the actual rendered background behind a text run
const boxes = await page.evaluate(() => {
  const header = document.querySelector("article header")
  const hb = header.getBoundingClientRect()
  const rect = (el) => {
    const r = el.getBoundingClientRect()
    return { x: r.left - hb.left, y: r.top - hb.top, w: r.width, h: r.height }
  }
  const regions = [...header.querySelectorAll("h1, p")].map(rect)
  // Hide the text so the capture is pure background.
  header.querySelector(".relative.z-10").style.visibility = "hidden"
  return regions
})

await page.screenshot({ path: "hero.png", clip: { x: 0, y: 0, ...heroSize } })
// then: draw into a canvas, find the brightest pixel per region,
// composite the text colour over it, and compute the WCAG ratio.

The worst case came out at 3.35:1 against the 4.5:1 that normal-size text requires. No automated tool in the stack would have reported it. That is the argument for eyes in one paragraph: the checks you already run define the bugs you are able to find.

Trust the DOM over the screenshot

One hard-won caveat. Embedded browser panes in coding tools suspend animation frames when hidden and can return stale or blank pixels while reporting perfectly correct geometry. An agent that screenshots through one and sees a blank page will conclude the feature is broken and start undoing working code.

So encode the precedence: DOM assertions are truth, pixels are evidence. If a screenshot disagrees with getBoundingClientRect and computed styles, the screenshot is wrong. And when you genuinely need an image, drive a real headless browser rather than the embedded pane — a note worth putting in the constraint file, because it is not guessable.

Where this goes next

Structure gives the agent the right facts — part one. This gives it a way to check them. Part three turns the two into something repeatable: reusable prompts for a known stack.

An environment an agent can verify in is an environment your own team can trust, and setting one up is part of putting AI tooling to work rather than a preliminary to it.

Questions

Why not just let the agent run the dev server?

Because a dev server and a production build share a build directory in most frameworks, so two processes racing over it serve broken pages mid-check. Give the agent its own build directory and its own port, and the two sessions stop corrupting each other's output.

Is a browser MCP server actually worth the setup?

It is the difference between an agent reporting that a change works and an agent knowing it does. Without one, verification is pattern-matching against the diff. With one, it can load the page, read the console, and check a computed style — which catches the class of bug where the code looks right and the render is wrong.

What's the minimum version of this?

One verification command the agent is told to run after every change, and one isolated way to serve the app. Everything else here is refinement. If an agent can build and look, it can already catch most of what it would otherwise get wrong.

Written by

Karol

Senior engineer and systems architect behind Tall Karol. Everything published here is grounded in real client work — no roundups, no tools that haven't run in production.

Why Tall KarolWork with Tall Karol

Related notes

More on ai integration

Related service: AI Integration

Want this kind of engineering on your project?

Tall Karol takes on fractional and project-based engagements for startups and agencies.

Book a working session