Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .design/project-log/2026-05-31-skills-template-only.md
Original file line number Diff line number Diff line change
@@ -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 `<harness-config>/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
17 changes: 8 additions & 9 deletions docs-site/src/content/docs/advanced-local/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| :--- | :--- |
Expand All @@ -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

Expand Down
11 changes: 0 additions & 11 deletions pkg/agent/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 6 additions & 5 deletions pkg/agent/provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading