Skip to content

Harnesses ​

A harness is the coding agent CLI an agent runs in: claude (Claude Code), codex, pi or cursor. Each agent's runtime says which harness, which model, how hard it thinks, and whether it runs in a TUI or headless. One workflow can mix them, so a planner can think hard in its TUI on your Claude plan while a cheap model lists files with nothing to watch.

ts
const planner = await wf.agents.open({
  key: "planner",
  runtime: { alias: "claude", effort: "high" }, // its TUI, on your Claude plan
  pane: { front: true }, // the pane to watch
});
const lister = await wf.agents.open({
  key: "lister",
  runtime: {
    harness: "codex",
    model: "gpt-5.6-sol",
    effort: "low",
    placement: "headless",
  },
});

The planner opens in its TUI, the default, at claude's high effort, and its pane stays open if its turn doesn't end answered. The lister runs codex headless. What each harness needs installed and logged in is on Install.

Harness and model ​

runtime takes an alias, an alias with changes, or the target spelled out:

ts
export const RUNTIMES = {
  // an alias: claude's sonnet, at its default effort
  alias: "claude",
  // an alias at another effort
  thorough: { alias: "codex", effort: "high" },
  // spelled out: the harness, its model in its own words, an effort it lists
  pi: {
    harness: "pi",
    model: "openrouter/deepseek/deepseek-v4.1-flash",
    effort: "low",
  },
  // cursor takes no effort: the variant is the model
  cursor: { harness: "cursor", model: "gpt-5.6-luna-high" },
  // headless: a process per turn, nothing to watch
  quiet: { harness: "codex", model: "gpt-5.6-sol", placement: "headless" },
  // headless claude is per token, and says so
  "metered-claude": {
    harness: "claude",
    model: "sonnet",
    placement: "headless",
    metered: true,
  },
} satisfies Record<string, RuntimeSelection>;
  • The aliases are claude, which is claude's sonnet, and codex, which is codex's gpt-5.6-sol. Neither names an effort, so the harness uses its default.
  • { alias, … } adds a placement, metered or effort to an alias. A harness or model you add must match the alias's, or the open rejects.
  • Spelled out, model goes to the harness as it is, in its own words: claude's sonnet or claude-opus-5-5, codex's gpt-6-luna, pi's {provider}/{model}, a cursor model id. awf doesn't check the name; the harness does.
  • An agent keeps its runtime. Opening the same key again with a different harness, model, effort or placement rejects.

Which model to start with. Start with an alias: "claude" and "codex" are capable general-purpose models, and the workflow reads the same whoever runs it. Spell a model out where a step needs something else: a small, fast one such as codex's gpt-6-luna for a narrow question, as the quick start does, or a stronger one such as claude-opus-5-5 for the hardest step. Model names are the harness's own, so its CLI, not awf, says which your login can use.

Effort ​

effort is how hard the model thinks, named in the harness's own words:

HarnessLevels, lowest first
claudelow, medium, high, xhigh, max
codexlow, medium, high, xhigh, max, ultra
pioff, minimal, low, medium, high, xhigh, max
cursornone: name the variant as the model, gpt-5.6-luna-high

A level the harness doesn't list rejects the open before anything runs:

text
claude has no effort "ultra"; its levels are low, medium, high, xhigh, max

Left out, awf passes none and the harness uses its own default. A listed level can still be one the model doesn't take: codex's levels cover all its models, and a model given one it lacks fails its turn. To change model or effort mid-session, see set in Agents; a cursor TUI agent can't switch.

Subscription and metered ​

awf runs agents on your subscription logins: your Claude plan, your ChatGPT plan, your Cursor account, whatever pi is signed in to. Three rules keep them there.

claude and codex must be logged in with a subscription. When a run opens its first agent on one, awf reads claude auth status or codex login status, and anything but a subscription login refuses the agent:

text
Claude subscription authentication is required (claude.ai login or `claude setup-token`); `claude auth status` reads as metered
Codex subscription authentication is required (ChatGPT login); `codex login status` reads as metered

