remove built-in skill handlers, replace with external tools

skill_list and skill_load are now external shell scripts (data/tools/).
Removed from builtins: SkillList, SkillLoad, SkillActive.
Removed: toolsrv/skills.go, tools/builtin/skill.go, WithSkillsRegistry,
SkillsRegistry accessor, ListSkillsTools.
ollie-remote no longer initializes a skills registry.

The demarcation: built-ins mutate agent internals (tool schema, context
injection). Skills are just file reads — no special treatment needed.
This commit is contained in:
Levi Neely 2026-07-31 16:45:37 +02:00
parent 9f8a1130cf
commit 7eb022faba
8 changed files with 8 additions and 170 deletions

View File

@ -29,7 +29,6 @@ import (
"sync"
"syscall"
"ollie/skills"
"ollie/toolsrv"
"ollie/tools/builtin"
)
@ -82,7 +81,6 @@ func main() {
// Build the tool server with builtins and registries.
toolReg, _ := toolsrv.NewRegistry()
skillsReg := skills.NewRegistry()
agentID := fmt.Sprintf("agent-%d", os.Getpid())
var opts []toolsrv.Option
@ -94,7 +92,6 @@ func main() {
if toolReg != nil {
opts = append(opts, toolsrv.WithToolRegistry(toolReg, agentID))
}
opts = append(opts, toolsrv.WithSkillsRegistry(skillsReg))
server := toolsrv.New(*cwd)
for _, o := range opts {
o(server)

View File

@ -0,0 +1 @@
# Placeholder — real config copied during build (just ollie-remote)

View File

@ -5,13 +5,18 @@ for cmd in mcp-to-command mcp-remote; do
command -v "$cmd" >/dev/null 2>&1 || { echo "Error: Missing required command: $cmd" >&2; exit 1; }
done
if [ -z "$OLLIE_LOGSEQ_TOKEN" ]; then
echo "Error: OLLIE_LOGSEQ_TOKEN not set" >&2
exit 1
fi
LOGSEQ_MCP_URL="${LOGSEQ_MCP_URL:-http://127.0.0.1:12315}"
have_timeout=
command -v timeout >/dev/null 2>&1 && have_timeout=1
if [ -n "$have_timeout" ]; then
exec timeout 30 mcp-to-command mcp-remote "$LOGSEQ_MCP_URL/mcp" --transport http-first -- "$@" || {
exec timeout 30 mcp-to-command mcp-remote "$LOGSEQ_MCP_URL/mcp" --transport http-first --header "Authorization: Bearer $OLLIE_LOGSEQ_TOKEN" -- "$@" || {
if [ $? -eq 124 ]; then
echo "Error: timeout exceeded, may want to retry" >&2
exit 124
@ -21,4 +26,4 @@ if [ -n "$have_timeout" ]; then
}
fi
exec mcp-to-command mcp-remote "$LOGSEQ_MCP_URL/mcp" --transport http-first -- "$@"
exec mcp-to-command mcp-remote "$LOGSEQ_MCP_URL/mcp" --transport http-first --header "Authorization: Bearer $OLLIE_LOGSEQ_TOKEN" -- "$@"

View File

@ -12,8 +12,5 @@ func Builtins() map[string]toolsrv.Handler {
"tool_list": ToolList,
"tool_load": ToolLoad,
"tool_active": ToolActive,
"skill_list": SkillList,
"skill_load": SkillLoad,
"skill_active": SkillActive,
}
}

View File

@ -1,83 +0,0 @@
package builtin
import (
"context"
"encoding/json"
"fmt"
"strings"
"ollie/toolsrv"
)
// SkillList lists all available skills from the global registry.
func SkillList(ctx context.Context, srv *toolsrv.Server, args json.RawMessage) (string, error) {
reg := srv.SkillsRegistry()
if reg == nil {
return "", fmt.Errorf("skill_list: no skills registry available")
}
summaries := reg.Summaries()
if len(summaries) == 0 {
return "(no skills found)", nil
}
var out strings.Builder
for _, s := range summaries {
out.WriteString(s.Name)
if s.Description != "" {
out.WriteString(" — ")
out.WriteString(s.Description)
}
out.WriteString("\n")
}
return strings.TrimRight(out.String(), "\n"), nil
}
// SkillLoad loads a skill into the current session.
func SkillLoad(ctx context.Context, srv *toolsrv.Server, args json.RawMessage) (string, error) {
var a struct {
Name string `json:"name"`
}
if err := json.Unmarshal(args, &a); err != nil {
return "", fmt.Errorf("skill_load: bad args: %w", err)
}
if a.Name == "" {
return "", fmt.Errorf("skill_load: name is required")
}
reg := srv.SkillsRegistry()
sid := srv.SessionID()
if reg == nil || sid == "" {
return "", fmt.Errorf("skill_load: no session registry")
}
if err := reg.Load(sid, a.Name); err != nil {
return "", fmt.Errorf("skill_load: %w", err)
}
content, err := reg.SkillContent(a.Name)
if err != nil {
return "", fmt.Errorf("skill_load: %w", err)
}
injectMsg := fmt.Sprintf("[system: loaded skill %s]\n\n%s", a.Name, content)
srv.InjectContent(injectMsg)
return fmt.Sprintf("loaded: %s (%d bytes)", a.Name, len(content)), nil
}
// SkillActive lists skills currently loaded in this session.
func SkillActive(ctx context.Context, srv *toolsrv.Server, args json.RawMessage) (string, error) {
reg := srv.SkillsRegistry()
sid := srv.SessionID()
if reg == nil || sid == "" {
return "(no skills loaded)", nil
}
loaded := reg.Loaded(sid)
if len(loaded) == 0 {
return "(no skills loaded)", nil
}
var out strings.Builder
for _, s := range loaded {
out.WriteString(s.Name)
if s.Description != "" {
out.WriteString(" — ")
out.WriteString(s.Description)
}
out.WriteString("\n")
}
return strings.TrimRight(out.String(), "\n"), nil
}

View File

@ -2,7 +2,6 @@ package toolsrv
import (
"context"
"ollie/skills"
)
// --- Exported accessors for builtin tool handlers ---
@ -20,9 +19,6 @@ func (e *Server) ToolRegistry() *Registry { return e.toolRegistry }
// SessionID returns the session ID associated with this server.
func (e *Server) SessionID() string { return e.sessionID }
// SkillsRegistry returns the attached skills registry, or nil.
func (e *Server) SkillsRegistry() *skills.Registry { return e.skillsRegistry }
// InjectContent calls the OnInjection hook if set.
func (e *Server) InjectContent(content string) {
if e.OnInjection != nil {

View File

@ -11,7 +11,6 @@ import (
"ollie/detach"
"ollie/paths"
"ollie/skills"
)
const (
@ -62,7 +61,6 @@ type Server struct {
builtins map[string]Handler
toolRegistry *Registry
skillsRegistry *skills.Registry
sessionID string
// OnInjection is called when a skill is loaded and its content
@ -145,11 +143,6 @@ func WithToolRegistry(r *Registry, sessionID string) Option {
}
}
// WithSkillsRegistry attaches a skills registry to the Server.
func WithSkillsRegistry(r *skills.Registry) Option {
return func(s *Server) { s.skillsRegistry = r }
}
// ListTools implements Server, returning shell plus any
// tools promoted in the session's tool registry.
@ -228,9 +221,6 @@ Returns tools with descriptions, one per line.`,
if e.toolRegistry != nil && e.sessionID != "" {
all = append(all, e.toolRegistry.Loaded(e.sessionID)...)
}
// Append skill tools if skills registry is available
skillTools := ListSkillsTools(e.skillsRegistry, e.sessionID)
all = append(all, skillTools...)
return all, nil
}

View File

@ -1,65 +0,0 @@
package toolsrv
import (
"encoding/json"
"ollie/skills"
)
// SetSkillsRegistry attaches a skills registry to the execute server.
// The server uses it to serve skill_list, skill_load, and skill_active.
func (e *Server) SetSkillsRegistry(r *skills.Registry) {
e.skillsRegistry = r
}
// ListSkillsTools returns ToolInfo entries for the skill_* built-ins.
// These are included alongside the standard tool_* tools.
func ListSkillsTools(skillsReg *skills.Registry, sessionID string) []ToolInfo {
if skillsReg == nil {
return nil
}
tools := []ToolInfo{
{
Name: "skill_list",
Description: `List available skill modules with name and description.
Usage: {"name": "skillname"} — if name is provided, loads that skill.
Otherwise lists all skills with descriptions.`,
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {}
}`),
},
{
Name: "skill_load",
Description: `Load a skill module into the current session's context.
Usage: {"name": "skillname"}
The skill content (SKILL.md body) is injected into the agent's context
and persists across turns.`,
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["name"],
"properties": {
"name": {"type": "string", "description": "Skill name to load."}
}
}`),
},
{
Name: "skill_active",
Description: `List skills currently loaded in this session.
Usage: (no arguments)
Returns loaded skill names, one per line.`,
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {}
}`),
},
}
return tools
}
// dispatchSkillList lists all available skills from the global registry.