agent-harness
Use popular coding agents from your Rust code.
Claude Code, OpenAI Codex, OpenCode, and any OpenAI-compatible model. Call them
from your program instead of opening a terminal. You do not shell out, and you
do not parse each agent's own output format. Every agent returns the same event
stream, so adding an agent means writing a parser into RunEvent — not
teaching your UI another format.
Imported as harness.
Highlights
- No terminal. Run an agent from your code and read its output as typed events, not scraped text.
- Every agent looks the same. Text, reasoning, tool calls, plans, token
usage, and lifecycle all arrive as
RunEvent. - Swap agents without rewriting. Change the constructor. Your loop stays.
- A built-in agent for open models. The
openai-compatiblefeature is not a wrapper. It speaks the chat API and runs the tool loop in Rust. - Add your own agent. Implement
Harnessin your own crate and register it. No fork. - Stable wire format.
RunEventserializes to camelCase JSON, so HTTP, SSE, and IPC transports all emit the same shape.
Features
The CLI adapters are on by default. Everything else is opt-in, so the core crate stays small.
# Claude Code and Codex
= "0.4"
# Add ACP agents and the built-in agent for open models
= { = "0.4", = ["acp", "openai-compatible"] }
# Or take just one adapter
= { = "0.4", = false, = ["claude"] }
| feature | what it adds |
|---|---|
claude, codex |
the CLI adapters (default) |
acp |
any Agent Client Protocol agent — OpenCode, Gemini, Goose |
openai-compatible |
the built-in agent for open models. Pulls an HTTP client and search libraries. |
models-dev |
live model lists from the models.dev catalog |
Getting started
Build an agent. Call run. Read events.
(run hands back a receiver to loop over. start takes a callback instead —
reach for it when you are forwarding events straight onto a socket and an
intermediate hop would be waste. An adapter implements only start; every
harness gets run for free.)
use ;
let = new.run?;
for event in events
Name what you mean; the rest defaults — no working directory, RunMode::Ask
(answer only; Edit lets it write files), no resumed session, no attachments.
Keep _handle if you want to stop the run. Dropping it does not cancel.
To use Codex instead, change one line:
let = new.run?;
Open models
The CLI adapters wrap an external agent. An open model has no agent to wrap,
so the openai-compatible feature ships one. It calls the chat API and runs
the tool loop in Rust: read, glob, grep, list, write, edit,
bash, webfetch, websearch, todowrite, question, and apply_patch.
It also handles sessions, skills, subagents, and MCP servers.
let ollama = ollama;
let = ollama.run?;
A provider is configuration, not code. Point the same type at any OpenAI-compatible endpoint:
use ;
let openrouter = custom;
Ollama is the one exception. It uses its native /api/* endpoints, so the
context window is set correctly and local models can be pulled and deleted.
Use ollama_at(url) when your server is not on the default port.
Setup and sign-in
readiness() reports what is on the machine: installed, auth_configured,
and a version.
This crate does not install agents. When one is missing, info().install_hint
says where to get it.
if !harness.readiness.installed
login() runs an agent's own sign-in flow, which opens a browser. In CI, set
the agent's API key in the environment instead. Then readiness() reports
ready.
Bring your own agent
Harness only emits RunEvents. Your implementation can spawn a CLI or call
an HTTP API. Register it next to the built-ins:
let registry = new
.register
.register;
What the trait gives you
Harness—info,readiness,run,credential,login,list_models. Object-safe, soBox<dyn Harness>works.RunEvent— text, thinking, tool start and end, plan, usage with cache tokens, suggested edits, questions, and lifecycle. It follows the Agent Client Protocol vocabulary.Registry— an open set. Built-ins and your own agents sit together.
The subprocess engine is a separate crate:
cli-stream.
Examples
One per harness. Run with cargo run --example <name>.
| example | harness | features |
|---|---|---|
claude |
Claude Code | default |
codex |
OpenAI Codex | default |
acp |
OpenCode over ACP | acp |
gemini |
Gemini CLI over ACP | acp |
ollama |
a local model on Ollama | openai-compatible |
openrouter |
a hosted model on OpenRouter | openai-compatible models-dev |
llama_cpp |
a local llama-server |
openai-compatible |
tool_call |
the agent loop calling tools and reading the results | openai-compatible |
setup |
readiness, install hints, and sign-in | default |
custom_harness |
your own agent | default |
playground |
a browser UI that streams a live run over SSE | openai-compatible acp |
claude and codex differ by one line. ollama, openrouter, and
llama_cpp differ only in configuration — no provider has its own adapter.
License
MIT or Apache-2.0, at your option.