135 lines
4.5 KiB
Go
135 lines
4.5 KiB
Go
// Package skills implements the M5 reusable-workflow capability: a Skill is a
|
|
// higher-order, persistable workflow — an ordered set of steps plus a base
|
|
// prompt, references, and guardrails — that builds on pkg/plugins/sdk and is
|
|
// more structured than a Tool (DevDocs/design/09 §0/§3).
|
|
//
|
|
// The package provides five pieces:
|
|
//
|
|
// - Skill: the concrete in-memory model implementing pkg/plugins/sdk.Skill.
|
|
// - Store: persistence of skills in a SQLite skills table.
|
|
// - Loader: loading Skill definitions from YAML (a single stream or a
|
|
// configs/skills/*.yaml directory).
|
|
// - Runner: expanding a Skill into an executable Workflow for the loop/agent
|
|
// executor (the actual execution itself lands in M2/M12).
|
|
// - Register: publishing skills to the global capability registry
|
|
// (internal/capability/registry) as KindSkill capabilities.
|
|
package skills
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"strings"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
|
)
|
|
|
|
// Sentinel errors returned by the skills package. Use errors.Is to test.
|
|
var (
|
|
// ErrInvalidSkill is returned when a skill is nil or has a blank name.
|
|
ErrInvalidSkill = errors.New("skills: invalid skill")
|
|
// ErrNotFound is returned by Store when a skill is absent.
|
|
ErrNotFound = errors.New("skills: skill not found")
|
|
// ErrNilRegistrar is returned by Register/NewRunner when no registry was
|
|
// supplied.
|
|
ErrNilRegistrar = errors.New("skills: nil registrar")
|
|
// ErrNilDB is returned by NewStore when no *sql.DB was supplied.
|
|
ErrNilDB = errors.New("skills: nil db")
|
|
// ErrRunPending is returned by Runner.Run, which awaits M12 wiring.
|
|
ErrRunPending = errors.New("skills: run pending")
|
|
)
|
|
|
|
// Skill is the concrete, persistable form of a skill (see pkg/plugins/sdk.Skill):
|
|
// a reusable workflow (ordered steps with dependencies), a base prompt,
|
|
// references, and guardrails. It is more structured than a Tool and is the
|
|
// higher-order capability described in DevDocs/design/09 §0.
|
|
//
|
|
// Skill is immutable after construction; its getters return copies of the slice
|
|
// fields, so a *Skill is safe for concurrent use as the sdk.Skill contract
|
|
// requires.
|
|
type Skill struct {
|
|
name string
|
|
description string
|
|
prompt string
|
|
steps []sdk.Step
|
|
refs []sdk.Ref
|
|
guardrails []sdk.Guardrail
|
|
}
|
|
|
|
// compile-time assertion that *Skill satisfies the SDK Skill interface.
|
|
var _ sdk.Skill = (*Skill)(nil)
|
|
|
|
// New builds a Skill. It returns ErrInvalidSkill when name is blank. The step,
|
|
// ref, and guardrail slices are copied, so the caller remains free to mutate
|
|
// them afterwards.
|
|
func New(name, description, prompt string, steps []sdk.Step, refs []sdk.Ref, guardrails []sdk.Guardrail) (*Skill, error) {
|
|
if strings.TrimSpace(name) == "" {
|
|
return nil, fmt.Errorf("skills: new: %w", ErrInvalidSkill)
|
|
}
|
|
return &Skill{
|
|
name: name,
|
|
description: description,
|
|
prompt: prompt,
|
|
steps: cloneSteps(steps),
|
|
refs: cloneRefs(refs),
|
|
guardrails: cloneGuardrails(guardrails),
|
|
}, nil
|
|
}
|
|
|
|
// Name returns the unique skill identifier.
|
|
func (s *Skill) Name() string { return s.name }
|
|
|
|
// Description explains what the Skill accomplishes; the model uses it to select
|
|
// a Skill for a task.
|
|
func (s *Skill) Description() string { return s.description }
|
|
|
|
// Prompt returns the base system prompt for the Skill.
|
|
func (s *Skill) Prompt() string { return s.prompt }
|
|
|
|
// Steps returns a copy of the ordered workflow steps.
|
|
func (s *Skill) Steps() []sdk.Step { return cloneSteps(s.steps) }
|
|
|
|
// Refs returns a copy of the references the Skill may draw on.
|
|
func (s *Skill) Refs() []sdk.Ref { return cloneRefs(s.refs) }
|
|
|
|
// Guardrails returns a copy of the constraints the Skill enforces.
|
|
func (s *Skill) Guardrails() []sdk.Guardrail { return cloneGuardrails(s.guardrails) }
|
|
|
|
func cloneSteps(in []sdk.Step) []sdk.Step {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]sdk.Step, len(in))
|
|
copy(out, in)
|
|
return out
|
|
}
|
|
|
|
func cloneRefs(in []sdk.Ref) []sdk.Ref {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]sdk.Ref, len(in))
|
|
copy(out, in)
|
|
return out
|
|
}
|
|
|
|
func cloneGuardrails(in []sdk.Guardrail) []sdk.Guardrail {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]sdk.Guardrail, len(in))
|
|
copy(out, in)
|
|
return out
|
|
}
|
|
|
|
// sourcedLog returns a source-tagged logger suitable for this package, falling
|
|
// back to a discard logger when l is nil so callers can pass nil safely.
|
|
func sourcedLog(l *slog.Logger) *slog.Logger {
|
|
if l == nil {
|
|
l = slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
}
|
|
return logger.WithSource(l, "capability/skills")
|
|
}
|