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
- func ActiveQualities() map[SkillQuality]bool
- func BuildTriggerIndex(skills []Skill) *triggerIndex
- func DeriveKeywords(body string) ([]string, []string)
- func EnhanceCurationWithLLM(llm LLMClient, report *CurationReport) string
- func FormatAsContext(s Skill) string
- func FormatCurationReport(r *CurationReport) string
- func FormatSuggestion(s SkillSuggestion) string
- func HashBody(body string) string
- func IsStopword(word string) bool
- func MarshalSkill(s Skill) string
- func MicroCuration(userDir string, newSkills []Skill, allSkills []Skill) string
- func ProjectSkillsDir() string
- func SaveSuggestion(dir string, s SkillSuggestion) error
- func UserSkillsDir() string
- func ValidateSkillName(name string) error
- func WriteSkill(dir string, s Skill) error
- type CurateOptions
- type CurationConfig
- type CurationReport
- type FetchResult
- type ImportAssessment
- type ImportConfig
- type ImportOptions
- type ImportResult
- type ImportRisk
- type LLMClient
- type LlmMessage
- type LlmToolCall
- type OverlapGroup
- type QualityIssue
- type ScanResult
- type Skill
- type SkillDeleteTool
- type SkillListTool
- type SkillLoadTool
- type SkillManager
- type SkillPatchTool
- type SkillQuality
- type SkillSaveTool
- type SkillSource
- type SkillSuggestion
- func DetectCorrection(calls []ToolCall, userMessages []string) []SkillSuggestion
- func DetectErrorRecovery(calls []ToolCall) []SkillSuggestion
- func DetectExplicitInstruction(userMessages []string, calls []ToolCall) []SkillSuggestion
- func DetectMultiStepProcedure(calls []ToolCall) []SkillSuggestion
- func DetectRepeatedAction(calls []ToolCall) []SkillSuggestion
- func GenerateSkillWithLLM(llm LLMClient, calls []ToolCall, userMessages []string, heuristic string) *SkillSuggestion
- func RunAllHeuristics(messages []LlmMessage, userMessages []string) []SkillSuggestion
- type SkillTrigger
- type SkillsConfig
- type ToolCall
Constants ¶
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 ¶
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 ¶
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 IsStopword ¶
IsStopword returns true if the word is a common English stopword.
func MarshalSkill ¶
MarshalSkill serializes a skill to its SKILL.md representation.
func MicroCuration ¶
MicroCuration runs lightweight curation after a session. Returns a message if actions were taken, empty string otherwise.
func SaveSuggestion ¶
func SaveSuggestion(dir string, s SkillSuggestion) error
SaveSuggestion saves a SkillSuggestion as a SKILL.md in the given directory.
func ValidateSkillName ¶
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 ¶
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 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 ¶
LlmToolCall is a subset of llm.ToolCall used for extraction.
type OverlapGroup ¶
type OverlapGroup struct {
Skills []string `json:"skills"` // skill names
Message string `json:"message"`
}
OverlapGroup groups skills that share trigger keywords and should be merged.
type QualityIssue ¶
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) 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) 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) 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) 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) 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