skills

package
v0.16.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 20, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package skills implements odek's skill system — just-in-time agent specialization.

Skills are structured markdown files (SKILL.md) with YAML frontmatter that provide domain knowledge. They load on demand when the user's input matches topic/action keywords, keeping the agent's context focused.

Storage layers (lowest → highest priority):

~/.odek/skills/<name>/SKILL.md      ← user-global (self-improvement writes here)
./.odek/skills/<name>/SKILL.md      ← project (committed, shared)

Skills can also be imported from URIs (file:// or https://) via odek skill import. The import flow includes an LLM risk assessment and user approval before saving.

Index

Constants

View Source
const MaxSkillBodySize = 1_048_576 // 1MB

MaxSkillBodySize is the maximum allowed body size for a skill, in bytes.

Variables

This section is empty.

Functions

func ActiveQualities

func ActiveQualities() map[SkillQuality]bool

ActiveQualities returns the set of quality states that should be loaded.

func BuildTriggerIndex

func BuildTriggerIndex(skills []Skill) *triggerIndex

BuildTriggerIndex builds a keyword trie from a list of skills. Each skill's topic and action keywords are indexed separately.

func DeriveKeywords

func DeriveKeywords(body string) ([]string, []string)

DeriveKeywords extracts topic and action keywords from body text. Uses word frequency + heuristic POS detection. Returns (topics, actions) slices.

func EnhanceCurationWithLLM

func EnhanceCurationWithLLM(llm LLMClient, report *CurationReport) string

EnhanceCurationWithLLM uses the LLM to assess skill quality and suggest improvements. Returns a message describing findings, or empty string.

func FormatAsContext

func FormatAsContext(s Skill) string

FormatAsContext formats a skill's body for injection into the system prompt.

func FormatCurationReport

func FormatCurationReport(r *CurationReport) string

FormatCurationReport formats a CurationReport for display.

func FormatSuggestion

func FormatSuggestion(s SkillSuggestion) string

FormatSuggestion formats a SkillSuggestion for display to the user.

func HashBody

func HashBody(body string) string

HashBody returns a sha256 hex digest of the body text.

func IsStopword

func IsStopword(word string) bool

IsStopword returns true if the word is a common English stopword.

func MarshalSkill

func MarshalSkill(s Skill) string

MarshalSkill serializes a skill to its SKILL.md representation.

func MicroCuration

func MicroCuration(userDir string, newSkills []Skill, allSkills []Skill) string

MicroCuration runs lightweight curation after a session. Returns a message if actions were taken, empty string otherwise.

func ProjectSkillsDir

func ProjectSkillsDir() string

ProjectSkillsDir returns ./.odek/skills/

func SaveSuggestion

func SaveSuggestion(dir string, s SkillSuggestion) error

SaveSuggestion saves a SkillSuggestion as a SKILL.md in the given directory.

func UserSkillsDir

func UserSkillsDir() string

UserSkillsDir returns ~/.odek/skills/

func ValidateSkillName

func ValidateSkillName(name string) error

ValidateSkillName checks that a skill name is safe for filesystem use. Returns an error if the name contains path separators, relative components, or hidden-file prefixes.

func WriteSkill

func WriteSkill(dir string, s Skill) error

WriteSkill writes a skill to the given directory as <name>/SKILL.md. Creates the directory if it doesn't exist. Returns an error if the skill name is unsafe for filesystem use (path traversal, etc.).

Types

type CurateOptions

type CurateOptions struct {
	StalenessDays int  // skills unused for this many days are flagged
	Apply         bool // apply changes (delete stale if auto_prune enabled)
	Interactive   bool // confirm each change interactively (not used here, set by CLI)
}

Curate runs all curation passes on a set of skills. Passes are read-only unless opts.Apply is set.

type CurationConfig

type CurationConfig struct {
	StalenessDays int  `json:"staleness_days"`
	AutoPrune     bool `json:"auto_prune"`
}

CurationConfig controls automated curation.

type CurationReport

type CurationReport struct {
	StaleSkills   []Skill        `json:"stale_skills"`
	OverlapGroups []OverlapGroup `json:"overlap_groups"`
	QualityIssues []QualityIssue `json:"quality_issues"`
	TotalSkills   int            `json:"total_skills"`
	Deduplicated  int            `json:"deduplicated"`
}

CurationReport summarizes the findings of a curation pass.

func CurateSkills

func CurateSkills(skills []Skill, opts CurateOptions) *CurationReport

CurateSkills runs the full curation pipeline.

type FetchResult

type FetchResult struct {
	Content    string // raw SKILL.md content
	SourceName string // "local file" or the URL
	SourcePath string // actual path or URL
}

FetchResult holds the fetched skill content and its source info.

func FetchFromURI

func FetchFromURI(uri string, maxBytes int, timeoutSecs int, requireHTTPS bool) (*FetchResult, error)

FetchFromURI fetches skill content from a file:// or https:// URI. When requireHTTPS is true, http:// URIs are rejected.

type ImportAssessment

type ImportAssessment struct {
	RiskClass           ImportRisk `json:"risk_class"`
	Reasons             []string   `json:"reasons"`
	WhatItDoes          string     `json:"what_it_does"`
	RecommendedTriggers []string   `json:"recommended_triggers"`
	RedFlags            []string   `json:"red_flags"`
}

ImportAssessment is the structured result from the LLM risk assessment.

func AssessSkill

func AssessSkill(content string, llmCall func(prompt string) (string, error)) (*ImportAssessment, error)

AssessSkill calls the LLM to assess the risk of an imported skill. The llmCall function is injected so tests can mock it.

type ImportConfig

type ImportConfig struct {
	MaxSizeBytes int  `json:"max_size_bytes"`
	TimeoutSecs  int  `json:"timeout_seconds"`
	RequireHTTPS bool `json:"require_https"`
}

ImportConfig controls the URI import flow.

type ImportOptions

type ImportOptions struct {
	URI          string // the URI to import from
	MaxBytes     int    // max bytes for fetched content
	Timeout      int    // HTTP timeout in seconds
	BasicOnly    bool   // skip LLM assessment, use basic validation only
	AutoYes      bool   // skip approval prompt (for scripting, shows warning)
	RequireHTTPS bool   // reject http:// URIs (enforce HTTPS)
	UserDir      string // directory to save the skill into
}

ImportOptions controls the import flow.

type ImportResult

type ImportResult struct {
	Skill      Skill             // the saved skill
	Assessment *ImportAssessment // the risk assessment (nil for basic mode)
	Path       string            // where the skill was saved
}

ImportResult holds the result of a successful import.

func ImportSkill

func ImportSkill(opts ImportOptions, confirmFn func(assessment *ImportAssessment) bool, llmCall func(string) (string, error)) (*ImportResult, error)

ImportSkill runs the full import flow: fetch → parse → assess → confirm → save. The confirmFn is called to get user approval. Return true to continue. The llmCall fn is called to assess risk. Set to nil for basic mode.

type ImportRisk

type ImportRisk string

ImportRisk represents the LLM-assessed risk of an imported skill.

const (
	RiskSafe      ImportRisk = "safe"
	RiskElevated  ImportRisk = "elevated"
	RiskDangerous ImportRisk = "dangerous"
)

type LLMClient

type LLMClient interface {
	SimpleCall(ctx context.Context, system, user string) (string, error)
}

LLMClient abstracts the LLM calls needed for skill enhancement.

type LlmMessage

type LlmMessage struct {
	Role       string
	Content    string
	Name       string
	ToolCallID string
	ToolCalls  []LlmToolCall
}

LlmMessage is a subset of llm.Message used for extraction. We define it here to avoid importing the llm package.

type LlmToolCall

type LlmToolCall struct {
	ID       string
	Function struct {
		Name      string
		Arguments string
	}
}

LlmToolCall is a subset of llm.ToolCall used for extraction.

type OverlapGroup

type OverlapGroup struct {
	Skills  []string `json:"skills"` // skill names
	Shared  []string `json:"shared"` // shared topic keywords
	Message string   `json:"message"`
}

OverlapGroup groups skills that share trigger keywords and should be merged.

type QualityIssue

type QualityIssue struct {
	Name   string   `json:"name"`
	Issues []string `json:"issues"`
}

QualityIssue flags a skill that fails structural validation.

type ScanResult

type ScanResult struct {
	AutoLoad []Skill // skills with auto_load=true
	Lazy     []Skill // skills with auto_load=false
}

ScanResult holds the result of scanning skill directories.

func ScanDirs

func ScanDirs(projectDir, userDir string, extraDirs []string) *ScanResult

ScanDirs scans the project-local and user-global skill directories, plus any additional dirs, and returns categorized skills. Dirs are scanned in order: project → user → extras. If a skill name exists in multiple dirs, the first (higher-priority) wins.

type Skill

type Skill struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Version     string         `json:"version,omitempty"`
	Author      string         `json:"author,omitempty"`
	Trigger     SkillTrigger   `json:"trigger"`
	AutoLoad    bool           `json:"auto_load"`
	Quality     SkillQuality   `json:"quality"`
	UsageCount  int            `json:"usage_count"`
	LastUsed    time.Time      `json:"last_used"`
	Body        string         `json:"body"`           // raw markdown body (no frontmatter)
	BodyHash    string         `json:"body_hash"`      // sha256 of body (for dedup)
	Source      SkillSource    `json:"source"`         // where the skill came from
	Meta        map[string]any `json:"meta,omitempty"` // arbitrary extra frontmatter fields
}

