summaryrefslogtreecommitdiff
path: root/src/Agent.Core/Runtime/AgentRuntime.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.Core/Runtime/AgentRuntime.cs')
-rw-r--r--src/Agent.Core/Runtime/AgentRuntime.cs67
1 files changed, 66 insertions, 1 deletions
diff --git a/src/Agent.Core/Runtime/AgentRuntime.cs b/src/Agent.Core/Runtime/AgentRuntime.cs
index 228ac41..f6bb483 100644
--- a/src/Agent.Core/Runtime/AgentRuntime.cs
+++ b/src/Agent.Core/Runtime/AgentRuntime.cs
@@ -1,3 +1,4 @@
+using System.Text.Json;
using Agent.Core.Ollama;
using Agent.Core.Prompts;
using Agent.Core.Sessions;
@@ -28,12 +29,16 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
await _tools.BeginTaskAsync(taskId, cancellationToken).ConfigureAwait(false);
if (task.Images.Count > 0 && await ollama.SupportsVisionAsync(cancellationToken).ConfigureAwait(false) is false)
throw new InvalidOperationException(settings.VisionUnsupportedMessage);
+
+ // Progressive Disclosure Stage 2: LLM Skill Selection.
+ IReadOnlyList<AgentSkill> selectedSkills = await SelectSkillsAsync(task.Request, cancellationToken).ConfigureAwait(false);
+
var messages = PromptComposer.Compose(new PromptContext(
FixedPrompt.Load(),
settings.UserSystemPrompt,
settings.Locale,
settings.Skills,
- [], // SelectedSkills: populated after LLM skill selection (Progressive Disclosure Stage 2→3).
+ selectedSkills, // Stage 3: selected skill instructions injected by PromptComposer.
settings.SandboxCapabilitySummary,
"Current task: " + task.Request,
string.Empty,
@@ -134,6 +139,66 @@ public sealed class AgentRuntime(TaskRegistry registry, IOllamaClient ollama, Ag
}
}
+ private async Task<IReadOnlyList<AgentSkill>> SelectSkillsAsync(string request, CancellationToken cancellationToken)
+ {
+ if (settings.Skills.Count == 0) return [];
+
+ // Build a minimal prompt with skill catalog for the LLM to choose from.
+ string catalog = string.Join("\n", settings.Skills.Select(static s => $"- {s.Name}: {s.Description}"));
+ var selectionMessages = new List<OllamaMessage>
+ {
+ new("system", $"Select skills relevant to the user request. Available skills:\n{catalog}\n\nReturn a JSON object with a 'selected_skills' array containing skill names. Select only relevant skills. If none are relevant, return an empty array."),
+ new("user", request)
+ };
+
+ // JSON Schema for structured output.
+ string schema = """{"type":"object","properties":{"selected_skills":{"type":"array","items":{"type":"string"}}},"required":["selected_skills"]}""";
+ using JsonDocument formatDoc = JsonDocument.Parse(schema);
+
+ OllamaResult result;
+ try
+ {
+ result = await ollama.ChatStructuredAsync(selectionMessages, 0.0, formatDoc.RootElement.Clone(), cancellationToken).ConfigureAwait(false);
+ }
+ catch
+ {
+ // If structured output fails (e.g., model doesn't support it), fall back to no skills.
+ return [];
+ }
+
+ return ParseSelectedSkills(result.Content);
+ }
+
+ private IReadOnlyList<AgentSkill> ParseSelectedSkills(string content)
+ {
+ try
+ {
+ using JsonDocument doc = JsonDocument.Parse(content);
+ if (!doc.RootElement.TryGetProperty("selected_skills", out JsonElement skillsElement)) return [];
+ var selectedNames = new HashSet<string>(StringComparer.Ordinal);
+ foreach (JsonElement item in skillsElement.EnumerateArray())
+ {
+ string? name = item.GetString();
+ if (!string.IsNullOrWhiteSpace(name)) selectedNames.Add(name);
+ }
+ // Validate: only known skills, no duplicates, max 10.
+ var valid = new List<AgentSkill>();
+ foreach (AgentSkill skill in settings.Skills)
+ {
+ if (selectedNames.Contains(skill.Name))
+ {
+ valid.Add(skill);
+ if (valid.Count >= 10) break;
+ }
+ }
+ return valid;
+ }
+ catch
+ {
+ return [];
+ }
+ }
+
private async Task<string?> EndTaskSafelyAsync(Guid taskId, bool success, CancellationToken cancellationToken)
{
try