Skip to content

agent_sdk

Runtime-first SDK for building PromptFleet agents with optional protocol adapters.

The public foundation of this crate is the agent runtime: skills, tools, services, protocol-neutral message types, and runtime event streams. Protocol adapters such as A2A and AG-UI are enabled through explicit features rather than defining the identity of the crate.

The canonical model is:

  • build one [Agent]
  • compose A2A support with [crate::a2a]
  • compose AG-UI support with [crate::agui]
  • agui-agent: user-facing AG-UI agent runtime
  • a2a-agent: A2A-capable interoperable agent runtime
  • dual-agent: both A2A and AG-UI
  • pf-agent: PromptFleet opinionated full bundle
  • Building a runtime with skills, tools, and your own loop: agent-core
  • Adding the built-in LLM loop: llm-engine
  • Serving only a user-facing AG-UI experience: agui-agent
  • Serving and calling A2A agents: a2a-agent
  • Supporting both A2A and AG-UI in one runtime: dual-agent
  • Using the full PromptFleet stack: pf-agent
  • Adding context trimming or token budgeting: context-window
  • Adding observability, storage, or sub-agents: compose the matching primitive features on top
SurfaceNativeWASM
[AgentBuilder]yesyes
[crate::a2a::A2aApp]yesyes
[crate::a2a::A2aClient]yestarget-gated
[AgentHostBuilder] + A2Ayesyes
[AgentHostBuilder] + AG-UIyesno
use agent_sdk::{AgentBuilder, error::SdkResult};
use serde_json::json;
#[tokio::main]
async fn main() -> SdkResult<()> {
let mut agent = AgentBuilder::new("weather-agent")?.build()?;
agent
.add_skill("get_weather")
.handler(|params| async move {
let location = params["location"].as_str().unwrap_or("unknown");
Ok(json!({"location": location, "temp": 22, "condition": "sunny"}))
})
.register()?;
// Metadata-only skill (no handler): use `add_skill("id").description("...").register()?`
Ok(())
}

On native with llm-engine, call [crate::Agent::configure_llm_runtime] after [AgentBuilder::build] with an OpenAI-compatible [llm_client::LlmClient] (or another type that implements the invoker traits) and a [crate::agent::tools::ToolRegistry]:

use agent_sdk::{AgentBuilder, SdkResult};
use agent_sdk::agent::tools::ToolRegistry;
use llm_client::{LlmClient, WireFormat};
use llm_client::auth::ApiKeyAuth;
fn wire_llm() -> SdkResult<()> {
let mut agent = AgentBuilder::new("my-agent")?.build()?;
let client = LlmClient::builder(WireFormat::OpenAiCompat)
.base_url("https://api.openai.com/v1")
.auth(ApiKeyAuth::new("sk-..."))
.build()
.map_err(|e| agent_sdk::SdkError::configuration(e.to_string()))?;
let tools = ToolRegistry::new();
agent.configure_llm_runtime(client, "gpt-4o-mini", tools, None, None, None)?;
Ok(())
}
  • Use [AgentBuilder::new] or [AgentBuilder::from_config] instead of crate-root new/new_runtime helpers (removed).
  • Use [crate::Agent::configure_llm_runtime] instead of set_llm_tools_message_handler_configured / _with.
  • Use [SkillEntryBuilder] via [crate::Agent::add_skill] for optional handler + full metadata.
# #[cfg(all(not(target_arch = "wasm32"), feature = "dual-agent"))]
# {
use agent_sdk::{AgentBuilder, AgentHostBuilder, SdkResult};
use agent_sdk::agui::AgUiConfig;
# fn example() -> SdkResult<()> {
let agent = AgentBuilder::from_config_path("agent.json")?.build()?;
let router = AgentHostBuilder::new(agent)
.with_a2a()
.with_agui(AgUiConfig::default())
.build_router()?;
# let _ = router;
# Ok(())
# }
# }
const SDK_VERSION: &str = ;

SDK version info

type CallableSkill = Box<dyn Fn + Send + Sync>;

Boxed async callable: serde_json::Value in, [SdkResult] of JSON out.

On native targets the future is Send; on WASM it is single-threaded.

type SdkResult = Result<T, SdkError>;

Result type for SDK operations

Core Agent implementation

The Agent struct is the main runtime entry point for registering skills, wiring handlers, and dispatching runtime messages. Protocol surfaces such as A2A and AG-UI compose around this type via adapter modules.

Methods

fn new_runtime(name: &str) -> SdkResult<Self>

Create a new runtime-first agent.

fn new(name: &str) -> SdkResult<Self>

