diff --git a/.design/project-log/2026-05-31-skills-template-only.md b/.design/project-log/2026-05-31-skills-template-only.md new file mode 100644 index 000000000..a089b9a86 --- /dev/null +++ b/.design/project-log/2026-05-31-skills-template-only.md @@ -0,0 +1,21 @@ +# Make skills template-only + +**Date**: 2026-05-31 +**PR**: #99 +**Issue**: #91 + +## Summary + +Removed the harness-config skill-copy step from `pkg/agent/provision.go`, making templates the sole source of skills during agent provisioning. Updated documentation and tests accordingly. + +## Findings + +- No shipped harness-config in the repository contains a `skills` directory, confirming this change drops no existing functionality. +- The provisioning code previously copied skills from two sources (harness-config base layer, then template overlay). Now only template skills are copied. +- The documentation in `templates.md` previously described skills as coming from "both templates and harness-configs" — corrected to template-only. + +## Changes + +1. `pkg/agent/provision.go` — removed 11-line block copying skills from `/skills` +2. `docs-site/src/content/docs/advanced-local/templates.md` — rewrote "Harness Skills" section as "Skills", removed harness-config references, fixed directory structure example +3. `pkg/agent/provision_test.go` — renamed `TestProvisionAgent_SkillsDirOverlay` to `TestProvisionAgent_SkillsAreTemplateOnly` and inverted the assertion to verify harness-config skills are NOT copied diff --git a/docs-site/src/content/docs/advanced-local/templates.md b/docs-site/src/content/docs/advanced-local/templates.md index fd892256d..ddc61952b 100644 --- a/docs-site/src/content/docs/advanced-local/templates.md +++ b/docs-site/src/content/docs/advanced-local/templates.md @@ -149,11 +149,11 @@ If you mess up a harness-config, you can restore the factory defaults: scion harness-config reset gemini ``` -## Harness Skills +## Skills -Templates and harness-configs can define **skills** — reusable instruction snippets that are automatically merged and mounted into the appropriate harness-specific directory during agent provisioning. +Templates can define **skills** — reusable, harness-agnostic instruction snippets that are automatically mounted into the appropriate harness-specific directory during agent provisioning. -When an agent is created, Scion collects skills from both the template and the harness-config, then mounts them into the correct location for the harness: +When an agent is created, Scion collects skills from each template in the chain and mounts them into the correct location for the harness: | Harness | Skills Directory | | :--- | :--- | @@ -164,19 +164,18 @@ This allows you to package domain-specific expertise (e.g., coding standards, re ### Defining Skills in a Template -Add skill files to the template's `home/` directory under the harness-specific path: +Place skill files in the template's `skills/` directory: ```text my-template/ ├── scion-agent.yaml ├── agents.md -└── home/ - └── .claude/ - └── skills/ - └── code-standards.md +└── skills/ + └── my-skill/ + └── SKILL.md ``` -Skills from both the template and the harness-config are merged at provisioning time, so you can define common skills in the harness-config and role-specific skills in individual templates. +When multiple templates are chained, skills from later templates overlay earlier ones. ### The `team-creation` Skill diff --git a/pkg/agent/provision.go b/pkg/agent/provision.go index 00522cd82..14f3ce675 100644 --- a/pkg/agent/provision.go +++ b/pkg/agent/provision.go @@ -593,17 +593,6 @@ func ProvisionAgent(ctx context.Context, agentName string, templateName string, if skillsDir != "" { skillsDest := filepath.Join(agentHome, skillsDir) - // Copy skills from harness-config base layer - hcSkills := filepath.Join(hcDir.Path, "skills") - if info, err := os.Stat(hcSkills); err == nil && info.IsDir() { - if err := os.MkdirAll(skillsDest, 0755); err != nil { - return "", "", nil, fmt.Errorf("failed to create skills dir: %w", err) - } - if err := util.CopyDir(hcSkills, skillsDest); err != nil { - return "", "", nil, fmt.Errorf("failed to copy harness-config skills: %w", err) - } - } - // Copy skills from each template in the chain (overlay behavior) for _, tpl := range chain { tplSkills := filepath.Join(tpl.Path, "skills") diff --git a/pkg/agent/provision_test.go b/pkg/agent/provision_test.go index b1ae6ccd7..f0481fd9f 100644 --- a/pkg/agent/provision_test.go +++ b/pkg/agent/provision_test.go @@ -881,7 +881,7 @@ func TestProvisionAgent_CopiesSkillsDir(t *testing.T) { } } -func TestProvisionAgent_SkillsDirOverlay(t *testing.T) { +func TestProvisionAgent_SkillsAreTemplateOnly(t *testing.T) { tmpDir := t.TempDir() oldWd, _ := os.Getwd() @@ -896,7 +896,7 @@ func TestProvisionAgent_SkillsDirOverlay(t *testing.T) { globalTemplatesDir := filepath.Join(globalScionDir, "templates") os.MkdirAll(globalTemplatesDir, 0755) - // Create a harness-config for gemini with its own skills + // Create a harness-config for gemini with its own skills (should be ignored) hcDir := filepath.Join(globalScionDir, "harness-configs", "gemini") os.MkdirAll(hcDir, 0755) configYAML := "harness: gemini\nimage: test-image:latest\n" @@ -926,12 +926,13 @@ func TestProvisionAgent_SkillsDirOverlay(t *testing.T) { t.Fatalf("ProvisionAgent failed: %v", err) } - // Both skills should exist (overlay/merge behavior) + // Harness-config skills should NOT be copied (skills are template-only) baseSkillPath := filepath.Join(agentHome, ".gemini", "skills", "base-skill", "SKILL.md") - if _, err := os.Stat(baseSkillPath); err != nil { - t.Errorf("expected base skill from harness-config at %s, got error: %v", baseSkillPath, err) + if _, err := os.Stat(baseSkillPath); err == nil { + t.Errorf("harness-config skill should not be copied, but found at %s", baseSkillPath) } + // Template skills should still be copied tplSkillPath := filepath.Join(agentHome, ".gemini", "skills", "tpl-skill", "SKILL.md") if _, err := os.Stat(tplSkillPath); err != nil { t.Errorf("expected template skill at %s, got error: %v", tplSkillPath, err)