Loading image...Kiro

Product

  • About Kiro
  • IDE
  • CLI
  • Web
  • Mobile
  • Crew
  • Pricing
  • Downloads

For

  • Enterprise
  • Startups
  • Students

Community

  • Overview
  • Ambassadors
  • Discord
  • Events
  • Powers
  • Shop
  • Showcase

Resources

  • Docs
  • Blog
  • Changelog
  • FAQs
  • Report a bug
  • Suggest an idea
  • Billing support

Social

Site TermsLicenseResponsible AI PolicyLegalPrivacy PolicyCookie Preferences
Loading image...Kiro
  • CLI
  • Web
  • Enterprise
  • Pricing
  • Docs
SIGN INDOWNLOADS
Loading image...Kiro

Get Started

InstallationAuthenticationYour first project

Models

OverviewAvailable modelsReasoning effort

Features

How Kiro works
Specs
Steering
Hooks
MCP
Permissions
Custom agents
Workflows
Overview
Author workflows
Workflow examples
Run and manage workflows
Agent Skills
Powers
Cloud sessionsCompactionKiroignoreCheckpoints and rewind
Built-in tools
Configuration scopes

IDE 1.x

What's new in 1.0
Setup & First Run
Editor
Chat
Experimental
Troubleshooting0.x reference

CLI

What's new in 3.0
Setup & First Run
Terminal UI
Chat
Fullscreen modeVoice modeHeadless modeACPAuto complete
Experimental
2.x reference

Crew

Quick startInstallationRunning 24/7
Chat
Agent Capabilities
Features
Interfaces
Apps
System & storageConfigurationSecurityTroubleshooting

Web

Setup & First RunIdentity Center
Connect your repositories
Working with the agent
Autonomous modeAutomationsMemoryConfiguration Sync
Sandbox

Mobile - Preview

Overview

Commands and Reference

CLI commandsSlash commandsBuilt-in toolsExit codesSettings

Billing

OverviewManaging your subscriptionUpgrading your planDowngrading your planCancelling your planPurchasing add-on creditsManaging your paymentsManaging usage notificationsManaging your taxesContacting billing supportDeleting your accountRelated questions

Enterprise

ConceptsOnboarding quickstart
Connecting your identity provider
Deployment optionsSubscribe your teamManage subscriptions
Governance
Monitor and track
SettingsManaged updatesBillingIAMSupported regions

Privacy and Security

OverviewData protectionCode referencesCompliance validationInfrastructure securityIAM permissionsFirewalls, proxies, and data perimetersVPC endpoints (AWS PrivateLink)

Guides

Overview
Language support
Learn by playing

Migration

Migrating from Q DeveloperMigrating from VSCodeUpgrading from Q CLI
  1. Docs
  2. Features
  3. Workflows
  4. Author workflows
View as Markdown

Author workflows

View as Markdown

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.

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 {{previous.output}} for implement. Before launching, ask Kiro to validate the saved recipe:

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:

AgentRole
wf-plannerRead-only investigation and planning; writes reports and plans
wf-coderImplements changes, runs tests, commits
wf-designWrites technical designs
wf-design-reviewerReviews a design against requirements
wf-review-aggregatorMerges several review reports into one
wf-pr-submitterOpens or reuses a pull request and records its URL
wf-pr-responderResponds to pull request feedback
wf-auto-researcherProposes, applies, and measures one experiment per iteration
wf-workflow-creatorDrafts a recipe from a task description; used by the orchestrator, not in your steps
semantic_reviewerBehavioral 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. Use narrowly scoped agents for unattended steps and review the permissions implied by each agent before launching the recipe.

Root fields

FieldRequiredDescription
nameYesStable recipe name
descriptionNoPurpose shown by recipe selectors
inputsNoMap of input names to free-form type hints such as prompt, file, or string; defaults to {}
modelIdNoDefault model for steps; auto or omission inherits from the parent session
effortLevelNoDefault reasoning effort for steps
stepsYesArray 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.

TypePurposeRequired fields
stepRun one named agent in its own sessionid, agent, prompt
sequenceRun child nodes in orderid, steps
repeatRun child nodes until a condition matches or a cap is reachedid, steps, maxIterations, onMaxIterations
parallelSchedule independent branches and join their resultsid, branches, joinPolicy
watchPoll an external system through a handler without using model turns while idleid, 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.

Figure 1.3: node types · one of each in a small recipe

A recipe is a tree of executable leaves and containers

Select any row to inspect the fields and runtime rule behind it.

selected · steps

release‑check

implicit sequence at the recipe root

required steps

top‑level nodes run in order

selected · steps

release‑check

implicit sequence at the recipe root

required steps

