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

Agent Providers

A provider is what an agent step talks to: the Claude Code CLI, a remote Claude Code, or an HTTP API such as OpenAI. Every provider implements AgentProvider, so a workflow written against one runs against any other. Only ClaudeCodeProvider is always available; the others are behind a feature flag.

ProviderFeature flagUse case
ClaudeCodeProvider(always available)Claude Code CLI installed locally
SshProvidertransport-sshClaude Code on a remote build server
DockerProvidertransport-dockerClaude Code inside a running container
K8sEphemeralProvidertransport-k8sOne pod per invocation, full isolation
K8sPersistentProvidertransport-k8sReuses a worker pod, lower latency
AnthropicApiProviderprovider-anthropic-apiAnthropic Messages API, no CLI needed
OpenAiProviderprovider-openaiOpenAI Chat Completions
GeminiProviderprovider-geminiGoogle Gemini
MistralProviderprovider-mistralMistral
NvidiaProviderprovider-nvidiaNVIDIA NIM, 100+ models behind one API

The SSH, Docker and Kubernetes providers run Claude Code somewhere else; they are described in Transports.

With a tool profile (or with_tools) and .output::<T>(), the model can call registry tools before returning the structured answer. GeminiProvider, and AnthropicApiProvider on models without native JSON output, send the registry tools together with a structured_output tool and require the model to call a tool every turn. On Anthropic models with native JSON output (Sonnet 5.5, Opus 5.5, Fable 5.1, Mythos 5.1), only the registry tools are sent: the schema stays in output_config.format and the final text turn is the answer.

Reasoning effort

AgentConfig::effort(Effort::High) (also available on AgentStepConfig) takes low, medium, high, xhigh or max. The Claude Code providers (local, SSH, Docker, Kubernetes) pass it as --effort, also when a session is resumed, and AnthropicApiProvider sends output_config.effort. The other HTTP providers ignore it. When unset, no flag is sent.

Offered tools

AgentConfig::available_tools([Tool::Read, Tool::Edit]) sets the built-in tools the Claude Code session offers (--tools); an empty list offers none, and without the call the CLI offers its default set. allow_tool only pre-approves: in dontAsk, Claude Code still runs a read-only Bash or Monitor command that the allowlist leaves out, so a step that must not run a shell sets both. Only built-in tools are filtered, the tools of --mcp-config stay offered; the CLI ignores an unknown name or a permission pattern (Bash(git log:*)) without error. HTTP providers ignore the field.

HTTP providers

HTTP providers are used exactly like the local one:

#![allow(unused)]
fn main() {
use ironflow_core::prelude::*;
use ironflow_core::providers::http::{NvidiaModel, NvidiaProvider};

async fn example() -> Result<(), OperationError> {
let provider = NvidiaProvider::from_env(); // reads NVIDIA_API_KEY

let result = Agent::new()
    .prompt("Summarize the changelog")
    .model(NvidiaModel::DEEPSEEK_V4_FLASH)
    .max_budget_usd(0.10)
    .run(&provider)
    .await?;
Ok(())
}
}

Tools for HTTP providers

The Claude Code CLI brings its own tools. HTTP providers do not, so tools are opt-in, one feature each: tool-bash, tool-read-file, tool-grep, tool-glob, tool-web-fetch, tool-web-search, and tool-mcp to bring any MCP server into the agent’s toolset. GrepTool and GlobTool search a codebase without handing the agent a shell: they are confined to the directories you give them.

#![allow(unused)]
fn main() {
use std::path::PathBuf;

use ironflow_core::providers::http::tools::glob::GlobTool;
use ironflow_core::providers::http::tools::grep::GrepTool;
use ironflow_core::providers::http::tools::{ToolError, ToolRegistry};

fn example() -> Result<(), ToolError> {
let repo = vec![PathBuf::from("/srv/repo")];
let tools = ToolRegistry::new()
    .register(GrepTool::with_allowed_paths(repo.clone())?)
    .register(GlobTool::with_allowed_paths(repo)?);
Ok(())
}
}

Tool profiles

A step picks a named tool profile and sees only its tools; without one it gets the with_tools registry, or none. Declare each ToolProfile once as a constant shared by the provider and the steps, so a misspelled profile does not compile. An MCP server shared between profiles is opened once:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use ironflow_core::prelude::*;
use ironflow_core::provider::{AgentConfig, ToolProfile};
use ironflow_core::providers::http::OpenAiProvider;
use ironflow_core::providers::http::tools::ToolRegistry;
use ironflow_core::providers::http::tools::mcp::{McpConnection, register_shared_mcp_tools};

const SUGGESTION: ToolProfile = ToolProfile::new("suggestion");
const BUG: ToolProfile = ToolProfile::new("bug");

async fn example() -> Result<(), Box<dyn std::error::Error>> {
let mut gitlab = McpConnection::stdio("mcp-gitlab", &[], &[]).await?;
gitlab.initialize().await?;
let gitlab = Arc::new(gitlab);
let provider = OpenAiProvider::from_env()
    .with_tool_profile(SUGGESTION, register_shared_mcp_tools(ToolRegistry::new(), &gitlab, "gitlab").await?)
    .with_tool_profile(BUG, register_shared_mcp_tools(ToolRegistry::new(), &gitlab, "gitlab").await?);

let config = AgentConfig::new("Find the root cause").tool_profile(BUG);
let result = Agent::from_config(config).model("gpt-5.5").run(&provider).await?;
Ok(())
}
}

In a workflow, an agent step selects the profile with .tool_profile(BUG); see Agent steps.

Routing between providers

ProviderRouter picks the provider from the model name, so a single workflow can mix vendors:

#![allow(unused)]
fn main() {
use std::sync::Arc;
use ironflow_core::prelude::*;
use ironflow_core::providers::http::NvidiaProvider;

async fn example() -> Result<(), OperationError> {
let claude = Arc::new(ClaudeCodeProvider::new());
let nvidia = Arc::new(NvidiaProvider::from_env());

let router = ProviderRouter::new(claude)
    .route(ProviderMatcher::ModelPrefix("nvidia/".into()), nvidia);

// Goes to Claude Code
let a = Agent::new().prompt("Review").model(Model::SONNET).run(&router).await?;

// Goes to NVIDIA
let b = Agent::new().prompt("Review").model("nvidia/deepseek-v4-flash").run(&router).await?;
Ok(())
}
}

To spread agent steps over several Claude subscriptions or API keys, see Provider Accounts.