Skip to content

claude-agent-sdk: use native skills option, and stop loading ambient skills implicitly #352

Description

@jrob5756

claude_agent_sdk.py takes the eager preamble-injection path for skills, on the stated grounds that the upstream SDK has no native surface. That is out of date — the native options exist and are older than our pin. Separately, because the provider never constrains setting_sources, the underlying claude CLI is already loading skills from the user's machine that the workflow never declared.

Both verified against the real installed package (claude-agent-sdk 0.2.128; our pin is >=0.2.82).

1. The native surface exists

claude_agent_sdk.py currently leaves supports_native_skills at the default False, with this comment on CAPABILITIES:

Skill content is eagerly injected into the rendered prompt by AgentExecutor (the claude-agent-sdk surfaces no skill_directories kwarg today; once it does we can flip to native via supports_native_skills).

There is indeed no kwarg named skill_directories — that is a Copilot name. The equivalent is called skills:

>>> dataclasses.fields(ClaudeAgentOptions)   # claude-agent-sdk 0.2.128
skills:          list[str] | Literal['all'] | None      = None
setting_sources: list[Literal['user','project','local']] | None = None
plugins:         list[SdkPluginConfig]                  = <factory>

skills landed in v0.1.62 and plugins in v0.1.5, both well below our >=0.2.82 pin. From the SDK's own docstring:

skills — Skills to enable for the main session. This is the single place to turn skills on; you do not need to add "Skill" to allowed_tools or set setting_sources yourself — the SDK does both when this is set.

  • "all": enable every discovered skill.
  • list[str]: enable only the listed skills. Names match the SKILL.md name / directory name, or plugin:skill for plugin-qualified skills.

Flipping to native would also make this provider progressive rather than eager: Claude Code loads only name + description (~100 tokens/skill) up front and reads the body on demand. Today we prepend the full SKILL.md plus the entire references/ tree on every call — for the bundled conductor skill that is 109KB (~27K tokens) per agent invocation, per retry, per validator call.

2. skills: [] does not actually disable skills here

This is the correctness problem. The provider never sets setting_sources, and the SDK docstring is explicit about what the default means:

setting_sources — Control which filesystem settings to load. When None, all sources are loaded (matches CLI defaults). Pass [] to disable filesystem settings (SDK isolation mode).

and for skills:

None (default): no SDK auto-configuration. The CLI's own defaults still apply, so this is not "skills off" — to suppress every skill from the listing, use [].

So with both left at None, the claude CLI discovers and enables skills from ~/.claude/skills/, .claude/skills/ (cwd and every parent up to the repo root), and enabled plugins. Consequences:

  • An agent receives skills the workflow never declared, varying by developer machine and by which directory the run happens to start in.
  • Conductor documents skills: [] as an explicit opt-out. On this provider it is not — AgentExecutor injects nothing, but the CLI still loads the ambient set. The opt-out silently fails to opt out.
  • CAPABILITIES.skills=True is therefore describing only half of what the provider actually does.

This is the same class of issue that strict_mcp_config=True was introduced to prevent, a few lines away in the same file:

strict_mcp_config=True is set unconditionally, including when the workflow declares no servers: otherwise the CLI loads project .mcp.json, user-global, and plugin-provided servers, and permission_mode bypasses approval for whatever they expose.

We close that door for MCP servers and leave it open for skills. Note the default setting_sources also pulls in CLAUDE.md, .claude/rules/*.md, project settings.json, and hooks — worth deciding deliberately rather than by omission.

Proposed change

  1. Set supports_native_skills = True on the provider and forward resolved skills via ClaudeAgentOptions.skills instead of eager injection. Names are SKILL.md name / directory name, or plugin:skill.
  2. Set setting_sources explicitly rather than inheriting the CLI default, so ambient discovery is a deliberate choice. Pair with plugins= for any skill directory Conductor resolves itself.
  3. Pass skills=[] when the workflow opts out, so skills: [] means what the docs claim on every provider.
  4. Update the CAPABILITIES comment and the claude_agent_sdk.py parity notes in AGENTS.md.

Worth confirming with a live run against the claude CLI before implementing — the reasoning above is from the SDK source and docs, not an executed end-to-end test (claude-agent-sdk is an optional extra and is not installed in the default dev venv).

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions