Files
Cyrene_v2/internal/capability/skills/runner.go
T

87 lines
2.9 KiB
Go

package skills
import (
"context"
"fmt"
"log/slog"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
)
// Workflow is a Skill expanded into its executable form: the base system prompt
// plus the ordered steps, references, and guardrails, ready to be handed to the
// loop/agent executor (M2). The Runner only assembles workflows; it does not
// execute commands.
type Workflow struct {
Name string
Description string
Prompt string
Steps []sdk.Step
Refs []sdk.Ref
Guardrails []sdk.Guardrail
}
// Runner expands skills into workflows. It is the M5 placeholder for the
// skill-execution wrapper: expansion (the real deliverable) is wired now, while
// Run awaits M12/loop integration.
type Runner struct {
reg *registry.Registry
log *slog.Logger
}
// NewRunner builds a Runner over the given registry, used by ExpandByName to
// look up registered skills. log may be nil.
func NewRunner(reg *registry.Registry, log *slog.Logger) *Runner {
return &Runner{reg: reg, log: sourcedLog(log)}
}
// Expand converts s into a Workflow, copying the step, ref, and guardrail slices
// so the caller owns the result. It returns ErrInvalidSkill when s is nil or has
// a blank name.
func (r *Runner) Expand(ctx context.Context, s sdk.Skill) (Workflow, error) {
if err := ctx.Err(); err != nil {
return Workflow{}, fmt.Errorf("skills: runner: expand: %w", err)
}
if s == nil || strings.TrimSpace(s.Name()) == "" {
return Workflow{}, fmt.Errorf("skills: runner: expand: %w", ErrInvalidSkill)
}
wf := Workflow{
Name: s.Name(),
Description: s.Description(),
Prompt: s.Prompt(),
Steps: cloneSteps(s.Steps()),
Refs: cloneRefs(s.Refs()),
Guardrails: cloneGuardrails(s.Guardrails()),
}
r.log.Info("skill expanded", "skill", wf.Name)
return wf, nil
}
// ExpandByName looks up a skill in the registry by name and expands it. It
// returns ErrNilRegistrar when the runner has no registry, the registry's error
// (registry.ErrNotFound) when the name is absent, and ErrNotFound when the
// registered value is not a Skill.
func (r *Runner) ExpandByName(ctx context.Context, name string) (Workflow, error) {
if r.reg == nil {
return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, ErrNilRegistrar)
}
cap, err := r.reg.Get(name)
if err != nil {
return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, err)
}
skill, ok := cap.Value.(sdk.Skill)
if !ok {
return Workflow{}, fmt.Errorf("skills: runner: expand %q: %w", name, ErrNotFound)
}
return r.Expand(ctx, skill)
}
// Run is a placeholder for executing a registered skill end to end. Execution
// lands in the loop/agent executor (M2) and the full wiring (M12); it always
// returns ErrRunPending.
func (r *Runner) Run(_ context.Context, _ string, _ any) (any, error) {
return nil, ErrRunPending
}