Running another harness
invoke
import { getHarness } from "@agntn/harnesses";
const codex = getHarness("codex");
const result = await codex.invoke("Review this patch for races", {
readOnly: true,
timeoutMs: 60_000,
});
result.command; // "codex"
result.args; // ["exec", "--skip-git-repo-check", "--sandbox", "read-only", "Review this patch for races"]
result.stdout; // whatever Codex printed
result.exitCode; // number, or null when stopped by a timeout or a signal
invoke expands the harness's argument template, spawns the binary with stdio piped, and resolves with the captured output. An exit code other than zero is a result, not an exception. It does throw when the requested mode has no template, before anything is spawned. buildInvocation(prompt, options) gives you the command without running it, invocationError(options) the reason it would be rejected, or null.
Modes
tools defaults to false. That gives an advisor: the harness with its tools removed by a native CLI flag, so the model can only answer. It is not an agent told to behave by prompt wording.
| Option set | Mode | Needs |
|---|---|---|
{} | advisor | noToolsArgs |
{ structured: true } | advisor, JSON output | noToolsJsonArgs |
{ readOnly: true } | full agent in a native read-only sandbox | readOnlyArgs |
{ readOnly: true, structured: true } | same, JSON output | readOnlyJsonArgs |
{ tools: true } | full agent | args |
{ tools: true, structured: true } | full agent, JSON output | jsonArgs |
readOnly implies tools. A harness that lacks the template for the requested mode rejects the call and says which retry would work:
Harness claude has no read-only full agent invocation
Harness codex has no advisor without tools invocation; retry with tools: true to start its full agent
Codex has no advisor because codex exec cannot switch its tools off. OMP has --no-tools, but it only disables the bundled tools, so it doesn't count either. Claude's --tools "" does. harness.invocationModes tells you the six booleans up front, and every harness page lists its templates. Nothing falls back to a wider mode, ever.
Models
const pi = getHarness("pi");
const { models } = await pi.listModels({ search: "gpt-5.4" });
models[0]; // { provider: "openai-codex", id: "gpt-5.4", contextWindow, maxOutputTokens, thinking, images }
await pi.invoke("Review this patch", { model: "openai-codex/gpt-5.4", readOnly: true });
model appends the harness's modelArgs; a harness without them rejects the option. listModels runs the native listing command and normalizes its output; Pi is the one harness that has it today, and it only lists providers with configured authentication. A harness without modelListing rejects the call the same way.
Timeouts and cancellation
timeoutMs and signal work the same on invoke and listModels. Unset or 0 means no deadline. An already aborted signal skips the spawn. Otherwise the first of cancellation or deadline starts cleanup:
- Linux and macOS: the child runs in its own process group;
SIGTERMto the group, thenSIGKILL500 ms later, even if the root already exited. - Windows:
taskkill /T /Fat once, with a 2 s budget for that command.
A stopped result keeps the output captured so far and has exitCode: null. aborted is true when your signal won, timedOut when the deadline did; the first reason wins and later aborts do nothing. Cleanup failures reject the promise.
const controller = new AbortController();
const pending = pi.invoke("Review this change", { tools: true, readOnly: true, signal: controller.signal });
controller.abort();
(await pending).aborted; // true
This is command cleanup, not a sandbox. A descendant that leaves the process group, or outlives an already exited root on Windows, is out of reach. Output pipes inherited by such a descendant do not extend the wait.
In the CLI
harnesses run claude "review this design" # advisor
harnesses run pi --model openai-codex/gpt-5.4 "review" # advisor, chosen model
harnesses run codex --read-only "review this" # native read-only sandbox
harnesses run claude --tools --json "fix lint" # full agent, structured output
harnesses models pi gpt-5.4 --json
--timeout is in seconds. A timed out run exits 124, otherwise the harness's own exit code passes through.