One Oracle, Many Hats: A Model You Call Once for a Plant That Answers Offline

Most arguments about AI on a plant network are about where to put the model. This experiment puts it nowhere.

what this is

The setup. A fictional water plant: three stations and a control room. My father's symbolic engine, Brainhat (the same one as the first three posts), runs as a small program on each station.

The idea. Ask a language model once, before the plant is sealed, to describe the plant in plain English. Compile that into facts and rules the engine can hold. Push them to the stations over WireGuard tunnels. Close the perimeter.

The test. Ask every station 24 operator questions. Ask the model the same 24. Compare. Then probe the perimeter from inside and from the plant network.

The result. A station answers in 44 milliseconds with no model and no route out; the model takes 2.1 seconds. All 24 answers agree. Both probes held.

I am a security engineer, so this post follows four questions in order: what crosses the perimeter, what it costs, whether it survives a real SCADA environment, and what an attacker on the plant network can reach. The first and the last are the same question.

The brief, not the pitch

The entire input is a 3,604-byte Markdown file. It reads like a handover note:

The intake station takes water from the river. Its main pump is the intake pump, also called pump one, the river pump. Water enters through the intake pump, fills the intake tank, and passes out through the intake valve toward treatment. Maria is the intake operator; she runs the intake pump and opens the intake valve when the tank needs to drain.

Procedure at intake: when the intake tank is full there is no more room for river water, so the intake pump stops until the tank is drawn down again.

That last paragraph is a rung of ladder logic with its explanation attached. On a PLC it would be a contact, a coil and a comment nobody updates. In English it is if the intake tank is full then the intake pump stops. — a sentence the engine can both fire and restate. Conventional PLC programming is very good at saying what a system does. It has no way to say what a system knows, and every question an operator actually asks — where is it, who runs it, is it full, why did it stop — is a question about what the system knows. Natural language states that far better than a rung does. Rather than argue it, this round demonstrates it: a page of plain English, one model call, and a control-room assistant that answers on the station.

One call, then the boundary closes

The oracle is claude-opus-5, consulted exactly once, with the brief and one instruction: turn this into facts, rules and any words the engine's dictionary is missing, in the engine's own shape. It wrote back 27 facts, 4 rules and a table of 20 nouns with their classes and plurals. One of the 20 collided with a label the stock word lists already own — "plant" names both a botanical plant-n1 and a location also labelled "plant" — so the compiler dropped it and kept 19, and every plant question still resolved to the right sense. Those 19 words compiled into the image the same way the Osage County cast did in the dictionary post: a word list, a build file, a preprocessor, one binary.

Then the call is over. Four engine copies came up from that one build, and nothing outside the plant is consulted again at question time.

OUTSIDE THE PLANTthe language modelasked once, before the plant is sealedTHE PLANT FENCEone exchangea page of plain English goes outfacts and rules come backthen the fence is closed;no question ever goes back outcontrol roomturns the reply into station knowledgeeach station is told its part, over a locked tunnelintakeanswers on the spottreatmentanswers on the spotdistributionanswers on the spotoperators ask here, and get an answer in about a twentieth of a second
One exchange crosses the plant fence before it is sealed. After that the control room tells each station its part over a locked tunnel, operators get their answers on the spot, and no question ever goes back out.

Zones, conduits and an electronic security perimeter

In IEC 62443 words, because that is the vocabulary the people who have to sign off on this already use: each station's network is a zone, the WireGuard tunnel between a station and the control room is a conduit, and a hat's identity is the key on its end of that tunnel. No hostname, no certificate chain, no API token in an environment variable: a 32-byte public key listed on the hub with a /32 AllowedIPs of its own. A packet that does not verify under a listed key is dropped before the engine hears it. The security level of the conduit is the tunnel's, which is why the conduit gets drawn and not just the wire.

In NERC CIP words it is the same picture from the other side. The plant network with the hats inside it is an Electronic Security Perimeter — an ESP, the logical border CIP-005 draws around the assets that matter — and the WireGuard endpoints are its Electronic Access Points, the EAPs: the only places a routable protocol gets through. The peer list and the four key pairs are the access-point inventory, and they fit on one page. The one build-time call is the only External Routable Connectivity event in the whole design; it happens at the control room, once, before the perimeter closes, and never again.

