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.