Skip to main content

otherone_agent/multi_agent/
runtime.rs

1use std::collections::{HashMap, VecDeque};
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::sync::{Arc, Weak};
4use std::time::{Duration, Instant};
5
6use async_trait::async_trait;
7use futures::future::join_all;
8use futures::{FutureExt, StreamExt};
9use otherone_ai::error::AiError;
10use otherone_ai::traits::ChatStream;
11use otherone_ai::types::{FunctionDefinition, Message, MessageContent, Tool, ToolCall};
12use otherone_storage::types::{AttributeBag, StorageType as StorageBackend, WriteEntryOptions};
13use otherone_tools::types::{
14    ToolCallContext, ToolError, ToolErrorKind, ToolExecutionClass, ToolResult,
15};
16use otherone_tools::{ToolHandler, ToolRegistration, ToolRegistry};
17use serde::Deserialize;
18use tokio::sync::{mpsc, oneshot, Mutex, Notify};
19use tokio_util::sync::CancellationToken;
20
21use crate::error::AgentError;
22use crate::types::{ContextLoadType, StorageType};
23
24use super::event::EventBus;
25use super::registry::{
26    validate_snapshot, AgentRegistry, ModelRegistry, RuntimePolicy, RuntimeSnapshot,
27    SkillRegistrySnapshot, AGENT_CALL_TOOL_NAME, MEMORY_RECALL_TOOL_NAME, MEMORY_STORE_TOOL_NAME,
28};
29use super::supervisor::{BudgetLedger, InMemoryRunStore, RunContext, RunSupervisor, SessionLease};
30use super::types::{
31    merge_other, metadata_with_lineage, AgentCallId, AgentCallRequest, AgentCallResult,
32    AgentDefinition, AgentEvent, AgentFailure, AgentId, AgentInput, AgentOutput, AgentRunCommand,
33    AgentRunContextView, AgentRunRequest, AgentRunResult, EffectiveLimits, MemoryPolicy,
34    ModelProfile, ModelProfileId, ResultContract, RunId, RunOutcome, RunRecord, RunStatus,
35    RunUsage, RuntimeLimits,
36};
37
38const EVENT_CHANNEL_CAPACITY: usize = 512;
39const COMMAND_CHANNEL_CAPACITY: usize = 64;
40const ROOT_IDEMPOTENCY_CACHE_CAPACITY: usize = 1024;
41
42#[async_trait]
43pub trait ModelExecutor: Send + Sync {
44    async fn stream(
45        &self,
46        profile: &ModelProfile,
47        config: serde_json::Value,
48    ) -> Result<ChatStream, AiError>;
49}
50
51#[derive(Default)]
52pub struct DefaultModelExecutor;
53
54#[async_trait]
55impl ModelExecutor for DefaultModelExecutor {
56    async fn stream(
57        &self,
58        profile: &ModelProfile,
59        config: serde_json::Value,
60    ) -> Result<ChatStream, AiError> {
61        otherone_ai::invoke_model_stream(
62            profile.provider.clone(),
63            profile.api_key(),
64            &profile.base_url,
65            config,
66        )
67        .await
68    }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum AuthorizationDecision {
73    Allow,
74    Deny(String),
75}
76
77#[async_trait]
78pub trait AgentCallAuthorizer: Send + Sync {
79    async fn authorize(
80        &self,
81        caller: &AgentRunContextView,
82        target: &AgentDefinition,
83        request: &AgentCallRequest,
84    ) -> Result<AuthorizationDecision, AgentError>;
85}
86
87#[derive(Default)]
88struct AllowAllAuthorizer;
89
90#[async_trait]
91impl AgentCallAuthorizer for AllowAllAuthorizer {
92    async fn authorize(
93        &self,
94        _caller: &AgentRunContextView,
95        _target: &AgentDefinition,
96        _request: &AgentCallRequest,
97    ) -> Result<AuthorizationDecision, AgentError> {
98        Ok(AuthorizationDecision::Allow)
99    }
100}
101
102pub struct AgentRuntimeBuilder {
103    agents: Vec<AgentDefinition>,
104    models: Vec<ModelProfile>,
105    tools: Vec<ToolRegistration>,
106    skills: SkillRegistrySnapshot,
107    default_model: Option<ModelProfileId>,
108    policy: RuntimePolicy,
109    limits: RuntimeLimits,
110    model_executor: Arc<dyn ModelExecutor>,
111    authorizer: Arc<dyn AgentCallAuthorizer>,
112}
113
114impl Default for AgentRuntimeBuilder {
115    fn default() -> Self {
116        Self {
117            agents: Vec::new(),
118            models: Vec::new(),
119            tools: Vec::new(),
120            skills: SkillRegistrySnapshot::default(),
121            default_model: None,
122            policy: RuntimePolicy::default(),
123            limits: RuntimeLimits::default(),
124            model_executor: Arc::new(DefaultModelExecutor),
125            authorizer: Arc::new(AllowAllAuthorizer),
126        }
127    }
128}
129
130impl AgentRuntimeBuilder {
131    pub fn register_agent(mut self, definition: AgentDefinition) -> Self {
132        self.agents.push(definition);
133        self
134    }
135
136    pub fn register_model(mut self, profile: ModelProfile) -> Self {
137        self.models.push(profile);
138        self
139    }
140
141    pub fn register_tool(mut self, registration: ToolRegistration) -> Self {
142        self.tools.push(registration);
143        self
144    }
145
146    pub fn skills(mut self, registry: &otherone_skills::SkillRegistry) -> Self {
147        self.skills = SkillRegistrySnapshot::from_registry(registry);
148        self
149    }
150
151    pub fn default_model(mut self, id: impl Into<String>) -> Self {
152        self.default_model = Some(ModelProfileId::unchecked(id));
153        self
154    }
155
156    pub fn policy(mut self, policy: RuntimePolicy) -> Self {
157        self.policy = policy;
158        self
159    }
160
161    pub fn runtime_limits(mut self, limits: RuntimeLimits) -> Self {
162        self.limits = limits;
163        self
164    }
165
166    pub fn model_executor(mut self, executor: Arc<dyn ModelExecutor>) -> Self {
167        self.model_executor = executor;
168        self
169    }
170
171    pub fn authorizer(mut self, authorizer: Arc<dyn AgentCallAuthorizer>) -> Self {
172        self.authorizer = authorizer;
173        self
174    }
175
176    pub fn build(self) -> Result<AgentRuntime, AgentError> {
177        self.limits.validate()?;
178
179        let mut agents = AgentRegistry::new();
180        for definition in self.agents {
181            agents.register(definition)?;
182        }
183
184        let mut models = ModelRegistry::new();
185        for profile in self.models {
186            models.register(profile)?;
187        }
188
189        let default_model = match self.default_model {
190            Some(id) => {
191                id.validate()?;
192                id
193            }
194            None => models.ids().into_iter().next().ok_or_else(|| {
195                AgentError::InvalidConfiguration(
196                    "at least one model profile must be registered".to_string(),
197                )
198            })?,
199        };
200
201        let mut user_tools = ToolRegistry::new();
202        for registration in self.tools {
203            if matches!(
204                registration.name(),
205                AGENT_CALL_TOOL_NAME | MEMORY_RECALL_TOOL_NAME | MEMORY_STORE_TOOL_NAME
206            ) {
207                return Err(AgentError::InvalidConfiguration(format!(
208                    "tool name '{}' is reserved by Otherone",
209                    registration.name()
210                )));
211            }
212            user_tools
213                .register(registration)
214                .map_err(|error| AgentError::InvalidConfiguration(error.to_string()))?;
215        }
216
217        let agent_snapshot = Arc::new(agents.snapshot());
218        let model_snapshot = Arc::new(models.snapshot());
219        let skill_snapshot = Arc::new(self.skills);
220        let policy = Arc::new(self.policy);
221        let limits = self.limits;
222        let model_executor = self.model_executor;
223        let authorizer = self.authorizer;
224
225        let inner = Arc::new_cyclic(|weak| {
226            let mut tools = user_tools.clone();
227            tools
228                .register(agent_call_registration(weak.clone()))
229                .expect("reserved agent call tool must register");
230
231            let memory_lock = global_memory_lock();
232            tools
233                .register(memory_recall_registration(Arc::clone(&memory_lock)))
234                .expect("reserved memory recall tool must register");
235            tools
236                .register(memory_store_registration(memory_lock))
237                .expect("reserved memory store tool must register");
238
239            let snapshot = Arc::new(RuntimeSnapshot {
240                version: uuid::Uuid::new_v4().to_string(),
241                default_model: default_model.clone(),
242                agents: Arc::clone(&agent_snapshot),
243                models: Arc::clone(&model_snapshot),
244                tools: Arc::new(tools),
245                skills: Arc::clone(&skill_snapshot),
246                policy: Arc::clone(&policy),
247            });
248
249            RuntimeInner {
250                snapshot,
251                supervisor: RunSupervisor::new(&limits),
252                limits: limits.clone(),
253                model_executor: Arc::clone(&model_executor),
254                authorizer: Arc::clone(&authorizer),
255                call_slots: Mutex::new(HashMap::new()),
256                root_slots: Mutex::new(HashMap::new()),
257            }
258        });
259
260        validate_snapshot(&inner.snapshot)?;
261        Ok(AgentRuntime { inner })
262    }
263}
264
265#[derive(Clone)]
266pub struct AgentRuntime {
267    inner: Arc<RuntimeInner>,
268}
269
270impl AgentRuntime {
271    pub fn builder() -> AgentRuntimeBuilder {
272        AgentRuntimeBuilder::default()
273    }
274
275    pub fn snapshot(&self) -> Arc<RuntimeSnapshot> {
276        Arc::clone(&self.inner.snapshot)
277    }
278
279    pub fn run_store(&self) -> InMemoryRunStore {
280        self.inner.supervisor.run_store.clone()
281    }
282
283    pub async fn run(&self, mut request: AgentRunRequest) -> Result<AgentRunResult, AgentError> {
284        let Some(idempotency_key) = request.idempotency_key.take() else {
285            return self.start_internal(request).await?.result().await;
286        };
287        if idempotency_key.is_empty() || idempotency_key.len() > 256 {
288            return Err(AgentError::InvalidConfiguration(
289                "idempotency_key must contain 1-256 characters".to_string(),
290            ));
291        }
292        validate_root_request(&request)?;
293        let key = RootCallKey {
294            partition_key: request
295                .runtime_context
296                .as_ref()
297                .map(|context| context.partition_key.clone())
298                .unwrap_or_else(|| "local".to_string()),
299            idempotency_key,
300        };
301        let (slot, should_start) = {
302            let mut slots = self.inner.root_slots.lock().await;
303            if !slots.contains_key(&key) && slots.len() >= ROOT_IDEMPOTENCY_CACHE_CAPACITY {
304                if let Some(completed_key) = slots
305                    .iter()
306                    .find_map(|(key, slot)| slot.is_complete().then(|| key.clone()))
307                {
308                    slots.remove(&completed_key);
309                } else {
310                    return Err(AgentError::BudgetExceeded(
311                        "root_idempotency_cache_capacity".to_string(),
312                    ));
313                }
314            }
315            match slots.entry(key) {
316                std::collections::hash_map::Entry::Occupied(entry) => {
317                    (Arc::clone(entry.get()), false)
318                }
319                std::collections::hash_map::Entry::Vacant(entry) => {
320                    let slot = Arc::new(CompletionSlot::new());
321                    entry.insert(Arc::clone(&slot));
322                    (slot, true)
323                }
324            }
325        };
326        if should_start {
327            let runtime = self.clone();
328            let producer_slot = Arc::clone(&slot);
329            tokio::spawn(async move {
330                let result = match runtime.start_internal(request).await {
331                    Ok(handle) => handle.result().await,
332                    Err(error) => Err(error),
333                }
334                .map_err(SharedAgentError::from_error);
335                producer_slot.complete(result);
336            });
337        }
338        slot.wait().await.map_err(SharedAgentError::into_error)
339    }
340
341    pub async fn start(&self, request: AgentRunRequest) -> Result<AgentRunHandle, AgentError> {
342        if request.idempotency_key.is_some() {
343            return Err(AgentError::InvalidConfiguration(
344                "streaming start does not replay idempotent event streams; use run() for root idempotency"
345                    .to_string(),
346            ));
347        }
348        self.start_internal(request).await
349    }
350
351    async fn start_internal(&self, request: AgentRunRequest) -> Result<AgentRunHandle, AgentError> {
352        validate_root_request(&request)?;
353        let agent = self.inner.snapshot.agent(&request.entry_agent)?;
354        let model = self.inner.snapshot.resolve_model(&agent, None)?;
355        let effective_limits =
356            EffectiveLimits::from_request(&self.inner.limits, request.limits.as_ref());
357        effective_limits.runtime.validate()?;
358        let budget = Arc::new(BudgetLedger::new(effective_limits));
359        budget.reserve_run()?;
360
361        let run_id = RunId::new();
362        let root_run_id = run_id.clone();
363        let session_id = request
364            .session
365            .session_id
366            .clone()
367            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
368        let cancellation = CancellationToken::new();
369        let (event_tx, event_rx) = mpsc::channel(EVENT_CHANNEL_CAPACITY);
370        let events = EventBus::new(
371            event_tx,
372            request.event_visibility,
373            request.include_thinking_events,
374        );
375        let (command_tx, command_rx) = mpsc::channel(COMMAND_CHANNEL_CAPACITY);
376        let metadata =
377            metadata_with_lineage(&request.metadata, &root_run_id, None, None, &agent.id, 0);
378        let deadline = run_deadline(
379            Instant::now(),
380            budget.limits().runtime.root_timeout,
381            budget.limits().runtime.run_timeout,
382            agent.limits.timeout_millis,
383            None,
384        );
385        let mut session = request.session.clone();
386        session.session_id = Some(session_id.clone());
387
388        let context = RunContext {
389            run_id: run_id.clone(),
390            root_run_id: root_run_id.clone(),
391            parent_run_id: None,
392            agent_call_id: None,
393            agent: Arc::clone(&agent),
394            model,
395            session_id: session_id.clone(),
396            parent_session_id: None,
397            session,
398            runtime_context: request.runtime_context.clone(),
399            depth: 0,
400            ancestry: vec![agent.id.clone()],
401            deadline,
402            max_iterations_override: None,
403            result_contract: None,
404            cancellation: cancellation.clone(),
405            budget,
406            snapshot: Arc::clone(&self.inner.snapshot),
407            events,
408            child_count: Arc::new(AtomicUsize::new(0)),
409            active_children: Arc::new(AtomicUsize::new(0)),
410            usage: Arc::new(std::sync::Mutex::new(RunUsage::default())),
411            metadata,
412        };
413
414        let lease = self
415            .inner
416            .supervisor
417            .register(context.clone(), command_tx)
418            .await?;
419        if let Err(error) = self.inner.create_run_record(&context).await {
420            self.inner.supervisor.unregister(&run_id).await;
421            drop(lease);
422            return Err(error);
423        }
424
425        let (result_tx, result_rx) = oneshot::channel();
426        let inner = Arc::clone(&self.inner);
427        tokio::spawn(async move {
428            let result = inner
429                .execute_run(context, request.input, command_rx, lease)
430                .await;
431            let _ = result_tx.send(result);
432        });
433
434        Ok(AgentRunHandle {
435            run_id: run_id.clone(),
436            events: event_rx,
437            commands: AgentRunCommandSender {
438                runtime: self.clone(),
439                run_id,
440            },
441            result: result_rx,
442        })
443    }
444
445    pub async fn cancel(&self, run_id: &RunId) -> Result<(), AgentError> {
446        self.inner.supervisor.cancel(run_id).await
447    }
448
449    pub async fn send_message(
450        &self,
451        run_id: &RunId,
452        messages: Vec<String>,
453    ) -> Result<(), AgentError> {
454        self.inner.supervisor.send_message(run_id, messages).await
455    }
456
457    pub async fn send_command(
458        &self,
459        run_id: &RunId,
460        command: AgentRunCommand,
461    ) -> Result<(), AgentError> {
462        match command {
463            AgentRunCommand::EnqueueUserMessages(messages) => {
464                self.send_message(run_id, messages).await
465            }
466            AgentRunCommand::Cancel => self.cancel(run_id).await,
467        }
468    }
469
470    pub async fn get_run(&self, run_id: &RunId) -> Option<RunRecord> {
471        self.inner.supervisor.run_store.get(run_id).await
472    }
473
474    pub async fn list_run_tree(&self, root_run_id: &RunId) -> Vec<RunRecord> {
475        self.inner.supervisor.run_store.list_root(root_run_id).await
476    }
477
478    pub async fn call_agent(
479        &self,
480        caller_run_id: &RunId,
481        call_id: AgentCallId,
482        request: AgentCallRequest,
483    ) -> Result<AgentCallResult, AgentError> {
484        RuntimeInner::call_agent(Arc::clone(&self.inner), caller_run_id, call_id, request).await
485    }
486}
487
488pub struct AgentRunHandle {
489    pub run_id: RunId,
490    pub events: mpsc::Receiver<super::types::AgentEventEnvelope>,
491    pub commands: AgentRunCommandSender,
492    result: oneshot::Receiver<AgentRunResult>,
493}
494
495impl AgentRunHandle {
496    pub async fn result(self) -> Result<AgentRunResult, AgentError> {
497        self.result
498            .await
499            .map_err(|_| AgentError::Internal("Agent run task stopped unexpectedly".to_string()))
500    }
501}
502
503#[derive(Clone)]
504pub struct AgentRunCommandSender {
505    runtime: AgentRuntime,
506    run_id: RunId,
507}
508
509impl AgentRunCommandSender {
510    pub async fn enqueue(&self, messages: Vec<String>) -> Result<(), AgentError> {
511        self.runtime.send_message(&self.run_id, messages).await
512    }
513
514    pub async fn cancel(&self) -> Result<(), AgentError> {
515        self.runtime.cancel(&self.run_id).await
516    }
517}
518
519struct RuntimeInner {
520    snapshot: Arc<RuntimeSnapshot>,
521    supervisor: RunSupervisor,
522    limits: RuntimeLimits,
523    model_executor: Arc<dyn ModelExecutor>,
524    authorizer: Arc<dyn AgentCallAuthorizer>,
525    call_slots: Mutex<HashMap<CallKey, Arc<CompletionSlot<AgentCallResult>>>>,
526    root_slots:
527        Mutex<HashMap<RootCallKey, Arc<CompletionSlot<Result<AgentRunResult, SharedAgentError>>>>>,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq, Hash)]
531struct CallKey {
532    root_run_id: RunId,
533    caller_run_id: RunId,
534    call_id: AgentCallId,
535}
536
537#[derive(Debug, Clone, PartialEq, Eq, Hash)]
538struct RootCallKey {
539    partition_key: String,
540    idempotency_key: String,
541}
542
543struct CompletionSlot<T> {
544    value: std::sync::OnceLock<T>,
545    notify: Notify,
546}
547
548impl<T> CompletionSlot<T>
549where
550    T: Clone,
551{
552    fn new() -> Self {
553        Self {
554            value: std::sync::OnceLock::new(),
555            notify: Notify::new(),
556        }
557    }
558
559    fn is_complete(&self) -> bool {
560        self.value.get().is_some()
561    }
562
563    fn complete(&self, value: T) {
564        let _ = self.value.set(value);
565        self.notify.notify_waiters();
566    }
567
568    async fn wait(&self) -> T {
569        loop {
570            let notified = self.notify.notified();
571            if let Some(value) = self.value.get() {
572                return value.clone();
573            }
574            notified.await;
575        }
576    }
577}
578
579#[derive(Clone)]
580enum SharedAgentError {
581    InvalidConfiguration(String),
582    AgentNotFound(String),
583    AgentCallDenied { caller: String, target: String },
584    AgentCallCycle(String),
585    MaxDepthExceeded(usize),
586    BudgetExceeded(String),
587    SessionBusy(String),
588    TimedOut,
589    Cancelled,
590    ContextError(String),
591    ToolError(String),
592    StorageError(String),
593    Internal(String),
594    MaxIterationsExceeded(u32),
595}
596
597impl SharedAgentError {
598    fn from_error(error: AgentError) -> Self {
599        match error {
600            AgentError::InvalidConfiguration(value) => Self::InvalidConfiguration(value),
601            AgentError::AgentNotFound(value) => Self::AgentNotFound(value),
602            AgentError::AgentCallDenied { caller, target } => {
603                Self::AgentCallDenied { caller, target }
604            }
605            AgentError::AgentCallCycle(value) => Self::AgentCallCycle(value),
606            AgentError::MaxDepthExceeded(value) => Self::MaxDepthExceeded(value),
607            AgentError::BudgetExceeded(value) => Self::BudgetExceeded(value),
608            AgentError::SessionBusy(value) => Self::SessionBusy(value),
609            AgentError::TimedOut => Self::TimedOut,
610            AgentError::Cancelled => Self::Cancelled,
611            AgentError::AiError(value) => Self::Internal(value.to_string()),
612            AgentError::ContextError(value) => Self::ContextError(value),
613            AgentError::ToolError(value) => Self::ToolError(value),
614            AgentError::StorageError(value) => Self::StorageError(value),
615            AgentError::Internal(value) => Self::Internal(value),
616            AgentError::MaxIterationsExceeded(value) => Self::MaxIterationsExceeded(value),
617        }
618    }
619
620    fn into_error(self) -> AgentError {
621        match self {
622            Self::InvalidConfiguration(value) => AgentError::InvalidConfiguration(value),
623            Self::AgentNotFound(value) => AgentError::AgentNotFound(value),
624            Self::AgentCallDenied { caller, target } => {
625                AgentError::AgentCallDenied { caller, target }
626            }
627            Self::AgentCallCycle(value) => AgentError::AgentCallCycle(value),
628            Self::MaxDepthExceeded(value) => AgentError::MaxDepthExceeded(value),
629            Self::BudgetExceeded(value) => AgentError::BudgetExceeded(value),
630            Self::SessionBusy(value) => AgentError::SessionBusy(value),
631            Self::TimedOut => AgentError::TimedOut,
632            Self::Cancelled => AgentError::Cancelled,
633            Self::ContextError(value) => AgentError::ContextError(value),
634            Self::ToolError(value) => AgentError::ToolError(value),
635            Self::StorageError(value) => AgentError::StorageError(value),
636            Self::Internal(value) => AgentError::Internal(value),
637            Self::MaxIterationsExceeded(value) => AgentError::MaxIterationsExceeded(value),
638        }
639    }
640}
641
642impl RuntimeInner {
643    async fn create_run_record(&self, context: &RunContext) -> Result<(), AgentError> {
644        let mut metadata = context.metadata.clone();
645        if let Some(parent_session_id) = &context.parent_session_id {
646            metadata.insert(
647                "parent_session_id".to_string(),
648                serde_json::json!(parent_session_id),
649            );
650        }
651        self.supervisor
652            .run_store
653            .create(RunRecord {
654                run_id: context.run_id.clone(),
655                root_run_id: context.root_run_id.clone(),
656                parent_run_id: context.parent_run_id.clone(),
657                agent_call_id: context.agent_call_id.clone(),
658                agent_id: context.agent.id.clone(),
659                agent_definition_version: context.agent.version,
660                session_id: context.session_id.clone(),
661                status: RunStatus::Created,
662                depth: context.depth,
663                started_at: chrono::Utc::now().to_rfc3339(),
664                finished_at: None,
665                usage: RunUsage::default(),
666                failure: None,
667                metadata,
668            })
669            .await
670    }
671
672    async fn execute_run(
673        self: Arc<Self>,
674        context: RunContext,
675        input: AgentInput,
676        mut command_rx: mpsc::Receiver<AgentRunCommand>,
677        lease: SessionLease,
678    ) -> AgentRunResult {
679        let started = Instant::now();
680        let _ = self
681            .supervisor
682            .run_store
683            .update_status(&context.run_id, RunStatus::Running)
684            .await;
685        emit(&context, AgentEvent::RunStarted);
686
687        let run_loop =
688            std::panic::AssertUnwindSafe(self.run_loop(&context, input, &mut command_rx))
689                .catch_unwind();
690        tokio::pin!(run_loop);
691        let execution = tokio::select! {
692            _ = context.cancellation.cancelled() => Err(AgentError::Cancelled),
693            _ = tokio::time::sleep_until(tokio::time::Instant::from_std(context.deadline)) => {
694                context.cancellation.cancel();
695                Err(AgentError::TimedOut)
696            }
697            result = &mut run_loop => match result {
698                Ok(result) => result,
699                Err(_) => Err(AgentError::Internal("Agent run panicked".to_string())),
700            },
701        };
702
703        let duration = started.elapsed().as_millis().min(u64::MAX as u128) as u64;
704        let (outcome, output, usage, failure, status, terminal_event) = match execution {
705            Ok((output, mut usage)) => {
706                usage.duration_millis = duration;
707                let output = match &context.result_contract {
708                    Some(contract) => apply_result_contract(Some(output), contract),
709                    None => Ok(Some(output)),
710                };
711                match output {
712                    Ok(output) => (
713                        RunOutcome::Completed,
714                        output,
715                        usage,
716                        None,
717                        RunStatus::Completed,
718                        AgentEvent::RunCompleted,
719                    ),
720                    Err(failure) => (
721                        RunOutcome::Failed(failure.clone()),
722                        None,
723                        usage,
724                        Some(failure.clone()),
725                        RunStatus::Failed,
726                        AgentEvent::RunFailed { failure },
727                    ),
728                }
729            }
730            Err(error) => {
731                let (outcome, status, event, failure) = error_outcome(&error);
732                let mut usage = context.usage_snapshot();
733                usage.duration_millis = duration;
734                (outcome, None, usage, failure.clone(), status, event)
735            }
736        };
737
738        if let Some(output) = output.as_ref() {
739            if output.byte_len() > effective_result_limit(&context) {
740                let failure = AgentFailure {
741                    code: "max_result_bytes".to_string(),
742                    message: "Agent result exceeds the configured size limit".to_string(),
743                    retryable: false,
744                };
745                let result = AgentRunResult {
746                    run_id: context.run_id.clone(),
747                    root_run_id: context.root_run_id.clone(),
748                    agent_id: context.agent.id.clone(),
749                    session_id: context.session_id.clone(),
750                    outcome: RunOutcome::BudgetExceeded,
751                    output: None,
752                    usage: usage.clone(),
753                    artifacts: Vec::new(),
754                    metadata: context.metadata.clone(),
755                };
756                let _ = self
757                    .supervisor
758                    .run_store
759                    .finish(
760                        &context.run_id,
761                        RunStatus::BudgetExceeded,
762                        usage,
763                        Some(failure.clone()),
764                    )
765                    .await;
766                emit(&context, AgentEvent::RunFailed { failure });
767                self.supervisor.unregister(&context.run_id).await;
768                if context.depth == 0 {
769                    self.call_slots
770                        .lock()
771                        .await
772                        .retain(|key, _| key.root_run_id != context.root_run_id);
773                }
774                drop(lease);
775                return result;
776            }
777        }
778
779        let _ = self
780            .supervisor
781            .run_store
782            .finish(&context.run_id, status, usage.clone(), failure)
783            .await;
784        emit(&context, terminal_event);
785        self.supervisor.unregister(&context.run_id).await;
786        if context.depth == 0 {
787            self.call_slots
788                .lock()
789                .await
790                .retain(|key, _| key.root_run_id != context.root_run_id);
791        }
792        drop(lease);
793
794        AgentRunResult {
795            run_id: context.run_id.clone(),
796            root_run_id: context.root_run_id.clone(),
797            agent_id: context.agent.id.clone(),
798            session_id: context.session_id.clone(),
799            outcome,
800            output,
801            usage,
802            artifacts: Vec::new(),
803            metadata: context.metadata.clone(),
804        }
805    }
806
807    async fn run_loop(
808        &self,
809        context: &RunContext,
810        input: AgentInput,
811        command_rx: &mut mpsc::Receiver<AgentRunCommand>,
812    ) -> Result<(AgentOutput, RunUsage), AgentError> {
813        persist_input(context, input).await?;
814        let mut queued_messages = VecDeque::new();
815        let mut usage = RunUsage::default();
816        let max_iterations = context
817            .agent
818            .limits
819            .max_iterations
820            .unwrap_or(context.budget.limits().runtime.max_iterations_per_run)
821            .min(context.budget.limits().runtime.max_iterations_per_run)
822            .min(
823                context
824                    .max_iterations_override
825                    .unwrap_or(context.budget.limits().runtime.max_iterations_per_run),
826            );
827        let (tool_registry, tool_definitions) = tool_registry_for(context)?;
828        let system_prompt = system_prompt_for(context);
829
830        for _ in 0..max_iterations {
831            drain_commands(context, command_rx, &mut queued_messages)?;
832            persist_queued_messages(context, &mut queued_messages).await?;
833
834            let messages = combine_context(context, &system_prompt, &tool_definitions).await?;
835            context.budget.reserve_model_call()?;
836            usage.model_calls = usage.model_calls.saturating_add(1);
837            context.record_usage(&usage);
838            emit(context, AgentEvent::ModelStarted);
839
840            let model_config = build_model_config(context, &messages, &tool_definitions);
841            let permit = self
842                .supervisor
843                .model_semaphore
844                .acquire()
845                .await
846                .map_err(|_| AgentError::Internal("model semaphore closed".to_string()))?;
847            let stream = self
848                .model_executor
849                .stream(&context.model, model_config)
850                .await?;
851            let response = collect_model_stream(context, stream).await;
852            drop(permit);
853            let response = response?;
854
855            usage.input_tokens = add_optional(usage.input_tokens, response.input_tokens);
856            usage.output_tokens = add_optional(usage.output_tokens, response.output_tokens);
857            usage.total_tokens = add_optional(usage.total_tokens, response.total_tokens);
858            context.record_usage(&usage);
859            if let Some(tokens) = response.total_tokens {
860                context.budget.add_tokens(tokens)?;
861            }
862            emit(
863                context,
864                AgentEvent::ModelCompleted {
865                    usage: response.usage(),
866                },
867            );
868
869            persist_assistant_response(context, &response).await?;
870
871            if response.tool_calls.is_empty() {
872                drain_commands(context, command_rx, &mut queued_messages)?;
873                if !queued_messages.is_empty() {
874                    persist_queued_messages(context, &mut queued_messages).await?;
875                    continue;
876                }
877                context.budget.add_output_bytes(response.content.len())?;
878                return Ok((AgentOutput::Text(response.content), usage));
879            }
880
881            for _ in &response.tool_calls {
882                context.budget.reserve_tool_call()?;
883            }
884            usage.tool_calls = usage
885                .tool_calls
886                .saturating_add(response.tool_calls.len() as u32);
887            usage.agent_calls = usage.agent_calls.saturating_add(
888                response
889                    .tool_calls
890                    .iter()
891                    .filter(|call| call.function.name == AGENT_CALL_TOOL_NAME)
892                    .count() as u32,
893            );
894            context.record_usage(&usage);
895
896            let parallel = context
897                .agent
898                .model_overrides
899                .parallel_tool_calls
900                .unwrap_or(false);
901            let results = self
902                .execute_tools(
903                    context,
904                    Arc::clone(&tool_registry),
905                    &response.tool_calls,
906                    parallel,
907                )
908                .await;
909            if serde_json::to_vec(&results)
910                .map(|value| value.len())
911                .unwrap_or(usize::MAX)
912                > effective_result_limit(context)
913            {
914                return Err(AgentError::BudgetExceeded("max_result_bytes".to_string()));
915            }
916            if let Some(result) = results
917                .iter()
918                .find(|result| result.error_kind == Some(ToolErrorKind::Fatal))
919            {
920                return Err(AgentError::ToolError(format!(
921                    "tool '{}' failed fatally: {}",
922                    result.function_name,
923                    result.error.as_deref().unwrap_or("unknown fatal error")
924                )));
925            }
926            persist_tool_results(context, &results).await?;
927
928            drain_commands(context, command_rx, &mut queued_messages)?;
929            persist_queued_messages(context, &mut queued_messages).await?;
930        }
931
932        Err(AgentError::MaxIterationsExceeded(max_iterations))
933    }
934
935    async fn execute_tools(
936        &self,
937        context: &RunContext,
938        registry: Arc<ToolRegistry>,
939        calls: &[ToolCall],
940        parallel: bool,
941    ) -> Vec<ToolResult> {
942        if parallel && calls.len() > 1 {
943            join_all(
944                calls
945                    .iter()
946                    .cloned()
947                    .map(|call| self.execute_tool(context, Arc::clone(&registry), call)),
948            )
949            .await
950        } else {
951            let mut results = Vec::with_capacity(calls.len());
952            for call in calls.iter().cloned() {
953                results.push(
954                    self.execute_tool(context, Arc::clone(&registry), call)
955                        .await,
956                );
957            }
958            results
959        }
960    }
961
962    async fn execute_tool(
963        &self,
964        context: &RunContext,
965        registry: Arc<ToolRegistry>,
966        call: ToolCall,
967    ) -> ToolResult {
968        emit(
969            context,
970            AgentEvent::ToolStarted {
971                tool_call_id: call.id.clone(),
972                tool_name: call.function.name.clone(),
973            },
974        );
975
976        let execution_class = registry
977            .get(&call.function.name)
978            .map(|registration| registration.execution_class)
979            .unwrap_or(ToolExecutionClass::Standard);
980        let tool_context = ToolCallContext {
981            tool_call_id: call.id.clone(),
982            run_id: context.run_id.to_string(),
983            root_run_id: context.root_run_id.to_string(),
984            agent_id: context.agent.id.to_string(),
985            session_id: Some(context.session_id.clone()),
986            runtime_context: context
987                .runtime_context
988                .as_ref()
989                .and_then(|value| serde_json::to_value(value).ok()),
990            metadata: serde_json::json!({ "memory_policy": context.agent.memory }),
991            deadline: Some(context.deadline),
992            cancellation: context.cancellation.clone(),
993        };
994
995        let result = if execution_class == ToolExecutionClass::Delegating {
996            registry.execute(&call, tool_context).await
997        } else {
998            match self.supervisor.tool_semaphore.acquire().await {
999                Ok(_permit) => registry.execute(&call, tool_context).await,
1000                Err(_) => ToolResult {
1001                    tool_call_id: call.id.clone(),
1002                    function_name: call.function.name.clone(),
1003                    result: None,
1004                    error: Some("tool semaphore closed".to_string()),
1005                    error_kind: Some(ToolErrorKind::Fatal),
1006                },
1007            }
1008        };
1009
1010        emit(
1011            context,
1012            AgentEvent::ToolCompleted {
1013                tool_call_id: call.id,
1014                tool_name: call.function.name,
1015                error: result.error.clone(),
1016            },
1017        );
1018        result
1019    }
1020
1021    async fn call_agent(
1022        self: Arc<Self>,
1023        caller_run_id: &RunId,
1024        call_id: AgentCallId,
1025        request: AgentCallRequest,
1026    ) -> Result<AgentCallResult, AgentError> {
1027        let caller = self
1028            .supervisor
1029            .active(caller_run_id)
1030            .await
1031            .ok_or_else(|| {
1032                AgentError::InvalidConfiguration(format!(
1033                    "caller run '{caller_run_id}' is not active"
1034                ))
1035            })?
1036            .context;
1037        let key = CallKey {
1038            root_run_id: caller.root_run_id.clone(),
1039            caller_run_id: caller.run_id.clone(),
1040            call_id: call_id.clone(),
1041        };
1042        let (slot, should_start) = {
1043            let mut slots = self.call_slots.lock().await;
1044            match slots.entry(key) {
1045                std::collections::hash_map::Entry::Occupied(entry) => {
1046                    (Arc::clone(entry.get()), false)
1047                }
1048                std::collections::hash_map::Entry::Vacant(entry) => {
1049                    let slot = Arc::new(CompletionSlot::new());
1050                    entry.insert(Arc::clone(&slot));
1051                    (slot, true)
1052                }
1053            }
1054        };
1055        if should_start {
1056            let producer_slot = Arc::clone(&slot);
1057            tokio::spawn(async move {
1058                let result = self.execute_agent_call(caller, call_id, request).await;
1059                producer_slot.complete(result);
1060            });
1061        }
1062        Ok(slot.wait().await)
1063    }
1064
1065    async fn execute_agent_call(
1066        self: Arc<Self>,
1067        caller: RunContext,
1068        call_id: AgentCallId,
1069        request: AgentCallRequest,
1070    ) -> AgentCallResult {
1071        emit(
1072            &caller,
1073            AgentEvent::AgentCallStarted {
1074                call_id: call_id.clone(),
1075                target: request.target.clone(),
1076            },
1077        );
1078        let execution = {
1079            let execution = self.try_execute_agent_call(&caller, &call_id, &request);
1080            tokio::pin!(execution);
1081            tokio::select! {
1082                _ = caller.cancellation.cancelled() => Err(AgentError::Cancelled),
1083                _ = tokio::time::sleep_until(tokio::time::Instant::from_std(caller.deadline)) => {
1084                    Err(AgentError::TimedOut)
1085                }
1086                result = &mut execution => result,
1087            }
1088        };
1089        let result = match execution {
1090            Ok(result) => result,
1091            Err(error) => failed_call_result(call_id.clone(), request.target.clone(), &error),
1092        };
1093        emit(
1094            &caller,
1095            AgentEvent::AgentCallCompleted {
1096                call_id,
1097                target: result.target.clone(),
1098                outcome: result.outcome.kind(),
1099            },
1100        );
1101        result
1102    }
1103
1104    async fn try_execute_agent_call(
1105        self: &Arc<Self>,
1106        caller: &RunContext,
1107        call_id: &AgentCallId,
1108        request: &AgentCallRequest,
1109    ) -> Result<AgentCallResult, AgentError> {
1110        if caller.cancellation.is_cancelled() {
1111            return Err(AgentError::Cancelled);
1112        }
1113        if Instant::now() >= caller.deadline {
1114            return Err(AgentError::TimedOut);
1115        }
1116        request.target.validate()?;
1117        if request.task.trim().is_empty() {
1118            return Err(AgentError::InvalidConfiguration(
1119                "Agent call task is required".to_string(),
1120            ));
1121        }
1122        if request.max_iterations == Some(0) {
1123            return Err(AgentError::InvalidConfiguration(
1124                "Agent call max_iterations must be greater than zero".to_string(),
1125            ));
1126        }
1127        if request.timeout.is_some_and(|timeout| timeout.is_zero()) {
1128            return Err(AgentError::InvalidConfiguration(
1129                "Agent call timeout must be greater than zero".to_string(),
1130            ));
1131        }
1132        let context_bytes = request
1133            .context
1134            .as_ref()
1135            .and_then(|value| serde_json::to_vec(value).ok())
1136            .map(|value| value.len())
1137            .unwrap_or(0);
1138        if context_bytes > caller.budget.limits().runtime.max_context_transfer_bytes {
1139            return Err(AgentError::BudgetExceeded(
1140                "max_context_transfer_bytes".to_string(),
1141            ));
1142        }
1143
1144        let target = caller.snapshot.agent(&request.target)?;
1145        if !caller.agent.callable_agents.allows(&target.id)
1146            || !caller.snapshot.policy.callable_agents.allows(&target.id)
1147        {
1148            return Err(AgentError::AgentCallDenied {
1149                caller: caller.agent.id.to_string(),
1150                target: target.id.to_string(),
1151            });
1152        }
1153
1154        if !caller.snapshot.policy.allow_recursive_calls
1155            && caller.ancestry.iter().any(|agent| agent == &target.id)
1156        {
1157            return Err(AgentError::AgentCallCycle(format!(
1158                "{} -> {}",
1159                caller
1160                    .ancestry
1161                    .iter()
1162                    .map(ToString::to_string)
1163                    .collect::<Vec<_>>()
1164                    .join(" -> "),
1165                target.id
1166            )));
1167        }
1168
1169        let depth = caller.depth + 1;
1170        if depth > caller.budget.limits().runtime.max_depth {
1171            return Err(AgentError::MaxDepthExceeded(
1172                caller.budget.limits().runtime.max_depth,
1173            ));
1174        }
1175
1176        match self
1177            .authorizer
1178            .authorize(&caller.view(), &target, request)
1179            .await?
1180        {
1181            AuthorizationDecision::Allow => {}
1182            AuthorizationDecision::Deny(reason) => {
1183                return Err(AgentError::AgentCallDenied {
1184                    caller: caller.agent.id.to_string(),
1185                    target: format!("{} ({reason})", target.id),
1186                })
1187            }
1188        }
1189
1190        caller.reserve_child()?;
1191        caller.budget.reserve_agent_call()?;
1192        caller.budget.reserve_run()?;
1193
1194        let model = caller
1195            .snapshot
1196            .resolve_model(&target, Some(&caller.model.id))?;
1197        let child_run_id = RunId::new();
1198        let child_session_id = uuid::Uuid::new_v4().to_string();
1199        let mut child_session = caller.session.clone();
1200        child_session.session_id = Some(child_session_id.clone());
1201        let child_input = build_child_input(caller, &target, request).await?;
1202        let cancellation = caller.cancellation.child_token();
1203        let deadline = run_deadline(
1204            Instant::now(),
1205            caller.deadline.saturating_duration_since(Instant::now()),
1206            caller.budget.limits().runtime.run_timeout,
1207            target.limits.timeout_millis,
1208            request.timeout,
1209        )
1210        .min(caller.deadline);
1211        let mut ancestry = caller.ancestry.clone();
1212        ancestry.push(target.id.clone());
1213        let metadata = metadata_with_lineage(
1214            &request.metadata,
1215            &caller.root_run_id,
1216            Some(&caller.run_id),
1217            Some(call_id),
1218            &target.id,
1219            depth,
1220        );
1221        let child_context = RunContext {
1222            run_id: child_run_id.clone(),
1223            root_run_id: caller.root_run_id.clone(),
1224            parent_run_id: Some(caller.run_id.clone()),
1225            agent_call_id: Some(call_id.clone()),
1226            agent: Arc::clone(&target),
1227            model,
1228            session_id: child_session_id,
1229            parent_session_id: Some(caller.session_id.clone()),
1230            session: child_session,
1231            runtime_context: caller.runtime_context.clone(),
1232            depth,
1233            ancestry,
1234            deadline,
1235            max_iterations_override: request.max_iterations,
1236            result_contract: Some(request.result_contract.clone()),
1237            cancellation,
1238            budget: Arc::clone(&caller.budget),
1239            snapshot: Arc::clone(&caller.snapshot),
1240            events: caller.events.clone(),
1241            child_count: Arc::new(AtomicUsize::new(0)),
1242            active_children: Arc::new(AtomicUsize::new(0)),
1243            usage: Arc::new(std::sync::Mutex::new(RunUsage::default())),
1244            metadata,
1245        };
1246
1247        let (command_tx, command_rx) = mpsc::channel(COMMAND_CHANNEL_CAPACITY);
1248        let lease = self
1249            .supervisor
1250            .register(child_context.clone(), command_tx)
1251            .await?;
1252        if let Err(error) = self.create_run_record(&child_context).await {
1253            self.supervisor.unregister(&child_run_id).await;
1254            drop(lease);
1255            return Err(error);
1256        }
1257
1258        let active_children = caller.active_children.fetch_add(1, Ordering::SeqCst) + 1;
1259        if active_children == 1 {
1260            let _ = self
1261                .supervisor
1262                .run_store
1263                .update_status(&caller.run_id, RunStatus::WaitingForChild)
1264                .await;
1265        }
1266
1267        let runtime = Arc::clone(self);
1268        let child_task = tokio::spawn(async move {
1269            runtime
1270                .execute_run(
1271                    child_context,
1272                    AgentInput::Text(child_input),
1273                    command_rx,
1274                    lease,
1275                )
1276                .await
1277        });
1278        let result = child_task.await;
1279
1280        if caller.active_children.fetch_sub(1, Ordering::SeqCst) == 1 {
1281            let _ = self
1282                .supervisor
1283                .run_store
1284                .update_status(&caller.run_id, RunStatus::Running)
1285                .await;
1286        }
1287        let result = result.map_err(|error| {
1288            AgentError::Internal(format!(
1289                "child Agent run task stopped unexpectedly: {error}"
1290            ))
1291        })?;
1292
1293        Ok(call_result_from_run(
1294            call_id.clone(),
1295            target.id.clone(),
1296            result,
1297        ))
1298    }
1299}
1300
1301fn validate_root_request(request: &AgentRunRequest) -> Result<(), AgentError> {
1302    request.entry_agent.validate()?;
1303    match &request.input {
1304        AgentInput::Text(value) if value.trim().is_empty() => {
1305            return Err(AgentError::InvalidConfiguration(
1306                "Agent input is required".to_string(),
1307            ))
1308        }
1309        AgentInput::Messages(messages) if messages.is_empty() => {
1310            return Err(AgentError::InvalidConfiguration(
1311                "Agent input messages are required".to_string(),
1312            ))
1313        }
1314        AgentInput::Text(_) | AgentInput::Messages(_) => {}
1315    }
1316    if request.session.context_window == 0 {
1317        return Err(AgentError::InvalidConfiguration(
1318            "context_window must be greater than zero".to_string(),
1319        ));
1320    }
1321    if request
1322        .session
1323        .session_id
1324        .as_ref()
1325        .is_some_and(|session_id| session_id.trim().is_empty() || session_id.len() > 256)
1326    {
1327        return Err(AgentError::InvalidConfiguration(
1328            "session_id must contain 1-256 characters".to_string(),
1329        ));
1330    }
1331    if request
1332        .session
1333        .threshold_percentage
1334        .is_some_and(|value| !value.is_finite() || value <= 0.0 || value > 1.0)
1335    {
1336        return Err(AgentError::InvalidConfiguration(
1337            "threshold_percentage must be greater than 0 and at most 1".to_string(),
1338        ));
1339    }
1340    let uses_database = request.session.context_load_type == ContextLoadType::Database
1341        || request.session.storage_type == StorageType::Database;
1342    if uses_database {
1343        if request.session.database_config.is_none() {
1344            return Err(AgentError::InvalidConfiguration(
1345                "database_config is required for database storage or context".to_string(),
1346            ));
1347        }
1348        let runtime_context = request.runtime_context.as_ref().ok_or_else(|| {
1349            AgentError::InvalidConfiguration(
1350                "runtime_context is required for database storage or context".to_string(),
1351            )
1352        })?;
1353        runtime_context
1354            .validate()
1355            .map_err(AgentError::InvalidConfiguration)?;
1356    } else if let Some(runtime_context) = &request.runtime_context {
1357        runtime_context
1358            .validate()
1359            .map_err(AgentError::InvalidConfiguration)?;
1360    }
1361    if request
1362        .idempotency_key
1363        .as_ref()
1364        .is_some_and(|key| key.is_empty() || key.len() > 256)
1365    {
1366        return Err(AgentError::InvalidConfiguration(
1367            "idempotency_key must contain 1-256 characters".to_string(),
1368        ));
1369    }
1370    Ok(())
1371}
1372
1373fn run_deadline(
1374    now: Instant,
1375    root_timeout: Duration,
1376    run_timeout: Duration,
1377    agent_timeout_millis: Option<u64>,
1378    call_timeout: Option<Duration>,
1379) -> Instant {
1380    let mut timeout = root_timeout.min(run_timeout);
1381    if let Some(value) = agent_timeout_millis {
1382        timeout = timeout.min(Duration::from_millis(value));
1383    }
1384    if let Some(value) = call_timeout {
1385        timeout = timeout.min(value);
1386    }
1387    now + timeout
1388}
1389
1390fn effective_result_limit(context: &RunContext) -> usize {
1391    context
1392        .agent
1393        .limits
1394        .max_result_bytes
1395        .unwrap_or(context.budget.limits().runtime.max_result_bytes)
1396        .min(context.budget.limits().runtime.max_result_bytes)
1397}
1398
1399fn emit(context: &RunContext, event: AgentEvent) {
1400    context.events.emit(
1401        &context.root_run_id,
1402        &context.run_id,
1403        context.parent_run_id.as_ref(),
1404        &context.agent.id,
1405        context.depth,
1406        event,
1407    );
1408}
1409
1410fn error_outcome(error: &AgentError) -> (RunOutcome, RunStatus, AgentEvent, Option<AgentFailure>) {
1411    match error {
1412        AgentError::Cancelled => (
1413            RunOutcome::Cancelled,
1414            RunStatus::Cancelled,
1415            AgentEvent::RunCancelled,
1416            None,
1417        ),
1418        AgentError::TimedOut => (
1419            RunOutcome::TimedOut,
1420            RunStatus::TimedOut,
1421            AgentEvent::RunFailed {
1422                failure: failure_from_error(error),
1423            },
1424            Some(failure_from_error(error)),
1425        ),
1426        AgentError::BudgetExceeded(_) | AgentError::MaxIterationsExceeded(_) => (
1427            RunOutcome::BudgetExceeded,
1428            RunStatus::BudgetExceeded,
1429            AgentEvent::RunFailed {
1430                failure: failure_from_error(error),
1431            },
1432            Some(failure_from_error(error)),
1433        ),
1434        _ => {
1435            let failure = failure_from_error(error);
1436            (
1437                RunOutcome::Failed(failure.clone()),
1438                RunStatus::Failed,
1439                AgentEvent::RunFailed {
1440                    failure: failure.clone(),
1441                },
1442                Some(failure),
1443            )
1444        }
1445    }
1446}
1447
1448fn failure_from_error(error: &AgentError) -> AgentFailure {
1449    let code = match error {
1450        AgentError::InvalidConfiguration(_) => "invalid_configuration",
1451        AgentError::AgentNotFound(_) => "agent_not_found",
1452        AgentError::AgentCallDenied { .. } => "agent_call_denied",
1453        AgentError::AgentCallCycle(_) => "agent_call_cycle",
1454        AgentError::MaxDepthExceeded(_) => "max_depth_exceeded",
1455        AgentError::BudgetExceeded(_) | AgentError::MaxIterationsExceeded(_) => "budget_exceeded",
1456        AgentError::SessionBusy(_) => "session_busy",
1457        AgentError::TimedOut => "timed_out",
1458        AgentError::Cancelled => "cancelled",
1459        AgentError::AiError(_) => "model_error",
1460        AgentError::ContextError(_) => "context_error",
1461        AgentError::ToolError(_) => "tool_error",
1462        AgentError::StorageError(_) => "storage_error",
1463        AgentError::Internal(_) => "internal_error",
1464    };
1465    AgentFailure {
1466        code: code.to_string(),
1467        message: error.to_string(),
1468        retryable: matches!(
1469            error,
1470            AgentError::TimedOut | AgentError::AiError(_) | AgentError::SessionBusy(_)
1471        ),
1472    }
1473}
1474
1475fn failed_call_result(
1476    call_id: AgentCallId,
1477    target: AgentId,
1478    error: &AgentError,
1479) -> AgentCallResult {
1480    AgentCallResult {
1481        schema_version: 1,
1482        call_id,
1483        target,
1484        run_id: None,
1485        outcome: RunOutcome::Failed(failure_from_error(error)),
1486        output: None,
1487        usage: RunUsage::default(),
1488        artifacts: Vec::new(),
1489    }
1490}
1491
1492fn call_result_from_run(
1493    call_id: AgentCallId,
1494    target: AgentId,
1495    result: AgentRunResult,
1496) -> AgentCallResult {
1497    AgentCallResult {
1498        schema_version: 1,
1499        call_id,
1500        target,
1501        run_id: Some(result.run_id),
1502        outcome: result.outcome,
1503        output: result.output,
1504        usage: result.usage,
1505        artifacts: result.artifacts,
1506    }
1507}
1508
1509fn apply_result_contract(
1510    output: Option<AgentOutput>,
1511    contract: &ResultContract,
1512) -> Result<Option<AgentOutput>, AgentFailure> {
1513    let Some(output) = output else {
1514        return Ok(None);
1515    };
1516    match contract {
1517        ResultContract::Text => match output {
1518            AgentOutput::Text(value) => Ok(Some(AgentOutput::Text(value))),
1519            AgentOutput::Json(value) => Ok(Some(AgentOutput::Text(value.to_string()))),
1520        },
1521        ResultContract::Auto => match output {
1522            AgentOutput::Text(value) => match serde_json::from_str(&value) {
1523                Ok(json) => Ok(Some(AgentOutput::Json(json))),
1524                Err(_) => Ok(Some(AgentOutput::Text(value))),
1525            },
1526            AgentOutput::Json(value) => Ok(Some(AgentOutput::Json(value))),
1527        },
1528        ResultContract::Json { schema } => {
1529            let value = match output {
1530                AgentOutput::Json(value) => value,
1531                AgentOutput::Text(value) => serde_json::from_str(&value).map_err(|error| {
1532                    invalid_output_failure(format!("invalid JSON Agent output: {error}"))
1533                })?,
1534            };
1535            if let Some(schema) = schema {
1536                if !schema.is_object() {
1537                    return Err(invalid_output_failure(
1538                        "Agent result JSON schema must be an object",
1539                    ));
1540                }
1541                validate_basic_json_schema(&value, schema).map_err(invalid_output_failure)?;
1542            }
1543            Ok(Some(AgentOutput::Json(value)))
1544        }
1545    }
1546}
1547
1548fn invalid_output_failure(message: impl Into<String>) -> AgentFailure {
1549    AgentFailure {
1550        code: "invalid_agent_output".to_string(),
1551        message: message.into(),
1552        retryable: true,
1553    }
1554}
1555
1556fn validate_basic_json_schema(
1557    value: &serde_json::Value,
1558    schema: &serde_json::Value,
1559) -> Result<(), String> {
1560    if let Some(expected_type) = schema.get("type").and_then(|value| value.as_str()) {
1561        let valid = match expected_type {
1562            "object" => value.is_object(),
1563            "array" => value.is_array(),
1564            "string" => value.is_string(),
1565            "number" => value.is_number(),
1566            "integer" => value.as_i64().is_some() || value.as_u64().is_some(),
1567            "boolean" => value.is_boolean(),
1568            "null" => value.is_null(),
1569            _ => true,
1570        };
1571        if !valid {
1572            return Err(format!("Agent output must be JSON type '{expected_type}'"));
1573        }
1574    }
1575    if let (Some(object), Some(required)) = (
1576        value.as_object(),
1577        schema.get("required").and_then(|value| value.as_array()),
1578    ) {
1579        for key in required.iter().filter_map(|value| value.as_str()) {
1580            if !object.contains_key(key) {
1581                return Err(format!("Agent output is missing required field '{key}'"));
1582            }
1583        }
1584    }
1585    Ok(())
1586}
1587
1588fn system_prompt_for(context: &RunContext) -> String {
1589    let skill_policy = context.snapshot.skill_policy_for(&context.agent);
1590    let skills = context.snapshot.skills.format_for_prompt(&skill_policy);
1591    let memory = match context.agent.memory {
1592        MemoryPolicy::Disabled => "",
1593        MemoryPolicy::ReadOnlyShared => {
1594            "\nLong-term memory is read-only. Use memory.recall only when relevant."
1595        }
1596        MemoryPolicy::ReadWriteShared | MemoryPolicy::PrivateAgent => {
1597            "\nLong-term memory is available. Recall relevant facts before answering and store only durable, non-sensitive information."
1598        }
1599    };
1600    format!("{}{}{}", context.agent.system_prompt, skills, memory)
1601}
1602
1603fn tool_registry_for(context: &RunContext) -> Result<(Arc<ToolRegistry>, Vec<Tool>), AgentError> {
1604    let names = context.snapshot.tool_names_for(&context.agent);
1605    let mut registry = context
1606        .snapshot
1607        .tools
1608        .subset(names.iter().map(String::as_str))
1609        .map_err(|error| AgentError::ToolError(error.to_string()))?;
1610
1611    let reachable = context.snapshot.reachable_agents(&context.agent);
1612    if !reachable.is_empty() {
1613        let mut registration = context
1614            .snapshot
1615            .tools
1616            .get(AGENT_CALL_TOOL_NAME)
1617            .cloned()
1618            .ok_or_else(|| AgentError::Internal("Agent call tool is not registered".to_string()))?;
1619        registration.definition = agent_call_tool_definition(&reachable);
1620        registry
1621            .register(registration)
1622            .map_err(|error| AgentError::ToolError(error.to_string()))?;
1623    }
1624
1625    let definitions = registry.definitions();
1626    Ok((Arc::new(registry), definitions))
1627}
1628
1629fn agent_call_tool_definition(reachable: &[Arc<AgentDefinition>]) -> Tool {
1630    let names = reachable
1631        .iter()
1632        .map(|agent| serde_json::Value::String(agent.id.to_string()))
1633        .collect::<Vec<_>>();
1634    let descriptions = reachable
1635        .iter()
1636        .map(|agent| format!("- {}: {}", agent.id, agent.description))
1637        .collect::<Vec<_>>()
1638        .join("\n");
1639    Tool {
1640        tool_type: "function".to_string(),
1641        function: FunctionDefinition {
1642            name: AGENT_CALL_TOOL_NAME.to_string(),
1643            description: format!(
1644                "Delegate one focused task to another Agent and wait for its structured result.\n{descriptions}"
1645            ),
1646            parameters: Some(serde_json::json!({
1647                "type": "object",
1648                "properties": {
1649                    "agent": { "type": "string", "enum": names },
1650                    "task": { "type": "string" },
1651                    "context": { "type": "object" }
1652                },
1653                "required": ["agent", "task"],
1654                "additionalProperties": false
1655            })),
1656        },
1657    }
1658}
1659
1660fn build_model_config(
1661    context: &RunContext,
1662    messages: &[Message],
1663    tools: &[Tool],
1664) -> serde_json::Value {
1665    use otherone_ai::types::ProviderType;
1666
1667    let overrides = &context.agent.model_overrides;
1668    let mut config = serde_json::json!({
1669        "model": context.model.model,
1670        "messages": messages,
1671        "stream": true,
1672    });
1673    let provider = &context.model.provider;
1674    let context_length = overrides.context_length.or(context.model.context_length);
1675    let max_tokens = overrides.max_tokens.or(context.model.max_tokens);
1676    if let Some(value) = context_length.filter(|_| {
1677        matches!(
1678            provider,
1679            ProviderType::OpenAI | ProviderType::OpenRouter | ProviderType::Local
1680        )
1681    }) {
1682        config["contextLength"] = serde_json::json!(value);
1683    }
1684    let effective_max_tokens = if matches!(provider, ProviderType::Fetch) {
1685        max_tokens.or(context_length)
1686    } else {
1687        max_tokens
1688    };
1689    if let Some(value) = effective_max_tokens {
1690        let key = match provider {
1691            ProviderType::Anthropic | ProviderType::Fetch => "max_tokens",
1692            ProviderType::OpenAI | ProviderType::OpenRouter | ProviderType::Local => "maxTokens",
1693        };
1694        config[key] = serde_json::json!(value);
1695    }
1696    if let Some(value) = overrides.temperature.or(context.model.temperature) {
1697        config["temperature"] = serde_json::json!(value);
1698    }
1699    if let Some(value) = overrides.top_p.or(context.model.top_p) {
1700        let key = if matches!(provider, ProviderType::Fetch) {
1701            "top_p"
1702        } else {
1703            "topP"
1704        };
1705        config[key] = serde_json::json!(value);
1706    }
1707    if !tools.is_empty() {
1708        config["tools"] = serde_json::json!(tools);
1709    }
1710    if let Some(value) = &overrides.tool_choice {
1711        match provider {
1712            ProviderType::Fetch => config["tool_choice"] = serde_json::json!(value),
1713            ProviderType::OpenAI | ProviderType::OpenRouter | ProviderType::Local => {
1714                config["toolChoice"] = serde_json::json!(value)
1715            }
1716            ProviderType::Anthropic => {}
1717        }
1718    }
1719    if let Some(value) = overrides.parallel_tool_calls.filter(|_| {
1720        matches!(
1721            provider,
1722            ProviderType::OpenAI | ProviderType::OpenRouter | ProviderType::Local
1723        )
1724    }) {
1725        config["parallelToolCalls"] = serde_json::json!(value);
1726    }
1727    if let Some(serde_json::Value::Object(values)) =
1728        merge_other(context.model.other.as_ref(), overrides.other.as_ref())
1729    {
1730        for (key, value) in values {
1731            config[key] = value;
1732        }
1733    }
1734    config
1735}
1736
1737async fn combine_context(
1738    context: &RunContext,
1739    system_prompt: &str,
1740    tools: &[Tool],
1741) -> Result<Vec<Message>, AgentError> {
1742    let load_type = match context.session.context_load_type {
1743        ContextLoadType::LocalFile => otherone_context::types::ContextLoadType::LocalFile,
1744        ContextLoadType::Database => otherone_context::types::ContextLoadType::Database,
1745    };
1746    otherone_context::combine_context(&otherone_context::types::CombineContextOptions {
1747        session_id: context.session_id.clone(),
1748        load_type,
1749        provider: context.model.provider.clone(),
1750        context_window: context.session.context_window,
1751        threshold_percentage: context.session.threshold_percentage,
1752        ai: merge_other(
1753            context.model.other.as_ref(),
1754            context.agent.model_overrides.other.as_ref(),
1755        ),
1756        system_prompt: (!system_prompt.is_empty()).then(|| system_prompt.to_string()),
1757        tools: (!tools.is_empty()).then(|| tools.to_vec()),
1758        database_config: context.session.database_config.clone(),
1759        runtime_context: context.runtime_context.clone(),
1760    })
1761    .await
1762    .map_err(|error| AgentError::ContextError(error.to_string()))
1763}
1764
1765struct ModelTurn {
1766    content: String,
1767    role: String,
1768    tool_calls: Vec<ToolCall>,
1769    input_tokens: Option<u64>,
1770    output_tokens: Option<u64>,
1771    total_tokens: Option<u64>,
1772}
1773
1774impl ModelTurn {
1775    fn usage(&self) -> RunUsage {
1776        RunUsage {
1777            input_tokens: self.input_tokens,
1778            output_tokens: self.output_tokens,
1779            total_tokens: self.total_tokens,
1780            model_calls: 1,
1781            ..Default::default()
1782        }
1783    }
1784}
1785
1786async fn collect_model_stream(
1787    context: &RunContext,
1788    mut stream: ChatStream,
1789) -> Result<ModelTurn, AgentError> {
1790    let mut content = String::new();
1791    let mut role = "assistant".to_string();
1792    let mut tool_calls = Vec::new();
1793    let mut input_tokens = None;
1794    let mut output_tokens = None;
1795    let mut total_tokens = None;
1796    let output_limit = effective_result_limit(context);
1797    let mut tool_payload_bytes = 0usize;
1798
1799    while let Some(chunk) = stream.next().await {
1800        let chunk = chunk?;
1801        if let Some(choice) = chunk.choices.first() {
1802            if let Some(delta) = &choice.delta {
1803                if let Some(value) = &delta.role {
1804                    role = value.clone();
1805                }
1806                if let Some(value) = &delta.content {
1807                    if content
1808                        .len()
1809                        .saturating_add(tool_payload_bytes)
1810                        .saturating_add(value.len())
1811                        > output_limit
1812                    {
1813                        return Err(AgentError::BudgetExceeded("max_result_bytes".to_string()));
1814                    }
1815                    content.push_str(value);
1816                    emit(
1817                        context,
1818                        AgentEvent::ModelDelta {
1819                            content: value.clone(),
1820                        },
1821                    );
1822                }
1823                if let Some(value) = delta_thinking(delta) {
1824                    emit(
1825                        context,
1826                        AgentEvent::ThinkingDelta {
1827                            content: value.to_string(),
1828                        },
1829                    );
1830                }
1831                if let Some(calls) = &delta.tool_calls {
1832                    for call in calls {
1833                        tool_payload_bytes = tool_payload_bytes
1834                            .saturating_add(call.id.len())
1835                            .saturating_add(call.function.name.len())
1836                            .saturating_add(call.function.arguments.len());
1837                        if content.len().saturating_add(tool_payload_bytes) > output_limit {
1838                            return Err(AgentError::BudgetExceeded("max_result_bytes".to_string()));
1839                        }
1840                        merge_tool_call_delta(&mut tool_calls, call);
1841                    }
1842                }
1843            } else if let Some(message) = &choice.message {
1844                if let Some(value) = &message.role {
1845                    role = value.clone();
1846                }
1847                if let Some(value) = &message.content {
1848                    if content
1849                        .len()
1850                        .saturating_add(tool_payload_bytes)
1851                        .saturating_add(value.len())
1852                        > output_limit
1853                    {
1854                        return Err(AgentError::BudgetExceeded("max_result_bytes".to_string()));
1855                    }
1856                    content.push_str(value);
1857                    emit(
1858                        context,
1859                        AgentEvent::ModelDelta {
1860                            content: value.clone(),
1861                        },
1862                    );
1863                }
1864                if let Some(calls) = &message.tool_calls {
1865                    tool_payload_bytes = serde_json::to_vec(calls)
1866                        .map(|value| value.len())
1867                        .unwrap_or(usize::MAX);
1868                    if content.len().saturating_add(tool_payload_bytes) > output_limit {
1869                        return Err(AgentError::BudgetExceeded("max_result_bytes".to_string()));
1870                    }
1871                    tool_calls = calls.clone();
1872                }
1873            }
1874        }
1875        if let Some(usage) = chunk.usage {
1876            if let Some(value) = usage.prompt_tokens {
1877                input_tokens = Some(u64::from(value));
1878            }
1879            if let Some(value) = usage.completion_tokens {
1880                output_tokens = Some(u64::from(value));
1881            }
1882            if let Some(value) = usage.total_tokens {
1883                total_tokens = Some(u64::from(value));
1884            }
1885        }
1886    }
1887
1888    if let (Some(input), Some(output)) = (input_tokens, output_tokens) {
1889        total_tokens = Some(input.saturating_add(output));
1890    }
1891
1892    Ok(ModelTurn {
1893        content,
1894        role,
1895        tool_calls,
1896        input_tokens,
1897        output_tokens,
1898        total_tokens,
1899    })
1900}
1901
1902fn delta_thinking(delta: &otherone_ai::types::ResponseDelta) -> Option<&str> {
1903    delta
1904        .reasoning_content
1905        .as_deref()
1906        .or(delta.reasoning.as_deref())
1907        .or(delta.thinking.as_deref())
1908        .or(delta.thought.as_deref())
1909        .filter(|value| !value.is_empty())
1910}
1911
1912fn merge_tool_call_delta(tool_calls: &mut Vec<ToolCall>, delta: &ToolCall) {
1913    let position = delta
1914        .index
1915        .and_then(|index| tool_calls.iter().position(|call| call.index == Some(index)))
1916        .or_else(|| {
1917            (!delta.id.is_empty())
1918                .then(|| tool_calls.iter().position(|call| call.id == delta.id))
1919                .flatten()
1920        });
1921
1922    if let Some(position) = position {
1923        let target = &mut tool_calls[position];
1924        if !delta.id.is_empty() {
1925            target.id = delta.id.clone();
1926        }
1927        if !delta.function.name.is_empty() {
1928            target.function.name = delta.function.name.clone();
1929        }
1930        target
1931            .function
1932            .arguments
1933            .push_str(&delta.function.arguments);
1934        if delta.index.is_some() {
1935            target.index = delta.index;
1936        }
1937    } else {
1938        tool_calls.push(delta.clone());
1939    }
1940}
1941
1942async fn persist_input(context: &RunContext, input: AgentInput) -> Result<(), AgentError> {
1943    match input {
1944        AgentInput::Text(value) => persist_entry(context, "user", &value, None, None).await,
1945        AgentInput::Messages(messages) => {
1946            for message in messages {
1947                let content = message_content_string(&message.content);
1948                let tools = message
1949                    .tool_calls
1950                    .as_ref()
1951                    .map(|calls| serde_json::json!({ "tool_calls": calls }));
1952                persist_entry(context, &message.role, &content, tools, None).await?;
1953            }
1954            Ok(())
1955        }
1956    }
1957}
1958
1959fn message_content_string(content: &MessageContent) -> String {
1960    match content {
1961        MessageContent::Text(value) => value.clone(),
1962        MessageContent::MultiPart(value) => serde_json::to_string(value).unwrap_or_default(),
1963    }
1964}
1965
1966async fn persist_assistant_response(
1967    context: &RunContext,
1968    response: &ModelTurn,
1969) -> Result<(), AgentError> {
1970    let tools = (!response.tool_calls.is_empty())
1971        .then(|| serde_json::json!({ "tool_calls": response.tool_calls }));
1972    persist_entry(
1973        context,
1974        &response.role,
1975        &response.content,
1976        tools,
1977        response
1978            .total_tokens
1979            .map(|value| value.min(u32::MAX as u64) as u32),
1980    )
1981    .await
1982}
1983
1984async fn persist_tool_results(
1985    context: &RunContext,
1986    results: &[ToolResult],
1987) -> Result<(), AgentError> {
1988    for result in results {
1989        let content = match (&result.result, &result.error) {
1990            (Some(value), _) => serde_json::to_string(value).unwrap_or_else(|_| "null".to_string()),
1991            (None, Some(error)) => serde_json::json!({ "error": error }).to_string(),
1992            (None, None) => "null".to_string(),
1993        };
1994        let tools = serde_json::json!({
1995            "tool_call_id": result.tool_call_id,
1996            "function_name": result.function_name,
1997            "result": result.result,
1998            "error": result.error,
1999            "error_kind": result.error_kind,
2000        });
2001        persist_entry(context, "tool", &content, Some(tools), None).await?;
2002    }
2003    Ok(())
2004}
2005
2006async fn persist_entry(
2007    context: &RunContext,
2008    role: &str,
2009    content: &str,
2010    tools: Option<serde_json::Value>,
2011    token_consumption: Option<u32>,
2012) -> Result<(), AgentError> {
2013    let storage_type = match context.session.storage_type {
2014        StorageType::LocalFile => StorageBackend::LocalFile,
2015        StorageType::Database => StorageBackend::Database,
2016    };
2017    otherone_storage::write_entry(&WriteEntryOptions {
2018        storage_type,
2019        session_id: context.session_id.clone(),
2020        role: role.to_string(),
2021        content: content.to_string(),
2022        tools,
2023        token_consumption,
2024        create_at: None,
2025        database_config: context.session.database_config.clone(),
2026        runtime_context: context.runtime_context.clone(),
2027        metadata: context.metadata.clone(),
2028    })
2029    .await
2030    .map_err(|error| AgentError::StorageError(error.to_string()))
2031}
2032
2033fn drain_commands(
2034    context: &RunContext,
2035    command_rx: &mut mpsc::Receiver<AgentRunCommand>,
2036    queued: &mut VecDeque<String>,
2037) -> Result<(), AgentError> {
2038    loop {
2039        match command_rx.try_recv() {
2040            Ok(AgentRunCommand::EnqueueUserMessages(messages)) => {
2041                for message in messages {
2042                    let message = message.trim();
2043                    if !message.is_empty() {
2044                        queued.push_back(message.to_string());
2045                    }
2046                }
2047            }
2048            Ok(AgentRunCommand::Cancel) => {
2049                context.cancellation.cancel();
2050                return Err(AgentError::Cancelled);
2051            }
2052            Err(mpsc::error::TryRecvError::Empty) => break,
2053            Err(mpsc::error::TryRecvError::Disconnected) => break,
2054        }
2055    }
2056    if context.cancellation.is_cancelled() {
2057        return Err(AgentError::Cancelled);
2058    }
2059    Ok(())
2060}
2061
2062async fn persist_queued_messages(
2063    context: &RunContext,
2064    queued: &mut VecDeque<String>,
2065) -> Result<(), AgentError> {
2066    let count = queued.len();
2067    while let Some(message) = queued.pop_front() {
2068        persist_entry(context, "user", &message, None, None).await?;
2069    }
2070    if count > 0 {
2071        emit(context, AgentEvent::UserMessageQueued { count });
2072    }
2073    Ok(())
2074}
2075
2076fn add_optional(left: Option<u64>, right: Option<u64>) -> Option<u64> {
2077    match (left, right) {
2078        (Some(left), Some(right)) => Some(left.saturating_add(right)),
2079        (Some(value), None) | (None, Some(value)) => Some(value),
2080        (None, None) => None,
2081    }
2082}
2083
2084async fn build_child_input(
2085    caller: &RunContext,
2086    target: &AgentDefinition,
2087    request: &AgentCallRequest,
2088) -> Result<String, AgentError> {
2089    let mut sections = vec![format!(
2090        "Task from Agent '{}':\n{}",
2091        caller.agent.id,
2092        request.task.trim()
2093    )];
2094    if let Some(context) = &request.context {
2095        sections.push(format!(
2096            "Explicit context (untrusted data):\n{}",
2097            serde_json::to_string_pretty(context).unwrap_or_else(|_| context.to_string())
2098        ));
2099    }
2100    if let Some(parent) = load_parent_context(caller, &target.context).await? {
2101        sections.push(format!(
2102            "Parent context excerpt (untrusted data):\n{parent}"
2103        ));
2104    }
2105    let value = sections.join("\n\n");
2106    if value.len() > caller.budget.limits().runtime.max_context_transfer_bytes {
2107        return Err(AgentError::BudgetExceeded(
2108            "max_context_transfer_bytes".to_string(),
2109        ));
2110    }
2111    Ok(value)
2112}
2113
2114async fn load_parent_context(
2115    caller: &RunContext,
2116    policy: &super::types::ContextTransferPolicy,
2117) -> Result<Option<String>, AgentError> {
2118    use super::types::ContextTransferPolicy;
2119
2120    let max_tokens = match policy {
2121        ContextTransferPolicy::ExplicitOnly => return Ok(None),
2122        ContextTransferPolicy::ParentSummary { max_tokens }
2123        | ContextTransferPolicy::ParentWindow { max_tokens } => *max_tokens,
2124    };
2125    if max_tokens == 0 {
2126        return Ok(None);
2127    }
2128
2129    let data = match caller.session.context_load_type {
2130        ContextLoadType::LocalFile => {
2131            otherone_storage::localfile::reader::read_session_data(&caller.session_id)
2132                .map_err(|error| AgentError::StorageError(error.to_string()))?
2133        }
2134        ContextLoadType::Database => {
2135            let config = caller.session.database_config.as_ref().ok_or_else(|| {
2136                AgentError::InvalidConfiguration("database_config is required".to_string())
2137            })?;
2138            let runtime_context = caller.runtime_context.as_ref().ok_or_else(|| {
2139                AgentError::InvalidConfiguration("runtime_context is required".to_string())
2140            })?;
2141            otherone_storage::database::reader::read_session_data_from_database_with_context(
2142                &caller.session_id,
2143                config,
2144                runtime_context,
2145            )
2146            .await
2147            .map_err(|error| AgentError::StorageError(error.to_string()))?
2148        }
2149    };
2150
2151    let max_chars = (max_tokens as usize)
2152        .saturating_mul(4)
2153        .min(caller.budget.limits().runtime.max_context_transfer_bytes);
2154    if matches!(policy, ContextTransferPolicy::ParentSummary { .. }) {
2155        if let Some(summary) = data
2156            .compacted_entries
2157            .iter()
2158            .max_by(|left, right| left.create_at.cmp(&right.create_at))
2159            .map(|entry| entry.summary.trim())
2160            .filter(|summary| !summary.is_empty())
2161        {
2162            return Ok(Some(take_last_chars(summary, max_chars)));
2163        }
2164    }
2165
2166    let mut selected = Vec::new();
2167    let mut selected_bytes = 0usize;
2168    for entry in data.entries.iter().rev().filter(|entry| {
2169        (entry.role == "user" || entry.role == "assistant") && entry.tools.is_none()
2170    }) {
2171        let line = format!(
2172            "{}: {}",
2173            entry.role,
2174            take_last_chars(&entry.content, max_chars)
2175        );
2176        selected_bytes = selected_bytes.saturating_add(line.len());
2177        selected.push(line);
2178        if selected_bytes >= max_chars {
2179            break;
2180        }
2181    }
2182    selected.reverse();
2183    let safe_messages = selected.join("\n");
2184    if safe_messages.is_empty() {
2185        Ok(None)
2186    } else {
2187        Ok(Some(take_last_chars(&safe_messages, max_chars)))
2188    }
2189}
2190
2191fn take_last_chars(value: &str, max_chars: usize) -> String {
2192    if max_chars == 0 {
2193        return String::new();
2194    }
2195    let start = value
2196        .char_indices()
2197        .rev()
2198        .nth(max_chars.saturating_sub(1))
2199        .map(|(index, _)| index)
2200        .unwrap_or(0);
2201    value[start..].to_string()
2202}
2203
2204#[derive(Debug, Deserialize)]
2205struct AgentCallArguments {
2206    agent: String,
2207    task: String,
2208    context: Option<serde_json::Value>,
2209}
2210
2211struct AgentCallToolHandler {
2212    runtime: Weak<RuntimeInner>,
2213}
2214
2215#[async_trait]
2216impl ToolHandler for AgentCallToolHandler {
2217    async fn call(
2218        &self,
2219        arguments: serde_json::Value,
2220        context: ToolCallContext,
2221    ) -> Result<serde_json::Value, ToolError> {
2222        let arguments: AgentCallArguments = serde_json::from_value(arguments)
2223            .map_err(|error| ToolError::invalid_arguments(error.to_string()))?;
2224        let runtime = self.runtime.upgrade().ok_or_else(|| {
2225            ToolError::new(ToolErrorKind::Fatal, "Agent runtime has been dropped")
2226        })?;
2227        let caller_run_id = RunId::from_string(context.run_id)
2228            .map_err(|error| ToolError::invalid_arguments(error.to_string()))?;
2229        let call_id = if context.tool_call_id.is_empty() {
2230            AgentCallId::generated()
2231        } else {
2232            AgentCallId::new(context.tool_call_id)
2233                .map_err(|error| ToolError::invalid_arguments(error.to_string()))?
2234        };
2235        let request = AgentCallRequest {
2236            target: AgentId::unchecked(arguments.agent),
2237            task: arguments.task,
2238            context: arguments.context,
2239            result_contract: ResultContract::Auto,
2240            max_iterations: None,
2241            timeout: None,
2242            metadata: AttributeBag::new(),
2243        };
2244
2245        let result = RuntimeInner::call_agent(runtime, &caller_run_id, call_id, request)
2246            .await
2247            .map_err(|error| ToolError::recoverable(error.to_string()))?;
2248        serde_json::to_value(result)
2249            .map_err(|error| ToolError::new(ToolErrorKind::Fatal, error.to_string()))
2250    }
2251}
2252
2253fn agent_call_registration(runtime: Weak<RuntimeInner>) -> ToolRegistration {
2254    ToolRegistration::new(
2255        Tool {
2256            tool_type: "function".to_string(),
2257            function: FunctionDefinition {
2258                name: AGENT_CALL_TOOL_NAME.to_string(),
2259                description: "Call another Agent".to_string(),
2260                parameters: Some(serde_json::json!({
2261                    "type": "object",
2262                    "properties": {
2263                        "agent": { "type": "string" },
2264                        "task": { "type": "string" },
2265                        "context": { "type": "object" }
2266                    },
2267                    "required": ["agent", "task"]
2268                })),
2269            },
2270        },
2271        Arc::new(AgentCallToolHandler { runtime }),
2272    )
2273    .execution_class(ToolExecutionClass::Delegating)
2274}
2275
2276#[derive(Debug, Deserialize)]
2277struct MemoryRecallArguments {
2278    #[serde(default)]
2279    memory_types: Vec<String>,
2280}
2281
2282struct MemoryRecallHandler {
2283    lock: Arc<Mutex<()>>,
2284}
2285
2286#[async_trait]
2287impl ToolHandler for MemoryRecallHandler {
2288    async fn call(
2289        &self,
2290        arguments: serde_json::Value,
2291        context: ToolCallContext,
2292    ) -> Result<serde_json::Value, ToolError> {
2293        let arguments: MemoryRecallArguments = serde_json::from_value(arguments)
2294            .map_err(|error| ToolError::invalid_arguments(error.to_string()))?;
2295        if arguments.memory_types.is_empty() {
2296            return Err(ToolError::invalid_arguments("memory_types is required"));
2297        }
2298        let _guard = self.lock.lock().await;
2299        let tree = otherone_memory::read_memory_tree()
2300            .map_err(|error| ToolError::new(ToolErrorKind::Fatal, error.to_string()))?;
2301        let scope = memory_scope(&context);
2302        let queries = arguments
2303            .memory_types
2304            .iter()
2305            .map(|value| value.trim().to_lowercase())
2306            .filter(|value| !value.is_empty())
2307            .collect::<Vec<_>>();
2308        let memories = tree
2309            .iter_points()
2310            .filter(|point| point.is_active())
2311            .filter_map(|point| {
2312                let types = point.types.as_deref()?;
2313                let unscoped = types.strip_prefix(&scope)?;
2314                queries
2315                    .iter()
2316                    .any(|query| {
2317                        let value = unscoped.to_lowercase();
2318                        value.contains(query) || query.contains(&value)
2319                    })
2320                    .then(|| point.storage.clone())
2321                    .flatten()
2322            })
2323            .take(64)
2324            .collect::<Vec<_>>();
2325        Ok(serde_json::json!({ "memories": memories }))
2326    }
2327}
2328
2329#[derive(Debug, Deserialize)]
2330struct MemoryStoreArguments {
2331    storage: String,
2332    types: String,
2333}
2334
2335struct MemoryStoreHandler {
2336    lock: Arc<Mutex<()>>,
2337}
2338
2339#[async_trait]
2340impl ToolHandler for MemoryStoreHandler {
2341    async fn call(
2342        &self,
2343        arguments: serde_json::Value,
2344        context: ToolCallContext,
2345    ) -> Result<serde_json::Value, ToolError> {
2346        let arguments: MemoryStoreArguments = serde_json::from_value(arguments)
2347            .map_err(|error| ToolError::invalid_arguments(error.to_string()))?;
2348        let storage = arguments.storage.trim();
2349        let types = arguments.types.trim();
2350        if storage.is_empty() || types.is_empty() {
2351            return Err(ToolError::invalid_arguments(
2352                "storage and types are required",
2353            ));
2354        }
2355        if contains_sensitive_memory(storage) {
2356            return Err(ToolError::new(
2357                ToolErrorKind::PermissionDenied,
2358                "sensitive values must not be stored in long-term memory",
2359            ));
2360        }
2361        let _guard = self.lock.lock().await;
2362        let mut tree = otherone_memory::read_memory_tree()
2363            .map_err(|error| ToolError::new(ToolErrorKind::Fatal, error.to_string()))?;
2364        let mut point = otherone_memory::MemoryPoint::new_root(
2365            storage,
2366            format!("{}{}", memory_scope(&context), types),
2367        )
2368        .map_err(|error| ToolError::recoverable(error.to_string()))?;
2369        point
2370            .set_attribute("source_run_id", serde_json::json!(context.run_id))
2371            .map_err(|error| ToolError::recoverable(error.to_string()))?;
2372        point
2373            .set_attribute("source_agent_id", serde_json::json!(context.agent_id))
2374            .map_err(|error| ToolError::recoverable(error.to_string()))?;
2375        if let Some(session_id) = &context.session_id {
2376            point
2377                .set_attribute("source_session_id", serde_json::json!(session_id))
2378                .map_err(|error| ToolError::recoverable(error.to_string()))?;
2379        }
2380        let point_id = tree
2381            .insert_point(point)
2382            .map_err(|error| ToolError::recoverable(error.to_string()))?;
2383        otherone_memory::write_memory_tree(&tree)
2384            .map_err(|error| ToolError::new(ToolErrorKind::Fatal, error.to_string()))?;
2385        Ok(serde_json::json!({ "stored": true, "point_id": point_id }))
2386    }
2387}
2388
2389fn memory_scope(context: &ToolCallContext) -> String {
2390    let partition = context
2391        .runtime_context
2392        .as_ref()
2393        .and_then(|value| value.get("partition_key"))
2394        .and_then(|value| value.as_str())
2395        .unwrap_or("default");
2396    let private = context
2397        .metadata
2398        .get("memory_policy")
2399        .and_then(|value| value.as_str())
2400        == Some("private_agent");
2401    if private {
2402        format!("[scope:{partition}:agent:{}] ", context.agent_id)
2403    } else {
2404        format!("[scope:{partition}] ")
2405    }
2406}
2407
2408fn contains_sensitive_memory(value: &str) -> bool {
2409    let value = value.to_lowercase();
2410    [
2411        "api key",
2412        "api_key",
2413        "password",
2414        "密码",
2415        "验证码",
2416        "verification code",
2417        "credit card",
2418        "银行卡",
2419    ]
2420    .iter()
2421    .any(|pattern| value.contains(pattern))
2422}
2423
2424fn memory_recall_registration(lock: Arc<Mutex<()>>) -> ToolRegistration {
2425    ToolRegistration::new(
2426        Tool {
2427            tool_type: "function".to_string(),
2428            function: FunctionDefinition {
2429                name: MEMORY_RECALL_TOOL_NAME.to_string(),
2430                description: "Recall scoped long-term memories by semantic type.".to_string(),
2431                parameters: Some(serde_json::json!({
2432                    "type": "object",
2433                    "properties": {
2434                        "memory_types": {
2435                            "type": "array",
2436                            "items": { "type": "string" },
2437                            "minItems": 1,
2438                            "maxItems": 8
2439                        }
2440                    },
2441                    "required": ["memory_types"],
2442                    "additionalProperties": false
2443                })),
2444            },
2445        },
2446        Arc::new(MemoryRecallHandler { lock }),
2447    )
2448}
2449
2450fn memory_store_registration(lock: Arc<Mutex<()>>) -> ToolRegistration {
2451    ToolRegistration::new(
2452        Tool {
2453            tool_type: "function".to_string(),
2454            function: FunctionDefinition {
2455                name: MEMORY_STORE_TOOL_NAME.to_string(),
2456                description: "Store one durable, non-sensitive long-term memory.".to_string(),
2457                parameters: Some(serde_json::json!({
2458                    "type": "object",
2459                    "properties": {
2460                        "storage": { "type": "string" },
2461                        "types": { "type": "string" }
2462                    },
2463                    "required": ["storage", "types"],
2464                    "additionalProperties": false
2465                })),
2466            },
2467        },
2468        Arc::new(MemoryStoreHandler { lock }),
2469    )
2470}
2471
2472fn global_memory_lock() -> Arc<Mutex<()>> {
2473    static LOCK: std::sync::OnceLock<Arc<Mutex<()>>> = std::sync::OnceLock::new();
2474    Arc::clone(LOCK.get_or_init(|| Arc::new(Mutex::new(()))))
2475}
2476
2477#[cfg(test)]
2478mod tests {
2479    use std::pin::Pin;
2480    use std::sync::atomic::AtomicUsize as TestAtomicUsize;
2481    use std::sync::OnceLock;
2482
2483    use futures::stream;
2484    use otherone_ai::types::{ChatResponse, Choice, ResponseDelta, Usage};
2485
2486    use super::*;
2487    use crate::multi_agent::RunLimitOverrides;
2488
2489    struct MockModelExecutor {
2490        responses: Mutex<VecDeque<ChatResponse>>,
2491        configs: Mutex<Vec<serde_json::Value>>,
2492    }
2493
2494    impl MockModelExecutor {
2495        fn new(responses: Vec<ChatResponse>) -> Self {
2496            Self {
2497                responses: Mutex::new(responses.into()),
2498                configs: Mutex::new(Vec::new()),
2499            }
2500        }
2501
2502        async fn configs(&self) -> Vec<serde_json::Value> {
2503            self.configs.lock().await.clone()
2504        }
2505    }
2506
2507    #[async_trait]
2508    impl ModelExecutor for MockModelExecutor {
2509        async fn stream(
2510            &self,
2511            _profile: &ModelProfile,
2512            config: serde_json::Value,
2513        ) -> Result<ChatStream, AiError> {
2514            self.configs.lock().await.push(config);
2515            let response = self
2516                .responses
2517                .lock()
2518                .await
2519                .pop_front()
2520                .ok_or_else(|| AiError::Other("no mock response".to_string()))?;
2521            Ok(Box::pin(stream::iter(vec![Ok(response)])) as Pin<Box<_>>)
2522        }
2523    }
2524
2525    fn response(content: &str, tool_calls: Option<Vec<ToolCall>>) -> ChatResponse {
2526        ChatResponse {
2527            id: Some(uuid::Uuid::new_v4().to_string()),
2528            object: None,
2529            created: None,
2530            model: Some("mock".to_string()),
2531            choices: vec![Choice {
2532                index: 0,
2533                message: None,
2534                delta: Some(ResponseDelta {
2535                    role: Some("assistant".to_string()),
2536                    content: Some(content.to_string()),
2537                    reasoning_content: None,
2538                    reasoning: None,
2539                    thinking: None,
2540                    thought: None,
2541                    tool_calls,
2542                }),
2543                finish_reason: Some("stop".to_string()),
2544            }],
2545            usage: Some(Usage {
2546                prompt_tokens: Some(2),
2547                completion_tokens: Some(1),
2548                total_tokens: Some(3),
2549            }),
2550        }
2551    }
2552
2553    fn profile() -> ModelProfile {
2554        ModelProfile::builder("default", otherone_ai::types::ProviderType::OpenAI, "mock")
2555            .api_key("test")
2556            .base_url("http://localhost")
2557            .build()
2558            .unwrap()
2559    }
2560
2561    fn temp_storage() -> std::path::PathBuf {
2562        std::env::temp_dir().join(format!("otherone-multi-agent-{}", uuid::Uuid::new_v4()))
2563    }
2564
2565    #[test]
2566    fn memory_policy_controls_builtin_memory_tools() {
2567        let runtime = AgentRuntime::builder()
2568            .register_model(profile())
2569            .register_agent(
2570                AgentDefinition::builder("reader")
2571                    .description("reader")
2572                    .memory(MemoryPolicy::ReadOnlyShared)
2573                    .build()
2574                    .unwrap(),
2575            )
2576            .register_agent(
2577                AgentDefinition::builder("writer")
2578                    .description("writer")
2579                    .memory(MemoryPolicy::ReadWriteShared)
2580                    .build()
2581                    .unwrap(),
2582            )
2583            .build()
2584            .unwrap();
2585        let snapshot = runtime.snapshot();
2586        let reader = snapshot.agent(&AgentId::new("reader").unwrap()).unwrap();
2587        let writer = snapshot.agent(&AgentId::new("writer").unwrap()).unwrap();
2588        let reader_tools = snapshot.tool_names_for(&reader);
2589        let writer_tools = snapshot.tool_names_for(&writer);
2590        assert!(reader_tools.contains(MEMORY_RECALL_TOOL_NAME));
2591        assert!(!reader_tools.contains(MEMORY_STORE_TOOL_NAME));
2592        assert!(writer_tools.contains(MEMORY_RECALL_TOOL_NAME));
2593        assert!(writer_tools.contains(MEMORY_STORE_TOOL_NAME));
2594    }
2595
2596    #[tokio::test]
2597    async fn memory_writes_include_run_session_and_agent_provenance() {
2598        let _guard = storage_guard().await;
2599        otherone_memory::set_memory_storage_root(temp_storage());
2600        let handler = MemoryStoreHandler {
2601            lock: global_memory_lock(),
2602        };
2603        let mut context =
2604            ToolCallContext::new("run-1", "root-1", "agent-1", CancellationToken::new());
2605        context.session_id = Some("session-1".to_string());
2606        context.metadata = serde_json::json!({ "memory_policy": "read_write_shared" });
2607
2608        let result = handler
2609            .call(
2610                serde_json::json!({
2611                    "storage": "The user prefers concise answers",
2612                    "types": "response preference"
2613                }),
2614                context,
2615            )
2616            .await
2617            .unwrap();
2618        let point_id = result["point_id"].as_str().unwrap();
2619        let tree = otherone_memory::read_memory_tree().unwrap();
2620        let point = tree.get(point_id).unwrap();
2621        assert_eq!(point.attributes["source_run_id"], "run-1");
2622        assert_eq!(point.attributes["source_session_id"], "session-1");
2623        assert_eq!(point.attributes["source_agent_id"], "agent-1");
2624        otherone_memory::clear_memory_storage_root();
2625    }
2626
2627    #[test]
2628    fn agent_skill_policy_filters_the_runtime_skill_snapshot() {
2629        let root = std::env::temp_dir().join(format!("otherone-skill-{}", uuid::Uuid::new_v4()));
2630        std::fs::create_dir_all(&root).unwrap();
2631        let skill_path = root.join("SKILL.md");
2632        std::fs::write(
2633            &skill_path,
2634            "---\nname: test-skill\ndescription: Test skill description\n---\n\n# Test\n",
2635        )
2636        .unwrap();
2637        let registry =
2638            otherone_skills::SkillRegistry::load_from_config(&otherone_skills::SkillsConfig {
2639                include_defaults: false,
2640                user_skills_dir: None,
2641                project_skills_dir: None,
2642                extra_paths: vec![skill_path],
2643                cwd: root,
2644            })
2645            .unwrap();
2646        let runtime = AgentRuntime::builder()
2647            .register_model(profile())
2648            .register_agent(
2649                AgentDefinition::builder("agent")
2650                    .description("agent")
2651                    .allow_skills(["test-skill"])
2652                    .build()
2653                    .unwrap(),
2654            )
2655            .skills(&registry)
2656            .build()
2657            .unwrap();
2658        let snapshot = runtime.snapshot();
2659        let agent = snapshot.agent(&AgentId::new("agent").unwrap()).unwrap();
2660        let prompt = snapshot
2661            .skills
2662            .format_for_prompt(&snapshot.skill_policy_for(&agent));
2663        assert!(prompt.contains("test-skill"));
2664        assert!(prompt.contains("Test skill description"));
2665    }
2666
2667    async fn storage_guard() -> tokio::sync::MutexGuard<'static, ()> {
2668        static LOCK: OnceLock<tokio::sync::Mutex<()>> = OnceLock::new();
2669        LOCK.get_or_init(|| tokio::sync::Mutex::new(()))
2670            .lock()
2671            .await
2672    }
2673
2674    fn agent_call(id: &str, target: &str, task: &str) -> ToolCall {
2675        ToolCall {
2676            index: Some(0),
2677            id: id.to_string(),
2678            call_type: "function".to_string(),
2679            function: otherone_ai::types::FunctionCall {
2680                name: AGENT_CALL_TOOL_NAME.to_string(),
2681                arguments: serde_json::json!({ "agent": target, "task": task }).to_string(),
2682            },
2683        }
2684    }
2685
2686    #[tokio::test]
2687    async fn runs_a_root_agent_with_the_unified_runner() {
2688        let _guard = storage_guard().await;
2689        let root = temp_storage();
2690        otherone_storage::localfile::set_storage_root(root);
2691        let runtime = AgentRuntime::builder()
2692            .register_model(profile())
2693            .register_agent(
2694                AgentDefinition::builder("agent")
2695                    .description("test agent")
2696                    .build()
2697                    .unwrap(),
2698            )
2699            .model_executor(Arc::new(MockModelExecutor::new(vec![response(
2700                "done", None,
2701            )])))
2702            .build()
2703            .unwrap();
2704
2705        let result = runtime
2706            .run(AgentRunRequest::new("agent", "hello"))
2707            .await
2708            .unwrap();
2709        assert_eq!(result.outcome, RunOutcome::Completed);
2710        assert_eq!(result.output, Some(AgentOutput::Text("done".to_string())));
2711        otherone_storage::localfile::clear_storage_root();
2712    }
2713
2714    #[tokio::test]
2715    async fn model_config_uses_provider_specific_parameter_names() {
2716        let _guard = storage_guard().await;
2717        otherone_storage::localfile::set_storage_root(temp_storage());
2718
2719        let anthropic_executor = Arc::new(MockModelExecutor::new(vec![response("done", None)]));
2720        let anthropic = AgentRuntime::builder()
2721            .register_model(
2722                ModelProfile::builder(
2723                    "default",
2724                    otherone_ai::types::ProviderType::Anthropic,
2725                    "mock",
2726                )
2727                .api_key("test")
2728                .base_url("http://localhost")
2729                .max_tokens(321)
2730                .top_p(0.4)
2731                .build()
2732                .unwrap(),
2733            )
2734            .register_agent(
2735                AgentDefinition::builder("agent")
2736                    .description("agent")
2737                    .build()
2738                    .unwrap(),
2739            )
2740            .model_executor(anthropic_executor.clone())
2741            .build()
2742            .unwrap();
2743        anthropic
2744            .run(AgentRunRequest::new("agent", "start"))
2745            .await
2746            .unwrap();
2747        let anthropic_config = anthropic_executor.configs().await.remove(0);
2748        assert_eq!(anthropic_config["max_tokens"], 321);
2749        assert!((anthropic_config["topP"].as_f64().unwrap() - 0.4).abs() < f64::from(f32::EPSILON));
2750        assert!(anthropic_config.get("maxTokens").is_none());
2751
2752        let fetch_executor = Arc::new(MockModelExecutor::new(vec![response("done", None)]));
2753        let fetch = AgentRuntime::builder()
2754            .register_model(
2755                ModelProfile::builder("default", otherone_ai::types::ProviderType::Fetch, "mock")
2756                    .api_key("test")
2757                    .base_url("http://localhost")
2758                    .context_length(2048)
2759                    .top_p(0.6)
2760                    .build()
2761                    .unwrap(),
2762            )
2763            .register_agent(
2764                AgentDefinition::builder("agent")
2765                    .description("agent")
2766                    .build()
2767                    .unwrap(),
2768            )
2769            .model_executor(fetch_executor.clone())
2770            .build()
2771            .unwrap();
2772        fetch
2773            .run(AgentRunRequest::new("agent", "start"))
2774            .await
2775            .unwrap();
2776        let fetch_config = fetch_executor.configs().await.remove(0);
2777        assert_eq!(fetch_config["max_tokens"], 2048);
2778        assert!((fetch_config["top_p"].as_f64().unwrap() - 0.6).abs() < f64::from(f32::EPSILON));
2779        assert!(fetch_config.get("topP").is_none());
2780
2781        otherone_storage::localfile::clear_storage_root();
2782    }
2783
2784    #[tokio::test]
2785    async fn streaming_output_limit_stops_before_persisting_the_oversized_message() {
2786        let _guard = storage_guard().await;
2787        otherone_storage::localfile::set_storage_root(temp_storage());
2788        let runtime = AgentRuntime::builder()
2789            .register_model(profile())
2790            .register_agent(
2791                AgentDefinition::builder("agent")
2792                    .description("agent")
2793                    .build()
2794                    .unwrap(),
2795            )
2796            .runtime_limits(RuntimeLimits {
2797                max_result_bytes: 4,
2798                ..Default::default()
2799            })
2800            .model_executor(Arc::new(MockModelExecutor::new(vec![response(
2801                "oversized",
2802                None,
2803            )])))
2804            .build()
2805            .unwrap();
2806
2807        let result = runtime
2808            .run(AgentRunRequest::new("agent", "start"))
2809            .await
2810            .unwrap();
2811        assert_eq!(result.outcome, RunOutcome::BudgetExceeded);
2812        let session =
2813            otherone_storage::localfile::reader::read_session_data(&result.session_id).unwrap();
2814        assert_eq!(session.entries.len(), 1);
2815        assert_eq!(session.entries[0].role, "user");
2816        otherone_storage::localfile::clear_storage_root();
2817    }
2818
2819    #[tokio::test]
2820    async fn agent_call_executes_child_and_returns_to_parent() {
2821        let _guard = storage_guard().await;
2822        let root = temp_storage();
2823        otherone_storage::localfile::set_storage_root(root);
2824        let call = agent_call("call_1", "worker", "do work");
2825        let runtime = AgentRuntime::builder()
2826            .register_model(profile())
2827            .register_agent(
2828                AgentDefinition::builder("coordinator")
2829                    .description("coordinates")
2830                    .allow_agents(["worker"])
2831                    .build()
2832                    .unwrap(),
2833            )
2834            .register_agent(
2835                AgentDefinition::builder("worker")
2836                    .description("works")
2837                    .build()
2838                    .unwrap(),
2839            )
2840            .model_executor(Arc::new(MockModelExecutor::new(vec![
2841                response("", Some(vec![call])),
2842                response("worker result", None),
2843                response("final answer", None),
2844            ])))
2845            .build()
2846            .unwrap();
2847
2848        let result = runtime
2849            .run(AgentRunRequest::new("coordinator", "start"))
2850            .await
2851            .unwrap();
2852        assert_eq!(result.outcome, RunOutcome::Completed);
2853        assert_eq!(
2854            result.output,
2855            Some(AgentOutput::Text("final answer".to_string()))
2856        );
2857        let records = runtime.list_run_tree(&result.root_run_id).await;
2858        assert_eq!(records.len(), 2);
2859        assert!(records
2860            .iter()
2861            .any(|record| record.agent_id.as_str() == "worker"));
2862        assert_eq!(
2863            otherone_storage::localfile::reader::get_all_sessions()
2864                .unwrap()
2865                .len(),
2866            1
2867        );
2868        assert_eq!(
2869            otherone_storage::localfile::reader::get_all_sessions_with_internal(true)
2870                .unwrap()
2871                .len(),
2872            2
2873        );
2874        otherone_storage::localfile::clear_storage_root();
2875    }
2876
2877    #[tokio::test]
2878    async fn child_context_is_isolated_from_parent_history() {
2879        let _guard = storage_guard().await;
2880        otherone_storage::localfile::set_storage_root(temp_storage());
2881        let executor = Arc::new(MockModelExecutor::new(vec![
2882            response("", Some(vec![agent_call("call_1", "worker", "child task")])),
2883            response("child done", None),
2884            response("root done", None),
2885        ]));
2886        let runtime = AgentRuntime::builder()
2887            .register_model(profile())
2888            .register_agent(
2889                AgentDefinition::builder("root")
2890                    .description("root")
2891                    .allow_agents(["worker"])
2892                    .build()
2893                    .unwrap(),
2894            )
2895            .register_agent(
2896                AgentDefinition::builder("worker")
2897                    .description("worker")
2898                    .build()
2899                    .unwrap(),
2900            )
2901            .model_executor(executor.clone())
2902            .build()
2903            .unwrap();
2904
2905        runtime
2906            .run(AgentRunRequest::new("root", "parent private history"))
2907            .await
2908            .unwrap();
2909        let configs = executor.configs().await;
2910        let child_messages = configs[1]["messages"].to_string();
2911        assert!(child_messages.contains("child task"));
2912        assert!(!child_messages.contains("parent private history"));
2913        otherone_storage::localfile::clear_storage_root();
2914    }
2915
2916    #[tokio::test]
2917    async fn model_cannot_execute_a_registered_but_disallowed_tool() {
2918        let _guard = storage_guard().await;
2919        otherone_storage::localfile::set_storage_root(temp_storage());
2920        let executions = Arc::new(TestAtomicUsize::new(0));
2921        let counter = Arc::clone(&executions);
2922        let dangerous_tool = ToolRegistration::sync(
2923            Tool {
2924                tool_type: "function".to_string(),
2925                function: FunctionDefinition {
2926                    name: "dangerous".to_string(),
2927                    description: "dangerous".to_string(),
2928                    parameters: None,
2929                },
2930            },
2931            move |_| {
2932                counter.fetch_add(1, Ordering::SeqCst);
2933                "executed".to_string()
2934            },
2935        );
2936        let hallucinated_call = ToolCall {
2937            index: Some(0),
2938            id: "call_dangerous".to_string(),
2939            call_type: "function".to_string(),
2940            function: otherone_ai::types::FunctionCall {
2941                name: "dangerous".to_string(),
2942                arguments: "{}".to_string(),
2943            },
2944        };
2945        let runtime = AgentRuntime::builder()
2946            .register_model(profile())
2947            .register_agent(
2948                AgentDefinition::builder("agent")
2949                    .description("agent")
2950                    .build()
2951                    .unwrap(),
2952            )
2953            .register_tool(dangerous_tool)
2954            .model_executor(Arc::new(MockModelExecutor::new(vec![
2955                response("", Some(vec![hallucinated_call])),
2956                response("done", None),
2957            ])))
2958            .build()
2959            .unwrap();
2960
2961        let result = runtime
2962            .run(AgentRunRequest::new("agent", "start"))
2963            .await
2964            .unwrap();
2965        assert_eq!(result.outcome, RunOutcome::Completed);
2966        assert_eq!(executions.load(Ordering::SeqCst), 0);
2967        otherone_storage::localfile::clear_storage_root();
2968    }
2969
2970    #[tokio::test]
2971    async fn fatal_tool_errors_terminate_the_current_run() {
2972        let _guard = storage_guard().await;
2973        otherone_storage::localfile::set_storage_root(temp_storage());
2974        let fatal_tool = ToolRegistration::sync(
2975            Tool {
2976                tool_type: "function".to_string(),
2977                function: FunctionDefinition {
2978                    name: "fatal_tool".to_string(),
2979                    description: "fails fatally".to_string(),
2980                    parameters: None,
2981                },
2982            },
2983            |_| -> String { panic!("fatal tool panic") },
2984        );
2985        let call = ToolCall {
2986            index: Some(0),
2987            id: "call_fatal".to_string(),
2988            call_type: "function".to_string(),
2989            function: otherone_ai::types::FunctionCall {
2990                name: "fatal_tool".to_string(),
2991                arguments: "{}".to_string(),
2992            },
2993        };
2994        let executor = Arc::new(MockModelExecutor::new(vec![response("", Some(vec![call]))]));
2995        let runtime = AgentRuntime::builder()
2996            .register_model(profile())
2997            .register_agent(
2998                AgentDefinition::builder("agent")
2999                    .description("agent")
3000                    .allow_tools(["fatal_tool"])
3001                    .build()
3002                    .unwrap(),
3003            )
3004            .register_tool(fatal_tool)
3005            .model_executor(executor.clone())
3006            .build()
3007            .unwrap();
3008
3009        let result = runtime
3010            .run(AgentRunRequest::new("agent", "start"))
3011            .await
3012            .unwrap();
3013        assert!(matches!(
3014            &result.outcome,
3015            RunOutcome::Failed(AgentFailure { code, .. }) if code == "tool_error"
3016        ));
3017        assert_eq!(result.usage.model_calls, 1);
3018        assert_eq!(result.usage.tool_calls, 1);
3019        assert_eq!(executor.configs().await.len(), 1);
3020        otherone_storage::localfile::clear_storage_root();
3021    }
3022
3023    #[tokio::test]
3024    async fn denies_agent_call_cycles_without_starting_a_third_run() {
3025        let _guard = storage_guard().await;
3026        otherone_storage::localfile::set_storage_root(temp_storage());
3027        let runtime = AgentRuntime::builder()
3028            .register_model(profile())
3029            .register_agent(
3030                AgentDefinition::builder("agent_a")
3031                    .description("A")
3032                    .allow_agents(["agent_b"])
3033                    .build()
3034                    .unwrap(),
3035            )
3036            .register_agent(
3037                AgentDefinition::builder("agent_b")
3038                    .description("B")
3039                    .allow_agents(["agent_a"])
3040                    .build()
3041                    .unwrap(),
3042            )
3043            .model_executor(Arc::new(MockModelExecutor::new(vec![
3044                response("", Some(vec![agent_call("call_b", "agent_b", "go to B")])),
3045                response(
3046                    "",
3047                    Some(vec![agent_call("call_a", "agent_a", "go back to A")]),
3048                ),
3049                response("B handled cycle", None),
3050                response("A final", None),
3051            ])))
3052            .build()
3053            .unwrap();
3054
3055        let result = runtime
3056            .run(AgentRunRequest::new("agent_a", "start"))
3057            .await
3058            .unwrap();
3059        assert_eq!(result.outcome, RunOutcome::Completed);
3060        let records = runtime.list_run_tree(&result.root_run_id).await;
3061        assert_eq!(records.len(), 2);
3062        otherone_storage::localfile::clear_storage_root();
3063    }
3064
3065    #[tokio::test]
3066    async fn nested_call_does_not_deadlock_with_concurrency_one() {
3067        let _guard = storage_guard().await;
3068        otherone_storage::localfile::set_storage_root(temp_storage());
3069        let limits = RuntimeLimits {
3070            max_concurrent_model_calls: 1,
3071            max_concurrent_tool_calls: 1,
3072            ..RuntimeLimits::default()
3073        };
3074        let runtime = AgentRuntime::builder()
3075            .register_model(profile())
3076            .register_agent(
3077                AgentDefinition::builder("root")
3078                    .description("root")
3079                    .allow_agents(["worker"])
3080                    .build()
3081                    .unwrap(),
3082            )
3083            .register_agent(
3084                AgentDefinition::builder("worker")
3085                    .description("worker")
3086                    .build()
3087                    .unwrap(),
3088            )
3089            .runtime_limits(limits)
3090            .model_executor(Arc::new(MockModelExecutor::new(vec![
3091                response("", Some(vec![agent_call("call_1", "worker", "work")])),
3092                response("worked", None),
3093                response("done", None),
3094            ])))
3095            .build()
3096            .unwrap();
3097
3098        let result = tokio::time::timeout(
3099            Duration::from_secs(2),
3100            runtime.run(AgentRunRequest::new("root", "start")),
3101        )
3102        .await
3103        .expect("nested Agent call deadlocked")
3104        .unwrap();
3105        assert_eq!(result.outcome, RunOutcome::Completed);
3106        otherone_storage::localfile::clear_storage_root();
3107    }
3108
3109    struct PendingModelExecutor;
3110
3111    #[async_trait]
3112    impl ModelExecutor for PendingModelExecutor {
3113        async fn stream(
3114            &self,
3115            _profile: &ModelProfile,
3116            _config: serde_json::Value,
3117        ) -> Result<ChatStream, AiError> {
3118            Ok(Box::pin(stream::pending()))
3119        }
3120    }
3121
3122    struct RootThenPendingModelExecutor {
3123        calls: TestAtomicUsize,
3124    }
3125
3126    #[async_trait]
3127    impl ModelExecutor for RootThenPendingModelExecutor {
3128        async fn stream(
3129            &self,
3130            _profile: &ModelProfile,
3131            _config: serde_json::Value,
3132        ) -> Result<ChatStream, AiError> {
3133            if self.calls.fetch_add(1, Ordering::SeqCst) == 0 {
3134                Ok(Box::pin(stream::iter(vec![Ok(response(
3135                    "",
3136                    Some(vec![agent_call("call_1", "worker", "wait")]),
3137                ))])))
3138            } else {
3139                Ok(Box::pin(stream::pending()))
3140            }
3141        }
3142    }
3143
3144    struct RootPendingThenInvalidJsonExecutor {
3145        calls: TestAtomicUsize,
3146    }
3147
3148    #[async_trait]
3149    impl ModelExecutor for RootPendingThenInvalidJsonExecutor {
3150        async fn stream(
3151            &self,
3152            _profile: &ModelProfile,
3153            _config: serde_json::Value,
3154        ) -> Result<ChatStream, AiError> {
3155            if self.calls.fetch_add(1, Ordering::SeqCst) == 0 {
3156                Ok(Box::pin(stream::pending()))
3157            } else {
3158                Ok(Box::pin(stream::iter(vec![Ok(response("not json", None))])))
3159            }
3160        }
3161    }
3162
3163    struct GatedModelExecutor {
3164        calls: TestAtomicUsize,
3165        release: Arc<tokio::sync::Semaphore>,
3166    }
3167
3168    #[async_trait]
3169    impl ModelExecutor for GatedModelExecutor {
3170        async fn stream(
3171            &self,
3172            _profile: &ModelProfile,
3173            _config: serde_json::Value,
3174        ) -> Result<ChatStream, AiError> {
3175            self.calls.fetch_add(1, Ordering::SeqCst);
3176            self.release
3177                .acquire()
3178                .await
3179                .map_err(|_| AiError::Other("gate closed".to_string()))?
3180                .forget();
3181            Ok(Box::pin(stream::iter(vec![Ok(response("once", None))])))
3182        }
3183    }
3184
3185    struct RootPendingWorkerGatedExecutor {
3186        calls: TestAtomicUsize,
3187        release: Arc<tokio::sync::Semaphore>,
3188    }
3189
3190    #[async_trait]
3191    impl ModelExecutor for RootPendingWorkerGatedExecutor {
3192        async fn stream(
3193            &self,
3194            _profile: &ModelProfile,
3195            _config: serde_json::Value,
3196        ) -> Result<ChatStream, AiError> {
3197            let call = self.calls.fetch_add(1, Ordering::SeqCst);
3198            if call == 0 {
3199                return Ok(Box::pin(stream::pending()));
3200            }
3201            self.release
3202                .acquire()
3203                .await
3204                .map_err(|_| AiError::Other("gate closed".to_string()))?
3205                .forget();
3206            Ok(Box::pin(stream::iter(vec![Ok(response("worked", None))])))
3207        }
3208    }
3209
3210    #[tokio::test]
3211    async fn cancellation_stops_an_active_root_run() {
3212        let _guard = storage_guard().await;
3213        otherone_storage::localfile::set_storage_root(temp_storage());
3214        let runtime = AgentRuntime::builder()
3215            .register_model(profile())
3216            .register_agent(
3217                AgentDefinition::builder("agent")
3218                    .description("agent")
3219                    .build()
3220                    .unwrap(),
3221            )
3222            .model_executor(Arc::new(PendingModelExecutor))
3223            .build()
3224            .unwrap();
3225        let handle = runtime
3226            .start(AgentRunRequest::new("agent", "wait"))
3227            .await
3228            .unwrap();
3229        handle.commands.cancel().await.unwrap();
3230        let result = tokio::time::timeout(Duration::from_secs(2), handle.result())
3231            .await
3232            .expect("cancelled run did not finish")
3233            .unwrap();
3234        assert_eq!(result.outcome, RunOutcome::Cancelled);
3235        otherone_storage::localfile::clear_storage_root();
3236    }
3237
3238    #[tokio::test]
3239    async fn request_root_timeout_tightens_the_runtime_deadline() {
3240        let _guard = storage_guard().await;
3241        otherone_storage::localfile::set_storage_root(temp_storage());
3242        let runtime = AgentRuntime::builder()
3243            .register_model(profile())
3244            .register_agent(
3245                AgentDefinition::builder("agent")
3246                    .description("agent")
3247                    .build()
3248                    .unwrap(),
3249            )
3250            .model_executor(Arc::new(PendingModelExecutor))
3251            .build()
3252            .unwrap();
3253        let request = AgentRunRequest::new("agent", "wait").limits(RunLimitOverrides {
3254            root_timeout: Some(Duration::from_millis(20)),
3255            ..Default::default()
3256        });
3257        let result = tokio::time::timeout(Duration::from_secs(2), runtime.run(request))
3258            .await
3259            .expect("request root timeout was not applied")
3260            .unwrap();
3261        assert_eq!(result.outcome, RunOutcome::TimedOut);
3262        otherone_storage::localfile::clear_storage_root();
3263    }
3264
3265    #[tokio::test]
3266    async fn root_cancellation_finishes_and_unregisters_active_children() {
3267        let _guard = storage_guard().await;
3268        otherone_storage::localfile::set_storage_root(temp_storage());
3269        let runtime = AgentRuntime::builder()
3270            .register_model(profile())
3271            .register_agent(
3272                AgentDefinition::builder("root")
3273                    .description("root")
3274                    .allow_agents(["worker"])
3275                    .build()
3276                    .unwrap(),
3277            )
3278            .register_agent(
3279                AgentDefinition::builder("worker")
3280                    .description("worker")
3281                    .build()
3282                    .unwrap(),
3283            )
3284            .model_executor(Arc::new(RootThenPendingModelExecutor {
3285                calls: TestAtomicUsize::new(0),
3286            }))
3287            .build()
3288            .unwrap();
3289        let handle = runtime
3290            .start(AgentRunRequest::new("root", "start"))
3291            .await
3292            .unwrap();
3293        let root_run_id = handle.run_id.clone();
3294        let child_run_id = tokio::time::timeout(Duration::from_secs(2), async {
3295            loop {
3296                if let Some(record) = runtime
3297                    .list_run_tree(&root_run_id)
3298                    .await
3299                    .into_iter()
3300                    .find(|record| record.depth == 1)
3301                {
3302                    break record.run_id;
3303                }
3304                tokio::task::yield_now().await;
3305            }
3306        })
3307        .await
3308        .expect("child run did not start");
3309
3310        handle.commands.cancel().await.unwrap();
3311        let root_result = tokio::time::timeout(Duration::from_secs(2), handle.result())
3312            .await
3313            .expect("cancelled root run did not finish")
3314            .unwrap();
3315        assert_eq!(root_result.outcome, RunOutcome::Cancelled);
3316
3317        tokio::time::timeout(Duration::from_secs(2), async {
3318            loop {
3319                if runtime
3320                    .get_run(&child_run_id)
3321                    .await
3322                    .is_some_and(|record| matches!(record.status, RunStatus::Cancelled))
3323                {
3324                    break;
3325                }
3326                tokio::task::yield_now().await;
3327            }
3328        })
3329        .await
3330        .expect("cancelled child run did not finish");
3331        assert!(runtime.cancel(&child_run_id).await.is_err());
3332        otherone_storage::localfile::clear_storage_root();
3333    }
3334
3335    #[tokio::test]
3336    async fn invalid_child_result_contract_updates_the_child_run_status() {
3337        let _guard = storage_guard().await;
3338        otherone_storage::localfile::set_storage_root(temp_storage());
3339        let executor = Arc::new(RootPendingThenInvalidJsonExecutor {
3340            calls: TestAtomicUsize::new(0),
3341        });
3342        let runtime = AgentRuntime::builder()
3343            .register_model(profile())
3344            .register_agent(
3345                AgentDefinition::builder("root")
3346                    .description("root")
3347                    .allow_agents(["worker"])
3348                    .build()
3349                    .unwrap(),
3350            )
3351            .register_agent(
3352                AgentDefinition::builder("worker")
3353                    .description("worker")
3354                    .build()
3355                    .unwrap(),
3356            )
3357            .model_executor(executor.clone())
3358            .build()
3359            .unwrap();
3360        let handle = runtime
3361            .start(AgentRunRequest::new("root", "wait"))
3362            .await
3363            .unwrap();
3364        tokio::time::timeout(Duration::from_secs(2), async {
3365            while executor.calls.load(Ordering::SeqCst) == 0 {
3366                tokio::task::yield_now().await;
3367            }
3368        })
3369        .await
3370        .expect("root model call did not start");
3371
3372        let call_result = runtime
3373            .call_agent(
3374                &handle.run_id,
3375                AgentCallId::new("trusted-call").unwrap(),
3376                AgentCallRequest::new("worker", "return JSON")
3377                    .result_contract(ResultContract::Json { schema: None }),
3378            )
3379            .await
3380            .unwrap();
3381        assert!(matches!(
3382            &call_result.outcome,
3383            RunOutcome::Failed(AgentFailure { code, .. }) if code == "invalid_agent_output"
3384        ));
3385        let child = runtime
3386            .get_run(call_result.run_id.as_ref().unwrap())
3387            .await
3388            .unwrap();
3389        assert!(matches!(child.status, RunStatus::Failed));
3390        assert!(matches!(
3391            &child.failure,
3392            Some(AgentFailure { code, .. }) if code == "invalid_agent_output"
3393        ));
3394
3395        handle.commands.cancel().await.unwrap();
3396        let _ = handle.result().await.unwrap();
3397        otherone_storage::localfile::clear_storage_root();
3398    }
3399
3400    #[tokio::test]
3401    async fn session_writer_lock_is_shared_across_runtimes() {
3402        let _guard = storage_guard().await;
3403        otherone_storage::localfile::set_storage_root(temp_storage());
3404        let build_runtime = || {
3405            AgentRuntime::builder()
3406                .register_model(profile())
3407                .register_agent(
3408                    AgentDefinition::builder("agent")
3409                        .description("agent")
3410                        .build()
3411                        .unwrap(),
3412                )
3413                .model_executor(Arc::new(PendingModelExecutor))
3414                .build()
3415                .unwrap()
3416        };
3417        let first_runtime = build_runtime();
3418        let second_runtime = build_runtime();
3419        let request = AgentRunRequest::new("agent", "wait")
3420            .session(super::super::types::SessionTarget::local().session_id("shared-session"));
3421        let first = first_runtime.start(request.clone()).await.unwrap();
3422        let second = second_runtime.start(request).await;
3423        assert!(matches!(second, Err(AgentError::SessionBusy(_))));
3424        first.commands.cancel().await.unwrap();
3425        let _ = first.result().await.unwrap();
3426        otherone_storage::localfile::clear_storage_root();
3427    }
3428
3429    #[tokio::test]
3430    async fn root_idempotency_returns_the_original_result() {
3431        let _guard = storage_guard().await;
3432        otherone_storage::localfile::set_storage_root(temp_storage());
3433        let executor = Arc::new(MockModelExecutor::new(vec![response("once", None)]));
3434        let runtime = AgentRuntime::builder()
3435            .register_model(profile())
3436            .register_agent(
3437                AgentDefinition::builder("agent")
3438                    .description("agent")
3439                    .build()
3440                    .unwrap(),
3441            )
3442            .model_executor(executor.clone())
3443            .build()
3444            .unwrap();
3445        let request = AgentRunRequest::new("agent", "hello").idempotency_key("request-1");
3446        let first = runtime.run(request.clone()).await.unwrap();
3447        let second = runtime.run(request).await.unwrap();
3448        assert_eq!(first.run_id, second.run_id);
3449        assert_eq!(executor.configs().await.len(), 1);
3450        otherone_storage::localfile::clear_storage_root();
3451    }
3452
3453    #[tokio::test]
3454    async fn root_idempotency_survives_a_cancelled_waiter() {
3455        let _guard = storage_guard().await;
3456        otherone_storage::localfile::set_storage_root(temp_storage());
3457        let release = Arc::new(tokio::sync::Semaphore::new(0));
3458        let executor = Arc::new(GatedModelExecutor {
3459            calls: TestAtomicUsize::new(0),
3460            release: Arc::clone(&release),
3461        });
3462        let runtime = AgentRuntime::builder()
3463            .register_model(profile())
3464            .register_agent(
3465                AgentDefinition::builder("agent")
3466                    .description("agent")
3467                    .build()
3468                    .unwrap(),
3469            )
3470            .model_executor(executor.clone())
3471            .build()
3472            .unwrap();
3473        let request = AgentRunRequest::new("agent", "hello").idempotency_key("request-1");
3474        let first_runtime = runtime.clone();
3475        let first_request = request.clone();
3476        let first_waiter = tokio::spawn(async move { first_runtime.run(first_request).await });
3477        tokio::time::timeout(Duration::from_secs(2), async {
3478            while executor.calls.load(Ordering::SeqCst) == 0 {
3479                tokio::task::yield_now().await;
3480            }
3481        })
3482        .await
3483        .expect("idempotent producer did not start");
3484        first_waiter.abort();
3485        let _ = first_waiter.await;
3486        release.add_permits(1);
3487
3488        let result = tokio::time::timeout(Duration::from_secs(2), runtime.run(request))
3489            .await
3490            .expect("replacement waiter did not receive the cached result")
3491            .unwrap();
3492        assert_eq!(result.output, Some(AgentOutput::Text("once".to_string())));
3493        assert_eq!(executor.calls.load(Ordering::SeqCst), 1);
3494        otherone_storage::localfile::clear_storage_root();
3495    }
3496
3497    #[tokio::test]
3498    async fn repeated_agent_call_id_executes_child_once() {
3499        let _guard = storage_guard().await;
3500        otherone_storage::localfile::set_storage_root(temp_storage());
3501        let executor = Arc::new(MockModelExecutor::new(vec![
3502            response("", Some(vec![agent_call("same_call", "worker", "work")])),
3503            response("worked once", None),
3504            response("", Some(vec![agent_call("same_call", "worker", "work")])),
3505            response("done", None),
3506        ]));
3507        let runtime = AgentRuntime::builder()
3508            .register_model(profile())
3509            .register_agent(
3510                AgentDefinition::builder("root")
3511                    .description("root")
3512                    .allow_agents(["worker"])
3513                    .build()
3514                    .unwrap(),
3515            )
3516            .register_agent(
3517                AgentDefinition::builder("worker")
3518                    .description("worker")
3519                    .build()
3520                    .unwrap(),
3521            )
3522            .model_executor(executor.clone())
3523            .build()
3524            .unwrap();
3525
3526        let result = runtime
3527            .run(AgentRunRequest::new("root", "start"))
3528            .await
3529            .unwrap();
3530        assert_eq!(runtime.list_run_tree(&result.root_run_id).await.len(), 2);
3531        assert_eq!(executor.configs().await.len(), 4);
3532        otherone_storage::localfile::clear_storage_root();
3533    }
3534
3535    #[tokio::test]
3536    async fn agent_call_idempotency_survives_a_cancelled_waiter() {
3537        let _guard = storage_guard().await;
3538        otherone_storage::localfile::set_storage_root(temp_storage());
3539        let release = Arc::new(tokio::sync::Semaphore::new(0));
3540        let executor = Arc::new(RootPendingWorkerGatedExecutor {
3541            calls: TestAtomicUsize::new(0),
3542            release: Arc::clone(&release),
3543        });
3544        let runtime = AgentRuntime::builder()
3545            .register_model(profile())
3546            .register_agent(
3547                AgentDefinition::builder("root")
3548                    .description("root")
3549                    .allow_agents(["worker"])
3550                    .build()
3551                    .unwrap(),
3552            )
3553            .register_agent(
3554                AgentDefinition::builder("worker")
3555                    .description("worker")
3556                    .build()
3557                    .unwrap(),
3558            )
3559            .model_executor(executor.clone())
3560            .build()
3561            .unwrap();
3562        let handle = runtime
3563            .start(AgentRunRequest::new("root", "wait"))
3564            .await
3565            .unwrap();
3566        tokio::time::timeout(Duration::from_secs(2), async {
3567            while executor.calls.load(Ordering::SeqCst) == 0 {
3568                tokio::task::yield_now().await;
3569            }
3570        })
3571        .await
3572        .expect("root model call did not start");
3573
3574        let first_runtime = runtime.clone();
3575        let caller_run_id = handle.run_id.clone();
3576        let first_waiter = tokio::spawn(async move {
3577            first_runtime
3578                .call_agent(
3579                    &caller_run_id,
3580                    AgentCallId::new("stable-call").unwrap(),
3581                    AgentCallRequest::new("worker", "work"),
3582                )
3583                .await
3584        });
3585        tokio::time::timeout(Duration::from_secs(2), async {
3586            while executor.calls.load(Ordering::SeqCst) < 2 {
3587                tokio::task::yield_now().await;
3588            }
3589        })
3590        .await
3591        .expect("child model call did not start");
3592        first_waiter.abort();
3593        let _ = first_waiter.await;
3594        release.add_permits(1);
3595
3596        let result = tokio::time::timeout(
3597            Duration::from_secs(2),
3598            runtime.call_agent(
3599                &handle.run_id,
3600                AgentCallId::new("stable-call").unwrap(),
3601                AgentCallRequest::new("worker", "work"),
3602            ),
3603        )
3604        .await
3605        .expect("replacement Agent-call waiter did not receive the result")
3606        .unwrap();
3607        assert_eq!(result.output, Some(AgentOutput::Text("worked".to_string())));
3608        assert_eq!(executor.calls.load(Ordering::SeqCst), 2);
3609        assert_eq!(runtime.list_run_tree(&handle.run_id).await.len(), 2);
3610
3611        handle.commands.cancel().await.unwrap();
3612        let _ = handle.result().await.unwrap();
3613        otherone_storage::localfile::clear_storage_root();
3614    }
3615
3616    #[tokio::test]
3617    async fn event_stream_contains_parent_and_child_lineage() {
3618        let _guard = storage_guard().await;
3619        otherone_storage::localfile::set_storage_root(temp_storage());
3620        let runtime = AgentRuntime::builder()
3621            .register_model(profile())
3622            .register_agent(
3623                AgentDefinition::builder("root")
3624                    .description("root")
3625                    .allow_agents(["worker"])
3626                    .build()
3627                    .unwrap(),
3628            )
3629            .register_agent(
3630                AgentDefinition::builder("worker")
3631                    .description("worker")
3632                    .build()
3633                    .unwrap(),
3634            )
3635            .model_executor(Arc::new(MockModelExecutor::new(vec![
3636                response("", Some(vec![agent_call("call_1", "worker", "work")])),
3637                response("worked", None),
3638                response("done", None),
3639            ])))
3640            .build()
3641            .unwrap();
3642        let mut handle = runtime
3643            .start(AgentRunRequest::new("root", "start"))
3644            .await
3645            .unwrap();
3646        let mut events = Vec::new();
3647        while let Some(event) = handle.events.recv().await {
3648            events.push(event);
3649        }
3650        let result = handle.result().await.unwrap();
3651        assert!(events.iter().any(|event| event.run_id == result.run_id));
3652        assert!(events.iter().any(|event| {
3653            event.parent_run_id.as_ref() == Some(&result.run_id)
3654                && event.agent_id.as_str() == "worker"
3655        }));
3656        let mut sequences = events
3657            .iter()
3658            .map(|event| event.sequence)
3659            .collect::<Vec<_>>();
3660        sequences.sort_unstable();
3661        sequences.dedup();
3662        assert_eq!(sequences.len(), events.len());
3663        otherone_storage::localfile::clear_storage_root();
3664    }
3665}