Flight Plan Manifest Spec
The Flight Plan manifest is the machine-readable contract that both the authoring tool prompts an operator to fill in and the runtime enforces. A Flight Plan is a sealed Aileron skill (ADR-0027). The skill is the authoring artifact and the Flight Plan is the same artifact after a freeze step seals it. One format spans both states. This page specifies that format field by field.
The manifest is the YAML frontmatter of a SKILL.md document in the agentskills.io ↗ format, extended with a single Aileron-specific block. Every Aileron field lives under one namespaced top-level key so the extension is lossless if stripped. A host without Aileron reads a valid skill and treats the Aileron block as inert.
The deterministic step-graph composition format is specified on this single page rather than a separate sibling page. It is one coherent format covered by one drift-guard test, amended in place under the Composition and the step graph section below. The existing “Flight Plan Manifest Spec” navigation entry stands and no new navigation entry is added.
This page is the normative format spec. The Go parser and validator ship under #1508 ↗. Freeze ships under #1509 ↗: aileron skill freeze resolves image references to digests, produces the lockfile, content-addresses the unit, and signs it. Runtime enforcement is tracked in #1511 ↗. The lock section below is freeze’s target, and freeze now populates it; the schema on this page stays the authoritative shape.
Schema
The normative schema is JSON Schema draft 2020-12. It lives outside the docs collection so the site never renders it as a page:
docs/schema/flight-plan-manifest.schema.jsonA worked, multi-action example that validates against the schema lives beside it:
docs/schema/flight-plan-manifest.example.skill.mdA committed Go drift-guard test (internal/flightplan/spec) keeps the example and the schema from drifting apart. The Aileron block is the only part the schema constrains. The skill keys it sits beside (name, description, and so on) are governed by the agentskills.io format. The Aileron block is named aileron at the top level of the frontmatter:
---
name: weekly-metrics-digest
description: Read a metrics window, summarize it, and file a tracking issue.
aileron:
schemaVersion: aileron.flightplan.v1
requires:
actions: [] # the actions the plan calls, each with a trust contract
environment: # the single container the plan runs in, optional
tools: [] # curated-catalog tools, or an image, or both
inputs: [] # declared inputs with resolution rules
outputs: [] # declared output artifacts
steps: [] # the deterministic step graph, optional
# lock: produced by freeze, absent before freeze
---Field reference
The tables below give every field’s type, required-ness, and semantics. Field names align with ADR-0003 (effect, idempotency, approval routing) and ADR-0019 (the credential-placement scheme set). The idempotency, credential, hosts, and audit vocabulary reuses the connector-spec names from the Sandbox Connector Specs page rather than inventing parallel ones.
aileron (top-level block)
| Field | Type | Required | Semantics |
|---|---|---|---|
schemaVersion | string | No | When present, must be aileron.flightplan.v1. Identifies the block contract this manifest was authored against. |
requires | object | No | The action dependencies. Optional: a tool-only plan that dispatches no connector actions omits it. |
environment | object | No | The single container the plan runs in. Declares tools, an image, or both. Absent when the plan needs no environment. |
inputs | array | Yes | The declared inputs, each with a resolution rule. |
outputs | array | Yes | The declared output artifacts. |
steps | array | No | The deterministic step graph. The composition that wires actions and transforms into a directed acyclic graph. Absent when the skill is instruction-only. |
lock | object | No | The frozen lock and digest section. Absent before freeze. |
requires
| Field | Type | Required | Semantics |
|---|---|---|---|
actions | array | No | The actions the plan calls, each with a per-action trust contract. Absent or empty for a tool-only plan that dispatches no connector actions. A plan that does call actions must declare each called action here: the runtime refuses an action-call step whose ref is not declared. |
Each actions[] entry:
| Field | Type | Required | Semantics |
|---|---|---|---|
ref | string | Yes | The action reference in the form aileron:<connector>.<action>. Attaches to the action model of ADR-0003. |
trustContract | object | Yes | The per-action trust contract for this action. |
An unsatisfied requires: entry is a missing-requirement signal the runtime surfaces. It is never a parse failure. A manifest that names an action the host has not installed still parses as a valid skill.
environment
The execution environment is one container per run (ADR-0027 the execution environment). The block is optional; a plan that needs no environment omits it. When present, it declares tools, an image, or both. A present block must declare at least one of the two, so an empty environment: {} is rejected. Freeze resolves the declared environment to a single content-addressed image digest recorded in the lock section.
| Field | Type | Required | Semantics |
|---|---|---|---|
tools | array | At least one of tools or image | The curated-catalog tools the plan’s steps invoke, each as <name>@<version> (for example [email protected]). The name set is closed to the curated catalog; an unknown name is rejected at validation, before freeze. The version grammar is deliberately loose (2, 2.x, and 2.19.1 all validate) and freeze resolves it against the catalog. Freeze composes the resolved tool Features onto the Aileron runner base into one image. |
image | string | At least one of tools or image | An OCI image reference for a custom base image, the escape hatch when the curated catalog does not carry the needed tooling. Pre-freeze this may be a tag. Freeze resolves it to an image@sha256: digest pin. Declaring both tools and image composes the declared tools onto the custom base. |
requires.actions[].trustContract
The trust contract is the field set absorbed from #925, quoted here as context rather than depended on. Freeze attaches this contract, and the detached signature is the human attestation that it is correct.
| Field | Type | Required | Semantics |
|---|---|---|---|
credential | object | Yes | The credential kind and placement. Never a credential value. |
oauth | object | Required when credential.kind is oauth2 | OAuth scopes, endpoints, and refresh behavior. |
hosts | array | Yes | The expected upstream hosts. The access-scope declaration IS the security boundary. The runtime grants nothing undeclared. |
paths | array | No | The expected request paths on the declared hosts. Part of the access-scope declaration. |
effect | string | Yes | One of read, write, delete, spend, external-send. Aligned to ADR-0003. Drives default approval routing through ADR-0009. |
idempotency | object | Yes | Whether the operation is safe to retry and whether it supports an idempotency key. |
redaction | array | No | Rules applied to response fields before they reach the agent or author. |
verification | object | No | A read-only probe the runtime can call to confirm the credential still works. |
audit | object | Yes | The structure of the audit record this operation emits. |
The credential block:
| Field | Type | Required | Semantics |
|---|---|---|---|
kind | string | Yes | One of none, api-key, oauth2, aws-sigv4. |
placement | string | Yes unless kind is none | One of header, query, cookie, body, signing, session. Maps 1:1 to the closed ADR-0019 injection-scheme set. A none credential has no wire placement. |
identityLabel | string | No | A subject or account identity label for multi-tenant binding. A non-secret label. |
An oauth2 credential must declare the oauth block. An aws-sigv4 credential is always placed via signing, and an oauth2 credential is always placed in a header.
The placement values map to the ADR-0019 wire mechanisms. header is a bearer or templated header (bearer or header-template). query is query-param. signing is AWS SigV4, which maps to sigv4-resign and is sealed by #1501 ↗. session is the cookie or session-token wire form for a session-authenticated upstream. body carries the credential in the request body. The exact header or wire shape is per service, not per placement alone.
The idempotency block:
| Field | Type | Required | Semantics |
|---|---|---|---|
safeToRetry | boolean | Yes | True when re-running with the same inputs produces no additional effect. |
idempotencyKey | boolean | No | True when the operation accepts a client-supplied idempotency key. Defaults to false. |
The audit block:
| Field | Type | Required | Semantics |
|---|---|---|---|
fields | array | Yes | The closed set of audit-record fields this operation emits. |
sink | string | No | A reference to the customer-owned audit sink. A non-secret reference. |
The closed audit.fields set is connector-hash, action-manifest-version, credential-binding, identity-label, approved-input, approval-decision, network-target, operation-effect, request-summary, response-summary, and result.
inputs[]
| Field | Type | Required | Semantics |
|---|---|---|---|
name | string | Yes | The input name, unique within the manifest. |
type | string | Yes | One of string, number, boolean, timestamp, object, array. |
description | string | No | Human-readable semantics. |
resolution | object | Yes | The resolution rule. One of literal, dynamic, or source. |
outputs[]
| Field | Type | Required | Semantics |
|---|---|---|---|
name | string | Yes | The artifact name, unique within the manifest. The audit references artifacts by this name. |
mimeType | string | Yes | The artifact media type. |
encoding | string | Yes | One of utf-8, base64. base64 is reserved. v1 implements utf-8 only. |
publish | object | Yes | The publish target. target is file or none; path is required and names the output file when target is file. |
steps[]
Every step carries an id unique within the graph and a kind. The remaining fields depend on the kind.
| Field | Type | Required | Semantics |
|---|---|---|---|
id | string | Yes | The step id, unique within the step graph. Later steps reference this step’s outputs as steps.<id>.<outputName>. |
kind | string | Yes | One of action-call, transform, tool, llm-seam. A closed enum. |
actionRef | string | Yes for action-call | The action this step invokes, in the form aileron:<connector>.<action>. Must match a declared requires.actions[].ref. |
command | array | Yes for tool | The argv array a tool step executes. The first element is the program, the rest its arguments. An argv array, never a shell string: no shell interpretation, no word splitting, no expansion. |
args | object | No (action-call only) | The action arguments. Each value is a binding reference, never a value. |
bindings | object | No (transform, tool, llm-seam) | The named inputs to the step. Each value is a binding reference. |
mount | object | No (tool only) | The step’s input-I/O boundary. mount.path is the path inside the environment where the step’s input files are placed. |
collect | object | No (tool only) | The step’s output-I/O boundary. collect.path is the path inside the environment whose contents are collected as the step output. |
trustContract | object | No (tool only) | The step’s per-step trust contract, reusing the per-action shape. Its hosts declare the step’s network reach. Freeze seals the declared reach into the lock stepTrust section so the signature covers it and it cannot be re-supplied at launch. A declared contract must be non-empty. |
outputs | array | Yes for transform, tool, and llm-seam, No for action-call | The named results the step produces. |
materializesOutput | string | No | Names a declared outputs[].name artifact this step’s result materializes into. |
Composition and the step graph
The steps block is the deterministic composition (ADR-0027). It wires declared actions and deterministic transforms into a directed acyclic graph. The block is optional. A skill with no steps block is instruction-only and still a valid manifest.
There are four step kinds.
kind | Reaches an LLM | Semantics |
|---|---|---|
action-call | No | Invokes a declared action. Its actionRef names the action and its args bind the action’s arguments. |
transform | No | Runs deterministic no-LLM logic over data already in the graph. It has no host, network, or credential surface. |
tool | No | Runs a declared environment tool as a deterministic subprocess inside the single booted plan container. Its command is an argv array run with no shell interpretation. Its optional mount and collect are the file-I/O boundary, and its optional trustContract declares the step’s network reach. |
llm-seam | Yes | The single marked non-deterministic seam. The only kind that reaches an LLM. |
The kind enum is closed. By construction no kind other than llm-seam can reach an LLM, so the no-LLM guarantee is structurally checkable. The kind: llm-seam value is the first-class mark the freeze and lint step (#1509 ↗) checks, and the runtime (#1511 ↗) enforces that only that one seam reaches an LLM. A transform and a tool step are the structural guarantee that intermediate logic stays deterministic.
A step reads its data through bindings. A binding is a reference, never a value. An action-call step binds its args; a transform, a tool, and an llm-seam step bind their bindings. A binding takes one of two forms. inputs.<name> references a declared input resolved once at the launch boundary (#1523 ↗). steps.<stepId>.<outputName> references a named output of a prior step. The references make the wiring a graph.
The step graph is a directed acyclic graph. The runtime executes it in deterministic topological order. A JSON Schema cannot express acyclicity, so the schema does not enforce it. The acyclicity rule and the topological-order rule are stated here as invariants, the freeze and lint step (#1509 ↗) checks them, and the runtime (#1511 ↗) enforces them. The drift-guard test asserts the worked example’s references resolve and are acyclic.
An action-call step’s actionRef must match a declared requires.actions[].ref. A JSON Schema cannot express cross-array membership, so this too is a stated invariant the lint step checks and the drift-guard asserts on the example.
A step result wires to a declared output artifact through materializesOutput. The value names a declared outputs[].name. This is how a step result reaches the declared outputs contract that the runtime materializes through the file-map transport. The split between the declared outputs contract and the file-map transport is unchanged. See the Outputs contract versus file-map transport section. v1 materializes utf-8 artifacts only and base64 stays reserved.
No step field may hold a secret. This extends the credential-sealing invariant. A step binds references, never values. Every step kind is a closed object, so an additionalProperties key carrying a value is rejected. The args and bindings values are binding references whose grammar is closed to inputs.<name> and steps.<id>.<output>, so a literal secret cannot be embedded in the wiring.
The composition lives under the aileron block, so it is lossless if stripped. Removing the block removes the steps graph with it and leaves a valid instruction-only skill.
Lossless if stripped
The extension is lossless if stripped. A tool that ignores the Aileron fields still reads a valid skill. The schema encodes this guarantee two ways. The aileron block is optional at the top level, so a manifest with the block removed is not rejected. Every Aileron-specific field is nested under that one key, so removing it leaves the agentskills.io skill keys untouched.
The same worked example with its Aileron block removed is still a valid skill:
---
name: weekly-metrics-digest
description: Read a metrics window, summarize it, and file a tracking issue.
license: Apache-2.0
---
# Weekly Metrics Digest
This skill reads a recent metrics window, writes a short digest, and files a
tracking issue that links to it.A host without Aileron parses the document above and runs it as an instruction-only skill. An unsatisfied requires: is a missing-requirement signal, never a parse failure, so a host that does read the Aileron block but lacks a named action reports a missing requirement rather than refusing to load the document.
Credential sealing invariant
The manifest declares credential kind and placement. It never declares a credential value. This is load-bearing and follows ADR-0005 and ADR-0019. The runtime mediates credential use at the data-plane proxy boundary so the composed code never holds a raw credential.
No field in the schema can hold a secret. The credential block declares kind, placement, and a non-secret identityLabel. The oauth block declares scopes, endpoint URLs, and refresh behavior, never a token. The audit structure records a credential binding reference, never the credential bytes. The frozen artifact and the audit records carry bindings and references, never secret material.
Inputs and resolution
A Flight Plan is a pinned, agent-invariant function over its declared inputs (ADR-0027, durable record in #1523 ↗). It is deterministic given its resolved inputs. Results legitimately vary as inputs and externally-connected data vary. The crisp line is that an LLM in the runtime loop is forbidden because it injects non-determinism into the logic, while a live or time-relative data read is an allowed input that varies the data, not the logic.
Every input the plan depends on is declared, each with a resolution rule. A value that varies by use case, such as a time window, is a declared input rather than a constant baked into the composition, so one composition serves many operators. Inputs resolve once, at the launch boundary, into a concrete resolved-input set. Two steps that read the same moving value such as the wall clock see one resolved value.
There are three resolution rules, discriminated by the rule field.
rule | Required fields | Semantics |
|---|---|---|
literal | none beyond rule | A value passed at launch. An optional default applies when no value is passed. |
dynamic | value | A launch-relative value resolved once at launch. value is now (the launch timestamp) or today (the launch date). |
source | source | A read from a live source. source.actionRef names the action whose result resolves the input, with an optional source.select. |
Outputs contract versus file-map transport
The outputs: block is the declared interface. The file-map is the transport. They are kept distinct so the transport can change later without changing the declared contract (#1519 ↗).
The outputs: block names each artifact, its mimeType, its encoding, and its publish target. This is the contract the runtime materializes and distribution publishes, and the audit references artifacts by name. The transport is a typed JSON file-map. A step returns an array of {path, mimeType, encoding, content} entries that the runtime materializes into files. The file-map generalizes the existing get-file-content shape. The outputs: block is the contract that names what those files are.
Both the file-entry and the outputs: contract carry mimeType and encoding. The encoding enum is utf-8 and base64. v1 implements utf-8 only. Text-only is the v1 implementation, never the declared interface. Binary outputs are a deferred follow-up blocked on a host-ABI binary-body field, and the mount or run-and-collect boundary (#1510 ↗) is the escape hatch for large or binary artifacts later.
Audit record structure
The audit records resolved inputs to outputs (ADR-0027 audit boundary, #1523 ↗). A scalar input is recorded by value, so a current_timestamp input that resolved to a concrete as_of is written down as that timestamp. A data read is recorded by its resolved binding, which is the parameters, the query, and a result or snapshot identifier with a summary, never the full dataset inline. The dataset is the run’s recorded output, and the audit references it rather than duplicating it.
Each operation’s audit.fields declares which of the closed record fields it emits. The closed set is connector-hash, action-manifest-version, credential-binding, identity-label, approved-input, approval-decision, network-target, operation-effect, request-summary, response-summary, and result. The sink is customer-owned. The recorded binding is what makes a past run explainable without reconstructing it from a moving source.
Freeze and the lock section
Freeze turns a skill into a Flight Plan (ADR-0027 freeze boundary). Freeze resolves every image reference to a content-addressed digest, produces a lockfile that pins those digests and the resolved capability set, binds the execution environment, attaches the per-action trust contract, and signs the result. The lock section is the record of those pins.
The lock section is absent before freeze. Freeze populates it and writes an immutable, signed version into the canonical skill store. See the Freezing a Flight Plan guide for the command and the reproducibility and signature-verification guarantees. See the Launching a Flight Plan guide for how the runtime (#1511 ↗) verifies a frozen unit, resolves inputs once at the launch boundary, walks the step graph with no model in the loop, enforces the per-action trust contract, and materializes the declared outputs.
| Field | Type | Required | Semantics |
|---|---|---|---|
resolvedImages | array | No | The resolved image digest pins. The plan runs in one container, so a plan with a declared environment pins exactly one image: the digest-resolved custom base, or the image composed from the runtime base plus the declared tools. Each entry pairs a pre-freeze ref with its resolved digest and carries no per-step linkage. |
resolvedCapabilitySet | array | No | The resolved capability set the freeze pinned. |
stepTrust | object | No | The step-keyed sealed trust section. Keyed by tool-step id; each entry carries the network reach freeze sealed from that step’s declared trust contract, as hosts only. Freeze stamps it here so the reach is covered by the content hash and signature and cannot be re-supplied at launch. A tool step that declares no trust contract has no entry. Launch consumes it to enforce the step’s reach. |
publisher | string | No | The publisher authority the plan is attributed to, a connector-style authority (github://owner/repo or bare github://owner). Recorded by freeze --publisher. Covered by the content hash and signature, so it cannot be re-supplied at launch. When present, launch resolves the plan’s signing key against the keyring for this authority and refuses when the publisher is not trusted (ADR-0013). Absent when the plan is frozen without a publisher, in which case launch enforces no publisher gate. |
contentHash | string | No | The content hash identifying the exact frozen manifest bytes. |
version | string | No | The human-facing semver label for this frozen version. |
The version is the content hash plus a semver label. The content hash identifies the exact frozen bytes. The semver label is the human-facing version name.
The execution environment
A Flight Plan runs inside one container declared by the environment block (ADR-0027 the execution environment). environment.tools names curated-catalog tools that freeze composes as devcontainer Features (ADR-0026) onto the Aileron-provided runner base. environment.image names a custom base image, the escape hatch for tooling the catalog does not carry, and declared tools compose onto it when both are present. Freeze resolves the declared environment to a single content-addressed digest pinned in the lock, and launch boots that one image and runs the whole plan inside it. A tool step runs a declared tool as a deterministic subprocess in that container; its optional per-step trust contract seals into the lock stepTrust section, and launch enforces the sealed reach at the daemon proxy. The execution container is agent-free. The image carries no coding agent. The Flight Plan runs composed steps, not an interactive agent session.