adk_agent/workflow/
loop_agent.rs1#[cfg(feature = "skills")]
2use crate::skill_shim::load_skill_index;
3use crate::skill_shim::{SelectionPolicy, SkillIndex};
4use adk_core::{
5 AfterAgentCallback, Agent, BeforeAgentCallback, CallbackContext, Content, Event, EventStream,
6 InvocationContext, ReadonlyContext, Result, Session, State,
7};
8use async_stream::stream;
9use async_trait::async_trait;
10use std::collections::HashMap;
11use std::sync::{Arc, RwLock};
12
13pub const DEFAULT_LOOP_MAX_ITERATIONS: u32 = 1000;
16
17pub struct LoopAgent {
19 name: String,
20 description: String,
21 sub_agents: Vec<Arc<dyn Agent>>,
22 max_iterations: u32,
23 skills_index: Option<Arc<SkillIndex>>,
24 skill_policy: SelectionPolicy,
25 max_skill_chars: usize,
26 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
27 after_callbacks: Arc<Vec<AfterAgentCallback>>,
28}
29
30impl LoopAgent {
31 pub fn new(name: impl Into<String>, sub_agents: Vec<Arc<dyn Agent>>) -> Self {
33 Self {
34 name: name.into(),
35 description: String::new(),
36 sub_agents,
37 max_iterations: DEFAULT_LOOP_MAX_ITERATIONS,
38 skills_index: None,
39 skill_policy: SelectionPolicy::default(),
40 max_skill_chars: 2000,
41 before_callbacks: Arc::new(Vec::new()),
42 after_callbacks: Arc::new(Vec::new()),
43 }
44 }
45
46 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
48 self.description = desc.into();
49 self
50 }
51
52 pub fn with_max_iterations(mut self, max: u32) -> Self {
66 self.max_iterations = max;
67 self
68 }
69
70 #[cfg(feature = "skills")]
72 pub fn with_skills(mut self, index: SkillIndex) -> Self {
73 self.skills_index = Some(Arc::new(index));
74 self
75 }
76
77 #[cfg(feature = "skills")]
79 pub fn with_auto_skills(self) -> Result<Self> {
80 self.with_skills_from_root(".")
81 }
82
83 #[cfg(feature = "skills")]
85 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
86 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
87 self.skills_index = Some(Arc::new(index));
88 Ok(self)
89 }
90
91 #[cfg(feature = "skills")]
93 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
94 self.skill_policy = policy;
95 self
96 }
97
98 #[cfg(feature = "skills")]
100 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
101 self.max_skill_chars = max_chars;
102 self
103 }
104
105 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
107 if let Some(callbacks) = Arc::get_mut(&mut self.before_callbacks) {
108 callbacks.push(callback);
109 }
110 self
111 }
112
113 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
115 if let Some(callbacks) = Arc::get_mut(&mut self.after_callbacks) {
116 callbacks.push(callback);
117 }
118 self
119 }
120}
121
122struct HistoryTrackingSession {
123 parent_ctx: Arc<dyn InvocationContext>,
124 history: Arc<RwLock<Vec<Content>>>,
125 streamed_id: RwLock<Option<String>>,
127 state: StateTrackingState,
128}
129
130struct StateTrackingState {
131 values: RwLock<HashMap<String, serde_json::Value>>,
132}
133
134impl StateTrackingState {
135 fn new(parent_ctx: &Arc<dyn InvocationContext>) -> Self {
136 Self { values: RwLock::new(parent_ctx.session().state().all()) }
137 }
138
139 fn apply_delta(&self, delta: &HashMap<String, serde_json::Value>) {
140 if delta.is_empty() {
141 return;
142 }
143
144 let mut values = self.values.write().unwrap_or_else(|e| e.into_inner());
145 for (key, value) in delta {
146 values.insert(key.clone(), value.clone());
147 }
148 }
149}
150
151impl State for StateTrackingState {
152 fn get(&self, key: &str) -> Option<serde_json::Value> {
153 self.values.read().unwrap_or_else(|e| e.into_inner()).get(key).cloned()
154 }
155
156 fn set(&mut self, key: String, value: serde_json::Value) {
157 if let Err(msg) = adk_core::validate_state_key(&key) {
158 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
159 return;
160 }
161 self.values.write().unwrap_or_else(|e| e.into_inner()).insert(key, value);
162 }
163
164 fn all(&self) -> HashMap<String, serde_json::Value> {
165 self.values.read().unwrap_or_else(|e| e.into_inner()).clone()
166 }
167}
168
169impl HistoryTrackingSession {
170 fn new(parent_ctx: Arc<dyn InvocationContext>) -> Self {
171 Self {
172 history: Arc::new(RwLock::new(parent_ctx.session().conversation_history())),
173 streamed_id: RwLock::new(None),
174 state: StateTrackingState::new(&parent_ctx),
175 parent_ctx,
176 }
177 }
178
179 fn apply_event(&self, event: &Event) {
180 if let Some(content) = &event.llm_response.content {
181 let mut history = self.history.write().unwrap_or_else(|e| e.into_inner());
186 let mut streamed_id = self.streamed_id.write().unwrap_or_else(|e| e.into_inner());
187 let continues_stream = streamed_id.as_deref() == Some(event.id.as_str());
188
189 if event.llm_response.partial {
190 if continues_stream
193 && let Some(last) = history.last_mut()
194 && last.role == content.role
195 {
196 for part in &content.parts {
197 if let adk_core::Part::Text { text } = part {
198 if let Some(adk_core::Part::Text { text: existing }) =
200 last.parts.last_mut()
201 {
202 existing.push_str(text);
203 } else {
204 last.parts.push(part.clone());
205 }
206 } else {
207 last.parts.push(part.clone());
208 }
209 }
210 return;
211 }
212 history.push(content.clone());
214 *streamed_id = Some(event.id.clone());
215 } else {
216 let content_complete = event
217 .llm_response
218 .provider_metadata
219 .as_ref()
220 .and_then(|metadata| metadata.get("content_complete"))
221 .and_then(serde_json::Value::as_bool)
222 == Some(true);
223 let replaced_stream = continues_stream && content_complete;
224 *streamed_id = None;
225 if replaced_stream && let Some(last) = history.last_mut() {
226 *last = content.clone();
228 } else if let Some(last) = history.last_mut() {
229 if last.role == content.role && !content.parts.is_empty() {
236 for part in &content.parts {
238 if let adk_core::Part::Text { text } = part {
239 if let Some(adk_core::Part::Text { text: existing }) =
240 last.parts.last_mut()
241 {
242 existing.push_str(text);
243 } else {
244 last.parts.push(part.clone());
245 }
246 } else {
247 last.parts.push(part.clone());
248 }
249 }
250 } else if !content.parts.is_empty() {
251 history.push(content.clone());
252 }
253 } else {
254 history.push(content.clone());
255 }
256 }
257 }
258 self.state.apply_delta(&event.actions.state_delta);
259 }
260}
261
262impl Session for HistoryTrackingSession {
263 fn id(&self) -> &str {
264 self.parent_ctx.session().id()
265 }
266
267 fn app_name(&self) -> &str {
268 self.parent_ctx.session().app_name()
269 }
270
271 fn user_id(&self) -> &str {
272 self.parent_ctx.session().user_id()
273 }
274
275 fn state(&self) -> &dyn State {
276 &self.state
277 }
278
279 fn conversation_history(&self) -> Vec<Content> {
280 self.history.read().unwrap_or_else(|e| e.into_inner()).clone()
281 }
282
283 fn conversation_history_for_agent(&self, _agent_name: &str) -> Vec<Content> {
284 self.conversation_history()
285 }
286
287 fn append_to_history(&self, content: Content) {
288 self.history.write().unwrap_or_else(|e| e.into_inner()).push(content);
289 }
290}
291
292#[cfg(test)]
294pub(crate) fn history_tracking_context_for_test(
295 parent: Arc<dyn InvocationContext>,
296) -> Arc<dyn InvocationContext> {
297 Arc::new(HistoryTrackingContext::new(parent))
298}
299
300struct HistoryTrackingContext {
301 parent_ctx: Arc<dyn InvocationContext>,
302 session: HistoryTrackingSession,
303}
304
305impl HistoryTrackingContext {
306 fn new(parent_ctx: Arc<dyn InvocationContext>) -> Self {
307 let session = HistoryTrackingSession::new(parent_ctx.clone());
308 Self { parent_ctx, session }
309 }
310
311 fn apply_event(&self, event: &Event) {
312 self.session.apply_event(event);
313 }
314}
315
316#[async_trait]
317impl adk_core::ReadonlyContext for HistoryTrackingContext {
318 fn invocation_id(&self) -> &str {
319 self.parent_ctx.invocation_id()
320 }
321
322 fn agent_name(&self) -> &str {
323 self.parent_ctx.agent_name()
324 }
325
326 fn user_id(&self) -> &str {
327 self.parent_ctx.user_id()
328 }
329
330 fn app_name(&self) -> &str {
331 self.parent_ctx.app_name()
332 }
333
334 fn session_id(&self) -> &str {
335 self.parent_ctx.session_id()
336 }
337
338 fn branch(&self) -> &str {
339 self.parent_ctx.branch()
340 }
341
342 fn user_content(&self) -> &Content {
343 self.parent_ctx.user_content()
344 }
345}
346
347#[async_trait]
348impl CallbackContext for HistoryTrackingContext {
349 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
350 self.parent_ctx.artifacts()
351 }
352
353 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
354 self.parent_ctx.shared_state()
355 }
356}
357
358#[async_trait]
359impl InvocationContext for HistoryTrackingContext {
360 fn agent(&self) -> Arc<dyn Agent> {
361 self.parent_ctx.agent()
362 }
363
364 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
365 self.parent_ctx.memory()
366 }
367
368 fn session(&self) -> &dyn Session {
369 &self.session
370 }
371
372 fn run_config(&self) -> &adk_core::RunConfig {
373 self.parent_ctx.run_config()
374 }
375
376 fn end_invocation(&self) {
377 self.parent_ctx.end_invocation();
378 }
379
380 fn ended(&self) -> bool {
381 self.parent_ctx.ended()
382 }
383
384 fn is_cancelled(&self) -> bool {
385 self.parent_ctx.is_cancelled()
386 }
387
388 fn user_scopes(&self) -> Vec<String> {
389 self.parent_ctx.user_scopes()
390 }
391
392 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
393 self.parent_ctx.request_metadata()
394 }
395
396 fn authoritative_transfer_targets(&self) -> bool {
397 self.parent_ctx.authoritative_transfer_targets()
398 }
399 fn delegation_depth(&self) -> u32 {
400 self.parent_ctx.delegation_depth()
401 }
402 fn max_delegation_depth(&self) -> Option<u32> {
403 self.parent_ctx.max_delegation_depth()
404 }
405
406 fn orchestration_root_invocation_id(&self) -> &str {
407 self.parent_ctx.orchestration_root_invocation_id()
408 }
409
410 fn orchestration_edge_id(&self) -> Option<&str> {
411 self.parent_ctx.orchestration_edge_id()
412 }
413
414 fn requires_tool_confirmation(&self, tool_name: &str) -> bool {
415 self.parent_ctx.requires_tool_confirmation(tool_name)
416 }
417
418 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
419 self.parent_ctx.get_secret(name).await
420 }
421
422 async fn get_secret_for(
423 &self,
424 request: &adk_core::SecretRequest,
425 ) -> adk_core::Result<Option<String>> {
426 self.parent_ctx.get_secret_for(request).await
427 }
428}
429
430#[async_trait]
431impl Agent for LoopAgent {
432 fn name(&self) -> &str {
433 &self.name
434 }
435
436 fn description(&self) -> &str {
437 &self.description
438 }
439
440 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
441 &self.sub_agents
442 }
443
444 fn supports_agent_transfer(&self) -> bool {
445 false
449 }
450
451 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
452 let sub_agents = self.sub_agents.clone();
453 let max_iterations = self.max_iterations;
454 let before_callbacks = self.before_callbacks.clone();
455 let after_callbacks = self.after_callbacks.clone();
456 let agent_name = self.name.clone();
457 let run_ctx = super::skill_context::with_skill_injected_context(
458 ctx,
459 self.skills_index.as_ref(),
460 &self.skill_policy,
461 self.max_skill_chars,
462 );
463 let run_ctx = Arc::new(HistoryTrackingContext::new(run_ctx));
464
465 let s = stream! {
466 use futures::StreamExt;
467
468 for callback in before_callbacks.as_ref() {
470 match callback(run_ctx.clone() as Arc<dyn CallbackContext>).await {
471 Ok(Some(content)) => {
472 let mut early_event = Event::new(run_ctx.invocation_id());
473 early_event.author = agent_name.clone();
474 early_event.llm_response.content = Some(content);
475 yield Ok(early_event);
476
477 for after_cb in after_callbacks.as_ref() {
478 match after_cb(run_ctx.clone() as Arc<dyn CallbackContext>).await {
479 Ok(Some(after_content)) => {
480 let mut after_event = Event::new(run_ctx.invocation_id());
481 after_event.author = agent_name.clone();
482 after_event.llm_response.content = Some(after_content);
483 yield Ok(after_event);
484 return;
485 }
486 Ok(None) => continue,
487 Err(e) => { yield Err(e); return; }
488 }
489 }
490 return;
491 }
492 Ok(None) => continue,
493 Err(e) => { yield Err(e); return; }
494 }
495 }
496
497 for _ in 0..max_iterations {
500 let mut should_exit = false;
501
502 for agent in &sub_agents {
503 let mut stream = agent.run(run_ctx.clone() as Arc<dyn InvocationContext>).await?;
504
505 while let Some(result) = stream.next().await {
506 match result {
507 Ok(event) => {
508 run_ctx.apply_event(&event);
509 if event.actions.escalate {
510 should_exit = true;
511 }
512 yield Ok(event);
513 }
514 Err(e) => {
515 yield Err(e);
516 return;
517 }
518 }
519 }
520
521 if should_exit {
522 break;
523 }
524 }
525
526 if should_exit {
527 break;
528 }
529 }
530
531 for callback in after_callbacks.as_ref() {
533 match callback(run_ctx.clone() as Arc<dyn CallbackContext>).await {
534 Ok(Some(content)) => {
535 let mut after_event = Event::new(run_ctx.invocation_id());
536 after_event.author = agent_name.clone();
537 after_event.llm_response.content = Some(content);
538 yield Ok(after_event);
539 break;
540 }
541 Ok(None) => continue,
542 Err(e) => { yield Err(e); return; }
543 }
544 }
545 };
546
547 Ok(Box::pin(s))
548 }
549}