Skip to main content
A Scout workflow moves data through a shared state object that’s passed from block to block. Each block reads what it needs from earlier blocks, does its job, and adds its own output back to the state for downstream blocks to use. Designing that state deliberately is what makes a workflow predictable, testable, and easy to debug.

How State Moves Between Blocks

Every block can read from earlier blocks using double-brace template references:
  • {{ inputs.field_name }} — reads a field from the Input block (the workflow’s trigger payload)
  • {{ block_id.output }} — reads the full output of a prior block by its ID
  • {{ block_id.some_field }} — reads a specific field from a prior block’s output
Reference the block by the ID you gave it, then the field you want. The reference is resolved at runtime with the current state.

A Practical Example

Consider a three-block chain: an Input block, an enrich_user block that looks up account details, and a send_email block that composes a message.
{# In the enrich_user block — read from the workflow inputs #}
User name: {{ inputs.user_name }}
Account tier: {{ inputs.account_tier }}
{# In the send_email block — read from the enrich_user block #}
Full name: {{ enrich_user.full_name }}
Plan label: {{ enrich_user.plan_label }}
Each block pulls only the fields it needs. Map references explicitly rather than passing the whole state object around — when something breaks, you want to see exactly which value a block depended on.
Reference the specific fields a block needs instead of handing it the entire upstream output. Explicit mappings make failures obvious in the logs.

Conditional Logic

Use Jinja if/elif/else blocks to branch on values in your state. This is useful for routing and for shaping dynamic content. Route by account tier:
{% if inputs.account_tier == "enterprise" %}
  Priority support queue — SLA: 4 hours
{% elif inputs.account_tier == "pro" %}
  Standard support queue — SLA: 24 hours
{% else %}
  Community support — check our docs first
{% endif %}
Build a dynamic prompt that adapts to the input:
You are a support assistant.
{% if inputs.has_prior_conversation %}
The user has contacted us before. Be warm and reference their history.
{% else %}
This is a new user. Introduce yourself briefly.
{% endif %}
User message: {{ inputs.message }}

Global Variables

Scout exposes built-in date and time variables you can use in any template without configuration. They’re handy for timestamps, logging, and report headers.
VariableExample output
{{ __exp_global.current_date }}2025-06-15
{{ __exp_global.current_time }}14:23:01
{{ __exp_global.current_datetime }}2025-06-15 14:23:01
{{ __exp_global.current_time_utc }}21:23:01 UTC
{{ __exp_global.current_time_pacific }}14:23:01 PDT
For example:
[{{ __exp_global.current_datetime }}] User {{ inputs.user_id }} submitted request
Report generated on {{ __exp_global.current_date }}

State Design Tips

A few habits keep workflow state clean as it grows:
  • Keep payloads small. Pass only the fields a block actually needs, not the whole state object.
  • Use stable key names. Renaming an output field silently breaks every downstream reference to it.
  • Normalize once. Clean and format data in one early block, then reuse the normalized values everywhere downstream.
  • Prefer structured output over string building. Return structured data like { "user_id": "abc", "status": "active" } rather than a formatted string — it’s far easier to branch on later.

Error and Fallback Paths

Don’t assume the happy path. Add branch logic for the failures you can anticipate:
  • Missing or empty input fields
  • Empty search or lookup results
  • External API failures or timeouts
Return explicit status fields so downstream blocks can branch on them. A lookup block might return either of these shapes:
{ "ok": true, "data": { "user_id": "abc", "email": "user@example.com" } }
{ "ok": false, "error_code": "USER_NOT_FOUND", "message": "No user found for that ID" }
Then branch on the ok field:
{% if lookup_user.ok %}
  Found {{ lookup_user.data.email }} — continuing workflow
{% else %}
  Stopping: {{ lookup_user.error_code }}{{ lookup_user.message }}
{% endif %}
This keeps error handling explicit and auditable rather than buried inside block logic.

Next Steps

Blocks

Choose the right block type for each step in your workflow.

Creating Workflows

Assemble blocks with the validate-fetch-decide-act-return pattern.

Running Workflows

Execute your workflow and inspect block-level state in the Console.

Logs

Trace block-by-block execution and debug failures in production.