Skills
Package instructions and bundled files that the model can auto-invoke or users can trigger with a slash command.
Skills extend what the model can do by packaging reusable instructions as
portable units. Each skill is a SKILL.md file plus optional bundled
scripts, references, and assets. You author skills with the
Agent Skills SKILL.md format, and manage them
through an OpenAI-compatible /v1/skills API — skills are immutable and
versioned, with a default_version/latest_version pointer, just like the
OpenAI Skills API (plus Hadrian's multi-tenant ownership extension).
What a skill looks like
Every skill is a directory with at minimum a SKILL.md file containing YAML
frontmatter and Markdown instructions:
---
name: pdf-processing
description: Extract PDF text, fill forms, merge files. Use when handling PDFs.
allowed-tools: Bash(python:*) Read
---
# PDF Processing
## When to use this skill
Use this skill when the user needs to work with PDF files.
## How to extract text
1. Run `scripts/extract.py <input.pdf>`
2. Parse the output into a structured summary.Optional bundled files (kept alongside the SKILL.md) become available to the model on demand:
pdf-processing/
├── SKILL.md # Required: instructions + metadata
├── scripts/ # Optional: executable code
│ └── extract.py
├── references/ # Optional: docs
│ └── REFERENCE.md
└── assets/ # Optional: templates, data files
└── template.mdFrontmatter fields
| Field | Required | Description |
|---|---|---|
name | Yes | Unique per owner. 1–64 lowercase ASCII alphanumerics or hyphens, no leading/trailing/consecutive hyphens. |
description | Yes | 1–1024 chars. Drives model invocation — front-load trigger phrases ("Use when…", "when the user mentions…"). |
user_invocable | No | Default true. Set to false to hide from the / menu; useful for background-knowledge skills the model auto-loads. |
disable_model_invocation | No | Default false. Set to true for skills that should only run when the user explicitly picks them. |
allowed_tools | No | Space-separated list (or YAML array) of tool patterns the skill may use. |
argument_hint | No | Shown during / autocomplete, e.g. [issue-number]. |
metadata | No | Arbitrary key/value pairs preserved verbatim. |
Any additional keys are preserved in frontmatter_extra so unknown
spec-adjacent fields (for third-party agents) round-trip cleanly.
How the model invokes a skill
Hadrian registers a single Skill function tool with the model whenever one
or more skills are enabled for the current session. The tool's description
lists each enabled skill by name and description — the model matches user
intent against those entries and calls Skill({command: "<name>"}) to load
the full instructions.
The executor runs in the browser:
- First call —
Skill({command: "<name>"})returns the skill'sSKILL.mdbody plus a manifest of bundled files. - Follow-up calls —
Skill({command: "<name>", file: "<path>"})returns the contents of a specific bundled file referenced in the SKILL.md.
This matches Claude Code's progressive-disclosure model: the main instructions enter context once; reference files load only when needed.
Skills are not injected into the system prompt. The directory lives in the Skill tool's
description, so unused skills don't pollute every request.
Ownership & sharing
Like prompt templates, skills belong to one of four owners:
| Owner | Visible to |
|---|---|
user | Only the owning user. |
project | All members of the project. |
team | All members of the team. |
organization | All organization members, subject to RBAC rules on skill:list/read. |
Chat surfaces all skills the current user can reach. In the admin UI, manage project skills from Project → Skills.
Managing skills via the API
Skills live under the OpenAI-compatible /v1/skills surface. IDs are prefixed
strings (skill_…, version ids skillver_…) and timestamps are unix seconds.
| Method & path | Purpose |
|---|---|
POST /v1/skills | Create a skill (publishes version 1). |
GET /v1/skills | List skills for the current project / accessible owners. |
GET /v1/skills/{id} | Get a skill (with the default version's files). |
POST /v1/skills/{id} | Set the default version ({ "default_version": "2" }). |
DELETE /v1/skills/{id} | Delete a skill. |
GET /v1/skills/{id}/content | Download the default version as a zip bundle. |
POST /v1/skills/{id}/versions | Publish a new immutable version. |
GET /v1/skills/{id}/versions | List versions. |
GET /v1/skills/{id}/versions/{version} | Get a specific version. |
DELETE /v1/skills/{id}/versions/{version} | Delete a version. |
GET /v1/skills/{id}/versions/{version}/content | Download a specific version as zip. |
Versioning
Skills are immutable — content never changes in place. To update a skill,
publish a new version with POST /v1/skills/{id}/versions (pass "default": true to make it the new default, or repoint later with POST /v1/skills/{id}).
The skill projection (name, description, files) always reflects the default
version. Versions are numbered 1, 2, … and never reused; the default
version cannot be deleted (set another default first).
Upload formats
Create and version-create accept either:
application/json— a{ path, content }file array (Hadrian extension; what the chat UI sends).owner,name, anddescriptionmay be supplied explicitly or sniffed from the SKILL.md frontmatter.multipart/form-data— a directory offiles[]parts, or a single zip part.owner_type/owner_id/name/description/defaultride along as form fields.
GET …/content returns an application/zip bundle.
Ownership extension
OpenAI's skills are project-scoped; Hadrian keeps owner_type/owner_id
(organization/team/project/user) as a documented extension. On create, omit
owner to default to the API key's scope, or set it explicitly. List with
?owner_type=&owner_id= to scope to one owner, or omit both for everything the
caller can access.
Referencing skills from the Responses API
Attach skills to a /v1/responses request via a skill_reference:
{ "type": "skill_reference", "skill_id": "skill_…", "version": "latest" }skill_id accepts a prefixed id, a bare UUID, or the skill's name slug.
version is a positive integer or "latest"; omit it for the default version.
Invoking a skill as a user
Two equivalent ways:
- Slash command. Type
/<skill-name>in the chat composer. A popover lists matching skills; press Enter or Tab to commit. The composer prefixes your message with a request to use that skill. - Skills button. Click the Skills icon in the chat toolbar to open the popover. Check a skill to enable it for the session — the model may then auto-invoke it when relevant.
Importing skills
Use the Skills button's + menu to import.
From GitHub
Paste a GitHub URL (e.g. https://github.com/anthropics/skills) or
owner/repo. Hadrian walks the tree via the public GitHub Contents API,
finds every directory containing a SKILL.md, and bundles the adjacent
files. Preview the discovered skills and pick which to import.
The unauthenticated GitHub API allows 60 requests/hour. Hadrian caches directory listings in
sessionStorage for 10 minutes to soften this, and surfaces the remaining quota in the import
modal.
From the filesystem
Choose a folder via the file picker (browsers supporting
<input webkitdirectory> — Chromium-based and Safari). Hadrian reads every
file under directories containing a SKILL.md.
Binary files are rejected in v1 — skill contents are stored as UTF-8 text.
Configuration
Per-skill size and per-owner limits live under [limits.resource_limits] in
hadrian.toml:
[limits.resource_limits]
# Maximum skills per owner (org/team/project/user). Default: 5000.
max_skills_per_owner = 5000
# Maximum total size of a skill's files in bytes (SKILL.md + bundled).
# Default: 512000 (500 KiB). Set to 0 for unlimited.
max_skill_bytes = 512_000
# Maximum immutable versions retained per skill. Default: 0 (unlimited).
max_skill_versions_per_skill = 0The skill upload request body cap is separate, under [server]:
[server]
# Max request body for skill uploads (zip bundles / multipart). Default: 64 MiB.
skills_body_limit_bytes = 67_108_864v1 limitations
- Text-only files. Binary assets (images, PDFs) are rejected at the API boundary. Future work: base64 encoding or object-storage offload.
- Scripts are read-only in chat. The chat UI's
Skilltool can readscripts/files but cannot execute them — there is no client-side sandbox. Skills that require script execution won't work end-to-end in the chat UI. - Two invocation paths. The chat UI's
/<name>slash command andSkilltool are frontend-only. Separately, the Responses API resolves askill_referenceserver-side and mounts the referenced version's files into the shell-tool sandbox (under/skills/<id>), where scripts can run if the configured runtime supports it.