YAML flows (escape hatch)
YAML is the power-user authoring path. It runs unchanged on the same executor, matcher, drivers, and reports as prose flows — the only difference is you write the IR yourself instead of letting the planner do it.
# flows/login.flow.yaml
# yaml-language-server: $schema=../.klera/flow.schema.json
name: Login smoke
defaultTimeoutMs: 8000
steps:
- assert: { visible: { testID: login-screen } }
- type:
into: { testID: login-email }
value: pm@example.com
- tap: Sign In
- wait:
for: { testID: welcome-greeting }
timeoutSeconds: 5Run it: klera run flows/login.flow.yaml. No LLM in the loop.
When to choose YAML
Reach for YAML when:
- Procurement won’t approve LLM spend. Hand-authored YAML never touches a planner. You can still run prose flows by compiling them out-of-band (manual transport), but YAML lets you skip planning entirely.
- You want a hand-tuned regression suite. A 200-line YAML flow with
precise
testIDs and tight timeouts iterates faster than recompiling prose every time you tweak a selector. - You’re importing from Maestro. klera reads Maestro YAML directly via the compatibility loader. See Migrating from Maestro.
- The prose surface is too loose. Some flows want explicit coordinate
gestures, raw
assertJSexpressions, or mock-network arrays that read more naturally as YAML than as prose.
Default to prose for new flows. Reach for YAML when one of the above applies. Both run on the same executor; the choice is about authoring ergonomics, not capability.
The schema
The IR is defined as a Zod schema in @klera/protocol/ir.ts. Every flow
parses through it on load — invalid YAML fails fast with a clear path
into the offending step.
A flow is an object with a name and a steps array. Three top-level
fields are tunable:
name: Login smoke
description: Asserts a fresh user can sign in. # optional
defaultTimeoutMs: 10000 # default per-step timeout (ms); default 10s
selfHealing: true # opt out by setting false
steps:
- tap: Sign In
- assert: { visible: Welcome }Two fields gate cross-platform divergence detection — see iOS and Android for the full picture:
parallel: ['ios', 'android']
divergenceTolerance:
visualDiffPercent: 1.5
textNormalize: trueEvery step is a single-key object. The key names the step kind (tap,
type, assert, …); the value is the payload. See the
IR reference for the full vocabulary.
Long form vs short form
Most steps accept a short-form sugar that normalises to the long form at parse time. The two forms are interchangeable; pick whichever reads better.
Short form
steps:
- tap: Sign In
- tap: { testID: login-submit }
- swipe: up
- screenshot: home.png
- setBiometric: success
- waitForIdle: true
- relaunch: true
- visualSnapshot: home-after-loginShort forms are not shortcuts the executor handles separately — they normalise to long-form IR at parse time, and the cached IR (or report payload) only contains the long form. Matcher behaviour, telemetry attributes, and report shape are identical regardless of which form you authored.
Editor support via JSON Schema
Add the language-server directive at the top of every YAML flow and your editor delivers autocomplete, hover docs, and inline validation:
# yaml-language-server: $schema=../.klera/flow.schema.json
name: Login smoke
steps:
- tap: # ← autocomplete suggests targetsMaterialise the schema once after klera init:
klera schema --out .klera/flow.schema.json
klera schema --out .klera/flow.schema.json --pretty # readableThe schema is derived from the same Zod schema the runtime parses, so
the editor’s view of “what’s valid” never drifts from what the executor
accepts. Works with VS Code, JetBrains, Neovim, Helix, Sublime —
anything that runs yaml-language-server.
See editor support for the end-to-end setup.
Opting into YAML scaffolding
klera init scaffolds a .flow.md + paired .flow.json cache by
default. To opt into a YAML-only scaffold:
npx @klera/cli init --yamlThis writes flows/welcome.flow.yaml with a hand-authored body, skips
the planner entirely, and configures .klera/config.yaml to expect
YAML files. You can mix YAML and prose flows in the same flows/
directory — the loader detects the file extension and dispatches to
the right pipeline.
klera init --yaml is for adopters who want to skip the prose loop
entirely. If you only want a YAML flow alongside your prose flows,
just drop a *.flow.yaml file into flows/ — no special init flag
needed.
A realistic flow side by side
The same login scenario, both surfaces, both run on the same executor.
Prose
---
fixtures:
email: pm@example.com
password: hunter2
---
# Login smoke
Type the email into the email field and the password into the password
field, then tap "Sign in". Wait up to five seconds for the welcome
greeting to appear.
If a "What's New" modal appears between login and the welcome screen,
dismiss it before the assertion.Both files run identically:
klera run flows/login.flow.md # prose path — loads paired .flow.json
klera run flows/login.flow.yaml # YAML path — parses straight to IRThe matcher, the strategy ladder, the bounded self-healing, the failure-evidence flush, the auto-triage, the OTel emission — all identical. You’re choosing how the IR is produced, not how it runs.
Validation
klera validate flows/login.flow.yaml # one file
klera validate flows/ # whole directoryValidation parses the YAML through the Zod schema and exits non-zero on
any error. Useful as a pre-commit hook or a fast CI gate before the
heavier klera run step.
klera validate flows/login.flow.yaml
✗ flows/login.flow.yaml
steps[3].type: Required
expected object, got undefinedSentinels in YAML values
YAML flows reference fixtures and secrets the same way prose flows do —
through the ${env:KEY} and ${secret:KEY} sentinels. The resolver
fires before each step’s value reaches the runtime, and the per-run
Redactor scrubs ${secret:...} values from every artefact.
- type:
into: { testID: login-email }
value: ${env:E2E_LOGIN_EMAIL}
- type:
into: { testID: login-password }
value: ${secret:E2E_LOGIN_PASSWORD}
- tap: { testID: login-submit }See fixtures and secrets for the full
treatment, including .env resolution order, fixture files, and what
the Redactor scrubs.
Next steps
- IR reference — every step kind, parameter shape, and short-form sugar.
- Migrating from Maestro — Maestro YAML runs unchanged via the compatibility loader.
- Editor support — autocomplete, hover docs, inline validation across every major editor.