The Rust agent harness. Build agents that read a repo, plan, edit files, run tests, and report back — then ship the same binary to your laptop, CI, a remote Linux sandbox, or the edge.
If you've used Claude Code, Codex, or Cursor, you already know how this feels:
an agent loop with sessions, tools, skills, and a workspace it can act on. The
difference is that it's headless, programmable, and yours. Agents are plain Rust
binaries; their behavior — AGENTS.md, roles, and skills — lives in Markdown,
so you change what an agent does without touching the build.
Native Rust end to end: SDK, CLI, runtime, HTTP dispatch, sessions, tools, workspace context. One toolchain, one self-contained binary, no JavaScript anywhere. Runs locally on a checkout or in CI, talks to remote Linux sandboxes (Vercel Sandbox, Daytona, E2B) over a small HTTP protocol, and emits a Cloudflare Workers boundary for edge control planes.
📚 Documentation lives in
docs/. Architecture, execution targets, runtime config, HTTP SessionEnv protocol, Cloudflare runtime, deployment guides, feature status, and release notes — all there.
Pick the path that matches what you are trying to do:
agentic-harness guide, then
agentic-harness code --workspace . --llm auto.agentic-harness new ./my-agent --template coding, then
agentic-harness dev --workspace ./my-agent.agentic-harness setup hosting --workspace .
once, then agentic-harness host --workspace ..agentic-harness build --target native for a native
binary, --target node for Node platforms, or --target cloudflare for the
Worker boundary artifacts described in
docs/cloudflare-runtime.md.AgentApp, AgentDefinition, AgentContext, and
run_cli from agentic_harness::prelude::*.crates/agentic-harness: Rust SDK for the agent
registry, context, sessions, roles, skills, tools, and HTTP serving.crates/agentic-harness-cli: Rust CLI for
wizard, code, new, template, setup, doctor, build, dev, run,
serve, manifest, and add.examples/hello-world: Native Rust example agent
workspace.Agentic Harness installs a single CLI binary named agentic-harness. Rust and
Cargo are required for source, tarball, and Homebrew builds.
curl -L -o agentic-harness-v0.1.1.tar.gz \
https://github.com/codejunkie99/agentic-harness/archive/refs/tags/v0.1.1.tar.gz
tar -xzf agentic-harness-v0.1.1.tar.gz
cd agentic-harness-0.1.1
./scripts/install.sh
export PATH="$HOME/.agentic-harness/bin:$PATH"
agentic-harness --version
git clone https://github.com/codejunkie99/agentic-harness.git
cd agentic-harness
./scripts/install.sh
export PATH="$HOME/.agentic-harness/bin:$PATH"
agentic-harness --version
Set AGENTIC_HARNESS_PREFIX to install somewhere other than
$HOME/.agentic-harness.
brew install --HEAD ./Formula/agentic-harness.rb
agentic-harness --version
The stable Homebrew formula is updated on main after each release tag because
it needs the GitHub tarball SHA.
agentic-harness guide --workspace . --env codex
agentic-harness doctor --workspace . --json
agentic-harness dashboard --workspace . --plain
agentic-harness code --workspace . --llm auto --prompt "Fix the failing tests"
agentic-harness inspect --workspace .
doctor checks readiness, dashboard summarizes the workspace, code runs the
coding-agent loop, and inspect reads the latest coding-run summary.
The simplest agent — no sandbox config, no model wiring, just a typed payload and a JSON response. Run it as a CLI or serve it over HTTP.
// src/main.rs
use agentic_harness::prelude::*;
use serde::Deserialize;
use serde_json::json;
// Every agent has a trigger. This one is invoked as an HTTP webhook.
#[derive(Deserialize)]
struct HelloPayload {
name: Option<String>,
}
fn app() -> Result<AgentApp, AgenticHarnessError> {
Ok(AgentApp::new()
.with_workspace(".")
.load_workspace_context()?
.agent(AgentDefinition::webhook("hello", |ctx: AgentContext| {
let payload: HelloPayload = ctx.payload()?;
let name = payload.name.unwrap_or_else(|| "World".to_string());
Ok(json!({
"id": ctx.id(),
"message": format!("Hello, {name}!"),
}))
})))
}
fn main() {
let code = match app().and_then(run_cli) {
Ok(code) => code,
Err(err) => {
eprintln!("[agentic-harness] {err}");
1
}
};
std::process::exit(code);
}
agentic-harness new ./my-agent --template hello
agentic-harness run hello --workspace ./my-agent --id demo \
--payload '{"name":"Ada"}'
Use --template coding, code-review, test-fixer, docs-writer,
repo-analyst, or another built-in template when you want a fuller software
agent starter instead of the minimal hello-world shape.
The flagship workflow. agentic-harness start opens the guided TUI front door;
agentic-harness code is the direct automation path. The coding loop inspects
the repo, reads AGENTS.md / CLAUDE.md, captures git diff context, drafts a
plan, hands the brief to your installed coding LLM, runs detected checks
(cargo test, etc.), and optionally commits or opens a PR. No prompt? It
defaults to a sensible "smallest safe step" brief.
# Detects whichever of claude / codex / cursor / wind-server you have
agentic-harness code --workspace . --llm auto \
--prompt "Add a flag to skip the network call in test mode" \
--deny-path .env \
--approve-dependencies \
--commit "feat: --offline flag" \
--pr
The harness writes a run-scoped brief to
.agentic-harness/runs/<id>/coding-brief.md, streams progress, captures the
agent result, and saves both latest summaries and a durable bundle under
.agentic-harness/runs/<id>/ (summary.md, run.json, events.jsonl,
diff.patch, checks.json, agent-instructions.md). Policy flags such as
--allow-path, --deny-path, --max-command-risk, and
--approve-dependencies gate code mutation before commit or PR handoff.
A CLI-only agent that runs in CI after cargo test produces failing
*.snap.new files. It reads the diffs, decides which are safe to bless under a
workspace policy (additive output, ordering changes, whitespace), applies the
safe ones, and flags the rest for human review. No HTTP trigger.
// src/main.rs
use agentic_harness::prelude::*;
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct Payload {
failing: Vec<String>, // paths to *.snap.new files
}
fn app() -> Result<AgentApp, AgenticHarnessError> {
Ok(AgentApp::new()
.with_workspace(".")
.load_workspace_context()?
.agent(AgentDefinition::cli("snapshot-repair", |ctx: AgentContext| {
let Payload { failing } = ctx.payload()?;
let session = ctx.session_with_id(ctx.id());
// The "snapshot-reviewer" role lives in .agentic-harness/roles/.
// It tells the model what counts as a safe bless vs. a human-only call.
let report = session.prompt_with_options(
format!(
"Review these failing snapshots and bless only the safe ones:\n\n{}",
failing.join("\n"),
),
PromptOptions::new().role("snapshot-reviewer"),
)?;
Ok(json!({ "report": report.text() }))
})))
}
fn main() { std::process::exit(app().and_then(run_cli).unwrap_or(1)); }
# In CI, after a failed test run, hand the new snapshots to the agent
SNAPS=$(find . -name '*.snap.new' | jq -Rsc 'split("\n") | map(select(length>0))')
agentic-harness run snapshot-repair --workspace . --id "ci-$RUN" \
--payload "{\"failing\":$SNAPS}"
A one-shot agent that produces ARCHITECTURE.md for a repo it's never seen. It
fans out one detached Session::task per top-level module, each with its own
message history but sharing the workspace, then merges the children's notes into
a single document. This is the Rust analogue of "kick off N research subagents
in parallel and stitch the results."
// src/main.rs
use agentic_harness::prelude::*;
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct Payload { src_dir: Option<String> }
fn app() -> Result<AgentApp, AgenticHarnessError> {
Ok(AgentApp::new()
.with_workspace(".")
.load_workspace_context()?
.agent(AgentDefinition::cli("cartograph", |ctx: AgentContext| {
let src = ctx.payload::<Payload>()?.src_dir.unwrap_or_else(|| "src".into());
let session = ctx.session_with_id(ctx.id());
let mut sections = Vec::new();
for entry in session
.readdir(&src)?
.into_iter()
.filter(|e| e.is_dir)
{
let child = session.task_with_id(
format!("module-{}", entry.name),
format!(
"Summarize the public surface and responsibilities of {}/{}.\n\
List entry points and any cross-module imports.",
src, entry.name,
),
TaskOptions::new().role("module-summarizer"),
)?;
sections.push(format!("## {}\n\n{}\n", entry.name, child.text()));
}
session.write("ARCHITECTURE.md", §ions.join("\n"))?;
Ok(json!({ "modules": sections.len() }))
})))
}
fn main() { std::process::exit(app().and_then(run_cli).unwrap_or(1)); }
Each child task gets a fresh AGENTS.md + skill discovery scoped to its working
directory, so adding a module-summarizer role tunes every task at once.
When an issue says "this fails on Linux but I'm on macOS," the agent provisions
a clean Linux sandbox over HttpSessionEnv, checks out the branch, runs the
reproducer steps, and captures evidence. The agent stays a native Rust binary on
your laptop; shell and file operations run on the other side of an HTTP
boundary.
use agentic_harness::HttpSessionEnv;
let sandbox = HttpSessionEnv::new(
// Vercel Sandbox / Daytona / E2B / your own service.
std::env::var("SANDBOX_URL")?,
"/workspace",
)
.header(
"Authorization",
format!("Bearer {}", std::env::var("SANDBOX_TOKEN")?),
);
let session = ctx.session_with_id_and_env("repro", sandbox);
session.shell(&format!(
"git clone {repo} /workspace/repo && \
git -C /workspace/repo checkout {branch}"
))?;
let probe = session.shell(
"cd /workspace/repo && cargo test --no-fail-fast 2>&1 | tail -200",
)?;
session.write(
"/workspace/repro-report.md",
&format!("## exit: {}\n\n```\n{}\n```\n", probe.status, probe.stdout),
)?;
The same protocol is documented in
docs/http-session-env.md — any sandbox provider
that speaks it works without a custom adapter. The CLI surfaces it for ad-hoc
use too:
agentic-harness setup sandbox --target e2b --endpoint $SANDBOX_URL
agentic-harness sandbox status --json
agentic-harness sandbox exec "uname -a && rustc --version" --json
MCP servers plug in as runtime tool providers. Connect once, hand the tools to a
session, and the model can call find_event, list_issues, etc. directly.
Streamable HTTP by default; pass transport: Sse for legacy SSE servers.
use agentic_harness::McpServerOptions;
let sentry = ctx.connect_mcp(
"sentry",
McpServerOptions::new("https://mcp.sentry.io/mcp")
.header("Authorization", format!("Bearer {}", std::env::var("SENTRY_TOKEN")?)),
)?;
let session = ctx.session_with_id(ctx.id()).with_tools(sentry);
let plan = session.prompt(
"Find the highest-volume new error in the last 24h, locate the \
commit that introduced it, and draft a hot-fix plan with \
rollback steps.",
)?;
Get typed, schema-validated data back from a prompt without manual JSON
wrangling. The model returns prose plus a structured block;
prompt_json_with_options extracts and decodes it directly into your type.
```rust use agentic_harness::PromptOptions; use serde::Deserialize; use serde_json::json;
struct CrateAudit { advisories: Vec, risk: Risk, next_action: String, }
struct Advisory { id: String, package: String, severity: Severity }
enum Severity { Low, Medium, High, Critical }
enum Risk { None, Low, Medium, High, Critical }
let audit: CrateAudit = session.prompt_json_with_options(
"Run cargo audit, group by severity, and pick the smallest safe upgrade plan.",
PromptOptions::new().result_schema(json!({
"type": "object",
"required": ["advisories", "risk", "next_action"],
"properties": {
"advisories": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "package", "severity"],
"properties": {
"id": { "type": "string" },
$ claude mcp add agentic-harness \
-- python -m otcore.mcp_server <graph>