From 29598df81451e5d22c1022bcf279af28e42e59a8 Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Sat, 4 Jul 2026 20:56:05 -0400 Subject: [PATCH] feat(skillpack): lazy BundleStager for bundled files in skill_use Replace Activate's stagedDir string with a BundleStager callback invoked lazily inside skill_use: when the model loads a pack with bundled files, the host stages them (mort: into run-scoped file storage) and the returned note is appended to the body so the model knows how to reach them. A nil stager (or a stager error) degrades gracefully to just listing the file names. Co-Authored-By: Claude Opus 4.8 --- skillpack/activation.go | 48 ++++++++++++++++++++++++------------ skillpack/activation_test.go | 23 ++++++++++++----- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/skillpack/activation.go b/skillpack/activation.go index e22699a..c1b4886 100644 --- a/skillpack/activation.go +++ b/skillpack/activation.go @@ -66,17 +66,26 @@ type skillUseArgs struct { Name string `json:"name" description:"the exact name of the skill to load, from the Available skills list"` } +// BundleStager makes a pack's bundled files available to the current run and +// returns a short note the model can act on (e.g. where the files are and how to +// reference them). It is called LAZILY, inside the skill_use tool, so a pack's +// files are staged only when the model actually loads that pack — not for every +// subscribed pack on every run. A host implements it over its own file plumbing +// (mort saves the files to run-scoped storage and returns their file_ids). nil = +// no staging: skill_use just lists the bundled file names. +type BundleStager func(ctx context.Context, p *Pack) (string, error) + // Activate turns a set of resolved packs into a majordomo agent.Skill: its // Instructions are the Catalog, and it contributes a single skill_use tool that // returns a named pack's full body (progressive disclosure). Attach the result // to an agent with agent.WithSkill. Returns nil when there are no packs, which // agent.WithSkill tolerates (a nil skill contributes nothing). // -// stagedDir, if non-empty, is the directory a host has staged the packs' bundled -// files into (see Stage); skill_use appends the concrete path so the model knows -// where to read scripts/references with its file tools. Leave it empty when the -// host has no staging. -func Activate(packs []*Pack, stagedDir string) mdagent.Skill { +// stager, if non-nil, is invoked when skill_use loads a pack with bundled files; +// its returned note is appended to the body so the model knows how to reach the +// staged scripts/references. A stager error degrades gracefully (the +// instructions still return, with a note that the files are unavailable). +func Activate(packs []*Pack, stager BundleStager) mdagent.Skill { byName := make(map[string]*Pack, len(packs)) for _, p := range packs { if p != nil && p.Manifest != nil { @@ -88,13 +97,23 @@ func Activate(packs []*Pack, stagedDir string) mdagent.Skill { } tool := llm.DefineTool("skill_use", - "Load the full instructions for a skill by name before doing a task it covers. Returns the skill's instructions and a list of any bundled files.", - func(_ context.Context, args skillUseArgs) (any, error) { + "Load the full instructions for a skill by name before doing a task it covers. Returns the skill's instructions and, if it has bundled files, how to access them.", + func(ctx context.Context, args skillUseArgs) (any, error) { p, ok := byName[strings.TrimSpace(args.Name)] if !ok { return fmt.Sprintf("No skill named %q. Use one of the names from the Available skills list.", args.Name), nil } - return renderPackBody(p, stagedDir), nil + body := renderPackBody(p) + if stager != nil && len(p.Bundled) > 0 { + note, err := stager(ctx, p) + switch { + case err != nil: + body += "\n\n(bundled files could not be staged: " + err.Error() + ")" + case note != "": + body += "\n\n" + note + } + } + return body, nil }) tb := llm.NewToolbox("skillpack", tool) @@ -104,20 +123,17 @@ func Activate(packs []*Pack, stagedDir string) mdagent.Skill { ) } -// renderPackBody is what skill_use returns: the pack's instructions plus a -// pointer to its bundled files (with the staged path when known). -func renderPackBody(p *Pack, stagedDir string) string { +// renderPackBody is the base skill_use payload: the pack's instructions plus, if +// it has any, a list of its bundled file names. A stager (see Activate) appends +// the concrete access note. +func renderPackBody(p *Pack) string { if p == nil || p.Manifest == nil { return "Error: invalid skill pack." } var b strings.Builder fmt.Fprintf(&b, "# Skill: %s\n\n%s\n", p.Manifest.Name, p.Manifest.Body) if len(p.Bundled) > 0 { - b.WriteString("\nBundled files") - if stagedDir != "" { - fmt.Fprintf(&b, " (under %s)", strings.TrimRight(stagedDir, "/")+"/"+p.Manifest.Name) - } - b.WriteString(":\n") + b.WriteString("\nBundled files:\n") for _, f := range p.Bundled { fmt.Fprintf(&b, "- %s\n", f) } diff --git a/skillpack/activation_test.go b/skillpack/activation_test.go index 0849a81..7fafedc 100644 --- a/skillpack/activation_test.go +++ b/skillpack/activation_test.go @@ -44,7 +44,12 @@ func TestActivate_SkillUseTool(t *testing.T) { packs := []*Pack{ mustPack(t, "pdf", "Use pdfplumber.", map[string]string{"scripts/x.py": "print()"}), } - sk := Activate(packs, "/stage") + staged := 0 + stager := func(_ context.Context, p *Pack) (string, error) { + staged++ + return "staged " + p.Manifest.Name + " (file_id=abc)", nil + } + sk := Activate(packs, stager) if sk == nil { t.Fatal("expected a non-nil skill") } @@ -56,6 +61,9 @@ func TestActivate_SkillUseTool(t *testing.T) { if !ok { t.Fatal("skill_use tool missing from toolbox") } + if staged != 0 { + t.Error("stager must be lazy — not called until skill_use runs") + } // load an existing pack out, err := tool.Handler(ctx, json.RawMessage(`{"name":"pdf"}`)) @@ -66,8 +74,11 @@ func TestActivate_SkillUseTool(t *testing.T) { if !strings.Contains(body, "Use pdfplumber.") { t.Errorf("skill_use body missing instructions: %q", body) } - if !strings.Contains(body, "scripts/x.py") || !strings.Contains(body, "/stage/pdf") { - t.Errorf("skill_use should list bundled files under the staged dir: %q", body) + if !strings.Contains(body, "scripts/x.py") { + t.Errorf("skill_use should list bundled files: %q", body) + } + if staged != 1 || !strings.Contains(body, "file_id=abc") { + t.Errorf("stager should run on load and its note append to the body: staged=%d body=%q", staged, body) } // unknown pack returns guidance, not an error @@ -81,7 +92,7 @@ func TestActivate_SkillUseTool(t *testing.T) { } func TestActivate_Empty(t *testing.T) { - if Activate(nil, "") != nil { + if Activate(nil, nil) != nil { t.Error("no packs should activate to a nil skill") } } @@ -92,7 +103,7 @@ func TestNilPackElementsAreSafe(t *testing.T) { if got := Catalog(packs); !strings.Contains(got, "real") { t.Errorf("catalog should include the valid pack and skip nils: %q", got) } - sk := Activate(packs, "") + sk := Activate(packs, nil) if sk == nil { t.Fatal("a valid pack among nils should still activate") } @@ -100,7 +111,7 @@ func TestNilPackElementsAreSafe(t *testing.T) { t.Error("skill_use missing") } // All-nil activates to nothing rather than panicking. - if Activate([]*Pack{nil, {Manifest: nil}}, "") != nil { + if Activate([]*Pack{nil, {Manifest: nil}}, nil) != nil { t.Error("only-nil packs should activate to nil") } }