Skip to content
Definition Authoring

Definition Authoring

Sovereign
Sovereign tier required. Workflow authoring turns repeatable delivery practice into validated, versioned automation.
This is the advanced path. Start with the Workflow Builder if you prefer visual authoring.

Author a definition when a repeated process should become portable, reviewable, and executable across projects. The YAML records its inputs, dependencies, decisions, and published results as one versioned contract.

Author a local workflow first

You can create a workflow in the Builder or save YAML through pm_workflow save_definition without creating a pack. Start with declared inputs and the final result, add the smallest set of steps, and validate before running it. Package the definition when you want to distribute it with capabilities, agents, or user tools.

This complete example checks an input and publishes it. It creates workflow history when tested, but launches no agent and calls no external tool:

schemaVersion: 1.0
workflow:
  key: example.input-check
  name: Check a workflow input
  version: 0.1.0
  start:
    mode: manual
  inputs:
    message:
      type: string
      required: true
      label: Message
    ready:
      type: boolean
      default: true
  outputs:
    message:
      type: string
      required: true
  outputMappings:
    message: "${steps.check_input.outputs.message}"
  steps:
    - key: check_input
      name: Check readiness
      kind: condition
      checks:
        - kind: value_equals
          value: "${workflow.inputs.ready}"
          equals: true
      outputs:
        message: "${workflow.inputs.message}"
      onFailure:
        action: block

Paste it into the Builder’s YAML dialog, apply it, and choose Test with a message. Keep Ready enabled to complete and inspect the declared message result; disable it to see the authored block. Save the definition when it behaves as intended.

Test executes the draft. If you add sessions, commands, user tools, or mutating Valdr actions, a test can launch agents and change real data. Use pm_workflow validate for a check that neither saves nor executes the definition.

File placement

Pack-authored workflows are standalone .workflow.yaml or .workflow.yml files under a pack.yaml include root:

example-workflows/
  pack.yaml
  workflows/
    task-delivery-review.workflow.yaml

Minimal definition

schemaVersion: 1.0
workflow:
  key: example.task.delivery-review
  name: Example Task Delivery Review
  version: 0.1.0
  pack: example-workflows
  source:
    kind: capability
    capabilityKey: example.workflow.task-delivery-review
  start:
    mode: manual
  inputs:
    taskKey:
      type: string
      required: true
  outputs:
    taskKey:
      type: string
      required: true
  outputMappings:
    taskKey: "${workflow.inputs.taskKey}"
  steps:
    - key: load_task
      name: Load task
      kind: tool
      tool:
        id: pm_task
        action: get
      inputs:
        taskKey: "${workflow.inputs.taskKey}"
      outputs:
        status: "$.normalized.task.status"

Every workflow needs a schema version, workflow key, name, version, start configuration, inputs, and steps. Add only the outputs the completed run should publish.

Definition fields

FieldRequiredPurpose
schemaVersionYesDefinition format; use 1.0
workflow.keyYesStable workflow identity
workflow.nameYesName shown in Valdr UI
workflow.descriptionNoShort explanation of the workflow outcome
workflow.versionYesExact version selected by runs and parent workflows
workflow.packPack sourceOwning pack key
workflow.sourcePack sourceCapability that owns the workflow source
workflow.start.modeYesUse manual
workflow.start.suggestedWhenNoCatalog hints for when the workflow is useful
workflow.inputsYesValues callers may or must provide
workflow.outputsNoResults the completed run publishes
workflow.outputMappingsFor required outputsMaps each required result from a workflow input, step output, or run-level runtime value
workflow.stepsYesExecutable steps and their dependencies

Input and output descriptors support string, boolean, number, integer, object, and array:

inputs:
  taskKey:
    type: string
    required: true
    description: Task to deliver
  includeDraft:
    type: boolean
    default: true

Use label for a friendly launch-field name and description for guidance. A default makes an optional input usable without a supplied value. Date inputs use type: integer with format: date; their values are epoch milliseconds. Output names must match [A-Za-z_][A-Za-z0-9_]*. Every required workflow output needs an outputMappings entry unless its descriptor supplies a type-correct default.

Version changes deliberately

Treat the workflow key and version as a published identity. When behavior changes, create a new version so existing runs and parent workflows keep the behavior they selected.

Update workflows installed from packs through their source pack so future imports include your changes. For a one-off variation, use Use as starting point in Valdr UI and save it under a new identity. If you need to replace a definition without changing its version, follow the MCP save instructions.

Existing runs and Run again continue to use the workflow steps, branching, and child workflow versions selected when the original run started. Saving an update does not switch those runs to the new version.