Skill represents a single skill file loaded from a skill directory. The struct is populated from the YAML frontmatter plus synthetic fields.

type SkillDeleteTool

type SkillDeleteTool struct {
	Manager *SkillManager
}

SkillDeleteTool removes a skill file from disk.

func (*SkillDeleteTool) Call

func (t *SkillDeleteTool) Call(args string) (string, error)

func (*SkillDeleteTool) Description

func (t *SkillDeleteTool) Description() string

func (*SkillDeleteTool) Name

func (t *SkillDeleteTool) Name() string

func (*SkillDeleteTool) Schema

func (t *SkillDeleteTool) Schema() any

type SkillListTool

type SkillListTool struct {
	Manager *SkillManager
}

SkillListTool lists all available skills with metadata.

func (*SkillListTool) Call

func (t *SkillListTool) Call(args string) (string, error)

func (*SkillListTool) Description

func (t *SkillListTool) Description() string

func (*SkillListTool) Name

func (t *SkillListTool) Name() string

func (*SkillListTool) Schema

func (t *SkillListTool) Schema() any

type SkillLoadTool

type SkillLoadTool struct {
	Manager *SkillManager
}

SkillLoadTool lets the agent load a skill's full content by name.

func (*SkillLoadTool) Call

func (t *SkillLoadTool) Call(args string) (string, error)

