Skip to content

feat: Agent Skills subsystem (SKILL.md, autonomous invocation, commands via ACP/MCP/CLI)#191

Merged
EvilFreelancer merged 14 commits into
mainfrom
feat/skills
Jul 15, 2026
Merged

feat: Agent Skills subsystem (SKILL.md, autonomous invocation, commands via ACP/MCP/CLI)#191
EvilFreelancer merged 14 commits into
mainfrom
feat/skills

Conversation

@EvilFreelancer

@EvilFreelancer EvilFreelancer commented Jul 14, 2026

Copy link
Copy Markdown
Member

Summary

Adds a first-class Skills subsystem to SGR Agent Core, modeled on the
Anthropic Agent Skills pattern (a SKILL.md directory 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_skill
tool, 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/rules workflow. 576 tests pass (+6 e2e), pre-commit clean, fully
backward compatible.

Behavior

  • Default folders, zero-config: skills are read from ./.agent/skills (CWD)
    and ~/.agent/skills. Missing/empty folders are fine — the agent runs normally.
  • Override via config: a skills: block (per-agent or global) sets
    enabled, paths (replaces the default roots), and include/exclude
    activation filters.
  • Invocation, two paths:
    • Autonomous — name + description of each skill are injected into the system
      prompt (progressive disclosure level 1, codex-style catalog); the agent calls
      use_skill <name> to load the full body (level 2). Gated on model_invocable.
    • Explicit — a /skill-name reference in a user message injects that skill's
      body into the prompt, expanded centrally in AgentFactory.create so all
      modes
      (ACP/CLI/OpenAI server) behave identically. The reference may appear
      anywhere in the message; URLs and filesystem paths are ignored. Gated on
      user_invocable.
  • Discovery: ACP available_commands (advertised on session start / agent
    switch), CLI --list-skills, and GET /v1/skills.

Structure (sgr_agent_core/skills/)

  • BaseSkill / SkillMetadata + SkillLoader — parse & validate SKILL.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.
  • SkillsConfigenabled, paths, include/exclude.
  • render_available_skills — system-prompt catalog (budget:
    execution.max_skill_desc_chars, global, default 500).
  • expand_skill_command — shared /skill-name args expansion (ACP + CLI).
  • SkillTool (use_skill) — level-2 body injection; added to the toolkit when
    any skill is present.

Verification

  • Full suite 576 passed, e2e 6 passed, pre-commit run -a clean.
  • Verified against a live config that autonomous system-prompt injection +
    use_skill, ACP available_commands + expansion, CLI --list-skills, and
    HTTP /v1/skills all list the same skills — including the zero-config default
    .agent/skills path.

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 = no
filter, available_skills excluded from state serialization, delimiter-spoofing
hardening, documented trust boundary.

Then revised per maintainer feedback: renamed SkillBaseSkill, removed
the 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, and
tightened types (list[BaseSkill], inline rendering in get_system_prompt).

Finally, a second adversarial review (4 lenses × independent verification, 6 of
11 findings confirmed) hardened the /skill-name path: use_skill now gates on
model_invocable (so disable-model-invocation skills are truly model-hidden),
the reference regex matches quoted/backticked forms while excluding URLs and
/a/b filesystem paths, and the design doc + type hints were tidied. Also fixed
10 user-facing strings that an earlier automated rename had corrupted.

Notes

  • Skills are trusted content (a body is injected verbatim into context) —
    documented; only run skills from trusted sources.
  • Skill resolution is not cached (small file sets; keeps future live-reload simple).

🤖 Generated with Claude Code

EvilFreelancer and others added 14 commits July 14, 2026 13:49
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>
@EvilFreelancer
EvilFreelancer merged commit 47ce205 into main Jul 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant