Session 04

Feedback·loops

Program
Agentic engineering
With
Arndt Voges
From
Arndt Voges Consulting
Where
Remote

The hard questions first, then the loops

Feedback loopsLoops for your stackClose

10 min opening, 65 min feedback loops, with a live demo, the rest together

0:00
Opening·the questions engineers ask
0:10
Feedback loops·with a live demo
0:45
Break
0:50
Loops for your stack·and a look at one real project
1:20
Homework, close
1:30
End

Once the tool does real work for you,
the hard questions start.

The questions engineers ask

I don't have definitive answers,
but I, too, think about this all the time.

How do I stay sharp and keep my judgment?

Review

Read every change as if you had to defend it. Approving out of habit is how the overview gets lost.

Reports

Read the discovery and spike reports. They are the repository, condensed.

The hard part

Write the core of a difficult change yourself now and then.

Consequences

Judgment comes from the consequences you own, and you still own them.

What you need to stay sharp in moves up a level, as it did when compilers replaced assembler. For now, that still includes reading code, because someone has to judge what the agent writes.

Beginners still need
to put the manual work in.

Learn to write code without the agent first, the way you learn arithmetic before you use a calculator. You learn to read and judge code by writing it.

“The dumbest thing I've ever heard. … How's that going to work when ten years in the future you have no one that has learned anything?”

Matt Garman, CEO of AWS, on replacing junior staff with AI, August 2025

What is my job now, if Claude does it all?

The spec

You decide what gets built and what done means.

The loops

You build the checks that tell the agent whether it is right. That is today.

The review

You read the result and decide whether it goes into main. That is next week.

The system

You know the codebase well enough to judge all of the above.

Claude writes more of the code. You are still responsible for all of it.

What would you build
if you had the time?

The agent gives you time back. Use it for the work you never got to.

Where we are

  1. 01Core concepts
  2. 02Preparing the codebase for agents
  3. 03Writing a good spec
  4. 04TodayFeedback loops
  5. 05Workflow integration and code review
  6. 06Claude Code in depth
  7. 07Orchestration and subagents
  8. 08Claude Code alternatives
Codebase, spec, and now the check. Next week: how the result gets into main.

The spec says when it is done.
Today: how the agent finds out for itself.

Today, you will go from babysitting AI to agentic, long-running AI.

Feedback loops are the key to letting AI iterate tirelessly and autonomously until your job is done.

What we cover today

Test commandExit code/goalChrome
Unit testFeedback loop
Stop hookPlaywrightFailure message
react-native-webFlaky testLocal environment

Part one

Theloop

A command that says pass or fail.

If nothing can tell the agent that it failed,
it stops at its first guess.

AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpmMzE1YzZiOC1mOTgzLTRjMTItODUzNi1mN2NkNzM2YjZmYjIAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaEpUugCTswLQMaD0phSbEQgAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpkZWE4OTE3MS00N2Q1LTQyMWItOTVmMC0yZDllMmM1ZWQzZDJscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNohVm/Od9W7nDAmpgACVfDTwAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggsZ/3HdI4MhGSGdPfwbh2b3Dkao/qE81GhP6RnlVtB/ykZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaBZ73OUMxTPEV1o5UAkSx9wAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDdIIZ+eJ/l+s7pg3vrxFgaoT4hjpIEbrFmBDer2552xmRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBiYZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmYzMTVjNmI4LWY5ODMtNGMxMi04NTM2LWY3Y2Q3MzZiNmZiMi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjNjNGRjMDBhLTVkN2QtNDFlMS1iZWNjLWY0NTNlOGRkMDdlMXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCCxn/cd0jgyEZIZ09/BuHZvcORqj+oTzUaE/pGeVW0H/KJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggKZBqZTt3nKWVekxG0dtwNVB3UCZ3w6bUGvLAtEnNfJCiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggnI8IbRFohb1B4LeWSmssTa8LTHn84zs4fzZvWv0hJBt0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQCfvY+kWvywqTtHEJ65hgBOzljlxZeiRse3KNPm2OQkioconilS18lMZjb6g7IH9vfQL38peFylJVho0jAWjjTk= NOTHING CAN SAY "FAILED" It writes the code It says it is done hours or days You find out, in review It stopped at its first guess, because nothing contradicted it. A COMMAND THAT RETURNS PASS OR FAIL It writes the code It runs your check $ npm test FAILED all passed Done, and proven On a failure it goes back and tries again, on its own, until something says pass.
Feedback loop

