Skip to content

Repository files navigation

Exfiltrate gives your running program a command line.

You write the commands in Rust, inside the program, against its live state. Then you — or a coding agent — run them from a terminal while the program is running: on your desktop, on a phone, in a browser tab, or on a box you can only reach over SSH.

logo

# // no_run because: the example starts a live server and requires an external CLI.
# #[cfg(target_arch = "wasm32")]
wasm_lite::set_panic_hook();
use exfiltrate::command::{Command, Response};

# fn connected_players() -> usize { 3 }
struct Players;
impl Command for Players {
    fn name(&self) -> &'static str { "players" }
    fn short_description(&self) -> &'static str { "Count connected players" }
    fn full_description(&self) -> &'static str { "Prints the number of connected players." }
    fn execute(&self, _args: Vec<String>) -> Result<Response, Response> {
        Ok(format!("{} players online", connected_players()).into())
    }
}

exfiltrate::begin();              // once, at startup
exfiltrate::add_command(Players);
// ... the rest of your program
$ exfiltrate help players
Prints the number of connected players.
$ exfiltrate players
3 players online

Why not a debugger?

Because most of what you want from a running program isn't a breakpoint. It's what's in the cache right now, flip this flag, kick that job, dump the scene graph, save me a screenshot of the framebuffer. A debugger can get you there, but you'll be typing expressions at an lldb prompt, and the moment you want the output formatted your way you're writing formatters in a scripting dialect nobody else on the team knows.

With exfiltrate you write that tooling once, in Rust, next to the code it inspects. It costs one dependency and one call at startup, and it works the same everywhere your program runs — including places a debugger can't attach. It's as useful for a library as for an application: register a command that dumps whatever your library knows and you have a live inspector for it.

Why not just read the source?

Because agents, like people, reason better about a program they can poke than one they can only read. Giving an LLM a purpose-built interface for running and observing the target — instead of retrieval over source — took SWE-bench issue resolution from 3.8% to 12.5%: more than 3× from the harness alone, with no change to the model.1 Expose your program's nouns and verbs, and an agent can list the nouns and do the verbs instead of guessing from source.

Features

  • One call to start. Add the dependency, call begin(). No daemon, no config file.
  • No async runtime. Plain threads, no tokio.
  • Text, files, images. Commands return strings, binary files, or RGBA images; the CLI prints or saves them.
  • Desktop, mobile, WebAssembly. Browser targets go through a small proxy (see below).
  • Log capture, separately. Logs come from the logwise_agent_exfiltrate package rather than a feature of this one.

Quick start (Rust)

  1. Add exfiltrate as a dependency. Call begin() as early as possible — the top of main — and register your commands with add_command(), as in the example above.

  2. With the program running, drive it from the exfiltrate CLI. The native server listens on 127.0.0.1:1337; the CLI connects there automatically.

    In a sandbox that forbids binding a port, point both ends somewhere else with EXFILTRATE_ADDR (or Config::with_addr and --addr): unix:/path/to/socket uses a Unix socket, access-controlled by its 0700 parent directory rather than by "anyone on loopback"; unix:@name uses the Linux abstract namespace; and fd:3 adopts a connected socketpair(2) a supervising process already handed the program, which needs no new socket at all.

    An address that is reachable from outside this machine needs a token, and gets one without you arranging anything: the server invents one for the run and prints it, and you type it into exfiltrate --token. Set $EXFILTRATE_TOKEN on both ends instead if you would rather pin one, in which case nothing is printed. A token authenticates but does not encrypt — debug output crossing a hostile network still wants a tunnel. exfiltrate connecting explains the whole picture.

exfiltrate list             # every command available right now
exfiltrate help <command>   # details for one command
exfiltrate <command> [args] # run it against the live program

list shows the CLI's own built-ins plus whatever the running program registered (a local name wins on collision); if the program is down, it says so. The CLI is self-documenting — list, then help, and there's nothing to memorize — which is also what makes it easy to hand to an agent:

claude "Run `exfiltrate list`, then integrate the exfiltrate library into my program."

Not on Rust, but on C or C++? That's a use case exfiltrate doesn't cover yet — file a feature request with details about your setup.

Custom commands

Implement command::Command and register it with add_command(). name is what you type; short_description is what list shows; full_description is what help prints — so write them for someone reading cold, human or agent.

Response types

A command's Response can carry:

  • Text — the common case; anything that's Into<Response> from a String.
  • Files — binary payloads via FileInfo.
  • Images — RGBA images via ImageInfo, built from rgb::RGBA8 pixels (the rgb crate is re-exported).

The CLI prints text and writes files and images to disk. For worked examples of every response type, run exfiltrate help custom_commands.

Architecture

Why threads

Most Rust networking crates pull in tokio or another async runtime. That's the right call for a high-concurrency server and the wrong one for a debug shim you just want to embed. Exfiltrate has no tokio dependency; it uses threads. Threads for everyone.

WebAssembly

A browser WASM app can't open raw TCP sockets, so exfiltrate bridges through a proxy:

  1. The WASM app connects out to exfiltrate proxy over WebSockets.
  2. Another exfiltrate invocation connects to that proxy over TCP.
  3. The proxy relays between them, so the CLI drives the WASM app as if it were local.

Start an installed copy with exfiltrate proxy --help, or run it from this workspace with cargo run -p exfiltrate_cli -- proxy --help.

Log capture

This crate has no feature flags. Log capture used to live here behind a logwise feature; it is now the separate logwise_agent_exfiltrate package, so a program that wants live state does not pull a logging integration in with it, and one that wants logs asks for it by name.

Further reading

Run exfiltrate help integration for embedding guidance, or exfiltrate help custom_commands for the full response-type reference.

Footnotes

  1. Yang et al., SWE-agent: Agent–Computer Interfaces Enable Automated Software Engineering, NeurIPS 2024. https://arxiv.org/abs/2405.15793

About

An embeddable MCP server for Rust

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages