cd /news/ai-agents/a-while-loop-is-not-an-agent-runtime… · home topics ai-agents article
[ARTICLE · art-86822] src=pub.towardsai.net ↗ pub= topic=ai-agents verified=true sentiment=· neutral

A while Loop Is Not an Agent Runtime. 8 Parts You’re Missing.

A new diagnostic framework from an unnamed senior engineer distinguishes five layers of agent systems—Context, Prompt, Loop, Graph, and Skills—to help developers triage failures, arguing that a while loop is not an agent runtime. The framework clarifies that prompt engineering is a subset of context engineering, and that misidentifying the layer of a problem leads to wasted effort, such as rewording prompts to fix permission issues.

read15 min views1 publishedAug 4, 2026

Here’s a question that has stalled more than one agent project:

“What exactlyiscontext engineering? How is it different from prompt engineering? Do memory and skills count as prompt engineering?”

It feels like a beginner question. It isn’t. I’ve watched senior engineers who have shipped real systems get stuck on it, and the reason is always the same: it’s not a knowledge gap, it’s a missing axis.

The more mechanisms you learn — MCP, memory, skills, hooks, workflows, subagents — the worse it gets. Every one of them looks like “stuff text into the context window.” So they blur into one undifferentiated pile, and when something breaks you have no idea which pile to dig in.

The fix is a single diagnostic. For any mechanism, ask: what does it actually move?

Five layers. Nested, innermost to outermost. Not an official standard — a lens for figuring out which layer you’re standing in when something goes wrong.

The payoff isn’t taxonomy. It’s triage. When your agent misbehaves, the first question is not “how do I fix this,” it’s “which layer is this.” Get that wrong and you’ll spend a week rewording a prompt to fix a problem that lives in your permission model.

Back then, an agent was a model plus a prompt. One layer. Nothing to confuse.

A real agent system today has memory injected on demand, tool results flowing back into context, subagents holding their own isolated windows, timers firing repeatedly, parallel nodes cross-verifying each other, and task handles that survive a process restart.

Two things changed:

Input became dynamic. Once what goes in is computed at runtime, optimizing the wording is a subset of designing the input. Prompt engineering became a component of context engineering rather than the whole job.

Execution became continuous. Once the agent runs more than once, a single turn is just the kernel. Loop and Graph wrap around it — one governing time, one governing space.

The question: how do I word this so the model understands and complies?

It operates on text inside an already-assembled context window. Instruction clarity, role setting, few-shot examples, chain-of-thought, output format constraints — all of it lives on the plane of expression.

Where specific things land:

Notice the pattern: the same artifact splits across layers. A skill’s body is Prompt. Its description — which decides whether the skill loads at all — is Context. That split is exactly why “is a skill prompt engineering?” feels unanswerable. It’s half of one.

The boundary: perfect wording on wrong content is still wrong. A model drowning in irrelevant files will go off the rails no matter how elegant the instruction. Prompt optimizes output quality given the content. It is not responsible for the content.

The question: what enters the window, what gets cut, when does it arrive, what must never appear?

Four verbs: select, compress, time, isolate.

Why Prompt and Context blur: both look like “writing text.” One test separates them cleanly — are you improving the wording of text that’s already in, or deciding what gets in?

The failure signature is distinctive. Context problems show up as hallucination, forgetting, and token burn. If your agent confidently invents a function that doesn’t exist, stop rewriting the prompt. Go look at what you actually sent it.

The question: what makes the agent run another turn? What convinces it that it’s done?

This is the time axis. A single agentic loop — observe, reason, call tool, verify, continue or finish — is the kernel. Loop engineering governs what happens after that turn.

Four kinds of “press continue”:

Then the machinery that keeps loops honest:

The division of labor with Graph: Loop answers when. Graph answers how it’s organized. In a single loop-until-dry, the loop is Loop; the multiple finders running in parallel inside it are Graph.

The question: how does work split into nodes? What are the edges? How do things fan out, route, verify, and converge?

This is the space axis. Four definitions carry the whole layer:

Where things land:

Why more agents doesn’t mean a better system: a graph only pays off when node boundaries, data contracts, and verification gates are all clear. Calling a model for every trivial step means paying inference prices for work that code does better. And without structured output, every downstream node is stuck guessing at upstream prose.

