The Outer Loop
By the Syncfusion DevTutorials team · Last updated
Triage inbox, escalation and human review. Unclear and unsafe tickets must land in the inbox, not get "fixed". Then you read the evidence and give the verdict.
Triage Inbox, Escalation and Human Review
Every lesson so far made the loop do more by itself. This lesson is about what it must not do by itself. Some tickets need a person. And every result needs a person's verdict.
Watch a Loop Touch What It Should Not First
Ticket #12 says "Make the board feel faster". Nobody said what "faster" means. Ticket #13 asks to delete old data from production. The loop has a good checker and stop rules, but no inbox.
The loop "fixes" what it should not touch
Press Run it in the Loop Lab:
- On #12, the maker guesses. It removes every animation and says "done". The tests still pass, so the checker passes it too.
- On #13, the maker runs the delete on production.
In simple words: the loop could not tell "hard" from "not mine to decide". So it decided anyway.
TL;DR
- Some tickets must not be changed by a loop: unclear ones and unsafe ones.
- The loop escalates them: it sends them to a triage inbox, with the evidence, and changes nothing.
- The maker needs two things to escalate: somewhere to send the ticket (
escalate:) and the know-how to spot it (the triage skill). - In the inbox, you read the evidence and give a verdict: accept, reject or send back.
- Your review is the outer loop. It is what keeps comprehension debt and cognitive surrender away.
A Real-World Example
Think about a self-checkout at a supermarket.
Most of the time, the machine does the whole job. You scan, it weighs, it takes the money. But some items it will not decide on by itself. A bottle of wine, or an item whose weight looks wrong. For those, a light turns on and a staff member comes over.
The staff member does not just press "OK". They look: is this person old enough? Is that really the item on the screen? Then they decide.
Now map this to a loop:
- The scanning and paying is the inner loop: the machine, on its own.
- The light turning on is escalation.
- The waiting list of lit-up machines is the triage inbox.
- The staff member's look and decision is your verdict: the outer loop.
What You Will Learn
In this lesson, you will learn:
- Which tickets a loop must escalate.
- What the maker needs in order to escalate.
- What evidence an inbox item carries.
- How to give a verdict: accept, reject or send back.
- How the outer loop stops comprehension debt.
The inner loop works by itself and sends some tickets out with evidence. You read the evidence and give the verdict.
Which Tickets Must Be Escalated
A loop should escalate a ticket when it cannot safely decide on its own. Three common cases:
| Kind | Example | Why the loop must not decide |
|---|---|---|
| Unclear | "Make the board feel faster" | There is no check for "done". Any change is a guess. |
| Unsafe | "Delete old tickets from production" | A mistake cannot be undone. |
| Stuck | A flaky test, same failure 3 times | Trying again will not help. A person must look. |
The next box sorts a few tickets with the same rules a triage skill uses.
Fix it, or send it to the inbox?
function triage(ticket) {
if (/production|delete|drop|secret/i.test(ticket)) return "inbox: unsafe";
if (!/\d|shows|ignores|test|error|fails/i.test(ticket)) return "inbox: unclear";
return "fix";
}
const tickets = [
"Ticket count shows 1250 instead of 1,250",
"Make the board feel faster",
"Delete old tickets from the production database",
"Search ignores capital letters",
];
for (const t of tickets) console.log(triage(t).padEnd(15) + t);
Expected output:
fix Ticket count shows 1250 instead of 1,250
inbox: unclear Make the board feel faster
inbox: unsafe Delete old tickets from the production database
fix Search ignores capital letters
A real triage skill uses better rules than two regular expressions. But the idea is the same: decide before touching any code.
What the Maker Needs to Escalate
Two things, and the loop needs both:
- Somewhere to send it. In the loop file:
escalate: triage-inbox. - The know-how to spot it. The triage skill from Lesson 6 tells the maker which tickets are unclear or unsafe.
With only the inbox, the maker never knows when to use it. With only the skill, it knows, but has nowhere to send the ticket.
Escalation needs a target and the know-how
const setups = [
{ name: "neither", target: false, skill: false },
{ name: "target only", target: true, skill: false },
{ name: "skill only", target: false, skill: true },
{ name: "both", target: true, skill: true },
];
for (const s of setups) {
const result = s.target && s.skill ? "sent to the inbox, nothing changed" : "changed by the maker";
console.log(s.name.padEnd(12) + "#13 -> " + result);
}
Expected output:
neither #13 -> changed by the maker
target only #13 -> changed by the maker
skill only #13 -> changed by the maker
both #13 -> sent to the inbox, nothing changed
In Lesson 9 you also blocked #13 with allowed_commands. That is a second wall. A good loop has both: the skill sends risky tickets to the inbox, and the blast radius blocks anything that slips past it.
Lab Exercise 1: Send Unclear and Unsafe Tickets to the Inbox
Send unclear and unsafe tickets to the inbox
Open the loop file and:
- Add
skill: triageundermaker:, with two spaces in front. - Add
escalate: triage-inboxat the top level.
Press Check my loop. On all 3 versions, #12 and #13 must land in the inbox with their evidence, neither may be changed, and the other tickets must still be fixed.
Evidence: What an Inbox Item Carries
An inbox item is only useful if it carries evidence: the facts you need to decide. A good inbox item has:
- the ticket and its text;
- the diff: what the loop changed, if anything;
- the test output;
- the checker's notes;
- for a blocked action, the command that was blocked.
"The loop says it is fine" is not evidence. The diff and the test output are.
An inbox item with evidence, and one without
const items = [
{ ticket: "#12", says: "The loop says it is fine." },
{ ticket: "#13", says: "blocked command: pnpm tsx scripts/cleanup.ts --prod", evidence: ["no backup step in the ticket"] },
];
for (const it of items) {
const usable = Array.isArray(it.evidence) && it.evidence.length > 0;
console.log(it.ticket + ": " + (usable ? "can be decided: " + it.evidence.join("; ") : "cannot be decided yet: no evidence"));
}
Expected output:
#12: cannot be decided yet: no evidence
#13: can be decided: no backup step in the ticket
An opinion is not evidence. The item for #12 needs a diff, a test result or a checker note before you can decide.
When you tick evidence in the lab, pick the line that decides the verdict. For a fix you accept, that is usually the test result plus the diff. For a fix you reject, it is the line that shows what is wrong.
Your Verdict: Accept, Reject or Send Back
For each item, you choose one of three verdicts:
| Verdict | Use it when | What happens next |
|---|---|---|
| Accept | The evidence shows the change is right | The fix is merged |
| Reject | The change is wrong, or must never be made | The change is dropped |
| Send back | The ticket needs more information or a person's fix | It goes back to its owner with a note |
Green Tests Are Not the Whole Story
Here is the trap. A fix can pass every test and still be wrong. The classic case: the warnings are hidden, not fixed.
Tests pass, but the diff hides the problem
const diff = [
"+ // @ts-ignore",
"+ const data: any = await res.json();",
];
const testsPass = true;
const hidesProblems = diff.some((line) => /@ts-ignore|pragma warning disable|eslint-disable/.test(line));
console.log("tests pass: " + testsPass);
console.log("hides problems: " + hidesProblems);
console.log("verdict: " + (testsPass && !hidesProblems ? "accept" : "reject"));
Expected output:
tests pass: true
hides problems: true
verdict: reject
This is why you read the diff. The tests said yes. The diff said no.
Accepting everything because the loop was right last week is cognitive surrender. It feels safe, and it is how hidden problems reach your users. Every verdict needs a reason you can point to.
Lab Exercise 2: You Give the Verdict
Five items wait in your inbox. Each one has three evidence lines.
You give the verdict
For each item:
- Tick the evidence line (or lines) that decides it.
- Press accept, reject or send back.
Then press Check my verdicts. You need at least 4 of 5 right, and every verdict must cite evidence.
The Outer Loop and Comprehension Debt
Your review is the outer loop. The inner loop runs fast and often. The outer loop runs at the speed of a person reading.
That is not a weakness. It is the point. Each item you review is code you now understand. Each item you accept without reading adds to comprehension debt: code in your project that nobody has read.
Comprehension debt grows with every unread change
const weeks = [
{ merged: 20, readByAPerson: 20 },
{ merged: 30, readByAPerson: 18 },
{ merged: 40, readByAPerson: 15 },
];
let debt = 0;
weeks.forEach((w, i) => {
debt += w.merged - w.readByAPerson;
console.log("week " + (i + 1) + ": " + debt + " changes nobody has read");
});
Expected output:
week 1: 0 changes nobody has read
week 2: 12 changes nobody has read
week 3: 37 changes nobody has read
If this number grows, slow the loop down, make it pick smaller tickets, or add reviewers. Do not stop reading.
Feedback Beyond the Code
Some writers on loop engineering describe two more outer loops: feedback from developers (does this change make sense to the team?) and feedback from users (did the change help anyone?). The inner loop cannot see either one. Both come from people, and both feed the next tickets into the backlog.
When a Bad Change Gets Through
Review catches most problems, not all. When a loop's change reaches main and breaks something:
- Pause the loop, so it does not build on the broken change.
- Revert the pull request. One ticket per pull request makes this one click.
- Re-open the ticket, with a note about what broke.
- Add a check that would have caught it: a test, a rubric line or a linter rule.
- Restart on 2 or 3 small tickets, and read the diffs.
Step 4 is what makes the loop better over time. Every escape should leave behind a new check.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No inbox | "The loop can handle every ticket." | Unclear and unsafe tickets go to a person. | The loop guesses, or damages production. |
| An inbox with no skill | "It has somewhere to send them." | The maker also needs to know when. | Nothing is ever escalated. |
| Trusting green tests | "Tests pass, so accept." | Read the diff. Hidden warnings pass tests. | Problems are hidden, not fixed. |
| Blind accept | "It is always right." | Every verdict cites evidence. | Comprehension debt and cognitive surrender. |
Exercises
Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.
What to Remember
- Unclear, unsafe and stuck tickets go to the triage inbox. The loop changes nothing.
- Escalation needs a target (
escalate:) and the know-how (the triage skill). - Every inbox item carries evidence: diff, test output, checker notes.
- You give the verdict: accept, reject or send back, with a reason.
- Green tests are not the whole story. Read the diff.
- The loop produces evidence. You produce the verdict.
Further Reading
- Loop Engineering Emerges, on ADTmag: Andrew Ng's three loops, and people moving out of the QA role.
- Loop Engineering, by Addy Osmani: comprehension debt and cognitive surrender.
- Loop Engineering and Autonomous Agent Systems, by Rick Hightower: where people stay in the loop.
Next: Capstone, where you build a full loop pack for your own project and export it.