fix: drive tool rounds iteratively instead of recursively to prevent stack overflow on long agentic turns to fix latent stack overflow recursion bug

This commit is contained in:
Dark-Alex-17 committed 2026-09-03 16:09:41 -06:00
1 parent cb5b46800f
commit 206f36d826
2 files changed
+31 -32

No files matched your search

+13 -13
View File
@@ -556,7 +556,6 @@ async fn run(
} }
} }
#[async_recursion::async_recursion]
async fn start_directive( async fn start_directive(
ctx: &mut RequestContext, ctx: &mut RequestContext,
input: Input, input: Input,
@@ -576,8 +575,10 @@ async fn start_directive(
return Ok(()); return Ok(());
} }
let client = input.create_client()?;
let extract_code = !*IS_STDOUT_TERMINAL && code_mode; let extract_code = !*IS_STDOUT_TERMINAL && code_mode;
let mut input = input;
loop {
let client = input.create_client()?;
ctx.before_chat_completion(&input)?; ctx.before_chat_completion(&input)?;
let (output, tool_results) = if !input.stream() || extract_code { let (output, tool_results) = if !input.stream() || extract_code {
call_chat_completions( call_chat_completions(
@@ -590,23 +591,20 @@ async fn start_directive(
) )
.await? .await?
} else { } else {
call_chat_completions_streaming(&input, client.as_ref(), ctx, abort_signal.clone()).await? call_chat_completions_streaming(&input, client.as_ref(), ctx, abort_signal.clone())
.await?
}; };
ctx.after_chat_completion(app.as_ref(), &input, &output, &tool_results)?; ctx.after_chat_completion(app.as_ref(), &input, &output, &tool_results)?;
if !tool_results.is_empty() { if !tool_results.is_empty() {
start_directive( input = input.merge_tool_results(output, tool_results);
ctx, continue;
input.merge_tool_results(output, tool_results), }
code_mode,
abort_signal,
)
.await?;
} else {
match check_pending_tasks_guardrail(ctx) { match check_pending_tasks_guardrail(ctx) {
GuardrailAction::Inject(prompt) => { GuardrailAction::Inject(prompt) => {
let guardrail_input = Input::from_str(ctx, &prompt, None)?; input = Input::from_str(ctx, &prompt, None)?;
return start_directive(ctx, guardrail_input, code_mode, abort_signal).await; continue;
} }
GuardrailAction::ForceTerminate(ids) => { GuardrailAction::ForceTerminate(ids) => {
warn!( warn!(
@@ -617,6 +615,8 @@ async fn start_directive(
} }
GuardrailAction::NoAction => {} GuardrailAction::NoAction => {}
} }
break;
} }
ctx.exit_session()?; ctx.exit_session()?;
+18 -19
View File
@@ -1424,19 +1424,22 @@ pub async fn run_repl_command(
Ok(false) Ok(false)
} }
#[async_recursion::async_recursion]
async fn ask( async fn ask(
ctx: &mut RequestContext, ctx: &mut RequestContext,
abort_signal: AbortSignal, abort_signal: AbortSignal,
mut input: Input, input: Input,
with_embeddings: bool, with_embeddings: bool,
) -> Result<()> { ) -> Result<()> {
let mut input = input;
let mut with_embeddings = with_embeddings;
loop {
if input.is_empty() { if input.is_empty() {
return Ok(()); return Ok(());
} }
ctx.session_abort = Some(abort_signal.clone()); ctx.session_abort = Some(abort_signal.clone());
if with_embeddings { if with_embeddings {
input.use_embeddings(abort_signal.clone()).await?; input.use_embeddings(abort_signal.clone()).await?;
with_embeddings = false;
} }
while ctx.is_compressing_session() { while ctx.is_compressing_session() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(100)).await;
@@ -1480,18 +1483,14 @@ async fn ask(
}; };
ctx.after_chat_completion(app.as_ref(), &input, &output, &tool_results)?; ctx.after_chat_completion(app.as_ref(), &input, &output, &tool_results)?;
if !tool_results.is_empty() { if !tool_results.is_empty() {
ask( input = input.merge_tool_results(output, tool_results);
ctx, continue;
abort_signal, }
input.merge_tool_results(output, tool_results),
false,
)
.await
} else {
match check_pending_tasks_guardrail(ctx) { match check_pending_tasks_guardrail(ctx) {
GuardrailAction::Inject(prompt) => { GuardrailAction::Inject(prompt) => {
let guardrail_input = Input::from_str(ctx, &prompt, None)?; input = Input::from_str(ctx, &prompt, None)?;
return ask(ctx, abort_signal, guardrail_input, false).await; continue;
} }
GuardrailAction::ForceTerminate(ids) => { GuardrailAction::ForceTerminate(ids) => {
warn!( warn!(
@@ -1533,9 +1532,10 @@ async fn ask(
format!("{prompt}\n\n{todo_state}") format!("{prompt}\n\n{todo_state}")
}; };
let continuation_input = Input::from_str(ctx, &full_prompt, None)?; input = Input::from_str(ctx, &full_prompt, None)?;
ask(ctx, abort_signal, continuation_input, false).await continue;
} else { }
reset_continuation(ctx); reset_continuation(ctx);
if let Some(reason) = ctx.auto_continue_paused.clone() if let Some(reason) = ctx.auto_continue_paused.clone()
&& ctx.todo_list.has_incomplete() && ctx.todo_list.has_incomplete()
@@ -1623,13 +1623,12 @@ async fn ask(
format!("{prompt}\n\n{todo_state}") format!("{prompt}\n\n{todo_state}")
}; };
let continuation_input = Input::from_str(ctx, &full_prompt, None)?; input = Input::from_str(ctx, &full_prompt, None)?;
return ask(ctx, abort_signal, continuation_input, false).await; continue;
} }
} }
Ok(()) return Ok(());
}
} }
} }