This document covers the on-disk formats that FlightDeck ingests: the release.yaml artifact
schema, bundle directory layout, bundle checksum algorithm, and pricing table YAML format.
For the operations that consume these formats (diff, promote, rollback) see
operations-and-policy.md.
release.yaml is the single file that describes an immutable agent release.
api_version: v1 # required; must be "v1"
kind: Release # required; must be "Release"
metadata:
name: support-agent # required; human label for the release
version: "2.1.0" # required; free-form version string
description: "..." # optional
created_by: "ci-bot" # optional
created_at: "2026-05-01T12:00:00Z" # optional ISO-8601
spec:
agent:
agent_id: agent_support # required; stable identifier across releases
entrypoint: "src/agent.py" # optional
runtime:
provider: openai # required; e.g. "openai", "anthropic"
model: gpt-4o # required; model name used for token cost lookup
temperature: 0.2 # optional
max_output_tokens: 800 # optional
prompts:
system_ref: prompts/system.md # required; path relative to bundle root
template_refs: # optional; additional prompt file paths
- prompts/user.md
tools: # optional
manifest_ref: tools.yaml # optional; path to tool manifest
tool_names: # optional; list of tool names
- search
- calculator
routing: # optional
strategy: single_model # "single_model" (default) | "fallback_model"
fallback: # only when strategy is "fallback_model"
model: gpt-4o-mini
on_error: true # trigger fallback on error (default: true)
safety: # optional
retry_policy:
max_retries: 2
backoff_ms: 200 # optional
timeouts_ms: # optional
model_call: 30000
tool_call: 10000
pricing_reference:
provider: openai # required; must match a loaded pricing table
pricing_version: "2024-05" # required; must match a loaded pricing table
tags: # optional; arbitrary key-value strings
env: production
team: supportAll field values are stored verbatim in the releases SQLite table as artifact_json. The
spec.agent.agent_id is the primary grouping key used by diff, promote, and rollback.
JSON Schema: schemas/v1/release.schema.json
flightdeck release register accepts either a single release.yaml file or a bundle
directory (any directory containing a release.yaml). The directory may include any
additional files referenced by spec.prompts, spec.tools, etc.
candidate-release/
├── release.yaml # required
├── prompts/
│ ├── system.md # referenced by spec.prompts.system_ref
│ └── user.md
└── tools.yaml # referenced by spec.tools.manifest_ref
Files under .git/ and __pycache__/ are excluded from the checksum. Symlinks are also
skipped.
# Register a bundle directory
RELEASE_ID=$(flightdeck release register ./candidate-release)
# Register a single release.yaml
RELEASE_ID=$(flightdeck release register ./candidate-release/release.yaml)The --env flag overrides default_environment from flightdeck.yaml for this
registration:
flightdeck release register ./candidate-release --env productionThe checksum is a SHA-256 hash over the canonical bundle representation, designed to be stable across operating systems and line-ending conventions.
Steps (src/flightdeck/bundle.py):
- Collect all files under the bundle path recursively. Exclude
.git/,__pycache__/, and symlinks. - Sort files by their POSIX-style relative path (e.g.
prompts/system.md). - For each file in sorted order:
- Text suffixes (
.md,.yaml,.yml,.txt,.json,.csv,.toml): read as UTF-8 and normalize CRLF (\r\n) and bare CR (\r) to LF (\n) before hashing. - Binary suffixes: hash raw bytes unchanged.
- Feed the hasher:
relative/posix/path+\0+file_bytes+\0.
- Text suffixes (
- The final digest is returned as a lowercase hex string (no
sha256:prefix).
When register is called on a single file, only that file contributes to the hash (using
the file's parent directory as the base for the relative path).
Why this matters: if a release has already been promoted and you want to prove the
on-disk files have not changed since registration, run release verify. The same algorithm
runs and compares against the stored checksum.
flightdeck release verify rel_abc123 --path ./candidate-release
# OK: checksum matches for rel_abc123
# sha256=e3b0c44298fc1c149afb...
# On mismatch: exits with code 2 (distinct from normal CLI error code 1)flightdeck init writes flightdeck.yaml in the current directory. Almost all CLI commands
look for this file in the current working directory.
api_version: v1
kind: WorkspaceConfig
db_path: .flightdeck/flightdeck.db # SQLite database path (default when database_url unset)
# Optional: ledger on PostgreSQL (requires psycopg; install flightdeck-ai[postgres])
# database_url: postgresql://user:pass@localhost:5432/flightdeck
default_environment: local # default environment for register/diff/promote
diff:
min_candidate_runs: 500 # HIGH confidence threshold (candidate side)
min_baseline_runs: 500 # HIGH confidence threshold (baseline side)
min_low_runs: 50 # LOW confidence floor
# Optional: YAML PricingCatalog for cross-vendor comparable lines on diffs (see schemas/v1/pricing_catalog.schema.json)
# pricing_catalog_path: pricing/catalog.yaml
# Optional: when true, direct promote is rejected until a pending request is confirmed (HTTP/CLI request + confirm)
# promotion_requires_approval: falseAll fields have defaults; an empty flightdeck.yaml is valid. db_path accepts any
relative or absolute SQLite path — the parent directory is created automatically on first use.
When database_url is a postgresql:// (or postgres://) DSN, the ledger uses that
database instead and db_path is ignored for storage (keep flightdeck doctor --backup
on SQLite, or use pg_dump for Postgres).
pricing_catalog_path — optional path to a PricingCatalog YAML
(relative to the workspace cwd or absolute). When set, diffs include additive pricing.catalog / pricing.hints.
promotion_requires_approval — when true, POST /v1/promote and flightdeck release promote reject until a row is
completed via POST /v1/promote/request then POST /v1/promote/confirm (or CLI release promote-request / promote-confirm).
GET /v1/workspace exposes non-secret booleans for automation and the web UI (promotion_requires_approval,
pricing_catalog_configured, server_version).
diff.* thresholds are the workspace defaults used when the active policy does not
override them. The policy's min_* fields take precedence when set (including 0 for
"no minimum"). See operations-and-policy.md § Confidence tiers.
Pricing tables provide per-model token rates used by compute_diff to calculate
cost_per_run_usd. Each table is identified by (provider, pricing_version).
provider: openai # required; matches spec.pricing_reference.provider
pricing_version: "2024-05" # required; matches spec.pricing_reference.pricing_version
entries:
- model: gpt-4o
input_usd_per_1k_tokens: 2.50
output_usd_per_1k_tokens: 10.00
cached_input_usd_per_1k_tokens: 1.25 # optional; omit if no cached rate
- model: gpt-4o-mini
input_usd_per_1k_tokens: 0.15
output_usd_per_1k_tokens: 0.60All rate fields are non-negative floats. cached_input_usd_per_1k_tokens defaults to
null when omitted; cached-token cost is only applied when the rate is set.
JSON Schema: schemas/v1/pricing_table.schema.json
# First-time import
flightdeck pricing import pricing-openai-2024-05.yaml
# Replace an existing table (requires audit reason)
flightdeck pricing import pricing-openai-2024-05.yaml --replace --reason "corrected cached rate"
# Inspect an imported table
flightdeck pricing show --provider openai --version 2024-05Every import — including --replace — writes a record to the pricing_import_audit
SQLite table. This record is retained even if the table is later replaced again.
| Column | Type | Description |
|---|---|---|
import_id |
TEXT | pim_ + 12 random hex chars |
provider |
TEXT | Matches the table's provider field |
pricing_version |
TEXT | Matches the table's pricing_version field |
action |
TEXT | "insert" (first import) or "replace" (with --replace) |
actor |
TEXT | Value of $USER / $USERNAME at import time |
reason |
TEXT | Rationale (required for "replace", null for first imports) |
old_pricing_json |
TEXT | Serialized previous table contents; null for first imports |
new_pricing_json |
TEXT | Serialized new table contents |
new_checksum |
TEXT | SHA-256 hex of new_pricing_json (deterministic serialization) |
created_at |
TEXT | ISO-8601 timestamp of this audit event |
--replace requires --reason to be non-empty. Importing without --replace when a
table already exists returns an error.
To inspect the audit trail directly:
sqlite3 .flightdeck/flightdeck.db \
"SELECT import_id, provider, pricing_version, action, actor, reason, created_at
FROM pricing_import_audit ORDER BY created_at;"The diff compares each release using its own pricing_reference — a baseline and
candidate may reference different pricing versions. When they differ, the diff output flags
pricing_or_model_changed: true and prints a note warning that the cost delta includes
pricing assumption changes.
A diff fails with OperationError (HTTP 400 from the API) if either release's
pricing_reference does not match a loaded pricing table. Fix with
flightdeck pricing import.