agentskills

package module
v0.18.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 12 Imported by: 0

README

AgentSkills Runtime for Go

lint test

Runtime for AgentSkills in Go with pluggable backend providers for skill lifecycle. Includes a bundled filesystem-backed provider.

Table of contents

Overview

An AgentSkill is a directory or location containing a SKILL.md file with YAML frontmatter.

This library is built around progressive disclosure of skills for LLM sessions:

  • the runtime maintains a catalog of known skills
  • the catalog exposes metadata for discovery
  • a session can activate specific skills
  • only active skills disclose their full SKILL.md body into the prompt

That keeps the base prompt smaller while still allowing the model to discover and load additional skills when needed.

Features

  • Runtime for managing:
    • skill catalog
    • session-scoped active skills
  • Provider abstraction via spec.SkillProvider
  • Reference provider:
    • providers/fsskillprovider
  • Tool integration via llmtools-go:
    • skills-load
    • skills-unload
    • skills-readresource
    • skills-runscript
  • Prompt generation APIs for:
    • available skills
    • active skills
    • combined session prompt output
  • FlexiGPT SKILL.md extensions:
    • insert: instructions | user-message; instructions is the default
    • named string arguments with optional defaults; $name and {{name}} substitution is done only for declared args

Supported SKILL.md extensions

This runtime supports normal Agent Skills-style SKILL.md files and a small extension for prompt-template use cases.

The supported semantic frontmatter fields are:

  • name: required skill name
  • description: required discovery text
  • insert: optional insertion hint, either instructions or user-message
  • arguments: optional list of named string arguments

Missing insert means instructions.

Use insert: instructions for normal skills whose body should be injected into instruction/context material. This is the default.

Use insert: user-message when the skill body is a user-message template. These skills are not advertised in the normal LLM-facing skills prompt and cannot be loaded into a session with skills-load. Hosts should render them with Runtime.RenderSkill and place the rendered text in the user message area.

The runtime preserves the full parsed frontmatter in RawFrontmatter, but it does not assign behavior to other fields. Wrappers can inspect or use those fields if they want compatibility with another client.

Example frontmatter:

name: summarize-text
description: Summarizes pasted text. Use when the user wants a concise summary.
insert: user-message
arguments:
  - name: text
    description: Text to summarize.
  - name: tone
    description: Summary tone.
    default: concise

The body may use $name, {{name}}, or {{ name }} placeholders. Only declared arguments are substituted. Unknown placeholders are left unchanged and reported as warnings. Runtime variables such as ${CLAUDE_SESSION_ID} are not expanded.

Claude Code style dynamic command expansion is not supported. The runtime never runs commands from SKILL.md during import, render, activation, or prompt generation.

Prompt format

Prompt output is structured plain text intended for LLM consumption.

It is deliberately not XML. Instead, it uses explicit start and end delimiters plus labeled fields so the model can interpret the structure clearly without paying the overhead of XML encoding.

Current behavior:

  • available skills are sorted by prompt-visible name, then location
  • available skills include only insert: instructions skills
  • active skills preserve session active order
  • empty sections render as (none)
  • when both sections are requested together, the runtime wraps them in a combined <<<SKILLS_PROMPT>>> ... <<<END_SKILLS_PROMPT>>> block

Typical shapes look like this.

Available skills:

<<<AVAILABLE_SKILLS>>>
name: hello-skill
location: /abs/path/to/hello-skill
description: Says hello
---
name: my-skill
location: /abs/path/to/my-skill
description: My Skill
<<<END_AVAILABLE_SKILLS>>>

Active skills:

<<<ACTIVE_SKILLS>>>
name: hello-skill
body:
# Hello Skill

Use this skill when the user wants a greeting.
<!-- SKILL SEPARATOR -->
name: my-skill
body:
# My Skill

Use this skill when the user wants to deal with me.
<<<END_ACTIVE_SKILLS>>>

Consumer responsibilities

This library does not decide how your chat product stores, displays, or executes skills. Consumers and wrappers should make those decisions explicitly.

  • If RenderSkill returns Insert == instructions, put the rendered text in your instruction/context area.
  • If RenderSkill returns Insert == user-message, put the rendered text in your user message composer/body.
  • If a skill body contains command examples or Claude-style dynamic command text, this runtime leaves the body as text. It does not execute or sanitize it.
  • If you expose skills-runscript, treat it as a separate tool capability governed by your product policy. The filesystem provider keeps script execution disabled by default.
  • If you need tags, enable/disable state, built-in state, source URIs, revisions, or trust policy, keep them in your wrapper/store layer rather than in SKILL.md.
  • If you need compatibility fields from other clients, read RawFrontmatter; this runtime only gives behavior to name, description, insert, and arguments.

Filesystem skill provider

Quickstart

Create a runtime with the filesystem provider:

fsp, _ := fsskillprovider.New() // RunScript disabled by default

rt, _ := agentskills.New(
  agentskills.WithProvider(fsp),
)

Add a skill to the catalog:

rec, err := rt.AddSkill(ctx, spec.SkillDef{
  Type:     "fs",
  Name:     "hello-skill",
  Location: "/abs/path/to/hello-skill",
})
_ = rec
_ = err

Build the available-skills prompt for discovery only:

prompt, _ := rt.SkillsPrompt(ctx, &agentskills.SkillFilter{
  Activity: spec.SkillActivityInactive, // without SessionID, treated as all known/inactive skills
})
_ = prompt

Create a session with initial active skills:

sid, active, err := rt.NewSession(ctx,
  agentskills.WithSessionActiveSkills([]spec.SkillDef{rec.Def}),
)
_ = sid
_ = active
_ = err

Build the active-skills prompt for that session:

activePrompt, _ := rt.SkillsPrompt(ctx, &agentskills.SkillFilter{
  SessionID: sid,
  Activity:  spec.SkillActivityActive,
})
_ = activePrompt

Render a skill for a chat UI:

rendered, err := rt.RenderSkill(ctx, agentskills.RenderSkillParams{
  Def: rec.Def,
  Arguments: map[string]string{
    "text": "Long pasted content...",
    "tone": "concise",
  },
})
_ = rendered
_ = err

If rendered.Insert is spec.SkillInsertUserMessage, place rendered.Text in the user message area. If it is spec.SkillInsertInstructions, place it in instruction/context material.

Build a combined prompt for a session:

prompt, _ := rt.SkillsPrompt(ctx, &agentskills.SkillFilter{
  SessionID: sid,
  Activity:  spec.SkillActivityAny,
})
_ = prompt

Create a tool registry for an LLM session:

reg, _ := rt.NewSessionRegistry(ctx, sid)
_ = reg

The registry includes:

  • skills-load
  • skills-unload
  • skills-readresource
  • skills-runscript
Security notes

The filesystem provider is intentionally thin and relies on llmtools-go for most of the operational sandboxing boundaries.

  • skills-readresource uses llmtools-go/fstool and is scoped to the skill root with:
    • allowedRoots = [skillRoot]
    • workBaseDir = skillRoot
  • skills-runscript uses llmtools-go/exectool and is scoped similarly
  • script execution is disabled by default in the filesystem provider
  • enabling script execution is a host decision and is separate from SKILL.md rendering:
fsskillprovider.WithRunScripts(true)

End-to-end examples

Working end-to-end coverage lives in:

It demonstrates:

  • creating a runtime
  • adding a skill
  • listing and prompting skills
  • creating a session with initial active skills
  • invoking skill tools

Development

  • Formatting follows gofumpt and golines via golangci-lint. Rules are in .golangci.yml.
  • Useful scripts are defined in taskfile.yml; requires Task.
  • Bug reports and PRs are welcome:
    • Keep the public API small and intentional.
    • Avoid leaking provider‑specific types through the public surface; put them under internal/.
    • Please run tests and linters before sending a PR.

License

Copyright (c) 2026 - Present - Pankaj Pipada

All source code in this repository, unless otherwise noted, is licensed under the MIT License. See LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*runtimeOptions) error

func WithLogger

func WithLogger(l *slog.Logger) Option

func WithMaxActivePerSession

func WithMaxActivePerSession(n int) Option

func WithMaxSessions

func WithMaxSessions(maxSessions int) Option

func WithProvider

func WithProvider(p spec.SkillProvider) Option

func WithProvidersByType added in v0.7.0

func WithProvidersByType(m map[string]spec.SkillProvider) Option

func WithSessionTTL

func WithSessionTTL(ttl time.Duration) Option

type RenderSkillParams added in v0.17.0

type RenderSkillParams struct {
	// Def is the exact host/lifecycle skill definition previously added to the runtime.
	Def spec.SkillDef

	// Arguments are named string values used for $name and {{name}} substitution.
	Arguments map[string]string
}

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

func New

func New(opts ...Option) (*Runtime, error)

func (*Runtime) AddSkill

func (r *Runtime) AddSkill(ctx context.Context, def spec.SkillDef) (spec.SkillRecord, error)

AddSkill indexes and registers a skill into the runtime-owned catalog.

IMPORTANT CONTRACT:

  • This is a HOST/LIFECYCLE API.
  • It accepts and returns only the user-provided skill definition (spec.SkillDef).
  • Provider canonicalization/cleanup is internal only and MUST NOT be exposed via this API.

func (*Runtime) CloseSession

func (r *Runtime) CloseSession(ctx context.Context, sid spec.SessionID) error

func (*Runtime) ListSkills

func (r *Runtime) ListSkills(ctx context.Context, filter *SkillListFilter) ([]spec.SkillRecord, error)

ListSkills lists skills for HOST/LIFECYCLE usage.

IMPORTANT CONTRACT:

  • Returns only user-provided skill definitions in SkillRecord.Def.
  • Filters (NamePrefix/LocationPrefix) apply to user-provided Def fields (not LLM-facing names).

func (*Runtime) NewSession

func (r *Runtime) NewSession(ctx context.Context, opts ...SessionOption) (spec.SessionID, []spec.SkillDef, error)

NewSession creates a new session.

IMPORTANT CONTRACT:

  • This is a HOST/LIFECYCLE API.
  • It accepts and returns skill definitions (spec.SkillDef), never LLM handles.

func (*Runtime) NewSessionRegistry

func (r *Runtime) NewSessionRegistry(
	ctx context.Context,
	sid spec.SessionID,
	opts ...llmtools.RegistryOption,
) (*llmtools.Registry, error)

func (*Runtime) ProviderTypes

func (r *Runtime) ProviderTypes() []string

ProviderTypes returns the registered provider type keys (e.g. "fs").

func (*Runtime) RemoveSkill

func (r *Runtime) RemoveSkill(ctx context.Context, def spec.SkillDef) (spec.SkillRecord, error)

RemoveSkill removes a skill from the catalog (and prunes it from all sessions).

IMPORTANT CONTRACT:

  • This is a HOST/LIFECYCLE API.
  • Removal is by the exact user-provided definition that was added.
  • No canonicalization-based matching is performed (to avoid internal cleanup becoming user-facing).

func (*Runtime) RenderSkill added in v0.17.0

func (r *Runtime) RenderSkill(ctx context.Context, p RenderSkillParams) (spec.RenderSkillOut, error)

RenderSkill renders a skill body using the FlexiGPT skill extensions.

This is a HOST/LIFECYCLE API intended for app wrappers and chat UIs. It does not activate the skill in a session and it never executes commands from SKILL.md.

func (*Runtime) SkillsPrompt added in v0.12.0

func (r *Runtime) SkillsPrompt(ctx context.Context, f *SkillFilter) (string, error)

SkillsPrompt builds prompt-facing text for available and/or active skills.

Output rules:

  • If only one section is requested, the return value is exactly that section.
  • If both sections are requested, the output is wrapped in: <<<SKILLS_PROMPT>>> ... <<<END_SKILLS_PROMPT>>>

type SessionOption added in v0.6.0

type SessionOption func(*newSessionOptions) error

SessionOption configures Runtime.NewSession.

func WithSessionActiveSkills added in v0.7.0

func WithSessionActiveSkills(defs []spec.SkillDef) SessionOption

WithSessionActiveSkills sets the initial active skills for the new session (host/lifecycle defs). These are activated during session creation.

func WithSessionMaxActivePerSession added in v0.6.0

func WithSessionMaxActivePerSession(n int) SessionOption

WithSessionMaxActivePerSession overrides the max active skills for this session only. If n <= 0, it is ignored (defaults apply).

type SkillFilter

type SkillFilter struct {
	// Types restricts to provider types (e.g. ["fs"]). Empty means "all".
	Types []string

	// NamePrefix restricts to LLM-visible names with this prefix.
	NamePrefix string

	// LocationPrefix restricts to skills whose (user-provided) location starts with this prefix.
	LocationPrefix string

	// AllowSkills restricts to an explicit allowlist of host/lifecycle skill defs. Empty means "all".
	AllowSkills []spec.SkillDef

	// SessionID optionally scopes active/inactive filtering.
	SessionID spec.SessionID

	// Activity defaults to spec.SkillActivityAny.
	Activity spec.SkillActivity
}

SkillFilter is an optional filter for listing/prompting skills (LLM/prompt-facing).

Semantics:

  • Types/NamePrefix/LocationPrefix/AllowSkills always apply.
  • SessionID (optional) allows filtering/annotating by "active in this session".
  • Activity controls whether to include active, inactive, or both.

Defaults:

  • Activity defaults to "any".
  • If SessionID is empty, no active skills exist.

IMPORTANT CONTRACT:

  • NamePrefix matches the LLM-visible computed handle name (not the host skill def name).
  • LocationPrefix matches the user-provided location (not provider-canonicalized).

type SkillListFilter added in v0.7.0

type SkillListFilter struct {
	Types          []string
	NamePrefix     string
	LocationPrefix string
	AllowSkills    []spec.SkillDef

	Inserts []spec.SkillInsert

	SessionID spec.SessionID
	Activity  spec.SkillActivity
}

SkillListFilter is a HOST/LIFECYCLE listing filter. Unlike SkillFilter (prompt), NamePrefix applies to the user-provided skill name (def.name), not the LLM-visible computed handle name.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL