An industrial AI copilot is an assistant that lets operators ask plain-language questions such as "why did line 2 slow down after lunch?" and get answers grounded in real plant data. In this guide you will learn how such a copilot is put together, why a well-structured ISA-95 topic tree is the most important ingredient, and how to wire a read-only copilot to live MQTT data without letting it touch the machines.
What an Industrial AI Copilot Actually Does
The word "copilot" is used loosely, so it helps to be precise. In a plant, a useful copilot does four things:
- Answers status questions from live data: current speed, temperature, alarm state, last good part.
- Explains context by combining live values with documents: the SOP for a fault code, the maintenance history of a pump, the changeover checklist.
- Summarises shifts, alarms and downtime in language a supervisor can paste into a handover note.
- Suggests next steps, but leaves the decision and the action to a human.
What it should not do, at least at first, is issue commands. A copilot that reads is a productivity tool. A copilot that writes to equipment is a control system, and it needs a different level of engineering, which we cover in our guide to safe AI control of IoT devices.
The large language model (LLM) itself knows nothing about your plant. Everything that makes the answer correct comes from the data you feed it at question time. That is why the architecture around the model matters far more than the choice of model.
Why Structured Live Data Beats a Data Dump
The naive approach is to export a CSV of tags and paste it into the prompt. It fails quickly: tag names like PLC3_DB12_W4 mean nothing to a model, the snapshot is stale by the time anyone reads the answer, and the context window fills with irrelevant values.
An ISA-95 structured namespace fixes all three problems. When every value lives at a path like acme/plant1/assembly/line2/press4/temperature, the hierarchy itself carries meaning. The model can see that press4 belongs to line2, which belongs to assembly. A question about "line 2" maps naturally to a topic filter acme/plant1/assembly/line2/#. This is the same idea behind the Unified Namespace explained in our article on MQTT Sparkplug B and the Unified Namespace.
| Data approach | Freshness | Meaning for the LLM | Context cost |
|---|---|---|---|
| Raw PLC tag export | Stale snapshot | Low, cryptic names | High |
| Historian query only | Good for history, slow for "now" | Medium | Medium |
| ISA-95 MQTT topics with retained values | Live | High, hierarchy is self-describing | Low, filter by subtree |
| ISA-95 topics plus document retrieval | Live plus procedures | Highest | Low to medium |
MQTT is a natural fit because the broker already holds the latest retained value for each topic. A copilot service can subscribe to the subtree it cares about, keep an in-memory snapshot, and hand the model only the part relevant to the question.
Reference Architecture for an Industrial AI Copilot
A practical layout has five parts:
- Edge publishers: PLC gateways, Sparkplug B edge nodes or small Python services publish machine data to the broker.
- The MQTT broker in the middle. ConnectMQ detects ISA-95 and Unified Namespace topics, and it can decode Sparkplug B payloads and republish them into readable ISA-95 topics, for example
spBv1.0/factory1/DDATA/line1/cnc1becomesconnectmq/factory1/production/line1/cnc1/telemetry. That republished tree is exactly the shape a language model handles well. - A copilot service that connects to the broker as an ordinary MQTT client, subscribes read-only, and maintains a live snapshot.
- Tools the LLM can call, such as
get_area_snapshot(path)orlist_active_alarms(line). The model never queries the broker directly; it asks your code. - A document index for procedures and manuals, which is the domain of retrieval augmented generation for industrial data.
The copilot service is your code, running on your infrastructure, talking to the LLM provider of your choice. ConnectMQ is the messaging layer; it does not run the model. That separation is healthy: you can change models without touching the plant.
If you are new to the role of the broker in this picture, our explainer on what an MQTT broker does is a quick primer.
Building the Live Context Layer in Python
The core of the copilot is a small service that mirrors part of the topic tree in memory and exposes it as a tool. The example below uses paho-mqtt 2.x and the public test broker so you can run it immediately. Replace the prefix with your own random ID.
import json
import threading
import time
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/k7f3q9/plant1" # use your own random id
snapshot = {}
lock = threading.Lock()
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
client.subscribe(f"{PREFIX}/#", qos=1)
def on_message(client, userdata, msg):
try:
value = json.loads(msg.payload)
except ValueError:
value = msg.payload.decode(errors="replace")
with lock:
snapshot[msg.topic] = {"value": value, "received": time.time()}
def get_area_snapshot(path: str, max_age_s: int = 300) -> dict:
"""Tool exposed to the LLM: latest values under an ISA-95 path."""
base = f"{PREFIX}/{path.strip('/')}"
now = time.time()
with lock:
return {
topic[len(PREFIX) + 1:]: {
"value": entry["value"],
"age_s": round(now - entry["received"]),
"stale": now - entry["received"] > max_age_s,
}
for topic, entry in snapshot.items()
if topic == base or topic.startswith(base + "/")
}
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="copilot-reader-k7f3q9")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=60)
client.loop_start()
if __name__ == "__main__":
time.sleep(3)
print(json.dumps(get_area_snapshot("assembly/line2"), indent=2))
To give it something to read, publish a few retained values from another terminal:
mosquitto_pub -h broker.connectmq.com -p 1883 -i seed-k7f3q9 -r -q 1 \
-t connectmq/demo/k7f3q9/plant1/assembly/line2/press4/temperature -m '{"c": 71.4}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i seed-k7f3q9 -r -q 1 \
-t connectmq/demo/k7f3q9/plant1/assembly/line2/press4/state -m '"RUNNING"'
Three design choices in that code matter more than they look:
- Age and staleness are returned with every value. An LLM will happily report a two-hour-old temperature as current. Making staleness explicit lets the model say "no recent data" instead of inventing confidence. Note that a retained value delivered at subscribe time gets a fresh receive timestamp, so for strict staleness include a source timestamp in the payload.
- Paths are relative and scoped. The tool only ever reads below
PREFIX, so a confused model cannot wander into other namespaces. - The service is read-only. It subscribes and never publishes to equipment topics.
You then register get_area_snapshot as a tool (function) with whatever LLM API you use. Our guide on LLM function calling for IoT walks through the tool schema pattern, and if you prefer a standard interface, an MCP server for MQTT exposes the same capability to any compatible client.
Prompting the Copilot for Plant Questions
The system prompt should describe the namespace, not the data. Something like this works well:
You are an assistant for operators at plant1.
Live data is organised as <area>/<line>/<cell>/<signal>, for example assembly/line2/press4/temperature.
Use get_area_snapshot(path) to read live values. Never guess a value you did not read.
If a value is marked stale, say so. You cannot change any equipment. For actions, tell the operator what to check.
Answer in short sentences and quote the topic path for every number you mention.
Asking the model to quote the topic path for every number is a cheap, powerful habit. Operators can verify any claim in the live topic explorer, and wrong answers become easy to spot and report.
Industrial AI Copilot Design Checklist
Before showing a copilot to a shift team, walk through this list:
- Namespace quality: are topics consistently named down to the signal level, with units in the payload or the name?
- Retained values: does every state topic publish retained, so a new subscriber sees current values immediately?
- Read-only credentials: does the copilot service use its own credentials that are never used for command topics?
- Staleness handling: does every tool result include age, and does the prompt instruct the model to respect it?
- Traceability: does every answer cite topic paths or document sources?
- Logging: are questions, tool calls and answers logged for review?
- Scope: is the copilot limited to the lines and areas the user is responsible for?
- Escalation: does the copilot tell the operator who to call when it cannot answer?
Common Pitfalls
Hallucinated readings. The model fills gaps with plausible numbers. Countermeasure: the "never guess" instruction plus tool results that clearly show missing data.
Too much context. Dumping the entire plant into every request is slow and confuses the model. Let the model call the tool with a narrow path, and summarise numeric series before returning them.
Unit confusion. A value of 71.4 without a unit invites mistakes. Put units in the payload ({"c": 71.4}) or in metadata the tool returns.
Sensitive data on public brokers. The public area of broker.connectmq.com is readable by anyone who guesses the topic. It is fine for this demo, but production plant data belongs on a private broker with its own credentials.
Treating the copilot as an authority. Operators should see it as a fast colleague who reads screens, not as the safety system. Keep alarm handling and interlocks where they are.
Running the Copilot Against Production Data
Moving from demo to a real line mostly means changing the connection. Create a broker in the ConnectMQ portal, copy the generated credentials, and connect over TLS:
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="YOUR_CLIENT_ID")
client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
client.tls_set() # system CA store trusts the Let's Encrypt certificate
client.connect("broker.connectmq.com", 8883, keepalive=60)
A fully managed MQTT service removes the broker from your list of things to patch and monitor, so the team can focus on namespace quality and the copilot itself. For a wider view of how AI and messaging fit together, see the AI and IoT guide, and for hosting options read about the cloud MQTT broker approach.
Give Your Copilot a Live Plant Namespace
A copilot needs a clean, live, well-structured data source before it needs a bigger model. ConnectMQ gives you a managed broker with ISA-95 topic detection, Sparkplug B decoding and a live topic explorer to check what the copilot sees. Create a free Serverless broker at app.connectmq.com, no credit card required, and point the read-only service above at it.
Frequently Asked Questions
Does ConnectMQ include an AI copilot?
No. ConnectMQ is the MQTT broker that delivers the live data. The copilot is your own service that connects as an MQTT client and calls the LLM of your choice.
Should an industrial AI copilot be allowed to control machines?
Not at the start. Begin read-only, prove accuracy and usefulness, and only add tightly guarded, human-confirmed actions later with allowlists, rate limits and audit logs.
Why does ISA-95 structure help a language model?
The hierarchy of enterprise, site, area, line and cell gives every value a self-describing path, so the model can map a question about "line 2" to a precise topic filter instead of guessing at cryptic tag names.
Can the copilot use historical data too?
Yes. Live MQTT values answer "what is happening now", while a historian or time-series database answers "what happened". Expose each as a separate tool so the model picks the right source.



