Skip to main content

Workflow schema v1

This is the definitive reference for Loom's schema v1 — every key, type, constraint, and validation rule enforced by loom check.

Workflow files are authored in YAML and validated against schema v1.

If you're trying to author a workflow, start with Hello Loom and Syntax (v1). Come back here when you need the exact rule for a specific key.

Root structure

The root must be a YAML mapping. Two keys are required; four are optional. Any other key is treated as a job definition.

KeyRequiredTypeDescription
versionyesstringMust be the exact string "v1".
stagesyesstring[]Non-empty sequence of stage names. Defines execution order.
includenosequenceSequence of {local: <path>} entries for template includes.
workflownomappingWorkflow-level configuration. Currently supports rules.
variablesnomappingWorkflow-level variables. Keys: UPPER_SNAKE_CASE → string values.
defaultnomappingDefault job keyword values merged into every job.
<job_name>nomappingAny key matching the job naming pattern is treated as a job definition.

Unrecognized root keys that don't match the job naming pattern produce a schema error.

Naming patterns

EntityPatternMax lengthNotes
Stage name^[a-z][a-z0-9_-]{0,31}$32 charsLowercase, starts with letter.
Job name^\.?[a-z][a-z0-9_-]{0,63}$64 charsDot prefix (.) marks a template job.
Variable key^[A-Z_][A-Z0-9_]*$UPPER_SNAKE_CASE. Applies to both variables and secrets.

version

Must be the exact scalar string "v1". Any other value fails validation.

stages

A non-empty YAML sequence of unique stage names. Each stage name must match ^[a-z][a-z0-9_-]{0,31}$. Duplicate stage names are rejected.

stages: [ci, build, deploy]

include

A YAML sequence of include entries. Each entry must be a mapping with a local key.

include[].local constraints

  • Must start with .loom/templates/
  • Must end with .yml or .yaml
  • Must not contain ..
include:
- local: .loom/templates/go-jobs.yml

See Includes & templates for how included templates interact with extends.

workflow

A mapping with optional key rules for workflow-level gating logic.

See Workflows → Rules for the rules syntax.

variables

A mapping of UPPER_SNAKE_CASE keys to string values. Applies at the workflow level (available to all jobs).

variables:
GO_VERSION: "1.22"
NODE_ENV: production

Variable keys must match ^[A-Z_][A-Z0-9_]*$. Values must be scalar strings.

default

A mapping of job keyword defaults that are merged into every job definition. Supports these keys:

KeyTypeDescription
targetstringDefault execution target. Must be "linux".
imagestring or mappingDefault container image. See image.
runner_poolstringDefault runner pool identifier.
variablesmappingDefault variables merged into each job.
invariantmappingDefault invariant configuration.
cachemapping or sequenceDefault cache configuration. See cache.
servicessequenceDefault sidecar services. See services.

default.secrets is explicitly invalid and fails validation. Secrets must be declared per-job.

Jobs

Any root key matching the job naming pattern (^\.?[a-z][a-z0-9_-]{0,63}$) is treated as a job definition. Jobs whose name starts with . are template jobs.

Job-level keys

KeyTypeRequiredDescription
stagestringyes (non-template)Must reference a name declared in stages.
targetstringyes (non-template)Execution target. Must be "linux" (MVP).
scriptstring[]yes (non-template)Non-empty sequence of command strings.
extendsstring or string[]noTemplate job(s) to inherit from.
needsnoAccepted by schema. Used for dependency declarations.
imagestring or mappingnoContainer image. See image.
runner_poolstringnoRunner pool identifier.
variablesmappingnoJob-scoped variables.
secretsmappingnoJob-scoped secrets. See secrets.
invariantmappingnoInvariant configuration.
cachemapping, sequence, or nullnoCache configuration. See cache.
servicessequencenoSidecar services. See services.
artifactsmappingnoArtifact extraction configuration. See artifacts.

Unknown job keys are rejected. The complete allowed set is: stage, target, script, extends, needs, image, runner_pool, variables, secrets, invariant, cache, services, artifacts.

Non-template job requirements

