Controllable AI Coding Assistants: Engineering Rules, Work Modes, and Runtime Guardrails

    AI coding assistants often fail not because they are insufficiently intelligent, but because each session starts without durable project knowledge and runs without hard boundaries. This article explains how to make assistant-driven development controllable by writing persistent project rules, separating exploration, implementation, and review modes, and adding runtime guardrails such as hooks, reusable skills, and role-specific agents. It also provides a practical rollout sequence and metrics for measuring whether the assistant follows team standards and keeps changes minimal.

    Many teams think of an AI coding assistant as a stronger form of autocomplete: give it a requirement, and it edits files, runs commands, and submits the result. Real collaboration usually works the other way around. An assistant does not inherently know repository boundaries, coding standards, test thresholds, or which files must never be touched. A session starts from a blank context, and whatever it reads becomes the basis for its next decision. Without external constraints, scope-creep refactors, style regressions, and opportunistic “optimizations” of dependency manifests are not occasional mistakes. They are the natural result of stateless reasoning combined with tool calls.

    Where Control Breaks Down

    A typical AI coding runtime can be abstracted into a minimal loop: call the model, choose a tool, feed the observation back into the model, and move to the next round. This “reason and act” approach is flexible, but it lacks a predefined pipeline. The decision itself is often only a small part of the system. A large amount of code goes into permissions, context compression, tool wrappers, and safety guardrails. In other words, the model is good at finding the next step within the current window. It is not responsible for remembering every rule your team agreed on last week.

    There are four common failure modes.

    1. Scope inflation. You ask for changes to three files, and the assistant follows references and rewrites surrounding modules as well.
    2. Standards drift. You emphasized quote style, naming, and error handling in the conversation. After context compression or a new session, the verbal instructions disappear and old habits return.
    3. Forbidden zones breached. Dependency manifests, generated code, migration scripts, and secret samples are treated as “optimization targets.”
    4. Missing acceptance. The feature appears to run, but there are no tests and no explanation of the diff. Reviewers cannot determine whether the change is minimal.

    The real engineering task is therefore not “make the model smarter.” It is to ensure that every loop starts with stable, reviewable project knowledge and that execution is stopped by permissions and checkpoints.

    Write Rules as Persistent Context

    Advice given in a chat has the same lifetime as the current context window. Once the context is compressed, the session changes, or another person takes over, that advice is lost. Important rules must live in the repository, be injected into system-side context before the first user message, and be reloaded from disk after context compression. You can call this a project rules file. Place it at the repository root or layer it by directory, and have tooling load it at session start.

    Rules should be short, executable, and verifiable. Avoid slogans. A practical structure uses five fixed blocks:

    • What the project is: service boundaries, primary languages, and the entry points for running and testing. Explain this in one or two paragraphs without pasting large source excerpts.
    • Allowed and prohibited actions: explicitly limit changes to task-relevant paths. Prohibit changes to dependency lock files, generated directories, credentials, and production configuration unless the task explicitly authorizes them.
    • Style and interfaces: naming, error codes, log fields, and comment language. Each item should be catchable by static checks or tests where possible.
    • Definition of done: tests must be added or updated, risks explained, and untouched areas justified. Opportunistic refactoring is not allowed.
    • Escalation conditions: when requirements are ambiguous, data must be deleted, public contracts must change, or a cross-module rewrite is needed, stop and present a plan before writing to disk.

    Example structure (illustrative, not configuration syntax for any specific product):

    1. # Project Rules
    2. Scope: Modify only paths listed in the task; do not commit unrelated formatting.
    3. Prohibited: Dependency lock files, generated code, and secret samples unless explicitly authorized.
    4. Style: Use double quotes for strings; public errors must include error codes and observable fields.
    5. Done: Relevant tests pass; behavior changes are explained; the diff remains minimal.
    6. Uncertainty: Output a plan and impact surface first; wait for confirmation before changing code.

    Rules should be reviewed along with code. Outdated rules are worse than no rules, because the model will confidently follow an agreement that no longer applies. Large repositories can use directory-level overrides: the root defines global prohibitions, and subdirectories define interface contracts for that module. Personal preferences should not be placed in team rules, or private habits become constraints for everyone.

    Verbal additions are still useful, but they should handle only the current exception. Any requirement that will recur should be written back into the rules file afterward. This is the boundary between “it listens once” and “it understands the project in every session.”

    Three Work Modes, Not One Instruction

    An unconstrained loop mixes exploration, implementation, and cleanup into the same round, so the assistant guesses while editing. A safer approach splits collaboration into three modes and enforces transitions through rules or task templates.

    Exploration mode is read-only. The assistant may search symbols, read interfaces, and trace call chains, but it may not write files or modify dependencies. The output is an impact surface: which paths will change, which tests will fail, and what remains uncertain. This mode is specifically designed to prevent impulsive large refactors.

    Implementation mode writes in small steps. Complete one verifiable slice at a time: write the test or contract first, then change the implementation, then run checks for that slice. If the diff suddenly grows, return to exploration mode instead of continuing to expand in the same round.

    Review mode checks the result. Compare tests, static checks, diff scope, and rollback points against the definition of done. If style regressions or out-of-scope files appear, reject the change. Do not “optimize while reviewing” in the review round.

    The three modes can use the same tools; the difference is permissions. Exploration is read-only by default. Implementation may write to allowlisted paths. Review may run tests and generate explanations, but by default it does not change business code. Permissions are harder than prompts. Prompts are diluted by compression; permissions remain enforced at runtime.

    Three Runtime Guardrails

    A rules file alone still cannot stop impulsive tool calls. Add three guardrails outside the loop, corresponding to “when to interrupt,” “how to reuse correct practices,” and “who performs specialized work.”

    Event hooks insert deterministic checks at critical points instead of asking the model again. For example, block writes to non-allowlisted paths; reject repository resets, force pushes, or migration deletion before a command runs; and mark the session as failed if tests were not run or the diff exceeds a limit at the end. Hook decisions should be based on paths, exit codes, and rules, not on the model’s self-report. The model may say “I only changed three places,” but the hook should use the actual diff.

    Reusable skills capture practices the team has already validated as steps, avoiding reinvention each time. Examples include “add a contract test for an API,” “add a configuration item in the standard way,” or “write an error log without leaking private data.” A skill should define inputs, prohibitions, and a completion signal, and cite clauses in the rules file. A skill is not another free agent; it is an instruction sheet that narrows the action space. New skills should be reviewed to prevent dangerous commands from personal scripts becoming institutionalized.

    Dedicated agents split responsibilities so that one session is not designer, implementer, and reviewer at the same time. A planning agent produces only a plan and file list. An implementation agent may touch only paths on that list. A review agent checks against standards and test results and returns pass or reject, with no write permission by default. Agents should hand off structured state rather than dumping the entire chat transcript. A handoff should include at least the objective, allowed paths, decisions made, open questions, and required checks. Even if a single context is compressed, task state survives in the handoff artifact rather than only in one conversation round.

    The division of labor can be summarized in one sentence: rules files provide persistent knowledge, hooks provide hard blocks, and skills and agents provide repeatable division of work. If any one is missing, the other two can be bypassed. Documentation without enforcement still lets the model write to forbidden zones. Enforcement without skills leads to repeated mistakes inside the boundary. Agents without shared rules invent their own standards.

    A Practical Rollout Order

    Do not start with a complex multi-agent setup on day one. Tighten controls according to the cost of failure, from highest to lowest.

    Step one: write a one-page rules file with forbidden zones, test commands, and the definition of done. Validate it with one real small change. After compression or a new session, check whether the forbidden zones are still respected. If verbally stated rules disappear, they were never in persistent context.

    Step two: separate exploration and implementation. Do not enter the write phase without a written impact surface. If the impact surface exceeds the agreed number of files or diff lines, require human confirmation.

    Step three: add hooks for writes and dangerous commands. Start with the most painful categories: dependency lock files, secrets, generated directories, and destructive version-control commands. Block messages should return specific paths and rule clauses so the model or a person can correct the action, rather than issuing a vague refusal.

    Step four: turn repeated tasks into skills, then consider a read-only review agent. When review fails, return the task to the implementation agent with the failed checks. Do not edit code directly in the review session, which would mix responsibilities again.

    To judge whether the system is working, do not look at how impressive a single demo is. Look at whether these metrics improve: the proportion of out-of-scope files decreases, the same standards issues stop recurring, average diff size shrinks, tests are actually run before delivery, and manual rework rounds decline. These are quantifiable and independent of any specific model brand.

    Context Compression and Knowledge Boundaries

    Long tasks will inevitably compress history. Compression keeps a summary, not all of your prohibitions. Design for the assumption that anything not written into rules files, skills, or handoff documents may disappear in the next round. Secrets, production data, and customer text should not be pasted into rules just to “help it understand.” Rules should describe how to handle such information, for example: “Logs must not contain ID numbers; use placeholders.” For long-term background, reference documentation paths inside the repository and let tools read them on demand. Do not stuff an entire repository’s documentation into the first prompt and crowd out the code that actually needs to change.

    Likewise, accept the model’s knowledge boundaries. Framework versions, internal middleware, and private interfaces cannot be guessed from common sense. Rules should specify authoritative sources: rely on interface definitions in the repository, and stop and ask when there is a conflict. Stopping when uncertain is better for engineering outcomes than confidently refactoring a public contract.

    What Humans Still Own

    Engineering constraints are not meant to cancel review. Humans own whether the goal is worth pursuing, whether the plan can be rolled back, and whether abnormal paths are acceptable. The assistant owns fast execution within the allowlist and must provide evidence: what changed, why other files were left untouched, and what the test results are. When the assistant starts discussing whether to upgrade the entire module “while we are here,” the correct action is to switch back to exploration mode, not to reward its enthusiasm.

    The core of making an AI coding assistant controllable is not a longer prompt. It is a set of constraints that evolve with the code: project rules injected before the session, mode switching across exploration, implementation, and review, and runtime guardrails made of hooks, skills, and dedicated agents. Rules make the assistant understand the project. Permissions keep it inside boundaries. Acceptance makes delivery checkable. With these three in place, it can move from a tool that randomly edits code to a collaborator that follows team standards.