DocsHarnesses

Build a harness

A harness is one entry under the harness key. Build it in Settings → Harnesses, which writes the block for you, or by hand:

~/.config/podcode/config.json
{
  "harness": {
    "ship-it": {
      "description": "Plan, build, test, review",
      "chain": [
        "plan",
        "implement",
        { "agent": "test", "on_failure": "implement" },
        "review"
      ],
      "max_hops": 8
    }
  }
}

Defining a harness only makes it appear in the picker. Nothing chains until someone selects it, so a harness defined globally has no effect on unrelated projects.

Stages

A chain entry is either a bare agent id, or an object that says more about that stage.

JSON
"chain": [
  "implement",
  { "agent": "test", "on_failure": "implement" },
  {
    "agent": "react-runner",
    "on_failure": "implement",
    "acceptance": { "agent": "react-runner", "check": "ui-renders", "max_retries": 4 }
  }
]
FieldMeaning
agentThe agent that runs this stage. Must be registered with mode primary or all.
on_failureWhere this stage hands back when it can't finish. Must itself be a stage in the same chain.
acceptanceA gate that must pass before the chain advances.

Selecting the harness starts at chain[0]. Reaching the last stage and answering cleanly ends the run.

Acceptance gates

An on_failure target says where to go when a stage gives up. An acceptance gate says the chain doesn't get to advance on the stage's own say-so.

JSON
{ "agent": "test", "check": "tests-pass", "max_retries": 4 }
  • agent — who evaluates the check.
  • check — one of tests-pass, typecheck-clean, or ui-renders. The first two run your project's test and typecheck scripts, so those scripts have to exist; the third requests the dev-server URL.
  • max_retries — how many times this stage may be sent back before the run halts. Defaults to 4.

When the gate fails, the chain routes back to the stage's on_failure target and the stage's retry counter increments. Retries are counted per run and per stage, so the same agent appearing twice in one chain is counted separately, and a new run starts fresh.

The loop also gives up early when it stops converging: a gate that fails twice with identical evidence has proven the remaining budget is worthless, and the run halts rather than spending it.

Hops

max_hops bounds the handoffs allowed per user turn, across both the chain and the agent_handoff tool. The default is 4.

Keep it small. Each hop re-enters the agent loop with a fresh step ceiling, so the ceiling multiplies — eight hops is eight full turns of budget. Size it for the happy path plus a couple of rounds of repair: the built-in React JS chain is five stages and allows twelve.

When the budget runs out, the tool tells the model to finish in place and the chain stops advancing. It isn't lost — your next message resumes the run with a fresh budget.

Per-stage models

A stage runs on whatever model its agent declares:

~/.config/podcode/config.json
{
  "agent": { "review": { "model": "anthropic/claude-sonnet-5" } },
  "harness": { "review-loop": { "chain": ["implement", "review"] } }
}

This is how you run the cheap stages on a local or self-hosted model and pay for a frontier model only where judgement is expensive. If the model fails to resolve, the stage runs on the session's current model and logs a warning — a bad override degrades the turn rather than stranding the chain.

Harness-only agents

A stage built for one chain isn't a persona anyone should pick from the dropdown. Mark it with the owning harness id:

~/.config/podcode/agent/ship-scaffold.md
---
description: Creates the project skeleton the later stages build on.
mode: primary
harness: ship-it
permission:
  bash:
    "*": allow
  agent_handoff:
    "*": allow
---

Create the skeleton. Stop when it typechecks clean.

What ownership does:

  • Forces mode: primary. An owned agent must be a valid stage and must never be a task target; a declared subagent is overridden with a warning.
  • Hides it from the general lists — Settings → Agents, the composer picker, the schedules picker, and the TUI's /agent autocomplete. It appears instead under Settings → Harnesses → your harness → Agents, which is also where you create and edit one.
  • Nothing more. The runtime still sees an ordinary primary agent, so any other chain may list it as a stage. Ownership is visibility, not exclusivity.

Two edges worth knowing. If the named harness doesn't exist — you hand-deleted it, or never saved the draft — the agent behaves as a general one again and is shown with a muted harness: <id> (missing) pill. And deleting a custom harness detaches the writable agents it owned, so they reappear as general agents rather than becoming unreachable.

Agent ids stay global, so a prefix still matters. harness: keeps a stage out of the picker; it does not namespace the id.

Confirming each hop

There's no config key for this. Set the permission and every handoff prompts:

~/.config/podcode/config.json
{
  "permission": [
    { "permission": "agent_handoff", "pattern": "*", "action": "ask" }
  ]
}

The default is allow, so a chain runs unattended — bounded by max_hops, visible in the transcript, interruptible with Stop, and confined to agents you could have picked yourself.

Next

  • Reference — keys, defaults, halt reasons, limits.