A command the agent can run
that tells it whether it is right.

Tests, a build, a type check, a linter, a screenshot compared with the design.

The loop is only as good as its failure message

two failures, same test
FAILED  cart.test.ts

FAILED  cart.test.ts > applies voucher to reduced items
        expected total 4990, received 5490
        at applyVoucher (src/cart/voucher.ts:42)
The agent reads output, not intent. The second one tells it where to look and what was expected.

Unit tests are the primary loop
because they are fast, local and repeatable.

Dozens of runs in one session cost seconds. That is what makes the loop worth having.

Ask the agent to write the tests first,
then the code.

A test written after the code tends to confirm the code.

Watch for the agent
changing the test.

A hook blocks skipped tests and removed assertions. Changes to existing tests go to QA review. New tests are always welcome.

A hook that only lets tests get stronger

.claude/settings.json · .claude/hooks/guard-tests.sh (illustrative)
// .claude/settings.json
"PreToolUse": [{ "matcher": "Edit|Write", "hooks": [{
  "type": "command", "command": ".claude/hooks/guard-tests.sh" }] }]
# .claude/hooks/guard-tests.sh
in=$(cat)
file=$(jq -r '.tool_input.file_path' <<< "$in")
[[ $file =~ \.(test|spec)\. ]] || exit 0          # not a test: allow
new=$(jq -r '.tool_input.new_string // .tool_input.content' <<< "$in")
old=$(jq -r '.tool_input.old_string // ""' <<< "$in")
n() { grep -o 'expect(' <<< "$1" | wc -l; }
if grep -qE '\.(skip|only)\(|xit\(|@Disabled' <<< "$new" ||
   (( $(n "$new") < $(n "$old") )); then
  echo "Tests only get stronger. Fix the code, not the test." >&2
  exit 2                                            # blocked
fi

Part two

Making the looprun

An instruction can be ignored. A hook cannot.

Enforcement comes in layers

CLAUDE.md an instruction, can be ignored Dev command tests run before the server starts /goal re-checked after every turn Hooks Stop hook, and a block on git commit Git hook and CI outside the agent, for people too WEAKER: context, can be ignored STRONGER: runs whether the agent remembers or not

Put the tests inside the commands it already runs

package.json
"scripts": {
  "predev": "pnpm test --silent",
  "dev": "nuxt dev",
  "test": "vitest run"
}
The agent cannot start the app without seeing the test result.

Hooks the tests run
whether the agent remembers or not

It says it is done The Stop hook runs the tests $ pnpm test all pass The turn ends fails The output goes back to it 3 failed · voucher.ts:42 KEEPS WORKING It cannot end the turn while the tests fail. ALSO PreToolUse hook refuses git commit while the tests fail Claude Code ends the turn after 8 blocks in a row
/goal

A condition such as "all tests pass",
re-checked after every turn until it holds.

A separate model does the checking, not the agent doing the work.

Live: test first, then the fix

prompt
In the cart, the voucher AUTUMN10 takes only 5 € off the reduced
jumper. It should take 10 €, on reduced items too.

First write a Playwright test that reproduces this. Run it and
check that it fails for the right reason.
Then fix the code until the test passes.
Do not change the test once it fails for the right reason.
A Stop hook runs the tests whenever the agent wants to finish. While they fail, it has to keep going.

Claude time!

Part three

Real examples of loops foryour stack

These loops cover the browser, the app and the API, and show where loops stop.

The fastest loop is the type checker,
which takes seconds instead of minutes.

vue-tsc, tsc, PHPStan, the Java compiler: after every change. Unit tests after every step, the full suite before the commit.
2.1 seconds
$ npx tsc --noEmit
src/cart.ts:8:49 - error TS2339:
Property 'grossPrice' does not exist
on type 'CartItem'.
Found 1 error in src/cart.ts:8

The browser can be a loop too.

Playwright opens the page, clicks through, compares a screenshot with the design. The Chrome integration does the same in your own browser, and reads the console.
Expected
Stored screenshot of the cart page
Actual
New screenshot: the Apply button is blue now
Diff
Playwright diff image with the changed pixels in red
2116 pixels (ratio 0.01 of all
image pixels) are different.

