diff --git a/docs.json b/docs.json index 1524f452..1180aef5 100644 --- a/docs.json +++ b/docs.json @@ -64,6 +64,7 @@ "overview/skills", "overview/skills/repo", "overview/skills/keyword", + "overview/skills/path", "overview/skills/org", "overview/skills/public", "overview/skills/adding", diff --git a/overview/skills.mdx b/overview/skills.mdx index 15484b5d..4202f974 100644 --- a/overview/skills.mdx +++ b/overview/skills.mdx @@ -17,12 +17,13 @@ The official global skill registry is maintained at [github.com/OpenHands/extens Skills inject additional context and rules into the agent's behavior. -At a high level, OpenHands supports two loading models: +At a high level, OpenHands supports three loading models: - **Always-on context** (e.g., `AGENTS.md`) that is injected into the system prompt at conversation start. - **On-demand skills** that are either: - **triggered by the user** (keyword matches), or - **invoked by the agent** (the agent decides to look up the full skill content). +- **Path-triggered rules** that are injected deterministically when the agent reads, edits, or creates a file whose path matches a glob pattern. ## Permanent agent context (recommended) @@ -82,6 +83,7 @@ Currently supported skill types: - **[Permanent Context](/overview/skills/repo)**: Repository-wide guidelines and best practices. We recommend `AGENTS.md` (and optionally `GEMINI.md` / `CLAUDE.md`). - **[Keyword-Triggered Skills](/overview/skills/keyword)**: Guidelines activated by specific keywords in user prompts. +- **[Path-Triggered Rules](/overview/skills/path)**: Guidelines injected automatically when the agent touches files matching a glob pattern. - **[Organization Skills](/overview/skills/org)**: Team or organization-wide standards. - **[Global Skills](/overview/skills/public)**: Community-shared skills and templates. @@ -93,6 +95,7 @@ Each skill file may include frontmatter that provides additional information. In |-------------|----------| | General Skills | No | | Keyword-Triggered Skills | Yes | +| Path-Triggered Rules | Yes | ## Skills Support Matrix @@ -142,4 +145,4 @@ Each skill file may include frontmatter that provides additional information. In - **For bundling multiple components**: See [Plugins](/overview/plugins) - **For SDK integration**: See [SDK Skills Guide](/sdk/guides/skill) - **For architecture details**: See [Skills Architecture](/sdk/arch/skill) -- **For specific skill types**: See [Repository Skills](/overview/skills/repo), [Keyword Skills](/overview/skills/keyword), [Organization Skills](/overview/skills/org), and [Global Skills](/overview/skills/public) +- **For specific skill types**: See [Repository Skills](/overview/skills/repo), [Keyword Skills](/overview/skills/keyword), [Path-Triggered Rules](/overview/skills/path), [Organization Skills](/overview/skills/org), and [Global Skills](/overview/skills/public) diff --git a/overview/skills/creating.mdx b/overview/skills/creating.mdx index 8d6822a5..bf44feea 100644 --- a/overview/skills/creating.mdx +++ b/overview/skills/creating.mdx @@ -167,6 +167,27 @@ Triggers are keywords that automatically activate your skill. Choose words users - List concrete scenarios - Mention related tools or frameworks + + + Instead of keywords, scope a skill to files with a `paths:` glob. The skill + becomes a [path-triggered rule](/overview/skills/path) that OpenHands injects + automatically whenever the agent reads, edits, or creates a matching file — no + keyword or model decision needed: + + ```yaml + --- + name: api-validation + paths: + - "src/api/**/*.ts" + - "**/*.route.ts" + --- + ``` + + **When to use:** + - Conventions tied to specific files (e.g. "validate request inputs with zod" for API routes) + - Guidance you want applied deterministically, without relying on trigger words + - `paths:` takes precedence over `triggers:` if a file declares both + ### Examples of Good Triggers diff --git a/overview/skills/path.mdx b/overview/skills/path.mdx new file mode 100644 index 00000000..77b22d86 --- /dev/null +++ b/overview/skills/path.mdx @@ -0,0 +1,85 @@ +--- +title: Path-Triggered Rules +description: Path-triggered rules are skills that OpenHands injects deterministically whenever the agent reads, edits, or creates a file whose path matches a glob pattern. They behave like Claude Code "rules" — guaranteed to load for the files they scope, with no reliance on the model choosing them. +--- + +## Usage + +A path-triggered rule is an ordinary skill with a `paths:` glob in its frontmatter. Whenever the +agent **touches** a file (reads, edits, or creates it) whose workspace-relative path matches one of +those globs, the rule's content is folded into the tool result the agent reads next — so the guidance +is guaranteed to be present exactly when the agent is working on the matching file. + +Unlike [keyword-triggered skills](/overview/skills/keyword), rules are **not** advertised in +`` and cannot be invoked by the model. They add **zero baseline cost** to the +context window: nothing is loaded until a matching file is actually touched, and each rule is injected +only once per conversation. + + +Use path-triggered rules for scoped conventions that should apply automatically when specific files +are edited — e.g. "validate all request inputs with zod" for `src/api/**/*.ts`, or "keep migrations +reversible" for `db/migrations/**`. + + +## Frontmatter Syntax + +Frontmatter is required for path-triggered rules. Enclose it in triple dashes (`---`) at the top of +the file, above the guidelines. + +| Field | Description | Required | Default | +|---------|--------------------------------------------------------------------|----------|---------| +| `paths` | Glob patterns (YAML list or comma-separated string) that scope the rule. | Yes | None | + +A file that declares both `paths:` and `triggers:` becomes a path-triggered rule — `paths:` wins. +This keeps rules deterministic and out of the model-invocable catalog. + +### Glob Semantics + +Patterns use gitignore-style matching against the workspace-relative POSIX path (matching is +case-sensitive): + +| Pattern | Matches | +|--------------------|-------------------------------------------------------------------------| +| `**` | Any number of path segments, including zero (crosses `/`). | +| `*` | Any run of characters **within a single** path segment. | +| `?` | A single non-separator character. | +| `*.ts` (no slash) | The basename at **any depth** — equivalent to `**/*.ts`. | + +`*` also matches leading-dot files (e.g. `src/*` matches `src/.env`). + +## Example + +Here's a rule located at `.agents/skills/api-validation.md`: + +```markdown +--- +paths: + - "src/api/**/*.ts" + - "**/*.route.ts" +--- + +API RULE: validate all request inputs with zod before using them. +Reject unknown fields and return a 400 with the validation error. +``` + +When the agent creates or edits `src/api/users.ts`, the rule content is appended to that tool result +inside an `` block: + +```xml + +The following rule applies because a file you touched matches "src/api/**/*.ts". Follow it when working with matching files. +Rule location: /repo/.agents/skills/api-validation.md + +API RULE: validate all request inputs with zod before using them. +Reject unknown fields and return a 400 with the validation error. + +``` + + +Path-triggered rules load from the same skills directories as other skills (`.agents/skills/`, +`.openhands/skills/`, …). They are repo-scoped: touching a file outside the workspace never fires a +rule. Injection is available for local conversations; ACP-backed conversations do not inject path +rules because the ACP server owns tool execution. + + +[See the SDK guide for the programmatic `PathTrigger` API](/sdk/guides/skill#path-triggered-rules). diff --git a/sdk/arch/skill.mdx b/sdk/arch/skill.mdx index 2d17c849..b0be92e8 100644 --- a/sdk/arch/skill.mdx +++ b/sdk/arch/skill.mdx @@ -12,7 +12,7 @@ The **Skill** system provides a mechanism for injecting reusable, specialized kn The Skill system has five primary responsibilities: 1. **Context Injection** - Add specialized prompts to agent context based on triggers -2. **Trigger Evaluation** - Determine when skills should activate (always, keyword, task) +2. **Trigger Evaluation** - Determine when skills should activate (always, keyword, task, path) 3. **Dynamic Content Rendering** - Execute inline shell commands for dynamic context injection 4. **MCP Integration** - Load MCP tools associated with repository skills 5. **Third-Party Support** - Parse `.cursorrules`, `agents.md`, and other skill formats @@ -26,12 +26,14 @@ flowchart TB Repo["Repository Skill
trigger: None"] Knowledge["Knowledge Skill
trigger: KeywordTrigger"] Task["Task Skill
trigger: TaskTrigger"] + Rule["Path Rule
trigger: PathTrigger"] end subgraph Triggers["Trigger Evaluation"] Always["Always Active
Repository guidelines"] Keyword["Keyword Match
String matching on user messages"] TaskMatch["Keyword Match + Inputs
Same as KeywordTrigger + user inputs"] + PathMatch["File-Touch Match
Glob match on touched file path"] end subgraph Content["Skill Content"] @@ -44,15 +46,18 @@ flowchart TB subgraph Integration["Agent Integration"] Context["Agent Context"] Prompt["System Prompt"] + ToolResult["Tool Result
Rules injected on file-touch"] end Repo --> Always Knowledge --> Keyword Task --> TaskMatch + Rule --> PathMatch Always --> Markdown Keyword --> Markdown TaskMatch --> Markdown + PathMatch --> ToolResult Markdown -.->|Optional| Dynamic Repo -.->|Optional| MCPTools @@ -68,9 +73,9 @@ flowchart TB classDef tertiary fill:#fff4df,stroke:#b7791f,stroke-width:2px classDef dynamic fill:#e9f9ef,stroke:#2f855a,stroke-width:2px - class Repo,Knowledge,Task primary - class Always,Keyword,TaskMatch secondary - class Context tertiary + class Repo,Knowledge,Task,Rule primary + class Always,Keyword,TaskMatch,PathMatch secondary + class Context,ToolResult tertiary class Dynamic dynamic ``` @@ -81,6 +86,7 @@ flowchart TB | **[`Skill`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/skills/skill.py)** | Core skill model | Pydantic model with name, content, trigger | | **[`KeywordTrigger`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/skills/trigger.py)** | Keyword-based activation | String matching on user messages | | **[`TaskTrigger`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/skills/trigger.py)** | Task-based activation | Special type of KeywordTrigger for skills with user inputs | +| **[`PathTrigger`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/skills/trigger.py)** | Path-based activation ("rules") | Glob match on a touched file path; injected into the tool result, not model-invocable | | **[`InputMetadata`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/skills/types.py)** | Task input parameters | Defines user inputs for task skills | | **[`render_content_with_commands`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/skills/execute.py)** | Dynamic content | Executes inline `!`command`` patterns | | **Skill Loader** | File parsing | Reads markdown with frontmatter, validates schema | @@ -209,6 +215,47 @@ inputs: **Note:** TaskTrigger uses the same keyword matching mechanism as KeywordTrigger. The distinction is semantic - TaskTrigger is used for skills that require structured user inputs, while KeywordTrigger is for knowledge-based skills. +### Path Skills (Rules) + +Skills that are injected **deterministically** when the agent touches a matching file, modeled on Claude Code "rules": + +```mermaid +%%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%% +flowchart TB + Touch["Agent Reads/Edits/Creates File"] + Match{"Path Matches
Glob?"} + Inject["Inject into Tool Result"] + Skip["Skip Rule"] + Dedup["Dedup: once per conversation"] + + Touch --> Match + Match -->|Yes| Dedup + Match -->|No| Skip + Dedup --> Inject + + style Match fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px + style Inject fill:#e8f3ff,stroke:#2b6cb0,stroke-width:2px +``` + +**Characteristics:** +- **Trigger:** `PathTrigger` with gitignore-style `paths` globs (matched against the workspace-relative POSIX path) +- **Activation:** The agent reads, edits, or creates a file whose path matches a glob (fires on `create` too) +- **Injection point:** Folded into the `ObservationEvent` tool result (`extended_content`) as an `` block — **not** the user message +- **Baseline cost:** Zero — excluded from `` and ``; `disable_model_invocation` is forced, so rules are never model-invocable +- **Dedup:** Each rule is injected only once per conversation (tracked via `ConversationState.activated_path_rules`) +- **Location:** Any skills directory (e.g. `.agents/skills/*.md`) — a rule is just a skill with `paths:` frontmatter + +**Trigger Example:** +```yaml +--- +paths: + - "src/api/**/*.ts" + - "**/*.route.ts" +--- +``` + +**Note:** A skill is either path-triggered or model-invocable, not both — if a file declares both `paths:` and `triggers:`, `paths:` wins. Path-rule injection applies to local conversations; ACP-backed conversations do not inject rules because the ACP server owns tool execution. + ## Trigger Evaluation Skills are evaluated at different points in the agent lifecycle: @@ -258,6 +305,7 @@ flowchart TB | **None** | Every step | Always active | | **KeywordTrigger** | On user message | Keyword/string match in message | | **TaskTrigger** | On user message | Keyword/string match in message (same as KeywordTrigger) | +| **PathTrigger** | On tool observation | Glob match on the touched file's path (read/edit/create) | **Note:** Both KeywordTrigger and TaskTrigger use identical string matching logic. TaskTrigger is simply a semantic variant used for skills that include user input parameters. @@ -349,6 +397,7 @@ Dynamic values: !`git branch --show-current` |-------|----------|-------------| | **name** | Yes | Unique skill identifier | | **trigger** | Yes* | Activation trigger (`null` for always active) | +| **paths** | No | Glob patterns that make the skill a path-triggered rule (`PathTrigger`); takes precedence over `triggers` | | **mcp_tools** | No | MCP server configuration (repo skills only) | | **inputs** | No | User input metadata (task skills only) | diff --git a/sdk/guides/skill.mdx b/sdk/guides/skill.mdx index 5bf4d441..b69cb0df 100644 --- a/sdk/guides/skill.mdx +++ b/sdk/guides/skill.mdx @@ -19,6 +19,7 @@ Understanding where skill content appears in the prompt is critical. The behavio | **AgentSkills** (`SKILL.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | | **Legacy** (inline/`*.md`) | `None` | **`` (full content in the initial system prompt; included in LLM context for each turn)** | ❌ No | | **Legacy** (inline/`*.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | +| **Rule** (inline/`*.md`) | `PathTrigger` (`paths:` globs) | Injected into the **tool result** (``) when a matching file is touched; never in `` or `` | ❌ No — deterministic on file-touch | **Token Usage Warning**: Legacy skills with `trigger=None` add their **full content** to `` in the initial `SystemPromptEvent`. That system message remains part of the conversation context for subsequent LLM calls, so the content still affects token usage on each turn. Consider using AgentSkills format (`SKILL.md`) for progressive disclosure instead. @@ -66,6 +67,7 @@ Skill location: /path/to/skill |--------|-------------------|----------| | **Always-loaded** | At conversation start | Repository rules, coding standards | | **Trigger-loaded** | When keywords match | Specialized tasks, domain knowledge | +| **Path-triggered** | When the agent touches a matching file | File-scoped rules (e.g. API validation, migration conventions) | | **Progressive disclosure** | Agent reads on demand | Large reference docs (AgentSkills) | ## Always-Loaded Context @@ -130,6 +132,69 @@ Use the encrypt.sh script to encrypt messages. ``` +## Path-Triggered Rules + +A **rule** is a skill with a `PathTrigger` (`paths:` glob frontmatter). Its content is injected +**deterministically** when the agent reads, edits, or creates a file whose workspace-relative path +matches one of the globs — no reliance on the model choosing a skill. See [Path-Triggered Rules](/overview/skills/path) +for the conceptual overview. + +Rules add **zero baseline cost**: they are excluded from `` and `` +and are never model-invocable (`disable_model_invocation` is forced on). Nothing is loaded until a +matching file is touched, and each rule is injected only once per conversation. + +```python icon="python" focus={6} +from openhands.sdk.skills import PathTrigger, Skill + +Skill( + name="api-validation", + content="API RULE: validate all request inputs with zod before using them.", + trigger=PathTrigger(paths=["src/api/**/*.ts", "**/*.route.ts"]), +) +``` + +As a file-based skill, this is just a `*.md` file with `paths:` frontmatter in a skills directory +(e.g. `.agents/skills/api-validation.md`): + +```markdown icon="markdown" +--- +paths: + - "src/api/**/*.ts" + - "**/*.route.ts" +--- + +API RULE: validate all request inputs with zod before using them. +``` + +When the agent creates or edits `src/api/users.ts`, the rule content is appended to that **tool +result** (not the user message) inside an `` block, so the agent reads it on its next step: + +```xml icon="file" + +The following rule applies because a file you touched matches "src/api/**/*.ts". Follow it when working with matching files. +Rule location: /repo/.agents/skills/api-validation.md + +API RULE: validate all request inputs with zod before using them. + +``` + +### Glob Semantics + +Patterns use gitignore-style matching against the workspace-relative POSIX path (case-sensitive): + +| Pattern | Matches | +|-------------------|---------------------------------------------------------------| +| `**` | Any number of path segments, including zero (crosses `/`). | +| `*` | Any run of characters **within a single** path segment. | +| `?` | A single non-separator character. | +| `*.ts` (no slash) | The basename at **any depth** — equivalent to `**/*.ts`. | + + +- A skill is **either** path-triggered **or** model-invocable, not both: if a file declares both `paths:` and `triggers:`, `paths:` wins. +- Rules are repo-scoped — touching a file outside the workspace never fires a rule. +- Injection is available for local conversations. ACP-backed conversations do not inject path rules, because the ACP server owns tool execution. + + ## Progressive Disclosure (AgentSkills Standard) For the agent to trigger skills, use the [AgentSkills standard](https://agentskills.io/specification) `SKILL.md` format. The agent sees a summary and reads full content on demand.