Skip to content

Latest commit

 

History

History
393 lines (307 loc) · 13.8 KB

File metadata and controls

393 lines (307 loc) · 13.8 KB

YAML Workflows

Motivation

Directory-based workflows require one file per state — a natural fit for large workflows, but heavyweight for small ones. A three-state workflow needs a directory, three .md files, and possibly a few .sh scripts. YAML workflows let you define the entire workflow in a single file:

states:
  1_START:
    prompt: |
      Greet the user, then transition.
    allowed_transitions:
      - tag: goto
        target: DONE.md
  DONE:
    prompt: |
      Say goodbye.
    allowed_transitions:
      - tag: result

Any CLI command that accepts a directory or ZIP scope also accepts a .yaml or .yml file.

Schema

A YAML workflow file has a single required top-level key, states, whose value is a mapping of state names to state definitions:

states:
  STATE_NAME:
    # ... state definition (see below)

Constraints:

  • states must be present, must be a mapping, and must contain at least one entry.
  • State names must be non-empty strings and must not contain path separators (/ or \).
  • Duplicate state names are not allowed.

Markdown states

A markdown state has a prompt key and optional policy fields:

states:
  REVIEW:
    prompt: |
      Review the code for correctness.
      Focus on edge cases and error handling.
    allowed_transitions:
      - tag: goto
        target: FIX.md
      - tag: result
    model: sonnet
    effort: high
Field Type Required Description
prompt string yes The prompt text sent to the model (supports {{input}} and other template variables — see authoring-guide.md)
allowed_transitions list no Constrains which transitions the model may use
model string no Model override (opus, sonnet, haiku)
effort string no Extended thinking level (low, medium, high)
timeout number no Per-state timeout in seconds. Overrides the workflow-level timeout and the run-level default (--timeout / config) for this state. 0 means no timeout. Negative values are a validation error.
force_implicit bool no When true, the orchestrator dispatches the (single) allowed transition without parsing the model's output for tags. Requires the policy to be implicit-eligible. See workflow-protocol.md.

Each entry in allowed_transitions is a mapping with a tag key and optional attributes like target, payload, return, etc. — the same attributes used in directory-based frontmatter. Targets reference virtual filenames (see Virtual files).

Script states

A script state has one or more platform keys (sh, ps1, bat) and no prompt:

states:
  BUILD:
    sh: |
      #!/usr/bin/env bash
      set -euo pipefail
      make build 2>&1
    bat: |
      @echo off
      nmake build 2>&1

At least one platform key must be present:

Field Type Description
sh string Shell script (Unix)
ps1 string PowerShell script (Windows)
bat string Batch script (Windows)
timeout number Per-state timeout in seconds (same semantics as on markdown states)

Restrictions: Script states must not have allowed_transitions, model, effort, or force_implicit — these policy fields apply only to markdown states.

Mutual exclusivity

Each state must be exactly one type — markdown or script. A state with both prompt and a script key (e.g. sh) is a validation error. A state with neither is also an error.

Workflow-level timeout

A top-level timeout key sets a workflow-wide idle timeout (seconds) applied to every state that does not declare its own. Unlike the daemon manifest keys below, it is honoured by both ray <file> (CLI) and ray serve:

timeout: 300        # workflow-wide default for all states
states:
  START:
    prompt: ...

0 disables the timeout; a negative value is a validation error.

Timeout precedence (most specific wins):

  1. A state's own timeout:.
  2. The top-level workflow timeout:.
  3. The run-level default — --timeout (CLI) or [raymond] timeout in .raymond/config.toml.
  4. The built-in default (600s).

An author's timeout: (state or workflow level) therefore always beats the run-level default; --timeout / config act as the fallback for workflows that don't set one. ray serve resolves that run-level default from the same [raymond] timeout (or the 600s built-in) as the CLI, so a workflow's effective timeout is identical whether run via ray <file> or the daemon.

Unknown fields

Unrecognized state keys (e.g. a misspelled force_implcit) are tolerated at runtime so a typo never aborts a run — the key is ignored and the workflow continues, but a non-fatal warning is emitted to the event stream so it's visible to anyone watching. ray lint reports the same keys as a unknown-field warning, which is the intended place to catch them before a run. The same behavior applies to --- frontmatter in directory-based .md state files. Top-level manifest keys (name, description, input, backend, etc.) are not treated as state fields.

Note this is distinct from a recognized field used in the wrong place — e.g. model or force_implicit on a script state — which remains a hard validation error, since that's a structural mistake rather than a typo.

Virtual files

YAML workflows present states as virtual files so the rest of the system (orchestrator, lint, diagram) can treat them identically to directory-based workflows:

State type Virtual filename(s)
Markdown STATE_NAME.md
Script (sh only) STATE_NAME.sh
Script (ps1 only) STATE_NAME.ps1
Script (bat only) STATE_NAME.bat
Script (multiple) One file per platform, in order: .sh, .ps1, .bat

When a markdown state is read, its policy fields are synthesized into YAML frontmatter — the same format used in directory-based .md files. A state with no policy fields produces bare prompt text with no frontmatter block.

CLI usage

Start a workflow

# Entry point resolved automatically (looks for 1_START or START)
ray workflow.yaml

# Start at a specific state
ray workflow.yaml/REVIEW

# With flags
ray workflow.yaml/REVIEW -m sonnet --input "review this PR"

The workflow.yaml/STATE syntax is not a filesystem path — the CLI splits on the YAML extension to extract the scope and initial state.

Resume

ray --resume <workflow-id>

On resume, the YAML file is re-parsed and re-validated. If the file has been deleted or become invalid since the workflow was started, the resume fails with a clear error.

Lint

ray lint workflow.yaml
ray lint --json --level error workflow.yaml

