How-to guides

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:

RoleDuty
ClientSend the task and receive the result
PlannerList and read files, then author a plan
WorkspaceHandle declared filesystem requests
CoderWrite files and describe the patch
ReviewerShip 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: str

The 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.py

For an offline regression test, replace every live model with ap.replies and provide a scripted client. Keep filesystem tests inside a temporary root.

Stable

On this page