The Planning Loop
At every solar milestone and band transition, Iris, the AI planner receives a structured greenhouse context window. Routine checks and major reviews use the same audited MCP tool contract, trigger IDs, planner-instance stamps, registry validation, scorecards, and ESP32 safety boundary. Iris does not choose what the greenhouse should target. The crop target band handles plant policy, computed from the diurnal profiles of the currently active control crops with smooth hour-by-hour interpolation. The dispatcher then derives the firmware-enforced whole-house band, including a VPD band that can differ from the strict crop envelope because the ESP32 controls one air mass while zone mister targets remain local. Verdify currently has five active crop profiles, including the center-zone Vanda orchids; the center zone uses derived climate context rather than a dedicated probe. 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, the dispatcher derives the firmware-enforced band, Iris chooses bounded tactics, MCP and the dispatcher validate writes, and ESP32 firmware decides relay state every 5 seconds.
The Closed Loop
Assemble context from sensors, forecast, crop bands, equipment, lessons, scorecards, prior plans, and static greenhouse documentation.
Score the previous plan and extract a lesson when reality diverged from the hypothesis.
Deliver each trigger with the same MCP allowlist, audit banner, and trigger-scoped context.
Balance temperature, VPD, water, fog, heat, light, cost, lessons, and retrieved context against the next 72 hours.
Write tactical waypoints for target tightening, hysteresis, misting, fog, fan staging, and water budget.
Every 5 minutes, push changed setpoints to the ESP32; firmware enforces physical state every 5 seconds.
Post the operator-facing Slack summary: what changed, why it changed, and what a human should watch.
Logged outcomes become scorecard data and validated lessons for the next planning cycle.
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:
Zone temperatures, VPD, humidity, CO2, lux, outdoor weather, active ESP32 mode, relays, and tunables.
Yesterday's hourly temperature, VPD, RH, outdoor conditions, and today's heat, cold, and VPD stress hours.
72-hour forecast, solar radiation, cloud cover, wind, actual-to-forecast firmware compliance bands, and source-trace tables for crop/dispatcher/readback provenance.
Active crops, zones, growth stages, VPD targets, health scores, disease risk, and DIF.
Validated lessons, previous waypoints, plan journal hypotheses, and scorecard outcomes.
Website pages describing the greenhouse structure, equipment, zones, crops, known limits, and build notes.
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. Planner runs are trigger-scoped so context stays bounded while memory remains database-backed and auditable. Semantic similarity search covers lessons, historical plans, site docs, playbooks, and observations, 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:
Routine heartbeats, transitions, milestones, and forecast/deviation checks all receive explicit context and an audit banner.
The routing policy can choose a smaller or heavier model, but the tool surface and audit requirements stay the same.
All routes use 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 per-circuit runtime, lux threshold, hysteresis, and dwell knobs.
- Irrigation and wetting — Climate misters, scheduled drip/fert paths, and flush paths all respect the same direct-wet activity gate, but demand still comes from the climate loop or the irrigation scheduler.
- 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:
vpd_hysteresis, temp_hysteresis, and optional target tightening inside crop limits.
d_cool_stage_2, fan cycling limits, and enthalpy-based economiser enablement.
mister_engage_kpa, pulse duration, pulse gap, and daily water budget.
fog_burst_min and escalation timing when misters cannot control VPD.
Minimum heat on/off times and staged electric/gas heat behavior.
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
The canonical tunable reference is now generated from the registry, MCP contracts, ingestor maps, firmware source, and live database evidence:
Iris can write only registry-approved tunables. set_plan is stricter than
set_tunable: routine sunrise/sunset plans must include the 37 tactical Tier 1
parameters 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 final writes in
setpoint_changes, including trigger_id and planner_instance.
setpoint_snapshot stores cfg_* readbacks from firmware so pushed intent can
be checked against actual configured state.
| Tunable lane | Planner can do | Firmware still owns |
|---|---|---|
| Routine plan contract | Write 37 tactical Tier 1 parameters at each waypoint. | Relay timing, mode priority, hard safety rails, dwell enforcement, and physical interlocks. |
| One-shot knobs | Make bounded tactical adjustments through set_tunable. | Clamp behavior, readback truth, and physical actuator state. |
| Band-owned values | Read for context; request explicit one-shot overrides only when justified. | Crop-band recomputation and controller enforcement. |
| Non-writable safety/readback values | Read for context only. | Emergency heat/cool/VPD safety behavior and sensor freshness. |
The important implementation nuance is mister_engage_kpa: it is effectful,
but it is not the firmware state-machine entry trigger. Firmware enters
humidification from vpd_high plus vpd_watch_dwell_s; mister_engage_kpa
gates physical S1 mister pulses once humidity or zone demand exists, and zone
stress can bypass the global threshold.
Activity And Direct-Wet Gates
Lighting and direct plant wetting now share one schedule concept: global biological activity. The dispatcher mirrors the main lighting circuit’s start hour and qualified-light-minutes target into activity_start_hour, activity_start_minute, and activity_duration_min. Firmware then calculates per-zone wet windows with tunable offsets and drydown holds.
The default direct-wet policy is zone-generic:
| Path | Start offset | Drydown hold | Relays covered |
|---|---|---|---|
| Wall drip | 60 min | 120 min | wall clean/fert drip and flush |
| South misters | 60 min | 120 min | south clean/fert misters |
| West misters | 60 min | 120 min | west clean/fert misters |
| Center wetting | 120 min | 180 min | center mister and center clean/fert drip |
direct_wet_min_temp_f defaults to 65°F and blocks automated direct wetting when the house is too cold. sw_direct_wet_gate_enabled can disable the gate, but normal operation leaves it on.
Fertigation is scheduler-style. irrig_wall_days_mask and irrig_center_days_mask choose watering days. irrig_wall_fert_days_mask and irrig_center_fert_days_mask choose fertigation days; when a fert mask is nonzero, it supersedes the older every-N-cycle fert setting. Fert jobs open the fert master, run the zone fert path, then flush with clean water.
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). cfg_* readbacks in setpoint_snapshot confirm what actually landed on the controller.
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 /setpoints endpoints remain aligned for diagnostics and recovery tooling, but the current production firmware does not run an HTTP setpoint poller.
The Learning System
- 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)
- Minimize VPD stress hours — plant transpiration balance
- Minimize heat stress hours — physics-limited by cooling capacity
- Maximize DLI — daily light integral for growth
- Minimize water usage — misting water isn’t free
- Minimize energy cost — USD 0.111/kWh, USD 0.83/therm, USD 0.00484/gal
- Maintain positive DIF — 8–15°F day warmer than night
- 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
Full dashboards: Controller ↗ · Planning ↗