Reason on Data

The Reason stage takes the data collected in Sense and decides what to do with it. It runs four substages in sequence: analyze → plan → govern → authorize.

You do not need all four. A simple agent might only need analyze and plan. Add govern when you want safety rules, and authorize to declare explicit permission boundaries.

Where does Reason read Sense data from? Both Reason modes, deterministic rules and large language model (LLM) prompts, read from the same environmentalData.payload object that Sense writes to. The keying inside payload depends on the Sense substage type and on whether a window is configured. See Where sensed data lands in agent state. What Reason writes back is covered in Where Reason reads and writes in agent state at the end of this page.

Analyze

The analyze substage produces a structured assessment of what the Sense data means. Use it to classify conditions, detect anomalies, or evaluate thresholds.

Option 1: Deterministic (Rule-Based, No LLM)

Write conditions as JavaScript expressions. When a condition is true, the rule fires.

reason:
  substages:
    - type: analyze
      name: threshold-check
      config:
        rules:
          - condition: 'temperature.value > 45'
            type: temperature_critical_high
            severity: critical
            message: 'Temperature {{temperature.value}}°C critically exceeds limit of 35°C'

          - condition: 'temperature.value > 35 && temperature.value <= 45'
            type: temperature_high
            severity: high
            message: 'Temperature {{temperature.value}}°C exceeds normal limit of 35°C'

          - condition: 'humidity.value < 30'
            type: humidity_low
            severity: high
            message: 'Humidity {{humidity.value}}% below minimum of 30%'

Multiple rules can match in the same cycle. Use this mode for fixed thresholds. It has zero LLM cost and minimal latency.

Option 2: LLM-Based (AI Reasoning)

Provide a prompt and define the expected output shape. The LLM analyzes the data and returns structured results.

reason:
  substages:
    - type: analyze
      name: quality-assessment
      prompt: |
        You are an industrial quality monitoring agent.
        Analyze the following quality metrics and identify any anomalies.

        Current readings:
        {{environmentalData | json}}

        Historical patterns:
        {{memory.patterns | json}}

        Identify anomalies, classify severity (low/medium/high/critical),
        and assess overall production health.
      outputSchema:
        type: object
        properties:
          anomalies:
            type: array
            items:
              type: object
              properties:
                metricName:
                  type: string
                severity:
                  type: string
                  enum: [low, medium, high, critical]
                description:
                  type: string
          overallHealth:
            type: string
            enum: [healthy, degraded, at-risk, critical]
          confidence:
            type: number
            minimum: 0
            maximum: 1

The LLM output is validated against outputSchema. If validation fails, the analyze result is marked as failed and logged. Results are available in the Plan substage as derivedData.analysis.

Plan

The plan substage decides which actions to execute based on the analysis. Like analyze, it supports both modes.

Deterministic Plan

Write conditions to select actions. Only actions whose condition evaluates to true are passed to Actuate.

reason:
  substages:
    - type: plan
      name: response-planner
      config:
        objective: 'Alert on threshold violations'
        actions:
          - action: critical-temperature-alert
            type: mqtt_publish
            target: factory/alerts/temperature
            condition: 'temperature.value > 45 || temperature.value < 5'
            priority: 1
            params:
              severity: critical
              message: 'CRITICAL: Temperature {{temperature.value}}°C outside safe range'

          - action: temperature-warning
            type: mqtt_publish
            target: factory/alerts/temperature
            condition: 'temperature.value > 35 && temperature.value <= 45'
            priority: 2
            params:
              severity: warning
              message: 'WARNING: Temperature {{temperature.value}}°C above normal range'

Actions are sorted by priority (lower number = higher priority) and passed to Actuate.

LLM-Based Plan

reason:
  substages:
    - type: plan
      name: response-planner
      prompt: |
        Based on the quality analysis, create a response plan.

        Analysis:
        {{derivedData.analysis | json}}

        Available actions:
        - publish-alert: Send alert to MQTT (use for notifications)
        - stop-line: Emergency production stop (critical quality failures only)
        - log-observation: Record for trending (low severity)

        Guidelines:
        - critical severity → stop-line + publish-alert
        - high severity → publish-alert
        - medium/low → log-observation
      outputSchema:
        type: object
        properties:
          actions:
            type: array
            items:
              type: object
              properties:
                action:
                  type: string
                  enum: [publish-alert, stop-line, log-observation]
                target:
                  type: string
                params:
                  type: object
                confidence:
                  type: number

Govern

The govern substage applies safety rules to the plan. If any rule rejects the plan, the Actuate stage is skipped entirely for that cycle.

Use govern to prevent the agent from taking too many actions in a short time, or to block specific actions under certain conditions.

