Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions docs/features/skills/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,80 @@ agents:

A name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored.

## GitHub Skill Sources

Load skills directly from a public GitHub repository, without installing Git or
an external skills CLI:

```yaml
agents:
root:
model: openai/gpt-4o-mini
skills:
- local
- https://github.com/docker/skills
toolsets:
- type: filesystem
- type: shell
```

Repository URLs use the default branch. To select a branch, tag, commit SHA, or
subdirectory, use a GitHub tree URL or query parameters:

```yaml
skills:
- https://github.com/docker/skills/tree/main/skills
# Alternative examples (use one source, not all of them):
# - https://github.com/docker/skills?ref=v0.3.0
# - https://github.com/docker/skills?ref=<full-40-character-commit-sha>
# - https://github.com/owner/repo?ref=feature%2Fskills&path=skills
```

A tree URL treats the first segment after `/tree/` as the revision and the rest
as a directory. Use `?ref=...&path=...` for branch or tag names containing `/`, or
URL-escape the slash as `%2F`. Short commit hashes are resolved through the API;
use a full 40-character SHA for an immutable pin.

Without a selected directory, discovery checks `skills/**/SKILL.md`, then
`.agents/skills/**/SKILL.md`, then a root `SKILL.md`, using the first layout
containing skills. An explicit directory is searched recursively without
fallback. Complete skill directories, including supporting files, are cached.

### Authentication and caching

Only **public repositories on github.com** are supported. The optional
`GITHUB_TOKEN` comes from the configured environment provider, including OS
environment variables, env files, and secrets. It raises GitHub API rate limits;
it is not required or prompted for, and does not enable private repositories.
The token is sent only to `api.github.com`, never to the archive host.

Default branches, named branches, and tags are resolved to a commit SHA and
cached for **five minutes**. Downloaded snapshots are immutable and cached by
repository, SHA, and directory. A warm load makes no network requests; after
five minutes mutable revisions are checked again. A full-SHA source needs no
further network requests once cached. All files come from the same commit.

Archives are fetched from `codeload.github.com`. Downloads are bounded to 32 MiB
compressed, 128 MiB expanded, and 4,096 selected files, with a 1 MiB limit per
skill file and 32 MiB total selected content. Failed downloads never publish a
partial snapshot; source failures appear as load-time warnings. Expired mutable
revisions are not silently reused on network errors.

### Trust and sandbox behavior

Cached GitHub skills remain **remote**: embedded `` !`command` `` expressions are
not expanded. Remote frontmatter `model` and `toolsets` overrides are ignored.
Fork skills inherit the parent's model and tools, with `allowed-tools` able to
restrict inherited tools. Symlinks and other non-regular entries inside selected
skill directories are rejected; Git submodules are not fetched.

In sandbox mode, GitHub sources are fetched inside the VM rather than staged as
local skills. Permit network access to `api.github.com` and `codeload.github.com`
and make any optional token available through the sandbox's environment provider.

See [`examples/skills_github.yaml`](https://github.com/docker/docker-agent/blob/main/examples/skills_github.yaml)
for a complete configuration.

## Inline Skills

Instead of (or alongside) loading skills from files and URLs, you can define skills directly in the agent config. An inline skill is a mapping item in the `skills` list, freely mixed with the string items above:
Expand Down
19 changes: 19 additions & 0 deletions examples/skills_github.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!yaml
# Public GitHub skill sources. GITHUB_TOKEN is optional and uses the configured
# environment provider. Branch/tag resolutions are cached for five minutes;
# immutable commit snapshots are reused without downloading again.
agents:
root:
model: openai/gpt-4o-mini
description: Assistant with Docker skills from GitHub.
instruction: Use the available skills when a task matches their description.
skills:
- local
- https://github.com/docker/skills
# To pin a revision or select a directory instead:
# - https://github.com/docker/skills/tree/main/skills
# - https://github.com/docker/skills?ref=v0.3.0
# - https://github.com/owner/repo?ref=feature%2Fskills&path=skills
toolsets:
- type: filesystem
- type: shell
3 changes: 2 additions & 1 deletion pkg/config/latest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,8 @@ type InlineSkill struct {
// inline definitions enables skills without loading local or remote ones.
//
// The special source "local" loads skills from the filesystem (standard locations).
// HTTP/HTTPS URLs load skills from remote servers per the well-known skills discovery spec.
// HTTPS GitHub repository URLs discover skills from commit snapshots. Other
// HTTP/HTTPS URLs use the well-known skills discovery spec.
type SkillsConfig struct {
// Sources lists where to load skills from: "local" and/or HTTP/HTTPS URLs.
Sources []string
Expand Down
8 changes: 3 additions & 5 deletions pkg/skills/frontmatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@ func parseFrontmatter(content string) (Skill, bool) {
content = strings.ReplaceAll(content, "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")

rest, found := strings.CutPrefix(content, "---")
rest, found := strings.CutPrefix(content, "---\n")
if !found {
return Skill{}, false
}

endIndex := strings.Index(rest, "\n---")
if endIndex == -1 {
block, _, found := strings.Cut("\n"+rest+"\n", "\n---\n")
if !found {
return Skill{}, false
}

block := content[4 : endIndex+3]

var skill Skill
var currentKey string // tracks multi-line keys like "metadata" or "allowed-tools"

Expand Down
13 changes: 13 additions & 0 deletions pkg/skills/frontmatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,16 @@ func TestSplitKeyValue(t *testing.T) {
})
}
}

func TestParseFrontmatterMalformed(t *testing.T) {
t.Parallel()
for _, content := range []string{"", "---", "---\n---garbage", "---\nname: test\n---garbage"} {
t.Run(content, func(t *testing.T) {
t.Parallel()
_, ok := parseFrontmatter(content)
assert.False(t, ok)
})
}
_, ok := parseFrontmatter("---\n---")
assert.True(t, ok)
}
Loading
Loading