MCPcopy Index your code
hub / github.com/codejunkie99/agentic-harness

github.com/codejunkie99/agentic-harness @v0.1.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.1.1 ↗ · + Follow
975 symbols 2,823 edges 7 files 13 documented · 1%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Agentic Harness

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.

Agentic Harness architecture

📚 Documentation lives in docs/. Architecture, execution targets, runtime config, HTTP SessionEnv protocol, Cloudflare runtime, deployment guides, feature status, and release notes — all there.

Start Here

Pick the path that matches what you are trying to do:

  • Use the tool on a repo: run agentic-harness guide, then agentic-harness code --workspace . --llm auto.
  • Create a Rust agent project: run agentic-harness new ./my-agent --template coding, then agentic-harness dev --workspace ./my-agent.
  • Serve an existing agent: run agentic-harness setup hosting --workspace . once, then agentic-harness host --workspace ..
  • Ship to a host: use 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.
  • Embed the SDK: use AgentApp, AgentDefinition, AgentContext, and run_cli from agentic_harness::prelude::*.

Workspace

Install

Agentic Harness installs a single CLI binary named agentic-harness. Rust and Cargo are required for source, tarball, and Homebrew builds.

Tagged Release Tarball

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

Source Checkout

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.

Homebrew

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.

Common Commands

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.

Examples

Quickstart

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.

Coding Agent (Local Repo)

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.

Snapshot Repair (CI)

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}"

Codebase Cartographer (Parallel Tasks)

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", &sections.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.

Reproducer Sandbox (Remote Linux)

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 Tools (Sentry)

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.",
)?;

Schema-Guided Cargo Audit

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;

[derive(Deserialize)]

struct CrateAudit { advisories: Vec, risk: Risk, next_action: String, }

[derive(Deserialize)]

struct Advisory { id: String, package: String, severity: Severity }

[derive(Deserialize)]

[serde(rename_all = "lowercase")]

enum Severity { Low, Medium, High, Critical }

[derive(Deserialize)]

[serde(rename_all = "lowercase")]

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" },

Extension points exported contracts — how you extend this code

ModelClient (Interface)
Minimal synchronous model integration point for native Rust agents. Agentic Harness does not force a provider here. Use [15 …
crates/agentic-harness/src/lib.rs
SessionEnv (Interface)
Runtime environment contract for native sandbox connectors. [6 implementers]
crates/agentic-harness/src/lib.rs
HttpSessionTransport (Interface)
Transport used by [`HttpSessionEnv`] to exchange JSON with a remote sandbox. [4 implementers]
crates/agentic-harness/src/lib.rs
SessionStore (Interface)
Runtime-agnostic storage for persisted session data. [3 implementers]
crates/agentic-harness/src/lib.rs
McpClient (Interface)
Minimal MCP client contract used to adapt MCP tools into Agentic Harness tools. [2 implementers]
crates/agentic-harness/src/lib.rs

Core symbols most depended-on inside this repo

iter
called by 108
crates/agentic-harness/src/lib.rs
get
called by 72
crates/agentic-harness/src/lib.rs
agent
called by 61
crates/agentic-harness/src/lib.rs
as_str
called by 58
crates/agentic-harness/src/lib.rs
exists
called by 44
crates/agentic-harness-cli/src/lib.rs
invoke
called by 40
crates/agentic-harness/src/lib.rs
insert
called by 36
crates/agentic-harness/src/lib.rs
env
called by 27
crates/agentic-harness/src/lib.rs

Shape

Function 564
Method 257
Class 128
Enum 19
Interface 7

Languages

Rust100%
Ruby1%

Modules by API surface

crates/agentic-harness-cli/src/lib.rs405 symbols
crates/agentic-harness/src/lib.rs383 symbols
crates/agentic-harness/tests/runtime.rs88 symbols
crates/agentic-harness-cli/tests/cli.rs88 symbols
examples/hello-world/src/main.rs8 symbols
Formula/agentic-harness.rb2 symbols
crates/agentic-harness-cli/src/main.rs1 symbols

For agents

$ claude mcp add agentic-harness \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact