Skip to main content

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 a loop-facing base agent.
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    /// Permission rules (RLS-style pattern strings).
60    /// Each entry is `(behavior, pattern)` where behavior is `"allow"` / `"deny"` / `"ask"`.
61    pub permission_rules: Vec<(String, String)>,
62    /// Declarative stop condition specs, resolved at runtime.
63    pub stop_condition_specs: Vec<StopConditionSpec>,
64    /// Stop condition references resolved from AgentOS StopPolicyRegistry.
65    pub stop_condition_ids: Vec<String>,
66}
67
68impl Default for AgentDefinition {
69    fn default() -> Self {
70        Self {
71            id: "default".to_string(),
72            name: None,
73            description: None,
74            model: "gpt-4o-mini".to_string(),
75            system_prompt: String::new(),
76            max_rounds: 10,
77            tool_execution_mode: ToolExecutionMode::ParallelStreaming,
78            chat_options: Some(
79                ChatOptions::default()
80                    .with_capture_usage(true)
81                    .with_capture_reasoning_content(true)
82                    .with_capture_tool_calls(true),
83            ),
84            fallback_models: Vec::new(),
85            llm_retry_policy: LlmRetryPolicy::default(),
86            behavior_ids: Vec::new(),
87            allowed_tools: None,
88            excluded_tools: None,
89            allowed_skills: None,
90            excluded_skills: None,
91            allowed_agents: None,
92            excluded_agents: None,
93            permission_rules: Vec::new(),
94            stop_condition_specs: Vec::new(),
95            stop_condition_ids: Vec::new(),
96        }
97    }
98}
99
100impl std::fmt::Debug for AgentDefinition {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.debug_struct("AgentDefinition")
103            .field("id", &self.id)
104            .field("name", &self.name)
105            .field("description", &self.description)
106            .field("model", &self.model)
107            .field(
108                "system_prompt",
109                &format!("[{} chars]", self.system_prompt.len()),
110            )
111            .field("max_rounds", &self.max_rounds)
112            .field("tool_execution_mode", &self.tool_execution_mode)
113            .field("chat_options", &self.chat_options)
114            .field("fallback_models", &self.fallback_models)
115            .field("llm_retry_policy", &self.llm_retry_policy)
116            .field("behavior_ids", &self.behavior_ids)
117            .field("allowed_tools", &self.allowed_tools)
118            .field("excluded_tools", &self.excluded_tools)
119            .field("allowed_skills", &self.allowed_skills)
120            .field("excluded_skills", &self.excluded_skills)
121            .field("allowed_agents", &self.allowed_agents)
122            .field("excluded_agents", &self.excluded_agents)
123            .field("stop_condition_specs", &self.stop_condition_specs)
124            .field("stop_condition_ids", &self.stop_condition_ids)
125            .finish()
126    }
127}
128
129impl AgentDefinition {
130    tirea_contract::impl_shared_agent_builder_methods!();
131
132    #[must_use]
133    pub fn with_name(mut self, name: impl Into<String>) -> Self {
134        self.name = Some(name.into());
135        self
136    }
137
138    #[must_use]
139    pub fn with_description(mut self, description: impl Into<String>) -> Self {
140        self.description = Some(description.into());
141        self
142    }
143
144    #[must_use]
145    pub fn display_name(&self) -> &str {
146        self.name.as_deref().unwrap_or(&self.id)
147    }
148
149    #[must_use]
150    pub fn display_description(&self) -> &str {
151        self.description.as_deref().unwrap_or("")
152    }
153
154    #[must_use]
155    pub fn descriptor(&self) -> AgentDescriptor {
156        AgentDescriptor::new(self.id.clone())
157            .with_name(self.display_name())
158            .with_description(self.display_description())
159    }
160
161    #[must_use]
162    pub fn with_stop_condition_spec(mut self, spec: StopConditionSpec) -> Self {
163        self.stop_condition_specs.push(spec);
164        self
165    }
166
167    #[must_use]
168    pub fn with_stop_condition_specs(mut self, specs: Vec<StopConditionSpec>) -> Self {
169        self.stop_condition_specs = specs;
170        self
171    }
172
173    #[must_use]
174    pub fn with_tool_execution_mode(mut self, mode: ToolExecutionMode) -> Self {
175        self.tool_execution_mode = mode;
176        self
177    }
178
179    #[must_use]
180    pub fn with_behavior_ids(mut self, behavior_ids: Vec<String>) -> Self {
181        self.behavior_ids = behavior_ids;
182        self
183    }
184
185    #[must_use]
186    pub fn with_behavior_id(mut self, behavior_id: impl Into<String>) -> Self {
187        self.behavior_ids.push(behavior_id.into());
188        self
189    }
190
191    #[must_use]
192    pub fn with_stop_condition_id(mut self, id: impl Into<String>) -> Self {
193        self.stop_condition_ids.push(id.into());
194        self
195    }
196
197    #[must_use]
198    pub fn with_stop_condition_ids(mut self, ids: Vec<String>) -> Self {
199        self.stop_condition_ids = ids;
200        self
201    }
202
203    #[must_use]
204    pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
205        self.allowed_tools = Some(tools);
206        self
207    }
208
209    #[must_use]
210    pub fn with_excluded_tools(mut self, tools: Vec<String>) -> Self {
211        self.excluded_tools = Some(tools);
212        self
213    }
214
215    #[must_use]
216    pub fn with_allowed_skills(mut self, skills: Vec<String>) -> Self {
217        self.allowed_skills = Some(skills);
218        self
219    }
220
221    #[must_use]
222    pub fn with_excluded_skills(mut self, skills: Vec<String>) -> Self {
223        self.excluded_skills = Some(skills);
224        self
225    }
226
227    #[must_use]
228    pub fn with_allowed_agents(mut self, agents: Vec<String>) -> Self {
229        self.allowed_agents = Some(agents);
230        self
231    }
232
233    #[must_use]
234    pub fn with_excluded_agents(mut self, agents: Vec<String>) -> Self {
235        self.excluded_agents = Some(agents);
236        self
237    }
238
239    /// Add a permission rule (behavior + pattern string).
240    ///
241    /// ```ignore
242    /// AgentDefinition::new("claude-3-opus")
243    ///     .with_permission_rule("allow", "Bash(npm *)")
244    ///     .with_permission_rule("deny", "Bash(rm *)")
245    /// ```
246    pub fn with_permission_rule(
247        mut self,
248        behavior: impl Into<String>,
249        pattern: impl Into<String>,
250    ) -> Self {
251        self.permission_rules
252            .push((behavior.into(), pattern.into()));
253        self
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::runtime::wiring::resolve::normalize_definition_models_for_test;
261
262    #[test]
263    fn normalize_definition_trims_model_and_fallback_models() {
264        let definition =
265            AgentDefinition::new(" openai::gemini-2.5-flash ").with_fallback_models(vec![
266                " gpt-4o-mini ".to_string(),
267                "   ".to_string(),
268                " claude-3-7-sonnet ".to_string(),
269            ]);
270        let normalized = normalize_definition_models_for_test(definition);
271
272        assert_eq!(normalized.model, "openai::gemini-2.5-flash");
273        assert_eq!(
274            normalized.fallback_models,
275            vec!["gpt-4o-mini".to_string(), "claude-3-7-sonnet".to_string()]
276        );
277    }
278}