How Coding Agents Are Built: Architecture and Engineering Practices
This article explains how coding agents are actually built, going beyond the model itself. It covers the shift from code completion to autonomous agents, a five-layer reference architecture, the core agent loop (convergence signals, error feedback, structured tool calls, termination guards), context engineering (layered context assembly, on-demand retrieval, compression and rolling summaries), tool design (good-tool characteristics, string-replacement file editing, sandboxed execution and test loops), permissions and multi-layered security including prompt-injection defense, observability, advanced capabilities such as sub-agents, parallel tool calls and long-term memory, running agents in cloud CI/CD pipelines, evaluation-driven iteration, cost control, and a minimal viable path for building your own coding agent. The central conclusion: the model sets the ceiling on capability, while the harness determines how much of that ceiling you actually get.
1. Introduction: What Happens When a Coding Agent’s Cards Are Laid on the Table
Coding agents have become one of the hottest tools for improving engineering productivity. Recently, a well-known commercial coding agent shipped a client build with a packaging misconfiguration that included source maps, and more than 500,000 lines of core code were fully reconstructed as a result. The first reaction among developers was not gossip — it was to go read the source. These are among the most capable coding assistants available today, and people wanted to know what exactly makes them so strong.
One key fact needs clarifying up front: what was reconstructed was the client-side code that runs on the user’s machine, consisting mainly of agent orchestration, tool implementations, and interaction logic. The model inference service was not part of it. That in itself proves a point: the model is not the whole of a coding agent. Every line of orchestration logic in the client is the product of an engineering team iterating against real usage.
This connects to the “harness engineering” idea that has been widely discussed in the industry. Some find the term pretentious, but it describes something that has been true for a long time: rather than hoping the model will suddenly become smart enough, constrain it with a system — like putting reins on a wild horse and marking out a track. Setting specific products aside, this article answers a general architectural question: how is a coding agent actually built?
2. Starting Point: From Code Completion to Coding Agent
To understand how a coding agent is implemented, first look at how it differs fundamentally from traditional code completion:
| Dimension | Code Completion | Coding Agent |
|---|---|---|
| Interaction model | Inline suggestions; a human accepts or rejects them | Receives a high-level task, then plans and executes across multiple steps autonomously |
| Context scope | The current file and nearby code | The whole workspace, with on-demand search, reading, and execution |
| Ability to act | Produces text only | Can read files, modify code, run commands, and run tests |
| Termination condition | Ends as soon as a suggestion appears | Ends when the goal is achieved or a stop condition triggers |
| Core difficulty | Latency and suggestion quality | Planning, context management, tool reliability, safety boundaries |
Code completion is more like a copilot: every action is confirmed by a human. A coding agent is closer to an intern: given a task such as “add pagination to this endpoint,” it locates the code on its own, understands the existing conventions, makes the change, runs the tests, and iterates on the errors until it delivers a result. Once an agent has the ability to make autonomous decisions and take actions, risk arrives with it: reading the wrong file, editing the wrong place, and executing dangerous commands can all genuinely happen. The core problem the harness exists to solve is this: how do you get a probabilistic model to complete multi-step tasks reliably and safely inside a real codebase?
3. Architecture Overview: A Layered Design for a Coding Agent
A typical coding agent breaks down into five layers, plus two cross-cutting capabilities that run through all of them:
- Interaction layer: the CLI, desktop app, IDE extension, web console, or a coding assistant entry point on a code hosting platform. It receives tasks, displays execution progress, and collects human confirmations.
- Orchestration layer: the core of the agent, containing the main loop, context assembly, tool dispatch, termination decisions, and subtask delegation.
- Tool layer: the “hands and feet” through which the model interacts with the real world — file reading, code search, editing, command execution, test running, sub-agent invocation, and so on.
- Environment layer: provides an isolated place to run, including workspace sandboxes, network egress, credential injection, and runtime dependencies such as language toolchains.
- Model layer: the actual inference service responsible for planning and generation, usually also handling model routing and context caching.
- Cross-cutting capability #1 — security and permissions. Every tool call must pass through this gate before it executes.
- Cross-cutting capability #2 — observability and evaluation. Log every invocation, and use evaluation sets to continuously verify the effect of changes.
If you treat “the model can write code” as a given premise, then what usually determines whether an agent is good or bad is the rest of those layers.
4. The Core Skeleton: The Agent Main Loop
At its heart, a coding agent is a perceive → decide → act → observe loop. Three architectural patterns are common in the industry:
- Single model, single agent: one model, one loop, one tool set — for example, early client-side agents.
- Single model with sub-agents: a main agent coordinates while sub-agents handle subtasks such as search or auditing, in order to isolate context.
- Orchestrator plus workflows: the main model decomposes the task and dispatches to several specialized workflows, each of which runs its own small loop. This shape is common in enterprise platforms where stability and governance matter.
Whatever the shape, a single turn is driven by the same main loop:
MAX_TURNS = 50 # Upper bound on turns, to prevent infinite loopsdef agent_loop(task, workspace, tools, model, permission):context = build_context(task, workspace) # 1. Assemble the initial contextfor turn in range(MAX_TURNS):if user_aborted(): # 2. Allow the user to interrupt at any timereturn Interrupted(turn)response = model.chat(context, tools.schemas()) # 3. Model inferenceif not response.tool_calls: # 4. No tool call = the task has convergedreturn FinalAnswer(response.text, turn)results = []for call in dedup_and_cap(response.tool_calls): # 5. Deduplicate and rate-limitdecision = permission.check(call, workspace) # 6. Permission checkif decision.needs_user_confirm:decision = wait_for_user(decision)if not decision.allowed:results.append(ToolResult(call, deny_reason(decision)))continuetry: # 7. Execute and observeresults.append(ToolResult(call, execute(call, workspace)))except Exception as e: # 8. Feed errors back as observationsresults.append(ToolResult(call, f"ERROR: {e}"))context.extend(results) # 9. Feed observations back into contextreturn BudgetExceeded(MAX_TURNS) # 10. Termination safeguard
Several design details in this loop are worth digging into.
First, “no tool call” is the convergence signal. The mainstream approach requires the model to emit its final answer as plain text; as long as it is still requesting tool calls, the loop continues. Some implementations also keep a dedicated “task complete” tool as an explicit end signal. The termination condition lives in the code, not in the model verbally announcing “I’m done.”
Second, errors are fed back to the model as observations. Exceptions thrown by tool execution are not silently swallowed; they are formatted as text and written back into the context so the model sees the failure reason on the next turn and corrects itself. This is what separates an agent from a one-shot script: the error itself is a feedback signal.
Third, structured tool calls replace free-text parsing. Early implementations asked the model to emit JSON and parsed it themselves, where a small formatting deviation would break things. The newer approach uses native tool calling: the inference service emits a typed call request, and the harness is responsible for executing it. Engineering responsibility shifts from “guessing what the model wants to do” to “constraining what the model is allowed to do.”
Fourth, termination conditions are written in advance. On the stop-condition front, a “naked” implementation loops until the budget is exhausted. A mature implementation includes at least:
- A maximum turn count or token budget;
- Timeouts and user interruption;
- Tool call deduplication and rate limiting;
- A consecutive-failure circuit breaker — for example, abort and report progress to the user once edits to the same file fail past a threshold.
Fifth, the process is made visible to the user. Execution is usually presented as a timeline: which file is being read, what changed, what the command returned. This granularity is not cosmetic. First, human-in-the-loop review is itself the most important safety barrier. Second, users often care more about how something was accomplished than just what was accomplished, and visibility is essential to building trust.
5. Context Engineering: A More Important Lesson Than Prompt Engineering
Coding agents face a unique constraint: a codebase never fits into the context window. A medium-sized project can easily reach hundreds of thousands or even millions of tokens of source code, and the context window — no matter how large — is a finite, expensive resource that also “dilutes attention.” So the capability that determines agent performance shifts from “writing good prompts” to “managing context well.”
5.1 Layered Assembly: System, Project, and Task Context
Mature implementations usually split context into three layers:
- System context: the agent’s identity, behavioral rules, tool descriptions, and safety constraints. It is relatively stable and well suited to cache reuse.
- Project context: build and test commands, code style, directory conventions, off-limits directories. Newer implementations no longer stuff all project information into one fixed block of text; instead they use a convention file at the project root that the agent reads on its own when it starts work.
- Task context: the current task description, relevant file contents, search results, and historical observations. It changes constantly and is the main source of token consumption.
This layering yields two direct benefits: the stable portion can hit the context cache, significantly reducing cost and time to first token; the dynamic portion can be compressed, trimmed, or even discarded without polluting the core instructions.
5.2 On-Demand Loading: Let the Agent Go Looking Instead of Feeding It Everything
Around the question of “how do we get the right code into the context,” three approaches have emerged:
- Bulk prefilling: stuff all relevant files into the context at startup. Simple and direct, but expensive and slow, prone to attention dilution, and it breaks down as the project grows.
- Retrieval-augmented generation (RAG): chunk the code, embed it, store it in a vector database, and recall fragments by similarity. Suitable for cross-repository knowledge Q&A, but code chunking easily splits semantics apart — for example, separating a function from its callers.
- Agentic search: give the model tools like grep, glob, and read-by-symbol, and let it work the way a human engineer does — “look at the directory structure first, then search for keywords, then read the specific function.” Newer client implementations generally make this the primary approach.
The mainstream practice combines them: use keyword search to locate things quickly, add vector search when necessary, and finally let the agent read in detail on demand. In code scenarios, symbol names are usually more precise than semantic vectors.
5.3 Compression and Rolling Summaries
Once a conversation grows long, the context must be compressed. Common strategies include:
- Tool output trimming: keep only the first and last few lines of command output plus a summary; return only the key fragments of large files.
- Summarizing older turns: compress earlier observations into structured summaries that keep “which files were changed, which commands were run, what conclusions were reached” and discard the raw output.
- Externalizing critical state: write the task plan, progress, and decisions into a plan file in the workspace, so compression keeps a pointer rather than the content.
In addition, newer models generally support a “context editing” capability, where the inference service automatically clears old, redundant content according to rules. The harness can declare a cleanup policy rather than hand-writing all of the compression logic on the client.
6. Tool Design: The “Hands and Feet” That Cap an Agent’s Ability
The model can only emit tokens; what actually changes the world is tools. The quality of tool design often determines an agent’s success more than the choice of model does.
6.1 Five Characteristics of a Good Tool
- Clear semantics: the name and description let the model immediately understand when to use it and when not to. The description is a prompt; a vague description leads directly to misuse.
- Right granularity: too coarse (“implement a feature”) leaves the model with nowhere to start; too fine (character-by-character editing) causes turn explosion. Reading files, precise replacement, executing commands, and running tests are widely accepted as the right granularity.
- Readable output: error messages should be actionable, telling the model what to do next, rather than dumping a raw stack trace.
- Idempotent and rollback-able: repeated execution produces no side effects, or task-level snapshots are established on top of version control.
- Permission-controllable: every tool can be intercepted, audited, and rate-limited by a policy engine.
6.2 File Editing: The Most Error-Prone Part of a Coding Agent
The fault-tolerance design of the edit tool deserves separate discussion. Early implementations often required the model to output the entire file, which was length-limited and easy to lose content in. The approach that later became popular is exact string replacement: the model supplies the old fragment and the new fragment, and the tool finds a unique match in the file and replaces it. The clever part of this design is that the verification authority sits with the harness, not the model:
Edit(file="src/api/user.py",old_str="def get_user(user_id):",new_str="def get_user(user_id, page=1, page_size=20):")
# Failed: found 3 matches in src/api/user.py, please provide more context to ensure uniqueness# Failed: no match found. Read lines 40-60 of the file first
When the match is not unique or cannot be found, the tool returns a clear message guiding the model to add context or re-read the file. Newer implementations also check for result uniqueness before the replacement and re-verify that the file has not been modified externally before writing, to avoid concurrent conflicts. Multi-file edits are often modeled as an atomic operation: either all of them succeed or none of them take effect.
6.3 Execution and Verification: Turning “Testing” Into the Agent’s Self-Correction Loop
Read-only tools carry limited risk; execution tools must be sandboxed by default and follow these principles:
- Networking disabled by default, with only necessary egress such as the model inference service allowed;
- Writes outside the workspace prohibited by default;
- Per-execution timeouts and output length limits;
- Sensitive credentials in environment variables not visible to child processes;
- High-risk operations such as deletion, pushing, and dependency installation require explicit authorization.
The closed-loop value of testing deserves special emphasis. Early implementations distinguished between a “read-only mode” and an “auto-accept edits mode”: read-only mode ran no commands, while edit mode ran the tests. Later the two were merged — edits are applied to disk by default, then formatting, type checks, and tests run automatically. Tests are no longer just an acceptance tool; they are the agent’s “sensory organ.” After changing code, the model runs the tests and keeps fixing based on the failure output until they pass. This execute → observe → correct loop is the fundamental reason coding agents can complete complex tasks.
7. Permissions, Security, and Observability: The Baseline for Shipping
7.1 Permission Tiers and Human-in-the-Loop
Permission systems are generally tiered by the blast radius of the operation:
- Read-only exploration: reading files and searching, usually confirmation-free;
- Workspace writes: limited to the project directory, allowed according to policy;
- Command execution: classified by command — allowlisted commands pass, denylisted ones are blocked, everything else prompts;
- Network and external systems: denied by default, authorized as needed;
- High-risk operations: mandatory human confirmation, with the exact command to be executed displayed.
A denylist cannot rely on string matching alone, because an attacker can bypass it through variable concatenation, encoding, and similar techniques. A more robust approach parses the command structure with an abstract syntax tree and then matches dangerous patterns.
7.2 Defense in Depth: Don’t Rely on the Model’s Good Behavior
Security cannot depend on the model being “obedient enough”; it needs mechanisms as a backstop:
- Entry-point interception: rules and a policy engine make the decision before a tool executes;
- Sandbox isolation: containers, least privilege, network allowlists, read-only mounts;
- Output scanning: check whether the model’s responses leak sensitive information such as keys;
- Prompt-injection protection: READMEs in repositories, issue comments, and web content may all contain malicious text like “ignore the previous instructions.” The principle is: external content is always data, never instructions. After ingesting external content, sensitive operations should be blocked.
7.3 Observability
A mature agent should be able to answer these questions: how many tokens each turn used, how long each tool call took, what the failure rate is, and which kinds of tasks get stuck most often. In practice, this usually combines client-side instrumentation with server-side analysis. When a task finishes, information such as the session ID, model, tool usage statistics, turn count, and whether the user interrupted is reported to the server; the data flows into logging services and analytics platforms to support cost attribution and quality analysis. Many implementations also ship a usage statistics command so users can see the consumption distribution across models and tools locally.
8. Advanced Capabilities: Sub-Agents, Parallelism, and Memory
8.1 Sub-Agents: Trading Context Isolation for Stability
When the main agent needs to explore a large number of files, the context fills up fast. The solution is to dispatch a sub-agent: the main agent issues only a well-defined task (such as “find every place that calls the old API”), the sub-agent completes the search in its own isolated context, and only the conclusion is returned to the main agent. The main agent’s context stays clean, and the sub-agent’s “digging process” does not pollute the main thread.
Sub-agents are in tension with each other. One experiment by an independent team found that having multiple sub-agents each handle a portion and then having the main agent aggregate the results performed worse than a single agent running serially, because of coordination overhead and duplicated work. But when the task was constrained to “sub-agents only search, the main agent writes code,” performance improved noticeably. This shows that whether parallelism pays off depends on how cleanly the task can be split and how coupled the subtasks are.
8.2 Parallel Tool Calls and Background Tasks
Independent read-only operations can run in parallel — reading three files at once, searching for several keywords at once — significantly shortening single-turn latency. Long-running commands such as full builds or long test suites can be moved to the background: the agent goes on to do something else and comes back for the results later.
8.3 Structured Output: Plans, Todos, and Subtask Lists
Complex tasks need planning first: produce a plan, confirm it with the user, then start executing. The plan itself should be structured, checkable, and updatable, so that progress at every step of a long task is clearly visible. For the user, it doubles as a progress board and as a handle for intervening midway.
8.4 Long-Term Memory
Cross-session memory usually lives in memory files inside the workspace, recording user preferences, project conventions, and pitfalls already encountered. At the start of each session, these files are automatically loaded into the context; when there are too many entries, they are retrieved by relevance. Newer implementations also introduce a “memory tool” that lets the agent decide on its own to write or update memory during a conversation, instead of relying on the user to maintain the files by hand.
9. Plugging Agents Into the Development Workflow: Cloud Engineering Practice
A coding agent’s value extends beyond writing code locally. When it is wired into a cloud development workflow, the problem becomes how to run it at scale safely, controllably, and observably. Taking Baidu AI Cloud as an example, this kind of platform typically offers a full chain from code hosting to runtime isolation: a code hosting platform can host the coding assistant and the code review assistant; DevOps pipelines can embed agent tasks into stages such as code review and continuous integration; a container platform provides an isolated execution environment for the agent; and logging services plus monitoring and alerting handle runtime observability. The following cloud pipeline for “automatically fixing a CI failure” illustrates the pattern:
stages:- build_and_test: # Normal build and test- agent_fix: # Triggered only if the previous stage failedwhen: on_failuresteps:- create_workspace_snapshot # Snapshot the workspace to enable rollback- run_agent:task: "Analyze the failure reason in the build log, fix the code, and make the tests pass"max_turns: 30sandbox: { network: deny, write_paths: ["repo/"] }tools: [read_file, edit_file, run_tests, read_log]- run_tests # Re-run the tests independently; do not rely on the agent's own report- open_pull_request: # Produce a reviewable PR rather than merging directlyrequire_human_review: true- notify: # Result notification and metrics reporting
This design has three key points:
- An explicit trigger condition: the agent is enabled only when the normal flow fails, avoiding wasted consumption;
- Least privilege: no network access inside the sandbox, writes only to the repository directory, and a tool set trimmed to the task;
- Results must be reviewable: the agent’s output is submitted as a PR and verified by an independent test re-run, rather than being merged straight into the main branch.
On the metrics side, you can continuously collect data such as task success rate, average turns, token consumption, and human modification rate, providing a basis for iterating on the harness.
10. Evaluation and Iteration: No Measurement, No Improvement
The biggest difference between harness engineering and “tuning prompts by feel” is that it iterates on data.
Building the evaluation set: collect tasks from real repositories — for example, pick a “fix this defect” commit from version history, use the pre-fix code as input, and use “do the tests pass” as the pass criterion. Such tasks are objectively judgeable and suitable for automation.
Core metrics: at minimum, task success rate, average turns, token cost, human intervention rate, and dangerous-operation interception rate.
Regression mechanism: any change to a prompt, tool description, permission policy, or model should be regressed against a fixed evaluation set, to avoid “fixing one scenario and breaking three others.”
Canary release: try a new version with a small set of users first, compare the key metrics, then roll it out to everyone.
11. Cost Control: Agents Are Big Spenders
An agent consumes far more tokens than an ordinary chat, because every turn carries the full context. Common optimization techniques include:
- Context caching: mark stable prefixes such as the system prompt and tool definitions as cacheable, and bill the repeated portion at a lower price;
- Tiered model routing: use a strong model for planning and complex reasoning, and a lightweight model for summarization, classification, and formatting;
- Prompt caching: cache a stable prompt prefix as a KV cache to reduce redundant computation;
- Context compression: trim tool outputs and historical turns promptly;
- Task-level budgets: set token and duration limits per task, stopping and reporting once exceeded.
It is worth stressing that optimization must not come at the cost of task success rate; every cost-reduction technique should be validated against the evaluation set.
12. Implementation Checklist: The Minimum Viable Path to a Coding Agent
If you are building a coding agent from scratch, you can proceed in this order:
- Get the main loop working first: model invocation plus 2–3 read-only tools (read file, search, list directory), and verify the perceive → decide → act loop;
- Add editing and execution: precise replacement plus command execution, and immediately add the sandbox and permission checks;
- Build context management: layered assembly, on-demand loading, output trimming, and compression policies;
- Complete the security defenses: permission tiers, dangerous-pattern detection, prompt-injection protection, and audit logs;
- Wire it into the development workflow: version control, code hosting, continuous integration, and code review;
- Build evaluation and observability: a fixed evaluation set, a core metrics dashboard, a regression mechanism, and canary releases;
- Keep optimizing: sub-agents, parallel execution, memory, caching, and model routing.
13. Conclusion: The Harness Sets an Agent’s Ceiling
Back to the opening question: how is a coding agent built? The answer is not “you connected a very strong model,” but a complete engineering system: a converging main loop, a well-designed tool set, a disciplined context management regime, a reliable permission and sandbox layer, plus an evaluation and observability loop. The model determines the ceiling of capability; the harness determines how much of that ceiling actually gets realized.
The reason the term “harness engineering” resonates is that it states a plain truth: at a time when model capabilities are improving rapidly, the real competitive edge has shifted to “how well you use the model.” For developers, understanding the architecture of a coding agent is more than satisfying curiosity — it is a way to find a repeatable engineering paradigm for your own team.
