Guide · AI and GenAI
Coding Agent Architecture: Harness, Tools and Sandboxes
24 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- Coding agent architecture is the normal code around a model, the AI program that reads and writes text. That code, the harness, runs a loop, picks what the model reads, runs its tools and checks its work.
- The model only writes text. A tool is one action it may ask for, like read this file or run the tests, and the harness carries it out.
- A repository, the project folder tracked in Git (the tool that records every change), is far too big for the model to read at once. So the harness searches first and shows 100-line windows of files.
- A sandbox is a sealed, throwaway copy of the project where commands run. Give it no secrets, no network by default and a person who approves risky steps and every merge.
- Beginners can stop after the sandbox section. Engineers then get the 2024 numbers for harness-only changes, a git command that catches weakened tests, and the skipped-test trap.
What goes wrong when you ask a model to fix a bug?
It guesses, because a model on its own cannot open your files, run your tests or see what its change broke. Coding agent architecture is the code you build around the model so it can do those things safely.
Picture a common setup. A billing team keeps its Python service in one repository of about 1,800 files and 210,000 lines. The setup is made up, but the problem is real.
Ticket BILL-412 arrives. When a customer uses a discount code, the invoice charges GST, India's goods and services tax, on the full price. An item costs Rs 1,000 with a Rs 100 discount, so 18 percent GST should be Rs 162, but the invoice shows Rs 180.
An engineer fixes it by hand. A search for gst gives 23 matches, and in invoice_totals.py the tax is worked out before the discount. Swapping the order, adding a test that expects Rs 162 and running all 412 tests takes about 45 minutes (made up).
The team sees about 30 small tickets like this a week, so they want an agent that drafts each fix for an engineer to review. The first try pastes the ticket into a chat window. The confident answer edits tax_utils.py, a file that does not exist.
The model could not see the repository, open a file or run a test. Everything in this guide is about giving it those abilities with limits.
Which words do you need first?
You need six words. Each gets a plain meaning and an example from the billing bug.
Model. The model is the AI itself: a program that reads text and writes text back, often called a large language model (LLM). It cannot open files or run code on its own.
Harness. The harness is all the normal code around the model. It runs the loop, decides what the model sees, carries out its requests and stops the run. The Python program the team is about to write is its harness.
Tool. A tool is one action the model may ask for, like read_file or run_tests. The model writes the tool's name and inputs, and the harness runs it and returns the result. Example: search_code for gst returns 23 lines.
Sandbox. A sandbox is a closed-off place where the agent's commands run, so mistakes cannot reach real systems. Example: a throwaway copy of the repository inside a container, a lightweight box that limits what a program can see.
Context window. The context window is the most text the model can read in one request. It is measured in tokens, small pieces of text about the size of a short word. The billing repository is far too big to fit.
Patch. A patch, also called a diff, is a short record of exactly which lines changed. The agent's output for BILL-412 is a 9-line patch. An engineer reviews it like a pull request, a request to merge a change after review.
What is coding agent architecture?
Coding agent architecture is the set of parts around the model that let it change real code safely: a loop, a context builder, tools, a sandbox and a test runner. The model sits in the middle, but most of the design work happens around it.
Think of a skilled mason building a wall. The mason is the model: the skill and the judgement. The scaffolding is the loop and the sandbox, a safe place to stand and work.
The sharp tools are the agent's tools. The plumb line, a weighted string that shows a true vertical, is the test suite. The site supervisor is the permission check and the engineer who reviews the patch.
Where the picture breaks: a mason remembers yesterday's work. The model remembers nothing between requests, so the harness must put everything it needs into each prompt, the text sent to the model in one request.
Here is the team's design. Start at the ticket and follow the arrows.
The billing team's coding agent architecture
Drag to exploreNotice that the model box only asks. Every action, limit and check sits in a box the harness controls.
Diagram connections:
- Bug ticket BILL-412 to Agent loop (Starts a run)
- Agent loop to Context builder (New round)
- Context builder to Model (Prompt)
- Model to Tool runner (Tool request)
- Tool runner to Sandbox (Runs inside)
- Sandbox to Run notes and log (Output)
- Run notes and log to Agent loop (Next round)
- Agent loop to Test runner (Model says done)
- Test runner to Engineer reviews the patch (All tests pass)
- Test runner to Run notes and log (Tests failed)
- Bug ticket BILL-412: GST charged before the discount
- Agent loop: Asks the model again, or stops the run
- Context builder: Picks the rules, ticket and latest results
- Model: Reads the prompt and asks for one tool
- Tool runner: Read, search, edit and run tests
- Sandbox: Throwaway copy, no secrets, no network
- Run notes and log: Every request and result, in order
- Test runner: Final check before a person sees it
- Engineer reviews the patch: A person merges, never the agent
How does the coding agent loop work?
The coding agent loop works in rounds: the model asks for one tool, the harness runs it in the sandbox, and the result is added to the notes. It repeats until the model says it is done and the tests pass, or a round limit is reached.
Here are the rounds for BILL-412. Pause and predict: which round is the strongest proof that the fix is real?
Round 1: search
The model asks for search_code with the text gst. It gets 23 matching lines from 6 files.
Round 2: read
It reads invoice_totals.py, lines 40 to 140, and sees compute_gst run before apply_discount.
Round 3: write a test
It adds test_gst_after_discount, which expects Rs 162 of GST on the Rs 1,000 item with a Rs 100 discount.
Round 4: prove the bug
It runs only the new test. The test fails with 180, so the bug is real.
Round 5: fix
It edits invoice_totals.py so GST is worked out after the discount.
Round 6: check the fix
The new test runs again and passes.
Round 7: check everything
It runs the full test suite, every test in the project. All 413 pass: the 412 old ones and the new one.
Round 8: done
The model says it is done. The harness runs the tests once more itself, then builds the patch.
Round 4 is the answer. A test that fails before a change and passes after it shows the change fixed this bug, not just that nothing crashed.
You can check the arithmetic in the test: 18 percent of Rs 1,000 is Rs 180, and 18 percent of Rs 900 is Rs 162. With a small test like this, pytest, a Python test runner, prints 1 failed before the fix and 1 passed after it.
Here is the loop in Python, kept small. The helper functions are placeholders: ask_model sends the notes and tool list to the model, and the others do what their names say. TOOLS maps each tool name to a function that runs inside the sandbox. If you do not read Python, skip to the paragraph after the code.
def fix_ticket(ticket, max_rounds=30):
notes = [build_start_context(ticket)] # rules, repo map, ticket
for round_number in range(max_rounds):
reply = ask_model(notes, TOOLS)
if reply.kind == "done":
result = run_tests() # the harness checks, not the model
if result.passed:
return make_patch() # a diff for a person to review
notes.append("Tests failed: " + result.summary)
continue
if reply.tool not in TOOLS:
notes.append(f"Error: there is no tool called {reply.tool}")
continue
try:
output = TOOLS[reply.tool](**reply.args) # runs inside the sandbox
except Exception as err: # bad inputs, timeouts
output = f"Error: {reply.tool} failed: {err}"
notes.append(shorten(output, max_lines=60))
return hand_to_person(ticket, notes)What just happened? The limit of 30 rounds stops a confused agent from running all night. When the model says done, the harness runs the tests itself instead of trusting the claim.
Bad requests turn into error notes, not crashes. We ran this loop with a stand-in model on Python 3.13. Without the try block, a tool called with a wrong input name stopped the whole run with a TypeError.
Which tools does a coding agent need?
A coding agent needs four kinds of tools: read, search, edit and run. A small, sharp set is easier for the model to use well, because every extra tool is one more way to pick wrong.
| Tool | What it does | Example on the bug | Limit the harness adds |
|---|---|---|---|
| list_files | Shows folder and file names | Lists the billing folder | At most 200 names |
| search_code | Finds lines with some text | Finds the 23 lines with gst | At most 50 results |
| read_file | Shows part of a file, with line numbers | invoice_totals.py, lines 40 to 140 | At most 100 lines |
| edit_file | Swaps one exact piece of text | Moves the GST step after the discount | Project files only; Python files must still parse |
| run_tests | Runs some or all tests | Runs test_gst_after_discount | 5-minute limit, 60 lines of output |
Notice what is missing: a shell, a tool that runs any command you type. OWASP, a non-profit that publishes security guidance, says in its Top 10 for LLM applications to avoid open-ended tools such as shell commands where possible. So the team offers run_tests and a short list of allowed commands.
Which edit format should the agent use?
For most changes, search and replace is the safest edit format, because the harness can check each edit before saving. The edit format is how the model describes a change. There are three common choices:
- Whole file: the model writes out the entire new file. It is simple, but slow for big files, and the model can quietly drop lines.
- Line range: the model says replace lines 52 to 55 with new text. It is short, but line numbers shift after every edit.
- Search and replace: the model quotes an exact old snippet and its new version. The harness refuses the edit if the old snippet is missing or appears twice.
Here is a search and replace tool in plain Python; we ran it on Python 3.11, 3.12 and 3.13. It refuses paths outside the project, unclear snippets and edits that would leave a Python file unreadable.
import ast
from pathlib import Path
WORKSPACE = Path("workspace").resolve()
def edit_file(path, old_text, new_text):
target = (WORKSPACE / path).resolve() # follows ../ and links
if not target.is_relative_to(WORKSPACE):
return "Error: you can only edit files inside the project folder."
if not target.is_file():
return f"Error: there is no file called {path}."
source = target.read_text()
matches = source.count(old_text)
if matches != 1:
return f"Error: old_text found {matches} times. Copy more lines so it is unique."
new_source = source.replace(old_text, new_text)
if target.suffix == ".py":
try:
ast.parse(new_source) # syntax check only
except SyntaxError as err:
return f"Error: edit NOT applied. Line {err.lineno}: {err.msg}"
target.write_text(new_source)
return f"Edited {path}"
WORKSPACE.mkdir(exist_ok=True)
(WORKSPACE / "totals.py").write_text("tax = 0\nfee = 0\n")
print(edit_file("totals.py", "= 0", "= 1"))
print(edit_file("totals.py", "tax = 0", "tax = (0"))
print(edit_file("../secrets.txt", "x", "y"))
print(edit_file("total.py", "tax = 0", "tax = 18"))
print(edit_file("totals.py", "tax = 0", "tax = 18"))
# Output on Python 3.11 to 3.13:
# Error: old_text found 2 times. Copy more lines so it is unique.
# Error: edit NOT applied. Line 1: '(' was never closed
# Error: you can only edit files inside the project folder.
# Error: there is no file called total.py.
# Edited totals.pyWhat just happened? The first edit was refused because = 0 appears twice, and the second because it broke the file. The count check is the core: zero matches means the model misremembered the code, and two or more means the edit is unclear.
The path check uses resolve(), which follows ../ and links before comparing. In our test, a link inside the workspace that pointed at a system file was refused too.
How does a coding agent find the right code in a big repository?
A coding agent finds the right code by searching and reading small pieces, not by loading the whole repository. The harness keeps the context window for what matters now: the rules, the ticket and the latest few results.
A 2023 benchmark paper built 2,294 tasks from real issue reports on GitHub, a site that hosts code, in 12 popular Python projects. Its Table 1 reports those codebases average about 3,010 non-test files and 438,000 non-test lines.
More code is not more help. In that paper's simple retrieval setup, a keyword search called BM25, one model solved 1.96 percent of tasks when its context was capped at 13,000 tokens. At 50,000 tokens, with more retrieved code, the same model solved 1.22 percent.
Follow the diagram from the whole repository down to one focused prompt.
From a whole repository to one focused prompt
Drag to exploreNotice that each step throws away code the model does not need for this ticket, so the prompt stays small.
Diagram connections:
- Whole repository to Repo map (Outline built by code)
- Repo map to Search for gst (Pick where to look)
- Search for gst to Open a 100-line window (Open the best match)
- Open a 100-line window to Shorten older results (Older results cut)
- Shorten older results to Focused prompt (Sent to the model)
- Whole repository: 1,800 files, about 210,000 lines
- Repo map: Folder, file and function names only
- Search for gst: 23 matches in 6 files, capped at 50
- Open a 100-line window: invoice_totals.py, lines 40 to 140
- Shorten older results: Only the latest few stay in full
- Focused prompt: Rules, ticket and what matters now
Here are the habits that make this work. The last three come from a 2024 paper on agent-computer interfaces:
- A repo map: a short outline of folders, files and function names, built by normal code. The model can guess where to look before reading anything.
- A project notes file: a small file that says how to run tests and what to avoid. The team's says never to edit database migration files, the scripts that change the database layout.
- Capped search results: at most 50 results; when there are more, the agent is told to search more narrowly.
- Small file windows: a 100-line window solved more tasks than a 30-line window or the whole file.
- Shortened old results: the last five results stay in full, and each older one shrinks to a single line.
When a long run still fills the window, the harness can compact the notes. The model summarises the older rounds, and that summary replaces them. The ticket and the rules always stay in full.
How does a coding agent check its own work?
A coding agent checks its work by running real checks, mainly tests, not by asking the model whether it feels done. The harness runs those checks itself and passes a change to a person only when they succeed.
The benchmark paper scores fixes the same way. A task counts as solved only if two groups of tests pass. Fail-to-pass tests failed before the fix and must pass after it; pass-to-pass tests must keep passing, which shows nothing else broke.
The team's harness uses a ladder of checks, from the cheapest to the most expensive:
Syntax check on every edit
The edit tool refuses any change that Python cannot parse, so broken files never reach the tests.
Reproduce the bug
A new test must fail before the fix, like test_gst_after_discount expecting Rs 162.
Pass the new test
After the fix, the same test must pass.
Run the full suite
All 412 old tests must still pass, so nothing else broke.
Lint and type checks
A linter flags likely mistakes, such as a name that is never defined. A type checker spots the wrong kind of value, like text where a number should be.
A person reviews the patch
An engineer reads the 9 changed lines and the new test before anything is merged.
Tests only catch what they test. With no tests around discounts, the agent could pass every check and still be wrong. That is why a new failing test comes first, and why a person still reads the patch.
Why does a coding agent need a sandbox?
A coding agent needs a sandbox because it runs commands chosen by a model, and any of them could be wrong or harmful. The sandbox makes sure a bad command breaks only a throwaway copy, not your laptop, your secrets or production.
Honest mistakes are not the only risk. The agent also reads text written by others, such as packages (ready-made code libraries). A comment in one could hide an instruction to print every password.
OWASP calls this indirect prompt injection: the model takes in instructions from outside sources such as files or websites. Its 2025 guidance says it is unclear whether any prevention is foolproof. So you also limit what a fooled agent can do.
The team's sandbox follows five rules:
- A fresh copy of the repository for every ticket, thrown away after the run.
- No secrets: no production passwords, API keys (secret codes that let a program use a service) or customer data. Tests use fake data.
- Network off by default, except an internal package mirror, a local copy of approved packages.
- Time and size limits: every command stops after 5 minutes, with 2 CPU cores and 4 GB of memory.
- No push rights: the agent produces a patch, and only a person can merge it.
A plain container shares the kernel, the core of the operating system, with the host. If code finds a flaw in that shared kernel, it can reach the host. For code you do not trust, add a stronger wall.
One option is gVisor. Its docs describe an application kernel that intercepts a program's system calls (requests like open this file) and handles them itself, instead of passing them to the host kernel. Another option is a small virtual machine, a whole pretend computer with its own kernel.
Which actions need permission?
Actions that are hard to undo, or that reach outside the sandbox, need a person's yes. Reading and editing inside the workspace can run at once, because a patch is easy to throw away. Start at the top of the chart.
The permission check for each action
Drag to exploreNotice that the check runs in code before the action, so a fooled model cannot talk its way past it.
Diagram connections:
- Model asks for an action to On the blocked list?
- On the blocked list? to Refuse with an error note (Yes)
- On the blocked list? to Read or workspace edit? (No)
- Read or workspace edit? to Run in the sandbox (Yes)
- Read or workspace edit? to Allowed command? (No)
- Allowed command? to Run in the sandbox (Yes)
- Allowed command? to Ask an engineer to approve (No)
- Ask an engineer to approve to Run in the sandbox (Approved)
- Ask an engineer to approve to Refuse with an error note (Denied)
- Model asks for an action: For example: run_tests or install a package
- On the blocked list?: Reading secret files, deleting Git history
- Refuse with an error note: The model reads why next round
- Read or workspace edit?: Easy to see and easy to undo
- Allowed command?: Tests, the linter, the type checker
- Ask an engineer to approve: New packages, network access
- Run in the sandbox: Result goes back into the notes
Does the harness matter as much as the model?
Often, yes. In a 2024 study that kept one model fixed, harness changes alone moved the share of tasks solved from 10.3 percent to 18.0 percent.
The study is the agent-computer interface paper. It tested on a 300-task subset of the 2023 benchmark and changed one harness feature at a time. The table shows the results from its Table 3.
| Harness setup | Tasks solved | About how many of 300 |
|---|---|---|
| Full design: 100-line file window, capped search, edit with a lint check, last 5 results kept | 18.0% | 54 |
| No edit command | 10.3% | 31 |
| Whole file shown instead of a 100-line window | 12.7% | 38 |
| 30-line window instead of 100 lines | 14.3% | 43 |
| Edit command without the lint check | 15.0% | 45 |
| Full history instead of the last 5 results | 15.0% | 45 |
Two lessons stand out. Without a proper edit command the agent solved about 23 fewer tasks. Showing the whole file did worse than a 100-line window, which fits the earlier finding that more code can hurt.
The same paper also compared harness and model directly, in its main results (Table 1). With one model, a simple retrieval setup with no loop solved 2.67 percent of the 300 tasks, and the agent harness 18.00 percent. A different model in the same harness solved 13.00 percent.
A second 2024 paper dropped the free-roaming loop entirely. It used three fixed phases: find the place to change, write candidate fixes, and check them with tests. Its revised version reported 32.00 percent (96 of the same 300 tasks) at low cost, the best of the open-source agents it compared.
That paper used a different model from the table's study, so its 32.00 percent and the 18.0 percent do not compare directly. What both show is that the design around the model moves the score.
The team saw the same pattern when it replayed 30 old tickets as a test set (made up). The first harness, with whole-file edits and no test loop, fixed 9. With search and replace edits, capped search and the check ladder, the same model fixed 19.
A newer, pricier model on the old harness fixed only 12 of the 30. A stronger model still helps. But a model swap is easy to try, while harness problems stay hidden until you read the runs.
A git command that catches weakened tests
To flag a patch that edits, deletes or renames an existing test, stage the agent's work with git add -A and compare it with the main branch. New test files do not show up, so the agent's own new test raises no false alarm.
# Run in the agent's copy of the repository (main = your base branch)
git add -A && git diff --cached --name-only --diff-filter=MDR main -- tests/
# Output on git 2.43, one line per touched test file:
# fix plus a new tests/test_gst.py -> (no output)
# an existing test edited -> tests/test_invoice.py
# an existing test deleted -> tests/test_refunds.py
# an existing test renamed -> tests/test_refunds_old.py (new name)
# Second check: new or changed conftest.py and pytest settings files
git add -A && git diff --cached --name-only main -- '*conftest.py' pytest.ini pyproject.toml setup.cfg tox.ini
# a new tests/conftest.py that skips tests -> tests/conftest.py
# a pytest.ini change that drops a test -> pytest.ini
# fix plus a new tests/test_gst.py -> (no output)The git add step matters. Without it, git diff never shows new untracked files. In the filter, M means modified, D deleted and R renamed.
The skipped-test trap
A passing exit code is not proof. In our test on pytest 9.1.1, a new tests/conftest.py, a pytest setup file, marked every test as skipped. The bug was still there, yet pytest printed 1 skipped and exited with code 0, the signal for success.
The first git command misses this, because conftest.py is a new file. It also misses a pytest.ini settings change that leaves out the failing test: in our test, pytest printed 2 passed, 1 deselected and exited with code 0. The second command catches both. Also treat any skipped test as a failure, and check that as many tests ran as before.
What are common coding agent failure modes?
Six failures are worth designing for: the wrong file, edit loops, gaming the tests, invented code, getting lost in a long run and commands that never finish. Each one has a fix in the harness, not just in the prompt.
- Wrong file: the agent fixes old_tax.py, a copy nobody uses. Fix: a failing test first, since a change in the wrong file cannot make it pass.
- Edit loops: it sends the same broken edit five times. Fix: clear error notes, and stop after three identical failed actions.
- Gaming the tests: it changes the expected GST from 162 to 180. Fix: flag every change to an existing test for a person.
- Invented code: it calls a helper like round_paise that does not exist. Fix: a lint check for undefined names, then the tests.
- Lost in a long run: after 25 rounds, it forgets the discount part of the ticket. Fix: keep the ticket at the top of every prompt.
- Hanging commands: it starts a web server that never exits. Fix: a time limit on every command.
You can check the invented-code fix yourself. A file that calls round_paise still parses, so a syntax check passes it. Running flake8, a Python linter, with --select=F821 prints invoice_totals.py:3:12: F821 undefined name 'round_paise' and exits with code 1.
The team saves every run as a log, a record of each round. Most of its harness fixes came from reading failed runs, not from rewording the prompt.
How do you explain coding agent architecture in a system design interview?
Explain it as a loop around a model, then spend most of your time on the parts around it: tools, context, sandbox and checks. Walk through one concrete ticket to keep it real.
Pin down the job
Say what the agent does and does not do. For example: draft fixes for small tickets in one repository, with a person merging every change.
Draw the loop
Model asks, harness acts, result goes into the notes. Stop when the harness's own test run passes, or at a round limit.
Name the tools and edit format
Read, search, edit and run tests, with search and replace edits and no general shell.
Handle a big repository
A repo map, capped search, small file windows and shortened old output.
Add the sandbox and permissions
A throwaway copy, no secrets, no network by default, and a person for risky actions.
Show how you verify
A failing test first, then the full suite, lint and type checks, flags on weakened tests, then a human review.
Say how you would measure it
Replay past tickets. Track how many get fixed, the cost per ticket and how often reviewers reject a patch.
Questions people ask
- What is an agent harness?
- An agent harness is the normal code around a model that turns it into an agent. It runs the loop, builds each prompt, runs tools in a sandbox and decides when to stop. The model chooses the next step, and the harness acts and enforces every limit.
- Is a coding agent the same as code autocomplete?
- No. Autocomplete suggests the next few lines while you type. A coding agent works through a whole task in a loop. It searches the repository, edits files and runs tests, then hands you a patch to review.
- Do coding agents need internet access?
- Usually not for a bug fix, and they are safer without it. Most fixes need only the repository and its installed packages. If packages must be downloaded, allow only a trusted package mirror, so a fooled agent cannot send code or secrets out.
- Is a container a good enough sandbox for a coding agent?
- It is a reasonable start for your own trusted code, but not the strongest wall. Containers share the host computer's kernel, so a flaw there can let code break out. For untrusted code, add gVisor, which handles system calls in its own kernel, or use a small virtual machine.
- Can a coding agent merge its own changes?
- It can be built to, but it should not merge into important branches without a person. Let it produce a patch and run every check, then leave the merge to an engineer. OWASP advises human approval for high-impact actions like this.
- Should I use a bigger model or build a better harness?
- Start with the harness. In a 2024 study with one model held fixed, harness changes alone moved the share of 300 tasks solved from 10.3 to 18.0 percent. Replay a fixed set of past tickets before and after each change to see what helped.
- How can I tell if a coding agent weakened a test?
- Compare the agent's work with the main branch. After git add -A, the command git diff --cached --name-only --diff-filter=MDR main -- tests/ lists existing test files it edited, deleted or renamed. Also flag new or changed conftest.py and settings files such as pytest.ini. Treat skipped tests as failures and compare how many tests ran: in our test, pytest 9.1.1 exited with code 0 when every test was skipped.
Sources and further reading
- Yang et al. (2024), Agent-Computer Interfaces Enable Automated Software Engineering, arXiv 2405.15793; main results in Table 1, ablation results in Table 3
- Jimenez et al. (2023), Can Language Models Resolve Real-World GitHub Issues?, arXiv 2310.06770; codebase sizes in Table 1, retrieval lengths in Table 2
- Xia et al. (2024), Demystifying LLM-based Software Engineering Agents, arXiv 2407.01489 (v2, October 2024)
- OWASP Top 10 for LLM Applications 2025: LLM06:2025 Excessive Agency
- OWASP Top 10 for LLM Applications 2025: LLM01:2025 Prompt Injection
- gVisor documentation: What is gVisor?