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