Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Installable tool binaries

rex::workflow discovers typed tool modules at runtime. Give the CLI a directory with --tool-dir DIRECTORY or REX_TOOL_DIR; an import of tools.NAME looks for the executable rex-tool-NAME in that directory. Tools that are not imported are neither inspected nor started. When neither setting is present, the CLI searches beside its own executable.

Installing a tool means copying its executable into that directory. Removing it makes the module unavailable on the next workflow compilation. rex does not link to tool crates, so this does not require recompiling the workflow host.

Binary contract

Every tool is a separate Rust crate named rex-tool-NAME and its binary implements two commands:

rex-tool-NAME manifest
rex-tool-NAME execute FUNCTION [JSON]

If the JSON argument to execute is omitted, the binary reads it from standard input. Arguments may be an object keyed by the documented parameter names or a positional array. A successful command writes only JSON to standard output; diagnostics go to standard error and failures return a nonzero status.

manifest returns protocol version 1, the exact Rex module ID, and a stable TypeBundle:

{
  "protocolVersion": 1,
  "module": "tools.example",
  "typeBundle": {
    "docs": "Module documentation.",
    "values": {
      "ping": [{
        "scheme": { "type": {
          "kind": "fun",
          "params": [{ "kind": "builtin", "name": "String" }],
          "ret": { "kind": "builtin", "name": "String" }
        } },
        "params": ["message"],
        "docs": "Function documentation."
      }]
    },
    "adts": []
  },
  "defaults": []
}

The bundle contains every public function scheme, Rex-facing parameter name, module/function/type documentation, and every ADT owned by the module. defaults preserves concrete host-backed Default instances used by record construction and update. Protocol version 1 requires each function to have one non-overloaded, concrete type scheme because JSON cannot represent unresolved type variables.

For execute, the shared runner uses json_to_rex for every argument, invokes the ordinary typed module handler, and uses rex_to_json for its result. The generated handler therefore performs the normal FromRex conversions before entering Rust and IntoRex conversion on return. The proxy in The rex::workflow proxy uses the same two JSON conversion functions, so direct binary calls and calls from a workflow have identical representations.

Authoring a tool

A tool crate depends on rex, defines an ordinary Rust-backed module, and gives that module factory to the shared command runner:

use rex::{
    engine::{EngineError, Module},
    workflow::state::State,
};

fn module() -> Result<Module<State>, EngineError> {
    // Usually generated by #[rex::module] and #[rex::export].
    api::rex_module()
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    rex::workflow::tool_protocol::run_tool_cli(
        module,
        || rex::workflow::tool_protocol::default_tool_state(
            "example",
            "REX_TOOL_EXAMPLE_IMAGE",
            "rex-tool-example:local",
        ),
    )
    .await
}

The bundled examples are the six workspace crates rex-tool-ffmpeg, rex-tool-gnuplot, rex-tool-graphviz, rex-tool-imagemagick, rex-tool-poppler, and rex-tool-qpdf. Their Rust types derive Rex; exported async functions receive State, convert typed requests into OCI jobs, and return typed results.

Use State::store and the helpers in rex::storage for content-addressed blobs and trees. Use State::execute_tool with a ToolExecutionPlan containing the tool’s trusted command. A tool can instead construct an OciJob and invoke an OciJobExecutor such as DockerOciJobExecutor; the public job boundary provides the same CAS staging, output validation, limits, and isolation controls.

default_tool_state opens the filesystem CAS named by REX_STORE and configures the Docker OCI backend for the tool name, image variable, and development default supplied by that tool crate. Embedding hosts with a different deployment model may supply a different state factory to run_tool_cli.

Trust boundary

An installed tool binary is native code and is therefore trusted to the same degree as the Rex workflow host. The directory must not be writable by untrusted workflow authors. Rex source cannot select an arbitrary binary: the requested module ID determines one filename, and the binary’s manifest must declare that exact module and a supported protocol version.

The native binary is only the typed protocol adapter. External media, plotting, document, or scientific programs should still run through the OCI executor rather than directly on the host. Workflow source controls typed function arguments, not image references, mounts, executable paths, or Docker options.