Skip to content

compusophy/localharness

Repository files navigation

localharness

A Rust-native agent SDK for Google's Gemini API.

Streaming text, custom tools, safety policies, hooks, background triggers, MCP bridge, context compaction -- one crate, zero external binaries. Compiles to native (tokio) and wasm32 (browser).

crates.io docs.rs license MSRV

Quick start

use localharness::{Agent, GeminiAgentConfig};

#[tokio::main]
async fn main() -> localharness::Result<()> {
    let agent = Agent::start_gemini(
        GeminiAgentConfig::new(std::env::var("GEMINI_API_KEY").unwrap())
            .with_system_instructions("You are a concise code reviewer."),
    ).await?;

    let response = agent.chat("Review: fn add(a: i32, b: i32) -> i32 { a - b }").await?;
    println!("{}", response.text().await?);
    agent.shutdown().await?;
    Ok(())
}
[dependencies]
localharness = "0.10"
tokio        = { version = "1", features = ["macros", "rt-multi-thread"] }

Get an API key from Google AI Studio. No Python, no Go binary, no harness process -- cargo build and you have an agent.

Features

  • Streaming. Independent cursors for text, thoughts, and tool calls -- safe to consume concurrently.
  • Tools. 15 built-in tools (filesystem, shell, image gen, sub-agents, inter-agent RPC, in-browser Rust compiler) plus ClosureTool for custom tools. MCP stdio bridge for external tool servers.
  • Hooks and policies. Six hook points. Deny-by-default policy engine with allow, deny, ask. workspace_only() sandboxes file tools.
  • Triggers. Background tasks that inject prompts on a schedule or condition.
  • Wasm. Same Agent loop compiles to wasm32-unknown-unknown. File tools use OPFS. Only run_command and MCP are native-only.
  • Multimodal. Images, PDFs, audio, video via Media / Part with zero-copy bytes::Bytes storage.

Architecture

Layer   Type                          Purpose
  1     Agent                         High-level facade: connect, chat, shutdown.
  2     Conversation / ChatResponse   Stateful session, multi-cursor streams.
  3     Connection                    Transport abstraction (swap backends).
 aux    Filesystem                    Pluggable FS for file tools (Native / OPFS / custom).

Cargo features

Feature Default Description
native yes Tokio runtime, run_command, MCP stdio bridge, NativeFilesystem.
wallet no secp256k1 keypair, BIP-39, on-chain registry client. Works on every target.
browser-app no In-browser IDE as a wasm cdylib (wasm-pack). Enables wallet transitively.

Built-in tools

Default config exposes the read-only subset; CapabilitiesConfig::unrestricted() enables all 15.

Tool Mode Description
list_directory R Sorted children with name, kind, size.
view_file R UTF-8 read with optional line range; 256 KiB cap.
find_file R Glob-matched recursive name search; 1000-match cap.
search_directory R Regex content search with optional file glob; 500-match cap.
create_file W Atomic write via tempfile + rename; refuses to overwrite.
edit_file W Exact substring replace (or replace_all); atomic write.
delete_file W Remove file or directory (recursive).
rename_file W Rename/move; atomic on native.
run_command W Shell exec, 30s default / 600s max timeout. Native only.
generate_image W Image model call; returns base64 + MIME.
ask_question I/O No-op default; register a custom impl for interactive UI.
start_subagent spawn One-shot subagent with isolated context.
call_agent RPC Inter-agent message by subdomain name.
compile_rustlite exec Compile Rust-subset source to wasm and run it in-browser.
finish term Terminate turn + capture structured output.

Custom tools with the same name as a built-in override it.

Examples

Custom tool
use localharness::{allow_all, Agent, ClosureTool, GeminiAgentConfig};
use serde_json::json;

let weather = ClosureTool::new(
    "get_weather",
    "Return the weather for a city.",
    json!({ "type": "object", "properties": { "city": { "type": "string" } } }),
    |args, _ctx| async move {
        let city = args["city"].as_str().unwrap_or("?");
        Ok(json!({ "weather": format!("sunny in {city}") }))
    },
);
let agent = Agent::start_gemini(
    GeminiAgentConfig::new(api_key)
        .with_tool(weather)
        .with_policies(vec![allow_all()]),
).await?;
Stream tokens
use futures_util::StreamExt;

let response = agent.chat("Write a haiku about Rust.").await?;
let mut tokens = response.text_stream();
while let Some(chunk) = tokens.next().await {
    print!("{}", chunk?);
}
Policies + workspace sandbox
use localharness::{deny_all, Policy, CapabilitiesConfig, GeminiAgentConfig};

let policies = vec![
    deny_all(),
    Policy::allow("view_file"),
    Policy::ask("run_command", std::sync::Arc::new(|call| {
        eprintln!("approve `{}`? {:?}", call.name, call.args);
        true
    })),
];

// Or sandbox everything to a directory:
let agent = Agent::start_gemini(
    GeminiAgentConfig::new(api_key)
        .with_capabilities(CapabilitiesConfig::unrestricted())
        .with_workspace("/home/me/project"),
).await?;
Background trigger
use localharness::every;
let watchdog = every(std::time::Duration::from_secs(60), "deploy_watch", |ctx| async move {
    ctx.send_when_idle("Check the deployment status.").await
});
MCP bridge (native only)
use localharness::types::McpServerConfig;

let agent = Agent::start_gemini(
    GeminiAgentConfig::new(api_key)
        .with_mcp_server(McpServerConfig::Stdio {
            command: "uvx".into(),
            args: vec!["mcp-server-fetch".into()],
        }),
).await?;

Run in the browser

The same agent loop runs in a browser tab. Live demo: localharness.xyz

git clone https://github.com/compusophy/localharness && cd localharness
./scripts/build-web.sh        # wasm-pack build -> web/pkg/
python -m http.server 8765 -d web

Links

docs.rs -- crates.io -- GitHub -- live demo

License

Apache-2.0

About

Unofficial Rust client SDK for the localharness agent runtime (Google Antigravity).

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors