Three terms now compete for the same line in AI engineering job descriptions. Prompt engineering is the established one. Loop engineering entered the AI vocabulary in late 2025 and dominated developer discussion through June 2026. Graph engineering followed roughly six weeks later.
They get used interchangeably. Should they be?
The three are not competing techniques. They are three different units of control, stacked. A prompt controls one model response. A loop controls one agent’s behavior cycle. A graph controls the organization of many agents. Each layer preserves the layer beneath it. A prompt does not disappear once a loop is built around it. it stops being the thing typed by hand.
This article separates the three: what gets designed at each layer, what the published claim says about when the higher layers pay for themselves, and where the skepticism is warranted.
One task, three layers
:root{
–bg:#0B1E1E; –panel:#122C2C; –line:#274B4B; –ink:#E8F5F1; –muted:#8FB3AC;
–prompt:#9BD7FF; –loop:#F2B705; –graph:#3FD9A8; –human:#FF9E7D;
–mono:ui-monospace,SFMono-Regular,”JetBrains Mono”,Menlo,Consolas,monospace;
–sans:-apple-system,BlinkMacSystemFont,”Segoe UI”,Inter,Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:transparent}
#plg-app{background:var(–bg); color:var(–ink); font-family:var(–sans);
border:1px solid var(–line); border-radius:14px; overflow:hidden; max-width:920px; margin:0 auto}
.hd{padding:20px 22px 16px; border-bottom:1px solid var(–line); background:linear-gradient(180deg,#143333 0%,#0B1E1E 100%)}
.eyebrow{font-family:var(–mono); font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(–muted); margin:0 0 8px}
.hd h2{margin:0; font-size:clamp(19px,2.6vw,25px); line-height:1.2; letter-spacing:-.02em; font-weight:700}
.hd p{margin:8px 0 0; color:var(–muted); font-size:14px; line-height:1.55; max-width:64ch}
.taskbar{display:flex; align-items:center; gap:14px; flex-wrap:wrap;
padding:14px 22px; background:#0E2525; border-bottom:1px solid var(–line)}
.taskbar .tl{font-family:var(–mono); font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(–muted)}
.taskbar .tv{font-size:14.5px; font-weight:600; flex:1; min-width:210px}
.btn{appearance:none; cursor:pointer; border:1px solid var(–line); background:#0B1E1E; color:var(–ink);
font-family:var(–mono); font-size:11.5px; letter-spacing:.06em; text-transform:uppercase;
padding:9px 15px; border-radius:7px; transition:border-color .18s, background .18s}
.btn:hover{border-color:var(–graph); background:#123232}
.btn:focus-visible{outline:2px solid var(–graph); outline-offset:2px}
.btn.primary{border-color:var(–graph); color:var(–graph)}
.btn[disabled]{opacity:.45; cursor:not-allowed}
.lanes{display:grid; grid-template-columns:repeat(3,1fr); background:var(–panel)}
.lane{padding:0 0 14px; border-right:1px solid var(–line)}
.lane:last-child{border-right:0}
.lh{padding:13px 15px 11px; border-bottom:1px solid var(–line)}
.lh .k{font-family:var(–mono); font-size:10px; letter-spacing:.13em; text-transform:uppercase; margin:0 0 4px}
.lane.p .lh .k{color:var(–prompt)} .lane.l .lh .k{color:var(–loop)} .lane.g .lh .k{color:var(–graph)}
.lh h3{margin:0; font-size:15px; letter-spacing:-.01em; font-weight:650}
.lh .sub{margin:4px 0 0; font-size:12px; line-height:1.45; color:var(–muted)}
.steps{list-style:none; margin:0; padding:12px 15px 0; min-height:250px}
.steps li{display:flex; gap:9px; align-items:flex-start; margin-bottom:9px;
font-size:12.5px; line-height:1.45; opacity:0; transform:translateY(5px);
transition:opacity .3s ease, transform .3s ease}
.steps li.show{opacity:1; transform:none}
.steps li .dot{flex:0 0 auto; width:7px; height:7px; border-radius:50%; margin-top:5px; background:var(–line)}
.lane.p .steps li.show .dot{background:var(–prompt)}
.lane.l .steps li.show .dot{background:var(–loop)}
.lane.g .steps li.show .dot{background:var(–graph)}
.steps li.hum .dot{background:var(–human) !important; box-shadow:0 0 0 3px rgba(255,158,125,.16)}
.steps li .tx{color:var(–muted)}
.steps li.hum .tx{color:var(–ink)}
.steps li .tx b{color:var(–ink); font-weight:600}
.steps li .tx em{font-style:normal; color:var(–human); font-family:var(–mono); font-size:10px;
letter-spacing:.1em; text-transform:uppercase; display:block; margin-top:2px}
.lstat{margin:8px 15px 0; padding-top:11px; border-top:1px dashed var(–line);
display:flex; gap:14px; flex-wrap:wrap; font-family:var(–mono); font-size:10.5px; color:var(–muted)}
.lstat b{color:var(–ink)}
.cmp{border-top:1px solid var(–line); background:#0E2525; padding:16px 22px 18px}
.cmp h4{margin:0 0 11px; font-family:var(–mono); font-size:10px; letter-spacing:.13em;
text-transform:uppercase; color:var(–muted); font-weight:500}
table{width:100%; border-collapse:collapse; font-size:13px}
th,td{text-align:left; padding:9px 10px; vertical-align:top; line-height:1.45; border-bottom:1px solid #1c3a3a}
thead th{font-family:var(–mono); font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(–muted); font-weight:500}
tbody th{font-family:var(–mono); font-size:10.5px; letter-spacing:.06em; color:var(–muted); font-weight:500; width:150px}
td{color:var(–ink)}
.c-p{color:var(–prompt)} .c-l{color:var(–loop)} .c-g{color:var(–graph)}
tbody tr:last-child th, tbody tr:last-child td{border-bottom:0}
.ft{padding:11px 22px; border-top:1px solid var(–line); background:#0E2525;
display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap;
font-family:var(–mono); font-size:10.5px; letter-spacing:.05em; color:var(–muted)}
.ft b{color:var(–graph); font-weight:600}
@media (max-width:760px){
.hd{padding:16px 15px 13px}
.taskbar{padding:13px 15px}
.lanes{grid-template-columns:1fr}
.lane{border-right:0; border-bottom:1px solid var(–line)}
.lane:last-child{border-bottom:0}
.steps{min-height:0; padding-bottom:4px}
.cmp{padding:14px 15px 16px; overflow-x:auto}
table{min-width:520px}
.ft{padding:10px 15px}
}
@media (prefers-reduced-motion:reduce){ .steps li{transition:none} }
Interactive explainer · 01
One task, three layers — watch what changes
The three terms are not competing techniques. They are three different units of control. Here is the same job handled at each layer, step by step. Orange dots mark the moments a human is required.
Task
Fix the failing tests in the auth module, then open a pull request.
Run all three
Reset
Layer 1
Prompt engineering
You control one model response. You are the loop.
Your turns 0 · Model calls 0
Layer 2
Loop engineering
You control one agent’s cycle. The loop does the prompting.
Your turns 0 · Model calls 0
Layer 3
Graph engineering
You control how many agents are organised.
Your turns 0 · Nodes 0 · Parallel 0
What actually differs
PromptLoopGraph
Unit of control
One model response
One agent’s behaviour cycle
An organisation of agents
What you write
Instructions, examples, output format
Trigger, tools, stop condition, retry budget
Nodes, edges, shared state, failure routes
Who says “again?”
A human, every turn
A verifier the loop calls itself
A routing rule written in advance
Where it breaks
Ambiguous or overstuffed instruction
It cannot tell done from stuck
Context never crossed an edge you forgot to draw
Enough when
One shot, a person reads the result
Repetitive, machine-checkable, one domain
Cross-domain work with parallel branches
Illustrative walkthrough · step counts are not benchmarks
Built by Marktechpost
(function(){
var PROMPT = [
{h:1, t:”You write the prompt: fix the failing auth tests”, tag:”you type”},
{h:0, t:”Model returns a patch for two files”},
{h:1, t:”You run pytest — two tests still fail”, tag:”you verify”},
{h:1, t:”You paste the traceback back into the chat”, tag:”you re-prompt”},
{h:0, t:”Model returns a revised patch”},
{h:1, t:”You run pytest — green — and open the PR”, tag:”you finish”}
];
var LOOP = [
{h:0, t:”Trigger fires on schedule at 09:00″},
{h:0, t:”Act — reads the CI log, edits the failing files”},
{h:0, t:”Verify — runs pytest, two tests still fail”},
{h:0, t:”Decide — below the bar, retry with updated state”},
{h:0, t:”Act — revises the patch”},
{h:0, t:”Verify — pytest green, stop condition met”},
{h:0, t:”Exit — opens the PR, writes the state file”},
{h:1, t:”You review the PR you did not write”, tag:”you review”}
];
var GRAPH = [
{h:0, t:”Plan node splits the task into three branches”},
{h:0, t:”Parallel — test analyst, patch writer, security reviewer run at once”},
{h:0, t:”Join node merges the three branch outputs”},
{h:0, t:”Validator gate fails on the security branch”},
{h:0, t:”Retry edge routes work back to the patch writer”},
{h:0, t:”Validator passes on the second attempt”},
{h:0, t:”Ship node opens the PR and checkpoints state”},
{h:1, t:”You review the PR you did not write”, tag:”you review”}
];
var timers = [];
function clearAll(){ timers.forEach(clearTimeout); timers = []; }
function el(id){ return document.getElementById(id); }
function build(listId, data){
var ul = el(listId); ul.innerHTML = “”;
data.forEach(function(s){
var li = document.createElement(“li”);
if(s.h) li.className = “hum”;
li.innerHTML = ” + s.t + (s.tag ? ” + s.tag + ” : ”) + ”;
ul.appendChild(li);
});
}
function reset(){
clearAll();
build(“sp”, PROMPT); build(“sl”, LOOP); build(“sg”, GRAPH);
[“hp”,”mp”,”hl”,”ml”,”hg”,”mg”,”pg”].forEach(function(i){ el(i).textContent = “0”; });
el(“run”).disabled = false;
ping();
}
function play(listId, data, pace, counters){
var ul = el(listId), items = ul.querySelectorAll(“li”);
var hum = 0, calls = 0;
data.forEach(function(s, i){
timers.push(setTimeout(function(){
items[i].classList.add(“show”);
if(s.h) hum++; else calls++;
el(counters.h).textContent = String(hum);
if(counters.m) el(counters.m).textContent = String(counters.mv ? counters.mv(calls) : calls);
ping();
}, pace * (i + 1)));
});
}
el(“reset”).addEventListener(“click”, reset);
el(“run”).addEventListener(“click”, function(){
reset();
el(“run”).disabled = true;
play(“sp”, PROMPT, 620, {h:”hp”, m:”mp”});
play(“sl”, LOOP, 620, {h:”hl”, m:”ml”});
play(“sg”, GRAPH, 620, {h:”hg”, m:”mg”});
timers.push(setTimeout(function(){ el(“pg”).textContent = “3”; ping(); }, 620 * 2));
timers.push(setTimeout(function(){ el(“run”).disabled = false; }, 620 * 9));
});
function ping(){
try{
var h = document.getElementById(“plg-app”).offsetHeight + 40;
parent.postMessage({ plgFrame:”embed1-one-task-three-layers”, height:h }, “*”);
}catch(e){}
}
window.addEventListener(“load”, ping);
window.addEventListener(“resize”, ping);
setTimeout(ping, 250);
reset();
})();
The stack, in order
Each step in the progression was named in practice before it appeared in vendor documentation.
Prompt engineering covers writing and structuring the instruction for a single call. Anthropic’s guidance is to separate a system prompt into labeled sections — background information, instructions, tool guidance, output description — delineated with XML tags or Markdown headers. The recommendation is to supply the minimal set of information that fully specifies the expected behavior. Minimal does not mean short.
Context engineering came next. Anthropic describes it as the natural progression of prompt engineering. The question moves from finding the right words to deciding what configuration of tokens belongs in the window at all. Context is a finite resource, and the engineering problem is optimizing the utility of those tokens against model constraints.
Harness engineering covers the environment a single agent runs inside: files, tools, memory, feedback.
Loop engineering sits one floor above the harness. A June 2026 arXiv paper on agentic AI in building engineering, Buildrix, sets out the same four-step progression explicitly — prompt, then context, then harness, then loop — with the final layer defining how a system repeatedly observes, acts, verifies and recovers.
Graph engineering is the newest label and the least settled. One enterprise writeup notes that the term’s provenance is unresolved and that it collides with an older knowledge-graph usage of the same word. The underlying practice, graph-based orchestration, has a documented lineage in multi-agent systems research.
Layer 1: Prompt Engineering
The defining assumption is that a human is present at every iteration. A prompt is written, the model responds, the output is judged, the prompt is revised.
That assumption is what breaks. High volume. Multi-step tasks. No human available to grade the output. Results that feed the next step automatically. Any one of these, and the prompt alone stops being sufficient.
Nothing about the prompt got worse. The surrounding conditions changed.
Prompt engineering also does not vanish inside the higher layers. Anthropic’s multi-agent research writeup reports that prompt engineering was the primary lever for fixing coordination failures. Early versions spawned 50 subagents for simple queries, and the fix was prompting rather than topology.
Prompt engineering, explained
:root{
–bg:#0B1E1E; –panel:#122C2C; –line:#274B4B; –ink:#E8F5F1; –muted:#8FB3AC;
–prompt:#9BD7FF; –human:#FF9E7D; –graph:#3FD9A8;
–mono:ui-monospace,SFMono-Regular,”JetBrains Mono”,Menlo,Consolas,monospace;
–sans:-apple-system,BlinkMacSystemFont,”Segoe UI”,Inter,Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:transparent}
#plg-app{background:var(–bg); color:var(–ink); font-family:var(–sans);
border:1px solid var(–line); border-radius:14px; overflow:hidden; max-width:920px; margin:0 auto}
.hd{padding:20px 22px 16px; border-bottom:1px solid var(–line); background:linear-gradient(180deg,#143333 0%,#0B1E1E 100%)}
.eyebrow{font-family:var(–mono); font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(–prompt); margin:0 0 8px}
.hd h2{margin:0; font-size:clamp(19px,2.6vw,25px); line-height:1.2; letter-spacing:-.02em; font-weight:700}
.hd .def{margin:10px 0 0; font-size:14.5px; line-height:1.6; color:var(–muted); max-width:66ch}
.hd .def b{color:var(–ink)}
.flowwrap{padding:16px 22px 10px; background:var(–panel); border-bottom:1px solid var(–line)}
.scroller{overflow-x:auto; -webkit-overflow-scrolling:touch}
svg{width:100%; height:auto; display:block}
.lb{font-family:var(–mono); font-size:11px; fill:var(–muted)}
.lbb{font-family:var(–sans); font-size:13px; fill:var(–ink); font-weight:600}
.bx{fill:#0E2525; stroke:var(–line); stroke-width:1.3}
.swipe{display:none; margin:6px 0 0; font-family:var(–mono); font-size:10px;
letter-spacing:.1em; text-transform:uppercase; color:var(–muted)}
.cap{margin:2px 0 0; font-size:12.5px; line-height:1.5; color:var(–muted); text-align:center}
.split{display:grid; grid-template-columns:1fr 1fr; background:var(–panel)}
.col{padding:16px 20px}
.col + .col{border-left:1px solid var(–line); background:#0E2525}
.col h3{margin:0 0 4px; font-family:var(–mono); font-size:10.5px; letter-spacing:.13em;
text-transform:uppercase; color:var(–muted); font-weight:500}
.col .sub{margin:0 0 12px; font-size:12.5px; line-height:1.5; color:var(–muted)}
.blk{display:block; width:100%; text-align:left; appearance:none; cursor:pointer;
border:1px solid var(–line); background:#0B1E1E; color:var(–ink); border-radius:8px;
padding:10px 12px; margin-bottom:8px; transition:border-color .18s, background .18s}
.blk:hover{border-color:var(–prompt); background:#0f2730}
.blk:focus-visible{outline:2px solid var(–prompt); outline-offset:2px}
.blk.on{border-color:var(–prompt); background:#0f2a33}
.blk .tag{font-family:var(–mono); font-size:11px; color:var(–prompt); letter-spacing:.02em}
.blk .ln{display:block; margin-top:4px; font-size:12.5px; line-height:1.45; color:var(–muted)}
#expl h4{margin:0 0 7px; font-size:15.5px; line-height:1.35; letter-spacing:-.01em; font-weight:650}
#expl p{margin:0 0 10px; font-size:13.5px; line-height:1.6; color:var(–muted)}
#expl p b{color:var(–ink)}
#expl .quote{border-left:2px solid var(–prompt); padding-left:12px; font-size:13px; line-height:1.6; color:var(–muted)}
.brk{border-top:1px solid var(–line); background:var(–panel); padding:16px 22px 18px}
.brk h3{margin:0 0 4px; font-family:var(–mono); font-size:10.5px; letter-spacing:.13em;
text-transform:uppercase; color:var(–muted); font-weight:500}
.brk .sub{margin:0 0 12px; font-size:13.5px; line-height:1.55; color:var(–muted); max-width:66ch}
.brk .sub b{color:var(–ink)}
.chips{display:flex; gap:8px; flex-wrap:wrap; margin-bottom:12px}
.chip{appearance:none; cursor:pointer; border:1px solid var(–line); background:#0E2525; color:var(–muted);
font-family:var(–mono); font-size:11px; letter-spacing:.05em; padding:8px 13px; border-radius:20px;
transition:border-color .18s, color .18s, background .18s}
.chip:hover{color:var(–ink); border-color:var(–human)}
.chip:focus-visible{outline:2px solid var(–human); outline-offset:2px}
.chip.on{color:var(–human); border-color:var(–human); background:#2a1a14}
.conseq{border:1px solid var(–line); border-radius:9px; background:#0E2525; padding:13px 15px;
font-size:13.5px; line-height:1.6; color:var(–muted); min-height:74px}
.conseq b{color:var(–ink)}
.conseq .verdict{display:block; margin-top:7px; font-family:var(–mono); font-size:11px;
letter-spacing:.06em; color:var(–human)}
.ft{padding:11px 22px; border-top:1px solid var(–line); background:#0E2525;
display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap;
font-family:var(–mono); font-size:10.5px; letter-spacing:.05em; color:var(–muted)}
.ft b{color:var(–graph); font-weight:600}
@media (max-width:700px){
.hd{padding:16px 15px 13px}
.flowwrap,.brk{padding-left:15px; padding-right:15px}
.split{grid-template-columns:1fr}
.col + .col{border-left:0; border-top:1px solid var(–line)}
.col{padding:15px}
.ft{padding:10px 15px}
svg{min-width:560px}
.swipe{display:block}
}
@media (prefers-reduced-motion:reduce){ .anim{animation:none !important} }
Interactive explainer · 02 · Layer 1
What prompt engineering is
Prompt engineering is designing the text of a single call to a model. One input, one forward pass, one output — and a human who reads the result and decides whether to run it again. That last part is the assumption everything above this layer exists to remove.
Prompt
what you write
Model
one forward pass
Response
one output
you
you judge it, then rewrite the prompt yourself
Swipe the diagram sideways →
The cycle exists here too — but you are the one closing it, every single turn.
Anatomy of a prompt
Tap a section. Anthropic’s guidance is to delineate parts with XML tags or Markdown headers rather than writing one undifferentiated block.
<background_information>What the model needs to know before it starts
<instructions>The task itself, and the rules for doing it
## Tool guidanceWhich tool to reach for, and when
## Output descriptionThe exact shape the answer should take
<examples>A few cases that cover the edges you actually hit
What this section does
Background information
The assumption that breaks
Prompt engineering assumes a human is present at every iteration. Tap a condition to see what happens when that stops being true.
high volume
multi-step task
nobody available to grade it
output feeds the next step
Layer 1 of 3 · the layer most tasks stop at
Built by Marktechpost
(function(){
var SEC = {
bg:{t:”Background information”,
b:”Everything the model cannot infer: the codebase conventions, the domain vocabulary, the constraint that exists because of one incident two years ago. Left out, the model fills the gap with a confident guess.”,
q:”Anthropic’s advice is to aim for the minimal set of information that fully describes the expected behaviour — and it is explicit that minimal does not mean short.”},
ins:{t:”Instructions”,
b:”The task and the rules for performing it. The common failure sits at two extremes: brittle if-else instructions hardcoded for cases you already saw, or instructions so general they assume context the model never received.”,
q:”Start at the minimum, watch how the model behaves, then add clear instructions and examples where precision is actually missing.”},
tool:{t:”Tool guidance”,
b:”Which tools exist, what each is for, and how to choose. An agent searching the web for something that only lives in Slack is doomed before it starts — the failure is in the tool description, not the reasoning.”,
q:”Anthropic reported that rewriting a bad tool description cut task completion time by 40% for later agents using it.”},
out:{t:”Output description”,
b:”The shape of the answer: the schema, the field names, the length, the format. This is the section that most often turns a model call from a demo into something another program can consume.”,
q:”Once output is machine-readable, it can be checked mechanically — which is the precondition for building a loop on top of it.”},
ex:{t:”Examples”,
b:”A handful of demonstrations, chosen to cover the cases that actually go wrong rather than the ones that already work. Examples move behaviour further per token than more prose instructions usually do.”,
q:”The point of examples is coverage of edges, not volume. Ten redundant examples teach less than three well-chosen ones.”}
};
var CQ = {
vol:{b:”You cannot sit in the chair for ten thousand runs. The prompt may be perfect and still be the wrong unit of work — there is no human bandwidth to close the cycle.”,
v:”u2192 you need a trigger, which means a loop”},
multi:{b:”Step three depends on what step two discovered. A single call cannot branch on information it does not have yet, and re-prompting by hand turns you into the control flow.”,
v:”u2192 you need iteration, which means a loop”},
nobody:{b:”Nobody reads the output before something acts on it. Quality now has to be decided by a check the system can run itself — tests, a schema, a rubric, a second model.”,
v:”u2192 you need a stop condition, which means a loop”},
chain:{b:”The response is consumed by the next step automatically. An error no longer costs you a re-run; it propagates, and it does so while nobody is looking.”,
v:”u2192 you need verification and failure routing”}
};
function el(id){ return document.getElementById(id); }
function showSec(k){
var d = SEC[k];
el(“ex-t”).textContent = d.t;
el(“ex-b”).innerHTML = d.b;
el(“ex-q”).innerHTML = d.q;
[].slice.call(document.querySelectorAll(“.blk”)).forEach(function(b){
b.classList.toggle(“on”, b.getAttribute(“data-k”) === k);
});
ping();
}
function showCq(k){
var d = CQ[k];
el(“cq”).innerHTML = d.b + ” + d.v + ”;
[].slice.call(document.querySelectorAll(“.chip”)).forEach(function(c){
c.classList.toggle(“on”, c.getAttribute(“data-c”) === k);
});
ping();
}
[].slice.call(document.querySelectorAll(“.blk”)).forEach(function(b){
b.addEventListener(“click”, function(){ showSec(b.getAttribute(“data-k”)); });
});
[].slice.call(document.querySelectorAll(“.chip”)).forEach(function(c){
c.addEventListener(“click”, function(){ showCq(c.getAttribute(“data-c”)); });
});
function ping(){
try{
var h = document.getElementById(“plg-app”).offsetHeight + 40;
parent.postMessage({ plgFrame:”embed2-prompt-explained”, height:h }, “*”);
}catch(e){}
}
window.addEventListener(“load”, ping);
window.addEventListener(“resize”, ping);
setTimeout(ping, 250);
showSec(“bg”); showCq(“vol”);
})();
Layer 2: loop engineering
The framing is that a coding agent is a brute-force tool for finding solutions. The craft is designing the goal, the tools and the loop and not only the prompt.
The term reached mainstream developer discussion in June 2026, after a widely shared post argued that engineers should stop prompting coding agents and start designing the loops that prompt them. The Claude Code team at Anthropic described the same shift on stage that week.
The most detailed public breakdown identifies five primitives, plus a sixth element that holds them together:
Automations: a schedule or event that performs discovery and triage without supervision
Worktrees: isolation so parallel agents cannot edit the same files
Skills: project knowledge written down once in a SKILL.md, rather than re-explained every session
Plugins and connectors: MCP-based access to the issue tracker, database or staging API
Sub-agents: a maker/checker split, since the model that wrote the code grades it too generously
State: a markdown file or board outside the conversation, because the model forgets between runs
Two in-session features are very important. /loop re-runs on a cadence. /goal runs until a written condition is actually true, with a separate small model checking after each turn — so the agent that wrote the code is not the agent grading it. Both Claude Code and the Codex app ship equivalents.
The cycle is not the hard part. The stop condition is. A loop that cannot mechanically distinguish done from stuck does not fail loudly. It keeps spending tokens.
Loop engineering, explained
:root{
–bg:#0B1E1E; –panel:#122C2C; –line:#274B4B; –ink:#E8F5F1; –muted:#8FB3AC;
–loop:#F2B705; –graph:#3FD9A8; –fail:#FF8A73;
–mono:ui-monospace,SFMono-Regular,”JetBrains Mono”,Menlo,Consolas,monospace;
–sans:-apple-system,BlinkMacSystemFont,”Segoe UI”,Inter,Roboto,Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:transparent}
#plg-app{background:var(–bg); color:var(–ink); font-family:var(–sans);
border:1px solid var(–line); border-radius:14px; overflow:hidden; max-width:920px; margin:0 auto}
.hd{padding:20px 22px 16px; border-bottom:1px solid var(–line); background:linear-gradient(180deg,#143333 0%,#0B1E1E 100%)}
.eyebrow{font-family:var(–mono); font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(–loop); margin:0 0 8px}
.hd h2{margin:0; font-size:clamp(19px,2.6vw,25px); line-height:1.2; letter-spacing:-.02em; font-weight:700}
.hd .def{margin:10px 0 0; font-size:14.5px; line-height:1.6; color:var(–muted); max-width:66ch}
.hd .def b{color:var(–ink)}
.band{display:grid; grid-template-columns:1fr 1fr; background:var(–panel)}
.side{padding:16px 20px}
.side + .side{border-left:1px solid var(–line); background:#0E2525}
.side h3{margin:0 0 4px; font-family:var(–mono); font-size:10.5px; letter-spacing:.13em;
text-transform:uppercase; color:var(–muted); font-weight:500}
.side .sub{margin:0 0 12px; font-size:12.5px; line-height:1.5; color:var(–muted)}
.ring{max-width:290px; margin:0 auto}
svg{width:100%; height:auto; display:block}
.node{fill:#0E2525; stroke:var(–line); stroke-width:1.3; transition:stroke .2s, fill .2s}
.node.on{stroke:var(–loop); fill:#241f08}
.nl{font-family:var(–mono); font-size:11px; fill:var(–muted); transition:fill .2s}
.nl.on{fill:var(–loop)}
.ns{font-family:var(–mono); font-size:8.5px; fill:#5f8880}
.ctr{font-family:var(–mono); font-size:24px; fill:var(–ink); font-weight:700}
.ctrl{font-family:var(–mono); font-size:9px; fill:var(–muted); letter-spacing:.14em}
.btns{display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; justify-content:center}
.btn{appearance:none; cursor:pointer; border:1px solid var(–line); background:#0E2525; color:var(–ink);
font-family:var(–mono); font-size:11.5px; letter-spacing:.06em; text-transform:uppercase;
padding:9px 14px; border-radius:7px; transition:border-color .18s, background .18s}
.btn:hover{border-color:var(–loop); background:#1a2418}
.btn:focus-visible{outline:2px solid var(–loop); outline-offset:2px}
.btn.primary{border-color:var(–loop); color:var(–loop)}
.btn[disabled]{opacity:.45; cursor:not-allowed}
.log{font-family:var(–mono); font-size:12px; line-height:1.65; height:196px; overflow-y:auto;
border:1px solid var(–line); border-radius:8px; padding:10px 12px; background:#0B1E1E}
.log div{margin-bottom:3px; opacity:0; animation:fade .28s forwards}
@keyframes fade{to{opacity:1}}
.t-dim{color:var(–muted)} .t-act{color:var(–ink)} .t-ok{color:var(–graph)}
.t-warn{color:var(–loop)} .t-bad{color:var(–fail)}
.verdict{margin-top:11px; border-left:2px solid var(–loop); padding-left:12px;
font-size:13.5px; line-height:1.6; color:var(–muted); min-height:60px}
.verdict b{color:var(–ink)}
.prims{border-top:1px solid var(–line); background:var(–panel); padding:16px 22px 18px}
.prims h3{margin:0 0 4px; font-family:var(–mono); font-size:10.5px; letter-spacing:.13em;
text-transform:uppercase; color:var(–muted); font-weight:500}
.prims .sub{margin:0 0 12px; font-size:13.5px; line-height:1.55; color:var(–muted); max-width:68ch}
.prims .sub b{color:var(–ink)}
.grid{display:grid; grid-template-columns:repeat(6,1fr); gap:8px; margin-bottom:12px}
.pc{appearance:none; cursor:pointer; border:1px solid var(–line); background:#0E2525; color:var(–muted);
border-radius:9px; padding:11px 8px; text-align:center; transition:border-color .18s, color .18s, background .18s}
.pc:hover{color:var(–ink); border-color:var(–loop)}
.pc:focus-visible{outline:2px solid var(–loop); outline-offset:2px}
.pc.on{color:var(–loop); border-color:var(–loop); background:#1d1a0c}
.pc .n{display:block; font-family:var(–mono); font-size:9px; letter-spacing:.1em; opacity:.65; margin-bottom:4px}
.pc .t{display:block; font-size:12.5px; line-height:1.3; font-weight:600}
.pd{border:1px solid var(–line); border-radius:9px; background:#0E2525; padding:13px 15px; min-height:96px}
.pd h4{margin:0 0 6px; font-size:15px; letter-spacing:-.01em; font-weight:650}
.pd p{margin:0 0 8px; font-size:13.5px; line-height:1.6; color:var(–muted)}
.pd p:last-child{margin-bottom:0}
.pd .impl{font-family:var(–mono); font-size:11.5px; color:var(–loop); letter-spacing:.02em}
.ft{padding:11px 22px; border-top:1px solid var(–line); background:#0E2525;
display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap;
font-family:var(–mono); font-size:10.5px; letter-spacing:.05em; color:var(–muted)}
.ft b{color:var(–graph); font-weight:600}
@media (max-width:760px){
.hd{padding:16px 15px 13px}
.band{grid-template-columns:1fr}
.side + .side{border-left:0; border-top:1px solid var(–line)}
.side{padding:15px}
.prims{padding:15px}
.grid{grid-template-columns:repeat(3,1fr)}
.ft{padding:10px 15px}
.log{height:170px}
}
@media (prefers-reduced-motion:reduce){ .log div{animation:none; opacity:1} }
Interactive explainer · 03 · Layer 2
What loop engineering is
Loop engineering is designing the system that prompts the agent, instead of prompting it yourself. The prompt does not disappear — it becomes a component inside a cycle that decides when to fire, what counts as passing, and when to stop trying. The skill was named in September 2025 and became the dominant developer topic in June 2026.
The cycle
Four phases and a memory. Press run to watch one complete, then force a failure to see why the guardrails exist.
0
ITERATION
trigger
cron / event / goal
act
tools + edits
verify
tests / 2nd model
decide
retry or exit
Run the loop
Force a stall
Reset
Run log
Everything below happens with nobody watching. That is the whole point, and the whole risk.
Idle. Press “Run the loop”.
A loop needs a stopping rule it can evaluate without you. Without one it does not fail loudly — it just keeps spending.
What you actually build
A working loop needs five primitives plus a place to remember things. Both Claude Code and the Codex app now ship all six. Tap one.
01Automations
02Worktrees
03Skills
04Connectors
05Sub-agents
06State
Layer 2 of 3 · illustrative simulation, not measured
Built by Marktechpost
(function(){
var PRIMS = {
auto:{t:”Automations — the heartbeat”,
b:”A schedule or event that does discovery and triage by itself. This is what makes a loop an actual loop rather than one run you did once. Findings come to you; you stop going around checking.”,
i:”/loop re-runs on a cadence u00b7 /goal runs until a written condition is true”},
wt:{t:”Worktrees — isolation”,
b:”A separate working directory on its own branch, sharing the same repository history. Two agents editing the same file is the same problem as two engineers committing to the same lines without talking.”,
i:”git worktree u00b7 –worktree u00b7 isolation: worktree on a subagent”},
sk:{t:”Skills — written-down intent”,
b:”Project knowledge captured once instead of re-explained every session. Without it the loop re-derives your conventions from zero on every cycle; with it, context compounds.”,
i:”a folder with SKILL.md, plus optional scripts and references”},
mcp:{t:”Connectors — reach”,
b:”A loop that can only see the filesystem is a small loop. Connectors let it read the issue tracker, query a database, hit a staging API, post to a channel. This is the difference between an agent that says what it would fix and a loop that opens the PR.”,
i:”built on MCP u00b7 the same connector usually works across tools”},
sub:{t:”Sub-agents — maker and checker”,
b:”Split the agent that writes from the agent that checks. The model that produced the code grades its own work far too generously. A second agent, often on a different model, catches what the first one talked itself into.”,
i:”a fresh model decides whether the loop is done, not the one that did the work”},
st:{t:”State — memory outside the context”,
b:”A markdown file, a board, anything that lives outside the single conversation and records what is done and what is next. The model forgets everything between runs, so the memory has to sit on disk. The agent forgets; the repo does not.”,
i:”AGENTS.md, progress files, or an issue tracker via a connector”}
};
function el(id){ return document.getElementById(id); }
var phases = [“trigger”,”act”,”verify”,”decide”];
var running = false, timers = [];
function clearT(){ timers.forEach(clearTimeout); timers = []; }
function lit(p){
phases.forEach(function(x){
el(“n-“+x).classList.toggle(“on”, x===p);
el(“l-“+x).classList.toggle(“on”, x===p);
});
}
function clearLit(){ phases.forEach(function(x){ el(“n-“+x).classList.remove(“on”); el(“l-“+x).classList.remove(“on”); }); }
function line(cls, txt){
var d = document.createElement(“div”);
d.className = cls; d.textContent = txt;
var L = el(“log”); L.appendChild(d); L.scrollTop = L.scrollHeight; ping();
}
function reset(){
running = false; clearT();
el(“log”).innerHTML = ‘Idle. Press “Run the loop”.

