ironflow-core 2.4.1

Rust workflow engine with Claude Code native agent support
Documentation

ironflow-core

Core building blocks for the ironflow workflow engine. This crate provides composable, async operations that can be chained via plain Rust variables to build headless CI/CD, DevOps, and AI-powered workflows.

Operations

Operation Description
Shell Execute a shell command with timeout, env control, and kill_on_drop.
Agent Invoke an AI agent (Claude Code by default) with structured output support.
Http Perform HTTP requests via [reqwest] with builder-pattern ergonomics.

Provider trait

The AgentProvider trait abstracts the AI backend. The built-in ClaudeCodeProvider shells out to the claude CLI; swap it for RecordReplayProvider in tests for deterministic, zero-cost replay.

Known limitations: Structured output

When using AgentConfig::output::<T>() to request structured (typed) output from the Claude CLI, be aware of these upstream bugs:

Issue Impact
claude-code#18536 structured_output is always null when tools are used alongside --json-schema. ironflow prevents this at compile time via typestate (tools and schema are mutually exclusive).
claude-code#9058 The CLI does not validate output against the provided JSON schema -- non-conforming JSON may be returned.
claude-agent-sdk-python#502 Wrapper objects with a single array field may be flattened to a bare array (e.g. [...] instead of {"items": [...]}).
claude-agent-sdk-python#374 The wrapping behavior is non-deterministic: the same prompt can produce differently shaped output across runs.

Recommended workarounds:

  1. Two-step pattern: use one agent with tools to gather data, then a second agent with .output::<T>() (no tools) to structure the result.
  2. Defensive deserialization: when deserializing structured output, handle both the expected wrapper object and a bare array/value as fallback.
  3. max_turns >= 2: structured output requires at least 2 turns; setting max_turns(1) with a schema will fail with error_max_turns.

Quick start

use ironflow_core::prelude::*;

# async fn example() -> Result<(), OperationError> {
let files = Shell::new("ls -la").await?;

let provider = ClaudeCodeProvider::new();
let review = Agent::new()
    .prompt(&format!("Summarise:\n{}", files.stdout()))
    .model(Model::HAIKU)
    .max_budget_usd(0.10)
    .run(&provider)
    .await?;

println!("{}", review.text());
# Ok(())
# }