For the complete documentation index, see llms.txt. This page is also available as Markdown.

Skill schemas

How a launchable skill declares typed input and output JSON Schemas, how `$ref`s are resolved, and the structured result a cloud launch returns.

A launchable skill can declare typed input and output contracts in its SKILL.md frontmatter. When you run the skill with tessl launch skill --cloud, Tessl validates the launcher-supplied inputs against the declared input schema before the skill runs, and validates the recorded result against the declared output schema when it finishes — so a launch either conforms to the contract or fails fast with a field-level diagnostic.

This page covers authoring the schemas block, the $ref forms it accepts, how those refs are resolved, and the structured result a launch returns.

Authoring the schemas block

Declare schemas under a single top-level schemas key in the skill's SKILL.md frontmatter. It has two optional slots, inputs and output:

---
name: greet
description: Greet someone by name.
schemas:
  inputs:
    $ref: ./schemas/inputs.schema.json
  output:
    $ref: ./schemas/output.schema.json
---

Each slot is either an inline JSON Schema or a single $ref reference. Both slots are optional and independent — declare one, both, or neither:

  • schemas.inputs — validated against the launcher-supplied inputs (the INPUTS object, keyed by the skill's own placeholder names) before the skill runs. A missing required input fails the launch during preparation rather than being discovered mid-run.

  • schemas.output — describes the structured payload the skill produces. It is composed into the returned result (see The returned result).

A slot may be written inline instead of via a $ref:

schemas:
  output:
    type: object
    properties:
      message: { type: string }
    required: [message]
    additionalProperties: false

For a complete, copyable example — a greet skill with both slots and a bundled ./schemas/ directory — see the quickstart-typed-skill tile.

$ref forms

A slot's $ref must be a non-empty string and the sole key of the slot (you cannot mix $ref with other keys). Three forms are accepted:

Form
Example
Resolved from

inline

{ type: object, ... } (no $ref)

Used as-is; nothing is fetched.

local

{ $ref: ./schemas/output.schema.json }

A file read relative to the SKILL.md directory. Must start with ./ or ../.

remote

{ $ref: https://example.com/schema.json }

Fetched over HTTPS at launch preparation time.

Anything else (a bare name, an http:// URL, a fragment-only #/... pointer) is rejected.

Local $refs are contained to the skill bundle

A local $ref is resolved relative to the directory holding the SKILL.md, but it is contained to the skill's bundle: a ref may point at a sibling or a shared file elsewhere in the same bundle (../shared/output.schema.json), but one that escapes the bundle directory — lexically or through a symlink — is rejected. This lets skills that genuinely share a contract stay DRY by pointing a local $ref at one shared file, without a ref reaching outside the bundle.

Remote $refs are fetched safely

A remote $ref is fetched only at launch preparation time (never during plain validation), over a guarded HTTPS client:

  • HTTPS only, with no embedded credentials.

  • No private or internal targetslocalhost, private/reserved IP ranges, and link-local / unique-local IPv6 are refused. The host is re-resolved and pinned to the validated address at connection time, so a DNS-rebinding trick cannot swap in an internal address after the check.

  • Bounded — a 10-second timeout and a 1 MiB body cap; HTTP redirects are refused outright.

The fetched body must be valid JSON describing a JSON Schema object.

Resolution precedence

When a launch prepares a skill, each declared slot is resolved into a fully inline JSON Schema:

  1. Read the schemas block from the skill's SKILL.md frontmatter. A skill with no schemas block declares no contract — its inputs are not validated and its output is left unconstrained.

  2. Resolve each slot by its form: an inline schema is used as-is; a local $ref is read from the bundle; a remote $ref is fetched.

  3. Inline the resolved schema. Resolution is single-level — a $ref inside a resolved schema document is not followed. Author self-contained schema files.

If a slot cannot be resolved (a malformed block, an unreadable local file, an unfetchable or unsafe remote URL), the launch fails during preparation with a diagnostic naming the failing slot, rather than proceeding with a partial contract.

The returned result

Every cloud launch returns a fixed envelope, regardless of whether the skill declared any schemas:

  • statussucceeded when the skill did its work, or no-action when it made no change. This is a coarse "did it change anything" flag; it is distinct from the launch run's own status.

  • summary — a short human-readable sentence on what the skill did.

  • output — the skill-governed structured payload.

When the skill declares a schemas.output, that resolved schema is composed into the envelope's output property and output becomes required — the skill must produce a payload conforming to its declared schema, and the launch validates it. When the skill declares no output schema, output is unconstrained and may be omitted. Composition never overrides the envelope: the { status, summary, output } shape is always enforced.

Consuming the result from the CLI

tessl launch skill --cloud renders the returned result when a launch completes, and --json emits the whole envelope for a script to consume:

See tessl launch skill for the full flag reference.

Last updated