# How to Build a Code Agent: From the Agent Loop to Engineering Implementation

    This article explains how to build a code agent, a coding agent that can read files, edit code, run commands, and verify its own changes. It covers the difference between code completion, Q&A assistants, and autonomous agents; a four-layer architecture; the reason-act-observe loop; context engineering; tool design; long-task planning; permissions and rollback; local versus cloud execution; common failure modes; and practical implementation priorities.

    1. First, Clarify the Difference: Code Completion, Q&A Assistants, and Code Agents

    Over the past few years, intelligent programming tools have evolved through three major forms:

    1. The first generation was code completion. The model only looked at local context from the current file and generated candidate code snippets.
    2. The second generation was the conversational assistant. It could understand code, answer questions, and suggest modifications, but the actual editing, running, and verification were still handled manually by the developer.
    3. The third generation is the current mainstream form: the code agent, also called a coding agent.

    What separates these forms is not merely model capability, but whether a closed loop exists:

    • Code completion and Q&A assistants perform single-step mapping: they take some context as input, produce text as output, and then the task ends.
    • A code agent runs as a multi-turn autonomous process. It has operational access to the file system, command terminal, and version control. It can read the repository, locate the issue, modify files, execute builds and tests, and decide the next action based on execution results.

    The judgment criterion is simple: if a tool cannot run code by itself to verify its own modifications, it remains an “advisor” rather than an agent. The essence of a code agent is to place both generation and verification inside the same iterable loop.

    2. Overall Architecture: Four Layers

    A usable code agent can usually be divided into four layers, and the responsibilities of each layer should be strictly isolated:

    1. Model layer (reasoning core): responsible for understanding intent, formulating plans, and generating tool-call parameters. It does not directly handle I/O details.
    2. Agent loop layer (orchestration core): drives the “reasoning-action-observation” iterations and manages session state, termination conditions, and error retries.
    3. Tool layer (capability interface): encapsulates capabilities such as file reading and writing, content retrieval, command execution, and process management into structured interfaces that the model can understand.
    4. Runtime layer (execution and isolation): provides the real working directory, shell environment, dependencies, and runtime, and limits the impact surface through permission control and sandboxing.

    One of the most common engineering mistakes is coupling these four layers into a single script: prompts, tool implementations, and permission checks all mixed together. This makes it impossible to test the loop logic independently, replace the model, or switch execution environments.

    3. The Core Runtime: The Reason-Act-Observe Loop

    The heart of a code agent is a constrained loop, not a model given unlimited freedom. Its skeleton is as follows:

    1. def agent_loop(task, tools, max_steps=30, max_budget=None):
    2. context = build_system_prompt() + summarize_task(task)
    3. for step in range(max_steps):
    4. decision = model.generate(context, tools=tool_schemas(tools))
    5. if decision.type == "finish":
    6. return decision.final_answer
    7. if decision.type == "ask_user":
    8. return request_clarification(decision.question, context)
    9. # Pre-tool validation: path traversal, dangerous commands, quota
    10. verdict = policy_check(decision.tool_name, decision.args)
    11. if verdict.needs_approval:
    12. if not human_approve(verdict.summary):
    13. observe(context, "User rejected the operation", decision)
    14. continue
    15. result = execute(decision.tool_name, decision.args,
    16. sandbox=sandbox, timeout=verdict.timeout)
    17. observe(context, format_observation(result)) # stdout / stderr / diff / exit code
    18. context = compact_if_needed(context, budget=max_budget)
    19. return partial_progress_report(context)

    Three design points are often underestimated:

    Multiple termination conditions are necessary. Relying only on the model saying “I’m done” is not reliable. In engineering practice, the system should also set:

    • a maximum number of steps,
    • a maximum token or time budget,
    • no-progress detection, such as detecting that the results of consecutive tool calls are highly repetitive,
    • external verification signals, such as tests passing or compilation succeeding with no errors.

    These conditions should run in parallel, and the loop should converge when any one of them is triggered.

    The quality of observations determines the quality of the next step. After a command is executed, the agent must return at least the exit code, truncated standard output, and truncated standard error, rather than simply saying “execution succeeded.” Truncation strategy also matters: preserving the tail is often more valuable than preserving the head, because error messages usually appear at the end.

    Errors should be modeled as observations, not exceptions. The model reads a file that does not exist, a regular expression fails to match, or tests still fail. These are all valid inputs within the loop. If errors are treated as crashes, the agent loses the opportunity to correct itself.

    4. Context Engineering: The Variable That Really Determines Success

    For a code agent, most stability issues come from context management rather than the model itself. The core contradiction is this: the amount of code in a repository is far larger than the context window, while the model needs only the relevant part.

    1. Layered Assembly Order

    A reproducible context structure should be arranged as follows:

    1. Global instructions: behavioral rules, coding standards, prohibited actions.
    2. Project-level conventions: build commands, test commands, directory structure notes, usually carried by documentation inside the repository.
    3. Task description.
    4. Retrieved relevant code.
    5. Current conversation history.
    6. Tool definitions.

    Information closer to the task description carries higher weight. Therefore, project conventions and global rules should be placed near the front.

    2. Progressive Disclosure: Retrieve First, Then Read

    Do not stuff a large number of files into the context at startup. Instead, allow the model to locate relevant content on demand through retrieval tools:

    1. # Illustrative tool-call sequence
    2. search(pattern="def .*retry|max_attempts", scope="**/*.py") # Locate candidate files
    3. read(path="service/queue_consumer.py", offset=120, limit=80) # Read only the relevant line range
    4. edit(path="service/queue_consumer.py", old="max_attempts = 3", new="max_attempts = 5")
    5. run(cmd="pytest tests/test_consumer.py -x -q") # Verify immediately

    A read tool that supports offset and line limits saves more context than reading an entire file. An edit tool that supports exact string replacement is safer than rewriting an entire file. Full rewrites often cause the model to copy large amounts of irrelevant code, and any omission can introduce hidden regressions.

    3. Context Compression and Summarization

    When context approaches the budget limit, structured compression should be used. The compressed context should preserve:

    • the original task description,
    • the list of completed actions,
    • the paths of modified files,
    • the current unresolved issue.

    Intermediate exploratory conversation can be folded into a summary. However, file modifications that have not yet been applied cannot be preserved only in summary form. They must be based on the actual file content, otherwise the model will continue reasoning based on incorrect memory.

    4. Subtask Isolation

    For tasks that require large-scale searching, such as “find all places that reference a deprecated configuration,” a sub-agent can perform retrieval in an independent context and return only the conclusions to the main loop. This prevents the main context from being polluted by hundreds of lines of search output and is one of the most effective ways to control context expansion in long-running tasks.

    5. Tool Design: Narrow Capabilities into Verifiable Actions

    The goal of tool design is predictability, not having as many capabilities as possible. Practical principles include:

    • Keep the number restrained. Common tools should be kept within 8 to 12, including file reading, precise editing, content retrieval, directory listing, command execution, and task-list maintenance. The more tools there are, the more likely the model is to choose the wrong one.
    • The description is part of the interface contract. Each tool’s purpose, parameter semantics, applicable scenarios, and non-applicable scenarios should be clear, especially “when it should not be used.” An edit tool with vague descriptions is often the primary reason a model repeatedly edits the wrong file.
    • Make operations idempotent and verifiable. An exact-replacement edit should report an error and ask for more context when it matches multiple locations, rather than replacing the first match on its own.
    • Failures should provide actionable information. When a command fails, return the exit code and error output. When a test fails, return the failed test name and assertion information, rather than only saying “failed.”

    6. Planning and Self-Verification for Long Tasks

    Cross-file refactoring tasks cannot rely on editing wherever the model happens to be thinking. A practical approach is to maintain an explicit to-do list. The model should list the steps before starting work and update the status after each step is completed. This serves both as working memory for the model and as a progress view for the developer.

    More importantly, there must be a verification loop. After modifications are completed, the system should automatically run builds, type checks, static scans, or related tests and feed the results back into the loop. Two design principles apply:

    • For repair tasks, define “done” with executable assertions, such as tests transitioning from failure to success.
    • For changes that cannot be automatically verified, such as copy changes, configuration changes, or purely structural adjustments, clearly state the limitations of verification. This prevents the agent from claiming that something was verified when nothing was actually run.

    7. Permissions, Security, and Rollback

    A code agent has real execution capabilities, so its permission model must be a first-class concern:

    • Least privilege principle: read-only by default; write operations and command execution require explicit authorization. Dangerous actions, such as recursive deletion, force push, changing permission bits, or calling externally billed APIs, should always require human confirmation.
    • Tiered allowlist: frequently used safe operations, such as listing directories, reading files, running unit tests, and formatting code, can be set as not requiring confirmation. Medium-risk operations, such as installing dependencies and running builds, should prompt for confirmation. High-risk operations should be forbidden or require mandatory human confirmation.
    • Sandbox isolation: execute commands in containers or restricted environments, limit network egress and file-system write scope, and inject credentials in a controlled way rather than writing them into the working directory.
    • Rollback is a safety net: all modifications should be made under version control, with diff previews and pre-commit checks, so that any incorrect change can be reverted at low cost.

    8. Two Execution Models: Local Terminal and Cloud Workspace

    The runtime of a code agent can run on the developer’s local machine or in a remote isolated workspace. Each option has trade-offs:

    • Local terminal: changes are applied directly to the real environment, providing the fastest feedback. However, it consumes local resources, may cause interaction lag during long-running tasks, and exposes a larger surface area for environments and credentials.
    • Cloud workspace: tasks run in isolated containers, resource usage is decoupled from the host build environment, multiple tasks can run concurrently, environment snapshots and restores are possible, and team-wide runtime versions are easier to standardize. The trade-off is that session state persistence, artifact transfer, and environment consistency must be handled. Dependencies, runtime versions, and system packages must align with the target environment.

    In either form, observability should be treated as infrastructure: record every tool call and command output completely, preserve the diff of each modification, connect execution logs, latency, and failure rates to logging and monitoring systems, and archive artifacts and intermediate files through object storage. Only then does the team have a foundation for retrospectives, audits, and cost optimization. When deploying on Baidu AI Cloud, these capabilities are typically carried by general-purpose cloud products such as container services, object storage, log services, and monitoring and alerting, without requiring teams to build the entire infrastructure from scratch.

    9. Common Failure Modes and Countermeasures

    Failure symptom Root cause Countermeasure
    Unrelated files are modified Edit tool is too broad, context is missing Use exact replacement and report an error when matches are ambiguous
    The same location is modified repeatedly Error messages are truncated or lost Return complete error output and exit code
    Long tasks drift midway Context expands and the goal is diluted Use subtask isolation, structured compression, and a to-do list
    Completion is claimed but not verified Missing mandatory verification step Make verification a required node in the loop
    Dangerous operations are executed Missing permission model Use a tiered allowlist and mandatory human confirmation
    Changes cannot be restored No version control Work under version control throughout and use diff previews

    10. Practical Recommendations

    If you are building or using a code agent, the recommended priority order is:

    1. Define a verifiable “done” signal first, such as tests, compilation, or static checks. Scenarios without a verification signal are not suitable for delegating to an agent.
    2. Decouple tools from the loop, so that loop logic can be unit-tested and the model can be replaced.
    3. Invest in context engineering, because this has the highest return on effort: retrieval tools, line-range reading, and compression strategies.
    4. Build permissions and rollback first, then gradually increase the level of automation.
    5. Add observability, because an agent without complete command records and diffs is difficult for a team to trust.

    In one sentence: the competitiveness of a code agent does not come from how much code the model can write, but from whether the loop can stably verify and correct itself, and whether every automated change can be viewed, reviewed, and rolled back in engineering practice.