API keys in your environment refuse the run. awf run won't start while your environment holds a variable that would move an agent to per-token billing:

text
awf: runtime: subscription runtime refused metered credential environment: ANTHROPIC_API_KEY; the run did not start, and nothing of it is kept

They are claude's and codex's API keys and base URLs, such as ANTHROPIC_API_KEY and OPENAI_API_KEY; Configuration lists them all. Unset them in the shell you run awf from. awf also withholds OPENROUTER_API_KEY from every agent, without refusing the run.

Headless claude needs metered: true. claude -p is billed per token even on a subscription login, so awf refuses a headless claude agent unless its runtime gives that consent:

ts
export const unmetered = defineWorkflow({
  meta: {
    name: "unmetered",
    description: "A headless claude agent without metered: true.",
  },
  async run(wf) {
    // rejects: headless claude is billed per token even on a subscription login; set metered: true to run it
    await wf.agents.open({
      key: "summarizer",
      runtime: { harness: "claude", model: "sonnet", placement: "headless" },
    });
    return null;
  },
});

To stay on your plan, run claude in its TUI, the default. codex, pi and cursor need no such flag, headless or not.

What the record says ​

out.json marks each agent's turns subscription, metered or unknown:

Harnesssubscriptionmeteredunknown
claudeevery TUI agentevery headless agent
codexevery agent on OpenAI's modelsa provider other than OpenAI
pithe model's provider signed in with /loginthe provider set up with an API keyneither could be told
cursoralways: nothing cursor reports tells plan usage from on-demand usage

The login checks above are why a claude TUI agent or a codex agent awf opens is never metered; a session you hand the run with awf run --here is read as it is logged in. The run's closing line gives one word for its agents, leaving the unknown ones out: subscription, metered, mixed, or unknown when none was known, then billing known {n}/{m} when some weren't.

metered is what the login says about how tokens are billed, not a bill. What a run's tokens would cost, and what is left of each plan, are on Cost.

Where panes go ​

Each TUI agent gets a tab of its own, named by its key, in the run's Herdr workspace, and the pane closes once the agent is done. The workflow says which agents share a tab with pane, and which one you should look at with front, which brings its tab forward when it opens in your own workspace (--workspace origin), and nowhere else:

ts
const lead = await wf.agents.open({
  key: "lead",
  runtime: "claude",
  pane: { tab: "review", front: true }, // a new tab, the one to look at
});
await lead.run({
  prompt: "Read the change on this branch and plan the review.",
});
const security = await lead.fork({
  key: "security",
  pane: { beside: "lead", side: "right" },
});
const style = await lead.fork({
  key: "style",
  pane: { beside: "security", side: "below" },
});

Where the tabs open and whether panes stay are yours, as you start the run:

sh
awf run review.ts --workspace origin --keep-panes on-failure

--workspace is run, the run's own workspace and the default; origin, the one you typed awf run in; or any other word, the workspace with that label, found or made. --keep-panes is never, the default, on-failure, when the agent's last turn wasn't answered, or always. A kept pane is left with its harness running, released from the run: what you type into it is yours, and awf never closes it. The run keeps both for every attempt, so a run that parks and wakes later places and keeps its panes as it was started to. A run an event starts, with awf emit, takes the defaults: the flags are yours to give when you start a run. A continue may repeat them, or a default where the run had none, but not change them.

A pane that can't be used, such as beside an agent with no pane, never fails the agent: it gets a tab of its own, and the run's closing lines say why. pane on a headless or sandboxed agent rejects the open. Every option and rule is in Where panes go.

awf starts Herdr headless, in a session named awf, and says where to look as the first TUI agent opens:

sh
HERDR_DISABLE_SOUND=1 herdr session attach awf

Catches ​

  • A cursor agent changes your cursor settings. cursor-agent --model saves the model, and its parameters, as your default in ~/.cursor/cli-config.json, headless too. A bare cursor model id then runs at whichever variant was chosen last.
  • pi's login is per provider, so awf can't check it up front: Install.
  • Nudges differ by harness: a claude TUI agent can say it is still on long work with wfa waiting. See Nudges.

Next ​