Skip to content

Commit 41dab26

Browse files
authored
Merge pull request #1074 from rumpl/feat-skills
Skills
2 parents 099802c + ac71243 commit 41dab26

14 files changed

Lines changed: 633 additions & 0 deletions

pkg/agent/agent.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Agent struct {
2929
tools []tools.Tool
3030
commands map[string]string
3131
pendingWarnings []string
32+
skillsEnabled bool
3233
}
3334

3435
// New creates a new agent
@@ -114,6 +115,11 @@ func (a *Agent) Commands() map[string]string {
114115
return a.commands
115116
}
116117

118+
// SkillsEnabled returns whether skills discovery is enabled for this agent.
119+
func (a *Agent) SkillsEnabled() bool {
120+
return a.skillsEnabled
121+
}
122+
117123
// Tools returns the tools available to this agent
118124
func (a *Agent) Tools(ctx context.Context) ([]tools.Tool, error) {
119125
a.ensureToolSetsAreStarted(ctx)

pkg/agent/opts.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ func WithLoadTimeWarnings(warnings []string) Opt {
117117
}
118118
}
119119

120+
func WithSkillsEnabled(enabled bool) Opt {
121+
return func(a *Agent) {
122+
a.skillsEnabled = enabled
123+
}
124+
}
125+
120126
type StartableToolSet struct {
121127
tools.ToolSet
122128
started atomic.Bool

pkg/config/config.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"slices"
78

89
"github.com/goccy/go-yaml"
910

@@ -116,6 +117,10 @@ func validateConfig(cfg *latest.Config) error {
116117
return fmt.Errorf("agent '%s' references non-existent sub-agent '%s'", agentName, subAgentName)
117118
}
118119
}
120+
121+
if err := validateSkillsConfiguration(agentName, &agent); err != nil {
122+
return err
123+
}
119124
}
120125

121126
return nil
@@ -124,3 +129,44 @@ func validateConfig(cfg *latest.Config) error {
124129
func boolPtr(b bool) *bool {
125130
return &b
126131
}
132+
133+
// validateSkillsConfiguration ensures that agents with skills enabled have the necessary tools
134+
func validateSkillsConfiguration(agentName string, agent *latest.AgentConfig) error {
135+
// Check if skills are enabled
136+
if agent.Skills == nil || !*agent.Skills {
137+
return nil
138+
}
139+
140+
// Skills are enabled, validate toolsets
141+
hasFilesystemToolset := false
142+
hasReadFileTool := false
143+
144+
for _, toolset := range agent.Toolsets {
145+
if toolset.Type == "filesystem" {
146+
hasFilesystemToolset = true
147+
148+
// Check if read_file tool is enabled
149+
// If no specific tools are listed, all tools are enabled
150+
if len(toolset.Tools) == 0 {
151+
hasReadFileTool = true
152+
break
153+
}
154+
155+
// Check if read_file is in the tools list
156+
if slices.Contains(toolset.Tools, "read_file") {
157+
hasReadFileTool = true
158+
break
159+
}
160+
}
161+
}
162+
163+
if !hasFilesystemToolset {
164+
return fmt.Errorf("agent '%s' has skills enabled but does not have a 'filesystem' toolset configured", agentName)
165+
}
166+
167+
if !hasReadFileTool {
168+
return fmt.Errorf("agent '%s' has skills enabled but the 'filesystem' toolset does not include the 'read_file' tool", agentName)
169+
}
170+
171+
return nil
172+
}

pkg/config/latest/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type AgentConfig struct {
3838
AddPromptFiles []string `json:"add_prompt_files,omitempty" yaml:"add_prompt_files,omitempty"`
3939
Commands types.Commands `json:"commands,omitempty"`
4040
StructuredOutput *StructuredOutput `json:"structured_output,omitempty"`
41+
Skills *bool `json:"skills,omitempty"`
4142
}
4243

4344
// ModelConfig represents the configuration for a model
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: "3"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
skills: false
7+
toolsets:
8+
- type: shell
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: "3"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
skills: true
7+
toolsets:
8+
- type: shell
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: "3"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
skills: true
7+
toolsets:
8+
- type: filesystem
9+
tools: ["write_file", "edit_file"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: "3"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
skills: true
7+
toolsets:
8+
- type: filesystem
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: "3"
2+
3+
agents:
4+
root:
5+
model: openai/gpt-4o
6+
skills: true
7+
toolsets:
8+
- type: filesystem
9+
tools: ["read_file", "write_file"]

pkg/config/validation_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ func TestValidationErrors(t *testing.T) {
4747
name: "post_edit in non filesystem toolset",
4848
path: "invalid_post_edit_v2.yaml",
4949
},
50+
{
51+
name: "skills enabled without filesystem toolset",
52+
path: "skills_missing_filesystem.yaml",
53+
},
54+
{
55+
name: "skills enabled without read_file tool",
56+
path: "skills_missing_read_file.yaml",
57+
},
5058
}
5159

5260
for _, tt := range tests {
@@ -58,3 +66,35 @@ func TestValidationErrors(t *testing.T) {
5866
})
5967
}
6068
}
69+
70+
func TestValidSkillsConfiguration(t *testing.T) {
71+
t.Parallel()
72+
73+
tests := []struct {
74+
name string
75+
path string
76+
}{
77+
{
78+
name: "skills with all filesystem tools",
79+
path: "skills_valid_all_tools.yaml",
80+
},
81+
{
82+
name: "skills with explicit read_file tool",
83+
path: "skills_valid_explicit_tools.yaml",
84+
},
85+
{
86+
name: "skills disabled",
87+
path: "skills_disabled.yaml",
88+
},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
t.Parallel()
94+
95+
cfg, err := Load(t.Context(), testfileSource(filepath.Join("testdata", tt.path)))
96+
require.NoError(t, err)
97+
require.NotNil(t, cfg)
98+
})
99+
}
100+
}

0 commit comments

Comments
 (0)