The Planning Loop

At every solar milestone and band transition, Iris (our OpenClaw AI agent) runs as an OpenClaw agent over a structured greenhouse context window. Routine checks and smaller deviations route through Gemma 4 26B A4B (MoE), served locally under the gemma4-26b alias; larger milestone reviews and major deviations can route to a heavyweight cloud peer. Iris does not choose what the greenhouse should target. The crop target band handles that, computed from the diurnal profiles of the currently active control crops with smooth hour-by-hour interpolation. As of the May 8 launch check, Verdify has four active-control crop profiles; a fifth active DB crop record, the center-zone Vanda orchid, is treated as observed/reference while the center zone remains offline. The planner chooses how aggressively the controller should chase those targets given forecast, memory, equipment state, and known physical limits. When the plan lands, Slack Operations is the human-readable layer: plan ID, prior scorecard, forecast, tactical posture, experiment, and watch items.

Each plan is a hypothesis. The system measures what happened, scores the result, and extracts lessons when predictions diverge from reality.

Control split: crop profiles define target bands, Iris chooses bounded tactics, MCP and the dispatcher validate writes, and ESP32 firmware decides relay state every 5 seconds.

The Closed Loop

Gather

Assemble context from sensors, forecast, crop bands, equipment, lessons, scorecards, prior plans, and static greenhouse documentation.

Validate

Score the previous plan and extract a lesson when reality diverged from the hypothesis.

Route

Send routine or minor events to Gemma 4 26B A4B (MoE), served locally under the gemma4-26b alias; reserve heavyweight cloud reasoning for major reviews and deviations.

Reason

Balance temperature, VPD, water, fog, heat, light, cost, lessons, and retrieved context against the next 72 hours.

Plan

Write tactical waypoints for target tightening, hysteresis, misting, fog, fan staging, and water budget.

Dispatch

Every 5 minutes, push changed setpoints to the ESP32; firmware enforces physical state every 5 seconds.

Brief

Post the operator-facing Slack summary: what changed, why it changed, and what a human should watch.

Learn

Logged outcomes become scorecard data and validated lessons for the next planning cycle.

Left: plan outcome scores over 30 days — each dot is a cycle's self-assessment. Right: recent plan history with hypotheses and outcomes.

Step 0: Validate the Previous Plan

Before writing anything new, the planner reviews what happened since its last plan. It reads the plan_journal entry — hypothesis, experiment, expected outcome — then checks actual data: stress hours, zone temperatures, VPD compliance, equipment runtimes, water usage. It scores the plan 1–10. If something unexpected happened, the planner extracts a lesson. Lessons accumulate in the planner_lessons table with confidence that increases each time the pattern is re-confirmed. The claim is not that the AI rewrites itself; it is that future plans can read validated outcomes from earlier failures.

Step 1: Gather Context

The planner assembles structured context from the database:

Current State

Zone temperatures, VPD, humidity, CO2, lux, outdoor weather, active ESP32 mode, relays, and tunables.

Recent Pattern

Yesterday's hourly temperature, VPD, RH, outdoor conditions, and today's heat, cold, and VPD stress hours.

Forward View

72-hour forecast, solar radiation, cloud cover, wind, and crop target bands for each hour.

Biology

Active crops, zones, growth stages, VPD targets, health scores, disease risk, and DIF.

Memory

Validated lessons, previous waypoints, plan journal hypotheses, and scorecard outcomes.

Static Context

Website pages describing the greenhouse structure, equipment, zones, crops, known limits, and build notes.

The crop target band is the key addition. The planner sees exactly what the ESP32 will be targeting at each hour. It can then plan tactics around those targets: pre-cool before a solar peak, increase misting before a dry afternoon, widen hysteresis when the band is narrow and physics makes it hard to hold.

The local-memory story matters. Iris is not reasoning from a blank prompt. The context bundle pulls current telemetry and prior outcomes, the static-context builder makes the website’s greenhouse documentation available to the agent, and the lessons table gives durable findings a structured place to live. OpenClaw now uses trigger-scoped planner sessions so the local gemma4-26b route gets bounded context while memory remains database-backed and auditable. Semantic similarity is already used for observation history where embeddings exist; plan history and scorecards are exposed today as structured memory so the planner can compare the next decision against what actually happened last time.

