feat(skills): --no-skill flag to disable all skills (including built-ins)
Mirrors --no-ext: one flag that skips skill discovery + the
`skill` tool registration entirely for one run. Useful for
clean-room runs where you want zero extra context biasing the
model.
Defaults are unchanged:
- user-installed skills under $ZOT_HOME/skills/, .zot/skills/,
.claude/skills/, .agents/skills/ load normally
- built-in skills compiled into the binary (currently the
write-zot-extension authoring guide) load normally
- both groups appear in the system-prompt manifest and are
loadable via the `skill` tool
With --no-skill (or --no-skills):
- skills.Discover() is not called
- the `skill` tool is not registered
- no "Available skills" addendum is appended to the system
prompt
- /skills picker is empty
Wired into both interactive and rpc modes via the existing
SkillSnapshot wiring (returns nil when args.NoSkill is set, so
the picker also stays empty).
End-to-end verified live:
default : tool list includes "skill"
--no-skill : tool list is read, write, edit, bash only
(no skill, no extra context manifest)- commit
- e9ffc744428f259d74b90d9c5c3c0dee87b41699
- author
- patriceckhart · · 2026-04-19 13:58 UTC
- parent
- 2cffe04
- signature
- unsigned
- refs
- tag: v0.0.31
3 files · +37 -12
M internal/agent/args.go +12 -0
| @@ -49,6 +49,13 @@ type Args struct { | |||
| 49 | 49 | // can run "with only this one extension" via --no-ext --ext PATH. | |
| 50 | 50 | NoExt bool | |
| 51 | 51 | ||
| 52 | + | // NoSkill disables ALL skill discovery for this run, including | |
| 53 | + | // the built-in skills compiled into the binary. The system | |
| 54 | + | // prompt loses its "Available skills" manifest and the `skill` | |
| 55 | + | // tool isn't registered. Useful for running zot without any | |
| 56 | + | // extra context biasing the model. | |
| 57 | + | NoSkill bool | |
| 58 | + | ||
| 52 | 59 | ListModels bool | |
| 53 | 60 | Help bool | |
| 54 | 61 | Version bool | |
| @@ -142,6 +149,8 @@ func ParseArgs(in []string) (Args, error) { | |||
| 142 | 149 | a.Exts = append(a.Exts, v) | |
| 143 | 150 | case "--no-ext", "--no-extensions": | |
| 144 | 151 | a.NoExt = true | |
| 152 | + | case "--no-skill", "--no-skills": | |
| 153 | + | a.NoSkill = true | |
| 145 | 154 | case "--reasoning": | |
| 146 | 155 | v, err := want(&i, arg) | |
| 147 | 156 | if err != nil { | |
| @@ -247,6 +256,9 @@ flags: | |||
| 247 | 256 | --no-ext skip extension discovery for this run | |
| 248 | 257 | (--ext PATH still works on top, so | |
| 249 | 258 | --no-ext --ext ./x runs only x) | |
| 259 | + | --no-skill skip skill discovery for this run, | |
| 260 | + | including built-in skills (no skill | |
| 261 | + | tool, no Available skills manifest) | |
| 250 | 262 | ||
| 251 | 263 | --max-steps N agent loop iteration cap (default 50) | |
| 252 | 264 | --list-models print known models and exit | |
| @@ -49,6 +49,13 @@ type Args struct { | |||||
| 49 | // can run "with only this one extension" via --no-ext --ext PATH. | 49 | // can run "with only this one extension" via --no-ext --ext PATH. | ||
| 50 | NoExt bool | 50 | NoExt bool | ||
| 51 | 51 | ||||
| 52 | + | // NoSkill disables ALL skill discovery for this run, including | |||
| 53 | + | // the built-in skills compiled into the binary. The system | |||
| 54 | + | // prompt loses its "Available skills" manifest and the `skill` | |||
| 55 | + | // tool isn't registered. Useful for running zot without any | |||
| 56 | + | // extra context biasing the model. | |||
| 57 | + | NoSkill bool | |||
| 58 | + | ||||
| 52 | ListModels bool | 59 | ListModels bool | ||
| 53 | Help bool | 60 | Help bool | ||
| 54 | Version bool | 61 | Version bool | ||
| @@ -142,6 +149,8 @@ func ParseArgs(in []string) (Args, error) { | |||||
| 142 | a.Exts = append(a.Exts, v) | 149 | a.Exts = append(a.Exts, v) | ||
| 143 | case "--no-ext", "--no-extensions": | 150 | case "--no-ext", "--no-extensions": | ||
| 144 | a.NoExt = true | 151 | a.NoExt = true | ||
| 152 | + | case "--no-skill", "--no-skills": | |||
| 153 | + | a.NoSkill = true | |||
| 145 | case "--reasoning": | 154 | case "--reasoning": | ||
| 146 | v, err := want(&i, arg) | 155 | v, err := want(&i, arg) | ||
| 147 | if err != nil { | 156 | if err != nil { | ||
| @@ -247,6 +256,9 @@ flags: | |||||
| 247 | --no-ext skip extension discovery for this run | 256 | --no-ext skip extension discovery for this run | ||
| 248 | (--ext PATH still works on top, so | 257 | (--ext PATH still works on top, so | ||
| 249 | --no-ext --ext ./x runs only x) | 258 | --no-ext --ext ./x runs only x) | ||
| 259 | + | --no-skill skip skill discovery for this run, | |||
| 260 | + | including built-in skills (no skill | |||
| 261 | + | tool, no Available skills manifest) | |||
| 250 | 262 | ||||
| 251 | --max-steps N agent loop iteration cap (default 50) | 263 | --max-steps N agent loop iteration cap (default 50) | ||
| 252 | --list-models print known models and exit | 264 | --list-models print known models and exit | ||
M internal/agent/build.go +21 -12
| @@ -179,18 +179,27 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||
| 179 | 179 | sandbox := tools.NewSandbox(args.CWD) | |
| 180 | 180 | reg := buildToolRegistry(args, args.CWD, sandbox) | |
| 181 | 181 | ||
| 182 | - | // Skill discovery: scan project + global locations and, if any | |
| 183 | - | // SKILL.md files were found, register the on-demand `skill` | |
| 184 | - | // loader tool plus a system-prompt manifest so the model knows | |
| 185 | - | // what's available. | |
| 186 | - | homeDir, _ := os.UserHomeDir() | |
| 187 | - | discovered, _ := skills.Discover(ZotHome(), args.CWD, homeDir) | |
| 188 | - | var skillTool *skills.Tool | |
| 189 | - | var skillAddendum string | |
| 190 | - | if len(discovered) > 0 { | |
| 191 | - | skillTool = skills.NewTool(discovered) | |
| 192 | - | reg[skillTool.Name()] = skillTool | |
| 193 | - | skillAddendum = skills.SystemPromptAddendum(discovered) | |
| 182 | + | // Skill discovery: scan project + global locations + built-in | |
| 183 | + | // skills shipped with the binary. If any are found, register | |
| 184 | + | // the on-demand `skill` loader tool plus a system-prompt | |
| 185 | + | // manifest so the model knows what's available. | |
| 186 | + | // | |
| 187 | + | // --no-skill bypasses the entire mechanism: no manifest in the | |
| 188 | + | // system prompt, no `skill` tool in the registry. Useful for a | |
| 189 | + | // clean-room run with zero extra context biasing the model. | |
| 190 | + | var ( | |
| 191 | + | discovered []*skills.Skill | |
| 192 | + | skillTool *skills.Tool | |
| 193 | + | skillAddendum string | |
| 194 | + | ) | |
| 195 | + | if !args.NoSkill { | |
| 196 | + | homeDir, _ := os.UserHomeDir() | |
| 197 | + | discovered, _ = skills.Discover(ZotHome(), args.CWD, homeDir) | |
| 198 | + | if len(discovered) > 0 { | |
| 199 | + | skillTool = skills.NewTool(discovered) | |
| 200 | + | reg[skillTool.Name()] = skillTool | |
| 201 | + | skillAddendum = skills.SystemPromptAddendum(discovered) | |
| 202 | + | } | |
| 194 | 203 | } | |
| 195 | 204 | _ = skillTool | |
| 196 | 205 | ||
| @@ -179,18 +179,27 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||||
| 179 | sandbox := tools.NewSandbox(args.CWD) | 179 | sandbox := tools.NewSandbox(args.CWD) | ||
| 180 | reg := buildToolRegistry(args, args.CWD, sandbox) | 180 | reg := buildToolRegistry(args, args.CWD, sandbox) | ||
| 181 | 181 | ||||
| 182 | - | // Skill discovery: scan project + global locations and, if any | 182 | + | // Skill discovery: scan project + global locations + built-in |
| 183 | - | // SKILL.md files were found, register the on-demand `skill` | 183 | + | // skills shipped with the binary. If any are found, register |
| 184 | - | // loader tool plus a system-prompt manifest so the model knows | 184 | + | // the on-demand `skill` loader tool plus a system-prompt |
| 185 | - | // what's available. | 185 | + | // manifest so the model knows what's available. |
| 186 | - | homeDir, _ := os.UserHomeDir() | 186 | + | // |
| 187 | - | discovered, _ := skills.Discover(ZotHome(), args.CWD, homeDir) | 187 | + | // --no-skill bypasses the entire mechanism: no manifest in the |
| 188 | - | var skillTool *skills.Tool | 188 | + | // system prompt, no `skill` tool in the registry. Useful for a |
| 189 | - | var skillAddendum string | 189 | + | // clean-room run with zero extra context biasing the model. |
| 190 | - | if len(discovered) > 0 { | 190 | + | var ( |
| 191 | - | skillTool = skills.NewTool(discovered) | 191 | + | discovered []*skills.Skill |
| 192 | - | reg[skillTool.Name()] = skillTool | 192 | + | skillTool *skills.Tool |
| 193 | - | skillAddendum = skills.SystemPromptAddendum(discovered) | 193 | + | skillAddendum string |
| 194 | + | ) | |||
| 195 | + | if !args.NoSkill { | |||
| 196 | + | homeDir, _ := os.UserHomeDir() | |||
| 197 | + | discovered, _ = skills.Discover(ZotHome(), args.CWD, homeDir) | |||
| 198 | + | if len(discovered) > 0 { | |||
| 199 | + | skillTool = skills.NewTool(discovered) | |||
| 200 | + | reg[skillTool.Name()] = skillTool | |||
| 201 | + | skillAddendum = skills.SystemPromptAddendum(discovered) | |||
| 202 | + | } | |||
| 194 | } | 203 | } | ||
| 195 | _ = skillTool | 204 | _ = skillTool | ||
| 196 | 205 | ||||
M internal/agent/cli.go +4 -0
| @@ -408,6 +408,10 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||
| 408 | 408 | LoadSession: loadSession, | |
| 409 | 409 | Extensions: extMgr, | |
| 410 | 410 | SkillSnapshot: func() []*skills.Skill { | |
| 411 | + | if args.NoSkill { | |
| 412 | + | // --no-skill: nothing for the picker to show. | |
| 413 | + | return nil | |
| 414 | + | } | |
| 411 | 415 | // Re-discover so the picker reflects edits made during | |
| 412 | 416 | // the session. Cheap; SKILL.md files are small. Filter | |
| 413 | 417 | // out built-in skills — they're hidden from user-facing | |
| @@ -408,6 +408,10 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||||
| 408 | LoadSession: loadSession, | 408 | LoadSession: loadSession, | ||
| 409 | Extensions: extMgr, | 409 | Extensions: extMgr, | ||
| 410 | SkillSnapshot: func() []*skills.Skill { | 410 | SkillSnapshot: func() []*skills.Skill { | ||
| 411 | + | if args.NoSkill { | |||
| 412 | + | // --no-skill: nothing for the picker to show. | |||
| 413 | + | return nil | |||
| 414 | + | } | |||
| 411 | // Re-discover so the picker reflects edits made during | 415 | // Re-discover so the picker reflects edits made during | ||
| 412 | // the session. Cheap; SKILL.md files are small. Filter | 416 | // the session. Cheap; SKILL.md files are small. Filter | ||
| 413 | // out built-in skills — they're hidden from user-facing | 417 | // out built-in skills — they're hidden from user-facing | ||