Skip to content
The idea

Goal-driven engineering: the software factory nobody has to ask.

A software factory waits to be asked. You hand it a spec, it hands you a pull request, and it goes quiet. Goal-driven engineering removes the asking. You write a goal once, in plain words, with a command that measures it. Then a monitoring agent watches the repositories in scope, notices the day the goal stops holding, and opens the issue or the pull request that makes it hold again.

  • 11 min read
  • Updated 22 September 2026
  • Alfonso Graziano

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.

The full architecture of a goal-driven engineering system. People write goals into a registry. Triggers wake a goal manager made of an observer, a memory, a planner and a guard. Below its authority it files an issue; above it, it sends work to a software factory whose pull requests pass the same harness gates as human work before merging. Merged changes deploy behind a flag into production, and production telemetry feeds back in as a signal.THE GOAL MANAGEREXECUTIONPRODUCTIONTriggersSchedule, merge, release, advisory,telemetry alertGoal registrygoals/*.yml in Git, reviewed in a pullrequestYouWrite the goals,own them, answerthe escalationsObserverRuns the signal,records the readingMemoryTried, landed,rejected, and whyPlannerThe cheapestaction, or noneGuardAuthority andbudget, beforeanything startsSoftware factoryAn orchestrator and worker agents, oneper repo in scopeHarness gatesChecks, LLM review,routed human reviewmainMerged onceevery gateis greenDeployBehind a flag, releasedas a canaryProductionReal traffic, realdata, real moneyTelemetryField metrics, errors,latency, spendThe recordEveryobservation andaction, whereyour teamalready readsMERGES, DEPLOYS AND PRODUCTION DATA COME BACK INA SPEC, A SCOPE, A BUDGETISSUEPRS
  1. 01You write a goal and commit it to the registry. Nothing else in the picture is written by a person.
  2. 02A trigger wakes the goal manager. The observer runs that goal's signal and records what it read.
  3. 03The planner compares the reading against the target, reads the memory, and picks an action or picks none.
  4. 04The guard checks the goal's authority and budget before any agent starts.
  5. 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.
  6. 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.
  7. 07Merged changes deploy behind a flag and go out as a canary.
  8. 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.
  9. 09Every observation and action lands in the record, and the record is what a person reads.
The whole system. Production telemetry is a signal source, not just somewhere changes end up.

Four things to build, in this order. Each one is a section below.

  1. 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.

  2. 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.

  3. 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.

  4. 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
The first row is the difference. The other three follow from it.

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.

A closed loop of four stages around a goal: observe the codebase, compare the reading against the goal, decide what to do, act on it, and observe the changed codebase again.SETPOINTTHE CODEBASESENSORObserveERRORCompareCONTROLLERDecideACTUATORActTHE GOALwritten once
  1. 01

    Observe Sensor

    Run the goal's signal against the codebase as it stands, and write the number down.

  2. 02

    Compare Error

    Hold the number against the target. The gap between them is the only reason the loop ever does anything.

  3. 03

    Decide Controller

    Read what was already tried, then pick the cheapest action that closes the gap. Doing nothing counts.

  4. 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.

A thermostat, with a codebase where the room should be.

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.

intent

Plain 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.

scope

The 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.

signal

A command that returns the current value, or a verdict.

The sensor. If you cannot write it, what you have is a wish.

target

The 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.

triggers

When 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.

authority

The 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.

budget

Ceilings: 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.

escalation

What 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.

owner

One person, by name.

A goal nobody owns becomes alerts nobody reads.

review

A date the goal gets re-read, after which it expires.

Goals rot, and an expired one is easier to spot than a wrong one.

10 fields. A goal that is missing one of them is a goal the system has to guess at.

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.

  1. 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

  2. 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

  3. 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

  4. 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

The rewrite is not longer prose. It is the sentence with a measurement in it.

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

Four bands. The control plane is the only one you have to build.

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.

L0

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.

L1

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.

L2

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.

L3

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.

L4

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.

Autonomy is granted per goal, one rung at a time, against evidence.

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.

Largest contentful paint on one route over fourteen days. It sits near two seconds, jumps above the two and a half second target on day six when a pull request merges, stays there for three days, and returns under the target on day nine after a fix is merged.1s2s3s4sTARGET 2.5s12DAY 0DAY 13
  1. D6PR #412 merges a hero image with no width
  2. D6 ·1Observer runs on merge, goal breached, issue #415 opened
  3. D8 ·2Factory opens #418, gates pass, a person merges it
p75 LCP on /checkout stays at or under 2.5s. Illustrative numbers: the shape is the argument.

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.

Issue #415, opened 11 minutes after the merge
Monitor

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.

What happened

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.

goals.registry · 6 goals · 4 repositories · 2 need attention
  • 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

A mock of the record layer, not a product. Two of six goals need a person today.

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.

Every one of these shows up as the system working exactly as written.

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Common questions

What is goal-driven engineering?
Goal-driven engineering makes the unit of work a standing goal instead of a ticket. You write the goal in plain words, pair it with a command that measures whether it holds, and give it a scope, a budget and a level of authority. A monitoring agent runs that command on a schedule or on every merge, compares the result against the target, and acts when the two come apart, by opening an issue or by sending the work to a software factory that opens a pull request. Nobody files the request. The goal is the request, and it stays filed.
How is it different from a software factory or a dark factory?
A software factory is reactive. It turns a spec into a reviewed change, and it does nothing until somebody hands it one. Goal-driven engineering is the layer that decides when to hand it one. You need both in practice: goal-driven engineering with no factory can only file issues, and a factory with no goals only ever works on what a person already noticed.
What makes a good goal?
A good goal has a signal you can run as a command and a target to compare it against. "Keep the code maintainable" cannot be checked, so it is a wish. "No file in src/ goes over 400 lines" can be checked, so it is a goal. The test is simple: if you cannot write the command that measures it, you do not have a goal yet. A good goal also names one owner, carries a budget, and has a date on which somebody re-reads it.
Is it safe to let an agent open pull requests unprompted?
It is as safe as the gates the pull request has to pass, which is why goal-driven engineering sits on top of a harness rather than replacing one. Autonomy is granted per goal on a five-rung ladder, starting at report-only and moving up one rung at a time against evidence. A goal only opens issues after a stretch where every alert it would have raised was one you agreed with, and it only merges on its own inside a blast radius agreed in advance, behind a flag that can roll it back.
What stops the agent from gaming its own metric?
Nothing, if you give it one number. Coverage climbs when a failing test is deleted, and a bundle shrinks when a route is lazy-loaded into a slower first paint. Pair every goal with a guard signal the same change cannot satisfy: coverage with mutation score, bundle size with LCP, latency with error rate. A goal with one number is an instruction to move that number.
Do I need this if I already have CI, Dependabot and a linter?
Those tools are the same idea with a fixed set of goals somebody else wrote, and with nothing to do about them beyond failing a build or opening one templated bump. Goal-driven engineering lets you write the goals yourself, in plain words, across several repositories at once, and gives the thing watching them a memory of what it already tried and the ability to open a real change. If a linter is the right tool for your goal, use the linter. Most of the goals teams care about cannot be written as a lint rule.