The design can be a loop too.

The agent takes a screenshot of its result and compares it with the Figma frame it can request via MCP. Differences go back into the loop, not into review.
Figma frame
Figma frame of a Check your email screen
Built
The built screen with three differences marked
1Spacing below the icon
2Button order
3Link color

The app gets the same loop.

React Native renders in the browser through react-native-web, so Playwright drives the app too. Detox and Browserstack stay for what only a device shows.
React Native code
react-native-web
renders it in the browser
Playwright
after every change
Device or simulator
Detox and BrowserStack
for what only a device shows

Testing API endpoints: a skill, not a script

.claude/skills/test-endpoint/SKILL.md
---
name: test-endpoint
description: Test a new or changed API endpoint against its contract
---
1. Read the contract: openapi.yaml, or schema.graphql
2. Start the service locally: make run-local
3. Get a token: ./scripts/dev-token.sh
4. Call the endpoint with valid input, then with every
   error case the contract defines
5. Report every status code or body that differs from the contract

The loop can cover several repositories.

One contract, the OpenAPI definition, generates the client for the frontend and the gateway. A change that breaks the contract fails the type check in the other repository.
openapi.yaml
price is renamed
Gateway repository
client regenerated, passes
Frontend repository
type check fails
error TS2339:
Property 'price'
does not exist
on type 'Product'.

The loop has to run locally.

Tests only on a staging branch means no loop. A local Kubernetes setup is the start of one.
Through staging
PushCIDeployTest
About 20 minutes, so 3 loops per hour.
Locally
Test
About 5 seconds, so hundreds of loops per hour.

Loops work without a staging environment.

Testcontainers starts Kafka, Elastic, Redis and the database locally. Recorded responses stand in for the commerce platform, the CMS and the ERP interfaces.
Containers
Kafka
Elasticsearch
Redis
Database
Service under test
runs locally
Recordings
Commerce API
CMS
ERP interfaces

A production error becomes a failing test.

A Sentry error becomes a failing test first. Then the fix, then the test passes.
Production error
TypeError: Cannot read properties
of undefined (reading 'price')
at CartSummary (CartSummary.tsx:42)
✗ FAIL CartSummary.test.tsx
renders an item without a price
✓ PASS CartSummary.test.tsx
renders an item without a price

Budgets are loops too.

Lighthouse for performance, axe for accessibility: over budget fails like a broken test.
Lighthouse in Chrome DevTools on a demo shop category page: performance 32, accessibility 70, best practices 73, SEO 92

Automating QA
means every ticket starts with writing tests, every time.

The agent writes and runs the QA scenarios for its own change. A person reviews the scenarios, not every click.
qa-scenarios.md
Voucher on reduced items
✓A voucher applies to a reduced item
✓Below the minimum order value it is rejected
✗A voucher plus a second reduction
expected 49,90 €, got 54,90 €
✓Removing the item removes the voucher
✓The mobile layout at 375 pixels

A loop the agent can run unattended

Some things no loop can check.

Visual judgment, business correctness, anything that needs production data.

Sidebar

Tests I am usingin a typical project

These are the kinds of tests in one of my own projects.

The project in lines of code

LinesFiles
App code88,789578
Backend46,253252
Frontend38,756310
Database migrations3,78016
Tests131,135529
Backend tests89,381351
Frontend tests and mocks33,524130
End-to-end tests8,23048
Other12,11737
Fake external services for dev and test4,60815
Dev scripts4,44210
Deploy and CI3,06712
Total232,0411,144

1.5×

There are one and a half lines of test code for every line of app code.

About 8,100 automated tests

fastslow
Static checks17 guards, lint and types run first
Unit2,784
Database2,059
API1,168
Frontend~1,965
Integration33
End-to-end52
Release rehearsals4 scripts
Most of the tests run in milliseconds. Only a few are long runs through a real browser.

Static checks and guards run before any test

Every change

Lint, format, strict types

Backend and frontend, before any test runs.

17 guards

Project rules as code

No direct clock calls, no floats for money, one choke point for sensitive writes.

108 operations

Contract drift

The API schema is regenerated and has to match what is committed.

A rule in CLAUDE.md can be ignored. A guard that fails the build cannot.

