Skip to content
Quick Guides
Quick Guides

Harness orchestration

Dispatch Claude Code, Codex, Gemini CLI, or OpenCode as typed workers with cost and turn caps.

Use a harness as a step inside a regular reasoner. The harness spawns Claude Code, Codex, Gemini CLI, or OpenCode, gives it tool access, enforces a cost cap, and returns a schema-validated object -- not free-form text.

from pydantic import BaseModel
from agentfield import Agent, HarnessConfig

app = Agent(
    node_id="migrator",
    harness_config=HarnessConfig(provider="claude-code", model="sonnet"),
)

class MigrationPlan(BaseModel):
    sql_statements: list[str]
    rollback_steps: list[str]
    risk_assessment: str

@app.reasoner()
async def plan_migration(description: str) -> dict:
    # Harness reads the schema, writes SQL, validates against the Pydantic model
    result = await app.harness(
        f"Analyze the database schema and produce a migration plan for: {description}",
        schema=MigrationPlan,
        max_budget_usd=1.00,   # hard cost cap
        max_turns=20,
    )

    if result.is_error:
        return {"error": result.failure_type, "message": result.error_message}

    plan: MigrationPlan = result.parsed
    return {
        "sql": plan.sql_statements,
        "rollback": plan.rollback_steps,
        "cost_usd": result.cost_usd,
        "turns": result.num_turns,
    }

# Swap providers per-call -- Codex for test generation, Gemini for big refactors
@app.reasoner()
async def write_tests(module: str) -> dict:
    result = await app.harness(
        f"Write a comprehensive test suite for {module}.",
        provider="codex",
        model="o4-mini",
        max_turns=40,
    )
    return {"output": result.text, "cost_usd": result.cost_usd}

app.run()
import { Agent, type HarnessConfig } from "@agentfield/sdk";
import { z } from "zod";

const harnessConfig: HarnessConfig = { provider: "claude-code", model: "sonnet" };
const app = new Agent({ nodeId: "migrator", harnessConfig });

const MigrationPlan = z.object({
  sqlStatements: z.array(z.string()),
  rollbackSteps: z.array(z.string()),
  riskAssessment: z.string(),
});

app.reasoner("plan_migration", async (ctx) => {
  // Harness reads the schema, writes SQL, validates against the Zod model
  const result = await app.harness(
    `Analyze the database schema and produce a migration plan for: ${ctx.input.description}`,
    {
      schema: MigrationPlan,
      maxBudgetUsd: 1.0,   // hard cost cap
      maxTurns: 20,
    },
  );

  if (result.isError) {
    return { error: result.errorMessage };
  }

  const plan = MigrationPlan.parse(result.parsed);
  return {
    sql: plan.sqlStatements,
    rollback: plan.rollbackSteps,
    costUsd: result.costUsd,
    turns: result.numTurns,
  };
});

// Swap providers per-call -- Codex for test generation, Gemini for big refactors
app.reasoner("write_tests", async (ctx) => {
  const result = await app.harness(
    `Write a comprehensive test suite for ${ctx.input.module}.`,
    { provider: "codex", model: "o4-mini", maxTurns: 40 },
  );
  return { output: result.text, costUsd: result.costUsd };
});

app.serve();
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
    "github.com/Agent-Field/agentfield/sdk/go/harness"
)

type MigrationPlan struct {
    SQLStatements  []string `json:"sql_statements"`
    RollbackSteps  []string `json:"rollback_steps"`
    RiskAssessment string   `json:"risk_assessment"`
}

func main() {
    a, err := agent.New(agent.Config{
        NodeID:        "migrator",
        Version:       "1.0.0",
        AgentFieldURL: "http://localhost:8080",
        HarnessConfig: &agent.HarnessConfig{Provider: "claude-code", Model: "sonnet"},
    })
    if err != nil {
        log.Fatal(err)
    }

    a.RegisterReasoner("plan_migration", func(ctx context.Context, input map[string]any) (any, error) {
        description := fmt.Sprintf("%v", input["description"])

        // Harness reads the schema, writes SQL, validates against the struct
        var plan MigrationPlan
        schema, _ := harness.StructToJSONSchema(plan)

        result, err := a.Harness(ctx,
            "Analyze the database schema and produce a migration plan for: "+description,
            schema, &plan,
            harness.Options{MaxBudgetUSD: 1.0, MaxTurns: 20}, // hard cost cap
        )
        if err != nil {
            return nil, err
        }
        if result.IsError {
            return map[string]any{"error": result.ErrorMessage}, nil
        }

        return map[string]any{
            "sql":      plan.SQLStatements,
            "rollback": plan.RollbackSteps,
            "turns":    result.NumTurns,
        }, nil
    })

    // Swap providers per-call -- Codex for test generation, Gemini for big refactors
    a.RegisterReasoner("write_tests", func(ctx context.Context, input map[string]any) (any, error) {
        module := fmt.Sprintf("%v", input["module"])
        result, err := a.Harness(ctx,
            "Write a comprehensive test suite for "+module+".",
            nil, nil,
            harness.Options{Provider: "codex", Model: "o4-mini", MaxTurns: 40},
        )
        if err != nil {
            return nil, err
        }
        return map[string]any{"output": result.Text()}, nil
    })

    a.Serve(context.Background())
}

What this gives you

  • A multi-turn harness looks like a regular function call -- input goes in, validated object comes out.
  • Cost and turn caps prevent runaway spending. The agent stops cleanly when either is hit.
  • failure_type distinguishes timeouts, crashes, schema-validation failures, and API errors.

Next