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

Transports

Transports control where agent steps execute. By default, agents run on the local machine via ClaudeCodeProvider. Ironflow ships additional transports for running agents in isolated environments.

Available transports

TransportProviderUse case
LocalClaudeCodeProviderDevelopment, simple setups
DockerDockerProviderIsolated containers on the same host
SSHSshProviderRemote machines
KubernetesK8sProviderEphemeral or persistent pods in a cluster

Docker transport

Executes agent commands inside a running Docker container via docker exec:

//! Docker transport example.
//!
//! Executes `claude` inside a running Docker container via `docker exec`.
//! The container must already be running and have the `claude` binary installed.
//!
//! # Usage
//!
//! ```sh
//! DOCKER_CONTAINER=claude-worker cargo run --bin docker-transport
//! ```

use std::env;

use ironflow_core::prelude::*;
use ironflow_core::providers::claude::DockerProvider;

#[tokio::main]
async fn main() -> Result<(), OperationError> {
    let container = env::var("DOCKER_CONTAINER").expect("DOCKER_CONTAINER env var required");

    let provider = DockerProvider::new(&container).working_dir("/workspace");

    let result = Agent::new()
        .prompt("What is 2 + 2?")
        .max_budget_usd(0.10)
        .run(&provider)
        .await?;

    println!("Response: {}", result.text());
    println!("Model: {}", result.model().unwrap_or("unknown"));
    println!("Duration: {}ms", result.duration_ms());

    Ok(())
}

SSH transport

Connects to a remote host via SSH:

//! SSH transport example.
//!
//! Connects to a remote host via SSH and runs `claude` there.
//! Requires the `claude` binary to be installed on the remote host.
//!
//! # Usage
//!
//! ```sh
//! SSH_HOST=build-server SSH_USER=deploy SSH_PASSWORD=secret cargo run --bin ssh-transport
//! ```

use std::env;

use ironflow_core::prelude::*;
use ironflow_core::providers::claude::SshProvider;
use ironflow_core::providers::claude::ssh::HostKeyPolicy;

#[tokio::main]
async fn main() -> Result<(), OperationError> {
    let host = env::var("SSH_HOST").expect("SSH_HOST env var required");
    let user = env::var("SSH_USER").expect("SSH_USER env var required");
    let password = env::var("SSH_PASSWORD").expect("SSH_PASSWORD env var required");

    // For production, use HostKeyPolicy::Fingerprint or HostKeyPolicy::KnownHostsFile
    let provider = SshProvider::new(&host, &user)
        .password(&password)
        .host_key_policy(HostKeyPolicy::AcceptAll);

    let result = Agent::new()
        .prompt("What is 2 + 2?")
        .max_budget_usd(0.10)
        .run(&provider)
        .await?;

    println!("Response: {}", result.text());
    println!("Model: {}", result.model().unwrap_or("unknown"));
    println!("Duration: {}ms", result.duration_ms());

    Ok(())
}

Kubernetes transport

Two modes are available:

  • Ephemeral – creates a pod for each agent call, deletes it when done
  • Persistent – reuses a long-lived pod for multiple calls

See the examples/transports/ directory for complete Kubernetes examples.

For untrusted prompts or multi-tenant clusters, K8sEphemeralProvider::sandboxed runs each agent in a hardened pod: non-root, read-only root filesystem, secrets read from Kubernetes Secrets, managed-settings presets and egress profiles. See Kubernetes Sandbox.

Step timeout

A step with AgentConfig::timeout (see Agent steps) replaces the provider timeout. The ephemeral Kubernetes pod is deleted when the step times out. With Docker, SSH or a persistent pod, a step with a timeout runs claude under timeout -s KILL, so the image or host needs a timeout binary (coreutils or busybox) on its PATH: the remote side then kills claude 5 s after the limit, even once the connection is gone. A step without a timeout runs the command unchanged.

Provider Account credentials

The local, Docker and SSH transports inject the credential of the Provider Account the worker selected: through the process environment, the Docker exec environment, or the first line of stdin over SSH. The token never appears on a command line. The Kubernetes transports do not inject accounts yet and keep using the pod environment.

Resuming an interrupted agent step

Before an agent step launches, the engine pins the Claude Code session it runs in (--session-id <uuid>) and records it on the step as session_id. When the worker running the step loses its lease, the step is interrupted (see Engine & Worker). The next execution of the same step, in the same attempt, resumes that session (--resume <uuid>) with a resume prompt instead of starting the agent from scratch, and keeps the conversation and the work already done.

The resume prompt defaults to DEFAULT_RESUME_PROMPT. Set your own on the step:

ctx.agent(
    "review",
    AgentStepConfig::new("Review the diff")
        .max_budget_usd(0.50)
        .resume_prompt("You were interrupted. Finish the review where you stopped."),
)
.await?;

Claude Code keeps its sessions under ~/.claude/projects/, keyed by the working directory. A step can only resume a session the transport can still read:

TransportSession kept
LocalYes, when the next worker runs on the same machine, under the same user and working directory
DockerYes, while the container lives
SSHYes, on the same host
Kubernetes persistentYes, while the pod lives
Kubernetes ephemeralOnly with sessions_volume(claim) on a sandboxed provider, see Kubernetes Sandbox

When the session is gone, the step does not fail: it logs session <uuid> not found, restarting the agent from scratch and runs the original prompt again in a session of the same id. A step that sets resume or session_id itself is left alone.

Each resume writes the system log line agent step resumed from session <uuid> on the step and publishes an agent_step_resumed event on the run’s event stream. Only a step interrupted by a lost lease resumes: a step failed by the agent, a manual retry of the run (a new attempt) or a parked step that never launched starts a fresh session.

Cost and tokens of a resumed step only cover the resume invocation. The interrupted invocation never wrote its final result line, so what it spent before the worker lost its lease stays unknown and is not added to the step, the run or the budget counters. Expect the real spend of a resumed step to be higher than the recorded one.

A step retry (retry_policy) stays in the session of the step: the retry sends the original prompt into the session the first try created (the CLI refuses to create a session id twice), so the step records a single session id.

Choosing a transport

  • Development: use ClaudeCodeProvider (local). No setup needed.
  • CI/CD pipelines: Docker or Kubernetes for isolation.
  • Remote build servers: SSH for machines you already manage.
  • Multi-tenant production: Kubernetes ephemeral pods for strong isolation between tenants.