Five-Layer Architecture for AI Coding Agents: Responsibilities from Entry Point to Model Invocation
This article explains a five-layer architecture for AI coding agents that separates entry-point routing, the agent loop, tool execution, permission control, and prompt/context construction. It covers why the layers should be separated, the responsibilities of each layer, structured tool results, permission decisions, context compression, error handling, and testing and observability practices. The goal is to keep the execution path understandable, testable, auditable, and extensible as tools, modes, and security requirements grow.
Five-Layer Architecture for AI Coding Agents: Responsibilities from Entry Point to Model Invocation
An AI coding agent is not simply a system that calls a model API once and then runs a command. A maintainable system must handle command parsing, session state, model output, tool scheduling, permission confirmation, error recovery, context construction, and many other concerns.
If all of this logic is packed into one main flow, development may be fast at first. But as the number of tools, runtime modes, and security requirements grows, the code often turns into a hard-to-understand collection of conditional branches. The entry function becomes longer, model calls and file operations become coupled, permission rules are scattered across tools, and changing any one part can cause unexpected problems.
A more robust approach is to split the agent into five layers:
| Layer | Typical modules | Main responsibilities |
|---|---|---|
| Command entry layer | entrypoints/, main.ts |
Parse arguments, select runtime modes, control initialization cost |
| Agent loop layer | agentLoop.ts, queryEngine.ts |
Drive model decisions, tool calls, and result feedback |
| Tool execution layer | tools/, toolExecutor.ts |
Convert structured intent into concrete system operations |
| Permission control layer | permissions/, policy.ts |
Evaluate, confirm, and audit high-risk actions |
| Prompt and context layer | prompts/, contextBuilder.ts |
Organize system instructions, workspace information, and tool definitions |
These five layers are not merely a directory split. They are an explicit division of responsibilities. The goal is to keep the entire execution path understandable:
User input → entry initialization → model decision → tool request → permission decision → tool execution → result feedback → next decision
The following sections discuss the architecture from four perspectives: motivation, module responsibilities, code organization, and engineering practice.
1. Why Split by Layer
The complexity of an AI coding agent comes mainly from three facts.
First, model output is uncertain. Even if the prompt and tool definitions remain unchanged, the model may choose different execution paths in different contexts. The system therefore must validate, constrain, and provide fallbacks for model output.
Second, tool operations have side effects. Reading a file is usually a low-risk action, but modifying configuration, deleting directories, running scripts, or accessing the network may affect the user’s environment. The tool layer cannot assume that “the model said to do it, so it should be done.”
Third, user experience and startup performance matter. When a user only checks the version, views help, or runs a lightweight subcommand, the system should not load the full interactive environment, workspace scanning logic, and model communication modules just to produce a simple output.
After layering, each layer solves only its own problem:
- The entry layer focuses on “how the system should start”;
- The loop layer focuses on “when to ask the model, when to execute a tool, and when to finish”;
- The tool layer focuses on “how to reliably complete one atomic operation”;
- The permission layer focuses on “whether the current operation is allowed to happen”;
- The prompt layer focuses on “which constraints and context the model should receive.”
This design significantly reduces cognitive load. When developers troubleshoot, they can quickly identify whether the problem is argument dispatch, uncontrolled loop state, a tool implementation defect, a permission policy misjudgment, or insufficient context construction.
2. Command Entry Layer: Keep the Startup Path as Light as Possible
The command entry layer is the first point of contact between the user and the system. Its responsibility is not to implement all business logic, but to select the most appropriate runtime path based on user intent.
A good entry layer usually follows a “decide first, load later” principle. Requests that do not need the full runtime environment should return as early as possible. Different runtime modes should use lazy loading so that unrelated modules do not participate in initialization.
Example code:
async function main(): Promise<void> {const args = process.argv.slice(2);if (args.length === 1 && args[0] === "--version") {console.log(readVersion());return;}if (args[0] === "service") {const { runServiceMode } = await import("./service/main");await runServiceMode(args.slice(1));return;}if (args[0] === "--help") {printHelp();return;}const { runInteractiveMode } = await import("./interactive/main");await runInteractiveMode(args);}
The key here is not the specific argument names. The key is that the entry layer avoids depending on complex modules too early.
2.1 Fast-Path Requests Should Avoid Full Initialization
Operations such as checking the version, printing help, and checking basic configuration usually do not need to:
- Scan the working directory;
- Read large configuration files;
- Initialize an interactive terminal;
- Establish model communication;
- Load the full tool registry;
- Restore historical sessions.
Designing these lightweight requests as “fast paths” improves response time and reduces the chance that basic commands become unavailable because of environment errors.
2.2 Runtime Modes Must Be Explicitly Dispatched
An AI coding agent may have interactive mode, batch mode, service mode, remote invocation mode, and others. If mode detection is mixed into later business logic, the flow gradually becomes difficult to control.
The entry layer should do only three things:
- Read and validate arguments;
- Determine the runtime mode;
- Hand control to the corresponding module.
This ensures that each mode has its own lifecycle management. For example, interactive mode needs to maintain sessions and terminal rendering; batch mode focuses more on standard input/output and exit codes; service mode also needs to handle concurrent requests, health checks, and resource cleanup.
2.3 The Entry Layer Should Not Carry Business Decisions
A common anti-pattern is to write permission checks, prompt assembly, tool execution, or even model calls directly into the entry file. This may look convenient, but it turns the entry file into a global control center.
The entry layer should remain thin and stable. It is a router, not a business implementation.
3. Agent Loop Layer: Control the Iteration Between Model and Tools
The agent loop layer is the scheduling hub of the entire system. It does not read or write files itself, and it does not define permission policies. It maintains execution state and drives the loop of “model decision—tool execution—result feedback.”
A simplified flow can be expressed as:
while (!state.finished) {const modelResult = await modelClient.generate({messages: state.messages,tools: toolRegistry.describe(),});const decision = parseModelResult(modelResult);if (decision.type === "final_answer") {state.finished = true;state.output = decision.content;continue;}if (decision.type === "tool_call") {const result = await executeToolCall(decision, state);state.messages.push(toToolResultMessage(result));continue;}state.messages.push(toRecoveryMessage(decision));}
This loop reflects a core principle: the model proposes intent, and the system validates and executes that intent.
3.1 The Loop Layer Should Maintain an Explicit State Machine
A real system should not rely on only a simple while loop. State should be made explicit. For example:
Initializing: initialize the session and context;WaitingModel: wait for the model to generate a decision;ValidatingAction: validate tool names and parameters;WaitingPermission: wait for user confirmation;ExecutingTool: execute the tool;Recovering: handle exceptions and construct recovery information;Finished: output the final result.
The value of an explicit state machine is observability and recoverability. When an exception occurs, the system can accurately answer: at which stage did the failure happen, have side effects already occurred, and can the operation be safely retried?
3.2 Loop Count and Resource Consumption Must Be Limited
The model may repeatedly request the same tool, or continue attempting invalid operations in an incorrect context. The loop layer therefore needs general protections:
- Maximum round limit;
- Timeout for a single tool call;
- Total execution time budget;
- Duplicate operation detection;
- Context size limit per round;
- Maximum failure count.
These limits are not meant to reduce capability. They keep the system controllable in the face of uncertainty. When a limit is reached, the system should explain the current progress, the failure reason, and suggested next steps to the user instead of looping indefinitely.
3.3 Tool Results Must Be Returned in a Structured Form
If tool results are simply concatenated into natural language, the model can easily misunderstand them. Structured results are recommended, for example:
type ToolResult = {ok: boolean;summary: string;data?: unknown;error?: {code: string;message: string;retryable: boolean;};};
Here:
okindicates whether the tool succeeded;summaryprovides a brief description suitable for model understanding;dataprovides necessary structured information;errorlets the loop layer distinguish retryable from non-retryable errors.
For example, a missing file may require the model to adjust the path; a permission denial usually should not be retried automatically; a temporary network error can be retried a limited number of times.
4. Tool Execution Layer: Turn Abstract Intent into Verifiable Operations
The tool execution layer connects the model world and the real environment. The model may express intents such as “read a file,” “search for text,” “modify configuration,” or “run a check.” The tool layer converts these intents into deterministic system behavior.
The basic unit of tool design should be a “small, clearly defined atomic capability,” not an all-powerful execution interface.
For example, file-related operations can be split into:
- List a directory;
- Read text;
- Search content;
- Create a file;
- Modify a specified range;
- Delete a specified file.
This has three benefits.
4.1 Inputs and Outputs Are Easier to Validate
Each tool should have an explicit parameter schema and return structure. Take a text-reading tool as an example:
type ReadTextInput = {path: string;startLine?: number;endLine?: number;};type ReadTextOutput = {path: string;content: string;truncated: boolean;};
Parameter validation should happen before resources are actually accessed. Fields such as paths, line numbers, encoding, and content length all need clear boundaries.
4.2 Tool Implementations Should Be Decoupled from Model Calls
A tool itself should not know “who called it,” and it certainly should not assemble model messages directly. It only needs to fulfill its own atomic responsibility.
The recommended call chain is:
Agent loop layer↓Tool dispatcher↓Permission decision layer↓Concrete tool implementation↓Structured execution result
This separation allows tools to be tested independently. For example, a file-reading tool can verify path handling, exception returns, and content truncation logic without connecting to a model or starting an interactive interface.
4.3 The Tool Layer Needs Defensive Implementation
Parameters generated by the model should not be treated as trusted input. Tool implementations should consider at least:
- Whether the path escapes the workspace;
- Whether parameter types are correct;
- Whether content exceeds a safe size;
- Whether commands contain dangerous fragments;
- Whether output needs truncation or redaction;
- Whether exceptions expose sensitive environment information.
For operations with side effects, preview or diff display is recommended. For example, when modifying a file, generate the proposed change first, then write it to disk only after permission-layer confirmation. This lets users understand what the agent is about to do instead of seeing only the final result.
5. Permission Control Layer: Separate Security Decisions from Tools
Permission control is not simply “allow” or “deny.” In an AI coding agent, a more reasonable strategy is to return different levels of handling based on operation type, target scope, runtime context, and user confirmation state.
Operations can be roughly divided into three categories:
| Operation category | Examples | Recommended strategy |
|---|---|---|
| Read-only operations | View files, search text, read configuration | Allow by default, but restrict access scope |
| Controlled write operations | Edit workspace files, create directories | Show impact scope, then confirm or allow by rule |
| High-risk operations | Delete data, run external commands, access sensitive resources | Require explicit confirmation by default and record audit information |
The core of the permission layer is not “block everything.” It is to make the risk level of each operation transparent, explainable, and traceable.
5.1 Policies Should Be Defined Centrally
If each tool decides permissions on its own, rules easily become inconsistent. For example, one file-writing tool requires confirmation, while another batch-update tool executes directly, creating a security gap.
A better approach is to let tools declare their own metadata:
type ToolMetadata = {name: string;sideEffect: "none" | "write" | "high";requiresWorkspaceScope: boolean;supportsPreview: boolean;};
The permission module makes a unified judgment based on tool metadata, parameters, and the current policy, instead of copying rules into every tool.
5.2 Permission Decisions Should Return Reasons
The permission system should not return only a boolean value. A richer result is recommended:
type PermissionDecision = {allowed: boolean;requireConfirmation: boolean;reason: string;auditLevel: "none" | "basic" | "detailed";};
This allows the interactive interface to show clear prompts to the user. The loop layer can also use the result to decide whether to pause, continue, retry, or terminate.
5.3 Auditing Is Important for Enterprise Scenarios
In team and enterprise environments, execution records are important for review and compliance. Audit content can include:
- Request time;
- Session identifier;
- Tool name;
- Operation target;
- Permission decision result;
- User confirmation result;
- Execution summary;
- Error information.
Audit records should avoid storing unnecessary sensitive content directly. They should be redacted, retained, and access-controlled according to actual security requirements.
6. Prompt and Context Layer: Give the Model Appropriate, Not Excessive, Information
The prompt layer is often misunderstood as a fixed block of text. In an AI coding agent, it is more like a “context compiler”: it dynamically constructs model-readable work instructions based on the user task, current directory, available tools, permission state, and conversation history.
Typical context includes:
- System role and behavioral boundaries;
- Current task objective;
- Workspace structure summary;
- Summary of files already read or modified;
- Callable tools and parameter descriptions;
- Permission restrictions;
- Output format requirements;
- Historical tool call results.
6.1 System Instructions Should Be Stable; Task Context Should Be Dynamic
Stable instructions define long-lived rules, such as:
- Do not fabricate tool execution results;
- Follow the permission process before making modifications;
- Read necessary context first when information is insufficient;
- Do not leak sensitive data;
- Adjust strategy based on error information after a tool failure.
Dynamic context changes with the task, including user input, workspace state, and the latest tool results.
Mixing the two into one long string makes maintenance difficult. It is better to build the prompt in fragments:
const prompt = [buildSystemRules(),buildWorkspaceSummary(workspace),buildPermissionNotice(policy),buildToolDescriptions(tools),buildTaskContext(task),buildRecentHistory(history),].filter(Boolean).join("\n\n");
6.2 Context Needs Compression and Eviction Mechanisms
As loop rounds increase, history messages become longer. Unlimited accumulation not only increases call cost but also reduces the model’s focus on the current task.
Common approaches include:
- Keep complete records for the most recent several rounds;
- Compress earlier processes into task summaries;
- Keep only necessary fragments for large files;
- Deduplicate repeated tool results;
- Prioritize failure reasons, modification records, and unfinished items.
Context management is essentially information priority management, not merely “deleting text.”
6.3 Tool Descriptions Must Be Accurate and Limited
Exposing too many tools with unclear boundaries increases the probability of incorrect calls. Tool descriptions should state:
- Applicable scenarios;
- Required parameters;
- Parameter restrictions;
- Whether there are side effects;
- Meaning of returned results;
- Common failure reasons.
At the same time, the tool set should be trimmed according to the current runtime mode. For example, a read-only analysis mode does not need to expose write tools; a restricted environment should not expose high-risk operation entry points.
7. Error Handling Strategy Across the Five Layers
A complete system must assume that every layer can fail.
The entry layer may encounter argument errors or an unsatisfied environment; model calls may time out or return unexpected content; tool execution may encounter file conflicts, insufficient permissions, or unavailable resources; the permission layer may wait for a user response; prompt construction may need trimming because the context is too large.
Error handling should therefore follow these principles:
- Clear error ownership: Each error should identify its source layer and error type.
- Explainable to the user: Avoid outputting only internal stack traces; provide actionable explanations.
- Recoverable for the model: Recoverable errors should be returned in a structured form so the model can adjust its plan.
- Traceable side effects: If an operation has partially completed, clearly state what has and has not been completed.
- Bounded retries: Do not retry indefinitely just because an operation “might succeed.”
For example, when writing a file fails, do not return only “execution failed.” A more reasonable result is: “The target file changed during execution, and the current modification was not written. You can re-read the latest content and then generate a new change plan.” This preserves the facts and provides a next step.
8. How to Verify That This Architecture Is Truly Maintainable
The value of the five-layer architecture must be verified through testing and observability.
8.1 Layered Testing
- Entry layer: verify that different arguments enter the correct runtime mode;
- Loop layer: simulate model decisions and verify state transitions, retries, and termination conditions;
- Tool layer: verify parameter validation, normal execution, and exception handling;
- Permission layer: verify policy results under different risk levels;
- Prompt layer: verify whether context is complete, whether it exceeds limits, and whether key constraints are missing.
8.2 End-to-End Scenario Testing
In addition to unit tests, real task chains should be covered, such as:
- Answering a code-structure question after reading multiple files;
- Completing a controlled modification after confirmation;
- Automatically switching to an alternative approach after a tool failure;
- Safely terminating after the user rejects a permission request;
- Correctly summarizing and compressing context when it becomes too large.
8.3 Observability Design
It is recommended to record unified events at key points:
Session start→ Receive user task→ Build context→ Request model→ Initiate tool call→ Permission decision→ Tool execution complete→ Return execution result→ Session end
These events can be used to locate performance bottlenecks, analyze failure modes, and improve tool design. However, logs should avoid recording full sensitive files, access credentials, and unnecessary user data.
Conclusion
“Readable” code is not just about naming conventions or sufficient comments. More importantly, every module should have stable and restrained responsibility boundaries.
In an AI coding agent, the entry layer handles lightweight dispatch, the loop layer handles process scheduling, the tool layer handles deterministic execution, the permission layer handles risk control, and the prompt layer handles context orchestration. The five layers collaborate without intruding into one another, forming an execution system that is testable, auditable, and extensible.
As features grow, what truly supports system evolution is not putting every capability into a more powerful main function, but continuously maintaining clear and verifiable boundaries among these five classes of problems.
