Breaking Down the Five-Layer Architecture of AI Coding Assistants
This article explains a five-layer architecture for AI coding assistants: entry, agent loop, tool execution, permission control, and system prompt. It covers why separating these responsibilities makes the code easier to read and change, what each layer does, and how the layers work together during a typical request.
An AI coding assistant has to parse commands, call models, execute tools, decide permissions, and assemble context. If all of this logic sits in one module, the code quickly becomes hard to read and evolve. A common and clear approach is to split the system into five layers: the entry layer, the loop layer, the tool layer, the permission layer, and the prompt layer. Each layer handles one kind of responsibility, and the layers cooperate through explicit interfaces. This article explains from an engineering perspective why this layering improves readability and how each layer is usually designed.
Why Layering Makes Code Easier to Read
Readability starts with being able to find the logic. When developers open a codebase, the worst case is finding argument parsing mixed with model calls, tool execution mixed with permission checks, and prompt assembly scattered across a dozen files. The goal of layering is not to add abstraction. It is to map the problem domain onto the directory structure. The entry layer answers only “how does this run start?” The loop layer answers only “when should the model be queried, and when should tools run?” The tool layer answers only “how is an intent carried out?” The permission layer answers only “is this allowed?” The prompt layer answers only “what should the model see right now?” When responsibility boundaries are clear, the reading path is clear too. Read the entry first, then the loop, and drill down when you need details.
Layering has a second benefit: localized change. Adding command-line options, integrating new tools, tightening permission policies, and adjusting system instructions tend to happen at different paces. If they are coupled, a small change can ripple through the whole call chain. With layers, most changes stay inside a single layer, and the scope of testing is easier to define.
Layer 1: CLI Entry Layer
The entry layer parses commands, selects the run mode, and handles startup optimization. It usually corresponds to a command-line entry point, an interactive interface entry, or a service entry. This layer should not interpret tool semantics, assemble the full system prompt, or call the model directly. Its output should be a structured runtime context: working directory, session ID, mode (one-shot Q&A, persistent session, or batch processing), log level, and whether certain capability switches are enabled.
Startup optimization also fits in the entry layer. Examples include preloading configuration, checking the runtime environment, initializing logging and metrics, and preparing cache directories. This work is unrelated to how a programming task gets done, yet it determines whether the process starts reliably. By placing it at the entry, the loop layer can assume the environment is ready and avoid repeating cold-start checks on the hot path.
The entry layer also handles mode dispatch. Interactive mode needs to keep session state. One-shot mode should exit quickly. Background or service mode must handle concurrency and timeouts. Dispatch logic is easier to follow in the entry layer than inside the loop, and it keeps the loop from being shaped by runtime form.
Layer 2: Agent Loop Layer
The loop layer is the heartbeat of the system. Its typical form is a controlled while loop. It assembles the current messages and context, calls the model, and parses the response. If the response contains a tool request, the loop executes the tool and writes the result back. Then it moves to the next round, until the model gives a final answer or a termination condition is triggered.
The core value of the loop layer is orchestration, not implementation details. It should treat “call the model” and “execute a tool” as two stable primitives. It should not expand filesystem reads and writes, command execution, or permission prompts inside the loop body. Termination conditions should also be managed in one place: reaching the maximum number of steps, user interruption, permission denial, the model explicitly ending, or an unrecoverable error. Writing these conditions down clearly keeps the loop from becoming an implicit state machine that is hard to reason about.
For readability, the loop layer should keep “single entry, single exit, explicit state.” The message list, current step count, pending tool queue, and cumulative token usage or elapsed time should be observable state. They should not be hidden variables scattered across closures. This gives stable hook points, whether you are debugging an infinite loop or inserting audit logs.
Layer 3: Tool Execution Layer
The tool layer turns the model’s intent into concrete operations: reading files, editing files, searching, running commands, and querying repository information. This layer is the most prone to bloat, so it needs a unified abstraction. A common approach is to define tool descriptions, input validation, executors, and result serialization. The model sees names, parameter schemas, and short descriptions. The runtime works with validated, structured input.
The tool layer should keep descriptions separate from implementations. Descriptions face the model and emphasize clear boundaries and validatable parameters. Implementations face the system and emphasize recoverable errors, truncatable output, and traceable side effects. For example, if a file write fails, the tool should return a structured error instead of throwing an unhandled exception. The loop layer can then treat the failure as context for the next round, rather than aborting the whole session.
Concurrency and idempotency also belong to the tool layer. Searches can run in parallel, while write operations usually need to be serialized or locked. When the same tool is called repeatedly, it must be clear whether re-entry is safe or whether duplicates must be removed. These rules belong near the tool implementation, where they are less likely to be overlooked than inside the loop.
Layer 4: Permission Control Layer
The permission layer decides whether each operation is allowed. It must be independent of tool implementations, or tools could approve their own actions. Permission decisions usually depend on the operation type, path scope, command content, user policy, and cached session authorizations. Read and write operations, operations inside and outside the workspace, and read-only queries versus external network access should follow different policies.
The readability of the permission layer comes from explainable decisions. A denial should not return only a boolean. It should give a reason, such as a path out of bounds, a command containing a dangerous pattern, a missing user authorization, or a policy that forbids network access. These reasons can be shown to the user or written to an audit log. Authorization results can be cached briefly so the same session does not ask again for the same operation. The cache key must include the operation’s semantics, though. Reading one file should not imply permission to edit it.
Placing permissions before tool execution creates a fixed interception point. The loop layer only consumes three outcomes: allow, deny, or confirm. It does not need to understand policy details. When policies change, both the tool code and the loop code can stay stable.
Layer 5: System Prompt Layer
The prompt layer dynamically assembles the instructions and context given to the model. It includes the role setting, capability boundaries, tool usage, a summary of the current repository, user rules, and session constraints. The main risk in this layer is string concatenation scattered throughout the code. A safer approach is to split the prompt into composable fragments: static principles, dynamic environment, tool list, user-added rules, and task-related context. Merge them by priority and control the total length.
The prompt layer should only handle what the model sees, not how execution happens. The tool list can be generated from the tool layer’s descriptions, so the two are not maintained separately. Permission-related constraints can be summarized in the prompt, but actual enforcement remains with the permission layer. This way, even if a user is manipulated into a bad request, the model cannot bypass runtime checks.
Dynamic assembly also needs determinism. The same repository state and configuration should produce a reproducible prompt structure where possible, which makes comparison and debugging easier. When truncating long context, keep the structural markers instead of silently dropping key constraints.
How the Five Layers Work Together
A typical request follows this path. The entry layer generates the runtime context. The prompt layer builds system instructions from that context and the tool descriptions. The loop layer calls the model with those instructions. When the model returns a tool request, the permission layer rules on it first. If the request is allowed, the tool layer executes it and hands the result back to the loop layer, which then decides whether to continue calling the model or end. Each layer depends only on the structured data the previous layer provides, and does not reach into another layer’s internals.
This collaboration also makes debugging layered. Startup failures point to the entry layer. Infinite loops or abnormal step counts point to the loop. Wrong operation results point to tools. Unexpected blocks point to permissions. Model behavior drifting off course points to prompts. When reading code, you do not need to search the whole repository for keywords. You can open the directory for the relevant layer.
Practical Engineering Advice
Align directories with responsibilities. Place the entry, loop, tool, permission, and prompt code each in its own module, and put shared types in a thin common layer. This avoids reverse dependencies, such as the tool directory referencing entry details. Keep interfaces stable. Tools should use a uniform input and output structure, permissions should use a uniform decision object, and prompts should use a uniform fragment protocol. Write tests per layer. Entry tests cover argument parsing, loop tests cover the state machine, tool tests cover side effects and errors, permission tests cover the policy matrix, and prompt tests cover assembly and truncation. Document each layer as well, so new team members can read the code using the same map.
Layering is a means, and clear boundaries are what matter. When a layer starts to know too much about its neighbors’ details, it is time to redraw the boundaries. The complexity of AI coding assistants will keep growing. As long as the entry, loop, tool, permission, and prompt layers each manage their own concerns, the code can stay readable, modifiable, and reviewable.