Where that call belongs in production is not where I ran it. In a real plant the build call goes outside the ESP entirely — an engineering workstation in a DMZ, the same side of the fence CIP-005 already puts an Intermediate System on for remote access — and the compiled sentences and word list come in over a controlled path, so that no BES Cyber Asset inside the perimeter ever holds a conversation with an internet model. In the proof it ran on the control-room host, because that is where the clock was and I wanted the crossing measured.

OUTSIDEthe language modelreached once, at build timeTHE PLANT'S OWN NETWORK · no route to the internetcontrol roomholds all the knowledgeand hands it outintake stationriver pump, intake tanka small program on theoperator's own computertreatment stationchlorine pump, filtera small program on theoperator's own computerdistribution stationtower tank, main valvea small program on theoperator's own computerlocked tunnels:only the station'sown key opens them
Inside the plant's own network, which has no route to the internet, a control room hands knowledge to three station computers over locked tunnels. The language model outside is reached once, at build time, and never again.

Each station container closes itself in both directions when it starts, and both halves matter:

  • Inbound is default-deny. The INPUT chain accepts loopback, the tunnel interface wg0, UDP 51820 (the tunnel's own handshakes arrive on the underlay, not on wg0) and established traffic, then sets policy DROP. The engine's two ports are protected by the policy, not by a rule written about them. That enumeration — loopback, wg0, UDP 51820, and TCP 8080 only on the control room, where the plant's shim is published — is a ports-and-services baseline of the kind CIP-007 R1 asks for: short enough to print.
  • Outbound is default-deny too. OUTPUT drops everything but loopback, the tunnel and RFC 1918 destinations. All four hats are offline, the control room included — it is the one station also attached to a routed network, because it publishes the plant's shim, and the egress rule holds it inside like the rest except for the one build call.
  • The engine's own ports are plain text. 4144 is the console, 4145 the websocket peer port, and neither authenticates anything. There is no second layer. The overlay is the security story. If a hat's key ends up on a machine that should not have it, that machine is the hat.

The record the run writes is the other half of that. It is the baseline of what the hats know: the word list and each station's script are the configuration items, and a new oracle call is a configuration change that produces a diff against the previous record rather than a mystery. The whole run comes back from a tarball, which is what a CIP-010 baseline and vulnerability assessment want: the system itself, repeatable, not a document about it. Two things this round does not do: there is no malicious-code control on these containers and no security event logging, CIP-007 R3 and R4, both out of scope.

the pieceIEC 62443NERC CIP
the plant network the hats sit ina zonethe Electronic Security Perimeter (CIP-005 R1)
a WireGuard tunnel, control room to stationa conduit, at the tunnel's security levelits two ends are Electronic Access Points
the peer list and the four key pairsidentity on the conduitthe access-point inventory and its permissions
default-deny in and out, with the enumerated exceptionsthe conduit's allowed trafficports and services baseline (CIP-007 R1)
the one build-time callthe single crossing between zonesExternal Routable Connectivity, once, at the control room
the record file, the word list, each station's scriptthe zone's configurationbaseline configuration and change management (CIP-010 R1)

One caveat on that right-hand column. A three-station water plant is not the Bulk Electric System, so NERC probably does not reach it at all, and an electric facility of this size would almost certainly land as low impact under CIP-002. The vocabulary transfers; the obligation may not. I use it because it is the language the people who assess this kind of design already speak, and because the two frameworks are describing the same picture from two directions — 62443 from the plant outward, CIP from the perimeter inward.

The plain-text bullet above is why this design holds up. A symbolic engine is not inherently safer than a model. The design is secure because the thing that would need protecting is never reachable, and the one crossing happens once, supervised, at build time.

What the plant network sees

A packet on the underlay, inside out: an IP header between two plant addresses, UDP 51820, a WireGuard payload under ChaCha20-Poly1305 keyed to the sender, and inside that the inner IP and the engine's clear-text port. The clear text exists only inside the tunnel.

ONE MESSAGE ON THE PLANT NETWORK, OPENED LAYER BY LAYERwho it is fromand toa sealed envelope, locked with the sender's keyseen in passing; unreadableinside: the sentence“the intake tank is full.”What a station acceptsa sealed envelope from a key on its listopened and readan envelope from any other keydropped before the program hears ita plain, unsealed message to the programdropped: the program only listens inside the tunnelanything trying to leave for the internetdropped: there is no way outThe station's programs speak plainly to each other. The tunnel is what makes that safe:the sealing, the sender's identity, and the no-way-out rule all live in the tunnel, not in the program.
What someone on the plant network can see: sealed envelopes going past, locked with the sender's key. A station opens envelopes only from keys on its short list, drops everything else, and has no way out to the internet. The programs speak plainly to each other; the tunnel is what makes that safe.

So the answer to "what does an attacker with a foothold on the plant network get" is: encrypted UDP between addresses they can already see. No console banner, because 4144 answers only on wg0. No peer port. No DNS. No egress to pivot through. Nothing on the wire says a language model was ever involved, because after build time none is.

Two probes, not two adjectives

"Unreachable from the underlay" and "cannot reach the internet" are claims until something tests them. Two probes do, and both results are in the record:

  • Inbound. A throwaway container on the underlay bridge tries the engine port on a station — 4144 on 172.31.0.11 — and times out. The same hat keeps answering over the tunnel and on its own loopback, so the probe is measuring the firewall and not a dead container.
  • Outbound. Each of the four hats opens a bare socket to an internet host and fails at the DNS step. Blocked, on all four: hat-intake, hat-treatment, hat-distribution, hat-control.

One change this round matters more than the probes themselves. A probe that never executed used to look exactly like one that passed. The run now demands positive evidence: the probe's own return code has to be present in the captured output, and the verdict requires all four hats blocked and the underlay port timed out, or the whole run fails. Absence of a failure is not a pass. That habit belongs in any test harness.

It is also the evidence an assessor asks for under CIP-005 R1: not a diagram of the perimeter but a demonstration that inbound and outbound access are denied by default, with an explicit reason for each thing that is permitted. Two commands, two recorded results, one per direction, and a run that fails if either one cannot show it executed.

Free after the first bill

The cost argument is usually made in dollars. There are three ledgers, and dollars is the least interesting.

ONCE, WHEN THE PLANT IS SET UPthe description goes outabout 2,500 words' worththe facts come backabout 900 words' worthone block ≈ 100 tokens, the units a model is paid inTHEN, FOR EVERY QUESTION AN OPERATOR ASKSask the model each timethe whole description travels again: 950 tokens, out and backask the stationnothing crosses; the answer comes from the station itself
What crosses the fence. Setting the plant up moves about 3,400 tokens across it, once. Afterwards, asking a model moves 950 tokens every question because the model keeps nothing between calls; asking the station moves none.

Tokens. On the one-call path, 3,400 tokens cross the boundary in total and then zero cross per question, forever. On a call-per-question path nothing crosses up front and about 950 tokens cross every single time, because the brief has to travel with each question — the model holds nothing between calls. At a thousand questions that is 950,000 tokens against 3,400.

Energy. Published median-query figures put a model call at about 0.3 Wh (Google's median Gemini text prompt is 0.24 Wh; Altman's number is 0.34; Epoch AI's is 0.3). A hat answering the same question is 50 ms on one core, about 0.15 mWh. Building the plant's knowledge and disseminating it costs about 1.56 Wh, once. The two lines cross after five questions.

ELECTRICITY FOR ONE THOUSAND QUESTIONS, ESTIMATEDa model, every timeabout 300 watt-hours: a 10-watt LED bulb burning for 30 hoursone call, then stationsabout 1.7 watt-hours: the same bulb for ten minutesThe model figure is the published per-query estimate (0.24 to 0.34 watt-hours); the station figure isone build call plus a twentieth of a second of one processor core per question. Break-even: the sixth question.
Electricity for a thousand operator questions, estimated: a model consulted every time is a 10-watt bulb burning for 30 hours; one call then station programs is the same bulb for ten minutes. The model figure is the published per-query estimate; the station figure is one build call plus a twentieth of a second of one processor core per question.

Money. The oracle cost $0.0066 a question and $0.0399 once to build the plant's knowledge. A station costs $0 a question after that. The round's whole oracle spend at the point the build and the 24 comparison answers were done — two build attempts included, since the first one failed to parse and is in the ledger — was $0.39. Small enough that the dollars are not the argument. The 950 tokens that never leave are.

Forty-eight times slower, and that is the wrong complaint

Every row of this table was predicted in the design note before the run, and the measured column comes from the record the run wrote:

quantitypredictedmeasured
hat answering through the shim, p5045 to 80 ms44 ms
oracle answering one question, p501 to 5 s2,128 ms
oracle cost per questionunder $0.05$0.0066
oracle cost to build the knowledge, onceunder $1$0.0399
dissemination, per line per hatabout 75 ms56 ms
agreement, station-local questionsat least 90%100% (18 of 18)
engine port reachable from the underlaynono (timeout)
internet reachable from any of the four hatsnono (blocked on all four)

One prediction landed outside its band: the hat answered faster than predicted, 44 ms against a 45-to-80 band, just under the low edge. The oracle took 2,128 ms at the median for the same question, about 48 times slower, over 24 questions; the hat's 44 ms p50 and 48 ms p95 are over 96 answers.

TIME TO AN ANSWER, MEASUREDthe station44 thousandths of a secondfaster than a blink; the operator sees no waitthe model2.1 seconds48 times longer, and a network round trip on top of thatdrawn to scale: the station's bar is the small navy mark
Time to an answer, measured on the run: the station answers faster than a blink; the model takes about two seconds, and a network round trip on top of that.

Forty-eight times slower is the wrong complaint, because the oracle is not in the answering path. What matters is where a station's 44 ms goes, and it goes to the engine. The tunnel is a millisecond or two. An operator's question never enters a tunnel — it is asked on the station that answers it — so the overlay changes how fast knowledge arrives, not how fast a station answers.

DOES THE LOCKED TUNNEL SLOW THE STATION DOWN? MEASUREDplain network, earlier round44 msthrough the locked tunnel, this run44 msa line of knowledge arriving56 msThe same answer takes the same time with the tunnel as without it. Sealing every message costs nothingthis clock can see; the program's own thinking is the whole bar.
Does sealing every message slow the station down? No. The same answer takes the same time through the locked tunnel as on a plain network; the program's own thinking is the whole bar.

Who knows what

A station is only told its own share. Intake got 13 lines, treatment 14, distribution 14, and the control room got all 31 because a control room is supposed to know everything; 72 lines reached the four stations in total. At 56 ms a line a station is provisioned in well under a second and the whole plant in about four.

WHAT EACH STATION WAS TOLD, IN ITS OWN WORDSintake station13 lines, loaded in 0.7 s“the intake pump is in the intake building.”“the intake tank is full.”“if the intake tank is full then the intake pump stops.”… and the plant-wide factstreatment station14 lines, loaded in 0.7 s“the chlorine tank is empty.”“tomas runs the filter.”“if the chlorine tank is empty then the chlorine pump stops.”… and the plant-wide factsdistribution station14 lines, loaded in 0.7 s“pump two is broken.”“nadia runs pump two.”“if pump two is broken then nadia calls ola.”… and the plant-wide factsEvery line is one plain sentence the control room says to the station over its tunnel, about a twentieth of asecond each. A line starting “if” is a rule: it lets the station answer a question nobody stated as a fact.
What each station was told, in its own words: its facts and rules, thirteen or fourteen plain sentences, loaded in under a second. A sentence starting with “if” is a rule, and it is what lets a station answer a question nobody stated as a fact.

Then the comparison. The same 24 questions went to the oracle and to every station, and the stations agreed with the oracle on all 24. On the 18 questions that belong to one station, every station that should have known, knew (18 of 18), and no station answered a question it had not been told; the record counts those separately as leaks, and there were none. One miss is flagged; it is an engine finding, described below. The whole run — build, dissemination, 96 hat answers, 24 oracle answers and both probes — took 98.4 seconds.

What deployable means here

"Runs on the edge" usually means a GPU in a cabinet. Here a hat is a 50-millisecond process on the operator station, a few megabytes of C and a compiled dictionary. The stack is a compose profile and a WireGuard star: four containers, four key pairs, one hub, no GPU, no vendor appliance, no license server, unpacked from a tarball. A plant that already runs an operator station has the hardware.

What is deliberately out of scope this round: live state, real hosts, and the conversational play. Nothing here reads a tag from a historian or a PLC. The facts are the ones the brief described and the oracle compiled, and they are static for the life of the build. Making a hat track a level that changes every second is the next question.

What the engine told us

Four findings came out of the round and went to my father with reproductions:

  • A question with the stock verb "start" never resolves. On a pristine release with no plant vocabulary at all, "the man starts the car." restates fine and "does the man start the car?" answers maybe. I do not know. every time, while the same shape with "stop" answers correctly. One observation, not an explanation: verbs.txt files start-v1/start-v2 under a class stop-v1 does not carry.
  • A rule-derived fact answers alone but not under the whole plant. "Does the intake pump stop?" answers yes when only intake's own lines and its rule are loaded. Load all 31 of the control station's lines first — which contain those same two sentences — and the same question answers maybe. I do not know. Same binary, same vocabulary, same two sentences present; only the surrounding 29 lines differ. This is the one flagged miss in the run.
  • A pronoun for a subject mentioned once. "Who runs the plant?" comes back as "he runs the plant in the valley." on the two stations whose lines mention Ola only in the shared plant-wide sentences, and by name on the two stations that also carry an earlier, separate mention of the name. Four otherwise identical stations made that a controlled comparison. We accepted the pronoun as knowing.
  • The engine's listeners are IPv4-only. Against a real Yggdrasil address — the mesh where the address is a hash of the node's public key, the refinement if you want identity bound to the address itself — the peer connect fails bare and bracketed alike, and /proc/net/tcp6 inside the receiving station has no entry for 4144 or 4145 at all. Recorded for him; nothing in the plant depends on it.

Neighbours

The parts of this exist elsewhere. Separating knowledge-base creation from inference is a published pattern. So is distilling an LLM's output into rules, and pairing a model with a symbolic reasoner for question answering, and caching model answers by semantic similarity, and natural-language interfaces to SCADA. Two patents sit closest — caching model answers at the edge, and conversational units deployed at local sites — and both deserve a careful read before anyone claims novelty. What is new is narrower: sentences as the knowledge form rather than embeddings or a fine-tune, the engine's own peer mechanism carrying that knowledge at the symbolic level, and one call standing in for what would otherwise be continuous inference on the plant floor. The parts exist; the combination, measured end to end against a real network boundary, is what this adds.

Who wrote this, again

Same as the last three posts. Brainhat, its logo, and thirty-six years of C are my father's, Kevin Dowd's, used with his permission; I did not touch a line of the engine. The plant brief, the WireGuard build, the oracle harness, the probes, the check scripts and the figure sheets these charts come from were produced with an AI coding agent — Claude Code — working under my direction. I chose the security posture, wrote the predictions before the run, decided what counted as a pass, and read every reply. The model that wrote the plant's knowledge and the agent that built the harness are both Claude; which is why the comparison is 24 questions checked against a recorded expectation. Every number on this page is computed from one record file that a single script can reproduce.

takeaway
AI reaches a plant through one call and a closed boundary, not through a model on the plant floor. Once the knowledge is a set of sentences, the thing that would need protecting is no longer reachable — the stations answer with no model, no network, and no route out, and an attacker on the underlay sees encrypted UDP and nothing else. That is not an argument; it is a timed-out port probe and four failed DNS lookups, recorded by a run that fails if they do not execute. In CIP words: one External Routable Connectivity event, at one Electronic Access Point, before the ESP closes. The useful security property of a language model in a control room is how quickly you can stop needing it.

Step through the run yourself: the water plant, replayed in four acts, built from the same record these numbers come from.