diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-08-10 00:57:30 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-08-10 00:57:30 +0900 |
| commit | 6394dfcc50fb1804ab07be309d940017eb11ef7d (patch) | |
| tree | efaf80787e9a205c6758c4c7a541077b2dbd53ce | |
| parent | e4a951d8b953c37b9078a48bce43cac6fd09edac (diff) | |
feat(skills): add uv-assisted Python workflow
Add a concise Japanese companion prompt that asks the model to be approachable, clarify uncertainty, select available skills and tools, verify results, and report the next useful action without adding excessive context.
Add the standard skills/python-via-uv/SKILL.md package and UI metadata. The workflow uses uv python find offline to resolve an interpreter, then asks the Broker to launch the separately approved Python executable. It deliberately avoids uv run because the v1 OS child-process restriction must reject descendants created directly by uv.
Support standard skills/<name>/SKILL.md directories while retaining legacy top-level Markdown loading. Skip reparse-point skill directories, load deterministically, package the complete skills tree, and avoid loading nested reference Markdown as independent skills.
Validation: skill-creator quick_validate PASS; uv 0.11.23 resolved Python and the two-stage Python execution printed UV_BROKER_TWO_STAGE_OK. Release build passed with 0 warnings/errors; Core tests passed 49/49 with skip 0. The 1.0.0 Bundle and manifest were regenerated with the prompt and Skill.
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | prompts/user-system.md | 3 | ||||
| -rw-r--r-- | skills/python-via-uv/SKILL.md | 27 | ||||
| -rw-r--r-- | skills/python-via-uv/agents/openai.yaml | 4 | ||||
| -rw-r--r-- | src/Agent.App/Agent.App.csproj | 2 | ||||
| -rw-r--r-- | src/Agent.App/Configuration/SkillLoader.cs | 24 | ||||
| -rw-r--r-- | src/Agent.App/Program.cs | 4 | ||||
| -rw-r--r-- | tests/Agent.Core.Tests/ConfigurationTests.cs | 13 |
8 files changed, 77 insertions, 4 deletions
@@ -91,6 +91,10 @@ sandbox: Ollama通信は管理Broker側から行うため、Sandbox processのnetwork許可とは独立です。 +## Agent Skills + +`skills/<skill-name>/SKILL.md`形式と、互換用の`skills/*.md`を読み込みます。SkillはLLMへの補助指示であり、Filesystem/Process/Network権限は付与しません。Skillが使用する実行ファイルは、別途`agent.yaml`でabsolute pathとSHA-256を承認してください。 + ## stdio WFP操作のため、stdioではMCP Client自体を管理者PowerShellから起動します。Client設定例: diff --git a/prompts/user-system.md b/prompts/user-system.md index 2986739..f1d255b 100644 --- a/prompts/user-system.md +++ b/prompts/user-system.md @@ -1 +1,2 @@ -<!-- Add project-specific instructions here. This prompt cannot override the embedded Fixed System Prompt. --> +あなたはユーザーの身近な相棒です。親しみやすく率直に会話し、考えを整理し、必要なときだけ実作業を提案・実行してください。 +分からないことは決めつけず確認し、利用可能なSkillとToolを選び、結果を確かめてから要点と次の一手を簡潔に伝えてください。 diff --git a/skills/python-via-uv/SKILL.md b/skills/python-via-uv/SKILL.md new file mode 100644 index 0000000..d840e4d --- /dev/null +++ b/skills/python-via-uv/SKILL.md @@ -0,0 +1,27 @@ +--- +name: python-via-uv +description: uv経由でPythonコードを作成・実行・検証する。ユーザーがPythonによる計算、データ処理、自動化、試作、デバッグ、またはスクリプト実行を求めたときに使用する。 +--- + +# Python via uv + +1. 作業directoryと必要な入出力を確認し、コードは許可されたdirectory内の`.py`へ`file_edit`で保存する。複雑なコードを`python -c`へ詰め込まない。 +2. 承認済み`uv.exe`を`process_run`し、projectではcwdから`uv python find --offline --no-python-downloads --resolve-links`、単独scriptでは同じ引数へ`--no-project`を追加してinterpreterの絶対pathを取得する。 +3. 取得した承認済み`python.exe`を別の`process_run`で起動し、`-I <script>`を渡す。shellを経由せず、引数は配列で分離する。 +4. v1 Sandboxは全descendant生成を禁止するため、`uv run`は使用しない。uvが返したPythonをBrokerから直接起動することで、両方のpath/SHA-256 approvalとchild-process禁止を維持する。 +5. dependencyが環境にない場合は勝手にnetwork接続やinstallを行わず、必要なpackageと許可をユーザーへ確認する。依頼なしに`uv add`や`uv pip install`を実行しない。 +6. `stdout`、`stderr`、exit codeを確認する。exit code 0と期待結果を確認できた場合だけ成功と報告し、生成・変更したfileも示す。 + +`process_run`の基本形: + +```text +executable: <承認済みuv.exe> +args: ["python", "find", "--offline", "--no-python-downloads", "--resolve-links", "--no-project"] +cwd: <許可された作業directory> + +executable: <uvが返した承認済みpython.exe> +args: ["-I", "script.py"] +cwd: <許可された作業directory> +``` + +`uv.exe`または`python.exe`が未承認、あるいは作業directoryのread/write/execute権限が不足している場合は迂回せず、必要な設定を具体的に報告する。 diff --git a/skills/python-via-uv/agents/openai.yaml b/skills/python-via-uv/agents/openai.yaml new file mode 100644 index 0000000..de95aab --- /dev/null +++ b/skills/python-via-uv/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Python via uv" + short_description: "Run reproducible Python tasks safely through uv" + default_prompt: "Use $python-via-uv to create and run Python code, then verify the result." diff --git a/src/Agent.App/Agent.App.csproj b/src/Agent.App/Agent.App.csproj index a42ebad..4059624 100644 --- a/src/Agent.App/Agent.App.csproj +++ b/src/Agent.App/Agent.App.csproj @@ -18,7 +18,7 @@ <Content Include="..\..\config\agent.yaml" Link="agent.yaml" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> <Content Include="..\..\config\locales\**\*.json" Link="config\locales\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> <Content Include="..\..\prompts\user-system.md" Link="prompts\user-system.md" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> - <Content Include="..\..\skills\.keep" Link="skills\.keep" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> + <Content Include="..\..\skills\**\*" Link="skills\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" /> <Content Include="..\..\README.md" Link="README.md" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup> </Project> diff --git a/src/Agent.App/Configuration/SkillLoader.cs b/src/Agent.App/Configuration/SkillLoader.cs new file mode 100644 index 0000000..6923c12 --- /dev/null +++ b/src/Agent.App/Configuration/SkillLoader.cs @@ -0,0 +1,24 @@ +namespace Agent.App.Configuration; + +public static class SkillLoader +{ + public static string[] Load(string directory) + { + var paths = new List<string>(); + paths.AddRange(Directory.EnumerateFiles(directory, "*.md", SearchOption.TopDirectoryOnly)); + + foreach (string child in Directory.EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly)) + { + var info = new DirectoryInfo(child); + if ((info.Attributes & FileAttributes.ReparsePoint) != 0) continue; + string standardSkill = Path.Combine(info.FullName, "SKILL.md"); + if (File.Exists(standardSkill)) paths.Add(standardSkill); + } + + return paths + .Distinct(StringComparer.OrdinalIgnoreCase) + .Order(StringComparer.OrdinalIgnoreCase) + .Select(File.ReadAllText) + .ToArray(); + } +} diff --git a/src/Agent.App/Program.cs b/src/Agent.App/Program.cs index d9ee17a..82a3f36 100644 --- a/src/Agent.App/Program.cs +++ b/src/Agent.App/Program.cs @@ -85,7 +85,7 @@ internal static class Program { string userPrompt = File.ReadAllText(ConfigurationLoader.Resolve(baseDirectory, configuration.Agent.UserSystemPrompt)); string skillDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Skills.Directory); - string[] skills = Directory.EnumerateFiles(skillDirectory, "*.md", SearchOption.TopDirectoryOnly).Select(File.ReadAllText).ToArray(); + string[] skills = SkillLoader.Load(skillDirectory); var runtimeSettings = new AgentRuntimeSettings(userPrompt, language, skills, "Windows AppContainer, ACL, Job Object, child-process restriction, and WFP sandbox capabilities.", configuration.Agent.MaxIterations, configuration.Agent.SameFailureLimit, locale.Get("ollama.vision_unsupported")); var visionLimits = new VisionLimits(configuration.Mcp.Images.MaxCount, configuration.Mcp.Images.MaxBytesEach, configuration.Mcp.Images.MaxTotalBytes); await McpHost.RunStdioAsync(locale, OllamaSettingsFactory.Create(configuration.Ollama), runtimeSettings, visionLimits, CancellationToken.None, @@ -96,7 +96,7 @@ internal static class Program { string userPrompt = File.ReadAllText(ConfigurationLoader.Resolve(baseDirectory, configuration.Agent.UserSystemPrompt)); string skillDirectory = ConfigurationLoader.Resolve(baseDirectory, configuration.Skills.Directory); - string[] skills = Directory.EnumerateFiles(skillDirectory, "*.md", SearchOption.TopDirectoryOnly).Select(File.ReadAllText).ToArray(); + string[] skills = SkillLoader.Load(skillDirectory); var runtimeSettings = new AgentRuntimeSettings(userPrompt, language, skills, "Windows AppContainer, ACL, Job Object, child-process restriction, and WFP sandbox capabilities.", configuration.Agent.MaxIterations, configuration.Agent.SameFailureLimit, locale.Get("ollama.vision_unsupported")); var visionLimits = new VisionLimits(configuration.Mcp.Images.MaxCount, configuration.Mcp.Images.MaxBytesEach, configuration.Mcp.Images.MaxTotalBytes); await McpHost.RunHttpAsync(locale, OllamaSettingsFactory.Create(configuration.Ollama), runtimeSettings, visionLimits, diff --git a/tests/Agent.Core.Tests/ConfigurationTests.cs b/tests/Agent.Core.Tests/ConfigurationTests.cs index 7ef4afb..2c52b0c 100644 --- a/tests/Agent.Core.Tests/ConfigurationTests.cs +++ b/tests/Agent.Core.Tests/ConfigurationTests.cs @@ -42,6 +42,19 @@ public sealed class ConfigurationTests Assert.Equal("http", result.Transport); } + [Fact] + public void SkillLoader_supports_legacy_markdown_and_standard_skill_directories() + { + using var directory = new TestDirectory(); + directory.File("skills/legacy.md", "legacy skill"); + directory.File("skills/python-via-uv/SKILL.md", "standard skill"); + directory.File("skills/python-via-uv/reference.md", "must not be loaded as a skill"); + + string[] skills = SkillLoader.Load(Path.Combine(directory.Path, "skills")); + + Assert.Equal(["legacy skill", "standard skill"], skills); + } + private static string CreateValidConfiguration(TestDirectory directory) { directory.File("prompts/user-system.md", string.Empty); |
