1use crate::config::SafetyConfig;
8use crate::memory::MemorySystem;
9use crate::safety::SafetyGuardian;
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::path::PathBuf;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct ResourceLimits {
18 pub max_memory_mb: Option<u64>,
19 pub max_tokens_per_turn: Option<u64>,
20 pub max_tool_calls: Option<u32>,
21 pub max_runtime_secs: Option<u64>,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum AgentStatus {
27 Idle,
28 Running,
29 Waiting,
30 Terminated,
31}
32
33pub struct AgentContext {
35 pub agent_id: Uuid,
37 pub name: String,
39 pub memory: MemorySystem,
41 pub safety: SafetyGuardian,
43 pub parent_id: Option<Uuid>,
45 pub workspace_dir: Option<PathBuf>,
47 pub llm_override: Option<String>,
49 pub resource_limits: ResourceLimits,
51 pub created_at: DateTime<Utc>,
53 pub status: AgentStatus,
55}
56
57impl AgentContext {
58 pub fn new(name: impl Into<String>, window_size: usize, safety_config: SafetyConfig) -> Self {
60 Self {
61 agent_id: Uuid::new_v4(),
62 name: name.into(),
63 memory: MemorySystem::new(window_size),
64 safety: SafetyGuardian::new(safety_config),
65 parent_id: None,
66 workspace_dir: None,
67 llm_override: None,
68 resource_limits: ResourceLimits::default(),
69 created_at: Utc::now(),
70 status: AgentStatus::Idle,
71 }
72 }
73
74 pub fn new_child(
76 name: impl Into<String>,
77 parent_id: Uuid,
78 window_size: usize,
79 safety_config: SafetyConfig,
80 ) -> Self {
81 Self {
82 agent_id: Uuid::new_v4(),
83 name: name.into(),
84 memory: MemorySystem::new(window_size),
85 safety: SafetyGuardian::new(safety_config),
86 parent_id: Some(parent_id),
87 workspace_dir: None,
88 llm_override: None,
89 resource_limits: ResourceLimits::default(),
90 created_at: Utc::now(),
91 status: AgentStatus::Idle,
92 }
93 }
94
95 pub fn is_child(&self) -> bool {
97 self.parent_id.is_some()
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::config::SafetyConfig;
105
106 #[test]
107 fn test_agent_context_new() {
108 let ctx = AgentContext::new("test-agent", 10, SafetyConfig::default());
109 assert_eq!(ctx.name, "test-agent");
110 assert!(!ctx.is_child());
111 assert!(ctx.parent_id.is_none());
112 }
113
114 #[test]
115 fn test_agent_context_child() {
116 let parent_id = Uuid::new_v4();
117 let ctx = AgentContext::new_child("child-agent", parent_id, 10, SafetyConfig::default());
118 assert!(ctx.is_child());
119 assert_eq!(ctx.parent_id, Some(parent_id));
120 }
121
122 #[test]
123 fn test_agent_context_isolation() {
124 let mut ctx1 = AgentContext::new("agent-1", 10, SafetyConfig::default());
126 let mut ctx2 = AgentContext::new("agent-2", 10, SafetyConfig::default());
127
128 ctx1.memory.working.set_goal("Goal A");
129 ctx2.memory.working.set_goal("Goal B");
130
131 assert_eq!(ctx1.memory.working.current_goal.as_deref(), Some("Goal A"));
132 assert_eq!(ctx2.memory.working.current_goal.as_deref(), Some("Goal B"));
133 assert_ne!(ctx1.agent_id, ctx2.agent_id);
134 }
135
136 #[test]
137 fn test_agent_context_unique_ids() {
138 let a = AgentContext::new("a", 5, SafetyConfig::default());
139 let b = AgentContext::new("b", 5, SafetyConfig::default());
140 assert_ne!(a.agent_id, b.agent_id);
141 }
142
143 #[test]
144 fn test_agent_context_with_workspace() {
145 let mut ctx = AgentContext::new("test", 10, SafetyConfig::default());
146 assert!(ctx.workspace_dir.is_none());
147 ctx.workspace_dir = Some(PathBuf::from("/tmp/agent-workspace"));
148 assert_eq!(
149 ctx.workspace_dir.as_deref(),
150 Some(std::path::Path::new("/tmp/agent-workspace"))
151 );
152 }
153
154 #[test]
155 fn test_agent_context_with_llm_override() {
156 let mut ctx = AgentContext::new("test", 10, SafetyConfig::default());
157 assert!(ctx.llm_override.is_none());
158 ctx.llm_override = Some("claude-3-opus".into());
159 assert_eq!(ctx.llm_override.as_deref(), Some("claude-3-opus"));
160 }
161
162 #[test]
163 fn test_resource_limits_default_unbounded() {
164 let limits = ResourceLimits::default();
165 assert!(limits.max_memory_mb.is_none());
166 assert!(limits.max_tokens_per_turn.is_none());
167 assert!(limits.max_tool_calls.is_none());
168 assert!(limits.max_runtime_secs.is_none());
169 }
170
171 #[test]
172 fn test_resource_limits_custom() {
173 let limits = ResourceLimits {
174 max_memory_mb: Some(512),
175 max_tokens_per_turn: Some(4096),
176 max_tool_calls: Some(50),
177 max_runtime_secs: Some(300),
178 };
179 assert_eq!(limits.max_memory_mb, Some(512));
180 assert_eq!(limits.max_tool_calls, Some(50));
181 }
182
183 #[test]
184 fn test_agent_status_transitions() {
185 let mut ctx = AgentContext::new("test", 10, SafetyConfig::default());
186 assert_eq!(ctx.status, AgentStatus::Idle);
187 ctx.status = AgentStatus::Running;
188 assert_eq!(ctx.status, AgentStatus::Running);
189 ctx.status = AgentStatus::Waiting;
190 assert_eq!(ctx.status, AgentStatus::Waiting);
191 ctx.status = AgentStatus::Terminated;
192 assert_eq!(ctx.status, AgentStatus::Terminated);
193 }
194
195 #[test]
196 fn test_agent_context_created_at() {
197 let before = chrono::Utc::now();
198 let ctx = AgentContext::new("test", 10, SafetyConfig::default());
199 let after = chrono::Utc::now();
200 assert!(ctx.created_at >= before);
201 assert!(ctx.created_at <= after);
202 }
203
204 #[test]
205 fn test_new_child_inherits_defaults() {
206 let parent_id = Uuid::new_v4();
207 let ctx = AgentContext::new_child("child", parent_id, 10, SafetyConfig::default());
208 assert!(ctx.workspace_dir.is_none());
209 assert!(ctx.llm_override.is_none());
210 assert!(ctx.resource_limits.max_memory_mb.is_none());
211 assert_eq!(ctx.status, AgentStatus::Idle);
212 }
213
214 #[test]
215 fn test_resource_limits_none_means_unlimited() {
216 let limits = ResourceLimits {
217 max_memory_mb: None,
218 max_tokens_per_turn: None,
219 max_tool_calls: None,
220 max_runtime_secs: None,
221 };
222 assert!(limits.max_memory_mb.is_none());
224 assert!(limits.max_tokens_per_turn.is_none());
225 assert!(limits.max_tool_calls.is_none());
226 assert!(limits.max_runtime_secs.is_none());
227 }
228}