func (*SkillLoadTool) Description

func (t *SkillLoadTool) Description() string

func (*SkillLoadTool) Name

func (t *SkillLoadTool) Name() string

func (*SkillLoadTool) Schema

func (t *SkillLoadTool) Schema() any

type SkillManager

type SkillManager struct {
	UserDir    string
	ProjectDir string
	Result     *ScanResult
	TrieIndex  *triggerIndex
	// contains filtered or unexported fields
}

SkillManager holds the state needed by skill management tools. It wraps the skill store and provides access to the scan result. Thread-safe: use GetResult/GetTrieIndex for concurrent access.

func NewSkillManager

func NewSkillManager(userDir, projectDir string) *SkillManager

NewSkillManager creates a SkillManager with the given directories. It scans the directories and builds the trigger index.

func (*SkillManager) GetResult

func (sm *SkillManager) GetResult() *ScanResult

GetResult returns a read-locked copy of the scan result.

func (*SkillManager) GetTrieIndex

func (sm *SkillManager) GetTrieIndex() *triggerIndex

GetTrieIndex returns the trigger index for read-only use. The caller must not modify the returned index.

func (*SkillManager) RecordUsage

func (sm *SkillManager) RecordUsage(name string)

RecordUsage marks a skill as used, updating LastUsed and UsageCount. Safe for concurrent access. Called when a skill is loaded into context.

func (*SkillManager) Reload

func (sm *SkillManager) Reload()

Reload rescans skill directories and rebuilds the trigger index. Call after saving or deleting skills to keep the manager in sync.

type SkillPatchTool

type SkillPatchTool struct {
	Manager *SkillManager
}

SkillPatchTool updates an existing skill's body content via find-and-replace.

func (*SkillPatchTool) Call

func (t *SkillPatchTool) Call(args string) (string, error)

func (*SkillPatchTool) Description

func (t *SkillPatchTool) Description() string

func (*SkillPatchTool) Name

func (t *SkillPatchTool) Name() string

func (*SkillPatchTool) Schema

func (t *SkillPatchTool) Schema() any

type SkillQuality

type SkillQuality string

SkillQuality represents the curation state of a skill.

