vtcode_core/core/agent/runner/
retry.rs1use super::AgentRunner;
2use crate::core::agent::task::{ContextItem, Task, TaskResults};
3use crate::error::{ErrorCode, Result as VtCodeResult, VtCodeError};
4use crate::retry::{RetryEvent, RetryPolicy, run_with_retry};
5use crate::utils::colors::style;
6use tracing::{info, warn};
7
8struct AgentRetryContext<'a> {
12 runner: &'a mut AgentRunner,
13 metrics: std::sync::Arc<crate::metrics::MetricsCollector>,
14 policy_max_attempts: u32,
15 task_id: String,
16}
17
18impl AgentRunner {
19 pub async fn execute_task_with_retry(
25 &mut self,
26 task: &Task,
27 contexts: &[ContextItem],
28 max_retries: u32,
29 ) -> VtCodeResult<TaskResults> {
30 use std::time::Duration;
31
32 let policy = RetryPolicy::from_retries(max_retries, Duration::from_secs(2), Duration::from_secs(30), 2.0);
33 let metrics = self.tool_registry.metrics_collector();
34 let task_id = task.id.clone();
35 let mut ctx = AgentRetryContext {
36 runner: self,
37 metrics,
38 policy_max_attempts: policy.max_attempts,
39 task_id: task_id.clone(),
40 };
41
42 run_with_retry(
43 &policy,
44 &mut ctx,
45 |ctx, event| match event {
46 RetryEvent::AttemptStart { attempt, max_attempts } => {
47 info!(
48 attempt = attempt + 1,
49 max_attempts,
50 task_id = %ctx.task_id,
51 "agent task attempt starting"
52 );
53 }
54 RetryEvent::Success { attempt } if attempt > 0 => {
55 ctx.metrics.record_retry_success();
56 ctx.runner.runner_println(format_args!(
57 "{} Task succeeded after {} attempt(s)",
58 style("[✓]").green().bold(),
59 attempt + 1
60 ));
61 info!(
62 attempt = attempt + 1,
63 task_id = %ctx.task_id,
64 "agent task succeeded after retry"
65 );
66 }
67 RetryEvent::Success { .. } => {}
68 RetryEvent::GiveUp { attempt, error, decision, category_was_retryable } => {
69 if category_was_retryable && attempt + 1 == ctx.policy_max_attempts {
70 ctx.metrics.record_retry_exhausted();
71 }
72 warn!(
73 attempt = attempt + 1,
74 max_attempts = ctx.policy_max_attempts,
75 task_id = %ctx.task_id,
76 error = %error,
77 category = ?decision.category,
78 "agent task attempt failed (non-retryable)"
79 );
80 }
81 RetryEvent::Backoff { attempt, error, decision, delay, .. } => {
82 warn!(
83 attempt = attempt + 1,
84 max_attempts = ctx.policy_max_attempts,
85 task_id = %ctx.task_id,
86 error = %error,
87 category = ?decision.category,
88 "agent task attempt failed"
89 );
90 ctx.metrics.record_retry_attempt();
91 ctx.runner.runner_println(format_args!(
92 "{} Task failed (attempt {}/{}), retrying in {}s...",
93 style("[Warning]").red().bold(),
94 attempt + 1,
95 ctx.policy_max_attempts,
96 delay.as_secs()
97 ));
98 info!(
99 delay_ms = delay.as_millis() as u64,
100 next_attempt = attempt + 2,
101 task_id = %ctx.task_id,
102 category = ?decision.category,
103 "backing off before retry"
104 );
105 }
106 RetryEvent::Exhausted { .. } => {
107 warn!(
108 task_id = %ctx.task_id,
109 attempts = ctx.policy_max_attempts,
110 "agent task failed after all retries"
111 );
112 ctx.runner.runner_println(format_args!(
113 "{} Task failed after {} attempts",
114 style("[Error]").red().bold(),
115 ctx.policy_max_attempts
116 ));
117 }
118 },
119 |ctx| {
120 let task = task.clone();
121 let contexts = contexts.to_vec();
122 let runner = &mut *ctx.runner;
123 Box::pin(async move { Box::pin(runner.execute_task(&task, &contexts)).await })
124 },
125 move |_policy| {
126 VtCodeError::execution(
127 ErrorCode::ToolExecutionFailed,
128 format!("agent task '{task_id}' exhausted the retry loop without an error payload"),
129 )
130 },
131 )
132 .await
133 }
134}