summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-11 13:51:59 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-11 13:51:59 +0900
commitb5ccecca01007f2a2459265df6d749fbfd31aa84 (patch)
treeffa633db5df1809b1c0adcfb35e66aa5b97f8c46
parent7aa4ff5d8403e4c1480faafce0af8d3549276c9f (diff)
Fix: AgentRuntime tool failure handling and PlannerService fallback graph persistence
AgentRuntime.ExecutePlannerTaskAsync: - Add PlannerValidationException and FileNotFoundException to the tool execution catch clause so that 'Completed tasks are immutable' and 'Approved path does not exist' errors are returned as tool results instead of propagating as unhandled task failures. - Treat task_update as a metadata tool: its failures no longer add to unresolvedToolFailures, so the LLM is not forced into an infinite retry loop when task_update receives invalid arguments. - Skip throwing InvalidOperationException for RetryAllowed=false failures originating from metadata tools (task_update), preventing unnecessary replan cycles caused by graph bookkeeping errors. - Improve the 'No tasks were executed' fallback message: when replan was attempted but no tasks could execute, report that the sandbox policy denied required operations. PlannerService.PlanAsync: - Persist fallback TaskGraph to session.TaskGraphs[taskId] in all three fallback paths (ChatStructuredAsync exception, empty/null plan, BuildTaskGraph exception) so that BeginTask's TryAdd does not create an empty graph that shadows the fallback.
-rw-r--r--src/Agent.Core/Runtime/AgentRuntime.cs13
-rw-r--r--src/Agent.Core/Runtime/PlannerService.cs14
2 files changed, 20 insertions, 7 deletions
diff --git a/src/Agent.Core/Runtime/AgentRuntime.cs b/src/Agent.Core/Runtime/AgentRuntime.cs
index aa81486..0b261da 100644
--- a/src/Agent.Core/Runtime/AgentRuntime.cs
+++ b/src/Agent.Core/Runtime/AgentRuntime.cs
@@ -110,9 +110,12 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
}
// All tasks completed — assemble final result.
+ // If replan was attempted but no tasks could execute, provide a meaningful message.
string finalContent = finalContents.Count > 0
? string.Join("\n\n", finalContents)
- : "No tasks were executed.";
+ : replanCount > 0
+ ? "The request could not be completed because the sandbox policy denied required operations."
+ : "No tasks were executed.";
string? cleanupFailure = await EndTaskSafelyAsync(taskId, true, CancellationToken.None).ConfigureAwait(false);
if (cleanupFailure is not null) throw new InvalidOperationException(cleanupFailure);
session.Append(new ConversationMessage("user", task.Request));
@@ -210,20 +213,22 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
{
toolResult = new(false, ex.Message, false, $"{call.Function.Name}:{ex.GetType().Name}:{ex.Message}", RetryAllowed: false);
}
- catch (Exception ex) when (ex is ToolValidationException or ArgumentException)
+ catch (Exception ex) when (ex is ToolValidationException or ArgumentException or PlannerValidationException or FileNotFoundException)
{
toolResult = new(false, ex.Message, false, $"{call.Function.Name}:{ex.GetType().Name}:{ex.Message}");
}
externalEffectEvidence |= toolResult.Success && toolResult.ExternalEffect;
+ // task_update is a metadata tool for graph bookkeeping; its failures should not block completion.
+ bool isMetadataTool = call.Function.Name == "task_update";
if (toolResult.Success) unresolvedToolFailures.Remove(call.Function.Name);
- else unresolvedToolFailures[call.Function.Name] = toolResult.FailureFingerprint ?? toolResult.Output;
+ else if (!isMetadataTool) unresolvedToolFailures[call.Function.Name] = toolResult.FailureFingerprint ?? toolResult.Output;
// Record ExecutionResult in session for context summary.
session.AppendExecutionResult(new ExecutionResult(taskId, call.Function.Name, toolResult.Success, toolResult.Output));
_observer.ToolResult(taskId, call.Function.Name, toolResult);
- if (!toolResult.Success && !toolResult.RetryAllowed)
+ if (!toolResult.Success && !toolResult.RetryAllowed && !isMetadataTool)
throw new InvalidOperationException($"Tool '{call.Function.Name}' was denied by sandbox policy and will not be retried: {toolResult.Output}");
failureFingerprint = toolResult.FailureFingerprint ?? failureFingerprint;
messages.Add(new OllamaMessage("tool", toolResult.Output, ToolName: call.Function.Name));
diff --git a/src/Agent.Core/Runtime/PlannerService.cs b/src/Agent.Core/Runtime/PlannerService.cs
index 110e476..bf896f0 100644
--- a/src/Agent.Core/Runtime/PlannerService.cs
+++ b/src/Agent.Core/Runtime/PlannerService.cs
@@ -48,12 +48,18 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
catch
{
// Fallback: single-task plan with conservative estimates.
- return CreateFallbackGraph(request);
+ TaskGraph fallback = CreateFallbackGraph(request);
+ session.TaskGraphs[taskId] = fallback;
+ return fallback;
}
PlanResult? plan = ParsePlan(result.Content);
if (plan is null || plan.Tasks.Count == 0)
- return CreateFallbackGraph(request);
+ {
+ TaskGraph fallback = CreateFallbackGraph(request);
+ session.TaskGraphs[taskId] = fallback;
+ return fallback;
+ }
TaskGraph graph;
try
@@ -62,7 +68,9 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
}
catch
{
- return CreateFallbackGraph(request);
+ TaskGraph fallback = CreateFallbackGraph(request);
+ session.TaskGraphs[taskId] = fallback;
+ return fallback;
}
session.TaskGraphs[taskId] = graph;
return graph;