Most teams that adopt AI coding agents land in the same place. The agent is good, the harness around it is decent, and work moves faster once somebody has decided what the work is. That last part did not change, and it is where the queue sits now.
A software factory does not change it either. You hand it a spec, it plans, writes, tests, and hands back a pull request. Real throughput, and still a fulfilment system: it starts when a person asks. Everything nobody has asked about keeps slipping one merge at a time. A dependency goes four majors behind. The checkout page's largest contentful paint creeps from 2.1 seconds to 3.4 and stays there, because the person who would have caught it was shipping something else.
Goal-driven engineering closes that gap. You describe the finish line once, and a system keeps the codebase on the right side of it.
Executive summary
What it is
A goal written in plain words, with a command that measures it and a target that decides it. A goal manager runs that command on a trigger, compares the reading against the target, and closes the gap by filing an issue or by sending the work to a software factory. The goal stays registered, so it keeps being enforced after everybody has moved on.
What it buys you
Regressions get caught by the merge that caused them instead of by a customer six weeks later. Work that never gets prioritised, like dependency lag and performance budgets, gets done. And the standard your team argues about once ends up written in a file that something actually enforces.
What it costs
More pull requests arriving at the same reviewers, which is the constraint in most teams already. Goals that are wrong alert until people stop reading them. And it only works on top of gates you trust, because from the third rung up those gates are what stands between an agent and your main branch.
Every component, and the loop that makes it a loop.
- 01You write a goal and commit it to the registry. Nothing else in the picture is written by a person.
- 02A trigger wakes the goal manager. The observer runs that goal's signal and records what it read.
- 03The planner compares the reading against the target, reads the memory, and picks an action or picks none.
- 04The guard checks the goal's authority and budget before any agent starts.
- 05Below its authority, the goal manager stops at an issue. Above it, it sends a spec, a scope and a budget to the software factory.
- 06The factory works across every repository in scope and opens pull requests, which pass the same harness gates as human work before they reach main.
- 07Merged changes deploy behind a flag and go out as a canary.
- 08Production telemetry feeds straight back in as a signal source, which is the only way to measure a goal about latency, error rate or spend.
- 09Every observation and action lands in the record, and the record is what a person reads.
Four things to build, in this order. Each one is a section below.
- 01
Write one goal properly
Ten fields, including the command that measures it and the person who owns it. If you cannot write the command, what you have is a wish.
- 02
Run the loop with no agent in it
A trigger, the signal, and a file it appends to. You are checking whether the signal is stable and the threshold is right before anything can act on either.
- 03
Add the goal manager
An observer, a memory of what was already tried, a planner that is allowed to choose nothing, and a guard that enforces authority and budget. This is the only part you have to build.
- 04
Grant autonomy one rung at a time
Report, then issues, then drafts, then pull requests, then merges. Per goal, against evidence, never all at once.
The definition
Interactive mode
- Who starts the work
- You, on every turn
- What you hand over
- A message
- It ends
- When you stop typing
- It fits
- Novel work, unfamiliar code, anything you will only recognise when you see it
The software factory
- Who starts the work
- You, once per request
- What you hand over
- A spec
- It ends
- When the pull request lands
- It fits
- Work you can specify up front and check without reading it
Goal-driven engineering
- Who starts the work
- Nobody. A trigger does
- What you hand over
- A goal
- It ends
- A standing goal never ends
- It fits
- Properties that have to stay true after everybody has moved on
Goal-driven engineering makes the unit of work a standing goal: a plain-language statement of something that has to be true about a system, with a signal that measures it and a target that decides it. A monitoring agent runs the signal on a trigger, compares the reading against the target, and acts on the difference.
That is a control loop, and the control-theory words fit without stretching. The goal is the setpoint, the signal is the sensor, the monitoring agent is the controller, the software factory is the actuator. The codebase is what gets controlled, and every merge is a disturbance.
- 01
Observe Sensor
Run the goal's signal against the codebase as it stands, and write the number down.
- 02
Compare Error
Hold the number against the target. The gap between them is the only reason the loop ever does anything.
- 03
Decide Controller
Read what was already tried, then pick the cheapest action that closes the gap. Doing nothing counts.
- 04
Act Actuator
File the issue, or send a spec to the factory and open the pull request. Then wait for the codebase to change and measure again.
Two kinds of goal
"Goal" is doing two jobs here, and a system that runs both through the same loop gets one of them wrong.
An invariant goal has to keep holding. Coverage above a floor. No critical advisory open longer than 72 hours. It never completes: it is met today, and tomorrow a change nobody connected to it can breach it. That is the reason something has to keep watching.
A terminal goal has a finish line. Move all eleven services off the deprecated auth client. Implement this spec. Progress is the measure, and when it lands you retire the goal.
Terminal goals need a stopping rule and an attempt limit, or an agent that cannot reach the line keeps trying. Invariant goals need the opposite. They have to survive being met, and they have to stay cheap, because they will run thousands of times and act almost never.
What a goal is made of
Everything downstream reads this record. Every agent, every gate, every issue it files can only be as good as what is written here.
intentPlain words: what has to be true, and why anybody cares.
Every agent in the chain reads this before it decides anything, and nothing downstream repairs a vague one.
scopeThe repositories and the paths the goal covers.
A goal can span repositories, which is most of the value. Without a scope it wanders into ones you did not mean.
signalA command that returns the current value, or a verdict.
The sensor. If you cannot write it, what you have is a wish.
targetThe value or the condition that counts as met.
It turns a number into a pass or a fail, so a person and an agent can argue about the code and never about whether the goal holds.
triggersWhen to measure: a schedule, a merge, a release, an external event.
Cheap signals run on every merge, expensive ones nightly. This field is where most of the running cost is decided.
authorityThe most it may do without being asked: report, open an issue, open a draft, open a pull request, merge.
Autonomy is granted per goal. Dependency bumps can merge themselves long before a schema migration should open a draft.
budgetCeilings: pull requests a week, turns an attempt, spend a month.
A loop with no ceiling is an outage with a changelog. It is also what stops one badly written goal from flooding the review queue.
escalationWhat happens when it cannot reach the target: after so many failed attempts, stop and ask a named person.
Some gaps need a decision rather than another attempt, and without this the loop grinds away at one only a person can give.
ownerOne person, by name.
A goal nobody owns becomes alerts nobody reads.
reviewA date the goal gets re-read, after which it expires.
Goals rot, and an expired one is easier to spot than a wrong one.
One goal, written out. Plain files in the repository, reviewed in a pull request like anything else that can change your code.
id: perf-entry-routes
intent: >
The three routes that carry our signups have to stay fast on a mid-range
phone. Historically they regress through images and third-party tags, not
through our own JavaScript, so look there first.
scope:
repos: [web]
paths: ["app/(marketing)/**", "next.config.ts"]
signal: npm run lighthouse:ci -- --routes / /pricing /checkout --metric performance
target: ">= 90 on every route"
guard: npm run bundle:report # a route made lazy to win the score gets caught here
triggers:
- on: merge
branch: main
- on: schedule
cron: "0 3 * * *"
authority: draft-pr # L2 on the ladder
budget:
pull_requests_per_week: 2
turns_per_attempt: 25
escalation:
after_failed_attempts: 2
notify: "@alfonso"
owner: "@alfonso"
review: 2027-03-01
Goals and wishes
Most first attempts are wishes. They are sentences everybody agrees with, which is a much weaker property than it sounds.
One question settles it: can you write the command that measures this? If you cannot, you have a wish, and handing a wish to an autonomous system buys you confident pull requests against a standard nobody stated.
- 01
A wish
Keep the code maintainable
A goal
No file in `src/` goes over 400 lines, and no function over a cyclomatic complexity of 12
Added: A command that returns a number
- 02
A wish
Improve performance
A goal
p75 LCP on `/`, `/pricing` and `/checkout` stays at or under 2.5 seconds, measured on the last 7 days of field data
Added: What is measured, where, and against which window
- 03
A wish
Stay up to date
A goal
No production dependency is more than one minor behind, and no major is more than 90 days behind its release
Added: A deadline, so the gap cannot grow quietly
- 04
A wish
Keep test coverage high
A goal
Branch coverage on changed files never lands below the figure the last release shipped with, and mutation score on `services/billing/` stays above 60%
Added: A baseline that moves with you, and a second signal the first one cannot be gamed against
Look at the fourth one. Coverage on its own is an instruction to raise coverage, and the cheapest way to raise coverage is to delete the failing test. Any goal expressed as one number is a number the system will move, by the cheapest route it can find.
How you would build one
Nothing here needs a model that does not exist yet. Four bands, one of them new work.
Band 01
Triggers
What wakes the system up. None of them is a person filing a ticket.
Schedule
Nightly or weekly, for signals that cost money to run
Merge event
The cheapest and the most useful: measure what just changed
Release
Check the goals that only mean something against shipped code
External feed
A CVE advisory, an upstream release, a provider deprecation
Production telemetry
An error budget burning, a field metric slipping, spend climbing
Band 02
The control plane
The part you have to build. It decides whether anything should happen, and it is the only part allowed to say no.
Goal registry
The goals as files in the repository, reviewed like code
Observer
Runs the signal, records the value, names the drift
Memory
What was proposed, merged, rejected and why, per goal
Planner
Picks the cheapest action that closes the gap, including none
Guard
Enforces authority and budget before a single agent starts
Band 03
The execution plane
The software factory. Goal-driven engineering keeps it busy without you.
Orchestrator
Turns the planner's action into a spec and a run
Worker agents
Do the work, in parallel, across every repository in scope
The harness
Your existing gates: checks, LLM review, routed human review
Band 04
The record
What a person looks at. If this layer is noisy, the system gets switched off within a month.
Ledger
Every observation and every action, with the evidence attached
Issues and pull requests
The output, in the tools your team already reads
Dashboard
One row per goal: where it stands and when it was last checked
Triggers are events you already emit. The execution plane is a software factory you either run already or can buy. The record layer is your issue tracker plus a page that reads the ledger. The control plane is the missing piece, and it is small: a registry of goal files, something that runs a command on a trigger, a store of what happened before, and a planner that picks the next action or picks none.
Watch the planner, because it is where people over-build. Its job is to pick the cheapest action that could close the gap, with doing nothing on the list, or it will generate work for its own sake.
Memory is what separates it from a linter
Take the memory out and you have a scheduled check that files a ticket, which is about a decade old.
Memory holds, per goal: what the signal read over time, what was proposed, what landed, what was rejected and why, and what is now out of bounds. That is what stops the second attempt at a problem from being a rerun of the first. The agent can open an issue saying coverage on services/billing has fallen for the fourth sprint running, that it proposed generated tests in March and you rejected them as meaningless, and that this drop sits in the two files that handle refunds. A linter repeats one sentence forever, at the same volume, until people learn to scroll past it.
Record the decision, not the discussion. Memory that stores threads rots within a quarter.
The autonomy ladder
The first thing people ask is whether an agent should be allowed to open pull requests on its own. Autonomy belongs to each goal rather than to the system, and it is granted one rung at a time against evidence.
Report
Measures the signal and writes it to the ledger. Opens nothing.
How a goal earns it Nothing. Every goal starts here, including the obvious ones.
Raise
Files an issue with the number, the change that moved it, and its reading of why.
How a goal earns it Two weeks at level 0 where every alert it would have raised is one you agree with.
Draft
Opens a draft pull request with a proposed fix and leaves it for a person to finish.
How a goal earns it Issues a human acted on more often than closed.
Propose
Opens a pull request ready for review, every gate already passed.
How a goal earns it Drafts that cleared the gates without somebody rewriting them.
Merge
Merges on its own, inside a blast radius agreed in advance, behind a flag it can roll back.
How a goal earns it A quarter at level 3 with no rollback traced to this goal.
A dependency bump can reach level 4 in a month. A schema migration may never leave level 1. Leaving a goal one rung too low costs somebody a button click. Putting it one rung too high costs you a merge at two in the morning, into a gate that was weaker than it looked.
The rungs are mine, and the bar on each one is a starting point rather than a measurement. I would expect a team that ran this for a year to move them, and to end up with different bars for different kinds of goal.
One goal, end to end
The performance goal above, over two weeks: p75 LCP on /checkout at or under 2.5 seconds.
- D6PR #412 merges a hero image with no width
- D6 ·1Observer runs on merge, goal breached, issue #415 opened
- D8 ·2Factory opens #418, gates pass, a person merges it
Day 6 is the part to look at. With nothing watching, day 6 is invisible. The pull request that caused it was reviewed, approved and merged by people checking whether the feature worked, and the regression surfaces weeks later through a complaint or somebody opening Lighthouse for another reason, by which point it is tangled up with thirty other merges.
With the goal registered, the observer runs on the merge event, reads 3.42 against a target of 2.5, checks the memory, and opens this.
Goal breached: p75 LCP on /checkout is 3.42s, target 2.5s.
The regression starts at a41f2c9 (PR #412, "Add seasonal hero"). LCP on that route was 2.18s on the previous commit. The new hero image is 1.4 MB, served as a PNG with no width or height, so it is both the LCP element and a layout shift.
History: this goal was breached in March by an uncompressed image in the same folder, and the fix then was to route it through the image pipeline. That pipeline exists. This file bypasses it.
Proposed action: convert the asset, set explicit dimensions, add the folder to the pipeline's glob. I am inside budget and my authority is draft pull requests, so I will open one unless somebody closes this within the hour.
PR #418 opened 40 minutes later. The gates passed. A person read the diff, agreed with the glob change, and merged it on day 8. The signal came back under target on the next run and the goal closed itself.
A threshold alert cannot write that issue. It names the commit, because it measured either side of one. It remembers March. And its proposal is the glob change, which fixes the next hero image as well as this one.
Multiply that across a handful of goals and repositories, and you get the layer a person looks at.
- Driftinginvariant · L2
Lighthouse performance stays above 90 on the three entry routes
web
87
target ≥ 90
Opened #418, 2h ago
next: On next merge
- Breachedinvariant · L1
Branch coverage never lands below the figure the last release shipped with
api
74%
target ≥ 81%
Opened #417, 1d ago
next: On next merge
- Metinvariant · L4
No critical advisory stays unpatched for more than 72 hours
api · web · jobs
0 open
target 0 open
Merged #414, 3d ago
next: Hourly, and on advisory
- Metinvariant · L4
No production dependency is more than one minor behind
api · web · jobs · sdk
1 minor
target ≤ 1 minor
Merged #410, 5d ago
next: Nightly
- Runningterminal · L3
Every service is off the deprecated auth client
api · jobs · sdk
7 of 11
target 11 of 11
Opened #419, 40m ago
next: Nightly
- Metinvariant · L3
The public API contract never breaks without a version bump
api · sdk
0 breaks
target 0 breaks
Reported, 6h ago
next: On next merge
What quietly breaks it
None of these are bugs.
The review queue floods
Six goals, each allowed three pull requests a week, is eighteen diffs nobody asked for landing on the same two reviewers.
The fix Budget per goal, and count the queue as a resource the way you count spend. While the queue is the constraint, autonomy anywhere else buys nothing.
The signal gets gamed
Coverage climbs because a failing test was deleted. The bundle shrinks because a route got lazy-loaded into a slower first paint. The number is honest and the codebase is worse.
The fix Pair every goal with a guard the same change cannot satisfy: coverage with mutation score, bundle size with LCP, latency with error rate.
Two goals want opposite things
Stay on the latest major of the framework, and keep the bundle under 200 KB. One release makes both impossible at once, and the system will quietly pick one.
The fix Give goals an explicit precedence, and make reporting a conflict a legal outcome. An agent that stops and names the trade-off is behaving correctly.
The memory rots
The same fix arrives for the fourth time, because the three rejections were comments on closed pull requests and nothing read them back.
The fix Write the decision into the goal's memory rather than the discussion: what was tried, what happened, what is now out of bounds and why.
Nobody reads it any more
A threshold set too tight alerts every week, everyone learns to close it, and the week it is right nothing happens.
The fix An alert people ignore is a broken goal. Re-tune it or retire it, and hold every goal to its review date.
Autonomy granted too early
A goal goes to level 4 because the first three fixes were clean, and the fourth merges at two in the morning into a gate that was never as strong as it looked.
The fix Move one goal up one rung at a time, with the evidence written down somewhere a sceptic can read it.
The first one kills most pilots. Human review is already the constraint in most teams, and a system built to generate more work to review finds that constraint in week one. Budget the queue before you budget anything else.
You cannot start here
This sits on top of a harness. The signal has to be a command that runs the same way on your laptop and in CI. The gates have to be trustworthy enough that a passing pull request means something, because from level 2 up they are what stands between an autonomous agent and your main branch. And you need a fast way to undo a change, because the system will eventually be wrong in a way nobody predicted.
Without those, goal-driven engineering will faithfully automate a process you do not trust. The harness guide covers what to put in the repository and what to wire around it, the baby steps put that work in order, and the Readiness Analyzer scores where you stand in about ninety seconds.
The smallest version worth running
Start with one goal, not with the control plane.
- 01
Pick the boring regression
The thing that has slipped twice already and that you can measure with a command you already have: coverage, bundle size, a Lighthouse score, dependency lag. Pick the most measurable goal you have rather than the most important one.
- 02
Write the record, all ten fields
Owner and review date included. If you cannot fill the signal field with a real command, stop and go write that command. It is worth having whether or not any of this follows.
- 03
Run it at level 0 for two weeks
A cron job, the command, and a file it appends to. No agent yet. You are finding out whether the signal is stable and whether the threshold is right, and you will almost certainly move the threshold.
- 04
Move to level 1 and read every issue
Now let it file issues, with the evidence and the commit it blames. Track one number: of the issues it opened, how many you are glad to have. My own cutoff would be about two thirds, though that figure is a guess and I would revise it after a month of real issues.
- 05
Add the memory before you add the second goal
The first time it proposes something you already rejected, you have found the thing that decides whether any of this scales. Fix it while you still have one goal.
- 06
Then a second goal, and only then a factory
Two goals running honestly at level 1 beats six at level 3 that everybody has stopped reading.
Where this goes
The dark factory is the usual end state for all of this: a spec in one end, a tested change out the other, almost nobody in the middle. It is a real destination, and parts of it already work on tasks whose intent is easy to write down and whose correctness a machine can judge.
Somebody still has to decide what should be true. Goal-driven engineering is the claim that the decision is the durable artifact, more durable than any ticket, and that once it is written down properly the same machinery that builds everything else can enforce it.
That moves your job one more step. You stopped writing all the code and started running the line that writes it. Now you write the goals the line is held to, decide how much rope each one gets, and read the few things that need you.
The book goes deeper into the machinery: the goal pattern and the dark factory in the orchestration chapter, and the gates that make any of it safe to run in the verification chapter. It is out now in Early Release.