Why AI Coding Agents Stay Readable: A Five-Layer Breakdown of Entry Points, Loops, Tools, Permissions, and Prompts

    An AI coding agent that reads code, edits files, runs commands, and answers questions is not just a model Q&A wrapper—it's an execution framework built from cooperating subsystems. This article breaks down such agents into five layers: entry point, agent loop, tool execution, permission control, and system prompts. For each layer, you'll learn its core responsibilities, typical design patterns, common pitfalls, and practical TypeScript examples, so you can build agents that are easier to read, test, and maintain.

    1. Why Break an AI Coding Agent into Five Layers

    An AI coding agent that can read code, modify files, execute commands, and answer questions is essentially not a simple “model Q&A interface,” but an execution framework completed by multiple cooperating subsystems.

    After a user inputs a request, the system typically goes through the following process:

    1. Parse command-line arguments or interactive requests;
    2. Determine the current run mode and working directory;
    3. Build the system instructions and context required by the model;
    4. Call the model to get the next decision;
    5. Parse the tool calls returned by the model;
    6. Check whether these operations have execution permission;
    7. Perform file reads, file edits, or command execution;
    8. Feed tool results back to the model;
    9. Keep looping until the task is completed or the user aborts it.

    If all of this logic is crammed into one giant function, the code quickly develops several problems: entry logic gets tangled with business logic, permission checks scatter everywhere, tool implementations become hard to test, prompt changes may break the execution flow, and failures are hard to locate.

    A clearer implementation therefore splits the system into five layers:

    Layer Typical Modules Core Responsibility
    Entry layer Startup file, argument parsing module Receive input, parse arguments, select run mode
    Agent loop layer Query engine, task scheduling module Drive the round trips between model calls and tool execution
    Tool execution layer Tool definitions, tool registry, executors Translate model intent into concrete operations
    Permission control layer Permission policies, approval module Decide whether an operation is allowed
    System prompt layer Prompt templates, context builders Organize rules, background, and task constraints

    These five layers are not just a way to sort files—they form a clear chain of control. The entry layer handles “how the system starts,” the loop layer handles “how the task progresses,” the tool layer handles “what to do concretely,” the permission layer handles “whether it can be done,” and the prompt layer handles “what the model should base its decisions on.”


    2. Layer 1: The Entry Layer Converts External Input into Run State

    The entry layer is the boundary between the user and the system. It should not handle complex model calls or file operations directly; it should focus on the startup-phase preparation.

    1. What the Entry Layer Typically Does

    A complete entry module usually handles:

    • Reading command-line arguments;
    • Recognizing interactive mode, one-shot task mode, or resume mode;
    • Determining the current working directory;
    • Loading necessary configuration;
    • Initializing logging, terminal output, and interrupt handling;
    • Creating the runtime context the agent needs;
    • Handing control over to the agent loop.

    For example, the entry logic can be abstracted as:

    1. async function start(args: string[]) {
    2. const options = parseArguments(args)
    3. const workspace = resolveWorkspace(options)
    4. const config = loadConfig(options, workspace)
    5. const runtime = createRuntime({
    6. workspace,
    7. config,
    8. output: createOutputChannel(),
    9. })
    10. return runAgent(runtime, options.task)
    11. }

    The key point: the entry function is only responsible for “preparing the run environment” and “starting the main flow.” It should not decide whether a given file may be modified, nor implement search or command execution itself.

    2. Why the Entry Layer Should Stay Thin

    The thinner the entry layer, the easier the system is to test and extend. Adding a new run mode later only requires a new startup configuration—no changes to the agent core.

    Common run modes include:

    • Interactive conversation;
    • Auto-execution after receiving a single task;
    • Code analysis only, no modifications;
    • Resuming a previously unfinished task;
    • Running with restricted permissions.

    If the entry layer is tightly coupled with the agent loop, every new mode introduces a flood of conditional branches. When the entry layer only produces a unified set of run parameters and the core loop decides behavior via configuration, the structure stays much more stable.


    3. Layer 2: The Agent Loop Is the Control Center of the System

    The agent loop coordinates the model and the tools, and it is the most important runtime mechanism of an AI coding agent.

    Models rarely complete complex tasks in a single pass. They may first list a directory, read a few files, propose changes, run tests, and then keep adjusting based on test results. The system therefore needs a loop of “model decision → tool execution → result feedback.”

    1. A Generic Agent Loop

    It can be expressed with the following pseudocode:

    1. async function runAgent(runtime, task) {
    2. const messages = buildInitialMessages(runtime, task)
    3. while (!isFinished(messages)) {
    4. const response = await callModel(messages)
    5. if (response.type === "final") {
    6. return response.content
    7. }
    8. if (response.type === "tool_call") {
    9. const result = await executeToolCall(runtime, response.call)
    10. messages.push(response.message)
    11. messages.push(toToolMessage(response.call, result))
    12. }
    13. }
    14. }

    This flow involves several key concepts:

    • messages: the context visible to the model;
    • callModel: ask the model for the next decision;
    • tool_call: the model asks the system to run a tool;
    • executeToolCall: enter the permission-check and execution stage;
    • isFinished: decide whether the task is complete.

    2. The Loop Layer Should Not Implement Tool Details

    The loop layer’s job is “scheduling,” not “executing.” It may know that the model returned a file-read request, but it shouldn’t know which low-level API the file read uses.

    If the loop layer directly contains branches like this:

    1. if (name === "read_file") {
    2. // read the file
    3. } else if (name === "run_command") {
    4. // run the command
    5. } else if (name === "edit_file") {
    6. // edit the file
    7. }

    the loop function will balloon as tools multiply. A better approach is a tool registry:

    1. const tools = {
    2. read_file: readFileTool,
    3. search_text: searchTextTool,
    4. edit_file: editFileTool,
    5. run_command: commandTool,
    6. }
    7. async function executeToolCall(runtime, call) {
    8. const tool = tools[call.name]
    9. if (!tool) {
    10. return { ok: false, error: "unknown tool" }
    11. }
    12. return tool.execute(runtime, call.input)
    13. }

    Now the loop layer only cares about “find the tool and run it,” while the tool layer owns the concrete business logic.

    3. What Else the Loop Layer Must Handle

    A production-ready agent loop also needs to deal with:

    • Retries after failed model calls;
    • Tool execution timeouts;
    • User-initiated interrupts;
    • Context length management;
    • Limits on consecutive failures;
    • Truncation of oversized tool results;
    • Three terminal states: task completed, task failed, task canceled.

    Context management deserves special attention. Tool results keep getting appended to the message list; without compression or trimming, long tasks can blow up the context. Common techniques include keeping key summaries, truncating repetitive output, passing large file contents in segments, and generating intermediate state snapshots at phase transitions.


    4. Layer 3: The Tool Layer Turns Model Intent into Real Operations

    The model only produces text or structured call requests; it is the tool layer that actually touches the file system, the terminal, and project resources.

    1. Tools Should Have Clear Input/Output Contracts

    A well-designed tool usually includes:

    • A unique name;
    • A parameter structure;
    • Validation rules;
    • A functional description;
    • An execution function;
    • A result format;
    • An error-handling strategy.

    For example, a file-read tool can be abstracted as:

    1. const readFileTool = {
    2. name: "read_file",
    3. validate(input) {
    4. return typeof input.path === "string"
    5. },
    6. async execute(runtime, input) {
    7. const path = resolveInsideWorkspace(runtime.workspace, input.path)
    8. const content = await runtime.fs.readFile(path)
    9. return {
    10. ok: true,
    11. path: input.path,
    12. content,
    13. }
    14. },
    15. }

    The tool layer should avoid exposing raw low-level exceptions to the model; instead, convert them into stable, understandable results. Missing files, insufficient permissions, invalid paths, and unrecognized encodings should all be mapped to structured messages with an error type and handling suggestions.

    2. Distinguish Read-Only Operations from Side-Effecting Operations

    Tools can be grouped by risk:

    • Read-only tools: reading files, searching content, listing directories;
    • Low-risk write tools: generating temp files, writing caches;
    • High-risk write tools: modifying source code, deleting files;
    • External-impact tools: running commands, network access, changing system state.

    This classification helps the permission layer apply different policies. For example, read-only operations can run automatically, file modifications may require user confirmation, and operations affecting external systems may be denied by default.

    3. Design Tool Output for Both the Model and Debugging

    Tool results must be easy for humans to read and easy for the model to understand. They should include:

    • Whether the operation succeeded;
    • Which resources were actually affected;
    • Key results;
    • Failure reasons;
    • Suggested next steps;
    • Necessary structured fields.

    The system should also log tool calls—tool name, parameter summary, execution time, result status, and error messages. But logs must avoid leaking sensitive information such as secrets, tokens, and full environment variables.


    5. Layer 4: The Permission Layer Is a Security Boundary, Not an Add-On

    An AI coding agent can perform real operations, so permission control is a core architectural component. Permission decisions should not rely on prompts alone, nor be left entirely to the model’s self-restraint.

    1. Permission Checks Should Exist Independently

    A basic permission flow can be expressed as:

    1. async function executeWithPermission(runtime, call) {
    2. const decision = await checkPermission({
    3. tool: call.name,
    4. input: call.input,
    5. workspace: runtime.workspace,
    6. })
    7. if (decision.type === "deny") {
    8. return { ok: false, error: "operation denied" }
    9. }
    10. if (decision.type === "ask") {
    11. const approved = await requestUserApproval(call)
    12. if (!approved) {
    13. return { ok: false, error: "operation rejected by user" }
    14. }
    15. }
    16. return executeToolCall(runtime, call)
    17. }

    The permission layer should evaluate at least:

    • Operation type;
    • Target path;
    • Whether the target lies within the current workspace;
    • Whether sensitive directories are involved;
    • Whether existing content would be overwritten;
    • Whether external resources would be accessed;
    • Whether explicit user confirmation is required.

    2. Path Security Cannot Rely on String Checks Alone

    For example, simply checking whether a path starts with the workspace string does not fully prevent path traversal. A more robust approach:

    1. Normalize the path;
    2. Resolve symbolic links;
    3. Convert both the workspace and the target path to absolute paths;
    4. Verify the target is genuinely within the allowed range;
    5. Apply extra restrictions to special directories.

    Likewise, command execution should not be controlled by a string blacklist alone. Command arguments may contain redirections, pipes, subcommands, and environment variable expansion, so prefer structured parameters, sandboxed runtimes, and explicit allow policies.

    3. Permission Decisions Must Be Explainable

    When asking for confirmation, the UI should clearly state:

    • What operation will be performed;
    • What the target is;
    • What impact it may have;
    • Whether files will be modified or external resources accessed;
    • How to reject the operation.

    “Allow or not” is only the outcome; “why confirmation is needed” matters just as much. An explainable permission mechanism reduces mistakes and makes troubleshooting easier.


    6. Layer 5: The System Prompt Layer Defines the Model’s Behavioral Boundaries

    The prompt layer is not about concatenating a few strings. It organizes system rules, task goals, environment information, and tool descriptions into a context the model can understand.

    1. Prompts Are Usually Composed of Multiple Parts

    A complete system prompt may include:

    • Role definition;
    • Task goals;
    • Workspace information;
    • Current run mode;
    • Available tools and their limits;
    • File modification conventions;
    • Testing and validation requirements;
    • Safety rules;
    • Output format requirements.

    It can be abstracted as:

    1. function buildSystemPrompt(context) {
    2. return [
    3. renderRoleRules(context),
    4. renderWorkspaceInfo(context.workspace),
    5. renderToolRules(context.tools),
    6. renderSafetyRules(context.permissions),
    7. renderTaskConstraints(context.task),
    8. ].filter(Boolean).join("\n\n")
    9. }

    This segmented design is far easier to modify, test, and reuse than maintaining one giant string.

    2. Separate Static Rules from Dynamic Context

    Static rules are relatively stable, for example:

    • Read the relevant content before modifying it;
    • Never fabricate test results;
    • State clearly when information is uncertain;
    • Never perform unauthorized high-risk operations.

    Dynamic context changes with the task, for example:

    • Current working directory;
    • Files already modified;
    • Current permission mode;
    • The most recent tool result;
    • Constraints added by the user.

    Separating the two avoids regenerating large amounts of fixed content on every prompt build and helps you pinpoint whether a problem comes from rules or from context.

    3. Prompts Cannot Replace Programmatic Constraints

    Prompts can guide the model but do not constitute a real security boundary. Even if a prompt says “never access files outside the workspace,” the tool execution layer must still validate paths; even if a prompt says “ask the user before executing,” the permission layer must still forcibly block high-risk operations.

    A reliable system uses defense in depth:

    • Prompts provide guidance;
    • Tool parameters provide constraints;
    • The permission module makes decisions;
    • The execution environment provides isolation;
    • The logging system provides auditability.

    7. How the Five Layers Form a Complete Execution Chain

    Combining the layers above, a typical task flows like this:

    1. User input
    2. ↓
    3. Entry layer: parse arguments, create runtime context
    4. ↓
    5. Prompt layer: build system rules and task context
    6. ↓
    7. Agent loop: call the model and parse the next decision
    8. ↓
    9. Tool layer: locate the tool, validate parameters, prepare execution
    10. ↓
    11. Permission layer: allow, deny, or request confirmation
    12. ↓
    13. Execute tool: read, modify, or run operations
    14. ↓
    15. Tool result returns to the agent loop
    16. ↓
    17. Update context and call the model again
    18. ↓
    19. Output the final result

    The value of this flow is that each layer has a single, focused responsibility. When something breaks, you can locate the issue quickly:

    • Won’t start correctly: check the entry layer;
    • Model stops progressing: check the loop state and context;
    • Tool not recognized: check tool registration;
    • Disallowed operation went through: check the permission layer;
    • Model repeatedly misunderstands the task: check the prompt and context construction;
    • Inaccurate execution results: check tool implementations and result formats.

    8. How to Further Improve Readability and Maintainability

    1. Keep Module Boundaries Stable

    Modules should communicate through explicit data structures rather than sharing mutable global state. A runtime context can uniformly hold the workspace, configuration, logs, file interfaces, and permission policies—but each module should only read the fields it needs.

    2. Write Independent Tests for Each Layer

    At minimum, cover:

    • Entry argument parsing tests;
    • Loop state transition tests;
    • Tool parameter validation tests;
    • Path and permission policy tests;
    • Prompt fragment composition tests;
    • Tool failure and timeout tests;
    • Flow tests after user rejection.

    3. Unify the Error Model

    If different modules return errors in completely different formats, the loop layer’s handling complexity grows. Unify them as:

    1. type Result<T> =
    2. | { ok: true; value: T }
    3. | {
    4. ok: false
    5. code: string
    6. message: string
    7. retryable?: boolean
    8. }

    This lets the system distinguish retryable errors, user rejections, parameter errors, and unrecoverable errors.

    4. Map Logs to Architectural Layers

    Logs should carry a layer identifier, for example:

    1. [entry] mode=interactive workspace=...
    2. [loop] model_request started
    3. [tool] name=read_file
    4. [permission] decision=ask
    5. [tool] result=success duration=...

    This helps with development and debugging, and with auditing and replaying issues in production.


    Conclusion

    An AI coding agent stays readable not because it has little code, but because its responsibility boundaries are clear. The entry layer solves “how to start,” the agent loop solves “how to advance the task,” the tool layer solves “what to execute concretely,” the permission layer solves “which operations are allowed,” and the prompt layer solves “what the model bases its decisions on.”

    Together, these five layers form an understandable, testable, extensible execution chain. For any intelligent application that calls models, operates tools, and handles external resources, this layered approach offers broadly applicable guidance: isolate the entry point first, then stabilize the loop; make tools registry-driven, make permissions independent; modularize the prompts—and use programmatic constraints to guard the real security boundary.