Runs the same static analysis checks as directory-based workflows (missing transition targets, unreachable states, etc.).

Diagram

ray diagram workflow.yaml
ray diagram --html --output diagram.html workflow.yaml

Generates a Mermaid flowchart or interactive HTML diagram from the workflow states and transitions.

Entry point resolution

When no initial state is specified (ray workflow.yaml), the entry point is resolved using the same rules as directory-based workflows:

  1. Look for a state named 1_START (any extension)
  2. Fall back to a state named START
  3. If both exist, report an ambiguity error
  4. If neither exists, report an error

When an initial state is given (ray workflow.yaml/REVIEW), the bare name is resolved to its virtual filename (REVIEW.md or REVIEW.sh, depending on the state type).

Errors

The YAML scope produces three error types:

Error When
Parse error File cannot be read, or YAML syntax is invalid
Validation error YAML is well-formed but violates schema rules (missing states, dual-type state, script with policy fields, etc.)
File not found A requested virtual filename does not correspond to any state

When states are defined at the root level without a states: wrapper, the parser detects common state-like keys and suggests adding the wrapper.

Manifest Metadata for Daemon Discovery

A YAML workflow may carry manifest metadata as top-level keys alongside states. Every well-formed YAML scope file in a configured serve root is discovered by the daemon (ray serve) — there is no opt-in field. The workflow id is derived from the filename (including extension), so vendor-approval.yaml is registered as vendor-approval.yaml.

The explicit id: field is no longer supported. The workflow id is always derived from the on-disk filename (for yaml-scope and zip archives) or the directory name (for directory-scope workflows). If a manifest declares an id: field, the parser hard-rejects it.

Field Type Default Description
name string "" Human-readable display name.
description string "" Description shown in tool/endpoint documentation.
input object {mode: optional} Describes the single optional string passed to the first state as {{input}}. See Input block.
default_budget float64 0 Default USD budget when callers don't specify one.
working_directory string "" Default working directory for runs.
environment map[string]string nil Environment variables for runs. Supports ${VAR} interpolation.
requires_human_input string "auto" "auto", "true", or "false". Controls whether the daemon rejects the workflow from contexts that cannot deliver human input. At discovery time, auto resolves to false for YAML workflows, matching the behavior for directory and zip scopes.

Input block

A workflow receives at most one string of input at launch time, substituted into the first state as {{input}}. The input block describes that string for UI surfaces, and lets the daemon enforce whether input is permitted or required.

Field Type Default Description
mode string "optional" One of required, optional, none. required rejects launches with an empty input; none rejects launches with a non-empty input.
label string "" Short prompt shown in the web UI field.
description string "" Longer help text shown under the input field.

The entire input block is optional. Omitting it is equivalent to input: {mode: optional} with no label or description.

Adding manifest fields has no effect on CLI behavior. ray workflow.yaml runs the workflow identically whether or not the manifest fields are present — the CLI ignores them. They are consumed only by the daemon registry.

Example: self-describing YAML workflow

The workflow id is the filename. Save the example below as vendor-approval.yaml and the daemon registers it as vendor-approval.yaml.

name: Vendor Approval
description: Evaluates a vendor proposal and routes through human approval
default_budget: 5.0
input:
  mode: required
  label: Vendor name
  description: The vendor to evaluate (passed to the first state as {{input}}).
requires_human_input: auto

states:
  1_START:
    prompt: |
      Research the vendor "{{input}}" and prepare an analysis covering
      price, support SLA, and references.

      STOP after writing your analysis. Do not make a recommendation yet.
    allowed_transitions:
      - tag: ask
        next: DECISION.md
        timeout: "48h"
        timeout_next: ESCALATE.md

  DECISION:
    prompt: |
      The reviewer's decision: {{input}}

      Execute the decision and produce a final report in vendor_report.md.
    allowed_transitions:
      - tag: result

  ESCALATE:
    prompt: |
      The vendor decision timed out. Send an escalation notice and close.
    allowed_transitions:
      - tag: result

This file can be run directly (ray workflow.yaml), discovered by the daemon (ray serve --root .), linted (ray lint workflow.yaml), and diagrammed (ray diagram workflow.yaml).

Comparison with other scope types

Directory ZIP YAML
One file per state yes yes (inside archive) no — all states in one file
Best for Large workflows, version control Distribution, immutable snapshots Small workflows, prototyping
Script support .sh/.bat/.ps1 files Same, inside archive sh/ps1/bat keys in state definition
Policy via frontmatter In each .md file Same allowed_transitions, model, effort, timeout keys
Manifest Separate workflow.yaml file workflow.yaml inside archive Embedded in same file
CLI syntax ray dir/ or ray dir/FILE.md ray archive.zip ray workflow.yaml or ray workflow.yaml/STATE

Complete example

A workflow that reviews code, optionally fixes issues, then reports results:

states:
  1_START:
    prompt: |
      You are a code reviewer. Analyze the code provided in {{input}}.
      If you find issues, transition to FIX. Otherwise, transition to REPORT.
    allowed_transitions:
      - tag: goto
        target: FIX.md
      - tag: goto
        target: REPORT.md
    model: sonnet
    effort: high
    timeout: 120

  FIX:
    prompt: |
      Apply the fixes you identified. When done, transition to REPORT
      with a summary of changes.
    allowed_transitions:
      - tag: goto
        target: REPORT.md

  REPORT:
    prompt: |
      Summarize the review outcome and any changes that were made.
    allowed_transitions:
      - tag: result
ray workflow.yaml --input "$(cat src/main.go)"

Transition targets use virtual filenames — .md for markdown states, .sh or .bat for script states. For example, a transition to a script state named BUILD would use target: BUILD.sh (Unix) or target: BUILD.bat (Windows).