Everything going on in AI - updated daily from 500+ sources
A while Loop Is Not an Agent Runtime. 8 Parts You’re Missing.
Prompt, Context, Loop, Graph, Harness — and what assembles them into a runtime. Here’s a question that has stalled more than one agent project: “What exactly is context 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. One question sorts everything The fix is a single diagnostic. For any mechanism, ask: what does it actually move? Moves the wording of text → Prompt Moves which content reaches the model’s eyes → Context Moves when it runs and when it stops → Loop Moves how work units connect → Graph Moves the runtime environment and operational guarantees → Harness 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. Why this didn’t matter in 2023 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. Layer 1: Prompt — how you say it 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: A SKILL.md's instruction body — how the steps are worded and organized. It does not control when the skill gets loaded. That’s Context. A CLAUDE.md's rule wording — how project rules are phrased so a model actually follows them. Not how much gets injected or when. Few-shot, roleplay, CoT — the examples themselves are content (Context); choosing how to phrase them is Prompt. “Return JSON please” — a textual constraint. Enforced schema validation is a runtime behavior, which makes it Graph. 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. Layer 2: Context — what the model knows The question: what enters the window, what gets cut, when does it arrive, what must never appear? Four verbs: select, compress, time, isolate. Memory systems — which history reaches this turn. Instruction memory, long-term, working, summary. Priority-tiered injection, recursive includes, threshold-triggered summarization. RAG / retrieval — pulling relevant fragments from an external store. Vector search, top-K, query rewriting. CLAUDE.md / imports — when project rules inject and how much. Startup injection, modular imports, dual-track loading. Skill trigger mechanics — the description is a thin entry point . It decides which skill loads instead of loading everything at once. Compaction — long conversations summarized to reclaim window space. Tool result truncation — large outputs (files, logs, command results) trimmed or summarized before they enter. Subagent isolation — a child inherits only what the parent chose to pass. Parallel tasks don’t contaminate each other. The window size itself — a hard physical constraint on everything above. 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. Layer 3: Loop — when it runs again, and when it stops 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”: Goal-based — the agent presses it itself, until verifiable evidence appears or it hits maxTurns. Time-based — a timer presses it. Observe the outside world first, then act. Stops when something external is actually done. Turn-based — a human presses it. The original loop, still the most common one. Proactive / scheduled — an event or cron presses it. Nobody’s watching. Then the machinery that keeps loops honest: Verifiers — an objective check independent of the executor: a test command, a script, a rules engine, a second model. The whole point is to not trust the agent’s own claim that it finished. Safety valves — maxTurns, token budgets. Hitting the ceiling is not success; report the gap honestly. Loop-until-dry — keep going until N consecutive rounds surface nothing new. Deduplicate against everything seen so far, not just against confirmed results, or it never converges. Idempotency — running twice must not create the resource twice or send the message twice. 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. Layer 4: Graph — how the nodes connect 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: A node is a work unit with a clean boundary. An edge is a data dependency , not a time ordering. This one trips people up constantly. Topology is fan-out, fan-in, diamond, routing. A verification gate is a verifier sitting on an edge. Where things land: Subagent — one node. Its own context, own system prompt, structured return. Agent teams — the collaboration layer between nodes. Direct messaging, shared tasks, approval chains, veto power. Workflow primitives — the orchestration vocabulary. parallel is a barrier; pipeline has none; phase groups for display. Worktrees — write isolation for nodes that modify files concurrently. Only pay this cost when you actually need parallel writes. Node contracts — machine-checkable input/output schemas. Routing — a classifier node picking the downstream path. Model judges, code controls the edge. Verification gates — independent verifiers that try to refute the upstream result, not confirm it. Graph frameworks — state graphs, conditional edges, checkpoints, interrupts. These are implementations of a graph runtime , not graph engineering itself. 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. Layer 5: Harness — how the system is operated 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. MCP servers and clients — standardized access to external capability. Execution. Task protocols — long-running work as a first-class object: task IDs, progress, approval, resumption, durable storage. State. App / UI resources — the spatial form of run state, with real security boundaries. Presentation. Hook systems — lifecycle interception. Block a dangerous command before it runs; audit on stop. Safety + observability. Plugins — packaged distribution of skills, agents, hooks, servers under one manifest. Execution + distribution. Sandboxes and permission modes — the execution boundary for commands and tools. Safety. Session management — save and restore. State. Background session views — attach, inspect, resume, clean up. Observability. Schedulers and cron — the carrier for proactive loops. Orchestration. Evals — completion evidence, output verification, rejecting wrong results. Evaluation — and the biggest gap in most stacks today. 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. How one request falls through all five 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). The two relationships that aren’t nesting 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. The historical throughline: control moving outward 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 placement drill 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: A subagent’s isolated context → Context. You’re deciding what it can see. A subagent as a node → Graph. You’re deciding how work is organized. 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. One task, all five layers 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.” Remove any one layer and watch it break No Context isolation → scan nodes contaminate each other’s findings. No Graph parallelism → three directories run serially, three times the wall clock. No Loop completion definition → the agent scans half the tree and feels finished. No Harness permission gate → it helpfully deletes the dead links it found. No Prompt discipline → it re-reports the same forty issues every single day. 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. So what is an agent runtime, exactly? 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, pause, resume, and human intervention. Several conditions have to hold simultaneously : It receives a task instance, not a string. With an ID, input, budget, permissions, run state, and a lifecycle. It drives model-tool interaction. The model emits a tool call; the runtime checks permission, executes, feeds the result back. It manages cross-turn state in durable storage, not process-local variables. It has control authority. Continue, pause, wait for input, retry, fail, complete. It carries operational responsibility. Logs, metrics, limits on tokens and time and concurrency, and recovery after a crash. 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. Why a bare API call isn’t one 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: LLM API — provides one inference. Agentic loop — describes how a model interacts with tools repeatedly. Agent runtime — makes that interaction run safely, durably, and controllably inside a real system. A while loop implements a minimal agentic loop. It does not become a runtime by growing. What a runtime actually runs Not an “agent name” — a task instance : type TaskInstance = { taskId: string; input: unknown; status: | "queued" | "running" | "input_required" | "paused" | "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. The eight parts 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 ; }; 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 ; }; tools: { has(name: string): boolean; execute(name: string, input: unknown): Promise ; }; check(result: unknown): Promise<{ met: boolean; gaps: string[] }>; save(context: RuntimeContext): Promise ; }; 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: Memory helps the model get useful information. It’s a Context mechanism. A checkpoint helps the runtime resume its execution position. It’s a state mechanism. A timer triggers repeated runs. It does not give you resumption for free. 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 pause 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. Five misconceptions worth killing “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. The line worth keeping 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.
Read Original Article →