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

Standalone Runtime

ironflow-runtime is the path without a database: a small axum server that exposes webhook endpoints and calls ironflow-core operations directly. There is no store, no worker and no dashboard, so nothing is persisted. Use it for a webhook that triggers a quick job; use the full platform when you need run history, approvals or retries.

use ironflow_core::prelude::*;
use ironflow_runtime::prelude::*;

async fn on_push(payload: serde_json::Value, provider: &ClaudeCodeProvider) {
    let branch = payload["ref"].as_str().unwrap_or("main");
    // The branch comes from the webhook: an argument, never a `sh -c` string.
    let range = format!("origin/main...origin/{branch}");
    let diff = Shell::exec("git", &["diff", &range])
        .await
        .expect("git diff");
    let review = Agent::new()
        .prompt(&format!("Review this diff:\n{}", diff.stdout()))
        .model(Model::SONNET)
        .max_budget_usd(0.50)
        .run(provider)
        .await
        .expect("agent review");
    println!("{}", review.text());
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = ClaudeCodeProvider::new();

    Runtime::new()
        .webhook("/hooks/github", WebhookAuth::github("my-secret"), {
            let p = provider.clone();
            move |payload| {
                let p = p.clone();
                async move { on_push(payload, &p).await }
            }
        })
        .serve("0.0.0.0:8080")
        .await?;

    Ok(())
}
Webhook authBehaviour
WebhookAuth::none()No authentication
WebhookAuth::header(name, value)Static header comparison
WebhookAuth::github(secret)GitHub HMAC-SHA256 (X-Hub-Signature-256)
WebhookAuth::gitlab(secret)GitLab token (X-Gitlab-Token)

Built-in endpoints: GET /health, and GET /metrics with the prometheus feature.

Webhook replays

GitHub and GitLab send a delivery again when the receiver times out or answers 5xx. Use webhook_with_context to get the provider delivery id, already prefixed and ready to pass as an Idempotency-Key, so a replay does not start a second run.

#![allow(unused)]
fn main() {
use ironflow_runtime::prelude::*;

Runtime::new().webhook_with_context(
    "/hooks/github",
    WebhookAuth::github("my-secret"),
    |ctx: WebhookContext| async move {
        // ctx.delivery_id == Some("github:8f4e2a10-...") when the provider
        // stamped the request, None otherwise. Pass it straight to
        // create_run_idempotent as the Idempotency-Key.
        match ctx.delivery_id {
            Some(key) => println!("replay-safe key: {key}"),
            None => println!("no delivery id, the call is not replay-safe"),
        }
    },
);
}
ProviderHeader readDerived key
GitHubX-GitHub-Deliverygithub:<id>
GitLabX-Gitlab-Event-UUIDgitlab:<id>

webhook() keeps its original signature and receives only the payload.

Metrics

With the prometheus feature, operations record these metrics, in the runtime as in a worker:

MetricTypeLabels
ironflow_shell_totalCounterstatus
ironflow_shell_duration_secondsHistogram
ironflow_http_totalCountermethod, status
ironflow_http_duration_secondsHistogram
ironflow_agent_totalCountermodel, status
ironflow_agent_duration_secondsHistogrammodel
ironflow_agent_cost_usd_totalGaugemodel
ironflow_agent_tokens_input_totalCountermodel
ironflow_agent_tokens_output_totalCountermodel
ironflow_agent_tokens_cache_read_totalCountermodel
ironflow_agent_tokens_cache_write_totalCountermodel
ironflow_webhook_received_totalCounterpath, auth
ironflow_runs_reaped_totalCounteroutcome
ironflow_worker_leases_lost_totalCounter