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
- 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.
- 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.
- Pass
skills=[] when the workflow opts out, so skills: [] means what the docs claim on every provider.
- 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
claude_agent_sdk.pytakes 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 constrainssetting_sources, the underlyingclaudeCLI is already loading skills from the user's machine that the workflow never declared.Both verified against the real installed package (
claude-agent-sdk0.2.128; our pin is>=0.2.82).1. The native surface exists
claude_agent_sdk.pycurrently leavessupports_native_skillsat the defaultFalse, with this comment onCAPABILITIES:There is indeed no kwarg named
skill_directories— that is a Copilot name. The equivalent is calledskills:skillslanded in v0.1.62 andpluginsin v0.1.5, both well below our>=0.2.82pin. From the SDK's own docstring: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 fullSKILL.mdplus the entirereferences/tree on every call — for the bundledconductorskill that is 109KB (~27K tokens) per agent invocation, per retry, per validator call.2.
skills: []does not actually disable skills hereThis is the correctness problem. The provider never sets
setting_sources, and the SDK docstring is explicit about what the default means:and for
skills:So with both left at
None, theclaudeCLI discovers and enables skills from~/.claude/skills/,.claude/skills/(cwd and every parent up to the repo root), and enabled plugins. Consequences:skills: []as an explicit opt-out. On this provider it is not —AgentExecutorinjects nothing, but the CLI still loads the ambient set. The opt-out silently fails to opt out.CAPABILITIES.skills=Trueis therefore describing only half of what the provider actually does.This is the same class of issue that
strict_mcp_config=Truewas introduced to prevent, a few lines away in the same file:We close that door for MCP servers and leave it open for skills. Note the default
setting_sourcesalso pulls inCLAUDE.md,.claude/rules/*.md, projectsettings.json, and hooks — worth deciding deliberately rather than by omission.Proposed change
supports_native_skills = Trueon the provider and forward resolved skills viaClaudeAgentOptions.skillsinstead of eager injection. Names areSKILL.mdname/ directory name, orplugin:skill.setting_sourcesexplicitly rather than inheriting the CLI default, so ambient discovery is a deliberate choice. Pair withplugins=for any skill directory Conductor resolves itself.skills=[]when the workflow opts out, soskills: []means what the docs claim on every provider.CAPABILITIEScomment and theclaude_agent_sdk.pyparity notes inAGENTS.md.Worth confirming with a live run against the
claudeCLI before implementing — the reasoning above is from the SDK source and docs, not an executed end-to-end test (claude-agent-sdkis an optional extra and is not installed in the default dev venv).Related
capabilities.skillsvalidator cross-check