1use std::{collections::BTreeMap, future::Future, pin::Pin, sync::Arc};
2
3use futures_util::{
4 StreamExt,
5 future::{Either, select},
6};
7use runifold_core::{
8 Budget, BudgetEvent, BudgetTracker, CapabilitySet, DomainEvent, EffectId, EffectKind,
9 EffectRequest, EventId, Instant, InvocationId, LifecycleEvent, RetrySafety, RunContext,
10 RunError, RunErrorKind, RunEventKind, Usage,
11};
12use runifold_effect::{
13 EffectExecutionContext, EffectExecutor, EffectExecutorErrorKind, EffectFuture, EffectHandler,
14 EffectRecoveryPolicy, InMemoryEffectStore,
15};
16use runifold_model::{
17 ContentPart, FeaturePolicy, GenerationOptions, Message, Model, ModelCallContext, ModelError,
18 ModelErrorKind, ModelRef, ModelRequest, ModelResponse, ModelStreamAccumulator, OutputFormat,
19 ProviderToolSpec, ResponseMode, Role, ToolCall, ToolChoice, ToolResult,
20};
21use runifold_retrieval::{Document, Retriever};
22use runifold_tool::{ToolError, ToolErrorKind, ToolOutput, ToolRegistry};
23use schemars::JsonSchema;
24use serde::{Deserialize, Serialize};
25
26use crate::checkpoint::CheckpointCursor;
27use crate::stream::{AgentObserver, BufferedObserver, NoopObserver, emit_agent_event};
28use crate::{
29 AgentCheckpoint, AgentCheckpointPhase, AgentCheckpointState, DurableConversationCheckpoint,
30 ResumePolicy,
31};
32
33const TOOL_RESULT_EXECUTION_ID_METADATA: &str = "runifold.agent.execution_id";
34use crate::{
35 AgentError, AgentEventStream, AgentGateway, AgentOutcome, AgentStreamEvent, CallableKind,
36 CompletionRequirement, GatewayError, GatewayErrorKind, StructuredAgent,
37};
38
39mod callable;
40mod checkpointing;
41pub(crate) mod completion;
42mod execution;
43mod observability;
44mod retrieval;
45
46#[cfg(not(target_arch = "wasm32"))]
48pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
49
50#[cfg(target_arch = "wasm32")]
52pub type AgentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
53
54#[derive(Clone)]
56pub(crate) struct DynamicContext {
57 pub(crate) limit: usize,
58 pub(crate) retriever: Arc<dyn Retriever>,
59}
60
61impl std::fmt::Debug for DynamicContext {
62 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 formatter
64 .debug_struct("DynamicContext")
65 .field("limit", &self.limit)
66 .field("retriever", self.retriever.descriptor())
67 .finish()
68 }
69}
70
71#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
73#[non_exhaustive]
74pub enum ToolErrorPolicy {
75 #[default]
77 ReturnToModel,
78 FailFast,
80}
81
82#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct AgentConfig {
85 pub max_turns: u32,
87 pub tool_error_policy: ToolErrorPolicy,
89 pub feature_policy: FeaturePolicy,
91}
92
93impl Default for AgentConfig {
94 fn default() -> Self {
95 Self {
96 max_turns: 16,
97 tool_error_policy: ToolErrorPolicy::ReturnToModel,
98 feature_policy: FeaturePolicy::Strict,
99 }
100 }
101}
102
103#[derive(Clone)]
105pub struct Agent {
106 pub(crate) name: String,
107 pub(crate) model: Arc<dyn Model>,
108 pub(crate) model_ref: ModelRef,
109 pub(crate) instructions: Vec<Message>,
110 pub(crate) context: Vec<Document>,
111 pub(crate) dynamic_context: Vec<DynamicContext>,
112 pub(crate) tools: ToolRegistry,
113 pub(crate) agents: AgentGateway,
114 pub(crate) effects: EffectExecutor,
115 pub(crate) effect_recovery: EffectRecoveryPolicy,
116 pub(crate) config: AgentConfig,
117 pub(crate) min_successful_tool_calls: u32,
118 pub(crate) output_format: OutputFormat,
119 pub(crate) generation: GenerationOptions,
120 pub(crate) response_mode: ResponseMode,
121 pub(crate) provider_tools: Vec<ProviderToolSpec>,
122 pub(crate) provider_options: BTreeMap<String, serde_json::Value>,
123 pub(crate) completion_requirement: CompletionRequirement,
124 pub(crate) completion_validator: completion::CompletionValidator,
125}
126
127impl Agent {
128 pub fn builder(
130 name: impl Into<String>,
131 model: Arc<dyn Model>,
132 model_ref: ModelRef,
133 ) -> crate::AgentBuilder {
134 crate::AgentBuilder::new(name, model, model_ref)
135 }
136
137 pub fn new(name: impl Into<String>, model: Arc<dyn Model>, model_ref: ModelRef) -> Self {
139 Self {
140 name: name.into(),
141 model,
142 model_ref,
143 instructions: Vec::new(),
144 context: Vec::new(),
145 dynamic_context: Vec::new(),
146 tools: ToolRegistry::new(),
147 agents: AgentGateway::new(),
148 effects: EffectExecutor::new(Arc::new(InMemoryEffectStore::new())),
149 effect_recovery: EffectRecoveryPolicy::RejectAmbiguous,
150 config: AgentConfig::default(),
151 min_successful_tool_calls: 0,
152 output_format: OutputFormat::Text,
153 generation: GenerationOptions::default(),
154 response_mode: ResponseMode::Streaming,
155 provider_tools: Vec::new(),
156 provider_options: BTreeMap::new(),
157 completion_requirement: CompletionRequirement::default(),
158 completion_validator: completion::CompletionValidator::content(),
159 }
160 }
161
162 #[must_use]
164 pub fn system(mut self, instruction: impl Into<String>) -> Self {
165 self.instructions.push(Message::system(instruction));
166 self
167 }
168
169 #[must_use]
171 pub fn tools(mut self, tools: ToolRegistry) -> Self {
172 self.tools = tools;
173 self
174 }
175
176 #[must_use]
178 pub fn agents(mut self, agents: AgentGateway) -> Self {
179 self.agents = agents;
180 self
181 }
182
183 #[must_use]
185 pub fn effect_executor(mut self, effects: EffectExecutor) -> Self {
186 self.effects = effects;
187 self
188 }
189
190 #[must_use]
192 pub const fn effect_recovery_policy(mut self, policy: EffectRecoveryPolicy) -> Self {
193 self.effect_recovery = policy;
194 self
195 }
196
197 #[must_use]
199 pub const fn with_config(mut self, config: AgentConfig) -> Self {
200 self.config = config;
201 self
202 }
203
204 #[must_use]
206 pub const fn completion_requirement(mut self, requirement: CompletionRequirement) -> Self {
207 self.completion_requirement = requirement;
208 self
209 }
210
211 #[must_use]
217 pub const fn min_successful_tool_calls(mut self, minimum: u32) -> Self {
218 self.min_successful_tool_calls = minimum;
219 self
220 }
221
222 #[must_use]
224 pub fn output_format(mut self, output_format: OutputFormat) -> Self {
225 self.output_format = output_format;
226 self
227 }
228
229 #[must_use]
231 pub fn structured_output<T>(self, name: impl Into<String>) -> Self
232 where
233 T: JsonSchema,
234 {
235 self.output_format(OutputFormat::typed::<T>(name))
236 }
237
238 pub fn into_structured<T>(mut self, name: impl Into<String>) -> StructuredAgent<T>
240 where
241 T: JsonSchema + serde::de::DeserializeOwned + Send + 'static,
242 {
243 self = self.structured_output::<T>(name);
244 self.completion_validator = completion::CompletionValidator::structured::<T>();
245 StructuredAgent::new(self)
246 }
247
248 pub fn name(&self) -> &str {
250 &self.name
251 }
252
253 pub const fn model_ref(&self) -> &ModelRef {
255 &self.model_ref
256 }
257
258 pub fn callable_capabilities(&self) -> CapabilitySet {
264 let mut capabilities = CapabilitySet::new();
265 for spec in self.tools.model_specs() {
266 if let Some(descriptor) = self.tools.descriptor(&spec.name) {
267 capabilities.grant(descriptor.capability());
268 }
269 }
270 for spec in self.agents.model_specs() {
271 if let Some(descriptor) = self.agents.descriptor(&spec.name) {
272 capabilities.grant(descriptor.capability());
273 }
274 }
275 for source in &self.dynamic_context {
276 capabilities.grant(source.retriever.descriptor().capability());
277 }
278 capabilities
279 }
280
281 #[must_use]
289 pub fn default_run_context(&self) -> RunContext {
290 RunContext::root(
291 BudgetTracker::new(Budget::default()),
292 self.callable_capabilities(),
293 )
294 }
295}
296
297impl std::fmt::Debug for Agent {
298 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 formatter
300 .debug_struct("Agent")
301 .field("name", &self.name)
302 .field("model_ref", &self.model_ref)
303 .field("instructions", &self.instructions)
304 .field("context", &self.context)
305 .field("dynamic_context", &self.dynamic_context)
306 .field("tools", &self.tools)
307 .field("agents", &self.agents)
308 .field("effects", &self.effects)
309 .field("effect_recovery", &self.effect_recovery)
310 .field("config", &self.config)
311 .field("output_format", &self.output_format)
312 .finish_non_exhaustive()
313 }
314}
315
316#[cfg(test)]
317mod tests;