Context Resolution

Every workflow run maintains a context object. It starts with the input payload and grows as each step adds its output. The @path syntax lets any step reference data produced by earlier steps.


How Context Accumulates

flowchart TD subgraph "Context after input" I["@input.invoices<br/>@input.payments"] end subgraph "Context after Step 1 (Matcher)" M["+ @matched<br/>+ @unmatched_invoices<br/>+ @unmatched_payments"] end subgraph "Context after Step 2 (ReAct)" R["+ @investigation"] end subgraph "Context after Step 3 (Approval)" A["+ @__approved<br/>+ @__approval_data"] end I --> M --> R --> A

Each step can read anything from context that was set before it. Steps cannot read from steps that haven't executed yet.


@path Reference Table

Path Source Description
@input.* Workflow execution payload The JSON body sent to POST /workflows/:id/execute
@input.field.nested Nested input Dot notation traverses nested objects
@outputKey.* Step output References output by outputKey or default output name
@matched Matcher Array of matched record pairs
@unmatchedLeft Matcher Left-side records with no match (or custom name via outputUnmatchedLeft)
@unmatchedRight Matcher Right-side records with no match (or custom name via outputUnmatchedRight)
@item Foreach loop Current item being processed
@item.field Foreach loop Field on the current loop item
@__run_id System Current run identifier
@__step System Current step index (0-based)
@now System Current ISO timestamp
@__approved PbotApproval Boolean — whether the reviewer approved
@__approval_data PbotApproval Additional data from the reviewer's response

Naming Step Outputs

By default, matcher outputs use their configured output names (outputMatched, outputUnmatchedLeft, outputUnmatchedRight). Other steps store results under their outputKey or under a default name.

json
{
  "type": "loop",
  "properties": {
    "mode": "react",
    "objective": "Investigate exceptions",
    "tools": [{ "type": "action", "name": "lookup_record" }],
    "result_key": "investigation"
  }
}

The agent's final answer is stored at @investigation in context, accessible by all subsequent steps.

For registered actions, outputKey controls the context key:

json
{
  "type": "fetch_customer",
  "properties": { "customer_id": "@input.id" },
  "outputKey": "customer_data"
}

Result available at @customer_data for subsequent steps.


Nested Path Resolution

Dot notation traverses nested objects to any depth:

json
{
  "type": "send_notification",
  "properties": {
    "email": "@customer_data.contact.email",
    "name": "@customer_data.contact.first_name",
    "company": "@customer_data.organization.name"
  }
}

Array Indexing

Access specific array elements by index:

json
{
  "first_match_id": "@matched.0.invoice_id",
  "second_match_amount": "@matched.1.amount"
}

Access array length:

json
{
  "condition": {
    "greaterThan": [{ "length": "@unmatched_invoices" }, 0]
  }
}

Template Interpolation: {{ }}

Double braces perform string interpolation — they convert a value to a string and embed it in surrounding text. Use for building human-readable messages, email subjects, Slack posts, and log entries.

json
{
  "type": "gmail_send",
  "properties": {
    "subject": "Invoice {{input.invoice_id}} — {{input.vendor_name}}",
    "body": "Dear {{input.vendor_name}},\n\nYour invoice for ${{input.amount}} has been processed.\n\nMatched records: {{matched.length}}\nExceptions: {{unmatched_invoices.length}}"
  }
}

@path vs {{ }} — These serve different purposes. @path returns the raw value (object, array, number, boolean) and is used for data references in properties. {{template}} converts to string and embeds in text. Use @path when passing data. Use {{ }} when constructing messages.

json
{
  "data_reference": "@input.invoices",
  "message": "Processing {{input.invoices.length}} invoices"
}
---

Worked Example: 4-Step Workflow

json
{
  "name": "full_context_example",
  "definition": {
    "actions": [
      {
        "type": "matcher",
        "properties": {
          "left": "@input.invoices",
          "right": "@input.payments",
          "matchOn": ["invoice_id"],
          "tolerance": 0.02,
          "outputMatched": "reconciled",
          "outputUnmatchedLeft": "exceptions"
        }
      },
      {
        "type": "loop",
        "properties": {
          "mode": "foreach",
          "items_path": "@exceptions",
          "item_variable_name": "exception",
          "actions_to_execute": [
            {
              "type": "lookup_vendor",
              "properties": {
                "vendor_id": "@exception.vendor_id"
              }
            }
          ],
          "collect_results": true,
          "result_key": "vendor_lookups"
        }
      },
      {
        "type": "PbotApproval",
        "filter": {
          "condition": { "greaterThan": ["@exceptions.length", 5] }
        },
        "properties": {
          "comment": "{{exceptions.length}} exceptions found — review before proceeding",
          "request_payload": {
            "exception_count": "@exceptions.length",
            "vendor_details": "@vendor_lookups",
            "reconciled_count": "@reconciled.length"
          }
        }
      },
      {
        "type": "custom-table",
        "properties": {
          "table": "recon_log",
          "operation": "write",
          "keys": ["run_id"],
          "values": ["@__run_id"],
          "fields": {
            "matched": "@reconciled.length",
            "exceptions": "@exceptions.length",
            "approved": "@__approved",
            "timestamp": "@now"
          }
        }
      }
    ]
  }
}

Context at each step:

After Step Keys Added Available
Input @input.invoices, @input.payments input
Step 1 (Matcher) @reconciled, @exceptions input + matcher output
Step 2 (Loop) @vendor_lookups input + matcher + loop results
Step 3 (Approval) @__approved, @__approval_data input + matcher + loop + approval
Step 4 (Table) — (writes to storage) everything

→ Next: Conditional Logic