tirea_agentos/composition/
agent_definition.rs

1use super::stop_condition::StopConditionSpec;
2use super::AgentDescriptor;
3use crate::runtime::loop_runner::LlmRetryPolicy;
4use genai::chat::ChatOptions;
5
6/// Tool execution strategy mode exposed by AgentDefinition.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ToolExecutionMode {
9    Sequential,
10    ParallelBatchApproval,
11    ParallelStreaming,
12}
13
14/// Agent composition definition owned by AgentOS.
15///
16/// This is the orchestration-facing model and uses only registry references
17/// (`behavior_ids`, `stop_condition_ids`) and declarative specs.
18/// Before execution, AgentOS resolves it into loop-facing [`BaseAgent`].
19#[derive(Clone)]
20pub struct AgentDefinition {
21    /// Unique identifier for this agent.
22    pub id: String,
23    /// Human-readable display name used in discovery surfaces.
24    #[allow(dead_code)]
25    pub name: Option<String>,
26    /// Short description exposed to callers/models when this agent is discoverable.
27    #[allow(dead_code)]
28    pub description: Option<String>,
29    /// Model identifier (e.g., "gpt-4", "claude-3-opus").
30    pub model: String,
31    /// System prompt for the LLM.
32    pub system_prompt: String,
33    /// Maximum number of tool call rounds before stopping.
34    pub max_rounds: usize,
35    /// Tool execution strategy.
36    pub tool_execution_mode: ToolExecutionMode,
37    /// Chat options for the LLM.
38    pub chat_options: Option<ChatOptions>,
39    /// Fallback model ids used when the primary model fails.
40    ///
41    /// Evaluated in order after `model`.
42    pub fallback_models: Vec<String>,
43    /// Retry policy for LLM inference failures.
44    pub llm_retry_policy: LlmRetryPolicy,
45    /// Behavior references resolved from AgentOS behavior registry.
46    pub behavior_ids: Vec<String>,
47    /// Tool whitelist (None = all tools available).
48    pub allowed_tools: Option<Vec<String>>,
49    /// Tool blacklist.
50    pub excluded_tools: Option<Vec<String>>,
51    /// Skill whitelist (None = all skills available).
52    pub allowed_skills: Option<Vec<String>>,
53    /// Skill blacklist.
54    pub excluded_skills: Option<Vec<String>>,
55    /// Agent whitelist for `agent_run` delegation (None = all visible agents available).
56    pub allowed_agents: Option<Vec<String>>,
57    /// Agent blacklist for `agent_run` delegation.
58    pub excluded_agents: Option<Vec<String>>,
59    /// Declarative stop condition specs, resolved at runtime.
60    pub stop_condition_specs: Vec<StopConditionSpec>,
61    /// Stop condition references resolved from AgentOS StopPolicyRegistry.
62    pub stop_condition_ids: Vec<String>,
63}
64
65impl Default for AgentDefinition {
66    fn default() -> Self {
67        Self {
68            id: "default".to_string(),
69            name: None,
70            description: None,
71            model: "gpt-4o-mini".to_string(),
72            system_prompt: String::new(),
73            max_rounds: 10,
74            tool_execution_mode: ToolExecutionMode::ParallelStreaming,
75            chat_options: Some(
76                ChatOptions::default()
77                    .with_capture_usage(true)
78                    .with_capture_reasoning_content(true)
79                    .with_capture_tool_calls(true),
80            ),
81            fallback_models: Vec::new(),
82            llm_retry_policy: LlmRetryPolicy::default(),
83            behavior_ids: Vec::new(),
84            allowed_tools: None,
85            excluded_tools: None,
86            allowed_skills: None,
87            excluded_skills: None,
88            allowed_agents: None,
89            excluded_agents: None,
90            stop_condition_specs: Vec::new(),
91            stop_condition_ids: Vec::new(),
92        }
93    }
94}
95
96impl std::fmt::Debug for AgentDefinition {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.debug_struct("AgentDefinition")
99            .field("id", &self.id)
100            .field("name", &self.name)
101            .field("description", &self.description)
102            .field("model", &self.model)
103            .field(
104                "system_prompt",
105                &format!("[{} chars]", self.system_prompt.len()),
106            )
107            .field("max_rounds", &self.max_rounds)
108            .field("tool_execution_mode", &self.tool_execution_mode)
109            .field("chat_options", &self.chat_options)
110            .field("fallback_models", &self.fallback_models)
111            .field("llm_retry_policy", &self.llm_retry_policy)
112            .field("behavior_ids", &self.behavior_ids)
113            .field("allowed_tools", &self.allowed_tools)
114            .field("excluded_tools", &self.excluded_tools)
115            .field("allowed_skills", &self.allowed_skills)
116            .field("excluded_skills", &self.excluded_skills)
117            .field("allowed_agents", &self.allowed_agents)
118            .field("excluded_agents", &self.excluded_agents)
119            .field("stop_condition_specs", &self.stop_condition_specs)
120            .field("stop_condition_ids", &self.stop_condition_ids)
121            .finish()
122    }
123}
124
125impl AgentDefinition {
126    tirea_contract::impl_shared_agent_builder_methods!();
127
128    #[must_use]
129    pub fn with_name(mut self, name: impl Into<String>) -> Self {
130        self.name = Some(name.into());
131        self
132    }
133
134    #[must_use]
135    pub fn with_description(mut self, description: impl Into<String>) -> Self {
136        self.description = Some(description.into());
137        self
138    }
139
140    #[must_use]
141    pub fn display_name(&self) -> &str {
142        self.name.as_deref().unwrap_or(&self.id)
143    }
144
145    #[must_use]
146    pub fn display_description(&self) -> &str {
147        self.description.as_deref().unwrap_or("")
148    }
149
150    #[must_use]
151    pub fn descriptor(&self) -> AgentDescriptor {
152        AgentDescriptor::new(self.id.clone())
153            .with_name(self.display_name())
154            .with_description(self.display_description())
155    }
156
157    #[must_use]
158    pub fn with_stop_condition_spec(mut self, spec: StopConditionSpec) -> Self {
159        self.stop_condition_specs.push(spec);
160        self
161    }
162
163    #[must_use]
164    pub fn with_stop_condition_specs(mut self, specs: Vec<StopConditionSpec>) -> Self {
165        self.stop_condition_specs = specs;
166        self
167    }
168
169    #[must_use]
170    pub fn with_tool_execution_mode(mut self, mode: ToolExecutionMode) -> Self {
171        self.tool_execution_mode = mode;
172        self
173    }
174
175    #[must_use]
176    pub fn with_behavior_ids(mut self, behavior_ids: Vec<String>) -> Self {
177        self.behavior_ids = behavior_ids;
178        self
179    }
180
181    #[must_use]
182    pub fn with_behavior_id(mut self, behavior_id: impl Into<String>) -> Self {
183        self.behavior_ids.push(behavior_id.into());
184        self
185    }
186
187    #[must_use]
188    pub fn with_stop_condition_id(mut self, id: impl Into<String>) -> Self {
189        self.stop_condition_ids.push(id.into());
190        self
191    }
192
193    #[must_use]
194    pub fn with_stop_condition_ids(mut self, ids: Vec<String>) -> Self {
195        self.stop_condition_ids = ids;
196        self
197    }
198
199    #[must_use]
200    pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
201        self.allowed_tools = Some(tools);
202        self
203    }
204
205    #[must_use]
206    pub fn with_excluded_tools(mut self, tools: Vec<String>) -> Self {
207        self.excluded_tools = Some(tools);
208        self
209    }
210
211    #[must_use]
212    pub fn with_allowed_skills(mut self, skills: Vec<String>) -> Self {
213        self.allowed_skills = Some(skills);
214        self
215    }
216
217    #[must_use]
218    pub fn with_excluded_skills(mut self, skills: Vec<String>) -> Self {
219        self.excluded_skills = Some(skills);
220        self
221    }
222
223    #[must_use]
224    pub fn with_allowed_agents(mut self, agents: Vec<String>) -> Self {
225        self.allowed_agents = Some(agents);
226        self
227    }
228
229    #[must_use]
230    pub fn with_excluded_agents(mut self, agents: Vec<String>) -> Self {
231        self.excluded_agents = Some(agents);
232        self
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::runtime::resolve::normalize_definition_models_for_test;
240
241    #[test]
242    fn normalize_definition_trims_model_and_fallback_models() {
243        let definition =
244            AgentDefinition::new(" openai::gemini-2.5-flash ").with_fallback_models(vec![
245                " gpt-4o-mini ".to_string(),
246                "   ".to_string(),
247                " claude-3-7-sonnet ".to_string(),
248            ]);
249        let normalized = normalize_definition_models_for_test(definition);
250
251        assert_eq!(normalized.model, "openai::gemini-2.5-flash");
252        assert_eq!(
253            normalized.fallback_models,
254            vec!["gpt-4o-mini".to_string(), "claude-3-7-sonnet".to_string()]
255        );
256    }
257}