const (
	QualityDraft    SkillQuality = "draft"    // auto-generated by self-improvement
	QualityVerified SkillQuality = "verified" // passed quality gate + user approved
	QualityImported SkillQuality = "imported" // imported from URI with LLM risk assessment
	QualityManual   SkillQuality = "manual"   // user-created via odek skill save
	QualityStale    SkillQuality = "stale"    // >90 days without use (skipped at load)
)

type SkillSaveTool

type SkillSaveTool struct {
	Manager *SkillManager
}

SkillSaveTool saves a new skill to the user directory.

func (*SkillSaveTool) Call

func (t *SkillSaveTool) Call(args string) (string, error)

func (*SkillSaveTool) Description

func (t *SkillSaveTool) Description() string

func (*SkillSaveTool) Name

func (t *SkillSaveTool) Name() string

func (*SkillSaveTool) Schema

func (t *SkillSaveTool) Schema() any

type SkillSource

type SkillSource struct {
	Dir  string `json:"dir"`  // e.g. "~/.odek/skills" or "./.odek/skills"
	Path string `json:"path"` // full path to SKILL.md
}

SkillSource identifies where a skill was loaded from.

type SkillSuggestion

type SkillSuggestion struct {
	Name        string   // suggested name
	Description string   // one-line description
	Body        string   // generated markdown body
	Heuristic   string   // which heuristic detected it
	CommandLog  []string // commands that were executed (for context)
}

SkillSuggestion represents a detected opportunity to save a skill.

func DetectCorrection

func DetectCorrection(calls []ToolCall, userMessages []string) []SkillSuggestion

DetectCorrection detects a user-corrected approach. The heuristic scans for keywords suggesting redirection.

func DetectErrorRecovery

func DetectErrorRecovery(calls []ToolCall) []SkillSuggestion

DetectErrorRecovery detects a terminal failure → retry → success pattern.

func DetectExplicitInstruction

func DetectExplicitInstruction(userMessages []string, calls []ToolCall) []SkillSuggestion

DetectExplicitInstruction returns a suggestion if any user message explicitly asks to save something as a skill.

func DetectMultiStepProcedure

func DetectMultiStepProcedure(calls []ToolCall) []SkillSuggestion

DetectMultiStepProcedure detects 4+ sequential terminal calls on related topics.

func DetectRepeatedAction

func DetectRepeatedAction(calls []ToolCall) []SkillSuggestion

DetectRepeatedAction detects the same tool sequence appearing twice.

func GenerateSkillWithLLM

func GenerateSkillWithLLM(llm LLMClient, calls []ToolCall, userMessages []string, heuristic string) *SkillSuggestion

GenerateSkillWithLLM takes heuristic-detected tool calls and user messages and uses the LLM to generate a rich, accurate skill with proper name, description, trigger keywords, and structured body. Returns nil if the LLM call fails or returns empty output.

func RunAllHeuristics

func RunAllHeuristics(messages []LlmMessage, userMessages []string) []SkillSuggestion

type SkillTrigger

type SkillTrigger struct {
	TopicKeywords  []string `json:"topic,omitempty" yaml:"topic,omitempty"`
	ActionKeywords []string `json:"action,omitempty" yaml:"action,omitempty"`
}

SkillTrigger defines when a skill should be loaded into context. Skills load when any topic keyword AND any action keyword match the user's input.

type SkillsConfig

type SkillsConfig struct {
	MaxAutoLoad  int            `json:"max_auto_load"`
	MaxLazySlots int            `json:"max_lazy_slots"`
	Learn        bool           `json:"learn"`
	Dirs         []string       `json:"dirs,omitempty"`
	Import       ImportConfig   `json:"import"`
	Curation     CurationConfig `json:"curation"`
	LLMLearn     bool           `json:"llm_learn"`
	LLMCurate    bool           `json:"llm_curate"`
}

SkillsConfig holds the skills section of odek.json.

func DefaultSkillsConfig

func DefaultSkillsConfig() SkillsConfig

DefaultSkillsConfig returns sensible defaults for the skills system.

type ToolCall

type ToolCall struct {
	Tool     string // "terminal", "read_file", "write_file", etc.
	Input    string // the full command or args passed to the tool
	Output   string // the tool's output (first 500 chars)
	ExitCode int    // 0 = success, non-zero = failure
	Turn     int    // which iteration of the loop this happened in
}

ToolCall represents a single tool invocation captured during a session.

func ExtractToolCalls

func ExtractToolCalls(messages []LlmMessage) []ToolCall

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL