Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/github-issue-advanced-claude-code-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Implement Advanced Claude Code Skills Runtime Semantics

## Problem

The first Claude Code skills alignment PR covered discovery, frontmatter tolerance, model/user visibility, direct slash invocation, argument substitution, and cwd-to-root project discovery. The remaining advanced semantics are now required: dynamic shell context injection, forked subagent-style execution, and skill-scoped model/tool/hook enforcement.

## Proposed Scope

- Parse and retain advanced skill frontmatter fields:
- `model`
- `effort`
- `allowed-tools`
- `disallowed-tools`
- `hooks`
- `context`
- `agent`
- `shell`
- Render dynamic shell context before direct skill invocation reaches the model:
- inline `` !`command` ``
- fenced ` ```! ` command blocks
- Preserve one-pass semantics: dynamic shell context is rendered from the original skill template before argument substitution, and inserted arguments are not rescanned.
- Run shell context commands from the session cwd using a deterministic shell choice.
- Support `context: fork` by running the skill in an isolated child transcript and appending only a new final assistant result to the parent transcript.
- Enforce `allowed-tools` and `disallowed-tools` for the invoked skill only.
- Run skill-scoped hooks for the invoked skill only, in addition to normal session hooks.
- Override the model for the invoked skill only when the named model is available from the host model factory.
- Add focused tests for all required runtime behaviors.

## Out Of Scope

- Filesystem watching or live rediscovery after startup.
- Adding new model-effort provider APIs.
- Expanding the global config format beyond what is needed to run skill-scoped frontmatter.

## Acceptance Criteria

- A directly invoked skill containing `` !`printf context` `` sends `context` to the model in place of the inline expression.
- A directly invoked skill containing a fenced ` ```! ` block sends the command output to the model in place of the command block.
- Shell context command failures are visible in the rendered prompt as an error marker.
- `context: fork` leaves the parent transcript free of the skill's internal user prompt while preserving only a new final assistant result.
- `allowed-tools` limits the tool specs advertised to the model and blocks out-of-scope tool execution.
- `disallowed-tools` removes denied tools even when the allow list would include them.
- Skill-scoped hooks can block a tool call during that skill invocation and do not remain active afterward.
- `model` selects the requested model for that skill invocation and restores the previous model afterward.
- `cargo fmt --check`, focused tests, full `cargo test`, and `openspec validate implement-advanced-claude-skills --strict` pass.
71 changes: 71 additions & 0 deletions docs/prd-advanced-claude-code-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# PRD: Advanced Claude Code Skills Runtime Alignment

## Overview / Problem Statement

Ra now supports the basic Claude Code skill shape, but advanced Claude Code skills still lose important runtime semantics. Users with existing skills expect dynamic shell context injection, isolated subagent-style execution, and skill-scoped model, tool, and hook constraints to affect the actual invocation rather than being silently ignored.

## Goals & Success Metrics

- Direct `/skill-name` invocation renders dynamic shell context from the original skill template before arguments are inserted.
- Skills that request forked execution run against an isolated copy of the conversation and do not mutate the parent session transcript with internal subagent turns.
- Skills that declare `model`, `allowed-tools`, `disallowed-tools`, or `hooks` apply those constraints for that skill invocation only.
- Existing prompt-template behavior and previously aligned skill discovery/frontmatter behavior remain backward-compatible.
- Focused Rust tests cover dynamic shell rendering, scoped tool filtering, scoped hooks, scoped model selection, and fork transcript isolation.

## User Personas & Stories

