app.harness()
Dispatch multi-turn coding tasks to external coding agents
app.harness()
Dispatch multi-turn coding tasks to external coding agents
Runs a prompt through a multi-turn coding agent (Claude Code, Codex, Gemini CLI, or OpenCode) and returns a structured result. Where app.ai() handles single-turn LLM calls, app.harness() handles tasks that require browsing files, editing code, running tests, and iterating across many turns.
Bring Your Own Credentials: Each provider requires its own API key or binary installed in your environment. See the Harness Providers guide for setup instructions per provider.
Under Active Development: app.harness() is being built as part of Epic
#208. The API shape is stable but some providers and features are still being
rolled out. Check the epic for current status.
Basic Example
from agentfield import Agent, HarnessConfig
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
),
)
result = await app.harness("Fix the auth bug in src/auth.py")
print(result.text)Parameters
Prop
Type
Returns: HarnessResult
HarnessConfig
Set agent-level defaults at construction time. All fields can be overridden per-call.
from agentfield import Agent, HarnessConfig
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code", # Required — no implicit default
model="sonnet",
max_turns=50,
max_budget_usd=2.0,
),
)Prop
Type
HarnessResult
Prop
Type
Supported Providers
| Provider | Integration | Required Credential |
|---|---|---|
claude-code | claude_agent_sdk (in-process Python SDK) | ANTHROPIC_API_KEY |
codex | CLI subprocess codex exec --json | CODEX_API_KEY |
gemini | CLI subprocess gemini --output-format json | GEMINI_API_KEY |
opencode | CLI subprocess opencode run | Provider-dependent |
Claude Code runs in-process via the claude_agent_sdk package. The other three providers run as CLI subprocesses and must be installed separately. See Harness Providers for installation and credential setup.
Examples
from agentfield import Agent, HarnessConfig
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
),
)
result = await app.harness(
"Add input validation to the login endpoint in src/routes/auth.py",
cwd="/my/project",
)
print(result.text)
print(f"Completed in {result.num_turns} turns, cost ${result.cost_usd:.4f}")from pydantic import BaseModel
from agentfield import Agent, HarnessConfig
class BugFix(BaseModel):
files_changed: list[str]
summary: str
tests_added: bool
breaking_change: bool
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
),
)
fix = await app.harness(
"Fix the race condition in src/worker.py and add regression tests",
schema=BugFix,
cwd="/my/project",
max_turns=80,
)
# fix.parsed is a validated BugFix instance
print(fix.parsed.files_changed) # ["src/worker.py", "tests/test_worker.py"]
print(fix.parsed.tests_added) # True
print(fix.parsed.breaking_change) # Falsefrom agentfield import Agent, HarnessConfig
# Agent defaults to claude-code
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
),
)
# Override to codex for this specific call
result = await app.harness(
"Refactor the database layer to use async/await throughout",
provider="codex",
model="o3",
max_turns=100,
tools=["Read", "Write", "Edit", "Bash"],
max_budget_usd=5.0,
cwd="/my/project",
)
print(result.text)from pydantic import BaseModel
from agentfield import Agent, HarnessConfig
class IssueFixResult(BaseModel):
files_changed: list[str]
summary: str
tests_added: bool
app = Agent(
node_id="issue-resolver",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
max_turns=150,
),
)
@app.reasoner
async def fix_issue(issue: dict) -> dict:
"""Resolve a GitHub issue by dispatching a coding agent."""
result = await app.harness(
f"Fix: {issue['title']}\n\n{issue['description']}",
schema=IssueFixResult,
cwd=issue["repo_path"],
tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
)
if result.is_error:
return {"error": result.error_message, "resolved": False}
return {**result.parsed.model_dump(), "resolved": True}from agentfield import Agent, HarnessConfig
app = Agent(
node_id="coder",
harness_config=HarnessConfig(
provider="claude-code",
model="sonnet",
max_retries=3,
max_budget_usd=3.0,
),
)
result = await app.harness(
"Migrate the test suite from unittest to pytest",
cwd="/my/project",
)
if result.is_error:
print(f"Run failed: {result.error_message}")
print(f"Completed {result.num_turns} turns before failure")
else:
print(result.text)
print(f"Cost: ${result.cost_usd:.4f} over {result.duration_ms}ms")Under the Hood
Related
- app.ai() - Single-turn LLM calls
- app.pause() - Pause execution for human approval
- app.call() - Call another agent
- HarnessConfig - Full configuration reference
- @app.reasoner - Wrap harness calls in typed reasoners
- Harness Providers guide - Provider setup, credentials, and sandboxing