Step 1b: Route the Planner Instance

Not every planning event needs the same model. Verdify uses a dual-Iris policy:

Local IrisGemma 4 26B A4B (MoE), served locally under the gemma4-26b alias

Routine heartbeats, transitions, and minor forecast/deviation checks. Cheap, local, frequent, and bounded. See Local Inference Setup.

Cloud peerHeavyweight review

Sunrise, sunset, midnight, and major forecast or deviation events that need deeper reasoning over the full context. This route is explicit and audit-stamped, not a hidden fallback.

Shared contractSame tools and audit trail

Both instances use OpenClaw, MCP tools, trigger IDs, planner-instance stamps, setpoint validation, scorecards, and the same ESP32 safety boundary.

Step 2: Reason Across All Systems

The planner doesn’t optimize one variable. It reasons across seven interconnected systems simultaneously:

  • Temperature — The south zone runs 9°F hotter than east at peak. temp_high applies to the average, so south is already in COOL_S2 when the average hits threshold.
  • VPD — On a 14% RH day, the system fights VPD stress for 8+ hours. Lowering vpd_high means misters engage earlier — more water, more humidity, less plant stress.
  • Misting — 60-second pulses with 45-second gaps is the tuned sweet spot. Higher VPD weight concentrates water on the driest zone.
  • Economiser — When outdoor enthalpy is lower than indoor, the vent opens for free cooling. The planner understands enthalpy dynamics when planning ventilation strategy.
  • Grow lights — Two circuits (816W + 630W) supplement natural DLI. The planner adjusts the lux threshold based on the DLI forecast.
  • Irrigation — Misting serves dual purpose: humidity control AND irrigation via foliar absorption.
  • Cost — Electric heat costs 3.9× more per BTU than gas. Running fog during solar production can be cheap, but it still appears in the resource accounting.

Step 3: Write the Plan

The planner writes two categories of parameters: Target tightening (optional, clamped to crop band): The crop band sets the outer envelope (e.g., temp 72-78F at midday). The planner can tighten within the band on mild days. If it sets temp_high=75, the ESP32 targets 75 instead of 78. On extreme days, it leaves these alone and the band edges are used. Values are always clamped to the band. The planner cannot exceed crop science limits. Tactical parameters are the planner’s main output:

Band Chasing

vpd_hysteresis, temp_hysteresis, and optional target tightening inside crop limits.

Cooling

d_cool_stage_2, fan cycling limits, and enthalpy-based economiser enablement.

Misting

mister_engage_kpa, pulse duration, pulse gap, and daily water budget.

Fog

fog_burst_min and escalation timing when misters cannot control VPD.

Heat

Minimum heat on/off times and staged electric/gas heat behavior.

A typical plan has 8-16 waypoints at natural breakpoints: pre-dawn, dawn, morning ramp, solar noon, afternoon peak, decline, evening, night. Each new plan atomically replaces all future waypoints from previous plans.

The Slack brief is not a separate source of truth. It is the readable operator version of the same plan_journal and setpoint_plan records: plan ID, hypothesis, forecast context, expected risks, and watch items. See Slack Operations for screenshots of a successful SUNRISE plan, daily operator tasks, and a forecast-deviation adjustment.

AI-Writable Tunables

Iris can write only registry-approved tunables. set_plan is stricter than set_tunable: routine sunrise/sunset plans must include the 24 tactical parameters below at each waypoint and may not include crop-band parameters. set_tunable can make a one-shot adjustment to any planner-pushable registry entry; band-owned values are still clamped or overwritten by the band dispatcher.

Write path: Iris calls MCP set_plan or set_tunable; MCP validates names and registry bounds, stamps trigger audit data, and writes setpoint_plan. v_active_plan resolves the active value per parameter. The dispatcher pushes changed values to ESPHome number/switch entities and logs actual ESP32 reports in setpoint_changes; /setpoints is the 5-minute HTTP fallback. setpoint_snapshot stores cfg_* readbacks from firmware so pushed intent can be checked against actual configured state.

Tunable lanePlanner can doFirmware still owns
Routine plan contractWrite 24 tactical parameters at each waypoint.Relay timing, mode priority, hard safety rails, and dwell enforcement.
One-shot knobsMake bounded tactical adjustments through set_tunable.Clamp behavior, readback truth, and physical actuator state.
Band-owned valuesSee and occasionally request clamped overrides.Crop-band recomputation and controller enforcement.
Non-writable safety railsRead for context only.Emergency heat/cool/VPD safety behavior.

Routine Plan Contract

These 24 parameters are required in every multi-waypoint plan. Bounds are registry and firmware clamps.

ParameterDefaultBoundsRelay/state-machine impact
vpd_hysteresis0.30 kPa0.05-0.50Sets seal/dehumidify exit deadband; larger values reduce SEALED_MIST churn.
vpd_watch_dwell_s60 s15-120Delay before high VPD can enter SEALED_MIST.
mister_engage_kpa1.6 kPa0.5-2.5Global VPD threshold for first mister-zone demand.
mister_all_kpa1.9 kPa1.0-2.5Escalation threshold for all-zone rotation.
mister_pulse_on_s60 s30-90Mister valve on-time per pulse.
mister_pulse_gap_s45 s10-60Evaporation gap between mister pulses.
mister_vpd_weight1.50.5-5.0Extends pulse duration for the most VPD-stressed zone.
mister_water_budget_gal500 gal100-600Daily mister budget; blocks misters unless VPD safety override fires.
mist_vent_close_lead_s15 s0-60Lead time before closed-vent misting.
mist_max_closed_vent_s600 s120-900SEALED_MIST max duration before THERMAL_RELIEF.
mist_vent_reopen_delay_s45 s0-120Holds vent closed after misting so evaporation can settle.
mist_thermal_relief_s90 s30-300Mandatory vent-open relief duration after sealed misting.
enthalpy_open-2.0 kJ/kg-5.0-0.0Economiser unblocks venting when outdoor air is sufficiently lower enthalpy.
enthalpy_close1.0 kJ/kg-5.0-20.0Economiser blocks venting when outdoor air is too costly/wet/hot.
min_vent_on_s60 s10-300Minimum vent relay on dwell.
min_vent_off_s60 s10-300Minimum vent relay off dwell.
min_fog_on_s60 s15-300Minimum fog relay on dwell.
min_fog_off_s60 s15-300Minimum fog relay off dwell.
fog_escalation_kpa0.4 kPa0.1-1.0VPD margin above high band that escalates from misters to fog.
d_cool_stage_23.0F2.0-15.0Extra degrees above cooling target before both fans run.
bias_heat0.0F-10.0-10.0Shifts heating target; positive values preheat and reduce cold stress.
bias_cool0.0F-10.0-10.0Shifts cooling target; positive values delay ventilation.
min_heat_on_s120 s30-300Minimum heat relay on dwell.
min_heat_off_s180 s60-600Minimum heat relay off dwell; protects against gas-valve rapid cycling.

Other Tier-1 One-Shot Knobs

These are planner-pushable through set_tunable, but are not required in every routine plan.