The question: how does everything above run safely, durably, observably, and with a human able to intervene?

The system axis. Seven concerns: execution, orchestration, state, evaluation, safety, observability, presentation.

Harness versus Graph, precisely: the graph is the blueprint — which nodes, which data flows. The harness is the factory building — power, safety procedures, the control room, the emergency plan. The same graph runs in a toy script or on a full runtime. Only one of those is operable.

Read it as outer layers decide, inner layers execute.

The harness decides whether this run is allowed at all and what budget it gets. The graph decides which nodes run. Each node’s interior is a loop. Each turn of that loop builds a context. Inside that context, a prompt gets assembled and handed to the model. The output comes back, a verifier (Loop) decides whether to go again, and the final result lands in state storage and the observability system (Harness).

Nesting isn’t the only way layers connect. Two pairs are orthogonal, and mixing them up causes real architectural mistakes.

Loop × Graph is time × space. They are not alternatives. A graph can be triggered by an external timer; a node inside that graph can run its own convergence loop. Both are true simultaneously. The composition looks like: timer fires → graph executes → each round verifies goal-style.

Context × Graph is subtler and more expensive to get wrong. Every node has its own context — isolation is a Context-layer action. What travels along an edge should be structured data, not prose. This is precisely why a giant shared state blob is an anti-pattern: it creates implicit dependencies at the Context layer that your graph topology doesn’t show.

Lay the five layers on a timeline and a pattern appears.

Every shift hands control that used to live in a human’s attention over to the system. In 2023 a person wrote the context by hand. Then retrieval decided it. Then a goal decided when to stop. Then a topology decided what ran in parallel. Now a runtime decides whether any of it is allowed to run at all.

If you’ve ever had to explain why agent runtimes suddenly matter, this is the argument. Not “agents got better.” The control surface moved outward, and the outermost layer had nobody home.

The way to actually internalize this isn’t memorizing five labels — it’s practicing placement until it’s automatic.

Two rows in that table are worth staring at, because they’re the same word landing in different layers:

Both are true about the same subagent. Which layer you’re in depends on which property you’re currently controlling. Once that stops feeling contradictory, the model has clicked.

Abstractions stay slippery until they touch something concrete. Here’s a real one: every day, scan a knowledge base for dead links, missing frontmatter, and structural anomalies. Report only what’s new.

Harness: a daily trigger; read-only permissions with human confirmation required for any bulk modification; a token budget; every run logged and persisted, with a durable task handle so an interrupted run resumes tomorrow; an approval surface for the human.

Graph: three directories scanned in parallel (independent subagent nodes, pure fan-out) → a reduce step that deduplicates in plain code, no model → each candidate dead link independently re-verified (a gate whose verifier tries to disprove “this is dead”) → one synthesis node writing the new-issues report. Every node has a schema contract.

Loop: inside each scan node, the agentic loop runs observe → reason → tool → verify. Completion is defined as “the directory is fully scanned and the result passes schema validation” — not “the agent said it’s done.” maxTurns catches the pathological case.

Context: each scan node receives only the directory path, the scan rules, and the previous run’s results (so it can tell new from old). It explicitly does not receive the entire knowledge base — that’s a hundred thousand lines of context explosion. Subagents stay isolated from each other.

Prompt: the actual wording of each node’s instruction. “Report only new issues, never repeat known ones. Before any bulk operation, list the blast radius and wait.”

Note also where the model appears: deduplication is code, re-verification is a verifier, and only “judge whether this link is semantically dead” and “write the report” need inference at all. That restraint is the Graph layer earning its keep.

Everything above describes design concerns. A runtime is the thing that assembles them and actually drives execution:

An agent runtime is a control plane that instantiates a task into a running agent, drives the model-and-tool interaction loop, manages state and permissions, enforces verification and stopping protocols, and supports observation, , resume, and human intervention.

Several conditions have to hold simultaneously:

The essence isn’t making the model smarter. It’s this: turning a single model call into a constrained, stateful, verifiable, resumable, operable execution process.

It’s usually not a discrete product. It might be a process, a set of services, an SDK, a queue consumer, or something embedded inside a framework. Agent runtime is an engineering responsibility and a runtime abstraction, not a brand name.

This is a model call:

