fabro-agent— a full AI coding agent with tool use, sandboxed execution, event streaming, and context management. Use this when you want to build an agent that can read files, run commands, and interact with a codebase.fabro-llm— a standalone LLM client for multi-provider completions, streaming, and tool execution loops. Use this when you want direct control over LLM calls without the agent layer.
Agent (fabro-agent)
The fabro-agent crate provides a session-based AI agent that runs an LLM with tool use in a sandboxed environment. The agent loop streams LLM responses, executes tool calls (shell, read_file, write_file, edit_file, glob, grep, web_fetch, web_search), feeds results back, and repeats until the model responds with text or hits a safety limit.
Cargo.toml
Quick start
Session
Session is the core type. It holds the LLM client, a provider profile, a sandbox, and configuration. The main loop lives inside process_input().
Constructor:
Inspection:
Steering:
SessionOptions
All fields are public. Key settings with their defaults:Sandbox
TheSandbox trait abstracts where tools execute — local filesystem, Docker container, SSH remote, or a cloud sandbox. All tool operations go through this interface.
The
DaytonaSandbox implementation (feature-gated: daytona) runs inside a Daytona cloud sandbox.
Provider profiles
TheAgentProfile trait encapsulates LLM-specific system prompts, tool definitions, and capability metadata. It controls how the agent presents itself to the model.
AnthropicProfile, OpenAiProfile, GeminiProfile.
Events
All operations emitAgentEvent values through a tokio broadcast channel. Subscribe before calling process_input().
AgentEvent variants:
Tool hooks
ImplementToolHookCallback to intercept tool calls for approval, logging, or transformation:
SessionOptions:
ToolApprovalAdapter to wrap a closure:
Error handling
All fallibleSession methods return Result<T, AgentError>:
LLM client (fabro-llm)
The fabro-llm crate is a standalone Rust library for calling LLM providers. It provides a unified client that routes requests to Anthropic, OpenAI, Gemini, and other providers, with built-in streaming, tool execution, retries, and middleware.
You can use it independently of Fabro’s workflow engine — add it as a dependency in any Rust project.
Cargo.toml
Quick start
The simplest path is an environment-backedCredentialSource, an explicit Arc<Catalog>, then Client::from_source(&source, catalog). That keeps credential and model resolution explicit while still auto-reading environment variables such as ANTHROPIC_API_KEY, OPENAI_API_KEY, and GEMINI_API_KEY.
Client
Client is the core type that holds provider adapters and middleware. It routes each request to the appropriate provider.
Creating from a credential source
EnvCredentialSource checks for API key environment variables and registers adapters for each provider found:
The first provider registered becomes the default. Provider base URLs come from the model catalog. For vault-backed usage inside Fabro, use
fabro_auth::VaultCredentialSource instead.
The built-in Modal definition reads two proxy-token headers from the vault, so EnvCredentialSource does not configure it automatically. For direct SDK use, enable Modal and set its endpoint URL in the catalog:
catalog from those settings:
Creating manually
Low-level calls
For direct control without the tool loop, usecomplete() and stream() on the client:
High-level generation
Thegenerate() function wraps the client with automatic tool execution loops, retries, and timeouts. It is the recommended entry point for most use cases.
Basic completion
Multi-turn conversations
Use.messages() instead of .prompt() to pass a full conversation history:
You cannot use both
.prompt() and .messages() on the same request — this returns SdkError::Configuration.GenerateParams reference
GenerateResult
GenerateResult dereferences to Response, so you can call response methods directly:
Tools
Tools let the model call functions during generation. There are two kinds:- Active tools have an execute handler — Fabro runs them automatically and feeds results back to the model.
- Passive tools have no handler — Fabro returns the tool calls to you in the response.
Defining an active tool
Using tools with generate
generate() function loops automatically: the model calls tools, Fabro executes them, feeds results back, and repeats until the model stops or max_tool_rounds is reached.
Tool choice
Control how the model selects tools:Passive tools
Passive tools let you handle execution yourself:Streaming
Text stream
For simple cases where you only need the text deltas:Full event stream
For fine-grained control, consumeStreamEvent variants directly:
StreamEvent variants
Structured output
Generate typed JSON objects that conform to a JSON Schema:Middleware
Middleware intercepts requests and responses for logging, caching, or transformation:Model catalog
The crate embeds a catalog of known models with metadata:Error handling
All fallible operations returnResult<T, SdkError>. The error type classifies failures to enable retry and failover decisions:
Error classification
EverySdkError exposes classification methods:
Provider error kinds
Retries
Thegenerate() function retries automatically based on max_retries (default: 2). For low-level use, the retry function wraps any async operation:
error.retryable() returns true and respects Retry-After headers.
Cancellation
Pass aCancellationToken to interrupt long-running generation:
Provider adapters
Each provider has a dedicated adapter. All adapters implement theProviderAdapter trait and are interchangeable.
All adapters support
.with_base_url() for proxies or custom endpoints. OpenAiAdapter also supports .with_org_id() and .with_project_id().
Custom provider
Implement theProviderAdapter trait to add a new provider: