Runtime for AgentSkills in Go with pluggable backend providers for skill lifecycle. Includes a bundled filesystem-backed provider.
- Overview
- Features
- Supported SKILL.md extensions
- Prompt format
- Consumer responsibilities
- Filesystem skill provider
- End-to-end examples
- Development
- License
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.mdbody into the prompt
That keeps the base prompt smaller while still allowing the model to discover and load additional skills when needed.
- Runtime for managing:
- skill catalog
- session-scoped active skills
- Provider abstraction via
spec.SkillProvider - Reference provider:
providers/fsskillprovider
- Tool integration via
llmtools-go:skills-loadskills-unloadskills-readresourceskills-runscript
- Prompt generation APIs for:
- available skills
- active skills
- combined session prompt output
- FlexiGPT SKILL.md extensions:
insert: instructions | user-message;instructionsis the default- named string
argumentswith optional defaults;$nameand{{name}}substitution is done only for declared args
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 namedescription: required discovery textinsert: optional insertion hint, eitherinstructionsoruser-messagearguments: 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: conciseThe 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 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, thenlocation - available skills include only
insert: instructionsskills - 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>>>
This library does not decide how your chat product stores, displays, or executes skills. Consumers and wrappers should make those decisions explicitly.
- If
RenderSkillreturnsInsert == instructions, put the rendered text in your instruction/context area. - If
RenderSkillreturnsInsert == 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 toname,description,insert, andarguments.
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
_ = errBuild the available-skills prompt for discovery only:
prompt, _ := rt.SkillsPrompt(ctx, &agentskills.SkillFilter{
Activity: spec.SkillActivityInactive, // without SessionID, treated as all known/inactive skills
})
_ = promptCreate a session with initial active skills:
sid, active, err := rt.NewSession(ctx,
agentskills.WithSessionActiveSkills([]spec.SkillDef{rec.Def}),
)
_ = sid
_ = active
_ = errBuild the active-skills prompt for that session:
activePrompt, _ := rt.SkillsPrompt(ctx, &agentskills.SkillFilter{
SessionID: sid,
Activity: spec.SkillActivityActive,
})
_ = activePromptRender 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
_ = errIf 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,
})
_ = promptCreate a tool registry for an LLM session:
reg, _ := rt.NewSessionRegistry(ctx, sid)
_ = regThe registry includes:
skills-loadskills-unloadskills-readresourceskills-runscript
The filesystem provider is intentionally thin and relies on llmtools-go for most of the operational sandboxing boundaries.
skills-readresourceusesllmtools-go/fstooland is scoped to the skill root with:allowedRoots = [skillRoot]workBaseDir = skillRoot
skills-runscriptusesllmtools-go/exectooland 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)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
- Formatting follows
gofumptandgolinesviagolangci-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.
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.