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 |
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 |
|---|---|---|
|
Yes |
Unique name for the rule, shown in logs |
|
Yes |
JavaScript expression evaluated against agent state |
|
Yes |
|
|
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 |
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 |
|---|---|
|
All sense data for this cycle (the |
|
Output from the analyze substage |
|
Agent memory (patterns, recent actions) |
|
History of human feedback responses |
|
Individual sensor reading from a simulator |
|
Always use |
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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
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.