Files
LocalAI/core/services/studio/skills
Ettore Di Giacinto 24c837e453 feat(studio): grant built-in capabilities to a saved agent
A saved agent now has a Builtins list: the capabilities its owner gave
it (memory.write, profile.write, skills.write, pages.write,
dashboard.write, triggers.propose, triggers.run, talkback.channels,
talkback.new_channel, files.upload, act.files, act.addons, act.send,
act.spend and the opt-in group saved_agents). The list is empty by
default, and nothing turns a grant on for a template, an import or a
write that omits the field.

A chat of the agent holds the grants the user may use at the turn
(agent scope). A tool exists when the grant is held, the chat did not
switch its group off and the run can hold rights: a sub-agent or a run
that started by itself never does. propose_change and revert_change
now exist only for a chat that holds the grant of at least one kind,
and the schema lists only those kinds.

propose_change gains the kinds skill, page and dashboard. A skill is a
SKILL.md for the personal or the agent layer, and a skill with
locked: true is refused, also as a shadow of a locked personal skill.
A page is a set of files checked against the Pages rules and written
as one change set. The dashboard is a home.json checked against its
schema. All three are re-checked when the owner confirms or edits.

GET /api/chats/agents/builtin-grants lists each grant with its title,
description and sensitive flag. The agent record, the write body and
the export file carry builtins; an old file imports as before and an
unknown name is dropped in an import and refused in a write.

Assisted-by: Claude:claude-sonnet-5-5 go gofmt swag
2026-11-03 14:56:42 +00:00
..

skills

skills finds the skills that apply to a chat and renders the index that goes into the prompt. This package holds the mechanism. The chats service holds the data: it decides which workspace folders are the roots of the layers, which skills a chat switched off, and how the user sees them.

A skill is a folder with a SKILL.md file. The frontmatter has a name and a description. The body is a procedure that the agent loads by the skill's name when a task matches. Other files in the folder (references, scripts) are read by the agent with its normal file tools. The package never reads them.

roots := []skills.Root{
	{Layer: skills.LayerBuiltin, Path: "/builtin-skills", Guest: "/workspace/shared-builtin-skills"},
	{Layer: skills.LayerShared, Path: "/skills", Guest: "/workspace/shared-skills"},
	{Layer: skills.LayerPersonal, Path: "/users/7/.skills", Guest: "/workspace/home/.skills"},
	{Layer: skills.LayerFolder, Path: "/users/7", Guest: "/workspace/home"},
}
list, notes, err := skills.Resolve(ctx, store, actor, roots, skills.Options{Off: chatSwitches})

// In the ChatTools hook of the session:
return session.TurnTools{
	Prompt:     skills.Index(list),
	Note:       strings.Join(notes, "; "),
	RefreshKey: hashOf(list), // changes when a SKILL.md is new or changed
}, nil

The sandbox of a chat sees only the files that existed when it was made. RefreshKey tells session when to refresh it: a key that differs from the key of the turn before refreshes the sandbox (its processes end). A switch that only removes lines from the index needs no refresh, so it must not change the key.

session does not import skills. The product joins them.

Loading a skill

The index names no path. Its first line tells the model to call the product's load tool (LoadTool, load_skill) with a skill's name. Usable returns the skills that the index lists, in its order, so the tool and the index never disagree: the product resolves once, Find picks the skill of a name and Body returns the text of its SKILL.md after the frontmatter. For a name that Find does not match, Suggest returns up to three near names: the same name in other case, the name with a <prefix>- or <layer>: part added or taken away, or a name one or two edits away. Names lists every usable name for the error.

Layers

Layer Root Path Meaning
LayerBuiltin the layer folder skills that ship with the product
LayerAddon the folder of one add-on skills an add-on ships; one root per add-on, with Addon and AddonTitle set
LayerShared the layer folder skills an administrator gives to everyone
LayerPersonal the layer folder the skills of one user
LayerFolder a folder to walk every .skills folder below it

Each layer folder holds one subfolder per skill. For LayerFolder, Resolve walks the folder breadth first, at most 6 levels deep and at most 2,000 folders per call, and uses each .skills folder it finds. It does not enter a .skills folder, a layer folder that has its own root, or a path in Root.Skip (a product skips a folder that holds many chat uploads, so that it does not use the budget; a .skills folder inside it is not found). Options.Logger gets the number of folders listed, at debug level. A walk that a limit cuts adds the note some folders were not searched for skills.

Resolve reads through the actor. A folder that the actor cannot read gives no skill and no error. It returns an error only when the store fails.

Precedence

The nearest layer wins: folder, personal, shared, built-in. Between two folder skills the deeper folder wins; at equal depth the path that sorts first wins. A skill that loses to one with the same name has Shadowed set to the layer of the winner and is not in the index. Options.Off runs before this rule, so if it turns off only a nearer skill, the one below it is uncovered. The chats turn off the winner for a plain name and one layer for layer:name (skillsvc.go). A skill that was switched off has Off set.

Limits

Item Limit
SKILL.md size 64 KiB (MaxFileSize)
name ^[a-z0-9][a-z0-9-]{0,63}$
description not empty, at most 1,024 bytes after Clean (MaxDescription)
files read per Resolve 200, nearest layers first; the note is some skills were not read
index 100 skills and 16 KiB; a last line says N more skills not listed

Clean removes control characters and invisible format characters (Unicode category Cf, bidi overrides included) and folds runs of white space to one space. A skill that breaks a limit is returned with Invalid set to the reason. It is not in the index.

Frontmatter

Parse reads a small subset of YAML: key: value lines, single or double quoted values, and block scalars (|, >) for any key. Unknown keys, such as allowed-tools or license, are ignored.

Safety

A skill is text that the model follows. A document that the agent reads can try to make it write a harmful skill. The package gives the product what it needs to show the risk (Hash, Layer, Path); marking new and changed skills, and undo of the turn that wrote one, are the product's job.