const response = await client.messages.create({  model: "some-model",  messages: [{ role: "user", content: "Check this project" }],});

You get text back. Nothing here is responsible for parsing and executing tool calls, checking tool permissions, feeding results back into history, deciding whether to run another turn, saving task state, handling timeouts and cancellation and retries, verifying the deliverable, resuming after a restart, or recording a complete trace.

The relationship worth memorizing:

A while loop implements a minimal agentic loop. It does not become a runtime by growing.

Not an “agent name” — a task instance:

type TaskInstance = {  taskId: string;  input: unknown;  status:    | "queued"    | "running"    | "input_required"    | "d"    | "completed"    | "failed"    | "cancelled";  agent: {    model: string;    systemPrompt: string;    tools: string[];    contextPolicy: string;  };  budget: {    maxTurns: number;    maxTokens: number;    deadlineAt: number;  };  state: {    messages: unknown[];    toolResults: unknown[];    checkpoints: unknown[];    attempts: number;  };};

Five categories, each earning its place: the input says what to accomplish; the agent config says which model, prompt, tools and context policy; the status says where it is in its lifecycle; the budget says how far it may go; the checkpoints say where to resume.

That’s the whole meaning of the word runtime here. It runs task objects that have a lifecycle, not isolated strings.

1. Trigger, scheduler, instantiator. The trigger answers “why is this starting now” — a user message, a timer, a webhook, a queue event, an upstream node. The scheduler answers “should it actually run right now”: is a worker free, are we at the concurrency ceiling, does this need to queue, what priority, and is an identical idempotent task already in flight? Cron and loops solve triggering. Triggering is not a runtime.

Then instantiation turns a definition into one concrete run: pick the model, load system prompt and skills, register permitted tools, create an isolated context, bind user and project and permissions and budget, mint a task ID. This is why one agent definition can be running fifty times at once — each run is its own instance with its own state.

2. Model adapter and context builder. Don’t scatter SDK details across every node:

type ModelAdapter = {  complete(input: {    messages: Message[];    tools: ToolDefinition[];  }): Promise<ModelResponse>;};

The adapter handles request and response format. The context builder decides which messages, memories, and tool results go into this turn. Keeping them separate means provider differences and context strategy can change independently.

3. Tool registry and executor. Every tool call passes through one gate. Does this tool exist? Do the arguments match the schema? Is this agent permitted to call it? Does the current user have rights? Does this need human approval? Did it time out? Is the failure retryable — and will retrying cause a duplicate write?

A tool executor is not a function call with extra steps.

4. The agentic loop driver. The execution kernel. Here’s a minimal teaching implementation — not anyone’s production source, just enough to show how model, tools, state, and stopping conditions wire together:

type Message = {  role: "user" | "assistant" | "tool";  content: unknown;}; type ToolCall = {  id: string;  name: string;  input: unknown;}; type ModelResponse = {  stopReason: "end_turn" | "tool_use";  text?: string;  toolCalls?: ToolCall[];}; type RuntimeContext = {  messages: Message[];  turn: number;  maxTurns: number;}; type RuntimeDeps = {  model: {    complete(context: RuntimeContext): Promise<ModelResponse>;  };  tools: {    has(name: string): boolean;    execute(name: string, input: unknown): Promise<unknown>;  };  check(result: unknown): Promise<{ met: boolean; gaps: string[] }>;  save(context: RuntimeContext): Promise<void>;}; async function runAgentTask(  input: string,  deps: RuntimeDeps,): Promise<{ status: "completed" | "failed"; output?: string; gaps?: string[] }> {  const context: RuntimeContext = {    messages: [{ role: "user", content: input }],    turn: 0,    maxTurns: 8,  };   while (context.turn < context.maxTurns) {    context.turn += 1;    await deps.save(context);     const response = await deps.model.complete(context);     if (response.stopReason === "end_turn") {      const evaluation = await deps.check(response.text ?? "");       if (evaluation.met) {        return { status: "completed", output: response.text };      }       context.messages.push({        role: "user",        content: `Verification failed. Fix these gaps: ${evaluation.gaps.join("; ")}`,      });      continue;    }     for (const call of response.toolCalls ?? []) {      if (!deps.tools.has(call.name)) {        return { status: "failed", gaps: [`No such tool: ${call.name}`] };      }       const result = await deps.tools.execute(call.name, call.input);      context.messages.push({        role: "tool",        content: { callId: call.id, result },      });    }  }   return {    status: "failed",    gaps: [`Hit max turns: ${context.maxTurns}`],  };}

