Inspiration
Every harvest season, Australian broadacre farmers make dozens of high-stakes calls with nothing but experience and a weather app: which field to cut first, whether to call in a contractor before the rain hits, whether it's worth risking one more hour of work on a field that's getting boggy. Existing farm software — telemetry platforms, weather dashboards, job trackers — shows all the data but computes none of the response. It's a pilot with instruments and no autopilot.
We wanted to build the autopilot: something that doesn't just alert a manager that a plan is suboptimal, but actually recomputes the best one — and explains its reasoning in plain English, the way a good agronomist would.
What it does
FarmOpti models a farm as a scheduling and optimization problem. Given fields, machines, weather forecasts, labour, and water limits, it computes the schedule that maximizes total financial value — not a heuristic, an actual solved optimization. When conditions change (a rain front moves forward, a machine breaks down, a manager adds a new constraint), it re-optimizes and shows exactly why the new plan is better. A conversational layer lets managers add hard rules in plain English — "never spray field F3 within 24 hours of heavy rain" — that the optimizer will never violate.
How we built it
The system has three layers:
The optimization core. A four-stage TypeScript pipeline: candidate generation (every individually feasible timing for every operation), field-state simulation (irrigation changes moisture, spraying changes pest pressure, so actions on the same field must be replayed in order), beam search (exploring combinations of required/optional actions per field), and a whole-farm MILP solve using HiGHS, compiled to WASM.
The objective cleanly separates direct cash effect from terminal crop value, so future yield benefit is never double-counted:
$$ V_o = \sum_{a \in A_o} (R_a - C_a) + V_{\text{terminal}, o} $$
$$ \max \sum_{f} \sum_{o \in O_f} V_{f,o} \, x_{f,o} \quad \text{s.t.} \quad \sum_{o \in O_f} x_{f,o} = 1 \ \ \forall f $$
subject to shared machine, labour, and water capacity across the whole planning horizon. Weather is modeled two ways: as a hard feasibility gate (some operations simply can't run past a threshold) and as a soft efficiency multiplier — bad conditions stretch how long a job takes rather than stopping it outright, tracked as the gap between raw clock time and weather-adjusted effective progress.
The chat layer. An LLM (Ollama locally, or Groq in the cloud) never decides the plan — it only translates a manager's sentence into one of three structured proposals (a config threshold change, a machine update, or a hard scheduling rule), which then flows through the same deterministic validate → confirm → reoptimize pipeline as the UI forms. Decision explanations work the same way in reverse: deterministic code extracts what happened and its financial effect first; the LLM only narrates evidence it's handed, never infers a reason from a raw schedule.
The deployment split. The dashboard runs on Cloudflare Workers via Next.js for speed and global reach — but Workers have no persistent filesystem and can't load a WASM solver in-process. So every write, reoptimization, or LLM call is proxied over fetch() to a small Node sidecar process, the only part of the system with real disk and solver access.
Challenges we ran into
Silent merge conflicts. Merging two independently-evolved branches — one adding weather-aware optimization, one adding a resource-plan comparison UI — repeatedly caused git to silently drop working code with no conflict marker, because the changes didn't textually overlap even though they touched the same logical feature. We caught it only by noticing tsc complaining about a function that had quietly vanished, and had to recover it from git history by hand.
Getting a small local LLM to be reliable. Our first version asked a 7B model to classify and extract fields for four different proposal shapes in one JSON-schema-constrained call. It kept confusing field IDs for machine IDs, or fixating on the wrong shape's fields entirely — and adding more instructions to the prompt made it worse, not better. Splitting the job into two focused calls — first classify the shape, then extract only that shape's small field set — fixed it completely and held up across repeated tests.
The sandbox boundary. Discovering that Cloudflare Workers can't touch a filesystem or load a WASM MILP solver in-process forced an architecture we hadn't originally planned: a local sidecar server as the one trusted process for real compute, with the edge layer reduced to a thin proxy.
What we learned
That the most trustworthy AI systems are the ones that use AI the least for the parts that matter. The plan itself is chosen by deterministic arithmetic over a real simulation — reproducible, auditable, and verifiable by hand. The LLM only does what LLMs are actually good at: reading messy human language and turning it into something structured, and reading structured evidence and turning it into something readable. Keeping that boundary sharp made the system dramatically easier to trust, debug, and explain.
What's next
Real data connectors (Deere Operations Center, Agworld), farm-specific calibration from historical yield data, and shadow-trial validation against real past disruptions to measure the value of the recommendations before anyone trusts them on a live farm.
Built With
- beam-search
- cloudflare-workers
- css
- groq
- highs
- html
- javascript
- llm
- mixed-integer-programming
- natural-language-processing
- nextjs
- node.js
- ollama
- react
- shadcn-ui
- tailwindcss
- typescript
- vinext
- wasm
- wrangler
Log in or sign up for Devpost to join the conversation.