Non-template jobs must have all three: stage, target, and script.

Template job requirements

Template jobs (dot-prefixed names like .go-base) must have at least one of script or extends.

script

A non-empty YAML sequence of non-empty strings. Each entry is a single command.

  • Empty strings are rejected.
  • Embedded newlines within a single entry are rejected. Use one command per entry.
script:
- echo "step one"
- make build
- ./run-tests.sh

image

Controls whether a job runs on the Host or Docker provider and which container image is used. Accepted at both default and job level.

Scalar form

A non-empty string naming a Docker image (pullable by the Docker daemon):

image: node:20-slim

Mapping form

KeyTypeRequiredDescription
namestringyesNon-empty image reference.
buildmappingnoBuild configuration for the image.

Unknown keys under image are rejected.

image.build

KeyTypeRequiredDescription
contextstringyesBuild context directory. Maps to docker build context.
dockerfilestringyesDockerfile path. Maps to docker build --file.
outputstring or mappingnoForwarded to docker build --output.

Unknown keys under build are rejected.

image:
name: my-app:latest
build:
context: .
dockerfile: Dockerfile

Runtime notes

  • When no image is specified, the job runs on the Host provider.

secrets

Declares sensitive values resolved at runtime from external providers, injected into the job environment, and redacted from all persistent output.

Placement rules

  • Allowed: per-job blocks only.
  • Disallowed: default.secrets is explicitly invalid and fails schema validation. This minimizes accidental fan-out of secrets across jobs.

Secret spec shape

<job_name>:
secrets:
<SECRET_NAME>:
ref: <provider-uri>
file: true
required: true

Fields

