summaryrefslogtreecommitdiff
path: root/src/Agent.Core
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-12 05:44:54 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-12 05:44:54 +0900
commitb56614c814b904c224315a11f9686f0d47abd3d3 (patch)
treee6ae4feb852ea4492bf947eabba1e11ab157686a /src/Agent.Core
parent98b6a2aeedd591058c1d81e401d24f3ded162926 (diff)
feat: revise sandbox usability around semantic policy and LPAC process trees
Rewrite the specification and design vocabulary around User Sandbox Configuration, Environment Resolution, Executable Approval, Effective Sandbox Policy, and Windows Enforcement. Move the Internal Tool catalog to the design without treating its count as a requirement, and update README, implementation planning, traceability, audit history, prompts, and the Python skill. Introduce schema_version 1 profiles, workspaces, mounts, executable aliases, network modes, and resource presets. Add local override merging, resolution and approval stores with atomic user-only persistence, fail-closed fingerprints, localized diagnostics, and executable/sandbox management CLI commands. Change Windows execution to LPAC entry approval with inherited descendant confinement. Separate read and execute ACLs, preserve protected OS DACLs, apply Job process/memory/timeout limits to the tree, enforce WFP network modes, prevent credential environment inheritance, disable out-of-tree MSBuild reuse, and harden transactional ACL/recovery cleanup. Isolate planning prompts from execution instructions, reject split coding plans that cannot satisfy task-scoped evidence, preserve configured thinking, and prevent optional task_update failures from corrupting completion checks. Add and update configuration, resolution, approval, LPAC, descendant, readonly/development, Registry/COM, WFP, recovery, .NET/Python/PowerShell process-tree, and Operational E2E coverage. Publish reproducible bundles while preserving deployment-local configuration, prompts, skills, logs, state, approvals, and resolutions.
Diffstat (limited to 'src/Agent.Core')
-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();
}