reason:
  substages:
    - type: govern
      name: safety-governance
      rules:
        - id: max-actions-per-cycle
          condition: 'plan.actions.length <= 5'
          action: approve
          message: 'Action count within limits'

        - id: too-many-actions
          condition: 'plan.actions.length > 5'
          action: reject
          message: 'Too many actions planned ({{plan.actions.length}}). Max is 5 per cycle.'

        - id: alert-rate-limit
          condition: 'memory.recentActions.length < 50'
          action: approve
          message: 'Alert rate within acceptable limits'

        - id: rate-exceeded
          condition: 'memory.recentActions.length >= 50'
          action: reject
          message: 'Alert rate limit exceeded'

Rules are evaluated in order. The first reject that matches stops the cycle. If no reject fires, the plan is approved.

Field Required Description

id

Yes

Unique name for the rule, shown in logs

condition

Yes

JavaScript expression evaluated against agent state

action

Yes

approve or reject

message

No

Log message when the rule fires

Authorize

The authorize substage declares which resources the agent is allowed to access. Think of it as an allowlist that the runtime checks before Actuate runs.

reason:
  substages:
    - type: authorize
      name: permission-check
      type: static
      permissions:
        - mqtt:publish:factory/quality/alerts
        - mqtt:publish:factory/quality/observations
        - mqtt:publish:factory/line-control/stop
        - email:send:alert-email
        - database:write:quality-db

Permission format: <protocol>:<operation>:<resource>. Wildcards (*) are supported at the end of the resource path.

The type field on authorize substages must always be set to static. Omitting it causes the substage to be silently skipped.

Complete Deterministic Example

This is a complete Reason stage with all four substages, no LLM required:

reason:
  substages:
    - type: analyze
      name: threshold-check
      config:
        rules:
          - condition: "temperature.value > 45"
            type: temperature_critical_high
            severity: critical
            message: "Temperature {{temperature.value}}°C critically exceeds maximum of 35°C"
          - condition: "temperature.value > 35 && temperature.value <= 45"
            type: temperature_high
            severity: high
            message: "Temperature {{temperature.value}}°C exceeds maximum of 35°C"
          - condition: "humidity.value > 70"
            type: humidity_high
            severity: high
            message: "Humidity {{humidity.value}}% exceeds maximum of 70%"

    - type: plan
      name: response-planner
      config:
        objective: "Alert on threshold violations for temperature and humidity"
        actions:
          - action: critical-temperature-alert
            type: mqtt_publish
            target: factory/alerts/temperature
            condition: "temperature.value > 45 || temperature.value < 5"
            priority: 1
            params:
              severity: critical
              message: "CRITICAL: Temperature {{temperature.value}}°C outside safe range"

          - action: temperature-warning
            type: mqtt_publish
            target: factory/alerts/temperature
            condition: "temperature.value > 35 && temperature.value <= 45"
            priority: 2
            params:
              severity: warning
              message: "WARNING: Temperature {{temperature.value}}°C above normal range"

    - type: govern
      name: rate-limiter
      rules:
        - id: alert-rate-limit
          condition: "memory.recentActions.length < 10"
          action: approve
        - id: rate-exceeded
          condition: "memory.recentActions.length >= 10"
          action: reject
          message: "Alert rate limit exceeded — max 10 per window"

    - type: authorize
      name: permission-check
      type: static
      permissions:
        - mqtt:publish:factory/alerts/*
        - email:send:alert-email

Template Variables in Prompts

Use {{variable}} syntax in prompts and rule messages:

Template What it contains

{{environmentalData | json}}

All sense data for this cycle (the payload object) as JSON

{{derivedData.analysis | json}}

Output from the analyze substage

{{memory.patterns | json}}

Agent memory (patterns, recent actions)

{{memory.feedbackHistory | json}}

History of human feedback responses

{{temperature.value}}

Individual sensor reading from a simulator

Always use \| json for objects and arrays in LLM prompts. Without it, you get [object Object] instead of JSON.

Where Reason Reads and Writes in Agent State

Reason reads the data Sense collected from environmentalData.payload (see Where sensed data lands in agent state). As each substage runs, it writes its result into a dedicated slot on derivedData. These slots are how the substages pass results forward within the same cycle. Later substages, the Actuate stage, and the Reflect stage all read from them.

Substage Output slot Shape

analyze

derivedData.analysis

{ summary, insights[], anomalies[], confidence }

plan

derivedData.plan

{ objective, actions[], reasoning }

govern

derivedData.governance

{ approved, violations[], appliedPolicies[] }

authorize

derivedData.authorization

{ authorized, deniedActions[], permissions[] }

The Actuate stage reads derivedData.plan to know what to do. The Reflect stage reads derivedData.analysis and the evaluation results to know what happened. Reference any of these slots in a later LLM prompt with {{derivedData.analysis | json}}.

derivedData is reset to empty at the start of every cycle. If you need a result from this cycle to influence the next one, route it through memory in the Reflect stage. derivedData itself does not survive a cycle boundary.