Subtractive Design for Coding Agents: Less Is More

    Coding agents often fail on long tasks as systems grow more complex. This article explains a subtractive design approach: a thin perceive–decide–act–verify loop, a minimal toolset, dense context, runtime permissions, externalized task state, and executable verification.

    Why subtractive design

    Over the past two years, coding agents have become far more capable, yet many teams find that as the system grows more complex, long-running tasks become harder to control. Typical failures include calling the wrong tool, flooding context with irrelevant information, and changing more code than intended. Agents that stay stable share a trait: restrained architecture. They do not stack many specialized modules. They expose a small, clear set of capabilities and let the model decide within those bounds.

    Call this subtractive design. The test for every new component is whether its benefit outweighs the complexity and uncertainty it introduces.

    The basic loop: perceive, decide, act, verify

    A coding agent is essentially a loop. Each turn, the runtime gives the model the current context. The model emits the next action—read a file, search code, run a command, edit content, or ask the user to confirm. The runtime executes the action, appends the result to context, and repeats until the model judges the task complete.

    Sketch:

    1. def agent_loop(task, tools, model, max_steps=50):
    2. context = [system_prompt(), user_message(task)]
    3. for step in range(max_steps):
    4. reply = model.generate(context, tools=tools.schemas())
    5. if reply.is_final_answer():
    6. return reply.text
    7. for call in reply.tool_calls:
    8. if not tools.is_allowed(call):
    9. context.append(tool_error(call, "operation not authorized"))
    10. continue
    11. result = tools.run(call)
    12. context.append(tool_result(call, result))
    13. raise RuntimeError("step limit reached")

    The runtime should stay thin. It only calls the model, validates and runs actions, and writes results back. Almost all business judgment stays with the model. A shorter loop is more predictable and easier to debug. A step cap is essential: it stops infinite retries and wasted compute.

    Converge the toolset: keep capability boundaries clear

    Tools are the agent’s only channel to the outside world. More tools mean a larger choice space at every step and a higher error rate. Tool descriptions also consume context. Subtractive design compresses the set to a minimum that still covers most coding work, typically:

    • Read file contents, including line ranges
    • Local replace in a file
    • Pattern search over code and text
    • Run commands in a controlled environment and return output
    • List directory structure

    Each tool should have a single responsibility, structured I/O, and explicit failure messages. For replace, require the original snippet rather than letting the model rewrite the whole file. If the snippet is missing or not unique, fail immediately; a full rewrite can silently overwrite unrelated code.

    Sketch:

    1. def replace_in_file(path, old, new):
    2. text = read(path)
    3. count = text.count(old)
    4. if count != 1:
    5. return error(f"expected 1 match, got {count}")
    6. write(path, text.replace(old, new))
    7. return ok()

    If a capability is already available through a general command, do not wrap it as a dedicated tool. Every new tool should answer: what problem cannot existing tools solve?

    Context management: density first

    Context windows are finite; more information spreads attention thinner. Three principles:

    First, read on demand, do not preload. Do not dump the whole repo into context. Let the model search, then read relevant slices. That saves space and reduces noise.

    Second, compress history in time. When the conversation grows long, summarize finished steps into short conclusions: key decisions, files changed, open issues. Drop verbose command output. The goal is enough information for the next step, not a full process log.

    Third, isolate exploratory subtasks. For noisy work such as tracing calls in an unfamiliar module, use a separate sub-session and return only the conclusion. Intermediate noise should not pollute the main thread.

    Permissions and safety: enforce constraints at runtime

    Agents that edit files and run commands carry real risk. Subtractive design does not rely on the model being “smart enough.” It relies on runtime constraints, layered by risk:

    • Read-only operations can usually run immediately
    • Writes and deletes can require confirmation or stay inside a designated working directory
    • Network access, system configuration changes, and privilege changes are denied by default and need explicit authorization
    • All command execution is logged for audit

    Least privilege applies. Boundaries are enforced by the runtime, not only by a prompt asking the model to behave. Prompts can guide; they cannot replace mechanism.

    Externalize task state: make progress visible

    On long tasks, models drift or forget completed steps. A common pattern is a short checklist the model updates as items finish. The list lives in context as plain text: it helps the model self-correct and lets a human inspect progress. No extra state machine is required—structured text is enough, which fits subtractive design.

    Verification loop: trust executable results

    Reliability for coding work depends on objective feedback. A mature pattern is to have the agent run build, typecheck, or tests after edits, and treat exit codes and output as evidence. “I am done” is not evidence; passing tests is.

    A practical detail: feed back a trimmed failure, keeping location, error type, and key stack frames. Full logs scatter attention; a compact error is easier to act on.

    Implementation checklist

    When evaluating or building a coding agent, check:

    1. Can the tool count still drop, and does each tool have a unique reason to exist?
    2. Do tool errors help the model locate the problem?
    3. Can any context be compressed or moved out?
    4. Are writes and high-risk operations constrained at runtime?
    5. Is completion based on executable verification?
    6. Does the loop have a step cap and a cost cap?

    Outlook

    As reasoning and long-context ability improve, some orchestration that used to be hand-written will move into the model, and the runtime may get even thinner. Permission control, audit, and verification will not disappear; they will matter more. Subtractive design, in essence, is: deterministic work to the program, judgment to the model, with a clear, controllable boundary between them.