Skip to content

feat(skills): give Claude Agents runs the skills attached to their variation - #208

Merged
XieX merged 9 commits into
mainfrom
xie/claude-agents-skills
Oct 23, 2026
Merged

XieX merged 9 commits into
mainfrom
xie/claude-agents-skills

Conversation

@XieX

@XieX XieX commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #201 (host options and built-in tool set). Review that one first. This PR's base is its branch.

A skill attached to an agent-mode variation never reached a model run through create_claude_agents_handler. #201's tools=[] removes the Skill tool, and nothing hands the CLI the skill files.

create_claude_agents_skills_handler(*, capture_content=None, cwd=None, setting_sources=None, skills_temp_dir=None), from the new launchdarkly_ai_claude_agents.experimental.skills module, fixes that. On every invoke and stream it:

  1. Writes skill_refs(config) with write_skills, through the store configured with set_skill_store, into mkdtemp(<skills_temp_dir>/ld-skills-)/launchdarkly/skills. Pinned versions are honored.
  2. Passes plugins=[{"type": "local", "path": <dir>/launchdarkly}] and skills=["launchdarkly:<key>", …], and appends Skill to tools (not to allowed_tools).
  3. Removes the directory when the run ends, however it ends.
  • One directory per run. There's no shared root, no watcher and no prune. Detaching a skill from a variation takes effect on the next run, and concurrent runs never share files.
  • Fail closed. If the reconcile isn't ok (no store, store not yet initialized, a skill withheld by verification, or an absent pin), the run raises before query() and names each skill.
  • No-skills path. A variation with no skills sends exactly the core handler's options and needs no store.
  • setting_sources is never None. With skills set and setting_sources=None, claude-agent-sdk sends --setting-sources=user,project. The core handler already always sends it ([] unless given), and this handler inherits that.
  • Telemetry. The tool hooks are installed whenever Skill is in tools, so a Skill call gets an execute_tool Skill span, with no $ld:ai:tool_call event.
  • Stage boundary. Core reaches this code only through a private _prepare keyword argument on _create_claude_agents_handler. It is not an option of the public factory. The package root doesn't import the experimental module, and a test checks that in a fresh interpreter.
  • Not covered: claude_agents(), claude_graph() and to_claude_agents(). The README says so.

Differences from TypeScript

  • No skipMcpDiscovery. claude-agent-sdk's SdkPluginConfig has no such key, so the plugin entry carries only type and path. The directory holds no MCP config, so there is nothing to discover.
  • Synchronous removal. The directory is removed with a synchronous shutil.rmtree, so a cancellation during teardown can't interrupt it. A failed removal is logged and doesn't replace the run's outcome.

Tests

  • A new test_skills.py (49 tests) uses a mocked query() with a real InMemorySkillStore, a real filesystem (tmp_path as skills_temp_dir) and a real OTel provider. Each spec bullet runs on both invoke and stream where it applies.
  • test_sdk_usage.py adds the experimental.createClaudeAgentsSkillsHandler row.
  • Mutation-checked: making the cleanup a no-op fails 9 tests. Dropping Skill from the hook-install condition fails the span tests.

Checks:

  • uv run pytest: everything passes except opencode-agents test_a_fake_opencode_is_started_spoken_to_and_stopped, the macOS /private/var temp-path failure already on main.
  • make lint, make format-check and make typecheck are clean.

Follow-ups in the monorepo

  • The A.18 Python rows and an EXPERIMENTAL.md row for Python.
  • api-surface/python/launchdarkly_ai_claude_agents.experimental.skills.txt.
  • python on experimental.createClaudeAgentsSkillsHandler in usage-ids.txt.

Not verified yet: a live run where a model actually loads the skill.

Spec: TESTING.md §2.x "Claude Agents Skills Integration" and A.18, in launchdarkly/ai-sdks-monorepo#59. The TypeScript side is launchdarkly/js-ai-sdk#136.

🤖 Generated with Claude Code


Note

Overview
Adds experimental create_claude_agents_skills_handler() under launchdarkly_ai_claude_agents.experimental.skills so Claude Agent runs get the skills attached to their AI Config variation. The core create_claude_agents_handler() path is unchanged and still ignores variation skills.

On each invoke/stream with skills, the handler writes skill files from set_skill_store into a per-run temp directory, passes it to query() as a local launchdarkly plugin with qualified launchdarkly:<key> skill names, enables the Skill built-in (via new internal _prepare / QueryPreparation on _create_claude_agents_handler), and removes the directory in a final cleanup on success, failure, cancel, or stream abandonment. Reconcile failures fail closed before query(); empty skills behave like the core handler.

Docs, API report, usage tracking for experimental.createClaudeAgentsSkillsHandler, and a large test_skills.py suite cover materialization, concurrency, telemetry (execute_tool Skill), and that claude_agents / graph helpers are not wired yet.

Reviewed by Cursor Bugbot for commit ac743e0. Bugbot is set up for automated code reviews on this repo. Configure here.

XieX and others added 2 commits October 22, 2026 15:28
…host settings

