Goal engineering answers what done means. Loop graph engineering answers how to get there without lying to yourself about progress. Together they are how I hand an AI agent a spec it can actually execute, and the second one is the part most teams skip.
The idea is simple to state. Decompose the goal into a graph of verifiable nodes. Build the graph before executing any of it. Then run a bounded loop on each node: change, verify, read the output, compare against the expectation you wrote in advance, close or diagnose.
What makes something a node
A node is the smallest change that can be independently verified as correct. If a unit of work cannot be verified on its own, it is not a node yet. Either split it further or merge it into a neighbor.
Every node gets five fields, written before anything runs. The precondition: what must already be true and verified before this node starts. The action: the concrete change. The verification: the exact command to run. The expected observable: the specific output, exit code, or runtime behavior that proves success, written before running anything. And the rollback: how to undo this node cleanly if it proves wrong.
The expected observable is the field people resist and the one that matters most. Writing “tests pass” before running tests is not an expectation. Writing “the build exits 0 and the inventory suite reports 14 passing, 0 failing” is. The difference is that the second one can fail loudly. When you write the expectation after seeing the output, you cannot be wrong, which means you learned nothing.
Edges, and why wide beats deep
Edges are dependencies. A node may only begin when every incoming edge terminates at a node that has passed its own verification. Never begin a node on the strength of an unverified precondition. That sentence has saved me more times than any other rule I work by, because the failure mode it prevents, building layer two on top of layer one that was never actually checked, is the one that produces the most confident and least true progress reports.
Where two nodes are independent, keep them independent. I deliberately prefer a wide shallow graph over a deep one. Long dependency chains hide the true source of a failure. When six nodes have run in sequence and the seventh fails, the cause could be anywhere in the chain. When three independent nodes fail the same way, the cause is almost always one shared precondition, and the graph itself points at it.
The bounded loop
Each node runs a loop. Make the change. Run the verification command. Read the actual output in full, not skimmed. Compare actual against expected. Match: close the node, move on. Mismatch: diagnose from the actual output, form one specific hypothesis, apply one targeted fix, repeat.
Two limits keep the loop from becoming thrashing. Three failed attempts on a single node, and I stop patching. A fix that failed three times is not three bad patches, it is one wrong assumption, and the repeated failure is evidence that the node, the graph, or the goal is wrong. I return to the graph, name the faulty assumption, revise it explicitly, and continue. Two graph revisions that fail to converge, and I escalate. Silently deviating from my own plan is a defect; revising it out loud is engineering.
I saw this pay for itself building Agaro ERP’s financial reporting. The graph put schema verification, seed data verification, and report logic in three independent nodes. When report totals came back wrong, two of the three nodes had already passed with recorded outputs, which eliminated half the search space before I read a single line of report code. The fault was in a seed fixture, exactly where the graph said to look last.
Verification discipline
Where practical, write the check before the implementation. Run it. Confirm it fails for the reason you expect. Only then implement. A test that has never been observed failing has not been shown to test anything. This is the same rule that makes verification meaningful at the system level: verify at every layer the change touches, build, types, lint, unit, integration, and where it matters, real runtime behavior. A green unit test on a code path nothing exercises at runtime is not evidence.
And before declaring anything complete, run the full suite, not just the tests the work touched. Passing the tests you wrote for the change and achieving the goal are different claims. The second one only counts after you have walked the success criteria from goal engineering, criterion by criterion, each with a command and a result attached.
Why this is system engineering
None of this is specific to any model or framework. It is classical system engineering applied to a new kind of executor: decompose into verifiable units, specify expectations in advance, verify at every boundary, bound the loops, keep the plan honest when reality disagrees with it. What the agent changes is who does the typing. What it does not change is what makes the work trustworthy.
That is the whole trick of practical AI autonomy. You do not get reliability by asking the model to be careful. You get it by building a spec where care is structurally impossible to skip.