top‑level nodes run in order

indentation and trunk lines encode parent-child relationships; badges identify executable leaves, recursive containers, and the conditions that let a repeat exit or a step complete; the detail pane follows the selected row

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:

FieldBehavior
artifactsMaps logical names to files produced by the step
captureOutputCaptures the step's final assistant message for later templates; defaults to true
completionKeeps the step interactive until a stop condition matches
modelIdOverrides the workflow and parent-session model
effortLevelOverrides the workflow and parent-session reasoning effort

Make the requested deliverable the step's final response. That response is what {{step-id.output}} and {{previous.output}} 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:

severityRecorded signalEffect on the step
successsuccessStep completes; output is captured; the workflow advances
warningneed_inputStep and run pause for user input; the step stays paused after each turn until it signals success or error
errorerrorStep fails; the failure propagates up the tree
infononeProgress 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.

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 {{artifacts.design}}. 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:

Figure 1.4: prompt wiring · what a step reads and writes
1/24·Launch
Paused. Step 1 of 24.

Select a step to see what it reads and writes. Step back through the run to catch a value before its producer has finished.

Recipe structure

repeatcode‑loopmax 3
parallelreviewsjoin all

plannot started · wf-planner

reads

  • {{task}}launch input
  • {{run_dir}}launch input

writes

  • {{plan⁠.⁠output}}read by implement
  • {{artifacts⁠.⁠plan}}read by implement

01Launch. You supply the task and run directory as declared inputs.

the tree on the left is the recipe structure; the list on the right names every value the selected step reads and where it comes from, then what it writes and who reads it. A value whose producer has not finished shows in amber

FormValue
{{previous.output}}Captured output of the most recent completed sibling in the same container
{{step-id.output}}Captured output of a prior step or watch by ID
{{steps.step-id.output}}Legacy alias for {{step-id.output}}
{{artifacts.name}}Path registered under name by a prior step
{{input-name}}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 {{previous.output}} in the first node of a container.
  • Do not use {{previous.output}} 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. {{unknown}} remains literal text and produces a validation warning rather than an error. Kiro provides no implicit variables. Every bare {{name}} 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 ({{setup.output}}/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. 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:

onMaxIterationsResult
abortAbort the repeat and the run
continueComplete the repeat and move to its next sibling
pausePause 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.

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.

Figure 1.5: parallel join · one situation, three answers

Two branches. What should the parent do when…

branch 1 failedbranch 2 running

  • allfailed nowreports the failure immediately and cancels branch 2
  • allSettledstill runningwaits for branch 2 to finish, then reports failed with every result kept
  • anystill runningbranch 2 can still complete and rescue the parent

Use all when one failure makes the rest pointless. Use allSettled when you need every branch's evidence, such as independent reviews. Use any when the first good result is enough.

pick what happened to the two branches; each line shows how one joinPolicy resolves it, including which siblings it cancels

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 {{watch-id.output}} 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 {"outcome": "idle-timeout", "idleTimeoutSec": <seconds>}, 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 fieldPurpose
prRef or urlOne is required: a workspace JSON file with a top-level url, or the PR URL directly
pollIntervalSecPoll cadence; default 60, minimum 30
includeOwnActivityLet comments by the authenticated gh identity wake the watch; default false
ignoreAuthorsLogins whose activity never wakes the watch, such as a bot that rewrites a marker comment
commandTimeoutSecOptional timeout for each underlying gh call

The watch reports terminal-state when the PR is merged or closed. On new activity, {{wait-for-pr.output}} 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", "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.

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 {{watch-id.output}}.

json
{ "type": "watch", "id": "review", "handler": "command", "config": { "command": "node watch-review.mjs", "reviewFile": "mock-review.json", "pollIntervalSec": 10, "commandTimeoutSec": 5 } }
config fieldPurpose
commandRequired. One static command line, run by the session's default shell in the run's workspace
pollIntervalSecPoll cadence; default 60, minimum 10
commandTimeoutSecOptional per-poll timeout in seconds; positive, no minimum
anything elseYour 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 {"revision":1} into the input's cursor and run again: the program prints {"outcome":"idle","cursor":{"revision":1}}. 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.

FieldMatches when
containsTextThe latest captured output contains the given text
completionSignalThe latest step signal is success, need_input (sent as send_message severity warning), or error
fileCheckA JSON value at jsonPath deep-equals value

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

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 for current public options. Workflow examples 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;
  • 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.

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 provides complete graphs to adapt for investigation, feature delivery, troubleshooting, and pull request delivery.
  • Run and manage workflows explains progress, step interaction, controls, and recovery across clients.
Page updated: September 30, 2026
Workflows
Workflow examples