The genuinely runtime-shaped parts of that: task state in context, the turn counter and ceiling, the model call, tool lookup and execution, tool results written back, the checkpoint save, independent verification, and three distinct exits — completed, continue, failed.

Look at what happens when verification fails. It doesn’t retry blindly and it doesn’t give up. It writes the gap back into the conversation as the next instruction. That single move is the difference between a loop that spins and a loop that converges.

A production version still needs cancellation signals, timeouts, token budgets, permission checks, structured logging, retries, idempotency, and crash recovery.

5. State, checkpoints, recovery. Keep state only in memory and the task dies with the process. A resumable runtime persists current status, recent messages or their summary, executed tool calls and results, the current graph node, turn count and budget consumed, retry count, pending human requests, and a checkpoint version.

Three things that get confused constantly:

6. Policy, safety, resources. The runtime is the control boundary between an agent and the real world: which tools, which directories readable and writable, network access or not, approval required or not, ceilings on tokens and turns and time and cost, per-user and global concurrency, background execution, and what happens to an in-flight tool when a task is cancelled.

A prompt can tell a model not to delete files. That’s advice. Runtime policy is the constraint. If your only protection against destructive action is a sentence in a system prompt, you don’t have a safety boundary — you have a preference.

7. Verification, terminal states, error handling. Never take “done” at face value. Drive the task to an explicit terminal state, and protect each one: a completed task must not be flipped back to running by a late worker; a cancelled task must not be resurrected by a stale retry.

And distinguish the failure modes, because they need different recovery: the model refused, a tool failed, verification didn’t pass, budget exhausted, timeout, user cancelled, the runtime itself crashed. “Call the model again” is not a recovery strategy for six of those seven.

8. Observability and human intervention. Without it, a runtime is a black-box loop. Record task and run and node and turn IDs, per-call latency and tokens and cost, tool names and argument summaries and durations and outcomes, every status transition, verifier feedback, retry and cancellation reasons, and the input/output relationships between graph nodes.

When a decision genuinely needs a human, the runtime moves the task to input_required and persists that state — it doesn’t keep guessing. Discovering that a job wants to delete thirty-seven files should the task, show the blast radius, and wait.

That’s what human-in-the-loop actually means at the runtime level. Not a line in a prompt asking the model to check with the user. A durable, resumable task state.

“A runtime is a while loop.” A while expresses repetition. It says nothing about lifecycle, state, permissions, recovery, verification, or observability. The loop is one organ, not the body.

“A runtime is [framework name].” Graph frameworks and agent SDKs implement parts of a runtime. Whether you get persistence, queuing, permissions, observability, and recovery depends entirely on what you built on top.

“A runtime is prompts plus tools.” Prompts shape behavior, tools provide capability. The runtime decides when capability is invoked, whether it’s permitted, how results are written back, and what happens on failure.

“A task protocol is a runtime.” Task protocols give you handles, status queries, and input-waiting interfaces. Valuable — but the model loop, tool execution, permissions, queuing, and verification still have to live somewhere.

“A state database gives you recovery.” Storage is necessary, not sufficient. Real resumability also needs checkpoint semantics, idempotent tools, worker leases, versioning, unfinished-step identification, and duplicate-execution protection.

If you remember one sentence from all of this:

The agentic loop is the heart. Graph is the skeleton. Context is the sensory system. Prompt is the language interface. Harness is the boundary and the infrastructure. The runtime is what assembles the organs so a task actually lives, runs, and eventually ends.

And the practical version, the one that saves you a week: when your agent does something stupid, don’t reach for the prompt first. Ask which layer moved. Nine times out of ten it’s a layer you weren’t looking at.

If this gave you a cleaner mental model, a clap 👏 (or fifty) helps other developers find it. And tell me in the comments which layer bites your stack hardest — my money is on Harness. I read every one.

A while Loop Is Not an Agent Runtime. 8 Parts You’re Missing. was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

── more in #ai-agents 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-while-loop-is-not-…] indexed:0 read:15min 2026-08-04 ·