- As a Claude Code skill author, I want `` !`command` `` and fenced ` ```! ` blocks to inject command output so that skills can include live project state.
- As an agent operator, I want high-risk skills to restrict tools and hooks at invocation time so that local policy travels with the skill.
- As a runtime integrator, I want forked skill execution so that exploratory skill work can produce a result without polluting the parent session history.

## Functional Requirements

| Priority | Requirement |
| --- | --- |
| Must | Parse and persist skill frontmatter for `model`, `allowed-tools`, `disallowed-tools`, `hooks`, `context`, `agent`, and `shell`. |
| Must | Replace inline `` !`command` `` expressions with captured stdout before the skill prompt is submitted, using one pass over the original skill template. |
| Must | Replace fenced ` ```! ` command blocks with captured stdout before the skill prompt is submitted, using one pass over the original skill template. |
| Must | Run shell context commands from the current session cwd, using `/bin/sh -c` by default and `bash -lc` when `shell: bash` is declared. |
| Must | Insert a readable error marker when a shell context command exits unsuccessfully instead of aborting the entire skill invocation. |
| Must | Support `context: fork` as an isolated skill execution mode that runs on a child transcript snapshot and appends only a new final assistant result to the parent transcript. |
| Must | Apply `allowed-tools` as an invocation-scoped allow list for tool specs and execution. |
| Must | Apply `disallowed-tools` as an invocation-scoped deny list on top of the allow list. |
| Must | Apply skill-scoped `hooks` in addition to session hooks for that invocation. |
| Must | Apply `model` as an invocation-scoped model override when the host can build the named model. |
| Should | Treat unsupported shell names as the default shell and include the declaration in tests/docs as best-effort compatibility. |
| Could | Parse `effort` for future model parameters without changing the model trait in this change. |
| Won't | Implement live filesystem watching or automatic nested skill discovery during an already-running session. |

## Non-Functional Requirements

- Scope changes to skill rendering and the shared session runner/session path.
- Preserve deterministic behavior in tests without network calls.
- Avoid weakening existing session-level hooks and tool filters.
- Do not silently broaden constrained tool declarations such as `bash(...)` to the whole tool.
- Keep failed shell context commands visible to the model for debugging.

## Design Considerations

The user-facing behavior should be compatible where Ra has the necessary runtime surfaces today. Skill-scoped behavior should be temporary and should restore the parent session model, hooks, tool visibility, and transcript after the invocation completes.

## Technical Considerations

The implementation will extend `Skill`/`SlashTemplate`, `SessionRunner`, and `Session`. `RunnerHost` will expose model construction so the runner can honor `model` overrides without making protocol-specific code leak into skill rendering. Session-level scoped runtime state will filter advertised and executable tools and combine hooks for a single invocation.

## Timeline & Milestones

| Milestone | Owner | Target |
| --- | --- | --- |
| Updated PRD, issue draft, and OpenSpec change | Agent | Before implementation |
| Runtime implementation and focused tests | Agent | Same PR |
| New GitHub issue and PR | Agent | After validation |

## Open Questions & Risks

- Claude Code's exact internal fork/subagent transcript behavior is not public API. Ra will implement a practical equivalent: cloned parent context for the skill and parent transcript isolation except for the final skill result.
- Skill-scoped model overrides depend on configured model IDs. Unknown model IDs should fail visibly rather than silently using the wrong model.
- Shell context injection executes local commands and therefore inherits Ra's existing local execution risk profile.

## Appendix

Reference: current Claude Code skills documentation at `https://code.claude.com/docs/en/skills.md` and `https://code.claude.com/docs/zh-CN/skills.md`, checked during this change.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-01
62 changes: 62 additions & 0 deletions openspec/changes/implement-advanced-claude-skills/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## Context

Ra's current `Skill` model already tolerates advanced Claude Code fields during YAML parsing, but it discards them. `ResourceBundle::prompt_map` converts user-invocable skills into `SlashTemplate` values consumed by `SessionRunner`, and the runner sends the rendered template through `Session::prompt`. `Session` owns the model, tool catalog, message log, cwd, and hook engine.

## Goals / Non-Goals

**Goals:**

- Preserve advanced skill frontmatter in the runtime template.
- Render dynamic shell context deterministically from the session cwd.
- Apply model/tool/hook settings only to the directly invoked skill turn.
- Implement a practical forked skill execution mode that isolates parent transcript history from internal skill turns.
- Keep existing non-skill prompt-template behavior unchanged.

**Non-Goals:**

- Do not implement provider-specific model effort controls.
- Do not add live skill rediscovery while a session is already running.
- Do not reinterpret Claude Code permissions beyond tool-name and simple `Tool(pattern)` declarations.

## Decisions

### Store Runtime Options On Skill Templates

`Skill` and `SlashTemplate` will carry a `SkillRuntimeOptions` struct containing optional model, effort, shell, context, agent, allowed/disallowed tool declarations, and hooks. Prompt templates keep `None`, so legacy prompt commands remain unaffected. `context: fork` is the fork trigger; `agent` is retained as the optional subagent type/compatibility alias.

