Skip to content
PanaceaLogics
← Blog

AI Agents and IoT Hub: The Night Shift at a Fish Farm

October 7, 2026 · PanaceaLogics Team

Farmed fish crowding at the surface of the water

Most writing about AI agents and IoT stays at the level of diagrams. This piece does the opposite. It takes one problem on one kind of site and follows it from the first sensor reading to the message that wakes someone up.

The problem is a falling oxygen level, in the middle of the night, on a land-based fish farm. It is a good test because the stakes are high, the time is short, and the same alarm can mean several completely different things.

It is also close to home. We built the data and rule-engine platform behind Urban Blue, which turns farm equipment and sensor data into decisions, and the computer vision behind our award-winning aquaculture AI. What follows is how we would put an agent on top of a system like that. The farm is representative rather than a named client, and every number is either from a published source or marked as illustrative.

The farm

Picture a land-based recirculating farm growing a warm-water species such as barramundi.

  • Twelve production tanks, each with a dissolved oxygen probe, a temperature probe and a pH probe, reporting every 30 seconds.
  • Three blowers supplying air. Line A serves tanks 1 to 4, line B tanks 5 to 8, line C tanks 9 to 12. A fourth blower stands by as backup.
  • A flow meter on each tank inlet and an automatic feeder on each tank.
  • A biofilter keeping ammonia under control.
  • A controller per line that talks to Azure IoT Hub.

Nobody is on site between 10 p.m. and 6 a.m. One technician is on call, about twenty minutes away.

Here is how that equipment looks to IoT Hub:

Device Telemetry it sends Device twin Direct methods
Tank probes (12) Oxygen, temperature, pH every 30 s Reported: last calibration date None
Blower controllers (4) Power draw, air pressure Desired: on or off. Reported: running, fault code Start, stop
Feeders (12) Feed delivered Desired: schedule, paused until. Reported: last feed Pause
Flow meters (12) Litres per minute None None

That is roughly 100,000 probe readings a day from the tanks alone. Remember that number when we get to cost.

Why minutes matter

Oxygen is the first thing that runs out in a recirculating system, and it runs out fast.

The Southern Regional Aquaculture Center gives a worked example for a warm-water system at 84°F, about 29°C. After a power failure, oxygen falls to a stressful 3 parts per million in 16 minutes at a quarter of a pound of fish per gallon, roughly 30 kg per cubic metre. At a pound per gallon, roughly 120 kg per cubic metre, it gets there in under six minutes.

The same source notes two more things that matter here. Nitrifying bacteria in the biofilter stop working efficiently below 2 parts per million, so an oxygen problem quickly becomes a water-quality problem too. And as fish approach harvest size, feeding can push oxygen demand beyond what the aeration can hold above 5 parts per million.

This farm is stocked at the moderate end, so losing the air on one line drops oxygen by roughly 0.3 mg/L a minute. That sounds slow until you put it next to a technician twenty minutes away, and two things become clear:

  1. The first response cannot wait for a person.
  2. The most safety-critical response cannot depend on a model either.

Keep both in mind. The design follows from them.

What the rule engine does, and what it cannot

The farm already has rules, and they stay exactly where they are.

At the edge, on each line’s controller: if oxygen in any tank falls below 3 mg/L, start the backup blower. No cloud, no model, no network needed. If the internet connection is down, the fish still get air.

In the cloud: if oxygen in any tank stays below 5 mg/L for a minute, raise an alarm. That leaves a gap between the alarm and the floor, and that gap is where the agent earns its keep.

What neither rule can do is say why. And on this farm the same alarm has at least six causes that each need a different response.

Cause What the data shows Right response
Blower failure Power draw on one blower drops to zero. Every tank on that line falls together Start backup, page now
Air supply fault Pressure drops on one line while power draw looks normal Start backup, page now
Inlet flow loss Flow into one tank drops and its temperature starts to drift Page now, check the pump and valve
Feeding demand The drop follows a scheduled feed and recovers on its own No page. Note it, consider the next feed
Probe fouling or drift One tank drifts slowly while the others on its line hold steady. Temperature and pH look normal No page. Check the probe in the morning
Biofilter stress Oxygen roughly normal but pH falling and ammonia rising over hours Morning task, unless it speeds up

The first and the fifth rows produce the same alarm and need opposite responses. A rule engine pages for both or for neither. Paging a technician at 2 a.m. for a dirty probe is how people learn to ignore alarms. Not paging for a failed blower is how a farm loses stock.

Telling those rows apart is the agent’s whole job.

A paddlewheel aerator at work, the kind of equipment that keeps the oxygen up

The tools the agent gets

The agent never holds IoT Hub credentials. It gets eight tools, each wrapping one operation, each with its own limit, all running under a managed identity with the narrowest role that works.

Tool What it wraps Limit
get_tank_state(tank) Device twin read Read only
get_telemetry(tank, metric, minutes) Query over routed telemetry Twelve hours at most
get_topology(tank) Which blower, line and pump serve this tank Read only
get_feed_log(tank, hours) Feeder twin and history Read only
start_backup_blower(line) Direct method on a blower controller Pre-approved. Once per line per 30 minutes
pause_feeding(tank, until) Desired property on a feeder Six hours at most. Needs approval
create_task(asset, note) The farm’s work order system None
page_oncall(summary) The paging service One page per incident

The one that matters most is the least exciting: get_topology. Without knowing which tanks share a blower, the agent cannot tell a blower failure from a probe fault. With it, the first question is easy to answer: is this one tank, or a whole line?

The night, minute by minute

01:57. Blower B stops. Its controller keeps reporting “running”, because the last state it sent was “running” and nothing has replaced it yet.

01:58. Oxygen in tanks 5 to 8 starts to fall from about 7 mg/L. Tank 7 holds the most fish, so it falls fastest, at about 0.3 mg/L a minute.

