feat: Agent Skills subsystem (SKILL.md, autonomous invocation, commands via ACP/MCP/CLI)#191
Merged
Conversation
Layered design for Agent Skills (SKILL.md, progressive disclosure): loader/model, registry+config, system-prompt auto-registration, use_skill invocation tool, and command surfaces (ACP available_commands, optional MCP prompts). Grounded in Anthropic Agent Skills best practices. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add sgr_agent_core.skills package: - SkillMetadata: validated frontmatter (name/description spec rules, allowed-tools alias, metadata) per Anthropic Agent Skills. - Skill: metadata + markdown body + source path. - SkillLoader: parse SKILL.md frontmatter/body, load_skill from a dir (defaults name to dir), discover skills under a root (skips malformed). Tests: tests/test_skill_loader.py (19 tests). Full suite 516 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SkillRegistry: instance-based, name-keyed catalog; register/get/names/ list_items/load_from_paths (later roots override earlier); clear. - SkillsConfig: enabled, paths, include/exclude filters, max_desc_chars (level-1 listing budget). Tests: tests/test_skill_registry.py (10). Full suite 526 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SkillMetadata gains model_invocable/user_invocable gating (maps
Claude-style disable-model-invocation / user-invocable frontmatter).
- render_available_skills(): codex-style catalog block (name + budgeted
description + how-to-use/use_skill trigger guidance); lists only
model-invocable skills; empty when none.
- PromptLoader.get_system_prompt(): new available_skills / max_skill_desc_chars
args; injects {available_skills}; backward compatible (empty when absent).
- Default system_prompt.txt / research_system_prompt.txt gain {available_skills}.
Tests: test_skills_rendering.py (6), test_prompts.py (+3), test_skill_loader.py
(+3). Full suite 538 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AgentContext.available_skills: skills available to the agent this run (excluded from agent_state serialization). - SkillTool (use_skill): SystemBaseTool that returns a named skill's body (level-2 expansion) from context.available_skills, or a helpful error listing available skills. Auto-registered in ToolRegistry; exported. Tests: tests/test_skill_tool.py (6). Full suite 544 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…actory - AgentConfig.skills: SkillsConfig | None. - BaseAgent accepts skills=, stores available_skills, seeds AgentContext.available_skills, and injects the skills catalog into the system prompt (max_desc_chars from config). - AgentFactory._resolve_skills / _default_skill_roots: discover skills from ~/.sgr/skills, <config_dir>/skills and skills.paths (later overrides earlier), apply include/exclude, add SkillTool when any skill is present, pass resolved skills to the agent. Tests: tests/test_skills_integration.py (7). Full suite 551 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pansion SGRACPBridge now surfaces skills as commands: - new_session pushes an AvailableCommandsUpdate advertising user-invocable skills as ACP slash commands (best effort; skipped when none). - prompt() expands a '/skill-name args' message into the skill body + args so typing a command runs the skill. - Helpers: _skills_for_session, _build_available_commands, _expand_skill_command, _advertise_commands. Tests: tests/test_skills_acp.py (7); existing ACP tests still green. Full suite 558 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mands) - build_skills_mcp_server(skills): FastMCP server registering each user-invocable skill as an MCP prompt (prompts/list advertises name+description; prompts/get returns the skill body, with optional arguments appended). MCP prompts are the user-controlled primitive clients render as slash commands. - Runnable: python -m sgr_agent_core.skills.mcp_server --config config.yaml (unions skills across all agent definitions and serves over stdio). - Export render_available_skills from the skills package. Tests: tests/test_skills_mcp_server.py (4). Full suite 562 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- sgrsh --list-skills: print skills available to the selected agent (format_skills_listing helper). - Server GET /v1/skills: list skills per agent model, optional ?model filter. Tests: test_cli.py (+2), test_api_endpoints.py (+2). Full suite 566 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- tests/test_skills_e2e.py: SGRAgent autonomously calls use_skill(greet)
then finishes; asserts the skill body enters the conversation (e2e mark).
- examples/skills/{citation-style,concise-answer}/SKILL.md: demonstrable
skills used to verify CLI/ACP/OpenAI/MCP surfaces against a live config.
Verified against a live config: CLI --list-skills, ACP available_commands +
slash expansion, OpenAI /v1/skills, autonomous system-prompt injection, and
MCP prompts all surface both skills. e2e 6 passed; full suite 566 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- docs/en/framework/skills.md and docs/ru/framework/skills.md: full skills guide (SKILL.md format, progressive disclosure, config, autonomous invocation, commands over ACP/MCP/CLI/HTTP, authoring tips). - mkdocs.yml: add Skills to nav (+ ru translation). - config.yaml.example: documented skills block. - pre-commit run -a: all hooks pass (docformatter/mdformat/ruff reflow). Full suite 566 passed; e2e 6 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- models: _coerce_bool so quoted YAML booleans ("false") parse correctly
for disable-model-invocation / user-invocable.
- prompt_loader: append the skills catalog when a custom template omits
{available_skills}, so autonomous discovery works regardless of template.
- loader: raise SkillError on unreadable/invalid-UTF-8 files, guard directory
iteration, and cap SKILL.md at 1 MiB (MAX_SKILL_FILE_BYTES).
- acp/bridge: swallow+log transport errors when advertising commands; re-advertise
available_commands after an agent switch; delegate expansion to shared helper.
- skills/commands.py: shared expand_skill_command used by ACP and CLI.
- cli: expand /skill-name slash commands in single-query and chat modes;
--list-skills shows only user-invocable skills.
- factory: empty include list means 'no filter' (symmetric with exclude).
- server: exclude available_skills from /agents/{id}/state dump.
- skill_tool: neutralize a stray </SKILL> delimiter in bodies.
- docs: security/trust-boundary note (en+ru).
- tests: quoted-bool, size cap, unreadable-utf8, include allowlist + empty
include, default-root ordering, shared expansion, CLI listing filter,
placeholder-absent append. Hermetic default-roots fixture.
Full suite 579 passed; e2e 6 passed; pre-commit clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lt .agent/skills Per user feedback: - Rename Skill -> BaseSkill (base type mirroring BaseTool/BaseAgent); keep instance-based SkillRegistry. Public API exports BaseSkill. - Remove the MCP prompts server entirely (ACP available_commands already covers 'skills as commands'); delete mcp_server.py + its tests + docs. - Default skill roots are now ./.agent/skills (CWD) and ~/.agent/skills, scanned by default even without a skills: block; missing/empty roots are safe. skills.paths overrides the defaults (resolved against CWD). - Move the prompt-listing budget to a global setting execution.max_skill_desc_chars (default 500); render reads it lazily. - get_system_prompt renders inline: render_available_skills(available_skills or []); available_skills typed list[BaseSkill] (no Optional/None threading). - Move example skills under examples/.agent/skills/. - Update docs (en+ru), config example, design doc. Full suite 576 passed; e2e 6 passed; pre-commit clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d strings - Fix 10 user-facing strings corrupted by the earlier Skill->BaseSkill sed rename (all 9 SkillMetadata validation errors + the use_skill not-found message) back to 'Skill ...'. - New feature: '/skill-name' references in a user message inject that skill's body into the prompt, centrally in AgentFactory.create, so ACP/CLI/OpenAI server all behave the same (removed the duplicated per-entrypoint expansion). Progressive disclosure + use_skill kept; both share render_skill_body. Adversarial review (4 lenses, each finding independently verified) — applied the 6 confirmed findings: - use_skill now gates on model_invocable (symmetric with the user path's user_invocable), so disable-model-invocation skills are truly model-hidden. - SKILL_REF_RE now also matches quoted/backticked/paren-wrapped refs and, via a possessive quantifier + negative lookahead, excludes filesystem paths (/etc/hosts) and URLs. - Fix stale ACP line in docs/design/skills.md; consistent Sequence[BaseSkill] type hints. - Docs (en+ru) describe the unified two-path invocation model. Full suite 588 passed; e2e 6 passed; pre-commit clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a first-class Skills subsystem to SGR Agent Core, modeled on the
Anthropic Agent Skills pattern (a
SKILL.mddirectory with YAML frontmatter +markdown body, progressive disclosure). Skills are auto-registered into the
agent system prompt so the agent can invoke them autonomously via a
use_skilltool, and are also surfaced as commands over ACP, the CLI, and the HTTP server.
Built strictly bottom-up (one class per layer) and test-first per the repo's
.cursor/rulesworkflow. 576 tests pass (+6 e2e), pre-commit clean, fullybackward compatible.
Behavior
./.agent/skills(CWD)and
~/.agent/skills. Missing/empty folders are fine — the agent runs normally.skills:block (per-agent or global) setsenabled,paths(replaces the default roots), andinclude/excludeactivation filters.
prompt (progressive disclosure level 1, codex-style catalog); the agent calls
use_skill <name>to load the full body (level 2). Gated onmodel_invocable./skill-namereference in a user message injects that skill'sbody into the prompt, expanded centrally in
AgentFactory.createso allmodes (ACP/CLI/OpenAI server) behave identically. The reference may appear
anywhere in the message; URLs and filesystem paths are ignored. Gated on
user_invocable.available_commands(advertised on session start / agentswitch), CLI
--list-skills, andGET /v1/skills.Structure (
sgr_agent_core/skills/)BaseSkill/SkillMetadata+SkillLoader— parse & validateSKILL.md(name/description spec rules,
allowed-tools,metadata,disable-model-invocation/user-invocable); discover on disk (read-error /1 MiB guards).
SkillRegistry— ordered catalog; later roots override earlier.SkillsConfig—enabled,paths,include/exclude.render_available_skills— system-prompt catalog (budget:execution.max_skill_desc_chars, global, default 500).expand_skill_command— shared/skill-name argsexpansion (ACP + CLI).SkillTool(use_skill) — level-2 body injection; added to the toolkit whenany skill is present.
Verification
pre-commit run -aclean.use_skill, ACPavailable_commands+ expansion, CLI--list-skills, andHTTP
/v1/skillsall list the same skills — including the zero-config default.agent/skillspath.Review
Independently reviewed by two subagents (OpenAI codex CLI + a Claude review
agent); findings applied: robust quoted-YAML boolean parsing, catalog appended
when a custom template omits the placeholder, loader read/size guards, ACP
advertise error-handling + re-advertise on agent switch, empty
include= nofilter,
available_skillsexcluded from state serialization, delimiter-spoofinghardening, documented trust boundary.
Then revised per maintainer feedback: renamed
Skill→BaseSkill, removedthe MCP-prompts server (ACP already covers "skills as commands"), switched
default roots to
./.agent/skills+~/.agent/skills(scanned by default),moved the description budget to the global
execution.max_skill_desc_chars, andtightened types (
list[BaseSkill], inline rendering inget_system_prompt).Finally, a second adversarial review (4 lenses × independent verification, 6 of
11 findings confirmed) hardened the
/skill-namepath:use_skillnow gates onmodel_invocable(sodisable-model-invocationskills are truly model-hidden),the reference regex matches quoted/backticked forms while excluding URLs and
/a/bfilesystem paths, and the design doc + type hints were tidied. Also fixed10 user-facing strings that an earlier automated rename had corrupted.
Notes
documented; only run skills from trusted sources.
🤖 Generated with Claude Code