### Render Shell Context In The Runner

Dynamic context syntax is prompt rendering, not model behavior. The runner already owns direct slash invocation and has access to session cwd, so it will replace inline `` !`command` `` and fenced ` ```! ` blocks on the original skill body before argument substitution. This is intentionally one-pass: user-provided arguments inserted through `$ARGUMENTS`, `$N`, named placeholders, or no-placeholder fallback are never scanned as dynamic shell context.

Unsuccessful shell commands should become readable error markers in the rendered prompt. This preserves debuggability and avoids unexpectedly aborting the entire skill invocation.

### Use Scoped Session Runtime State

`Session` will expose a scoped invocation method that takes an optional runtime override. During that invocation, it will:

- temporarily select an override model when provided,
- filter advertised tool specs and executable tool lookup using an allow/deny policy,
- merge skill hooks with session hooks,
- restore the previous runtime state after completion.

Tool names are matched case-insensitively. Constrained declarations such as `bash(git status:*)` are not expanded to the whole `bash` tool; they fail closed until Ra has command-level policy enforcement.

The scope is held in the async prompt path and is not persisted into the message log. Skill-scoped Stop hooks run on normal successful completion as well as early block/stop paths.

### Extend RunnerHost For Model Resolution

`RunnerHost` already abstracts host behavior needed by `SessionRunner`. Add a `build_model_for_id` method with a default `None` implementation so ACP can resolve model IDs via its existing factory, while tests and other hosts can opt in without changing protocol code.

Unknown model IDs should fail the skill invocation visibly. Silent fallback to the default model would violate the skill author's explicit runtime policy.

### Forked Skill Execution Uses Transcript Snapshot Isolation

For `context: fork`, the runner will run the rendered skill prompt against a child session state initialized from the parent transcript snapshot. After the child finishes, the parent transcript is restored to its pre-skill state and receives only the final assistant text produced after the fork snapshot. Tool calls and intermediate skill messages remain isolated from the parent transcript. If the fork produces no new assistant text, nothing is appended to the parent transcript.

This matches the operational need for fork/subagent behavior with Ra's current single-session architecture and avoids protocol-specific session creation in the shared runner.

## Risks / Trade-offs

- Tool declarations in Claude Code can include richer permission patterns than Ra tool names. This implementation enforces whole-tool names only and fails closed on constrained declarations so it does not silently broaden permissions.
- Forked execution cannot perfectly emulate Claude Code internals without a public transcript contract. Tests will lock Ra's defined behavior: parent snapshot in, final assistant result out.
- Shell context injection executes local commands before the model call. That is expected for Claude Code-compatible skills and is contained to direct skill invocation.
28 changes: 28 additions & 0 deletions openspec/changes/implement-advanced-claude-skills/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Why

The merged Claude Code skills alignment made existing skills discoverable and directly invocable, but it still ignores advanced runtime semantics that users now require. Advanced Claude Code skills often depend on live shell context, isolated/forked execution, and per-skill model/tool/hook policy; treating those fields as inert metadata causes behavior drift and can bypass author intent.

## What Changes

- Parse and retain advanced Claude Code skill frontmatter fields used at runtime.
- Render inline and fenced dynamic shell context blocks before a skill prompt reaches the model.
- Add invocation-scoped runtime options for direct skill slash commands.
- Support `context: fork` by running a skill against an isolated child transcript and returning only the final result to the parent session.
- Enforce skill-scoped model overrides, tool allow/deny lists, and hooks for the duration of a skill invocation.
- Add PRD, GitHub issue draft, OpenSpec requirements, focused tests, and documentation comments for the new runtime behavior.

## Capabilities

### New Capabilities

- None.

### Modified Capabilities

- `claude-code-skills`: Extend previously aligned skill discovery/invocation behavior with advanced runtime semantics for shell context, forked execution, and scoped model/tool/hook policy.

## Impact

- Affects `src/skills.rs`, `src/session_runner.rs`, `src/session.rs`, `src/hooks.rs`, protocol runner host implementations, docs, and tests.
- Adds no new external runtime dependency.
- Keeps existing prompt-template and basic skill invocation behavior backward-compatible.
Loading
Loading