02:06. Tank 7 has been below 5 mg/L for a minute. The cloud rule raises an alarm, and the event is routed to the agent.

02:06, twenty seconds later. The agent works through its tools:

  • get_topology(7): line B, which also serves tanks 5, 6 and 8.
  • get_telemetry for tanks 5 to 8: all four falling, at similar rates.
  • get_telemetry for blower B’s power draw: zero since 01:57.
  • get_tank_state for blower B: reports “running”. That contradicts the power reading, which is itself evidence of a fault.
  • get_feed_log(7, 12): last feed at 6 p.m. Feeding demand is ruled out.

Four tanks on one line, falling together, with a blower drawing no power. This is not a probe and it is not feeding.

02:07. start_backup_blower(B). The direct method returns success, and the backup controller’s reported state changes to running within seconds. The action is confirmed, not assumed.

02:07. The page goes out:

URGENT: Blower B stopped at 01:57. Tanks 5 to 8 losing oxygen.
Tank 7 at 4.3 mg/L and falling. Tanks 5, 6 and 8 on the same trend.
Blower B drawing 0 W since 01:57, but its controller still says "running".
Backup blower started 02:07 and confirmed. Watching tank 7 for recovery.
Not a probe fault: four tanks on one line, same slope.
Suggest pausing the 06:00 feed for tanks 5 to 8. Reply Y to approve.

02:09. The technician replies Y. The 06:00 feed for tanks 5 to 8 is paused.

02:12. Tank 7 is back above 5 mg/L. The agent adds the recovery to the incident record.

02:27. The technician arrives and finds a tripped breaker on blower B. They reset it, switch back from the backup, and close the incident.

02:35. The agent writes up the incident and opens a work order: find out why blower B’s breaker tripped, and why its controller kept reporting “running” with no power. That second question is one a rule engine would never have raised, and it may matter more than the first.

Without the agent, the backup would have started only when tank 7 reached the 3 mg/L floor, at around 02:11, and the technician would have been woken by a number. With it, the backup was running four minutes earlier, and the technician was woken by a diagnosis.

The technician still goes. They just go knowing what they will find.

The same night, a different alarm

03:40. Tank 2 has been below 5 mg/L for a minute. Same alarm, same rule.

This time get_telemetry shows tanks 1, 3 and 4, on the same line, holding steady at 7 mg/L. Blower A’s power draw is normal. Tank 2’s temperature and pH have not moved. Its oxygen reading has been sliding slowly for two days, and the twin shows the probe was last calibrated eleven weeks ago.

The agent does not page. It marks tank 2’s readings as suspect, creates a morning task to clean and calibrate the probe, and notes in the incident that it chose not to wake anyone and why.

This is the half of the value nobody puts in the demo. The technician slept through it, and the next real alarm will still be taken seriously.

What the agent never does

  • It never stands between a hard floor and the backup blower. The edge rule at 3 mg/L runs whether or not the cloud, the network or the agent is working.
  • It never changes feed rates, temperatures or water treatment on its own. It can suggest. A person approves.
  • It never closes an incident. A person does, once they have seen the equipment.

When the equipment does not answer

There is one failure the agent has to handle with special care: a controller that does not respond.

IoT Hub offers two ways to change a device. A direct method is a call that needs the device connected right now, and it either succeeds or fails within a timeout. A desired property is stored in the device twin and applied whenever the device next connects, which could be in a second or in an hour.

For routine changes, desired properties are the better choice, because the device confirms what it actually did. For an emergency, they are the wrong tool. A backup blower that starts “when the controller reconnects” is not a backup.

So emergency actions on this farm use direct methods with a short timeout, and the agent treats a timeout as an emergency in its own right:

URGENT: Could not reach blower controller B at 02:07.
Backup blower NOT confirmed. Tanks 5 to 8 losing oxygen.
Edge safety rule will start backup at 3 mg/L if the controller is alive.
Go now.

Measuring it before trusting it

For the first month the agent changes nothing. It reads, it diagnoses, and it writes its conclusion into each incident, but it neither acts nor pages. Then you compare.

  • Diagnosis match rate. Did the agent’s cause match what the technician found?
  • Pages that would have been avoided. Probe faults and feeding dips that would not have woken anyone.
  • Pages that would have been missed. This number has to be zero. One miss and the agent stays read-only.
  • Minutes from alarm to the right action. The figure that actually protects stock.

Only when those numbers hold does start_backup_blower get switched on. pause_feeding stays behind approval for good.

What it costs to run

Not much, and that is a design choice. The agent never sees the 100,000 readings a day. It sees the handful of alarms the rules let through, and for each one it makes a few tool calls and a short chain of reasoning. Pointing a language model at raw telemetry would cost far more and decide far worse. We go into the arithmetic in where agentic AI gets expensive.

Where else this shape works

The pattern is not specific to fish. It fits anywhere a single alarm has several causes that need different responses, and getting it wrong in either direction is costly:

  • Cold storage, where a rising temperature might be a failed compressor, a door left open or a sensor in the wrong place.
  • Water and pump stations, where low pressure might be a pump, a valve or a burst.
  • Greenhouses, where the climate depends on several pieces of equipment at once. We built the IoT side of that for Sperotec.

In every case the work is the same. Keep the safety rule at the edge. Give the agent the topology. Let it explain before you let it act.


Oxygen figures from Southern Regional Aquaculture Center Publication No. 452, Masser, Rakocy and Losordo (1999). The farm, its layout and the incident timeline are illustrative.

We build the data platforms and agents behind systems like this, on .NET and Azure. See our aquaculture and agtech work and our AI agents and copilots service, or get in touch. For the water-quality side, read managing water quality in aquaculture with AI.