Skip to main content
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); and in-conversation capture (capture_skill from pasted text / URL / folder, code inert until gaia skill promote — see Capture).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.
A skill is not an agent: it has no loop, no model, no autonomy. It is the reusable middle layer the agent system is currently missing.

Anatomy of a skill

A GAIA skill is a directory whose only required file is SKILL.md. Everything else is optional and loaded on demand.
scripts/ and references/ are the Agent Skills standard’s conventional names, not reserved ones: GAIA treats every file in the skill folder the same way, so any folder name (reference/, docs/, templates/) works as long as the body points at it by its path relative to the skill folder. This shape yields two flavors that sit on one spectrum:
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.
A single skill may be both — instructions and tools. The runtime treats the two flavors uniformly; the only difference is whether a 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 same name 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. A malformed skill under a read-only claude-import root is another tool’s file, so gaia skill list reports it as a warning and still exits 0.
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.
The user root is also the registry-lock root: 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 locally has no lock entry until gaia skill lock --relock records one as source: local (an import records its own, as source: imported). Loading a skill from the user root does check it against the lock: a community / verified install whose files changed since is refused, because those are not the bytes its signature covered. If the lock itself is unreadable, every user-root skill is refused — including local ones — since nothing can tell an attested skill from a local one at that point.

Progressive disclosure

A skill is loaded in three levels, mirroring the standard — long reference material costs nothing until it’s actually needed:
1

Metadata (one line in the prompt)

name + description of every installed skill are listed in the system prompt, one line each — about a line per skill for the whole library, instead of any skill body sitting in the prompt on every turn. See Skill catalogue.
2

Body (on trigger)

When a skill is selected — by the model calling load_skill, or by the user asking for it — the SKILL.md Markdown body is injected and the skill’s declared tools are registered into the agent’s tool registry. A loaded body does not stay resident forever: it collapses to a one-line menu entry on turns that no longer relate to it.
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. Nothing under the skill folder is ever added to the prompt automatically. The load_skill tool returns the skill’s directory so the model can join relative paths onto it, and Agent.load_skill grants the agent’s file tools read-only access to that folder — so a skill installed in ~/.gaia/skills stays readable even when the session’s allowed paths don’t cover it. Writes there are still refused, and the grant is withdrawn by unload_skill. From Python, SkillManager.resource_path resolves a bundled file and rejects paths that leave the skill folder.

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 the skills: / skill_sets: blocks in gaia-agent.yaml, which every Agent subclass resolves at construction — so a custom harness consumes an installed hub skill with no code change. Either way the agent points SKILL_DIRS at any skills/ 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.
At runtime an agent resolves its skill set, then loads each per the disclosure levels above:
Agent.load_skill(name, *, manager=None) -> Skill does exactly this, in this order:
  1. Resolves name across the roots by precedence (SKILL_DIRS → ~/.gaia/skills → read-only .claude/skills) using the agent’s lazily-built skill_manager. Pass manager= to resolve against a different SkillManager.
  2. Parses and validates the manifest, body included.
  3. Gates permissions before anything is registered — a refused skill leaves no tools and no prompt fragment behind (see Permission & security model).
  4. Registers the skill’s @tool functions from tools.py under <skill-name>/<tool>, so two skills may both provide search_web — unless the skill is a capture whose code is not yet trusted, in which case tool registration is deferred (see Capture).
  5. Merges any resolved ConnectorRequirements into REQUIRED_CONNECTORS — per instance, never mutating the ClassVar, so one agent’s skill never leaks requirements into a sibling.
  6. Grants the agent’s path validator read-only access to the skill’s folder, so the file tools can open the files its body references.
  7. Rebuilds the system prompt, injecting the body via get_skills_system_prompt().
