summaryrefslogtreecommitdiff
path: root/src/Agent.Core/Runtime
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Core/Runtime')
-rw-r--r--src/Agent.Core/Runtime/AgentRuntime.cs12
-rw-r--r--src/Agent.Core/Runtime/PlannerService.cs38
2 files changed, 41 insertions, 9 deletions
diff --git a/src/Agent.Core/Runtime/AgentRuntime.cs b/src/Agent.Core/Runtime/AgentRuntime.cs
index 5ca9d1c..676fe0d 100644
--- a/src/Agent.Core/Runtime/AgentRuntime.cs
+++ b/src/Agent.Core/Runtime/AgentRuntime.cs
@@ -303,7 +303,8 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
throw new PolicyDeniedException(toolResult.FailureFingerprint ?? $"policy:{call.Function.Name}:{toolResult.Output}", toolResult.Output,
toolResult.ErrorCode ?? "policy_denied", toolResult.ErrorCategory, toolResult.TargetPath,
toolResult.RequiredPermission, toolResult.Resolution);
- failureFingerprint = toolResult.FailureFingerprint ?? failureFingerprint;
+ if (!toolResult.Success && !isMetadataTool)
+ failureFingerprint = toolResult.FailureFingerprint ?? failureFingerprint;
messages.Add(new OllamaMessage("tool", toolResult.Output, ToolName: call.Function.Name));
}
bool verified = plannerTask.RequiredTools.All(successfulRequiredTools.Contains) && (!requiresExternalEffect || externalEffectEvidence);
@@ -527,12 +528,19 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
private string LocalizePolicyDenial(PolicyDeniedException error)
{
if (locale is null) return error.Message;
+ string resolutionKey = error.ErrorCategory switch
+ {
+ "filesystem" => "runtime.policy_resolution.filesystem",
+ "process" => "runtime.policy_resolution.process",
+ "network" => "runtime.policy_resolution.network",
+ _ => "runtime.policy_resolution.default"
+ };
return locale.Format("runtime.policy_denied", new Dictionary<string, object?>
{
["code"] = error.ErrorCode,
["path"] = error.TargetPath ?? "-",
["permission"] = error.RequiredPermission ?? "-",
- ["resolution"] = error.Resolution ?? "-"
+ ["resolution"] = locale.Get(resolutionKey)
});
}
diff --git a/src/Agent.Core/Runtime/PlannerService.cs b/src/Agent.Core/Runtime/PlannerService.cs
index 3591259..756cc41 100644
--- a/src/Agent.Core/Runtime/PlannerService.cs
+++ b/src/Agent.Core/Runtime/PlannerService.cs
@@ -31,12 +31,13 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
var messages = new List<OllamaMessage>
{
- new("system", FixedPrompt.Load()),
- new("system", _settings.UserSystemPrompt),
+ // Planning has a dedicated read-only contract. Execution prompts contain instructions
+ // to inspect files and call tools, so mixing them here creates contradictory system
+ // instructions and causes models to execute narratively instead of returning a graph.
+ new("system", BuildPlanningInstruction()),
new("system", $"Respond in locale: {_settings.Locale}."),
new("system", ComposeSkillCatalog(_settings.Skills)),
new("system", _settings.SandboxCapabilitySummary),
- new("system", BuildPlanningInstruction()),
new("user", request, images)
};
@@ -58,11 +59,9 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
var messages = new List<OllamaMessage>
{
- new("system", FixedPrompt.Load()),
- new("system", _settings.UserSystemPrompt),
+ new("system", BuildReplanInstruction(existing, replanReason)),
new("system", $"Respond in locale: {_settings.Locale}."),
new("system", _settings.SandboxCapabilitySummary),
- new("system", BuildReplanInstruction(existing, replanReason)),
new("user", request)
};
@@ -74,6 +73,7 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
private TaskGraph BuildTaskGraph(PlanResult plan, string originalRequest)
{
+ ValidatePlanStructure(plan);
var graph = new TaskGraph(_settings.MaxTasks);
var idMap = new List<Guid>();
@@ -105,6 +105,7 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
private TaskGraph RebuildTaskGraph(TaskGraph existing, PlanResult plan, string originalRequest)
{
+ ValidatePlanStructure(plan);
PlannerTask[] completed = existing.Tasks.Where(static t => t.State == PlannerTaskState.Completed).ToArray();
var completedByDescription = completed.GroupBy(static t => t.Description, StringComparer.Ordinal)
.ToDictionary(static group => group.Key, static group => group.First(), StringComparer.Ordinal);
@@ -182,6 +183,29 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
return tools;
}
+ private static void ValidatePlanStructure(PlanResult plan)
+ {
+ for (int index = 0; index < plan.Tasks.Count; index++)
+ {
+ PlannedTask task = plan.Tasks[index];
+ if (!task.RequiredTools.Contains("file_edit", StringComparer.Ordinal) ||
+ !task.RequiredTools.Contains("process_run", StringComparer.Ordinal)) continue;
+
+ var pending = new Stack<int>(task.Dependencies);
+ var visited = new HashSet<int>();
+ while (pending.TryPop(out int dependency))
+ {
+ if (dependency < 0 || dependency >= index || !visited.Add(dependency)) continue;
+ PlannedTask ancestor = plan.Tasks[dependency];
+ if (ancestor.RequiredTools.Contains("file_edit", StringComparer.Ordinal))
+ throw new PlannerValidationException(
+ "A coding implementation-and-verification Task cannot depend on a separate file_edit Task. " +
+ "Combine the edits, optional corrections, and process verification in one Task requiring file_edit and process_run.");
+ foreach (int parent in ancestor.Dependencies) pending.Push(parent);
+ }
+ }
+ }
+
private static PlanResult? ParsePlan(string content)
{
try
@@ -266,7 +290,7 @@ public sealed class PlannerService(IOllamaClient ollama, AgentRuntimeSettings se
{
attemptMessages = messages.Concat([
new OllamaMessage("system",
- "The previous response was not a valid TaskGraph JSON object. Do not call or execute any Tool while planning. " +
+ $"The previous response was not a valid TaskGraph: {ex.Message} Do not call or execute any Tool while planning. " +
"Return only one JSON object that matches the supplied schema, beginning with {\"tasks\":[ and containing no Markdown or commentary.")
]).ToArray();
}