IoT and AI

Industrial AI Copilot for Operators: Grounding Answers in Live ISA-95 Data

An industrial AI copilot is only as good as the data it can see. This guide shows how to ground an operator assistant in live, ISA-95 structured MQTT topics so its answers are current, traceable and safe.

Industrial AI copilot chat panel next to a production line dashboard reading live ISA-95 MQTT topics
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

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:

  1. Answers status questions from live data: current speed, temperature, alarm state, last good part.
  2. Explains context by combining live values with documents: the SOP for a fault code, the maintenance history of a pump, the changeover checklist.
  3. Summarises shifts, alarms and downtime in language a supervisor can paste into a handover note.
  4. 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:

  1. Edge publishers: PLC gateways, Sparkplug B edge nodes or small Python services publish machine data to the broker.
  2. 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/cnc1 becomes connectmq/factory1/production/line1/cnc1/telemetry. That republished tree is exactly the shape a language model handles well.
  3. A copilot service that connects to the broker as an ordinary MQTT client, subscribes read-only, and maintains a live snapshot.
  4. Tools the LLM can call, such as get_area_snapshot(path) or list_active_alarms(line). The model never queries the broker directly; it asks your code.
  5. 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:

  1. Namespace quality: are topics consistently named down to the signal level, with units in the payload or the name?
  2. Retained values: does every state topic publish retained, so a new subscriber sees current values immediately?
  3. Read-only credentials: does the copilot service use its own credentials that are never used for command topics?
  4. Staleness handling: does every tool result include age, and does the prompt instruct the model to respect it?
  5. Traceability: does every answer cite topic paths or document sources?
  6. Logging: are questions, tool calls and answers logged for review?
  7. Scope: is the copilot limited to the lines and areas the user is responsible for?
  8. 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.

Related articles

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.