Building Controllable AI Coding Agents: Model-Native vs. Orchestrated Architectures

    This article compares two architecture approaches for AI coding agents: model-native agents, where the model drives the execution loop, and externally governed designs, where an orchestration layer manages state, stages, and permissions. It also covers two multi-agent patterns, fixed-role planner-executor splits and dynamically spawned subagents, and gives criteria for choosing between them. Engineering practices for permissions, checkpoints, limits, and logging are included.

    1. Background

    AI coding agents have evolved from completing single lines of code into full workflows. They accept a goal, break the goal into tasks, read and write files, and run tests. The core question for users has changed accordingly. The question is no longer whether a model can write code. It is who decides how the model carries out each step. This decision affects the tool’s controllability, auditability, cost structure, and suitable use cases.

    This article does not evaluate specific products. Instead, it starts from architectural principles, compares two mainstream design approaches, and offers a method for choosing between them.

    2. Two Architectural Approaches

    2.1 Model-Native Agents

    This design builds capabilities such as planning, tool selection, result reflection, and stop decisions into the model itself. The external system provides only a fixed set of tools, such as reading files, editing files, executing commands, and searching code. The model decides when to call each tool. The system has no separate scheduler. The execution loop is driven by the model’s output.

    The main advantage is natural interaction. Users only need to describe the goal. They do not need to design a workflow. The model can adjust its strategy based on intermediate results, which gives it strong adaptability to problems it has not seen before.

    The limitation is that the model’s internal decisions are hard to constrain in advance. The system cannot guarantee that a given step will or will not happen. When the model makes a wrong judgment, the external system has no forced checkpoint to intercept it.

    2.2 LLM Plus Control Framework

    This design treats the model as a generation component. An independent orchestration program sits outside it and handles state management, stage transitions, permission control, and error rollback. Each time the model is called, the orchestrator passes in only the context and tool set required for the current stage.

    The main advantage is that the workflow is visible, testable, and replaceable. A team can require a design review before any code change, or insert human approval at any point.

    The limitation is that the orchestration logic must be designed and maintained by people over time. Once a workflow is fixed, it can feel rigid when a task falls outside what was anticipated.

    2.3 Comparison Summary

    Dimension Model-Native Externally Governed
    Control ownership The model The orchestrator and the user
    Process predictability Lower Higher
    Adaptability to new tasks Higher Depends on predefined workflows
    Auditability Relies on log replay Each stage can be inspected separately
    Maintenance cost Concentrated in prompt and tool design Concentrated in workflow and state design

    3. Two Patterns for Multi-Agent Collaboration

    3.1 Fixed-Role Planner and Executor Split

    A common approach is to separate planning from execution. The planner agent has read-only permissions. It reads code, maps dependencies, and produces a plan document. The executor agent has write and run permissions. It modifies code according to the plan and runs tests. The two agents exchange structured artifacts through the file system, so users can review, edit, or stop the process between the two stages.

    A simplified example:

    1. from dataclasses import dataclass
    2. @dataclass(frozen=True)
    3. class AgentRole:
    4. name: str
    5. allowed_tools: frozenset[str]
    6. PLANNER = AgentRole("planner", frozenset({"read_file", "search", "write_plan"}))
    7. EXECUTOR = AgentRole("executor", frozenset({"read_file", "edit_file", "run_tests"}))
    8. def check_permission(role: AgentRole, tool: str) -> None:
    9. if tool not in role.allowed_tools:
    10. raise PermissionError(f"{role.name} is not allowed to call {tool}")

    The value of this design is that code enforces the permission boundary. It does not rely on prompt instructions. Even if the planner’s output tries to modify a file, the tool layer rejects the call.

    The cost is that the roles are fixed. For tasks outside the predefined split, such as database migrations, performance load testing, or security audits, the system can only loosely fit the existing roles. Otherwise, a person must define new roles.

    3.2 Dynamically Spawned Subagents

    Another approach is for a main agent to create subagents on the fly based on the task. Each subagent has its own context window and a subset of tools. After finishing its subtask, it returns its conclusion to the main agent. The collaboration pattern is also decided at runtime. It can run serially, run in parallel, or let several candidate solutions vote.

    A simplified example:

    1. import asyncio
    2. async def run_subagent(spec: dict) -> str:
    3. # spec contains the task description, tool allowlist, and context summary
    4. ...
    5. return "subtask conclusion"
    6. async def orchestrate(subtasks: list[dict], mode: str = "parallel") -> list[str]:
    7. if mode == "parallel":
    8. return await asyncio.gather(*(run_subagent(s) for s in subtasks))
    9. results = []
    10. for s in subtasks:
    11. results.append(await run_subagent(s))
    12. return results

    The advantage of this pattern is adaptability. It can build a suitable structure for rare tasks as needed. Its drawbacks are also clear. As the number of subagents and call rounds grows, context is passed repeatedly, and token consumption rises noticeably. When the structure becomes too complex, debugging and reproducing failures also become harder.

    3.3 Choosing Between the Two Patterns

    Four factors help with the decision.

    • Task repetition: Frequent tasks with stable workflows suit fixed roles. The workflow can become a template, which reduces decision overhead for each run.
    • Risk level: For tasks involving production environments, permission changes, or data deletion, enforce permission isolation in code. Do not rely only on the model to behave correctly.
    • Boundary clarity: For tasks with unclear boundaries that require exploratory research, dynamic decomposition is usually more effective.
    • Cost sensitivity: When the token budget is tight, limit the number of subagents and set a call cap for each task.

    4. Engineering Recommendations

    4.1 Enforce Permissions at the Tool Layer

    Prompts can be bypassed, and they can stop working when the context grows too long. A more reliable approach is to validate every tool call at the entry point. The check should cover the calling role, path allowlists, and command denylists. These rules should be written as code and covered by unit tests.

    4.2 Set Checkpoints That Match the Risk

    For large changes, place a human review checkpoint after the plan is generated and before any code is modified. The number of checkpoints should match the task’s risk. Too many checkpoints interrupt users. Too few fail to catch bad plans in time.

    4.3 Limit Subagent Depth and Count

    Set upper limits for dynamic architectures. These include maximum nesting depth, maximum parallelism, and a token budget for each task. When a threshold is exceeded, the scheduler should stop further calls and return the partial results already completed. It should also state which parts were not finished.

    4.4 Keep Complete Call Logs

    Record the input, output, duration, and owning agent for every tool call. Logs support post-incident review. They also help determine whether a failure came from a planning error or an execution error, so the team can fix the workflow or the prompts accordingly.

    5. Common Misconceptions

    First, the belief that more control always produces better results. Excessive constraints prevent the system from handling situations outside the plan. Users then have to intervene manually and often, and overall efficiency drops.

    Second, the belief that a dynamic structure is always smarter. Subagents make mistakes too. The more layers of calls there are, the longer the path errors can travel, and the harder they are to trace.

    Third, ignoring cost. Multi-agent solutions usually consume far more tokens than single-agent solutions. Teams need to compare the expected benefit of a task against its resource cost explicitly.

    Fourth, treating tools as the capability itself. Tools only provide interfaces for reading, writing, and execution. Task quality still depends on planning, context management, and result verification.

    6. Adoption Path

    Teams introducing this type of tool can proceed in four steps.

    1. Choose a category of tasks that is highly repetitive and low in risk, such as adding unit tests or syncing interface documentation, as a pilot.
    2. Record completion rate, rework count, and token consumption during the pilot to establish baseline data.
    3. Use the baseline to decide whether to expand the scope, and gradually grant more permissions or subagent capabilities.
    4. Review failure cases regularly, and turn recurring problems into tool constraints or workflow templates.

    7. Conclusion

    Each architecture suits different scenarios. The model-native approach feels smooth for exploratory tasks. The externally governed approach is more reliable in high-risk, audit-heavy settings. Fixed roles provide predictability, and dynamic generation provides adaptability. The trade-off between them depends on task characteristics and organizational requirements. Teams should choose based on task risk, repetition, and cost budget, rather than on the marketing descriptions of any single architecture.