feat: first-class skill packs on agents + ship gifsmith builtin
executus CI / test (push) Successful in 3m21s

Lifts the 'an agent uses a SKILL.md pack' concept out of a host and into the
harness:
- run.Ports.SkillPacks (SkillPackActivator) — nil-safe port; the executor folds
  a loaded agent's pack catalog into the system prompt and adds a skill_use
  loader tool to the toolbox (uses the existing ra.SystemPrompt + toolbox seams)
- run.RunnableAgent.SkillPacks + persona.Agent.SkillPacks (+ skill_packs YAML,
  extends-inherit, ToRunnable) — the Agent noun is now pack-aware
- skillpack.Activator — the battery's default port impl (resolve names → packs →
  catalog + skill_use), with a per-run BundleStager factory the host plumbs;
  satisfies the port structurally (no import of run)
- agentbuiltins: ships gifsmith, a portable focused GIF/MP4 render agent that
  uses the gif pack — references tool/tier/pack NAMES only, no host coupling

A host now wires run.Ports.SkillPacks instead of carrying its own activation
glue. Tests: Activator resolution + gifsmith loads through persona→RunnableAgent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 01:05:58 -04:00
parent d5ea9b6e5e
commit 8ecdadf8b8
11 changed files with 279 additions and 0 deletions
+5
View File
@@ -44,6 +44,11 @@ type RunnableAgent struct {
LowLevelTools []string
SkillPalette []string
SubAgentPalette []string
// SkillPacks names SKILL.md skill-pack subscriptions activated for the run
// via Ports.SkillPacks: each pack's name+description joins a catalog folded
// into the system prompt, and a skill_use tool loads a pack's body on demand
// (progressive disclosure). nil Ports.SkillPacks => inert.
SkillPacks []string
// Phases optionally model a multi-step pipeline (each phase its own prompt
// + tier + tools). An empty slice is a single-phase run — the common case.
+26
View File
@@ -275,6 +275,32 @@ func (e *Executor) Run(ctx context.Context, ra RunnableAgent, inv tool.Invocatio
postRun = st.PostRun
}
// Skill packs: resolve the agent's subscribed packs into a catalog (folded
// into the system prompt) + a skill_use loader tool added to the toolbox.
// nil-safe; activation failures are non-fatal — the run proceeds without
// packs rather than dying on a fetch/cache miss.
if len(ra.SkillPacks) > 0 && e.cfg.Ports.SkillPacks != nil {
instr, packTools, aerr := e.cfg.Ports.SkillPacks.ActivateSkillPacks(ctx, ra.SkillPacks, inv.RunID, ra.ID)
if aerr != nil {
slog.Warn("run: skill-pack activation failed; continuing without packs", "run_id", inv.RunID, "error", aerr)
} else {
for _, t := range packTools {
if err := toolbox.Add(t); err != nil {
res.Err = fmt.Errorf("add skill-pack tool: %w", err)
e.finishAudit(ctx, rec, "error", res, started, res.Err)
return res
}
}
if instr != "" {
if ra.SystemPrompt != "" {
ra.SystemPrompt += "\n\n" + instr
} else {
ra.SystemPrompt = instr
}
}
}
}
// Run context: detached from the caller's deadline so a lane/queue wait doesn't
// eat the run budget (mort's V10 lesson). Caller cancellation still propagates
// via MergeCancellation. Created BEFORE the step observer so the observer
+16
View File
@@ -49,6 +49,22 @@ type Ports struct {
// are silently ignored (the run still proceeds, text-only). The bytes are
// never inlined into the model context — the LLM can't read raw audio/binary.
InputFiles InputFileStager
// SkillPacks activates a RunnableAgent.SkillPacks (SKILL.md subscriptions)
// for the run: it folds a catalog into the system prompt and adds a skill_use
// loader tool. nil = SkillPacks are inert. The executus/skillpack battery
// ships a default impl (skillpack.Activator).
SkillPacks SkillPackActivator
}
// SkillPackActivator resolves an agent's subscribed skill-pack names for a run
// into system-prompt instructions (a catalog of what's available on demand) and
// the tools that back them (a single skill_use loader). It receives the run +
// subject ids so the impl can scope any per-run file staging. It returns "" +
// nil when nothing resolves; activation errors are non-fatal to the run. Defined
// here (the consumer) so the battery satisfies it structurally without importing
// run — the same inversion as the other ports.
type SkillPackActivator interface {
ActivateSkillPacks(ctx context.Context, names []string, runID, subjectID string) (instructions string, tools []llm.Tool, err error)
}
// InputFileStager persists a single non-image input attachment into a host file