> ## Documentation Index
> Fetch the complete documentation index at: https://kiro.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Author workflows

> Create and validate declarative workflow recipes with steps, loops, branches, watches, templates, and artifacts.

Start in the parent chat. Describe the outcome, constraints, agents, and review depth you need; Kiro can generate a workflow for the task at hand. Save the graph as a recipe when it becomes useful to repeat, review, or share.

A workflow recipe is a JSON or YAML file that declares inputs and a tree of nodes. Store project recipes in `.kiro/workflows/` with one of these suffixes:

- `*.workflow.json`
- `*.workflow.yaml`
- `*.workflow.yml`

Kiro discovers these files at runtime. If files with the same stem use more than one supported suffix, the JSON file takes precedence and Kiro reports the other file as a conflict.

Store personal recipes that should be available across workspaces in `~/.kiro/workflows/`. Recipe-name precedence is project, user, synchronized account recipes, then bundled recipes. An invalid higher-priority recipe does not hide a valid lower-priority recipe. Available synchronized and bundled recipes can vary by account and client version.

In Kiro Web, upload one supported recipe from **Settings → Workflows**. To import a complete `.kiro/workflows/` folder, use [Configuration Sync](https://kiro.dev/docs/web/cloud-configuration.md).

## Create a two-step recipe

Create `.kiro/workflows/plan-and-implement.workflow.json`:

```json
{
  "name": "plan-and-implement",
  "description": "Plan a change, then implement it",
  "inputs": {
    "task": "prompt"
  },
  "steps": [
    {
      "type": "step",
      "id": "plan",
      "agent": "wf-planner",
      "prompt": "Create an implementation plan for: {{task}}. Do not edit files."
    },
    {
      "type": "step",
      "id": "implement",
      "agent": "wf-coder",
      "prompt": "Implement this plan, then run relevant tests:\n\n{{previous.output}}"
    }
  ]
}
```

The `plan` step's final output becomes `}` for `implement`. Before launching, ask Kiro to validate the saved recipe:

```text
Run `validate_workflow` on `.kiro/workflows/plan-and-implement.workflow.json`. Report every error and warning, and do not launch the Workflow.
```

Fix any reported errors, then launch the recipe through your client. In IDE, ask Kiro in the parent chat to run the saved recipe and describe the task; then open **Workflows** above the parent chat and select the `plan` step to open its dedicated **Workflow step** tab. In CLI, run `/workflow run plan-and-implement --task "Describe the change"`; if you omit a declared input, Kiro opens a form to collect it. Select the `plan` step in the monitor to follow its session. In Kiro Web, upload the recipe under **Settings → Workflows**, ask Kiro in a cloud session to run it and describe the task, then open **Workflows** and select the `plan` step to follow or message it.

**ℹ️ Info:** The `agent` value must resolve to a workflow-capable agent. You can use the agents bundled for workflows or an agent defined in `.kiro/agents/`. A built-in sub-agent is not a valid workflow step agent.

These agents ship with Kiro for use in recipes. They are available to steps only while workflows are enabled and do not appear in the `/agent` picker; the exact list your client offers is the one Kiro shows when it describes `run_workflow`:

| Agent | Role |
|---|---|
| `wf-planner` | Read-only investigation and planning; writes reports and plans |
| `wf-coder` | Implements changes, runs tests, commits |
| `wf-design` | Writes technical designs |
| `wf-design-reviewer` | Reviews a design against requirements |
| `wf-review-aggregator` | Merges several review reports into one |
| `wf-pr-submitter` | Opens or reuses a pull request and records its URL |
| `wf-pr-responder` | Responds to pull request feedback |
| `wf-auto-researcher` | Proposes, applies, and measures one experiment per iteration |
| `wf-workflow-creator` | Drafts a recipe from a task description; used by the orchestrator, not in your steps |
| `semantic_reviewer` | Behavioral code review; registered as both a workflow step agent and a general sub-agent |

A step's tool access comes from its agent definition, including its `tools` and `excludedTools` settings. The bundled `wf-planner`, `wf-coder`, and `wf-design` agents can use installed Powers. The bundled `wf-design` agent can also use configured MCP servers. Use narrowly scoped agents for unattended steps and review the permissions implied by each agent before launching the recipe.

## Root fields

| Field | Required | Description |
|---|:---:|---|
| `name` | Yes | Stable recipe name |
| `description` | No | Purpose shown by recipe selectors |
| `inputs` | No | Map of input names to free-form type hints such as `prompt`, `file`, or `string`; defaults to `` |
| `modelId` | No | Default model for steps; `auto` or omission inherits from the parent session |
| `effortLevel` | No | Default reasoning effort for steps |
| `steps` | Yes | Array of workflow nodes; top-level nodes run in order |

Input type hints help a launch interface collect values, but Kiro does not enforce them at runtime. Define every bare template variable in `inputs` and supply every value at launch.

## Node types

A recipe is a tree. The root `steps` array is an implicit sequence; containers nest to compose control flow, and `step` and `watch` nodes are the leaves that do work.

| Type | Purpose | Required fields |
|---|---|---|
| `step` | Run one named agent in its own session | `id`, `agent`, `prompt` |
| `sequence` | Run child nodes in order | `id`, `steps` |
| `repeat` | Run child nodes until a condition matches or a cap is reached | `id`, `steps`, `maxIterations`, `onMaxIterations` |
| `parallel` | Schedule independent branches and join their results | `id`, `branches`, `joinPolicy` |
| `watch` | Poll an external system through a handler without using model turns while idle | `id`, `handler`, `config` |

The tree below places one of each node type in a small generic recipe. Select any row to see how that type behaves at runtime; the badges are the same ones used in every later figure. The `gate` badge is not a node type: it marks a `stopCondition`, the check a repeat evaluates after each iteration, described under [Define stop conditions](#define-stop-conditions).

Every node `id` must be unique across the static recipe. A recipe can contain at most **50 `step` nodes** and can nest nodes to a maximum depth of **8**. Repeat iterations do not consume additional static step slots.

## Configure a step

A `step` supports these optional fields in addition to `id`, `agent`, and `prompt`:

| Field | Behavior |
|---|---|
| `artifacts` | Maps logical names to files produced by the step |
| `captureOutput` | Captures the step's final assistant message for later templates; defaults to `true` |
| `completion` | Keeps the step interactive until a stop condition matches |
| `modelId` | Overrides the workflow and parent-session model |
| `effortLevel` | Overrides the workflow and parent-session reasoning effort |

Make the requested deliverable the step's final response. That response is what `}` and `}` pass downstream.

### Signal the outcome with `send_message`

A step agent signals its outcome by calling the `send_message` tool. The tool posts a short note to the parent session, and its `severity` doubles as the step's lifecycle signal:

| `severity` | Recorded signal | Effect on the step |
|---|---|---|
| `success` | `success` | Step completes; output is captured; the workflow advances |
| `warning` | `need_input` | Step and run pause for user input; the step stays paused after each turn until it signals `success` or `error` |
| `error` | `error` | Step fails; the failure propagates up the tree |
| `info` | none | Progress note only; no lifecycle effect |

Kiro injects this protocol into every step session automatically, so you do not need to explain it in your prompt. It does help to tell the agent *when* to signal, for example "Finish with `send_message` severity `success` once the tests pass." A turn that ends without any `send_message` call, and without an unmet `completion` condition, completes the step.

`send_message` is the outward half of a two-way channel: guidance can also reach a step while it runs or waits. Write prompts that leave room for that guidance rather than assuming the step runs unattended; see [Respond when a step needs input](https://kiro.dev/docs/workflows/manage.md#respond-when-a-step-needs-input).

**ℹ️ Info:** Only the step agent itself can signal. A workflow custom agent can invoke sub-agents when its tool configuration permits it, but those sub-agents run outside the workflow's session tree and cannot call `send_message` or `update_workflow` on the parent step's behalf.

### Pass artifacts

Use artifacts when a file is the durable handoff:

```json
{
  "type": "step",
  "id": "design",
  "agent": "wf-planner",
  "prompt": "Write the design for {{task}} to .kiro/workflow-output/design.md.",
  "artifacts": {
    "design": ".kiro/workflow-output/design.md"
  }
}
```

A later step can reference the registered path as `}`. Relative artifact paths resolve from the primary workspace root.

## Use template variables

Every value a step can read arrives through a `}` template. Inputs come from launch, captured outputs come from the last message of an earlier step or the JSON payload of a watch, and artifacts come from files an earlier step declared. Templates work in step prompts, artifact paths, stop-condition file paths, and string values in a `watch` node's `config`.

The prompt is the wiring. Pick a step below to see every value it reads, which step produced each one, and what it publishes for later steps. Step the run back with the transport and a value whose producer has not finished yet turns amber, which is the ordering rule the rest of this section spells out:

| Form | Value |
|---|---|
| `}` | Captured output of the most recent completed sibling in the same container |
| `}` | Captured output of a prior `step` or `watch` by ID |
| `}` | Legacy alias for `}` |
| `}` | Path registered under `name` by a prior step |
| `}` | Launch input declared in `inputs` |

Structured references must point to a producer that runs before the consumer. They fail the consuming step if Kiro cannot resolve them. In particular:

- Do not use `}` in the first node of a container.
- Do not use `}` inside a parallel branch; branches have no guaranteed order.
- Do not reference outputs or artifacts from a later sibling or another parallel branch.
- In a repeat, a node's captured output is replaced on each iteration, so later consumers see the latest output.

Unknown bare inputs behave differently. `}` remains literal text and produces a validation warning rather than an error. Kiro provides no implicit variables. Every bare `}` must be declared in `inputs` and supplied at launch.

**ℹ️ Info:** When a captured output is interpolated into a step prompt, Kiro wraps it in tamper-resistant delimiters that an earlier step cannot forge. A confused or compromised upstream step therefore cannot inject text that a downstream agent would mistake for its instructions. Artifact paths, watch config values, and stop-condition templates resolve to bare values. Because of the framing, avoid splicing a captured output into a file path inside a prompt (`}/report.md`); pass directories as inputs or register files as artifacts instead.

## Compose control flow

### Sequence

Use `sequence` to name and group nodes that must run in order. The root `steps` array is already an implicit sequence, so add an explicit `sequence` only when nesting makes the plan clearer.

### Repeat

A repeat always runs its body at least once. After each completed iteration, Kiro evaluates the stop condition; a match completes the repeat before another iteration begins. For `fileCheck`, Kiro reads the JSON file at `path` and compares the value at `jsonPath` with `value`. `jsonPath` is a dotted key path; a leading `$.` names the document root, so `$.complete` and `complete` read the same field. Before Kiro CLI 2.33, a `jsonPath` that started with `$.` never matched and the repeat ran to its cap; in 2.33 or later both forms work. Relative paths resolve from the primary workspace root. A missing or unreadable file, invalid JSON, missing JSON path, or value mismatch does not match, so the repeat continues.

```json
{
  "type": "repeat",
  "id": "implementation-loop",
  "maxIterations": 10,
  "onMaxIterations": "pause",
  "stopCondition": {
    "fileCheck": {
      "path": ".kiro/workflow-output/status.json",
      "jsonPath": "$.complete",
      "value": true
    }
  },
  "steps": [
    {
      "type": "step",
      "id": "implement-next-item",
      "agent": "wf-coder",
      "prompt": "Implement the next incomplete item and update .kiro/workflow-output/status.json."
    }
  ]
}
```

A repeat may omit both `stopCondition` and `stopWhen`; it then runs to `maxIterations`.

Choose what happens at the cap:

| `onMaxIterations` | Result |
|---|---|
| `abort` | Abort the repeat and the run |
| `continue` | Complete the repeat and move to its next sibling |
| `pause` | Pause for a human decision |

Resuming a repeat paused at its cap does not add iterations; it pauses again immediately. Revise the remaining plan, or stop the run and launch the corrected definition as a new run. In Kiro CLI 3.2.0 or later, you can instead grant one more iteration or mark the loop complete; see [Resume](https://kiro.dev/docs/workflows/manage.md#resume).

### Parallel

A parallel node schedules each branch independently:

```json
{
  "type": "parallel",
  "id": "reviews",
  "joinPolicy": "allSettled",
  "branches": [
    {
      "type": "step",
      "id": "security-review",
      "agent": "semantic_reviewer",
      "prompt": "Review the current changes for security issues."
    },
    {
      "type": "step",
      "id": "test-review",
      "agent": "semantic_reviewer",
      "prompt": "Review the current changes for missing tests."
    }
  ]
}
```

Three policies decide when the parallel node settles and whether one branch's failure cancels its siblings: `all`, `allSettled`, and `any`. A paused branch never cancels its siblings; a branch can pause when, for example, a nested repeat reaches `onMaxIterations: "pause"`. Pick a situation below to see how each policy would resolve it; the difference is timing and cancellation.

**⚠️ Warning:** Kiro starts parallel branches concurrently. Use `parallel` when branches need separate context or a join policy, and measure any wall-clock gain rather than assuming one.

Interactive steps with `completion` cannot be nested inside `parallel`.

### Watch

A `watch` node polls an external system through a handler without spending model turns while nothing changes. When the handler reports new activity, its JSON payload becomes `}` for the next step. A terminal result stays latched for `stopWhen: "watch-id.terminal"`. Two handlers are built in: `github-pr` watches a pull request, and `command` runs a program you write, so a workflow can wait on anything a script or CLI can read.

`idleTimeoutSec` sits on the watch node itself, not in `config`, and applies to either handler. When set, a watch that stays idle for that many seconds ends with a terminal outcome and its output becomes ``, so `stopWhen: "watch-id.terminal"` fires and any following step still runs; write that step's prompt to handle a timeout as well as real activity. The idle clock restarts each time a repeat re-enters the watch, and the repeat's `maxIterations` does not bound the idle polls within one entry. Unset means the watch waits indefinitely.

#### Watch a pull request with `github-pr`

Point the handler at a URL, or at a workspace JSON file whose top-level `url` field contains the pull request URL. The file form is the usual pattern when an earlier step opens the PR and records where it went:

```json
{
  "type": "watch",
  "id": "wait-for-pr",
  "handler": "github-pr",
  "config": {
    "prRef": ".kiro/workflow-output/pr.json",
    "pollIntervalSec": 60
  },
  "idleTimeoutSec": 3600
}
```

| `config` field | Purpose |
|---|---|
| `prRef` or `url` | One is required: a workspace JSON file with a top-level `url`, or the PR URL directly |
| `pollIntervalSec` | Poll cadence; default 60, minimum 30 |
| `includeOwnActivity` | Let comments by the authenticated `gh` identity wake the watch; default `false` |
| `ignoreAuthors` | Logins whose activity never wakes the watch, such as a bot that rewrites a marker comment |
| `commandTimeoutSec` | Optional timeout for each underlying `gh` call |

The watch reports `terminal-state` when the PR is merged or closed. On new activity, `}` is a JSON object your responder prompt can rely on; every key is always present:

```json
{
  "url": "https://github.com/org/repo/pull/42",
  "state": "OPEN",
  "newComments": [],
  "newReviews": [],
  "inlineComments": [],
  "excludedComments": [],
  "excludedReviews": [],
  "excludedInlineComments": [],
  "inlineCommentsFetch": "ok",
  "backlog": { "remaining": 0 },
  "headSha": "4b715b8b",
  "mergeable": "MERGEABLE",
  "mergeStateStatus": "CLEAN",
  "newFailedChecks": [],
  "passingCheckCount": 12,
  "pendingCheckCount": 3
}
```

`state` is `OPEN`, `MERGED`, or `CLOSED`. The `new*` arrays hold unseen activity that woke the watch; the `excluded*` arrays hold unseen activity from ignored authors, for context only. Each item wakes the watch at most once; edits and deletions do not wake it. Delivery is budgeted at 10 wake-relevant items per poll, oldest first; anything beyond that drains on later polls and is counted in `backlog.remaining`. The terminal payload is exempt from the budget and carries every remaining item. In Kiro CLI 2.33 or later, each delivered inline comment also carries `threadId`, the id of its review thread, or `null` when Kiro could not read it, so a responder can resolve the thread it answers.

In Kiro CLI 2.33 or later, a merge conflict with the base branch also wakes the watch; before 2.33, a conflicting pull request left it idle. `mergeable` is `MERGEABLE`, `CONFLICTING`, or `UNKNOWN` while GitHub is still computing the state, as it is right after a push. `mergeStateStatus` carries GitHub's finer state, such as `CLEAN`, `DIRTY`, or `BEHIND`, for context only; it never wakes the watch. A conflict is reported once per head commit and base branch tip, so a pull request that stays conflicting does not re-wake every poll, while a new head that still conflicts, or a newer base tip, does. Write the responder prompt to merge the base branch when `mergeable` is `CONFLICTING`; the bundled `wf-pr-responder` does this with a merge commit and never rebases or force-pushes.

A failed CI check also wakes the watch. `headSha` is the head commit the checks ran on, or `null` when unknown. Each `newFailedChecks` entry carries `name`, `workflowName`, `conclusion`, `detailsUrl`, `runId`, `startedAt`, and `completedAt`, and a failure is reported once per head commit, check name, details URL, and completion time, so a check that stays red does not re-wake every poll while a new head or a rerun does. Failed checks are never budgeted, and a failed Actions check is held until its run finishes so one wake carries every failure of that run. `passingCheckCount` and `pendingCheckCount` summarize the rest; skipped, cancelled, neutral, and stale checks count toward neither. When a responder needs full check details, have it run `gh pr view <url> --json statusCheckRollup`.

Across an interruption and recovery, delivery is at least once, so write responder prompts that tolerate a repeated item.

`prRef` paths are confined to the workspace roots the same way `fileCheck` paths are. Kiro validates handler configuration when the run is created; the standalone `validate_workflow` tool cannot perform that registry-dependent check.

#### Watch anything else with `command`

Use `handler: "command"` when a script or CLI can tell you whether something changed: a ticket in your issue tracker, a deployment, a build in another CI system, a review in a tool without a built-in handler. Kiro starts your program once per poll; the program checks for changes, prints one result, and exits. An idle result keeps the watch polling with no agent turn. A new-activity or terminal-state result completes that watch entry, and the next step reads the payload from `}`.

```json
{
  "type": "watch",
  "id": "review",
  "handler": "command",
  "config": {
    "command": "node watch-review.mjs",
    "reviewFile": "mock-review.json",
    "pollIntervalSec": 10,
    "commandTimeoutSec": 5
  }
}
```

| `config` field | Purpose |
|---|---|
| `command` | Required. One static command line, run by the session's default shell in the run's workspace |
| `pollIntervalSec` | Poll cadence; default 60, minimum 10 |
| `commandTimeoutSec` | Optional per-poll timeout in seconds; positive, no minimum |
| anything else | Your own keys, passed to the program as data |

Rules that go with those fields:

- The shell is PowerShell on Windows and `$SHELL` elsewhere.
- Put fixed arguments inside the `command` string. There is no `args` key, and a `config` that carries one is rejected. A `}` template anywhere in `command` is rejected at submission.
- An unset `commandTimeoutSec` lets a poll wait indefinitely. A timeout ends the command's whole process tree and counts as an idle poll with the old cursor, so set it long enough for one poll's work. The node's `idleTimeoutSec` is checked between polls and does not interrupt a hung command.
- Top-level string values among your own keys support templates, resolved when the watch is entered; nested objects and arrays pass through unexpanded. Treat them as data in your program, never as shell text.

Each poll writes one JSON object to the program's standard input, then closes it. `config` holds every watch config key except `command`; `workspacePath` is the primary workspace and the process's working directory; `additionalDirectories` lists the other workspace roots. The first poll of a run has `cursor: null`.

```json
{
  "cursor": null,
  "config": {
    "reviewFile": "mock-review.json",
    "pollIntervalSec": 10,
    "commandTimeoutSec": 5
  },
  "workspacePath": "/path/to/project",
  "additionalDirectories": []
}
```

The program writes exactly one JSON object to standard output and exits 0:

```json
{
  "outcome": "new-activity",
  "cursor": { "revision": 1 },
  "payload": "{\"state\":\"OPEN\",\"summary\":\"Please add a test.\"}"
}
```

- `outcome` is `idle`, `new-activity`, or `terminal-state`.
- `cursor` is required and can be any JSON value, including an explicit `null`; it must never be omitted. Kiro stores it opaquely, on idle polls too, hands it back on the next poll, carries it across repeat iterations, and restores it when a run resumes. Your program decides what counts as new; Kiro never compares payloads or event IDs, and a new run starts from `null`.
- `payload` is an optional string. Use `JSON.stringify` when the next step needs structured data, and supply it on both `new-activity` and `terminal-state` results; an idle payload is not captured.
- `targetId` is an optional stable string naming the watched resource. Kiro uses it to derive a run label when none was supplied, not to deduplicate events.

Exit codes decide what a failed poll means. Exit 0 with a valid result is the poll result; exit 0 with malformed JSON, an invalid `outcome`, or a missing `cursor` fails the watch node. Exit 2 fails the node as invalid input, and exits 126 and 127 fail it because the shell could not execute or could not find the program, so no later poll would succeed either. Any other non-zero exit, including a `commandTimeoutSec` kill, is an idle poll, so a program that is briefly unhappy retries on the next poll. PowerShell does not preserve these reserved codes; on Windows they can arrive as a generic non-zero exit and keep the watch polling, so end a watch there through the result instead: return `terminal-state` with an explanatory payload and exit 0.

Every spawn goes through the permission policy of the session that launched the run. A rule that asks sends the prompt to that session, and a denial fails the watch node; pausing or cancelling the watch withdraws an open prompt, so a late answer has no effect. The command line runs the way an `execute_bash` command runs for that session: inside its sandbox when one is active, and not at all when a configured sandbox cannot be prepared, in which case the node fails with the reason rather than running unconfined. A run whose launching session is not loaded waits for it, pausing with the detail code `ParentSessionUnavailable` and continuing on its own when the session loads; a deleted launching session fails the node.

Keep standard output free of logs, banners, and progress messages, including output from any CLI you call, and send diagnostics to standard error; keep credentials out of both. Kiro imposes no byte cap on the streams, the cursor, or the payload and no item cap, so bound them in your program: the payload is stored with the run and may enter the next agent's prompt. For a real event stream, return a small batch plus IDs or a file reference for the rest, and advance the cursor only past items you delivered or deliberately excluded. Delivery is at least once across an interruption, so the responder must tolerate duplicates.

The example below needs no network. Save this snapshot as `mock-review.json` in your workspace; `revision` is a counter you bump whenever the summary or state changes:

```json
{ "revision": 1, "state": "OPEN", "summary": "Please add a test." }
```

Save the watcher as `watch-review.mjs` beside it. It reports the snapshot on the first poll, stays idle while the revision is unchanged, reports a higher revision as new activity, and reports a non-`OPEN` state as terminal:

```js
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';

try {
  let input = '';
  for await (const chunk of process.stdin) input += chunk;
  const { cursor, config, workspacePath } = JSON.parse(input);
  const review = JSON.parse(
    await readFile(resolve(workspacePath, config.reviewFile), 'utf8')
  );
  const seen = cursor?.revision ?? 0;
  let result = { outcome: 'idle', cursor };
  if (review.state !== 'OPEN' || review.revision > seen) {
    result = {
      outcome: review.state === 'OPEN' ? 'new-activity' : 'terminal-state',
      cursor: { revision: review.revision },
      payload: JSON.stringify({ state: review.state, summary: review.summary })
    };
  }
  process.stdout.write(JSON.stringify(result) + '\n');
} catch {
  process.stderr.write('Invalid input or unreadable review file\n');
  process.exitCode = 2;
}
```

Test it by hand before putting it in a recipe: save the stdin object above as `poll-input.json` with your workspace's absolute path, then run `node watch-review.mjs < poll-input.json` in a POSIX shell, or `Get-Content -Raw poll-input.json | node watch-review.mjs` in PowerShell. The first poll prints the `new-activity` result shown earlier. Copy `` into the input's `cursor` and run again: the program prints `}`. Change the snapshot to revision 2 and run once more with the same input, and the cursor advances with the new summary in the payload. A real watcher should distinguish a bad configuration (exit 2) from a temporary service failure (any other non-zero exit), which this local example does not need to.

Put the watch and its responder inside a `repeat`, with `stopWhen` on the repeat rather than the watch. The responder also runs for the terminal payload, because the repeat checks `stopWhen` after its body finishes:

```json
{
  "name": "mock-review-watch",
  "steps": [
    {
      "type": "repeat",
      "id": "review-loop",
      "maxIterations": 5,
      "onMaxIterations": "pause",
      "stopWhen": "review.terminal",
      "steps": [
        {
          "type": "watch",
          "id": "review",
          "handler": "command",
          "config": {
            "command": "node watch-review.mjs",
            "reviewFile": "mock-review.json",
            "pollIntervalSec": 10,
            "commandTimeoutSec": 5
          }
        },
        {
          "type": "step",
          "id": "respond-to-review",
          "agent": "wf-coder",
          "prompt": "Summarize this mock review without changing files. If MERGED or CLOSED, report that it is finished.\n\n{{review.output}}"
        }
      ]
    }
  ]
}
```

Set the snapshot's state to `MERGED` and bump its revision to exercise the terminal result and watch the loop stop.

## Define stop conditions

The gate rows in the node-type tree above are `stopCondition` objects. Use `stopCondition` on a repeat or as a step's `completion`. When an object includes more than one field, any single match stops the loop or completes the step.

| Field | Matches when |
|---|---|
| `containsText` | The latest captured output contains the given text |
| `completionSignal` | The latest step signal is `success`, `need_input` (sent as `send_message` severity `warning`), or `error` |
| `fileCheck` | A JSON value at `jsonPath` deep-equals `value`. `jsonPath` is a dotted key path; `verdict` and `$.verdict` read the same field, and a bare `$` compares the whole document |

A `fileCheck` path outside every allowed workspace root fails the node.

In Kiro CLI 2.33 or later, a `jsonPath` that contains `[` or `]`, `*`, `..`, or a `$` segment after the first (as in `review.$.verdict`) fails validation, and the error names the first unsupported token. Before 2.33, such a path never matched and the repeat ran to its cap with no warning. A key that begins with `$`, such as `$.$schema` or `status.$ref`, still passes and is read as written.

If `value` is an array, Kiro treats it as an any-of list. Wrap a literal array in another array:

```json
{
  "fileCheck": {
    "path": "result.json",
    "jsonPath": "labels",
    "value": [["ready", "reviewed"]]
  }
}
```

A repeat can use `stopWhen` instead of `stopCondition`:

```json
{
  "stopWhen": "wait-for-pr.terminal"
}
```


```json
{
  "stopWhen": "{{aggregate.output}} contains VERDICT: APPROVED"
}
```

Do not set both forms on the same repeat.

## Choose models, providers, and effort per step

Each step resolves its model and effort in this order:

1. Step override.
2. Workflow default.
3. Parent session setting captured when the run is created.
4. The step agent's default.

Use `modelId: "auto"` or omit `modelId` to inherit. Effort values depend on the selected model. An unsupported effort value is reconciled to that model's default when the step session is created; an unknown model passes validation with only a warning, then fails at the step's first model call with no fallback, so a guessed model id fails the run mid-flight.

Choose configurations for the role each step performs:

- Use a stronger reasoning configuration for requirements, design, difficult diagnosis, or synthesis when the expected quality gain justifies the additional time and usage.
- Use an available lower-latency configuration for mechanical setup, focused edits, or checks that are independently verified.
- Give independent review branches different available model or provider configurations when diversity of judgment is valuable.
- Keep the evaluator and success rubric explicit; disagreement between branches is experiment evidence, not a workflow failure.

Workflow shape and model choice can be tested together. Run the same task set through alternative `plan → execute → verify` graphs, record quality, elapsed time, and estimated usage as artifacts, then compare them with a declared evaluator. A model that performs well with one broad prompt may behave differently when work is decomposed into planning, execution, critique, and revision steps.

Do not assume automatic provider selection, fallback, or performance optimization. Provider, model, and effort availability varies by account and client. Validate every `modelId` before launch and see [Available models](https://kiro.dev/docs/models/available-models.md) for current public options. [Workflow examples](https://kiro.dev/docs/workflows/patterns.md#experiment-with-orchestration) shows the comparison shape.

## Validate before launch

After saving the complete JSON or YAML recipe, ask Kiro to run `validate_workflow` on the file before you launch it. The tool reports the load-time constraints a recipe violates, including:

- schema errors and invalid enum values;
- duplicate node IDs;
- more than 50 static steps or more than 8 nesting levels;
- malformed `stopWhen` expressions;
- `fileCheck` `jsonPath` values that use `[`, `]`, `*`, `..`, or a second `$` segment;
- invalid happens-before relationships in structured templates;
- file-check paths that are provably outside the workspace;
- built-in sub-agents used where a workflow-capable agent is required.

A passing result does not prove that the run can start. The standalone validator cannot reject an unknown custom-agent name or validate runtime watch-handler availability and configuration. Kiro performs those checks when it creates the run.

### Security and trust

A recipe you author can run shell commands, edit files, push branches, and poll external systems with a program you supply, in background sessions you can inspect, pause, resume, or steer while they run. The agent definition selects the tools each step can see; the permission rules of the session that launched the run govern every step and every `command` watch, and each step's agent rules apply to that step as well, as they do for any session. Set `tools` and `excludedTools` on step agents, cap every repeat, and give watches an `idleTimeoutSec`. A recipe under the workspace's `.kiro/workflows/` directory does not load until you trust the workspace; personal recipes under `~/.kiro/workflows/`, synchronized recipes, and bundled recipes follow their own precedence rules. For the full picture, see [Security and trust](https://kiro.dev/docs/workflows.md#security-and-trust).

## Authoring checklist

- Give every node a unique, descriptive ID.
- Keep every file handoff inside an allowed workspace root.
- Supply all declared inputs before launch.
- Put a finite `maxIterations` on every repeat and choose the cap behavior deliberately.
- Keep parallel branches independent.
- Hand off files as artifacts; hand off short text as captured output.
- Validate the final definition, then run a small input before relying on it for a long task.

## Next steps

- [Workflow examples](https://kiro.dev/docs/workflows/patterns.md) provides complete graphs to adapt for investigation, feature delivery, troubleshooting, and pull request delivery.
- [Run and manage workflows](https://kiro.dev/docs/workflows/manage.md) explains progress, step interaction, controls, and recovery across clients.
