From 748268eee25cb8167464c97fe2f82bab0d9ee604 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 29 Apr 2026 11:01:24 +0000 Subject: [PATCH 1/4] docs: clarify skill injection behavior and add migration guide Address documentation gaps identified in OpenHands/software-agent-sdk#2981: 1. Add 'Skill Injection Behavior' section with clear table showing where content appears based on format and trigger configuration 2. Add warning about legacy trigger=None skills consuming tokens on every turn 3. Add 'Prompt Structure' section showing exactly where skills appear in the system prompt 4. Add warning to inline skill code example about legacy behavior 5. Improve load_skills_from_dir() documentation with table explaining injection behavior for each return value 6. Add 'Migrating from Legacy to AgentSkills Format' section with before/after examples and benefits comparison Co-authored-by: openhands --- sdk/guides/skill.mdx | 117 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/sdk/guides/skill.mdx b/sdk/guides/skill.mdx index 42ed749af..475143a77 100644 --- a/sdk/guides/skill.mdx +++ b/sdk/guides/skill.mdx @@ -9,6 +9,56 @@ This guide shows how to implement skills in the SDK. For conceptual overview, se OpenHands supports an **extended version** of the [AgentSkills standard](https://agentskills.io/specification) with optional keyword triggers. +## Skill Injection Behavior + +Understanding where skill content appears in the prompt is critical. The behavior differs based on skill format and trigger configuration: + +| Skill Format | Trigger | Where Content Appears | Model Mediated? | +|--------------|---------|----------------------|-----------------| +| **AgentSkills** (`SKILL.md`) | Any | `` (description only) | ✅ Yes — agent calls `invoke_skill()` | +| **AgentSkills** (`SKILL.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | +| **Legacy** (inline/`*.md`) | `None` | **`` (full content, every turn)** | ❌ No | +| **Legacy** (inline/`*.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | + + +**Token Usage Warning**: Legacy skills with `trigger=None` inject their **full content** into `` on **every turn**. This can consume significant tokens. Consider using AgentSkills format (`SKILL.md`) for progressive disclosure instead. + + +### Prompt Structure + +Skills appear in different parts of the system prompt: + +```xml icon="file" + + + + + [BEGIN context from [agents]] + ... AGENTS.md content ... + [END Context] + + + + + + + github + Interact with GitHub... + + + +``` + +When a trigger matches, content is injected into the **user message**: + +```xml icon="file" + +The following information has been included based on a keyword match for "github". +Skill location: /path/to/skill +... skill content ... + +``` + ## Context Loading Methods | Method | When Content Loads | Use Case | @@ -50,6 +100,10 @@ agent_context = AgentContext( ) ``` + +**Important**: Inline skills with `trigger=None` use **legacy format** behavior — full content is injected into `` on every turn. For large skills, consider using the AgentSkills `SKILL.md` format for progressive disclosure. + + ## Trigger-Loaded Context Content injected when keywords appear in user messages. See [Keyword-Triggered Skills](/overview/skills/keyword). @@ -836,9 +890,15 @@ from openhands.sdk.context.skills import load_skills_from_dir repo_skills, knowledge_skills, agent_skills = load_skills_from_dir(skills_dir) ``` -- **repo_skills**: Skills from `repo.md` files (always active) -- **knowledge_skills**: Skills from `knowledge/` subdirectories -- **agent_skills**: Skills from `SKILL.md` files (AgentSkills standard) +| Return Value | Source Files | Injection Behavior | +|--------------|--------------|-------------------| +| **repo_skills** | `repo.md`, `AGENTS.md`, `.cursorrules` | Full content in `` every turn | +| **knowledge_skills** | `knowledge/` subdirectories, `*.md` with triggers | Listed in ``, auto-inject on trigger | +| **agent_skills** | `SKILL.md` files (AgentSkills standard) | Listed in ``, agent calls `invoke_skill()` | + + +When passing to `AgentContext(skills=...)`, all three types are accepted. The injection behavior depends on the skill's `is_agentskills_format` flag and `trigger` field — see [Skill Injection Behavior](#skill-injection-behavior). + #### `discover_skill_resources()` @@ -1165,6 +1225,57 @@ print(rendered) # Commands replaced with output The `working_dir` parameter sets the current directory for command execution, enabling workspace-relative commands like `git status`. +## Migrating from Legacy to AgentSkills Format + +If you have legacy inline skills consuming many tokens, convert them to AgentSkills format for progressive disclosure: + +### Before (Legacy Format) + +```python icon="python" +# Legacy: Full content in every turn +Skill( + name="api-guidelines", + content=""" + # API Guidelines + ... 2000 lines of detailed documentation ... + """, + trigger=None, # Always injected - uses many tokens! +) +``` + +### After (AgentSkills Format) + +Create a directory `api-guidelines/SKILL.md`: + +```markdown icon="markdown" +--- +name: api-guidelines +description: Comprehensive API design guidelines for the project. Invoke when designing or reviewing API endpoints. +--- + +# API Guidelines + +... 2000 lines of detailed documentation ... +``` + +Then load it: + +```python icon="python" +from openhands.sdk.skills import load_skills_from_dir + +# AgentSkills: Only description in prompt, agent reads full content on demand +_, _, skills = load_skills_from_dir("/path/to/skills") +agent_context = AgentContext(skills=list(skills.values())) +``` + +### Benefits + +| Aspect | Legacy `trigger=None` | AgentSkills `SKILL.md` | +|--------|----------------------|------------------------| +| Token usage | Full content every turn | Description only (~100 chars) | +| Model control | None — always present | Agent decides when to read | +| Scalability | Limited by context window | Many skills without token bloat | + ## Next Steps - **[Custom Tools](/sdk/guides/custom-tools)** - Create specialized tools From d5c5b8e931dbee3ac1b4c9765cb0e56ec591ef82 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 29 Apr 2026 14:14:10 +0000 Subject: [PATCH 2/4] chore: require source code evidence links in code review guidelines Update the code review skill to explicitly require that every review comment and approval includes GitHub permalink evidence from the original source codebase. Adds: - Mandatory evidence requirement in the source verification section - Format examples for inline review comments with source links - Source Verification table template for the overall review body - Instructions to flag unverifiable claims explicitly - GitHub base URLs in the repository mapping table Co-authored-by: openhands --- .agents/skills/code-review.md | 64 +++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/.agents/skills/code-review.md b/.agents/skills/code-review.md index 0d6908676..a03d8f0e7 100644 --- a/.agents/skills/code-review.md +++ b/.agents/skills/code-review.md @@ -12,19 +12,23 @@ multiple source-of-truth codebases. Accuracy is critical. ## Source Code Verification (Required for SDK/CLI/App docs) -When reviewing documentation that references APIs, function signatures, class names, or code examples -from an upstream repository, you **MUST** clone the corresponding repository and verify the documentation -against the actual source code. Do not trust that code examples or API descriptions are accurate without -checking. +When reviewing documentation that references APIs, function signatures, class names, code examples, +or behavioral descriptions from an upstream repository, you **MUST** clone the corresponding repository +and verify the documentation against the actual source code. Do not trust that code examples or API +descriptions are accurate without checking. -### Repositories to clone for verification +**Every review comment and every approval MUST include evidence from the source code.** For each +documentation claim you verify (or flag as incorrect), provide a direct GitHub permalink to the +relevant source file and line(s). A review that simply says "looks good" without citing source +code is incomplete. -| Documentation path | Source repository | -|--------------------|-------------------| -| `sdk/` | `OpenHands/software-agent-sdk` | -| `openhands/` | `OpenHands/OpenHands` | -| CLI-related docs | `OpenHands/OpenHands-CLI` | +### Repositories to clone for verification +| Documentation path | Source repository | GitHub base URL | +|--------------------|-------------------|-----------------| +| `sdk/` | `OpenHands/software-agent-sdk` | `https://github.com/OpenHands/software-agent-sdk` | +| `openhands/` | `OpenHands/OpenHands` | `https://github.com/OpenHands/OpenHands` | +| CLI-related docs | `OpenHands/OpenHands-CLI` | `https://github.com/OpenHands/OpenHands-CLI` | ### Prefer upstream PR branches when linked @@ -58,6 +62,46 @@ git clone --depth=1 https://github.com/OpenHands/software-agent-sdk.git /tmp/age grep -rn "function_name" /tmp/agent-sdk/ ``` +### Providing evidence in review comments (Required) + +For **every** documentation change that describes code behavior, APIs, or implementation details, +your review MUST include a link to the corresponding source code as evidence. Use GitHub permalinks +(with commit SHA or branch) so links remain stable. + +**Format for inline review comments:** + +When commenting on a specific line or section of the documentation, include evidence like: + +``` +✅ Verified: `is_agentskills_format` field exists on the Skill class. +→ Source: https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context.py#L42 + +🔴 Incorrect: The docs say `trigger=None` causes injection into ``, but the code +shows it injects into ``. +→ Source: https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/agent.py#L150-L165 +``` + +**Format for the overall review body:** + +Include a "Source Verification" section that lists the key claims verified and their evidence: + +``` +## Source Verification + +| Documentation Claim | Verified? | Source Evidence | +|---------------------|-----------|-----------------| +| Legacy skills with `trigger=None` inject full content every turn | ✅ | [agent.py#L150](permalink) | +| `load_skills_from_dir()` returns tuple of (skills, repo_skills) | ✅ | [context.py#L85](permalink) | +| AgentSkills format uses progressive disclosure | ❌ Incorrect | [agent.py#L200](permalink) shows... | +``` + +If you cannot find source code to verify a documentation claim, explicitly flag it: + +``` +⚠️ Unverified: Could not find source code for the claimed behavior of "X". +This should be verified before merging. +``` + ## Review Decisions You **must** use the correct GitHub review `event` value when submitting your review. From a1d803e7d2cc55e9dec554ead365a3788b9c69c4 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 1 May 2026 15:14:05 +0000 Subject: [PATCH 3/4] docs: clarify legacy skill prompt timing Clarify that legacy trigger=None skills are added to the initial system prompt and then sent with each LLM request while retained in conversation history, rather than being re-rendered as a new injection on every turn. Co-authored-by: openhands --- sdk/guides/skill.mdx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/sdk/guides/skill.mdx b/sdk/guides/skill.mdx index 475143a77..38ad1b852 100644 --- a/sdk/guides/skill.mdx +++ b/sdk/guides/skill.mdx @@ -17,11 +17,11 @@ Understanding where skill content appears in the prompt is critical. The behavio |--------------|---------|----------------------|-----------------| | **AgentSkills** (`SKILL.md`) | Any | `` (description only) | ✅ Yes — agent calls `invoke_skill()` | | **AgentSkills** (`SKILL.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | -| **Legacy** (inline/`*.md`) | `None` | **`` (full content, every turn)** | ❌ No | +| **Legacy** (inline/`*.md`) | `None` | **`` (full content in the initial system prompt; sent with each LLM request)** | ❌ No | | **Legacy** (inline/`*.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | -**Token Usage Warning**: Legacy skills with `trigger=None` inject their **full content** into `` on **every turn**. This can consume significant tokens. Consider using AgentSkills format (`SKILL.md`) for progressive disclosure instead. +**Token Usage Warning**: Legacy skills with `trigger=None` add their **full content** to `` in the initial `SystemPromptEvent`. That system message remains in conversation history and is sent with each LLM request, so the content still affects token usage on each turn. Consider using AgentSkills format (`SKILL.md`) for progressive disclosure instead. ### Prompt Structure @@ -32,7 +32,8 @@ Skills appear in different parts of the system prompt: - + [BEGIN context from [agents]] ... AGENTS.md content ... [END Context] @@ -101,7 +102,7 @@ agent_context = AgentContext( ``` -**Important**: Inline skills with `trigger=None` use **legacy format** behavior — full content is injected into `` on every turn. For large skills, consider using the AgentSkills `SKILL.md` format for progressive disclosure. +**Important**: Inline skills with `trigger=None` use **legacy format** behavior — full content is added to `` in the initial system prompt and sent with each LLM request. For large skills, consider using the AgentSkills `SKILL.md` format for progressive disclosure. ## Trigger-Loaded Context @@ -892,7 +893,7 @@ repo_skills, knowledge_skills, agent_skills = load_skills_from_dir(skills_dir) | Return Value | Source Files | Injection Behavior | |--------------|--------------|-------------------| -| **repo_skills** | `repo.md`, `AGENTS.md`, `.cursorrules` | Full content in `` every turn | +| **repo_skills** | `repo.md`, `AGENTS.md`, `.cursorrules` | Full content in `` in the initial system prompt; sent with each LLM request | | **knowledge_skills** | `knowledge/` subdirectories, `*.md` with triggers | Listed in ``, auto-inject on trigger | | **agent_skills** | `SKILL.md` files (AgentSkills standard) | Listed in ``, agent calls `invoke_skill()` | @@ -1232,14 +1233,14 @@ If you have legacy inline skills consuming many tokens, convert them to AgentSki ### Before (Legacy Format) ```python icon="python" -# Legacy: Full content in every turn +# Legacy: Full content in in the initial system prompt Skill( name="api-guidelines", content=""" # API Guidelines ... 2000 lines of detailed documentation ... """, - trigger=None, # Always injected - uses many tokens! + trigger=None, # Always-on context - uses tokens on each LLM request! ) ``` @@ -1272,7 +1273,7 @@ agent_context = AgentContext(skills=list(skills.values())) | Aspect | Legacy `trigger=None` | AgentSkills `SKILL.md` | |--------|----------------------|------------------------| -| Token usage | Full content every turn | Description only (~100 chars) | +| Token usage | Full content in system prompt, sent with each LLM request | Description only (~100 chars) | | Model control | None — always present | Agent decides when to read | | Scalability | Limited by context window | Many skills without token bloat | From 64c974b83908b02894117b5c2a4bcf211492599d Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 1 May 2026 15:24:50 +0000 Subject: [PATCH 4/4] docs: avoid ambiguous prompt context wording Replace transport-level wording around legacy skill context with unambiguous prompt/context terminology. Record the preference in repository guidance and code review guidelines. Co-authored-by: openhands --- .agents/skills/code-review.md | 3 ++- AGENTS.md | 1 + sdk/guides/skill.mdx | 14 +++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.agents/skills/code-review.md b/.agents/skills/code-review.md index a03d8f0e7..90b9134d4 100644 --- a/.agents/skills/code-review.md +++ b/.agents/skills/code-review.md @@ -46,6 +46,7 @@ It's OK to approve a docs/ PR on the basis of a linked upstream PR, they will be - **Field/attribute names** on data classes and models are correct - **Supported values** (e.g., format strings like `"github:owner/repo"`) are actually handled in code - **Behavioral descriptions** match the implementation logic +- **Prompt/context descriptions** distinguish between initial injection/construction, conversation-history inclusion, LLM context, and actual request transport. Flag ambiguous wording such as “sent with each request” unless the docs are intentionally describing transport-level behavior. - **Example code** would actually run without errors ### How to verify @@ -90,7 +91,7 @@ Include a "Source Verification" section that lists the key claims verified and t | Documentation Claim | Verified? | Source Evidence | |---------------------|-----------|-----------------| -| Legacy skills with `trigger=None` inject full content every turn | ✅ | [agent.py#L150](permalink) | +| Legacy skills with `trigger=None` are included in the initial system prompt and remain in LLM context for subsequent turns | ✅ | [agent.py#L150](permalink) | | `load_skills_from_dir()` returns tuple of (skills, repo_skills) | ✅ | [context.py#L85](permalink) | | AgentSkills format uses progressive disclosure | ❌ Incorrect | [agent.py#L200](permalink) shows... | ``` diff --git a/AGENTS.md b/AGENTS.md index a8e99f357..de1edc63b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -151,6 +151,7 @@ Workflow: `.github/workflows/sync-agent-sdk-openapi.yml` - Follow the style rules in `openhands/DOC_STYLE_GUIDE.md`. - Use Mintlify components (``, ``, ``, etc.) where appropriate. - When linking internally, prefer **absolute** doc paths (e.g. `/overview/quickstart`). +- When documenting prompt/context behavior, avoid ambiguous transport phrasing like “sent with each request” unless discussing the actual API transport. Prefer precise wording such as “included in the initial system prompt” and “remains part of the conversation/LLM context for subsequent turns.” - Cloud integration docs live under `openhands/usage/cloud/`, and pages surfaced in **Documentation → Integrations → Cloud API** must also be added to the `Cloud API` group in `docs.json`. ### Mintlify tab ownership diff --git a/sdk/guides/skill.mdx b/sdk/guides/skill.mdx index 38ad1b852..5bf4d441f 100644 --- a/sdk/guides/skill.mdx +++ b/sdk/guides/skill.mdx @@ -17,11 +17,11 @@ Understanding where skill content appears in the prompt is critical. The behavio |--------------|---------|----------------------|-----------------| | **AgentSkills** (`SKILL.md`) | Any | `` (description only) | ✅ Yes — agent calls `invoke_skill()` | | **AgentSkills** (`SKILL.md`) | Has triggers | `` + auto-inject on match | ✅ Yes | -| **Legacy** (inline/`*.md`) | `None` | **`` (full content in the initial system prompt; sent with each LLM request)** | ❌ No | +| **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 | -**Token Usage Warning**: Legacy skills with `trigger=None` add their **full content** to `` in the initial `SystemPromptEvent`. That system message remains in conversation history and is sent with each LLM request, so the content still affects token usage on each turn. Consider using AgentSkills format (`SKILL.md`) for progressive disclosure instead. +**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. ### Prompt Structure @@ -33,7 +33,7 @@ Skills appear in different parts of the system prompt: + included in LLM context for each turn while retained in history --> [BEGIN context from [agents]] ... AGENTS.md content ... [END Context] @@ -102,7 +102,7 @@ agent_context = AgentContext( ``` -**Important**: Inline skills with `trigger=None` use **legacy format** behavior — full content is added to `` in the initial system prompt and sent with each LLM request. For large skills, consider using the AgentSkills `SKILL.md` format for progressive disclosure. +**Important**: Inline skills with `trigger=None` use **legacy format** behavior — full content is added to `` in the initial system prompt and remains part of the conversation context for subsequent LLM calls. For large skills, consider using the AgentSkills `SKILL.md` format for progressive disclosure. ## Trigger-Loaded Context @@ -893,7 +893,7 @@ repo_skills, knowledge_skills, agent_skills = load_skills_from_dir(skills_dir) | Return Value | Source Files | Injection Behavior | |--------------|--------------|-------------------| -| **repo_skills** | `repo.md`, `AGENTS.md`, `.cursorrules` | Full content in `` in the initial system prompt; sent with each LLM request | +| **repo_skills** | `repo.md`, `AGENTS.md`, `.cursorrules` | Full content in `` in the initial system prompt; included in LLM context for each turn | | **knowledge_skills** | `knowledge/` subdirectories, `*.md` with triggers | Listed in ``, auto-inject on trigger | | **agent_skills** | `SKILL.md` files (AgentSkills standard) | Listed in ``, agent calls `invoke_skill()` | @@ -1240,7 +1240,7 @@ Skill( # API Guidelines ... 2000 lines of detailed documentation ... """, - trigger=None, # Always-on context - uses tokens on each LLM request! + trigger=None, # Always-on context - affects token usage on each turn! ) ``` @@ -1273,7 +1273,7 @@ agent_context = AgentContext(skills=list(skills.values())) | Aspect | Legacy `trigger=None` | AgentSkills `SKILL.md` | |--------|----------------------|------------------------| -| Token usage | Full content in system prompt, sent with each LLM request | Description only (~100 chars) | +| Token usage | Full content in system prompt; included in LLM context for each turn | Description only (~100 chars) | | Model control | None — always present | Agent decides when to read | | Scalability | Limited by context window | Many skills without token bloat |