Create a new agent (alias of new_runtime)

fn new_with_config(config: AgentConfig) -> SdkResult<Self>

Create agent with custom configuration

fn with_service<T>(self, service: T) -> Self
fn get_service<T>(&self) -> Option<Arc<T>>
fn has_service<T>(&self) -> bool
fn add_skill(&mut self, skill_id: &str) -> SkillEntryBuilder<'_>

Register a skill via the fluent builder (handler optional — omit for metadata-only).

fn skill<F, Fut>(&mut self, name: &str, handler: F) -> SkillEntryBuilder<'_>
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?

Register a skill with a handler (add_skill(name).handler(handler)).

fn register_notification<F, Fut>(&mut self, name: &str, handler: F) -> SdkResult<()>
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?

Register a notification handler.

fn config(&self) -> &AgentConfig
fn list_skills(&self) -> Vec<String>
fn list_notifications(&self) -> Vec<String>
fn skill_registry(&self) -> &SkillRegistry

Get read-only reference to the skill registry.

fn set_message_handler<F, Fut>(&mut self, handler: F)
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?
async fn dispatch_message(&self, msg_ctx: MessageContext, task_ctx: Option<TaskContext>) -> SdkResult<RuntimeResponse>

Unified entrypoint to route a built MessageContext through the active handler.

Agent Configuration

Configuration for agent behavior, capabilities, and response patterns.

The agent supports different response patterns:

  • Stateful (default): Creates Task responses for conversation continuity
  • Stateless: Prefers lightweight Message responses for API-style interactions
use agent_sdk::agent::AgentConfig;
// Default stateful agent
let config = AgentConfig::default();
// Stateless agent for API-style interactions
let config = AgentConfig::new("api-agent", "API Agent")
.stateless();

Fields

FieldTypeDescription
nameString
descriptionString
versionString
storage_prefixOption&lt;String&gt;Optional storage namespace prefix used by shared task-storage backends.
max_message_sizeu64
streamingbool
batch_processingbool
concurrent_tasksOption&lt;u32&gt;
stateless_methodsboolPrefer stateless responses (Message) over stateful (Task) for methods
base_urlOption&lt;String&gt;Base URL for constructing absolute AgentInterface URLs in the agent card.
history_policyOption&lt;HistoryPolicyConfig&gt;Optional history policy used for durable continuation preparation.

Methods

fn new<impl Into<String>, impl Into<String>>(name: impl Into, description: impl Into) -> Self

Create a new agent configuration

  • name - Agent name
  • description - Agent description
use agent_sdk::agent::AgentConfig;
let config = AgentConfig::new("my-agent", "My Agent Description");
fn stateless(self) -> Self

Configure agent for stateless method responses

When enabled, methods will prefer lightweight Message responses over Task responses for API-style interactions.

use agent_sdk::agent::AgentConfig;
let config = AgentConfig::new("api-agent", "API Agent")
.stateless();
fn stateful(self) -> Self

Configure agent for stateful method responses (default)

Methods will create Task responses for conversation continuity.

use agent_sdk::agent::AgentConfig;
let config = AgentConfig::default()
.stateful(); // Explicit stateful (default behavior)
fn with_base_url<impl Into<String>>(self, url: impl Into) -> Self

Set the base URL for absolute AgentInterface URLs in the agent card.

The A2A v1.0 spec requires absolute URLs in AgentInterface.url. When set, the SDK produces {base_url}/jsonrpc instead of /jsonrpc.

fn with_history_policy(self, policy: HistoryPolicyConfig) -> Self

Attach a history policy for pre/post-turn continuation preparation.

Core Agent implementation

The Agent struct is the main runtime entry point for registering skills, wiring handlers, and dispatching runtime messages. Protocol surfaces such as A2A and AG-UI compose around this type via adapter modules.

Methods

fn new_runtime(name: &str) -> SdkResult<Self>

Create a new runtime-first agent.

fn new(name: &str) -> SdkResult<Self>

Create a new agent (alias of new_runtime)

fn new_with_config(config: AgentConfig) -> SdkResult<Self>

Create agent with custom configuration

fn with_service<T>(self, service: T) -> Self
fn get_service<T>(&self) -> Option<Arc<T>>
fn has_service<T>(&self) -> bool
fn add_skill(&mut self, skill_id: &str) -> SkillEntryBuilder<'_>

Register a skill via the fluent builder (handler optional — omit for metadata-only).

fn skill<F, Fut>(&mut self, name: &str, handler: F) -> SkillEntryBuilder<'_>
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?

Register a skill with a handler (add_skill(name).handler(handler)).

fn register_notification<F, Fut>(&mut self, name: &str, handler: F) -> SdkResult<()>
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?

Register a notification handler.

fn config(&self) -> &AgentConfig
fn list_skills(&self) -> Vec<String>
fn list_notifications(&self) -> Vec<String>
fn skill_registry(&self) -> &SkillRegistry

Get read-only reference to the skill registry.

fn set_message_handler<F, Fut>(&mut self, handler: F)
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?
async fn dispatch_message(&self, msg_ctx: MessageContext, task_ctx: Option<TaskContext>) -> SdkResult<RuntimeResponse>

Unified entrypoint to route a built MessageContext through the active handler.

Agent Configuration

Configuration for agent behavior, capabilities, and response patterns.

The agent supports different response patterns:

  • Stateful (default): Creates Task responses for conversation continuity
  • Stateless: Prefers lightweight Message responses for API-style interactions
use agent_sdk::agent::AgentConfig;
// Default stateful agent
let config = AgentConfig::default();
// Stateless agent for API-style interactions
let config = AgentConfig::new("api-agent", "API Agent")
.stateless();

Fields

FieldTypeDescription
nameString
descriptionString
versionString
storage_prefixOption&lt;String&gt;Optional storage namespace prefix used by shared task-storage backends.
max_message_sizeu64
streamingbool
batch_processingbool
concurrent_tasksOption&lt;u32&gt;
stateless_methodsboolPrefer stateless responses (Message) over stateful (Task) for methods
base_urlOption&lt;String&gt;Base URL for constructing absolute AgentInterface URLs in the agent card.
history_policyOption&lt;HistoryPolicyConfig&gt;Optional history policy used for durable continuation preparation.

Methods

fn new<impl Into<String>, impl Into<String>>(name: impl Into, description: impl Into) -> Self

Create a new agent configuration

  • name - Agent name
  • description - Agent description
use agent_sdk::agent::AgentConfig;
let config = AgentConfig::new("my-agent", "My Agent Description");
fn stateless(self) -> Self

Configure agent for stateless method responses

When enabled, methods will prefer lightweight Message responses over Task responses for API-style interactions.

use agent_sdk::agent::AgentConfig;
let config = AgentConfig::new("api-agent", "API Agent")
.stateless();
fn stateful(self) -> Self

Configure agent for stateful method responses (default)

Methods will create Task responses for conversation continuity.

use agent_sdk::agent::AgentConfig;
let config = AgentConfig::default()
.stateful(); // Explicit stateful (default behavior)
fn with_base_url<impl Into<String>>(self, url: impl Into) -> Self

Set the base URL for absolute AgentInterface URLs in the agent card.

The A2A v1.0 spec requires absolute URLs in AgentInterface.url. When set, the SDK produces {base_url}/jsonrpc instead of /jsonrpc.

fn with_history_policy(self, policy: HistoryPolicyConfig) -> Self

Attach a history policy for pre/post-turn continuation preparation.

Agent-level history policy configuration.

Fields

FieldTypeDescription
modeHistoryPolicyMode
strategyHistoryStrategyKind
context_window_tokensu32
max_output_tokensu32
enable_summarizationbool
enable_long_term_memorybool
recall_top_kusize
memory_token_budgetu32

Skill Call Information

Extracted from DataPart when a structured skill call is detected.

Fields

FieldTypeDescription
skill_idString
parametersserde_json::Value

Fluent builder for skill registration.

Created via [SkillRegistry::add_skill] / [crate::Agent::add_skill], or [SkillRegistry::skill] / [crate::Agent::skill] when providing a handler. Finalize with .register().

Methods

fn handler<F, Fut>(self, handler: F) -> Self
where
F: Fn + Send + Sync + ?,
Fut: Future + Send + ?

Attach an async handler. Omit for metadata-only skills (discovery card + LLM awareness).

fn display_name<S>(self, name: S) -> Self
fn description<S>(self, desc: S) -> Self
fn schema(self, schema: Value) -> Self
fn examples(self, examples: Vec<String>) -> Self
fn example<S>(self, example: S) -> Self
fn tags(self, tags: &[&str]) -> Self
fn tag<S>(self, tag: S) -> Self
fn input_modes(self, modes: &[&str]) -> Self
fn output_modes(self, modes: &[&str]) -> Self
fn json_only(self) -> Self
fn text_only(self) -> Self
fn instructions<S>(self, text: S) -> Self

Set behavioral instructions (injected into LLM context when skill is activated).

fn expose(self, visible: bool) -> Self

Control whether the skill is visible on the discovery card (default: true).

fn llm_callable(self, callable: bool) -> Self

Control whether the LLM can invoke this skill via read_skill tool (default: false).

fn register(self) -> SdkResult<()>

Finalize registration.

Single host value combining the shared [crate::Agent] with enabled protocol adapters.

Builder for [AgentHost] — enable A2A and/or AG-UI adapters before [Self::build].

Methods

fn new(agent: Agent) -> Self

Start from a fully built [crate::Agent].

fn build(self) -> SdkResult<AgentHost>

Finish configuration. Fails if no adapter was enabled (A2A and/or AG-UI).

A selectable option for interaction prompts.

Fields

FieldTypeDescription
idStringStable id for this option (used in selected_option_id on the response).
labelStringShort label shown in the UI.
descriptionOption&lt;String&gt;Optional longer description.

Request payload emitted by agents to ask for user input.

Fields

FieldTypeDescription
interaction_idStringCorrelates the request with [InteractionResponse::interaction_id].
kindInteractionKindQuestion vs confirmation flow.
questionStringPrompt text shown to the user.
optionsVec&lt;InteractionOption&gt;For choice-style prompts; empty if free-text only.
allow_free_textboolWhether the user may type an answer instead of picking an option.
allow_cancelboolWhether the user may dismiss without resolving (cancel).
default_option_idOption&lt;String&gt;Pre-selected option id when options is non-empty.
timeout_msOption&lt;u64&gt;Optional auto-expiry for the interaction (milliseconds).
continuation_idOption&lt;String&gt;Optional id for multi-step / resumed flows.
source_nodeOption&lt;String&gt;Optional coordination graph node id (if applicable).
metadataOption&lt;serde_json::Value&gt;Arbitrary extension payload.

Response payload for resolving a pending interaction.

Fields

FieldTypeDescription
interaction_idStringMust match the pending [InteractionRequest::interaction_id].
selected_option_idOption&lt;String&gt;Chosen option when the user picked from options.
free_textOption&lt;String&gt;Free-text answer when allow_free_text was true.
confirmedOption&lt;bool&gt;For confirmations: explicit true/false when applicable.
cancelledboolTrue when the user cancelled instead of resolving.
metadataOption&lt;serde_json::Value&gt;Arbitrary extension payload.

Top-level timeout configuration.

Propagated from CRD → AgentRuntimeConfigAgentBuilder → sub-components. OSS users get TimeoutPolicy::streaming_default() automatically via AgentBuilder::from_config_path().

Fields

FieldTypeDescription
connect_msu64TCP + TLS handshake timeout (ms). Default: 10_000.
first_byte_msu64Time until first data chunk arrives (ms). Default: 45_000.
idle_msu64Max silence between consecutive chunks (ms). Resets on each chunk. Default: 90_000.
activation_budget_msu64Total activation budget for cold-start retries (ms). Default: 60_000.
wall_clock_msOption&lt;u64&gt;Wall-clock ceiling for LLM execution loops (ms).

Methods

fn streaming_default() -> Self

Streaming-optimized defaults. Used for agents that serve SSE streams.

fn rpc_default() -> Self

RPC (non-streaming) defaults. Total wall-clock 30s, no idle.

fn to_streaming_policy(&self) -> StreamingPolicy

Derive a StreamingPolicy for transport-level clients.

Minimal service container for dependency injection

Allows agents to accept optional services through clean dependency injection without hardcoding specific service fields in the Agent struct.

use agent_sdk::services::ServiceContainer;
use std::sync::Arc;
#[derive(Clone)]
struct MyService {
name: String,
}
let mut container = ServiceContainer::new();
container.register(MyService { name: "test".to_string() });
let service: Option<Arc<MyService>> = container.get();
assert!(service.is_some());

Methods

fn new() -> Self

Create a new empty service container

fn register<T>(&mut self, service: T)

Register a service in the container

Services are stored by their type and can be retrieved later using the same type signature.

  • service - The service instance to register
# use agent_sdk::services::ServiceContainer;
# #[derive(Clone)]
# struct DatabaseService;
let mut container = ServiceContainer::new();
container.register(DatabaseService);
fn get<T>(&self) -> Option<Arc<T>>

Get a service from the container

Returns an Arc-wrapped service if found, or None if the service type is not registered.

  • Some(Arc<T>) - The service if found
  • None - If no service of type T is registered
# use agent_sdk::services::ServiceContainer;
# use std::sync::Arc;
# #[derive(Clone)]
# struct DatabaseService { pub name: String }
# let mut container = ServiceContainer::new();
# container.register(DatabaseService { name: "test".to_string() });
let service: Option<Arc<DatabaseService>> = container.get();
if let Some(db) = service {
println!("Database: {}", db.name);
}
fn has<T>(&self) -> bool

Check if a service type is registered

  • true - If a service of type T is registered
  • false - If no service of type T is found
# use agent_sdk::services::ServiceContainer;
# #[derive(Clone)]
# struct CacheService;
let container = ServiceContainer::new();
assert!(!container.has::<CacheService>());
fn len(&self) -> usize

Get the number of registered services

fn is_empty(&self) -> bool

Check if the container is empty

fn clear(&mut self)

Remove all services from the container

Resolved skill context ready for LLM message injection.

Produced by SkillRegistry::resolve_skill_context(). Contains the handler’s output (if any) and the skill’s instructions (if any).

Fields

FieldTypeDescription
skill_idString
handler_outputOption&lt;String&gt;
instructionsOption&lt;String&gt;

Protocol-independent skill definition.

This is the single source of truth for skill metadata inside the SDK. Protocol adapters (A2A AgentSkill, MCP, etc.) convert from this type.

Fields

FieldTypeDescription
idString
nameString
descriptionString
input_modesVec&lt;String&gt;
output_modesVec&lt;String&gt;
schemaOption&lt;serde_json::Value&gt;
examplesOption&lt;Vec&lt;String&gt;&gt;
tagsOption&lt;Vec&lt;String&gt;&gt;
instructionsOption&lt;String&gt;Behavioral guidance injected into LLM context when this skill is activated.
exposeboolWhether the skill is visible on the discovery card (default: true).
llm_callableboolWhether the LLM can invoke this skill’s handler via read_skill tool (default: false).

Fluent builder to construct the protocol-neutral [crate::Agent] runtime.

Methods

fn from_config(config: AgentConfig) -> SdkResult<Self>

Build from an in-memory [crate::agent::config::AgentConfig] (no JSON file required).

fn new(name: &str) -> SdkResult<Self>

Minimal builder with default [crate::agent::config::AgentConfig] except name.

fn from_config_path(path: &str) -> SdkResult<Self>
fn with_name(self, name: &str) -> Self
fn with_timeout_policy(self, policy: TimeoutPolicy) -> Self

Override the default timeout policy.

fn timeout_policy(&self) -> Option<&TimeoutPolicy>

Get the currently configured timeout policy.

fn build(self) -> SdkResult<Agent>

History preparation mode for durable continuation.

Variants

VariantDescription
PassThrough
HistoryManager

Strategy name exposed through SDK config without leaking llm_context_core types.

Variants

VariantDescription
SlidingWindow
SlidingWindowWithSummary
PriorityBased

Message Type Classification

Classifies incoming messages based on their Part composition to determine the appropriate handling strategy (tool-like vs conversational vs hybrid).

Variants

VariantDescription
DataPure DataPart - structured tool calls
TextPure TextPart - conversational interactions
MixedMultiple part types - hybrid interactions

SDK-specific error types

Wraps underlying A2A protocol errors and adds SDK-specific error cases for better error handling and user experience.

Variants

VariantDescription
A2AProtocol(error::A2AError)A2A protocol error
Configuration { ... }Configuration error
AgentInitialization { ... }Agent initialization error
SkillRegistration { ... }Skill registration error
MethodExecution { ... }Method execution error
Serialization(serde_json::Error)Serialization error
Io(Error)IO error
Generic(anyhow::Error)Generic error
FeatureNotEnabled { ... }Feature not enabled
InvalidInput { ... }Invalid input

Methods

fn configuration<impl Into<String>>(details: impl Into) -> Self

Create a configuration error

fn agent_initialization<impl Into<String>>(reason: impl Into) -> Self

Create an agent initialization error

fn skill_registration<impl Into<String>, impl Into<String>>(skill: impl Into, reason: impl Into) -> Self

Create a skill registration error

fn method_execution<impl Into<String>, impl Into<String>>(method: impl Into, details: impl Into) -> Self

Create a method execution error

fn feature_not_enabled<impl Into<String>>(feature: impl Into) -> Self

Create a feature not enabled error

fn invalid_input<impl Into<String>>(details: impl Into) -> Self

Create an invalid input error

fn is_recoverable(&self) -> bool

Check if error is recoverable

fn category(&self) -> &''static str

Get error category for monitoring/logging

Interaction kinds supported by the typed interaction contract.

Variants

VariantDescription
Question
Confirmation