Pydantic AIPydantic AI 2.51 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
29 small wins to finish your pathNext lesson →

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:

Example
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.

The folder is not a sandbox
The file tools stay inside 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:

python
def refund_amount(total, percent):
    """Money back for a partial refund."""
    return total * percent

A 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:

python
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))'"}),
]
python
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:

python
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:

python
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

CapabilityWhat it adds
FileSystem, ShellThe pieces Coder is made of, each with its own settings
PlanningA task list the agent keeps up to date as it works
SubAgentsThe delegate_task tool that hands work to a child agent, like Multi-agent delegation: agents that call agents
MemoryNotes the agent keeps between sessions
InputGuardrail, ToolGuardrail, OutputGuardrailChecks on the prompt, the tool calls and the output
SpendLimitsA 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.
Try it yourself
  • Change the plan so grep looks for percent before reading the file.
  • Give the stand-in a plan that edits text which is not in the file, and read what edit_file answers.
  • Build Coder("repo") with a fresh repo folder of your own and a different bug.

Slow is fine. Stopping is the only problem.