Unit tests: pure logic, milliseconds each

2,784 tests

Domain rules and pinned edge cases

A false positive that was fixed keeps a test that holds it fixed.

21 properties

Property-based tests

Invariants such as "the parts sum to the total" are checked over thousands of generated inputs.

88 signatures

Contract pins

Public signatures, fields and enum values fail the build when they change.

74 snapshots

AI prompts and templates

Prompts, tool definitions and emails are compared byte for byte.

A real database instead of mocks

Fresh database

Every test gets its own Postgres, cloned from a migrated template.

Real SQL

Transactions, locks, concurrency and queues run against the real engine.

Migrations

141 tests run each migration up and down and compare the result with a freshly seeded database.

Tenant isolation

An auditor flags any SQL statement that is not scoped to a tenant.

Matrices instead of examples

RouteAnonymousMemberOther tenant
GET /accounts401200404
POST /transactions401201404
GET /reports/{id}401200404
DELETE /rules/{id}401204404
530 checks cover every route for every kind of caller. Tests this exhaustive used to be too tedious to write by hand, and an agent writes them quickly.

The expected numbers come from a separate oracle

Fixed dataseta year of data The app's code Oracle scriptnone of the app code Actual numbers Expected numbers737 values The testare they equal?
If the agent writes both the code and the expected values, the test only shows that the code agrees with itself.

No test talks to a real external service*

Fixed dataset

Two tenants, a year of transactions

A seeded script writes it, and CI checks it for drift.

Fake servers

Every external API

Bank feed, email and captcha serve the fixed data.

Fake LLM

80 canned answers

Directives force refusals, failures and delays.

Clock

Injected, frozen or offset

Dates are the same on every run.

Factories

Valid rows with defaults

Each test overrides only what it is about.

Frontend

Typed mocks of 108 endpoints

They follow the same rules as the real API.

* Only real touchpoints, none of them a regular test: a disposable copy of the production database for migration rehearsals, a live AI check using three made-up transactions, and read-only production queries after each deploy.

What this means for your team

Claude time!

The most important loops at a glance

LoopThe agent runsWhenEnforced by
Types and lintvue-tsc, tsc, phpstan, eslintAfter every editPostToolUse hook
Project guardsA script that fails on forbidden patternsAfter every editPostToolUse hook and CI
Unit testsvitest, phpunit on the changed codeAfter every stepStop hook
Full suitepnpm test, ./mvnw testBefore every commitPreToolUse hook on commit
API endpointsThe /test-endpoint skillChanged endpointDone criteria in the spec
UI and designPlaywright, compared with FigmaAfter every UI changeDone criteria in the spec
External servicesTestcontainers, recorded responsesInside the test runPart of the test command
Production bugsThe Sentry error becomes a failing testEvery bug ticketThe failing test comes first
Example homework, to be tailored to your team

Homework: one ticket, end to end, everyone

The ticket

Everyone picks one real ticket and takes it with the agent from the ticket to the pull request: spec, tests, code.

Problem log

One problem log for the whole team: every problem anyone ran into, consolidated.

Summary

One slide with a table the team fills in together: who, which ticket, how many tests it added, and a short review of how it went.

Share

Be ready to walk through the table and the problem log in 5 minutes.

Done means everyone in the team has a row in the table. As a team, before the next session.
>Yo, look at slides 43-54 for inspiration and give me a comprehensive list of suggestions for this codebase.
? for shortcuts

What you will run into, and what you already know

Missing tests

Add them first, and work with your QA engineer if you need to. That was today.

Missing fakes and test data

Mock APIs, fake servers and demo data to seed a test database are one-time work you benefit from anyway. The agent builds them quickly.

Missing integrations

This should not be a blocker anymore. You should already have them set up.

A ticket without details

You now know how to write a good spec. That was session 03.

A codebase the agent misreads

Improve your CLAUDE.md and standards as you go. That was session 02.

Everything this ticket needs, you have learned in the last four sessions.

Do not pick an easy ticket.
Do not stop after the first failed attempt.
Do not keep what you learn to yourself.

Pick one that would take you days to work on. Iterate until it works. Share what you learn with the team.

Next session:
workflow integration and code review.

Arndt Voges Consulting · arndt@arndtvoges.com · arndtvoges.com

<stop>