Tracking issue: #285 · Milestone: Agent Hub Platform [OSS] · Status: Phase 1 (#888) and the marketplace (#2467) shipped — the local-capability sandbox and the
/api/skills UI surface remain proposedShipped: the gaia.skills runtime — SKILL.md parser + validator, the three
discovery roots with auditable precedence, progressive disclosure,
<skill>/<tool> tool registration, Agent.load_skill / unload_skill, and
gaia skill list|info|create|import|export. Plus the declarative
skills: / skill_sets: manifest blocks and per-launch
skill-set selection (#2466),
whose reference implementation is the email agent (which currently ships with its
sets commented out — see below); and
gaia skill migrate --from openclaw|hermes|auto
(#692), which converts a third-party
skill to GAIA format at the experimental tier.Also shipped (hub side): the skill catalog lane — POST /publish/skill,
the skills/ R2 namespace, server-side validation of the SKILL.md grammar
(#2467), and the pre-publish audit
gate, which binds a verdict to the skill, version, tier, and bytes it audited
(#2468).Also shipped (client side): the marketplace verbs
search/install/remove/publish, bundle signing + install-time tier
ceilings, skill-lock.json, and the local resolver that turns a declared
skills: / skill_sets: block into loaded skills at construction
(#2467); plus gaia skill audit, the
engine that produces the verdict the hub gate checks
(#2468).Still proposed: the local-capability permission sandbox
(#1019), plus the /api/skills router
and Agent UI Skills panel.This spec defines the architecture and integration model for skills. The
on-disk SKILL.md schema (full field reference, permission grammar, tier
promotion) lives in Skill Format — this document builds
on it rather than repeating it.Problem
A GAIA agent’s capabilities are compiled in. Tools arrive through hardcoded mixins (RAGToolsMixin, ShellToolsMixin, …) wired into the agent class and
shipped inside the wheel. A user who wants a new capability — a domain checklist,
a wrapper around an internal API, a specialized procedure — has no path short of
forking the agent or editing AMD source.
Meanwhile the wider ecosystem has converged on a portable format for exactly
this: the Agent Skills open standard, used by Claude
Code and a growing set of agent runtimes. A skill is a folder with a SKILL.md
that the agent loads only when relevant, so a library of capabilities costs
almost nothing until used.
Goal
GAIA agents compose skills at runtime the same way they compose tools today — declaratively, per-agent, least-privilege — and the format is the Agent Skills open standard so the existing ecosystem of skills works in GAIA unchanged. A plain Claude Code skill drops into~/.gaia/skills/ and runs; a GAIA skill adds
typed tools, a permission model, and security tiers on top of that baseline.
Skill vs Tool vs Agent
These three are layered, not interchangeable. The distinction is the foundation of everything below.
The one-line model: a tool is a function, a skill is a packaged capability an
agent composes, an agent is the loop that decides when to use it.
Anatomy of a skill
A GAIA skill is a directory whose only required file isSKILL.md. Everything
else is optional and loaded on demand.
- Instructional skill (standard)
- Tool skill (GAIA extension)
Frontmatter + Markdown body, no code. The body is context, not a function —
a procedure, checklist, or domain knowledge injected into the agent when the
skill triggers. This is the plain Agent Skills
shape and is byte-for-byte compatible with Claude Code skills.
tools block (and its
backing code) is present.
Manifest format
The baseline is the Agent Skills standard:name and description are the only
required frontmatter fields, and description is the trigger signal the model
reads to decide relevance. GAIA layers a superset on top — version, permissions,
typed tools, security tier, requirements — defined in full in
Skill Format. The mapping:
GAIA-specific fields are nested under
metadata.gaia so a standard runtime
ignores them losslessly — the full grammar is in
Skill Format. The standard’s
optional compatibility and allowed-tools keys are not part of GAIA’s
adopted base (they overlap metadata.gaia); a skill using them still parses, and
GAIA ignores them.
The compatibility rule: GAIA reads a bare standard SKILL.md as a valid
instruction-only skill. Missing GAIA fields take conservative defaults —
security_tier: experimental (the most restrictive tier — installing one takes an
explicit --allow-experimental), no tools, no permissions. A skill author adopts
GAIA features incrementally; nothing is required to make an existing standard
skill load.
Division of authority: this spec owns the integration surface (discovery,
scoping, progressive disclosure); Skill Format owns the
field grammar (the
SKILL.md schema, permission grammar, tiers). Both adopt
Agent Skills (agentskills.io, as implemented by
Claude Code) as the base standard, and treat Hermes and OpenClaw as compatible
third-party formats nested under metadata.<vendor>.Discovery, loading, and scoping
Discovery locations
v1 ships three roots, highest precedence first. A later root never overrides a skill of the samename found earlier.
Roots 1–2 are native GAIA. Root 3 lets an existing Claude Code skill library work
in GAIA with zero migration (see Compatibility).
Precedence is auditable, not silent: a shadowed lower-precedence copy stays
visible via
SkillManager.shadowed() and is reported by gaia skill list and
gaia skill info. A skill directory that fails to parse is likewise surfaced
(SkillManager.discovery_errors, a non-zero exit from gaia skill list) rather
than quietly omitted.
Deferred to a later phase — not dropped:
- Project-local
./.gaia/skills/. A per-working-directory native root. It would slot in above the user root, so adding it later only ever narrows scope; nothing authored against v1 changes meaning. Do not add it to the runtime without updating this table.
gaia skill install writes
~/.gaia/skills/skill-lock.json beside the skills it installs
(#2467). The lock is provenance, not a
fourth discovery root — discovery still reads the directory, and a skill created
or imported locally simply has no lock entry.Progressive disclosure
A skill is loaded in three levels, mirroring the standard — long reference material costs nothing until it’s actually needed:1
Metadata (always in context)
name + description of every discovered, in-scope skill are listed for the
model. This is the only always-resident cost — a few tokens per skill.2
Body (on trigger)
When the model judges a skill relevant (its
description matches the task) or
the user invokes it explicitly, the SKILL.md Markdown body is injected and the
skill’s declared tools are registered into the agent’s tool registry.3
Resources (on demand)
Files the body references (scripts, reference docs, templates) load only when the
agent reads or executes them — not at trigger time.
Scoping into an agent
A skill is never globally active. It is scoped to an agent two ways:- Declared — either imperatively, by calling
load_skill(typically in_register_tools), or declaratively, via theskills:/skill_sets:blocks ingaia-agent.yaml, which everyAgentsubclass resolves at construction — so a custom harness consumes an installed hub skill with no code change. Either way the agent pointsSKILL_DIRSat anyskills/folder it bundles. - Granted — a skill carrying connector-bridged permissions contributes
ConnectorRequirements to the agent, reusing the per-agent grant model from the connectors framework. An agent only ever sees skills explicitly in scope (least privilege); discovery does not imply activation.
Agent.load_skill(name, *, manager=None) -> Skill does exactly this, in this
order:
- Resolves
nameacross the roots by precedence (SKILL_DIRS→~/.gaia/skills→ read-only.claude/skills) using the agent’s lazily-builtskill_manager. Passmanager=to resolve against a differentSkillManager. - Parses and validates the manifest, body included.
- Gates permissions before anything is registered — a refused skill leaves no tools and no prompt fragment behind (see Permission & security model).
- Registers the skill’s
@toolfunctions fromtools.pyunder<skill-name>/<tool>, so two skills may both providesearch_web. - Merges any resolved
ConnectorRequirements intoREQUIRED_CONNECTORS— per instance, never mutating the ClassVar, so one agent’s skill never leaks requirements into a sibling. - Rebuilds the system prompt, injecting the body via
get_skills_system_prompt().
load_skill of the same name returns the
already-loaded Skill) and reversible — unload_skill(name) removes the
tools and the prompt fragment, returning True if the skill was loaded.
loaded_skills is the {name: Skill} view of what is currently active.
A manifest whose declared tools don’t match its tools.py fails loudly and
loads nothing: not a missing tool, an undeclared extra tool, a parameter-name
mismatch, a required/optional flip, or a type contradiction. There is no
partial load — the tool registry is restored byte-for-byte on any failure.
Tier ceilings are enforced at install, not at load.
gaia skill install verifies the bundle’s signature, collapses the claimed tier to
the one that signature earned, and refuses a permission above the resulting
ceiling (#2467). What is still absent is
the load-time ceiling and the run-time sandbox behind it
(#1019): a skill placed in a discovery
root by hand — gaia skill create, gaia skill import, or a plain cp — never
passed through install, so load_skill gates on domain (below), not on tier.Invocation
Once a skill is in scope, it is triggered either by the model (description match — automatic) or explicitly by the user.load_skill in code is the
shipped explicit path; a user-facing /web-research slash command follows the
Agent UI panel and is not yet available. This matches Claude Code’s dual-invocation model. The
standard’s allowed-tools/disallowed-tools keys are parsed but not used as
a permission mechanism (see Skill Format) —
GAIA’s permissions come from metadata.gaia.
Skill sets
One agent, more than one job. The same email assistant should behave differently against a personal mailbox than against a work one; the same code agent should carry different conventions in different repositories. Hardcodingload_skill
calls forces the author to pick one behaviour, or to ship two agents.
A skill set is a named, mutually-exclusive bundle of skills, exactly one of
which is active per launch. It is declared in gaia-agent.yaml, layered on top
of an always-on skills: list:
version, required, set-name rules, and
every fail-loud parse error) is specified in
Skill Format.
This section owns selection.
Sets overlap; they are not a partition. inbox-triage above belongs to both
sets — a shared capability stays available in every context it applies to. Only
the always-on list is exclusive: a set may not re-declare a skill that is already
always on.
Selection order
1
Explicit — the generic override
The
skill_set= argument to Agent.__init__, surfaced by an agent’s entry point
as --skill-set <name>. Highest precedence, and never second-guessed — an
explicit request is not re-examined against agent state.2
The agent's selector hook
Agent.select_skill_set() -> str | None. Override it to key the active set off
something the agent already knows — a connected account’s type, a workspace mode,
a device profile. Returning None means “no opinion” and defers to the default.
Consulted only when nothing explicit was passed.3
`default_skill_set`
The manifest’s declared default. Required whenever
skill_sets is non-empty, so
a launch that selects nothing still resolves a set explicitly rather than
running with an arbitrary one.resolve_skill_set() returns which set won and which rule chose it
(explicit / selector / default / none), so “why is this set active?” is
answerable from a log line rather than by reading code.
Loading
load_skill_set() runs at the end of Agent.__init__ and loads the always-on
list plus the resolved set, in declaration order, each through the same
load_skill path (and therefore the same permission gate). A skill declared
required: false that no discovery root provides is logged and skipped; every
other failure propagates.
Calling it again with a different name switches sets: skills the previous
resolution loaded and the new one does not declare are unloaded first, so a stale
set never bleeds into the prompt. active_skill_set is the current answer, or
None for an agent that declares no sets — which is every agent that has not
adopted the blocks, whose behaviour is unchanged.
A skill body costs context. Loading a set injects each skill’s Markdown body
into the system prompt, which is prompt budget the agent’s own tool results no
longer have. An agent with a tight context envelope must account for the loaded
set’s cost, not assume it is free — the email agent’s bulk-triage path subtracts
it from its result-envelope budget for exactly this reason — and its three-skill
personal set still cut that envelope from 6,144 tokens to 4,810 (the four-skill
work set, to 4,070). Budget the set, and keep bodies short.Reference implementation: the email agent
The email agent (hub/agents/email/)
is the worked example in the tree. It bundles six instruction-only skills and its
manifest carries two sets — personal (triage, newsletter digests, travel
itineraries) and work (triage, meeting scheduling, action-item extraction,
escalation routing) — with inbox-triage deliberately in both.
Its selector maps the connected mailbox’s account type onto a set: a personal
Microsoft account gets personal, a work/school account gets work. The kind is
derived at connect time from the id_token tid claim (personal accounts always
carry the well-known consumers tenant; work/school accounts carry the
organization’s Entra tenant id) and recorded on the connection, so no agent
re-derives it. A Gmail mailbox has no equivalent claim, so its kind is genuinely
unknown and the selector returns None — the manifest’s default_skill_set
applies, explicitly.
For a step-by-step author walkthrough see
Composing skills and skill sets.
Permission & security model
Instruction-only skills carry no code, but they are not free of risk — a body is injected into the model’s context and can attempt prompt injection. Tool skills carry the full risk of the code they run. The model gates both. How the bridge resolves. There is no second grant ledger — a bridged permission becomes the sameConnectorRequirement an agent already declares via
REQUIRED_CONNECTORS:
mcp:connect:<connector-id>→ a requirement for that connector. The scope is mandatory and must name a connector in the live catalog: a baremcp:connect, or one naming an unknown id, fails loudly with the available ids and a pointer togaia connectors list.network:<level>:<scope>where the scope names a catalog connector → a requirement for that connector, scoped to the level.network:<level>[:<scope>]with no matching catalog connector → a requirement against the reservednetworkpseudo-connector-id. This is a declaration surface only: it records the egress the skill asks for sogaia skill infoand a future egress policy can read it. Phase 1 does not enforce network egress.
verified / community / experimental (defaulting to experimental). The tier
sets the permission ceiling and the grant behavior — auto-grant, prompt, or an
explicit --allow-experimental opt-in — and gaia skill install enforces it, as
defined in
Skill Format → Permission model and Security tiers.
A tier is decided by the bundle’s signature, not its front-matter claim: the
claimed tier is collapsed to the one the signature earned.
Highlights as they apply at the agent boundary:
This ties the skill layer to the broader
security model: skills are a grant surface, audited and
scoped per agent, not a global capability.
Agent Hub integration
Skills are first-class Agent Hub artifacts, distributed and versioned alongside agent packages from the Agent Hub Restructure. They are a fourth catalog lane on the existing hub — not a second registry: the same Worker, the same R2 bucket, the sameindex.json, the same signing and
immutability rules (#2467).
Distribution. A skill ships one of two ways:
- Standalone — from the hub with
gaia skill install <name[@range]>, which resolves a SemVer range against the catalog, verifies the artifact checksum and the bundle signature, enforces the tier ceiling, and records the outcome in~/.gaia/skills/skill-lock.json(#2467, #647); publish the other direction withgaia skill publish. Off-hub,gaia skill import <folder|zip|url>copies a skill in (always stampedexperimental— no hub provenance to trust) andgaia skill export <name>produces the bundle. See Skill Format → CLI. - Bundled inside an agent package’s
skills/directory — version-pinned to that agent, no separate install. Shipped: the agent pointsSKILL_DIRSat it and the folder becomes the highest-precedence root.
The catalog lane
A published skill is a catalog entry withtype: "skill", served from the same
GET /index.json as every other package. Segmenting a Skills lane is a filter
on type, not a second document — which is why adding the lane broke no
existing consumer.
A skill entry reuses the shared catalog keys where the meaning is identical —
name, description, latest_version, security_tier, permissions — and
nests everything skill-specific under skill_metadata: the tools it provides,
the tools_required it consumes, its requirements block, and the audit record
below. Its readme carries the SKILL.md body, since for a skill that markdown
is the package’s primary doc.
Publish gates. Two rules apply to the skill lane specifically:
-
A SemVer
versionis required to publish, and0.0.0— the format’s “unversioned” sentinel — is rejected, so it can never become an immutable release. -
Skills enter the catalog by pull request. Publishing is not open to the
public — the Worker authenticates against a maintainer-held
PUBLISH_TOKENSsecret, the same curation model agents use. A contributor opens a PR againstskills/community/<name>/, the audit runs as a required check, and a maintainer publishes after merge. Merging is not itself a tier promotion. -
The pre-publish security audit
(#2468) gates the tier a skill may
claim.
communityandverifiedare refused without a cleared report, and aBLOCKverdict is rejected. AREVIEWverdict is currently refused too: holding it needs a quarantine lane and a maintainer sign-off path, which do not exist yet (#2675) — refusing beats admitting an unreviewed skill. Anexperimentalskill may publish without a report and is recorded asunauditedrather than stamped as having passed, so the catalog never implies an audit that did not happen. The verdict is bound to what it audited — skill, version, claimed tier, and a digest of theSKILL.md— so a report cannot be replayed onto a different publish, and a new version re-earns its verdict. The claimed tier must appear in the report’scleared_tiers, which is what makes “cannot claim a tier it did not clear” enforced rather than recorded. See Skill Format → Pre-publish security audit.
The gate resists replay, not forgery. The audit report is attached by the
publisher and is not yet signed, so nothing proves it was produced by a trusted
engine. Binding it to the skill, version, and audited bytes closes replay
(#2468); making it unforgeable needs a
signing key the publisher cannot mint
(#1710). Until then a recorded verdict
is marked
publisher-asserted — it means self-consistent, not vouched for.manifest.json records the Worker-computed SHA-256 of every published
artifact, and the installer checks the bytes it downloaded against it.
Versioning. Skills are SemVer’d independently of the framework and of the
agents that use them. Major = breaking tool-signature or permission change;
minor = additive; patch = fixes. Agent manifests pin ranges. Each version
re-earns its audit verdict.
The gaia-agent.yaml link. An agent declares the skills it composes in its
manifest, and the base Agent resolves and loads them at construction via
load_declared_skills() — see Skill sets:
SKILL_MANIFEST to the manifest’s path to opt in, or leave it unset and the
agent auto-detects a gaia-agent.yaml beside its module. A skills/ folder in
either of those two places is auto-detected the same way, so the skills a package
ships resolve for every class in it — SKILL_DIRS is only needed for a bundled
folder somewhere else. An agent that sets neither is unaffected.
The block resolves the way dependencies: resolves agent-to-agent links:
topological order (a skill providing a tool another declares in tools_required
loads first), the highest installed version satisfying the range, and fail-loud on
a version conflict or a circular dependency. Resolution is local — the skill
must already be installed, so an agent never pulls code from the network at
startup. A missing required: true skill raises with the gaia skill install
command that fixes it; a missing optional one is logged and skipped.
skill_sets: — naming a reusable group instead of individual skills
(#2466) — expands to the same
requirements and resolves through the same path, so there is one resolver.
Agent Skills / Claude Code compatibility
GAIA implements the Agent Skills open standard as its baseline format, so reuse is bidirectional and lossless for the common case. Standard skill → GAIA. A folder with aname/description SKILL.md loads
as an instruction-only skill with no changes. Claude Code skill libraries in
.claude/skills/ are discovered directly (read-only import root). gaia skill import <path> copies one into ~/.gaia/skills/ and stamps it
experimental for explicit promotion.
Third-party format → GAIA. Hermes and OpenClaw/ClawHub nest their fields
under metadata.<vendor>, the same pattern metadata.gaia uses, so
gaia skill migrate --from openclaw|hermes|auto reads the foreign namespace and
writes a metadata.gaia block (#692).
Fields GAIA models are consumed; the rest stay under metadata.<vendor> and are
reported. Migrated skills land experimental, and one needing a local capability
GAIA cannot yet enforce is refused with the reason rather than downgraded — see
Skill Format → Cross-format compatibility.
GAIA skill → standard runtime. A GAIA skill degrades gracefully: the
frontmatter name/description and Markdown body are standard, so the
instructions work anywhere. Bundled scripts run if the host can execute them.
GAIA-specific frontmatter (permissions, security_tier, typed tools) is
ignored by runtimes that don’t understand it rather than breaking the parse.
What each side adds:
The principle: be a strict superset of the open standard. Anything that runs
as an Agent Skills skill runs in GAIA; GAIA adds the enforcement and typing the
standard intentionally leaves open.
Non-goals
- Not a new format. GAIA does not invent a skill format; it adopts Agent Skills and extends the frontmatter. A competing schema is explicitly out of scope.
- Not a replacement for tools or mixins. Inline
@toolfunctions and framework mixins remain valid for agent-specific, non-distributable capability. Skills are for the reusable, portable middle layer. - Not autonomous. Skills add capability to an agent’s loop; they do not run their own loop or make their own model calls.
Open questions
- Tool-skill execution in foreign runtimes. How much of a GAIA tool skill should degrade vs. fail when run by a host that can’t enforce its permissions?
Body-injection trust.Resolved by #2468. Every skill’s body is scanned for injection patterns at publish, and the claimed tier decides the consequence rather than a separate policy: advisory atexperimental, forcingREVIEWatcommunity, and gated on amediumatverified. Run it yourself withgaia skill audit ./<skill>/. The precision trade-offs (fenced text is downgraded one step rather than ignored; a prohibition is not a directive) are recorded in Skill Format → Open questions.- Versioning of bundled vs standalone. Reconciling an agent-pinned bundled
skill with a newer standalone install of the same
name.
Related
- Skill Format —
SKILL.mdschema, permission grammar, tiers, CLI - Composing skills and skill sets — author walkthrough
- CLI Reference → Skills —
gaia skill list|info|create|import|export - Agent Hub Restructure —
gaia-agent.yaml, packaging, distribution - Agent Hub — marketplace vision
- Security Model — grants, audit trail, sandboxing
- Agent Skills standard · Claude Code skills