Loading is idempotent (a second 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.

Capture: code inert until promoted

The flagship agent’s capture_skill tool (backed by gaia.skills.capture.capture_skill) brings a skill in from the conversation: pasted SKILL.md text, an http(s) URL (a raw SKILL.md or a .zip bundle), or a local folder/.zip, classified in that precedence. It is confirmation-gated (over REST/non-TTY it is denied, not prompted), the fetch rides the SSRF-guarded WebClient (loopback only via GAIA_WEB_ALLOWED_HOSTS), and every capture runs the static security audit first — a BLOCK verdict refuses the capture and writes nothing. What lands lands at the experimental tier with its provenance recorded in skill-lock.json (source: "captured", origin, captured: true). The capture lifecycle separates instructions from code: The deferral exists because a pasted body only reaches the prompt, while tools.py is imported into the agent’s own process — so code-trust is a terminal, human step (promote), never a chat approval. Trust binds to the audited bytes: editing a promoted skill re-defers its code, and a failed re-promote revokes the earlier trust. An instruction-only capture has nothing to defer and simply loads.

Invocation

Once a skill is in scope, it is triggered by the model calling load_skill — because the skill catalogue below shows the work matches it, or because the user named it. A user-facing /web-research slash command follows the Agent UI panel and is not yet available. 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 catalogue

A user should never have to know a skill’s name. The flagship lists every installed skill in its system prompt, one line each (name and description), and the model loads a skill when the work matches it — at the start of a task or partway through, for example after a command the skill grants was refused.
Write the description for this. The model decides from it. Say what the skill does and when to use it, naming the objects, formats, and services a user would mention — github-triage names the “unread notification inbox”, the “issue backlog”, and the gh CLI.Use the user’s plainest word, not the precise one. inbox-triage originally said only “mailbox” and “email” — a request phrased as “anything urgent in my mail?” shares no word with either, so name the casual synonyms too (mail, unread, deal with, rundown).
The list depends only on what is installed, so it is identical on every turn and stays in the cached head of the prompt. It costs about one line per skill (roughly 1,000 tokens for the starter pack) plus a ~96-token sourcing rule: facts about live external state must come from a tool call, never from memory. A description is cut at 480 characters in the list, so put the “use when” triggers early. A skill already under LOADED SKILLS stays listed, and the model is told not to load it again — dropping it would change the block from turn to turn. Read-only .claude/skills imports are loadable by name but not listed — they are another host’s marketplace, written for working on a repo rather than answering a user’s question. A CLI granted through shell:execute:<binary> is not refused before its skill is loaded: each call goes to the user’s approval prompt instead. The grant is pre-approval — loading the skill lets the policy’s reads run without a prompt. A call outside the policy (git push, gh auth token) is not covered by any grant: it is shown verbatim with a risk note for a one-time approval, with or without the skill loaded, and one that deletes or discards work stays refused. With nobody to answer the prompt, the call is refused. Tunable: GAIA_SKILL_DISCOVERY (0 hides the list).

Learned changes: the effective skill vs. the shipped one

A skill you install is a starting point, not a fixed contract. An agent can adapt one it was given — correcting a command that fails on your machine, or reshaping a procedure written around a workflow you do not follow — and what it learns is stored in agent memory, resolved over the authored body at load time. The SKILL.md on disk is never written. What changes is the effective skill the agent runs, which is why two machines with the same installed skill can behave differently, and why gaia skill deltas <name> exists to show you when that is happening. Three properties are load-bearing:
  • Learned changes replace, they do not accumulate. A correction substitutes the text it fixes rather than being appended beneath it, and a second correction to the same section supersedes the first. Ten corrections to the same section cost what one costs; across different sections they add up, and a total that would push the skill past its overlay budget is refused at write time with the reason. Adapting a skill to the workflow you actually use usually makes it smaller, because the workflow you do not use gets replaced rather than kept.
  • It applies at once, and you are told. An agent that had to ask permission to act on what it just learned would not be adaptive, so a correction takes effect immediately. What you get instead of a prompt is an announcement: the agent says which skill and section changed, why, and prints the exact command that undoes it. gaia skill deltas <name> --diff shows the effective skill against the shipped one at any time.
  • Only your words can teach it. A lesson persists across sessions, so it may only be recorded in a turn where nothing but your own message has arrived. The moment any tool has returned anything in that turn — a fetched web page, an email, an issue body, a command’s output, another skill’s text, an MCP call — the write is refused and the agent has to tell you the correction instead of adopting it. That is what stops text merely shaped like an instruction from rewriting the agent’s standing instructions. The check is per turn: content pulled in an earlier turn is not tracked, which is why the announce-and-undo above is the other half of this and not a nicety.
  • It is always recoverable. gaia skill deltas <name> --reset returns the agent to the shipped skill from its next launch, and --archived lists what was retired. GAIA_NO_LEARNED_SKILLS=1 runs every agent in every process with none of them — including the ones behind the daemon and the Agent UI, which no chat flag reaches — and gaia chat --no-learned-skills does the same for one chat session. Either way the composed prompt is byte-identical to a build with no overlay.
A learned change can only rewrite instruction text. It cannot edit frontmatter, so it can never widen a permission or raise a security tier; it cannot introduce a tool, because tools come from frontmatter and the live registry, never from prose; and it cannot change when a skill activates, because selection embeds a skill’s name and description, never its body. Removing a section is a human action, not one the model can take. Design and current status: Adaptive Skills.

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. Hardcoding load_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:
The field grammar (reference shapes, 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.
An undeclared set name never falls back. Whether it came from --skill-set or from a selector hook, a name the agent does not declare raises SkillSetError naming the valid sets. Launching with the wrong capability bundle is worse than not launching — a work mailbox quietly handed the personal set is precisely the silent-wrong-answer this indirection exists to prevent (GAIA’s no-silent-fallbacks rule).The same applies to a missing signal. If a selector cannot determine the context, it must return None — which resolves the declared default explicitly — rather than guess a set.

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.
It ships with those blocks commented out, pending an eval that shows the skills improve triage, so the released agent resolves no set and loads no skills — active_skill_set is None, loaded_skills == {}, and a --skill-set request is a startup error. Treat it as a wiring reference, not as evidence that a shipped agent has a set active.
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.
The shipped rule: connector-bridged permissions only. Everything else is refused, not faked.A skill declaring any local-capability permission raises SkillPermissionError at load time, naming the offending grants and pointing at #1019. It is never silently loaded and never wrapped in a sandbox that does not exist — an unenforceable grant is a refusal, not a warning. To load such a skill today, drop those permissions and use the agent’s own tools for local access.<domain>:none is an explicit denial — it asks for nothing, so it is always inert: filesystem:none loads fine and produces no requirement. Refusing it would reject a skill for declaring less than the default.
How the bridge resolves. There is no second grant ledger — a bridged permission becomes the same ConnectorRequirement 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 bare mcp:connect, or one naming an unknown id, fails loudly with the available ids and a pointer to gaia 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 reserved network pseudo-connector-id. This is a declaration surface only: it records the egress the skill asks for so gaia skill info and a future egress policy can read it. Phase 1 does not enforce network egress.
Security tiers gate what a skill may do. Every skill resolves to one of 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.
A tier is provenance, not containment. experimental is not “sandboxed” — there is no sandbox in v1. A skill’s tools.py runs in the agent’s own process at every tier, which is why local-capability permissions are refused outright rather than granted to a trusted-looking skill.
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.

Download-server integration

Skills are distributed and versioned alongside agent packages. They are a fourth catalog lane on the existing download server — not a second registry: the same Worker, the same R2 bucket, the same index.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) — including a digest of the installed files, which gaia skill lock compares against later; publish the other direction with gaia skill publish. Off-hub, gaia skill import <folder|zip|https-url> copies a skill in after the same audit and consent checks (always stamped experimental — no hub provenance to trust) and gaia 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 points SKILL_DIRS at it and the folder becomes the highest-precedence root.

The catalog lane

A published skill is a catalog entry with type: "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.
A consumer that lists installable agents must filter on type. A skill installs into ~/.gaia/skills/, not through the agent installer, so rendering every catalog entry as an agent offers an install path that cannot work. Package ids are one namespace across all lanes: a skill name can never collide with an agent id (the hub rejects the publish).
Publish gates. Two rules apply to the skill lane specifically:
  • A SemVer version is required to publish, and 0.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_TOKENS secret, the same curation model agents use. A contributor opens a PR against skills/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. community and verified are refused without a cleared report, and a BLOCK verdict is rejected. A REVIEW verdict 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. An experimental skill may publish without a report and is recorded as unaudited rather 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 the SKILL.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’s cleared_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.
Verification. Install-time integrity uses the same mechanism as agents: the per-skill 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:
Set 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 a name/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 @tool functions 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 at experimental, forcing REVIEW at community, and gated on a medium at verified. Run it yourself with gaia 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.