KeyTypeRequiredDefaultDescription
refstringyesProvider reference URI (env://, keepass://, op://). Must be non-empty.
filebooleannotruetrue: inject temp-file path. false: inject raw value.
requiredbooleannotruetrue: fail on unresolved. false: silently omit.

Validation rules

  • Secret names must match ^[A-Z_][A-Z0-9_]*$ (same as variable keys).
  • ref must be non-empty. Scheme/shape validity is enforced during secrets resolution.
  • file and required must be booleans.
  • A key cannot appear in both variables and secrets for the same effective job after merge — this fails with a schema/planner error.

Supported provider URI schemes

SchemeFormatStatus
env://env://<ENV_VAR_NAME>Implemented
keepass://keepass://<db-alias>#<entry-path>:<field>Implemented
op://op://<vault>/<item>/<field>Implemented (op CLI + token required)

Injection behavior

  • file: true (default): value written to a node-scoped temp file (0600 permissions). The variable is set to the file path. Docker providers bind-mount and rewrite to container-local paths.
  • file: false: variable set directly to the secret value. Blocked when CI_DEBUG_TRACE=true.

Redaction

All resolved values are redacted from runtime output before persistence (events.jsonl, receipt fields, error messages). Redaction replaces matches with [REDACTED:SECRET_<NAME>].

Error codes

Resolution failures produce structured SECRETS_* error codes. See Secrets error codes.

Example

deploy:
stage: ci
target: linux
image: loom:nix-local
secrets:
DATABASE_PASSWORD:
ref: env://DEPLOY_DB_PASSWORD
file: true
API_TOKEN:
ref: env://DEPLOY_API_TOKEN
file: false
script:
- ./scripts/deploy.sh

artifacts

Declares files to extract from the job workspace after execution. Extracted artifacts are copied to .loom/.runtime/logs/<run_id>/jobs/<job_id>/artifacts/, preserving their relative directory structure.

Artifact mapping keys

KeyTypeRequiredDescription
pathsstring[]yesNon-empty sequence of glob patterns matching files/directories to extract.
excludestring[]noGlob patterns for files to exclude from extraction.
namestringnoHuman-readable name for the artifact set.
whenstringnoWhen to extract: on_success (default), on_failure, or always.

Unknown keys under artifacts are rejected.

build:
stage: ci
target: linux
image: node:20-alpine
script:
- npm run build
artifacts:
paths:
- dist/
exclude:
- dist/**/*.map
name: build-output
when: on_success

artifacts:when values

ValueBehavior
on_success (default)Extract only when the job succeeds.
on_failureExtract only when the job fails. Useful for test reports or core dumps.
alwaysExtract regardless of job outcome.

Runtime notes

  • Artifact extraction runs as a system section (artifact_extract) after the job's user scripts complete.
  • Extraction events appear in jobs/<job_id>/system/artifact_extract/events.jsonl.
  • Extracted files remain under jobs/<job_id>/artifacts/. When at least one file is extracted, an archive is also produced at jobs/<job_id>/artifacts/artifacts.tar.gz.
  • The job manifest includes artifacts.archive_path, artifacts.archive_format, and artifacts.archive_size_bytes when an archive exists. The pipeline manifest includes artifacts_archive_path per job.
  • If no files match the configured paths (after exclusions), extraction succeeds with zero files copied and no archive is produced.
  • Paths are relative to the job workspace root.

services

Declares sidecar containers that run alongside the job. Accepted at both default and job level.

Must be a YAML sequence. Each entry is either a scalar image string or a mapping.

Scalar form

A non-empty string naming the service image:

services:
- postgres:15
- redis:7

Mapping form

KeyTypeRequiredDescription
namestringyesNon-empty image reference for the service.
aliasstringnoNetwork alias for the service container.
entrypointstring[]noOverride container entrypoint. Non-empty sequence of strings.
commandstring[]noOverride container command. Non-empty sequence of strings.
variablesmappingnoService-specific environment variables.

Unknown keys are rejected. The following keys produce explicit "not supported yet" errors: docker, kubernetes, pull_policy.

services:
- name: postgres:15
alias: db
variables:
POSTGRES_DB: testdb
POSTGRES_PASSWORD: test

Runtime notes

Services are supported in Docker jobs (those with image: set).

cache

Configures job-level caching. Accepted at both default and job level.

Accepted forms

FormMeaning
cache: nullExplicitly disables cache for this job.
cache: []Explicitly disables cache for this job.
cache: {paths: [...], ...}Single cache mapping.
cache: [{name: ..., paths: [...]}, ...]Sequence of named cache entries.

Cache mapping keys

KeyTypeRequiredDescription
namestringyes (sequence)Unique name for the cache entry. Required when cache is a sequence.
disabledbooleannoDisables this cache entry when true.
pathsstring[]yes (unless disabled)Non-empty sequence of paths to cache.
keymappingnoCache key specification. Allowed sub-keys: prefix and files.
fallback_keysstring[]noFallback cache keys tried if primary key misses.
policystringnoOne of: pull, push, pull-push.
whenstringnoOne of: on_success, on_failure, always.

Unknown cache keys are rejected.

Example

cache:
paths:
- node_modules/
key:
prefix: deps
files:
- pnpm-lock.yaml
policy: pull-push
when: on_success

See Workflows → Cache for key placeholder syntax and strategies.

Minimal valid example

The smallest workflow that passes schema v1 validation:

version: v1
stages: [ci]

check:
stage: ci
target: linux
script:
- echo "hello"

Validator vs runtime semantics

Schema v1 has two layers:

  • Validator contract — what loom check enforces (YAML shape + constraints). Everything on this page.
  • Runtime semantics — what loom run does when executing jobs (provider routing, service lifecycle, caching behavior, artifact writes).

Some keywords are accepted by the schema for forward compatibility, but their runtime effect may be partial or planned. When in doubt, treat what's documented on Syntax (v1) as the contract.

Runtime-only behavior (not validated by loom check)

  • Provider routing — host vs Docker based on image presence. See Providers.
  • Services lifecycle — sidecar containers via Docker provider. See Docker provider.
  • Artifact extraction — files copied to .loom/.runtime/logs/<run_id>/jobs/<job_id>/artifacts/ with an archive at artifacts/artifacts.tar.gz when files exist. See Runtime logs contract.

Versioning policy

Schema v1 is expected to evolve:

  • Additive changes remain compatible within v1 when possible.
  • Breaking changes ship as a new schema version with migration notes.

See the CLI overview for the current command surface.

Cross-references