Build a coding harness
Declare a planner, coder, reviewer, and directory-scoped filesystem service as one protocol.
The reference harness accepts a task, inspects a working directory, edits files, and submits the result for one review. Its five roles are visible in the protocol:
| Role | Duty |
|---|---|
Client | Send the task and receive the result |
Planner | List and read files, then author a plan |
Workspace | Handle declared filesystem requests |
Coder | Write files and describe the patch |
Reviewer | Ship the patch or request one final correction |
The complete executable version lives in
examples/coding_agent.py.
Declare the conversation first
Define the roles, typed messages, and control flow without constructing a
model or opening a directory. The planner loop offers List, Read, and
Ready; the coder loop offers Write and Done; the reviewer chooses Ship
or Fix.
# requires: coding-harness-declarations
import agentsparty as ap
Client, Planner, Workspace, Coder, Reviewer = ap.roles(
'Client', 'Planner', 'Workspace', 'Coder', 'Reviewer'
)
class Task(ap.Message):
text: str
class Plan(ap.Message):
text: str
class Write(ap.Message):
path: str
content: str
class Saved(ap.Message):
path: str
class Ship(ap.Message):
pass
class Fix(ap.Message):
text: strThe full example uses loop, choose, and repeat to make each phase and its
exit visible. Allowance.unfoldings supplies the runtime bound; the prompt does
not decide whether another protocol iteration exists.
Implement Workspace as a Machine
Bind the filesystem role to a handler table keyed by request classes. Resolve every path against an application-owned root before reading or writing.
# requires: coding-harness-machine
from pathlib import Path
import agentsparty as ap
def _resolve(root: Path, rel: str) -> Path | None:
candidate = (root / (rel or '.')).resolve()
if not candidate.is_relative_to(root.resolve()):
return None
return candidate
def workspace_at(root: Path) -> ap.Machine:
return ap.Machine({
Write: lambda request: _write_file(root, request),
})Machine is a protocol boundary, not an operating-system sandbox. The host
still needs a symlink policy, file-size limits, an allowed root, and declared
failure behaviour. A path refinement rejects malformed messages early; the
handler must still enforce the real filesystem boundary.
Bind and run
The cast is the deployment seam. Keep the protocol fixed while swapping live
models for replies, a person for Desk, or a deterministic rule for
Policy.
# requires: coding-harness-session
def session_at(root: Path, model=None) -> ap.Session:
model = model or ap.model('openai:gpt-5.6-luna')
return ap.Session(
protocol,
{
Client: ap.Human(ap.Cli()),
Planner: ap.Agent(model, PLANNER_BRIEF, repair=2),
Coder: ap.Agent(model, CODER_BRIEF, repair=2),
Reviewer: ap.Agent(model, REVIEWER_BRIEF, repair=2),
Workspace: workspace_at(root),
},
allowance=ap.Allowance(unfoldings=12),
)Run the maintained file rather than copying a second full listing from this page:
uv run python examples/coding_agent.pyFor an offline regression test, replace every live model with ap.replies and
provide a scripted client. Keep filesystem tests inside a temporary root.
Related
- Protocols and composition: understand branches and loops.
- Roles, participants, and models: bind
Agent,Human, andMachine. - Test sessions offline: remove provider calls from tests.
- Example catalogue: find smaller focused programs.