ParameterDefaultBoundsRelay/state-machine impact
d_heat_stage_25.0F2.0-15.0Temperature drop below heating target before gas heat stage 2 latches.
temp_hysteresis1.5F0.5-3.0Temperature mode deadband for VENTILATE/IDLE transitions.
heat_hysteresis1.0F0.0-3.0Heat-stage clear margin; higher values hold heat longer.
mister_engage_delay_s45 s30-900Delay before first mister pulse during moisture demand.
mister_all_delay_s300 s60-900Delay before all-zone mister escalation.
sw_summer_vent_enabled10/1Enables the outdoor-cooler-and-drier gate that preempts SEALED_MIST in favor of VENTILATE.
vent_prefer_temp_delta_f5.0F2.0-15.0Outdoor air must be this much cooler before summer vent preemption.
vent_prefer_dp_delta_f5.0F2.0-15.0Outdoor dew point must be this much lower before summer vent preemption.
outdoor_staleness_max_s600 s120-1800Disables summer vent gate when outdoor data is stale.
summer_vent_min_runtime_s180 s60-600Minimum runtime for summer vent gate.
sw_fog_closes_vent10/1Suppresses fog while the vent is physically open, except v2 vent-assist paths.
sw_mister_closes_vent00/1Suppresses misters while the vent is physically open.
sw_dwell_gate_enabled00/1Enables the non-safety mode dwell gate.
dwell_gate_ms300000 ms60000-1800000Minimum non-safety mode hold time; safety and VPD rescue still preempt.
sw_fsm_controller_enabled10/1Controller v2 band-first state machine. Locked ON by dispatcher/MCP guardrails; legacy fallback requires operator/firmware rollback.
mist_backoff_s600 s60-3600Lockout after sealed humidification times out.

Band-Owned Values

These are visible to Iris and technically planner-pushable as one-shot overrides, but routine plans do not own them. /setpoints recomputes temp_low, temp_high, vpd_low, and vpd_high from crop profiles and clamps any one-shot value inside the active crop band. Per-zone VPD targets are also dispatcher-owned from crop data.

ParameterDefaultBoundsOwner and impact
temp_low40.0F30.0-80.0Crop-band low edge; firmware targets the interior of this band for heat decisions.
temp_high95.0F40.0-100.0Crop-band high edge; cooling and safety-adjacent seal gates reference it.
vpd_low0.35 kPa0.1-1.0Low-VPD band edge; below it firmware enters DEHUM_VENT when economiser allows.
vpd_high2.8 kPa0.4-3.0High-VPD band edge; above it firmware starts the VPD watch and may seal/mist.
vpd_target_south1.3 kPa0.3-3.0South-zone mister scoring target.
vpd_target_west1.2 kPa0.3-3.0West-zone mister scoring target.
vpd_target_east1.0 kPa0.3-3.0East-zone stress proxy; boosts adjacent misting because east has no mister.
vpd_target_center0.8 kPa0.1-3.0Center/average VPD target for center mister scoring.

Tier-2 Escape Hatch

Iris can set these through set_tunable, but normal planning rarely should. They are mostly operator posture, irrigation, lighting, diagnostic, or older duty-cycle controls.

GroupParametersDefaults and boundsImpact
Mister scoringmister_center_penalty, east_adjacency_factor0.5 and 0.3; both 0.0-1.0Reweights which zone gets the next mister pulse.
Legacy mister dutymister_on_s, mister_off_s, mister_all_on_s, mister_all_off_s, mister_max_runtime_min300 s 60-600; 600 s 120-900; 480 s 120-600; 420 s 120-600; 120 min 5-480Legacy duty-cycle and runtime caps; current control primarily uses pulse rotation.
Fog safety windowfog_rh_ceiling_pct, fog_min_temp_f, fog_time_window_start, fog_time_window_end90% 75-98; 55F 40-65; 6 h 5-12; 18 h 14-20Blocks fog when too humid, too cold, or outside the allowed local-hour window.
Relief and dehummax_relief_cycles, dehum_aggressive_kpa, vent_latch_timeout_ms, safety_max_seal_margin_f3 cycles 1-10; 0.6 kPa 0.05-1.0; 1800000 ms 60000-7200000; 5F 1-15Controls relief-cycle breaker, aggressive dehumidifying, forced-vent retry timeout, and hot seal blocking.
Relay dwell extrasmin_fan_on_s, min_fan_off_s, lead_rotate_s120 s 30-300; 90 s 30-300; 600 s 60-1800Fan relay dwell and lead/lag rotation.
Manual burst durationsfan_burst_min, vent_bypass_min, fog_burst_min10 min each; 1-60Durations used by manual HA burst/bypass buttons.
Economiser postureecon_heat_margin_f, sw_economiser_enabled5F 1-15; switch default 1Blocks or enables enthalpy-based venting, especially when heat demand exists.
Irrigation scheduleirrig_wall_start_hour, irrig_wall_start_min, irrig_wall_duration_min, irrig_wall_fert_duration_min, irrig_wall_fert_every_n, irrig_wall_flush_min, irrig_wall_interval_days, irrig_center_start_hour, irrig_center_start_min, irrig_center_duration_min, irrig_center_fert_duration_min, irrig_center_fert_every_n, irrig_center_flush_min, irrig_center_interval_daysWall 06:00, 10 min, fert 5 min every 0, flush 2 min, interval 1 day. Center 06:30 with same durations. Hours 0-23, minutes 0-59, durations 1-120 or 0-60, every-n 0-30, flush 0-30, interval 1-14.Schedules drip and fertigation state machines; not climate relay control.
Irrigation VPD boostirrig_vpd_boost_pct, irrig_vpd_boost_threshold_hrs25% 0-200; 3 h 0-24Extends irrigation after sustained high-VPD stress.
Grow lightsgl_dli_target, gl_lux_threshold, gl_lux_hysteresis, gl_sunrise_hour, gl_sunset_hour, sw_gl_auto_mode14 mol 1-50; 3000 lx 100-50000; 1500 lx 0-10000; 7 h 0-23; 19 h 0-23; switch default 1Controls supplemental light eligibility and cycling.
Irrigation switchessw_irrigation_enabled, sw_irrigation_wall_enabled, sw_irrigation_center_enabled, sw_irrigation_weather_skipswitches default 1Enables irrigation branches and rain-skip behavior.
Occupancy switchsw_occupancy_inhibitswitch default 1Blocks mist/fog while the greenhouse is occupied.

Not AI-Writable

Safety rails and site constants are registry entries but MCP rejects planner writes to them: safety_min, safety_max, safety_vpd_min, safety_vpd_max, and site_pressure_hpa. Firmware still consumes the safety rails directly: safety_min forces SAFETY_HEAT, safety_max forces SAFETY_COOL, and the VPD rails trigger dry/humid safety rescue paths.

Step 4: Journal the Hypothesis

Every plan writes a structured journal entry: what the world looks like, what the planner thinks will happen, one specific experiment being tested, and a measurable expected outcome. The next cycle validates the hypothesis and scores the result. This isn’t logging for debugging. It’s the experimental protocol that turns each cycle into a learning opportunity.

Step 5: Dispatch to the ESP32

The dispatcher runs every 5 minutes inside the ingestor service. It reads the active plan, compares each value to what the ESP32 currently reports, and pushes only changed values via aioesphomeapi (encrypted, immediate). The ESP32 also pulls /setpoints via HTTP every 5 minutes as a fallback. Between dispatches, the ESP32 runs completely autonomously. If the AI layer goes offline, the controller keeps the last valid setpoints and hard safety rails.

The Learning System

Left: forecast temperature bias over 30 days. Right: setpoint write rate and oscillation count. Confidence levels:
  • Low: First observation. Might be coincidence.
  • Medium: Confirmed 2+ times under similar conditions.
  • High: Validated 5+ times. Mandatory unless conditions clearly differ. The planner must check every active lesson before finalizing a plan. See Lessons Learned for the full list.

Planning Goals (Priority Order)

  1. Minimize VPD stress hours — plant transpiration balance
  2. Minimize heat stress hours — physics-limited by cooling capacity
  3. Maximize DLI — daily light integral for growth
  4. Minimize water usage — misting water isn’t free
  5. Minimize energy cost — USD 0.111/kWh, USD 0.83/therm, USD 0.00484/gal
  6. Maintain positive DIF — 8–15°F day warmer than night
  7. Maximize compliance % — time within setpoint bands These are ordered. The planner will spend water and energy to reduce VPD stress. It won’t sacrifice plant health for a lower utility bill.

Control Loop Performance

Temperature (left) and VPD (right) vs setpoint bands. Where the line tracks the plan, the system is in control. Where it diverges — typically on hot, dry spring afternoons — the greenhouse has hit a physics limit. The planner knows this. It optimizes for minimal stress, not zero stress. ---

Full dashboards: Controller ↗ · Planning ↗