Skip to main content
Grounds on (exists today): src/gaia/skills/ — the shipped runtime: format.py (parser/writer/validator), manager.py (SkillManager, discovery + precedence), loader.py (<skill>/<tool> registration), permissions.py (grammar + connector bridge), cli.py (gaia skill), sets.py (the skills: / skill_sets: grammar), migrate.py (OpenClaw/Hermes → GAIA, #692) · src/gaia/agents/base/agent.py (Agent.load_skill, unload_skill, SKILL_DIRS, skill_manager, loaded_skills, SKILL_MANIFEST, load_skill_set, select_skill_set) · src/gaia/agents/base/tools.py (@tool, _TOOL_REGISTRY) · src/gaia/agents/registry.py (KNOWN_TOOLS, namespaced_agent_id) · src/gaia/connectors/providers/base.py (ConnectorRequirement) · src/gaia/ui/routers/agents.py (/api/agents, the panel to mirror)Also exists (hub side): workers/agent-hub/src/ — the skill catalog lane, the skills/ R2 namespace, POST /publish/skill validating this grammar server-side, and the pre-publish audit gate (#2467).Still proposed (not written yet): the /api/skills router and Agent UI panel, gaia skill update, and the permission sandbox for local-capability domains (#1019). Every such symbol is marked PROPOSED where it appears.Shipped since: the marketplace verbs gaia skill search|install|remove|publish plus keygen/trust, install-time tier-ceiling enforcement, and the declarative skills: block (#2467); plus gaia skill migrate (#692).
Component: the SKILL.md field grammar — the on-disk contract for GAIA’s skill ecosystem (issue #691).Module: gaia.skillsshipped (#888). The parser, validator, discovery, tool loader, permission grammar, and gaia skill list|info|create|import|export all exist. The contract is grounded on the live tool registry in gaia.agents.base.tools.Status: Format = decided. Runtime = Phase 1 + the marketplace shipped (loader, CLI, publish/install, signature verification, install-time tier ceilings, declarative skills: / skill_sets:); the local-capability sandbox and the REST/UI surface remain proposed. The companion Agent Skills spec owns the integration surface (discovery, scoping, progressive disclosure); this document is authoritative for the field grammar.Target consumers: #887 (skill auto-synthesis), #553 (self-improving agent), #1451 (tool-loader Part 3), #647 (skill marketplace), #648 (OEM bundling), #462 (Agent Manifest).

Why this exists

A GAIA agent’s capabilities are compiled in. Tools arrive through hardcoded mixins (RAGToolsMixin, BrowserToolsMixin, … catalogued in KNOWN_TOOLS, registry.py:38) wired into the agent class and shipped inside the wheel. A user who wants a new capability has no path short of forking the agent. Skills make a capability portable: a folder with a SKILL.md an agent loads only when relevant. The Agent Skills spec answers why skills, and how an agent composes them. This document answers a narrower, higher-stakes question: what exactly goes in the frontmatter. That matters because the frontmatter is a contract, not a private detail — several landed and in-flight features read or emit these fields:
  • #887 (skill auto-synthesis) and #553 (self-improving agent) will emit SKILL.md files via skill extraction. The field names they write must be the field names this spec defines.
  • #1451 (tool-loader Part 3) reads the tools_required field to union a recipe’s tools into per-turn tool selection — a hard cross-spec dependency already written down in tool-loader.mdx.
  • #647 (marketplace) and #648 (OEM bundling) need a stable definition of “a skill” and its trust tiers to publish and pre-bundle.
So the single thing this spec must nail down is a frontmatter schema that is (a) the open standard, so the existing skill ecosystem works in GAIA unchanged; (b) a strict superset that carries GAIA’s typed tools, permissions, and trust tiers; and (c) stable enough that the consumers above can build against it without churn.

Prior art (credit before contribution)

GAIA does not invent a skill format. It adopts one and extends the frontmatter. The base and the compatible formats: GAIA’s contribution is the metadata.gaia namespace: typed @tool declarations, a <domain>:<level> permission grammar bridged to the connector grant model, three install-time security tiers, and the tools_required recipe contract — all expressed inside the standard’s metadata map, so a GAIA skill remains a valid standard skill.

Decided design

The schema is the standard’s required base plus a single GAIA-owned namespace. A standard runtime reads the base and ignores metadata.gaia; GAIA reads both.

Adopted base (Agent Skills standard)

compatibility and allowed-tools are deliberately excluded from GAIA’s adopted base. The Agent Skills standard defines both as optional, but they overlap GAIA’s structured fields (allowed-toolsmetadata.gaia.permissions
  • tools; compatibilitymetadata.gaia.requirements) and neither Hermes nor OpenClaw uses them. A standard skill that happens to set them still parses — GAIA simply ignores those two keys; permissions and requirements come solely from metadata.gaia.

The metadata.gaia namespace

Everything GAIA-specific is nested here. Omit the whole block and the skill is a valid instruction-only skill (see Off-states).

tools vs tools_required — never conflate them

These are two different fields with two different consumers, and collapsing them breaks the recipe contract: The field name tools_required is locked as the cross-spec contract with #887/#1451. A skill may have either, both, or neither.

Field reference

Manifest-side grammar: skills, skill_sets, default_skill_set

The fields above describe one skill. An agent declares which skills it composes in its gaia-agent.yaml, using three top-level keys (#2466):
Each entry in skills or a set’s list is either a skill name or a mapping: Set names use the same slug shape as skill names, capped at 32 chars — a lowercase alphanumeric start and end with internal hyphens:
Sets overlap; they do not partition. The same skill may appear in several sets — that is how a shared capability stays available across contexts. What a set may not do is re-declare a skill already in the always-on skills: list; that is a contradiction (an always-on skill loads for every set) and fails to parse. Every malformed shape fails at manifest-parse time with a message naming the fix — a list that repeats a name, an empty set, a default_skill_set naming an undeclared set, an unrecognized key in a reference mapping, a non-boolean required. There is no partial acceptance: an agent either declares a coherent set of skills or does not load.
version: is enforced at load. The range is matched against the installed skill’s frontmatter version, and a mismatch is fail-loud: a required entry raises and the agent does not launch, an optional one is skipped with the reason logged. A skill with no version in its frontmatter cannot satisfy a pin — otherwise a local edit would silently shadow a pinned hub install. A range that names no version (>=v2, 1.2.x) is rejected at manifest-parse time with the shapes above, rather than treated as “any” — so the verdict never depends on which skills happen to be installed on the machine doing the parsing.What #2467 still owns is resolution across installable versions — choosing which version to fetch from the marketplace. Checking the one version actually installed happens today.
Selection semantics — the order a set is chosen in, the selector hook, and the fail-loud behaviour on an undeclared name — belong to the integration surface and live in Agent Skills → Skill sets. The grammar above is implemented once, in gaia/skills/sets.py, and consumed by both the manifest validator and the runtime, so the two cannot drift.

Naming

Skill names follow the Agent Skills rule: ^[a-z0-9]+(-[a-z0-9]+)*$, ≤64 chars, and must equal the skill’s directory name (so web-research/SKILL.md has name: web-research). No leading, trailing, or consecutive hyphens. Names are unique within a discovery root; precedence between roots is defined in Agent Skills → Discovery.

Permission model

Permissions follow <domain>:<level> with an optional :scope qualifier. There are no implicit grants — a skill declares every domain it touches. Scopes narrow a grant: filesystem:read:~/.gaia/data/**, network:read:*.brave.com, shell:execute:git,npm. An unscoped grant covers the whole domain — except mcp:connect, where a scope is mandatory (below). Grammar violations — an empty entry, a missing level, an unknown domain, or a level the domain does not define — fail loudly at parse time.
A local-capability permission is refused, not sandboxed. Until the sandbox lands (#1019), loading a skill that declares filesystem / shell / database / desktop / env raises SkillPermissionError naming the offending grants. GAIA will not load a grant it cannot enforce, and will not pretend to enforce one.<domain>:none is inert in both directions: it produces no requirement and never triggers a refusal, because an explicit denial asks for less than the default. filesystem:none therefore loads fine.

Bridge to the connector grant model — no parallel ledger

Agent Skills already states that a skill carrying permissions is granted to an agent reusing the connectors per-agent grant model. This spec makes the mapping concrete rather than inventing a second grant system:
  • network:* and mcp:connect resolve, at scope-time, to a ConnectorRequirement(connector_id, scopes, reason) (connectors/providers/base.py:23) — the same frozen primitive agents already declare via REQUIRED_CONNECTORS (agent.py:297). The skill’s permissions become the agent’s connector requirements when the skill is loaded — appended to a per-instance copy, never to the ClassVar, so one agent’s skill cannot leak requirements into a sibling.
  • filesystem:*, shell:*, database:*, desktop:*, env:* are new local-capability domains with no connector equivalent. Until the skill sandbox lands (PROPOSED — see Phased build) a skill declaring one is refused outright — at every security tier, verified included. A tier is a provenance claim, not a sandbox.
Per-agent scoping reuses the namespaced_agent_id keying (builtin: / custom:sha256: / installed:, registry.py:392) that already isolates agent grants today.

Two decisions the runtime made where this spec was ambiguous

The bridge above did not say what happens when a permission’s scope has no counterpart in the connector catalog. The implementation (permissions.py) settled both cases, and these are now part of the contract:
  1. mcp:connect must name a catalog connector. A bare mcp:connect is a validation error, not a wildcard — the error lists the available connector ids and points at gaia connectors list. So is mcp:connect:<unknown-id>. An unscoped MCP grant would mean “connect to any MCP server”, which is exactly the ambient authority the permission model exists to prevent.
  2. An unmatched network:* scope resolves to a reserved network pseudo-connector-id. Nothing in the catalog represents raw outbound HTTP, so network:read:*.brave.com becomes ConnectorRequirement(connector_id="network", scopes=("read:*.brave.com",), …). If the scope does name a real catalog connector, the requirement targets that connector instead.
The network pseudo-requirement is a declaration surface, not enforcement. It records what egress a skill asked for so gaia skill info and a future egress policy can read it. Phase 1 does not enforce network egress — a loaded skill’s code can reach any host its process can. Do not treat a network:read:*.brave.com grant as a firewall.

Security tiers

Three tiers set the install-time permission ceiling and grant behavior. Promotion path: experimental → community → verified. A publisher signs to reach community; an AMD audit request reaches verified. Re-audit on every version bump. A claim is not a tier. gaia skill install computes min(claimed tier, tier the signature earned) and enforces the ceiling against that, so an unsigned skill can never install as verified no matter what its front matter says. A key’s trust-store role decides what it attests: publishercommunity, amdverified. The trust store ships empty and no AMD key is bundled, so out of the box every skill installs experimental — the honest floor rather than a placeholder that makes the top tier look anchored. See gaia skill install. The dangerous set the spec names — shell:execute, desktop:control, database:write — is retained as authoritative, but all three are local-capability domains, so in v1 they are refused before any tier check: the prompt is currently reachable only for network:write.
Tiers are NOT the governance decision system. The governance layer’s DecisionType = Literal["ALLOW", "REVIEW", "BLOCK"] (governance/schemas.py:17) is a run-time, per-action verdict. Security tiers are an install-time trust ladder that sets the ceiling a skill’s permissions may reach. They are complementary, not the same axis: a verified skill still has each action adjudicated at run time by governance.The install-time ceiling now ships (#2467): signatures are verified, the claimed tier is collapsed to the attested one, and a permission above the resulting ceiling refuses the install. What remains deferred to Phase 2 is the run-time sandbox for local-capability domains — which is why those domains are refused outright rather than granted at any tier.
A verified stamp is provenance, not containment. v1 skills are instruction + connector-bridged only. Do not read a tier as evidence that a sandbox is policing what the skill’s code does at run time — no such sandbox exists yet (#1019).

Pre-publish security audit

Every skill passes an automated audit before it can enter the hub catalog (#2468). Skills are contributed by pull request — there is no self-serve publish path (the hub’s publish endpoint is gated on a maintainer-held token secret), so the PR check is the gate: BLOCK fails it, REVIEW holds the PR for maintainer sign-off, ALLOW passes. Submit under skills/community/<name>/; see Publishing to the Hub → Publishing a skill. Authors run the same engine locally, so a clean local audit is the whole requirement:
Exit codes are distinct so CI can branch: 0 allow, 5 review, 6 block, 4 the skill could not be parsed. A skill the audit cannot read is never passed — there is nothing to audit and nothing to publish. Four analyzers run over the directory. Nothing imports the skill, runs its code, or touches the network, so auditing a hostile skill is itself safe — this is a static gate, and the runtime sandbox remains Phase 2. Tier-scaled rigor. The same findings produce different verdicts depending on the tier a skill claims, which is what makes the tiers above mean something at publish time: A skill cannot be stamped a tier whose gate it did not clear. The report lists the tiers its findings actually cleared, and the hub compares the claimed tier against that list — so an unearned claim is refused, not recorded. verified never appears in that list, which is what stops a publisher self-stamping the top tier; the AMD audit is what adds it. A new version re-earns its verdict. The report is bound to the skill name, the version, and a digest of the audited bytes, so a report cannot be replayed onto a different publish. CI re-runs the full audit on every PR touching a skill, with no “already audited” short-circuit.
The gate proves consistency, not trustworthiness. The audit report is a publisher-supplied form part with no attestation, so the binding checks close replay and accident — stale reports, forgotten re-audits, careless tier bumps — but not forgery. A hostile publisher can fabricate a report whose every field agrees, including the digest over their own files.That is why a recorded verdict says attestation: "publisher-asserted". Read it as “self-consistent”, never as “AMD vouches for this”. Making a verdict unforgeable needs an attestation the publisher cannot mint — report signing with a CI-held key (#1710), or the hub running the audit itself — not a stricter parse.
Disclosure. Findings name file:line and the fix, but the offending source text is withheld from every report unless a caller passes --show-snippets locally. In CI the per-finding detail goes to the repo’s private Security → Code scanning tab as SARIF; the PR comment carries verdicts, tiers, and counts only. REVIEW currently refuses the publish rather than admitting it — the quarantine lane and its maintainer sign-off workflow are #2675.

Tool registration

A skill’s tools map onto the existing decorator and registry. On load, the runtime imports the skill’s tools.py and registers each @tool under a <skill-name>/<tool-name> namespace into _TOOL_REGISTRY to prevent collisions when an agent composes multiple skills — the same namespacing Agent Skills describes. The skill’s tools.py uses the standard @tool decorator unchanged:
The manifest is the contract, in both directions. The loader rejects the skill — registering nothing — when any of these holds: Every failure restores _TOOL_REGISTRY (and sys.modules) byte-for-byte — there is no partial load, as Agent Skills requires. Shipping a tools.py while declaring no tools is not an error: the module is simply not imported, and the skill loads instruction-only.

CLI

Shipped verbs (gaia/skills/cli.py) — authoring and local management. Full flag reference in CLI Reference → Skills.
Exit codes are distinct so scripts can branch: 0 ok, 2 usage, 3 not found, 4 invalid (including a discovery root containing a malformed skill), and — for audit5 review, 6 block. REVIEW and BLOCK are separate codes so CI can hold a skill for sign-off without reporting it as rejected. Marketplace verbs (#2467 / #647) — these talk to the Agent Hub’s skills lane:
install accepts a SemVer range and takes the highest published version satisfying it — the same rule agent dependencies: use. Installs are recorded in ~/.gaia/skills/skill-lock.json with the requested range, the resolved version, the artifact SHA-256, the signing key, and the tier actually installed at. Still PROPOSEDupdate (marketplace):
The proposed verbs are absent, not stubbed. gaia skill update is not a command that prints “not implemented” — it does not parse, so a user cannot discover a verb that cannot work. #691’s acceptance asked for list/install/remove: all three now ship.

What the hub enforces at publish

The Agent Hub Worker serves POST /publish/skill and a skill catalog lane (#2467), and gaia skill publish is the client that calls it. That endpoint re-validates this grammar server-side before a skill may enter the catalog. It is a gatekeeper, not a replacement for the loader’s validation above: a skill can be locally valid and still be refused publication. Rejections name the offending field.
  • name and description are required; name matches ^[a-z0-9]+(-[a-z0-9]+)*$ and is ≤64 chars, description is ≤1024.
  • version is required to publish (optional in the format itself, where a skill may be unversioned) and must be SemVer. 0.0.0 is refused — the unversioned sentinel can never become an immutable release.
  • metadata.gaia.security_tier must be one of verified / community / experimental, and the tier is gated by the pre-publish security audit (#2468) — community and verified cannot publish without a cleared report, and a skill cannot claim a tier whose gate it did not clear.
  • permissions, tools, tools_required, and requirements must match the shapes above; the catalog entry carries them so an installer can show the permission preview before anything is written to disk.
Skill names share one namespace with agent ids, so a name already taken by a published agent is refused (and vice versa). See Agent Skills → Agent Hub integration for the catalog shape and the storage/route layout.

Declarative consumption — the skills: block

An agent names the skills it composes in its gaia-agent.yaml, and any Agent subclass resolves that block at construction — so a custom agent under ~/.gaia/agents/<id>/ consumes an installed hub skill with no code change:
Resolution is localgaia skill install puts the skill in a discovery root and this reads what is there; an agent never pulls code from the network at startup. Ordering mirrors agent dependencies:: a skill providing a tool another declares in tools_required loads first, declaration order breaks ties, and a cycle raises rather than picking a winner. A missing required: true skill raises with the gaia skill install command that fixes it; a missing optional one is logged and skipped. The skill_sets: grammar is #2466. It expands a set into SkillRequirement values and resolves them through the same path, so there is one resolver and one place that enforces conflicts.

Agent UI dashboard support

Acceptance #4 requires a skills management panel. The proposed /api/skills router mirrors the existing /api/agents router (ui/routers/agents.py: list :130, detail :174, export :184, import :222) — the same list/detail/install/remove shape, so the panel reuses the agents-panel conventions rather than inventing new ones.
There is no /api/skills router today — the dashboard is greenfield. The data it needs now exists (SkillManager.list_skills(), and gaia skill list --json / gaia skill info --json already emit the list and detail shapes), so the router is a thin wrapper rather than new machinery. Until it ships the panel is absent, not broken (see Off-states).

Cross-format compatibility & migration

Three formats, one namespace pattern. GAIA reads the standard base directly and treats Hermes/OpenClaw as migration sources, not runtime targets. A single shipped tool — gaia skill migrate --from {hermes,openclaw,auto} (migrate.py, #692) — reads the foreign namespace and writes a metadata.gaia block. Every migrated skill lands experimental, re-earning trust via the same reset_security_tier() gaia skill import uses. Migration emits a report of fields needing manual review, and the output is round-tripped through parse_skill() before install, so a migration can never emit a SKILL.md the parser rejects.

Field-by-field: OpenClaw → GAIA

Fields GAIA models fully are consumed — they move into metadata.gaia and leave the vendor block. Anything partially or un-modeled is preserved under metadata.<vendor> and named in the report, which is what makes the vendor-namespace pattern lossless in practice rather than in principle.
requires.env is a requirement, not a permission. It declares which variables must exist; it grants no access, so it maps to the advisory requirements.env_vars rather than the env:read domain. Treating it as a permission would make nearly every real OpenClaw skill unmigratable for a field GAIA already has a home for.

Field-by-field: Hermes → GAIA

The shapes published skills actually use

Validated against 26 real published ClawHub skills (each commit-pinned, with its source URL, license, and digest recorded in tests/fixtures/openclaw_skills/PROVENANCE.md). The documented schema is not what the registry contains, so the migrator reads all of these: Of the 26, 13 migrate cleanly and 13 are refused — 10 for a local capability (they shell out) and 3 for having no frontmatter. That ratio is the honest shape of the corpus, not a shortfall: a skill whose whole purpose is running git or tmux cannot be bridged until the sandbox lands.
No runtime adapter in v1. The prior draft promised an OpenClawAdapter that wrapped foreign handlers in @tool at load time (from gaia.skills.openclaw import OpenClawAdapter). That is retracted — v1 is migration-only. A runtime adapter is deferred to a later phase; do not build against OpenClawAdapter.

Relationship to neighbors

  • Agent Skills — owns the architecture and integration surface (discovery roots, progressive disclosure, scoping, the skill/tool/agent distinction). This doc owns the field grammar. Where they overlap, that spec is canonical for the compatibility surface and this one for the schema; the two are reconciled (see Correcting the prior spec).
  • Agent Manifest (#462) — a gaia-agent.yaml references skills by name + version range; SKILL.md is the skill-level complement. The keys are specified in manifest-side grammar above; how a set is selected per launch is in Agent Skills → Skill sets.
  • Agent bundles — skill packaging relates to the existing agent bundle format (.zip + bundle.json, BUNDLE_FORMAT_VERSION = 1, export_import.py:40).
  • Builder template — scaffolds agent.py today (builder/template.py); emitting SKILL.md is a future extension, not current behavior.
  • OEM bundling (#648) — needs a stable definition of “a skill” to pre-configure hardware SKUs. A SKU pre-bundles skills as verified-tier entries in a discovery root; the directory anatomy and tier defaults this doc fixes are that definition.

Example skills

Six worked examples. The three for existing agents (acceptance #6) declare only tool names that exist in the current registry. All parse as YAML; tool skills round-trip against their tools.py.
Several examples declare permissions Phase 1 refuses. rag-search (4a) and file-operations (4c) declare filesystem:*, and the migrated git-status (6) declares shell:execute:git. These are valid manifests — they parse and validate — but load_skill refuses them until the sandbox lands (#1019), because GAIA will not load a grant it cannot enforce. They are kept here as the format’s target state; the examples that load today are the instruction-only ones (1, 3) and the network/mcp-scoped ones (2, 4b).
Proves a bare Agent Skills SKILL.md loads with no GAIA fields — defaulting to experimental, no tools, no permissions.
The restructured schema: standard base + metadata.gaia.
A distilled procedure that consumes existing registry tools. Each tools_required name resolves against a real registry tool (query_documents from RAGToolsMixin, read_file from FileIOToolsMixin; remember from the tool-loader CORE set).
Acceptance #6 — a skill mapping the rag mixin’s real tools.
Acceptance #6 — the browser mixin’s real tools (search_web, fetch_page, download_file).
Acceptance #6 — the file_io mixin’s real tools.
A Hermes skill (left) and what gaia skill migrate --from hermes produces (right) — the modeled metadata.hermes fields become metadata.gaia, and the result lands experimental. Unmodeled ones (category) stay under metadata.hermes; experimental being the omitted default, the emitted file need not restate it (gaia skill info always shows the effective tier).
An OpenClaw skill and the mapping migrate computes for it. metadata.openclaw requirements map to metadata.gaia fields and trust resets to experimental.
This particular skill does not install in v1. bins: [git] maps to shell:execute:git, a local-capability permission with no enforcement until the sandbox lands, so gaia skill migrate reports git-status as unmigratable with that reason rather than writing it. The AFTER block below is the mapping — the target state once the sandbox ships — not a file v1 produces. A skill needing local access is never silently stripped of the permission to make the migration succeed.

Off-states (safe floors)

Every degraded condition lands on a conservative, well-defined floor — nothing breaks because a GAIA feature is absent. Future-gated phases are additive with precedence. The tools_required signal that #1451 consumes returns [] cleanly until #887 lands (tool-loader.mdx), so a recipe skill authored today is valid but inert until its consumer ships — it never blocks or errors.

Correcting the prior spec

This document revises an earlier draft of skill-format.mdx. The most important correction:
The base standard is Agent Skills (agentskills.io), not OpenClaw. The prior draft framed “OpenClaw / ClawHub” as the external standard to be compatible with. In fact agentskills.io (as used by Claude Code) is the base GAIA adopts; OpenClaw is just one of several compatible third-party formats, alongside Hermes. This reconciles skill-format.mdx with Agent Skills, which had already flagged “OpenClaw/ClawHub” as a placeholder from an earlier draft.
The second structural correction: GAIA-specific fields move from the top level into metadata.gaia. The prior draft put permissions / security_tier / requirements / tools at the top level, which collides with the standard’s namespace and breaks lossless cross-runtime parsing. Nesting them under metadata.gaia mirrors the convergent prior art (Hermes metadata.hermes, OpenClaw metadata.openclaw).
Retractions — do not build against these:
  • OpenClawAdapter runtime adapter is dropped from v1. Compatibility is migration-only — via gaia skill migrate, which ships (#692); it is the runtime adapter that is deferred, not the migration path.
  • The flat top-level schema is superseded. permissions, security_tier, requirements, and tools are now under metadata.gaia. Top-level version is the only GAIA superset key outside metadata.
  • compatibility and allowed-tools are not part of GAIA’s base. The prior reliance on allowed-tools for permission scoping is removed; permissions come from metadata.gaia.permissions.
  • The five-root discovery list is superseded by three. Project-local ./.gaia/skills/ is deferred — see Agent Skills → Discovery. The registry-lock root is not a fourth root: gaia skill install writes ~/.gaia/skills/skill-lock.json beside the skills it installs, so the lock is provenance over the existing user root, not a new discovery location.
  • Still greenfield, still PROPOSED: the local-capability permission sandbox and run-time enforcement, /api/skills and the UI panel, and gaia skill update. Do not build against these.
No longer a retraction — these now exist. From #888: gaia.skills, SkillManager, Agent.load_skill, ~/.gaia/skills/, and gaia skill list|info|create|import|export. From #2467: gaia skill search|install|remove|publish (plus keygen/trust), bundle signing, install-time tier-ceiling enforcement, and skill-lock.json. From #692: gaia skill migrate and gaia.skills.migrate. An earlier revision of this document said the #888 symbols were “verified absent on main”; that is no longer true of any of these sets.

KPIs

A format contract is judged by conformance and losslessness, not runtime speed. The bar:

Phased build

The format (Phase 0) and the runtime core (Phase 1) have shipped; the remaining phases are additive. Each phase holds the prior floor until it lands.

Phase 0 — Format contract (SHIPPED)

Success criteria:
  • Schema decided: adopted base + metadata.gaia; tools vs tools_required separated; compatibility/allowed-tools excluded.
  • Agent Skills reconciled — mapping table nests metadata.gaia.*, the placeholder Note becomes a Division-of-authority Note, and allowed-tools/compatibility are marked excluded.
  • All example skills parse; the three acceptance-#6 skills use only real registry tool names.
  • Traceability covers 100% of #691 acceptance criteria.

Phase 1 — Loader, validator, discovery, CLI core (SHIPPED, #888)

What landed:
  • gaia.skills.formatSKILL.md parser, writer, and validator (Skill, SkillTool, SkillRequirements, GaiaMetadata, parse_skill, parse_skill_file, parse_skill_metadata, validate_skill). Round-trip is identity: unknown top-level keys, other metadata.<vendor> namespaces, and unknown metadata.gaia keys are all preserved, so passing a foreign skill through GAIA loses nothing. compatibility / allowed-tools / disallowed-tools parse and are ignored, exactly as specified above.
  • gaia.skills.managerSkillManager over three roots (agent-bundleduserclaude-import), progressive disclosure (discover = frontmatter, load = + body, resource_path = on-demand files, traversal-checked), auditable shadowing (shadowed()), surfaced parse failures (discovery_errors), and optional hot-reload via start_watching().
  • gaia.skills.loader — imports tools.py and registers each @tool as <skill>/<tool> in _TOOL_REGISTRY, cross-checking the manifest against the real signatures. No partial load: any failure restores the registry byte-for-byte.
  • gaia.skills.permissions — the <domain>:<level>[:scope] grammar, the connector bridge for network/mcp, and the loud refusal of local-capability domains.
  • gaia.skills.sets (#2466) — the declarative skills: / skill_sets: / default_skill_set: manifest grammar (see Manifest-side grammar), parsed once and shared by gaia.hub.manifest and the base Agent, plus per-launch selection (SKILL_MANIFEST, select_skill_set, load_skill_set). An undeclared set name raises SkillSetError rather than falling back. The email agent is the reference consumer.
  • Agent.load_skill / unload_skill / SKILL_DIRS / skill_manager / loaded_skills — plus get_skills_system_prompt(), auto-discovered by _get_mixin_prompts(), which returns "" when no skill is loaded so every existing agent’s prompt stays byte-identical.
  • gaia skill list|info|create|import|export.
Success criteria — met:
  • A tool skill loads and its tools register and call under <skill>/<tool>.
  • A bare standard skill loads instruction-only at security_tier: experimental.
  • Validation fails loudly on a toolstools.py mismatch — missing tool, undeclared extra, parameter-name mismatch, required/optional flip, or type contradiction — with no partial load.
Deliberately deferred out of Phase 1: tier-ceiling enforcement, the project-local ./.gaia/skills/ root, and network-egress enforcement. (The install-time half of the tier ceiling has since shipped with the marketplace — see Phase 4.)

Phase 2 — Permission enforcement + load-time tier ceilings + sandbox (PROPOSED, #1019)

The connector bridge shipped early, in Phase 1: network:* / mcp:connect already resolve to ConnectorRequirement at load-time. The install-time tier ceiling shipped with the marketplace (Phase 4). What remains is the sandbox that makes local-capability domains loadable at all, the load-time ceiling — which covers a skill that never passed through install, e.g. one from gaia skill create/import or copied into a root by hand — and network-egress policy over the declared network requirements. Success criteria:
  • A hand-placed skill requesting a permission above its tier ceiling fails to load (installing one already fails).
  • filesystem/shell/database/desktop/env route to the sandbox and load instead of being refused.
  • A declared network:<level>:<scope> actually constrains egress.

Phase 3 — Agent UI dashboard (PROPOSED; #701)

/api/skills mirroring /api/agents, plus the skills management panel. gaia skill migrate --from {hermes,openclaw,auto} was slated for this phase and shipped ahead of it (#692) — it needs no UI surface, so it did not wait for one. Visual import remains part of this phase: it is the same migrator behind a panel that does not exist yet. Success criteria:
  • The panel lists installed skills with tier + version and supports install/remove (acceptance #4).
  • The panel offers OpenClaw import over gaia.skills.migrate, reusing the shipped mapper rather than a second implementation.

Phase 4 — Marketplace (SHIPPED, #2467) + consumer wiring (PROPOSED; #887 / #1451)

Shipped: the hub’s skills lane (POST /publish/skill, the skills/ R2 namespace, GET /index.json filtered by type: "skill"), gaia skill search|install|remove|publish, Ed25519 bundle signing with a trust store, install-time tier ceilings, skill-lock.json, and the declarative skills: block. The pre-publish security audit gate is #2468 — publish calls it and fails loudly when its engine is absent rather than publishing un-audited. Still proposed: gaia skill update; #887 emits conformant SKILL.md; #1451 unions tools_required. Success criteria:
  • A published skill installs by name@<range>, lock-tracked, and loads into an agent — including a non-bundled one. (Met.)
  • An unsigned skill cannot install as verified; experimental needs --allow-experimental; the per-tier permission ceiling is honored. (Met.)
  • #887-synthesized files validate against this schema unchanged.
  • #1451 unions a matched skill’s tools_required ahead of semantic results (the contract in tool-loader.mdx).

Open questions

The five design forks (base standard, metadata.gaia nesting, top-level version, migration-only v1, excluding compatibility/allowed-tools) are locked by the maintainer and are recorded as settled above. What remains are implementation-level questions for whoever builds the runtime:
  1. tools_required validation timing. Fail-vs-skip is not open — GAIA’s fail-loudly policy settles it: a name unknown to the framework is a manifest defect and fails loudly; a name valid but inactive in the current agent’s registry is scoping (omitted + logged, not an error). The genuine open nuance is when the unknown-name check runs — at install against a static tool catalog, or deferred — since the active registry is assembled dynamically from mixins and other skills, so the full tool universe may not be known at install time. Affects #1451 robustness.
  2. Skill→skill dependencies. The prior draft allowed a skill to depend on other skills (requirements.skills). Keep it under metadata.gaia.requirements, or defer dependency resolution to the marketplace (#647)?
  3. /api/skills install sources. Registry-only, or also local path / URL? The latter widens the install-time security surface.
  4. Body-injection scanning. Resolved by #2468: every skill’s body is scanned, and the tier decides the consequence. The “required for community+, advisory for experimental” split was the right instinct, but it does not need to be its own policy — body findings feed the same tier gate as code findings, so the split falls out for free: at experimental a body finding is surfaced and does not gate, at community it forces REVIEW, at verified a medium is already enough to hold it. See Pre-publish security audit for the scan itself. Two decisions inside it are worth recording, because both trade precision against evasion:
    • Fenced and quoted text is downgraded one step, never ignored. A skill that documents injection patterns so the model can refuse them is the good case, so blocking it would punish the careful author. But fenced text still reaches the model — models do not reliably treat it as inert data — so a code block buys a severity step, not a pass. Otherwise “put the directive in a fence” would be the standing bypass.
    • Prohibitions are not directives. “NEVER dump the environment” is the security instruction, not a request for one, so a prohibition immediately before a match suppresses it. Rules whose own pattern opens with a negation (concealment’s “do not tell the user”) are unaffected, because the check looks only at text before the match.
  5. docs/spec/ relocation. Settled with Phase 1: this document stays in docs/plans/. Agent Skills is the spec surface — it already lives in docs/spec/ and satisfies #691’s “documented in docs/spec/” acceptance. Moving this doc too would split the field grammar from the architecture across two directories for no reader benefit, and would break every inbound /plans/skill-format link, including the ones the runtime’s own error messages emit (FORMAT_DOCS_URL in skills/errors.py points at https://amd-gaia.ai/docs/plans/skill-format). Revisit only if the two documents ever merge.

Current state of the code

The format is grounded on real primitives, and the Phase 1 runtime now exists. Only the sandbox/run-time-enforcement and REST/UI layers are greenfield; the marketplace shipped in #2467.

Dependencies

Blocking: none. This is a foundational format contract; its design inputs are external standards plus the in-repo drafts. Design inputs: Agent Skills (base), Hermes (metadata.hermes), OpenClaw (metadata.openclaw), Claude Code (reference impl); in-repo Agent Skills (architecture / compatibility surface) and tool-loader.mdx Part 3 (the tools_required contract). Downstream consumers. The features whose contract this format must satisfy. A closed tracking issue means that design is settled and already references this format — not that the dependency is gone.

Acceptance-criteria traceability

Every acceptance criterion from #691, verbatim, mapped to its section and what it guarantees a consumer.