One exception applies when you manually retry an eligible Command step from a saved workflow: the next attempt can use the updated command, working directory, timeout, and environment settings saved under the same workflow key and version. Earlier attempts remain unchanged in the run history.

Step kinds

KindUse it to
toolCall an action-based Valdr MCP tool, bounded Git or GitHub action, trusted shell command, or pinned user tool
sessionLaunch or continue an agent session
reviewLaunch an independent reviewer
await_conditionWait for an expected result or evidence
human_gateRequire an operator decision or collect answers to authored questions
conditionOutcome route: evaluate a value for branching or repeat a bounded correction loop
subworkflowRun an exact version of another workflow

Human Gate Questions

In the Builder, select a Human Gate step, open Approval content in its inspector, and set its mode to Questions. Add the prompt and questions, then choose whether each question is required. Questions can be a literal list or an exact reference to an array produced by an earlier step. The gate waits for an authorized operator’s answers, which later steps can use through declared outputs. See the Human-Gate Questions contract for supported types, limits, outcomes, draft recovery, and a focused YAML example.

Add Preview documents in the same inspector when a specification, brief, or other document provides context for the questions. Bind each document’s path and producing session to outputs of an earlier step, and include that step in the gate’s needs. The operator can read and reload the context while answering.

Use a separate gate when the workflow needs an approval decision, feedback, a pull-request proposal, or session-output evidence. A question submission records the operator’s answers; it does not approve a diff or publish work.

Pin a user tool

User tools use kind: tool. Add one from the Builder’s User tools library group, such as Node text summary (valdr-tools.user.node), then inspect its YAML. Check Installed revision before copying the reference; the Builder prefers the highest installed semantic version. A tool with a single action has it selected automatically. The tool reference contains six fields:

FieldSource
idInstalled tool ID, such as valdr-tools.user.node
actionManifest action ID, such as summarize
revisionExact installed revision, as shown in the inspector’s Installed revision field
contentHashComplete sha256: hash of the selected tool’s content
inputSchemaExact selected action input schema
outputSchemaExact selected action output schema

Copy the reference from the Builder after selecting the installed revision. Don’t type a hash by hand or simplify the saved schemas; Valdr checks that the reference matches the installed tool before it runs. Put the action’s input under the step’s ordinary inputs field. Successful output is under $.normalized.data.

Optional cwd is a step field beside tool and inputs, not an action input, and it accepts expressions. An absolute path is used as-is; a relative path resolves from the workflow’s session worktree or project repository. Omit it to use that default, which falls back to Valdr’s own working directory when neither exists. Valdr doesn’t add project or workflow context to the tool’s request.

Importing a newer revision doesn’t change this reference or existing runs. To upgrade, review the new revision’s source and contract, select it explicitly, and save a new workflow version. See the User Tools example.

Expressions and outputs

Use expressions to connect declared data:

SyntaxMeaning
${workflow.inputs.taskKey}A workflow input
${steps.load_task.outputs.taskStatus}A named output from an upstream step
${runtime.actor}A runtime value Valdr provides for every run
TASK-${workflow.inputs.taskKey}Text interpolation

An exact reference preserves the value’s type, even though the expression is quoted in YAML: "${workflow.inputs.ready}" can supply a Boolean, and "${workflow.inputs.targets}" can supply an array. Adding surrounding text produces a string. Use nested properties and numeric indexes such as ${steps.load_task.outputs.task.title} or ${workflow.inputs.targets[0]} when those values exist.

Expressions select data; they do not run JavaScript, shell commands, functions, arithmetic, or JSONPath filters. Keep comparisons in checks or when. A missing exact reference has no value; a missing reference inside text contributes an empty string.

Paths beginning with $.normalized select values from the current step’s result when declaring its outputs. Later steps use ${steps.<key>.outputs.<name>}. Workflow outputMappings publish those declared values as the run’s final results. Prefer the Builder’s action-specific output picker to guessing a path from a direct MCP response.

Task and project targets

Set each step’s destination in the action’s taskKey or projectKey field, using a literal, a declared workflow input, or an earlier step’s output. Where an action permits a default, the run’s task or project association can supply it. An explicit step target takes precedence, so one workflow can work across several projects without changing its parent run’s association.

Input names are yours to choose. An alias such as valdrTaskKey identifies a destination only when you bind it to the action’s taskKey:

inputs:
  valdrTaskKey:
    type: string
    required: true
steps:
  - key: load_task
    name: Load target task
    kind: tool
    tool:
      id: pm_task
      action: get
    inputs:
      taskKey: "${workflow.inputs.valdrTaskKey}"

A task identifies its project, so you usually need only the task key. If you supply both a task and project for the same operation or run association, they must agree. A parent association for project A can still contain steps explicitly targeting project B. Sprint membership and sprint operations remain within one project; use separate sprints for tasks from different projects.

When starting a root run, the optional top-level taskKey and projectKey in pm_workflow start, or the UI’s Run scope controls, select its association. With neither selected, declared workflow inputs named taskKey or projectKey, including their defaults, can supply that association. Choosing an association explicitly does not replace those workflow inputs. Other input names do not select an association automatically.

Use ${runtime.taskKey} and ${runtime.projectKey} when a step should use the run’s association. If the run has neither association nor a usable input default, supply an explicit destination. Making an input optional does not make the action’s target optional. Destinations supplied by earlier steps are checked when the receiving step runs.

Runtime values

Runtime values are read-only context supplied by Valdr. You do not declare or assign them under Variables. Declare a workflow input for a value the caller should choose; use the same declared inputs for UI launches, MCP calls, draft tests, and Run again.

Reference fields offer a Runtime values group with the values available for that field and step. Type ${ to open the reference choices. Availability depends on where the expression is used.

Run values

These values are available in step expressions and final workflow outputMappings:

ReferenceValue
${runtime.runUlid}This run’s ID
${runtime.rootRunUlid}The top-level run’s ID; the same as runUlid for a root run
${runtime.taskKey}The run’s associated task, or null when it has none
${runtime.projectKey}The run’s associated project, derived from its associated task when present, or null when it has none
${runtime.contextRef}The run’s context reference
${runtime.createdFromSessionUlid}The session the run was created from, or null when none was supplied
${runtime.startedAt}The recorded run start time in epoch milliseconds, or null before it starts; it is not a live clock
${runtime.actor} or ${runtime.actorHandle}The actor recorded for the run, or pm when none was given. Both names keep that identity when another operator advances or approves the run

Use the task/project values when a step should use the run’s association. For a destination the caller chooses, declare an input and bind it to the action’s target field as described under Task and project targets.

Step attempt values

Step expressions can also use these values:

ReferenceValue
${runtime.stepKey}The current step’s key
${runtime.attempt}This step’s attempt number, starting at 1; it is not the loop pass number
${runtime.clientRequestId}The ID for this step attempt, for actions that require a client request ID

Step outputs keep this context when a wait, human approval, child workflow, or sequential forEach completes. Runtime values on a Run workflow step describe the parent run and step. For a single child, use $.normalized.child.runUlid for its ID; sequential forEach returns the child results under $.normalized.children.

Final workflow outputMappings cannot read step attempt or loop values directly. Capture the value in a step’s declared outputs, then publish it through ${steps.<key>.outputs.<name>}.

Loop values and missing data

Inside a bounded loop, runtime.loop.targetStepKey and runtime.loop.sourceStepKey identify the repeated path, while runtime.loop.currentPass and runtime.loop.maxPasses give the pass number and limit. Use each as an exact reference such as ${runtime.loop.currentPass}. Loop values are available only to steps on that repeated path.

${runtime.loop.previous} is null on pass 1. On later passes, it contains sourceStepRunUlid and outputs from the immediately preceding loop-back route attempt. ${runtime.loop.previous.outputs.<name>} must name an output declared by that route. Use previous values in step inputs, prompts, condition checks, or step outputs. A when check can use loop identity and counters, but cannot read loop.previous; capture the prior decision as a step output first.

An exact reference preserves null, including an absent task/project association or first-pass loop.previous. A property beneath a missing prior result, such as ${runtime.loop.previous.outputs.scope} on pass 1, has no value. Both null and missing values contribute an empty string inside surrounding text. A required action input can reject either, so choose a valid target or handle the first pass explicitly. Named step outputs describe current attempts; use loop.previous to carry the route’s decision across passes.

Validation rejects unknown runtime paths, properties beneath scalar values such as runtime.taskKey.title, and references unavailable at that location. The same rules apply to references in human-gate prompts and supporting content.

Map step outputs

Map only values later steps or the final result need:

- key: load_task
  name: Load Task
  kind: tool
  tool:
    id: pm_task
    action: get
  inputs:
    taskKey: "${workflow.inputs.taskKey}"
  outputs:
    taskStatus: "$.normalized.task.status"

Later steps use ${steps.load_task.outputs.taskStatus}. The Workflow Builder offers supported output choices for the selected action.

Dependencies

Use needs whenever a step depends on another step or consumes its outputs:

- key: mark_in_progress
  name: Mark Task In Progress
  kind: tool
  needs: [load_task]
  tool:
    id: pm_task
    action: change_status
  inputs:
    taskKey: "${workflow.inputs.taskKey}"
    to: in_progress
    actorHandle: "${runtime.actor}"

Dependencies must form an acyclic graph; list order and canvas position do not order execution. A skipped dependency counts as finished, but supplies no outputs. Add matching when checks to consumers of optional branches so they do not try to use missing results.

Compose another workflow

A subworkflow step selects an exact saved child version and binds its declared inputs:

- key: verify_delivery
  name: Verify Delivery
  kind: subworkflow
  workflow:
    key: example.task.verify-delivery
    version: 1.0.0
  inputs:
    taskKey: "${workflow.inputs.taskKey}"
  outputs:
    status: "$.normalized.child.outputs.status"

The child must declare every bound input and selected output. Its declared taskKey or projectKey, from mapped inputs or defaults, can select its own association; otherwise it inherits the parent’s association. See child associations. Publishing a newer child version does not update the parent automatically; change the parent’s selected version when you want the new behavior.

Wait for agent or review evidence

An await_condition step lists the outcomes it accepts and the agent or session allowed to provide them:

- key: await_executor
  name: Await Executor
  kind: await_condition
  needs: [launch_executor]
  waitsFor:
    kind: workflow_input
    expected: [executor_completed]
    authorizedHandles: ["${workflow.inputs.executorHandle}"]
    sourceSessionUlid: "${steps.launch_executor.outputs.executorSessionUlid}"
  outputs:
    outcome: "$.normalized.input.outcome"

This wait expects the selected agent or session to report an accepted outcome. Keep its accepted outcomes narrow and bind it to the session, review, or task that owns the evidence. For a new Session or Review launch, prefer waitsFor on the launch step itself. If the launch is detached: true, use both session and turn identifiers as shown in Join one detached turn; waiting for a session callback alone does not wait for the detached turn to finish.

Add a human gate

- key: approve_release
  name: Approve Release
  kind: human_gate
  needs: [prepare_release]
  humanGate:
    prompt: "Approve ${steps.prepare_release.outputs.releaseName}?"
    approvalText: Ship
    rejectionText: Hold
  waitsFor:
    kind: workflow_input
    expected: [approved, rejected]
    authorizedHandles: ["@release-operator"]
  outputs:
    outcome: "$.normalized.input.outcome"

Human gates may also present one to five reviewed documents and send feedback to the configured writer session. Feedback keeps the gate open; the operator approves or rejects only after reviewing the refreshed result.

Add a bounded correction loop

Use an Outcome route with loop_back when requested changes should return to an earlier step:

- key: route_review
  name: Route Review
  kind: condition
  needs: [await_review]
  checks:
    - kind: value_equals
      value: "${steps.await_review.outputs.outcome}"
      equals: review_approved
      domain: [review_approved, review_changes_requested]
  outputs:
    outcome: "${steps.await_review.outputs.outcome}"
  onFailure:
    action: loop_back
    to: implement
    max: 3
    exhausted: block

max is from 1 to 10 and counts the first pass and every correction pass. The domain must be a non-empty, unique list containing equals and every possible observed value. Keep loops narrow and bounded: corridor steps cannot declare retry, and corridor subworkflows cannot use forEach or detached.

Validate and import

For a local definition, call pm_workflow validate with definitionYaml or definitionJson, then fix its errors before saving or testing. Validation checks structure, references, supported actions, and resolvable dependencies. It does not establish provider authentication or prove that a command or external service will succeed.

When asking an agent to author a workflow, give it the expected inputs, results, side effects, and approval points. Have it inspect the installed catalog and exact child versions, validate the complete definition, and report what still requires execution testing:

  • Create a local workflow draft for this process. Inspect the installed workflow and tool contracts first, declare inputs and final results, connect every output consumer with needs, and use supported Session, Review, Human Gate, and Run workflow steps where appropriate. Validate the complete YAML with pm_workflow and show the diagnostics. Do not test or start the workflow until its side effects have been reviewed.

Validate the pack source:

valdr validate-pack example-workflows

Generate an archive:

valdr generate-valdr-pack example-workflows \
  --output scratch/example-workflows.valdr-pack.tar.gz

Open Settings in Valdr UI to preview and import the pack. Fix validation errors at the reported workflow and step before importing.

Next step

Continue to Build User Tools to add your own scripts and CLIs as workflow actions. To compose a definition visually, use the Workflow Builder.