Let's use an analogy to remember the knowledge system this article covers:
-
Harness is the operating system — the top-level container that manages all resources.
-
LLM is the CPU — it takes in instructions and produces results.
-
Agent Loop is the kernel's main scheduling loop — constantly picking up tasks, dispatching, waiting for I/O to return, then picking up the next one.
-
Two-Stage ReAct is the OS's dry-run mode — it runs a trial first to see the plan, and only after confirmation does it really execute.
-
The four tools are the basic operations of a file system — read, write, edit, bash, corresponding to the rwx permission model.
-
AGENTS.md / Skills / PlanMode form a three-level storage system — BIOS, hard disk, memory + disk persistence.
1. Agent Loop: The Kernel's Main Scheduling Loop
At its core, every Agent is just a while(!done).
while not terminated:
1\. Assemble context (system prompt + history + tool declarations + current observation)
2\. Hand the context to the LLM
3\. Parse the LLM's response:
3.1 Text segment → append to context history
3.2 If there are tool\_calls → execute each one, write results back to history, continue
3.3 No tool\_calls → terminate the loop and return the text
Just like the OS kernel's main scheduling loop:
Take a process from the ready queue → allocate a CPU time slice → wait for the I/O interrupt to return → schedule the next one.
The Agent Loop is:
Keep assembling context → hand it to the LLM → wait for the response → execute tools (I/O) → write results back to history → loop again.
It is the heart of the entire system.
To keep this loop running reliably, you need to think through three things:
-
Termination condition: no tool_call, hitting max_turns, user cancellation, or a verifier judging the task complete. Without an explicit termination condition, the loop will spin forever and burn tokens until you go broke.
-
Verification method: don't trust the model's claim that it's done — verify by running tests, checking diffs, and assertion scripts.
-
State persistence: context history, PLAN.md/TODO.md, tool-call logs. Context compression can silently swallow early information, so write it to disk.
Scenarios that suit an Agent Loop:
-
High frequency (run at least once a week); for one-off tasks there's no need to bother with an Agent.
-
Almost no human intervention required.
-
Failures can be re-run.
2. Two-Stage ReAct: Dry-Run First, Then Really Execute
If you just hand the tool declarations straight to the LLM, it will be very impulsive about calling tools instead of first thinking about what to do.
Two-Stage ReAct splits "thinking" and "doing" into two independent LLM calls (letting the LLM restrain its impulses), physically isolating them:
Stage 1 (no-tool prompt): give only the task + context, expose no tools at all
→ LLM outputs a plan / reasoning chain / assumptions (dry-run)
Stage 2 (with-tool prompt): stitch Stage 1's output back in, then expose the tool declarations
→ LLM calls read/bash/edit... according to the plan
This is like the OS's dry-run mode — apt-get install -s, kubectl apply --dry-run, make -n.
The first call is simulated execution: it touches no system state and only outputs what it intends to do;
The second is real execution, which modifies state.
Why does this layer still have engineering value even when models already have "native thinking" ability?
-
Intervention point: between Stage 1 and Stage 2 you can intercept — revise the plan, require human approval, run compliance audits. Native thinking is a black box; you can't intercept it.
-
Observability: Two-Stage makes the plan explicit as text, so it can be diffed, rolled back, and reviewed. Native thinking blocks are visible but hard to audit.
-
Multi-model compatibility: any program can dry-run; you don't need the program itself to support any special capability. You can swap in a cheap small model and still run, without depending on a large model with native thinking.
When to enable thinking (not every turn — it's expensive):
-
When a new task goal is first issued (dry-run the plan first).
-
When a tool returns an unexpected result (execution went wrong; re-dry-run to reconsider whether to switch approaches).
-
Before high-risk actions like deletion, pushing, payment, or writing to a production database (trial-run major operations first).
3. Tools: The Basic Operations of a File System
Give the Harness only four basic tools: read, write, edit, bash.
This exactly corresponds to the basic operations in a file system / permission model:
| Tool | File-system operation | Effect |
|---|---|---|
read | Read file (open + read) | No side effects; look only, don't touch |
write | Write file (overwrite, like > redirection) | Whole-file overwrite |
edit | Modify file content (like sed -i, precise patch) | Only changes the lines that need changing |
bash | Execute program (execute) | Run any command; one tool beats ten special-purpose tools |
Why have edit when you have write? write overwrites the whole file; once a file gets large, the token consumption is enormous — both slow and expensive. What's more, large models are extremely prone to truncation when generating long text, or to accidentally introducing new syntax errors. edit works like sed -i: it only touches the lines that need changing, so diffs are controllable, reviewable, and rollbackable.
Giving too many tools is like chmod 777 — opening up permissions recklessly: it doesn't make the model more capable, it makes it more likely to impulsively call tools it shouldn't, and the error surface gets bigger instead. It also tends to limit a smart model's initiative (a smart model could do a better operation itself, but once it sees you've provided a ready-made operation, it assumes based on your description that this one is more suitable and uses it — yet the model doesn't know how you implemented it, and it may not be the operation the model actually wanted). Four basic operations plus bash's all-purpose execution capability already cover the vast majority of tasks.
edit's uniqueness check (mandatory): if a fuzzy match hits more than one location, the tool must absolutely not blindly replace — just like sed globally replacing when a multi-line match has no line number specified would change lines that shouldn't be changed. It must raise an error and ask the LLM to provide more surrounding code to pinpoint the exact location. This is the safeguard against an Agent "accidentally editing a different similar fragment" while changing code.
def apply\_edit(text, old, new):
cnt = text.count(old)
if cnt == 0:
raise ToolError("old\_text not found")
if cnt > 1:
raise ToolError("ambiguous match (>1); provide more surrounding context")
return text.replace(old, new, 1)
4. Improving Harness Execution Efficiency: Concurrent for Read-Only, Serial for Writes
A model's single reasoning pass may emit multiple tool_calls. If the harness executes them all serially one by one, efficiency suffers — but the harness also shouldn't run them blindly in parallel. It needs a proper scheduling strategy: generally, if all the tools are read-only, they can run in parallel.
func dispatch(calls \[]ToolCall) {
var readonly, writable \[]ToolCall
for \_, c := range calls {
if isReadOnly(c) {
readonly = append(readonly, c)
} else {
writable = append(writable, c)
}
}
if len(writable) == 0 {
// read-only batch: concurrent goroutines
fanOut(readonly)
} else {
// write-involving batch: strictly serial execution
for \_, c := range calls {
exec(c)
}
}
}
At extremely low complexity, this strategy guarantees both performance and correctness in the vast majority of scenarios.
5. Three-Level Memory: AGENTS.md / Skills / PlanMode
5.1 AGENTS.md
This solves the question of "what the current project looks like." You can compare it to BIOS info on an operating system — loaded at startup, telling the system what hardware config it has, what the directory structure is, and what restricted zones exist. When an Agent starts, it reads this once and knows which project it's working in.
Note: Claude Code still stubbornly reads CLAUDE.md, which serves the same purpose as AGENTS.md.
5.2 Skills
This solves the question of "how a specific task should be done." Compare it to binary programs on a hard disk loaded on demand — only when the corresponding scenario is hit are they loaded into context (the CPU). AGENTS.md tells the machine what its config is; Skills tell it which "program" to invoke for this task.
5.3 PlanMode
-
PLAN.md: the macro routing table, the strategic direction. It keeps an Agent from drifting off course across long tasks spanning dozens of turns.
-
TODO.md: the scheduling queue — check off each item as it's completed.
Context compression is like memory reclamation losing data — early dialogue history compacted away is gone. So you have to periodically flush to disk: write progress into PLAN.md/TODO.md to prevent loss on "power failure" (compression). When the Agent wakes up and re-reads the disk, it knows what to continue with.
ThinkingPhase (slow thinking) is pipeline-level error correction on the CPU: even if TODO.md already contains new tasks, without a per-turn slow-thinking constraint the model may still take shortcuts when choosing the concrete implementation path — skipping boundary-condition validation, picking the first runnable solution without weighing tradeoffs. Slow thinking is immediate correction at every step; PlanMode is the state safety net on disk. The two complement each other.
6. Tying It All Together in One Sentence
Harness (operating system)
├── LLM (CPU computing core)
│ └── Agent Loop (kernel's main scheduling loop)
│ └── Two-Stage ReAct (dry-run simulated execution → real execution)
├── Tool set (basic file-system operations: read/write/edit/bash)
└── Three-level memory (BIOS / hard disk / memory + disk persistence)
The Agent that runs reliably isn't the one with the strongest model — it's the one whose Loop has a termination condition, whose tools have boundary constraints, whose read/write operations have parallel/serial optimization, and whose long tasks have disk-persistence as a safety net.
Appendix: Directly Reusable Loop Pseudocode
def agent\_loop(task, max\_turns=30):
ctx = bootstrap(task) # AGENTS.md + Skill discovery + history
for turn in range(max\_turns):
if need\_plan(ctx): # PlanMode intervention
ctx = two\_stage\_plan(ctx)
resp = llm(ctx) # Stage 2 (with tools)
ctx.append(resp.text)
if not resp.tool\_calls:
return resp.text # termination condition
batch = group\_by\_readonly(resp.tool\_calls)
for r in concurrent\_run(batch.readonly):
ctx.append(r.result)
for w in sequential\_run(batch.writable):
if w.name == "edit" and match\_count(w) != 1:
ctx.append(edit\_ambiguity\_error(w))
break
ctx.append(w.result)
raise Timeout("max\_turns reached")