A run with no NativeTool attached now sends tools=[], so the Claude Code built-ins
(Read, Bash, Skill and the rest) are off unless a config attaches them. 50e82fd
omitted the key instead, which gave every run the full built-in set. TypeScript
has sent tools=[] since its first release. TESTING.md §2.x.2.

create_claude_agents_handler takes cwd and setting_sources, forwarded to every
query() on both paths. setting_sources not given sends [], because the Agent
SDK reads None as "load every source": the host's plugins, skills, hooks,
permission rules and CLAUDE.md reached every run (AIC-3584). cwd is sent only
when given. to_claude_agents also sends setting_sources=[]. §2.x.1.

BREAKING CHANGE: runs no longer get Claude Code built-ins unless the config
attaches them as NativeTool sentinels, and no longer load host Claude Code
settings unless setting_sources names them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riation

A skill attached to an agent-mode variation never reached a model run
through create_claude_agents_handler: tools=[] removes the Skill tool,
and nothing hands the CLI the skill files.

create_claude_agents_skills_handler(), from the new
launchdarkly_ai_claude_agents.experimental.skills module, does. On every
invoke and stream it:

- writes skill_refs(config) with write_skills, through the store
  configured with set_skill_store, into
  mkdtemp(<skills_temp_dir>/ld-skills-)/launchdarkly/skills;
- passes plugins=[{"type": "local", "path": <dir>/launchdarkly}] and
  skills=["launchdarkly:<key>", ...], and appends Skill to tools (not to
  allowed_tools);
- removes the directory when the run ends, however it ends.

It fails closed: a reconcile that is not ok raises before query(),
naming each skill. A variation with no skills runs exactly as with the
core handler and needs no store. setting_sources is always sent, so
claude-agent-sdk never falls back to user,project once skills is set.

Core reaches this only through a private _prepare keyword on the
internal factory. The package root does not import the experimental
module.

Spec: TESTING.md §2.x Claude Agents Skills Integration and A.18
(launchdarkly/ai-sdks-monorepo#59).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX requested a review from jeffdupont October 23, 2026 17:36
@XieX
XieX marked this pull request as ready for review October 23, 2026 17:36
XieX and others added 4 commits October 23, 2026 14:03
…t-options

# Conflicts:
#	packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
…native adapter

Matches launchdarkly/js-ai-sdk#135. claude_agents(), claude_graph() and
to_claude_agents() hard-coded setting_sources=[], so a wrapper user could
not opt back in to host settings, and these signatures freeze at 1.0.

- claude_agents and claude_graph take cwd / setting_sources keyword-only,
  build the handler with them, and pass neither on to config() / graph().
- to_claude_agents reads both from opts and sends _host_query_options(...)
  to every node's query(), root and sub-agents, in place of the
  hard-coded [].
- The README gains a migration note: [] also drops the settings-file env
  (ANTHROPIC_BASE_URL, CLAUDE_CODE_USE_BEDROCK / _VERTEX, proxies),
  apiKeyHelper and model, so a Bedrock, Vertex or gateway deployment
  configured through ~/.claude/settings.json stops authenticating.
- agents.md §2a describes the model-parameter allowlist from main
  instead of "forwards none of model.parameters".
- API report regenerated for the new signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kly/python-ai-sdk into xie/claude-agents-skills

# Conflicts:
#	packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py
…xperimental.skills

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX requested a review from a team as a code owner October 23, 2026 18:08
Base automatically changed from xie/claude-agents-host-options to main October 23, 2026 19:01
jeffdupont and others added 2 commits October 23, 2026 12:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…spans raises

The blocking path ran cleanup after finish_root_span and end_unfinished_spans in the same
finally, so a raise from either leaked the run's plugin directory. Cleanup now has its own
finally.

Lists the experimental skills module in FORWARDED_LISTS: it delegates to the core handler,
which forwards the parameters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeffdupont

Copy link
Copy Markdown
Contributor

Pushed 5acd989 and 85d37ee:

  • Merged origin/main (fix(claude-agents)!: switch the built-ins off by default and load no host settings #201). The only conflicts were in handler.py, where this branch adds the _prepare hook beside main's lines. I kept both.
  • Added launchdarkly_ai_claude_agents.experimental.skills: set() to FORWARDED_LISTS in tests/test_never_forwarded_parameters.py. The module delegates to the core handler.
  • Moved await prepared.cleanup() in _call_impl into its own finally, so the temp dir is removed even if finish_root_span or end_unfinished_spans raises. Added test_removes_the_directory_when_closing_the_blocking_spans_raises. I checked that it fails without the fix.
  • The module stays experimental and still imports launchdarkly_ai_server.experimental.skills until feat(skills)!: promote Agent Skills to core #210 lands.

This needs a human reviewer.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 85d37ee. Configure here.

Comment thread packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py Outdated
…stream spans raises

The stream path closed the vendor generator and ran the preparation's
cleanup after inference.finish(), abandon_tool_spans and end_span_once in
the same finally, so a raise from any of them skipped both and leaked the
run's plugin directory. They now run in their own finally, as on the
blocking path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XieX
XieX merged commit 6bcdc18 into main Oct 23, 2026
9 checks passed
@XieX
XieX deleted the xie/claude-agents-skills branch October 23, 2026 22:30
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.

2 participants