agent-harness
Drive LLM coding agents — Claude Code, OpenAI Codex, bob, and any ACP agent —
from Rust behind one trait, with a single normalized streaming event
vocabulary. Any OpenAI-compatible model — local Ollama or a hosted endpoint —
runs on the built-in openai-compatible runtime (OpenHarness), which owns
the agent loop in Rust. Bring your own agent too.
Imported as harness. One Harness trait, one RunEvent stream: your app
learns the event shape once and never grows a per-agent parser — adding an
agent is "write a parser into RunEvent," not "teach the UI another format."
# Pre-release: pin the exact version — cargo won't auto-select a pre-release.
= "0.4.0-alpha.1" # default: the Claude Code / Codex / bob adapters
# Add the opt-in backends — ACP agents (opencode / Gemini) + the local /
# OpenAI-compatible runtime:
= { = "0.4.0-alpha.1", = ["acp", "openai-compatible"] }
# Or slim it to one adapter — no keychain SDK, no HTTP client:
= { = "0.4.0-alpha.1", = false, = ["claude"] }
Quickstart
Construct an agent, then run_channel streams normalized RunEvents. Swap
the agent, keep the loop — that's the whole point.
Claude Code
use ;
let = new.run_channel?;
for event in events
OpenAI Codex
Identical loop — construct Codex instead:
let = new.run_channel?;
Open models — the openai-compatible feature
The claude/codex/acp adapters wrap an external agent. An OpenAI-compatible
model has none to wrap, so the openai-compatible feature ships a runtime
that is the agent — it speaks the OpenAI chat API and owns the tool loop
(read/write/edit/bash/glob/grep + webfetch/websearch/todos/skills/subagents/MCP)
in Rust, behind this same Harness trait. Think the Claude Code / Codex SDK,
but for any open-source or OpenAI-compatible model (Ollama, OpenRouter, vLLM, …):
= { = "0.4", = ["openai-compatible"] }
let ollama = ollama; // or ::custom(OpenHarnessConfig { id, display_name, base_url, .. })
let = ollama.run_channel?;
Off by default (it pulls a blocking HTTP client + search libs), so the core crate
stays lean — same RunEvent loop, no CLI required.
OpenCode, Gemini, Goose, and other ACP agents work via the
acpfeature —AcpHarness::opencode()spawnsopencode acpand drives it over the Agent Client Protocol.
Bring your own agent
Harness only emits RunEvents, so an implementor can spawn a CLI or call
an HTTP API — both fit. Register it alongside the built-ins; nothing to fork:
let registry = new
.register
.register; // your own `impl Harness`
RunEvent derives Serialize with stable camelCase field names, so any
transport (HTTP/SSE, an IPC channel) emits identical JSON.
What you get
Harnesstrait —info/readiness/install/run/credential/login/list_models. Object-safe (Box<dyn Harness>).RunEvent— text, thinking, tool start/end, plan, usage (with cache tokens), suggested edits, questions, lifecycle. Aligned with the Agent Client Protocol vocabulary.- Open
Registry— compose built-ins and your own providers.
The subprocess engine is the standalone
cli-stream crate.
Examples
Runnable demos (cargo run --example <name>):
run_prompt— Claude Code (default features).run_acp— an ACP agent (opencode); add--features acp.run_openai— an OpenAI-compatible / local model; add--features openai-compatible.playground— a browser UI that streams a live run over SSE for any backend; add--features "openai-compatible acp".
License
MIT OR Apache-2.0.