Harness Coder: a ready-made coding agent
Coder is a ready-made Harness capability that gives an agent a whole coding workshop: tools to read, edit and list files, run a shell, search with ripgrep, and hand work to a sub-agent.
Last updated: 28 Sep, 2026 · Pydantic AI 2.51
A capability is a bundle of tools, instructions and behaviour that an agent takes on through capabilities=[...]. The MCP capability from MCP servers: tools from another program was one. The Pydantic AI Harness is a library of them, and Coder is several combined into a complete coding agent.
Installing the harness
The harness is its own package. The [coder] extra also installs ripgrep, which the search tool runs:
pip install "pydantic-ai-harness[coder]==0.36.0"What the Coder gives the model
Coder streams its requests, so the stand-in is a stream_function, as in the streaming lesson. Printing the tool names shows what the model can call:
from pydantic_ai import Agent
from pydantic_ai.models.function import AgentInfo, FunctionModel
from pydantic_ai_harness import Coder
async def peek(messages, info: AgentInfo):
print([tool.name for tool in info.function_tools])
yield "ok"
Agent(FunctionModel(stream_function=peek), capabilities=[Coder("repo")]).run_sync("hi")Seven tools. read_file, write_file, edit_file, list_files and grep work inside the folder you name, repo here; shell runs commands; and delegate_task hands a sub-task to a child agent.
repo, but shell runs any command on your machine, with no allowlist, and what it starts can keep running after the run. Give Coder a folder you are happy for it to change, and a container for any work you do not trust.A bug to fix in the repo
The refund helper forgets that percent is out of 100:
def refund_amount(total, percent):
"""Money back for a partial refund."""
return total * percentA real model reads the request and decides for itself what to open and run. So that you can see each tool at work, the stand-in follows a fixed plan, one step per tool result it has seen:
import json
from pydantic_ai.models.function import DeltaToolCall
PLAN = [
("list_files", {}),
("read_file", {"path": "refunds.py"}),
("edit_file", {"path": "refunds.py", "old_text": "total * percent", "new_text": "total * percent / 100"}),
("shell", {"command": "python3 -c 'from refunds import refund_amount; print(refund_amount(80, 25))'"}),
]async def coder_model(messages, info: AgentInfo):
done = sum(1 for m in messages for part in m.parts if part.part_kind == "tool-return")
if done < len(PLAN):
name, args = PLAN[done]
yield {0: DeltaToolCall(name=name, json_args=json.dumps(args))}
else:
yield "Fixed refund_amount: it divides by 100 now, so 25% of 80 is 20.0."Fixing the bug end to end
The run walks the plan: find the file, read it, edit the exact text, then run a check:
agent = Agent(FunctionModel(stream_function=coder_model), capabilities=[Coder("repo")])
result = agent.run_sync("refund_amount(80, 25) returns 2000. Fix it.")
for message in result.all_messages():
for part in message.parts:
if part.part_kind == "tool-call":
print("call ", part.tool_name)
elif part.part_kind == "tool-return":
print(" back", str(part.content).splitlines()[0])
print(result.output)
print(open("repo/refunds.py").read())What each tool did
- list_files found the one file,
refunds.py. - read_file returned it with a line count.
- edit_file replaced the exact text and reported the edit.
- shell ran the check, which printed
20.0, the right partial refund. - The last lines are the file on disk, now dividing by 100.
Swapping in a real coding model
With a hosted model the code is one line and the model plans the steps itself. This needs a key, so it is shown, not run:
agent = Agent("anthropic:claude-sonnet-4-5", capabilities=[Coder("repo")])
result = agent.run_sync("Find out why refund_amount(80, 25) returns 2000 and fix it.")The seven tools, the instructions and the limits are the same; only the choices change hands.
Capabilities to build your own agent
| Capability | What it adds |
|---|---|
FileSystem, Shell | The pieces Coder is made of, each with its own settings |
Planning | A task list the agent keeps up to date as it works |
SubAgents | The delegate_task tool that hands work to a child agent, like Multi-agent delegation: agents that call agents |
Memory | Notes the agent keeps between sessions |
InputGuardrail, ToolGuardrail, OutputGuardrail | Checks on the prompt, the tool calls and the output |
SpendLimits | A budget across runs, refusing the next request once it is spent |
Each is added the same way, to capabilities=[...], so a coding agent that remembers and plans is [Coder("repo"), Planning(), Memory(...)]. Researcher is a second complete agent, for web research with sources.
Where you use the Coder
- A bot that opens a pull request to fix a small, well-described bug.
- A batch job that applies the same edit across many files in a repo.
- An assistant that runs a test suite and reports what failed.
Related
- Previous: MCP servers: tools from another program
- Next: Testing agents with pytest and TestModel
- See also: Multi-agent delegation: agents that call agents
- Reference: Harness Coder
- Change the plan so
greplooks forpercentbefore reading the file. - Give the stand-in a plan that edits text which is not in the file, and read what
edit_fileanswers. - Build
Coder("repo")with a freshrepofolder of your own and a different bug.
Slow is fine. Stopping is the only problem.