Note
Letting an agent write to a control system
Giving a language model a read of a building is a documentation problem. Giving it a write is a safety problem, and the gates that make it survivable are not the ones people reach for first.
- Agents
- Permissions
- Station engineering
A write is a plant movement at a priority
In an ordinary program a write sets a variable. In a control system it enters a priority array, competing with everything else that wants the same output, and the result depends on what else is writing and at what level. Nothing about the call tells you that. The verb is the same length as any other.
So the first thing an agent integration has to get right is not the protocol. It is that a successful write is not the same as a correct one, and that the mechanism which decides the outcome lives at the station in the point's priority levels rather than in the request.
Visible does not mean writable
On a Niagara station, exposing a writable point over oBIX is a deliberate act: an export descriptor is added under the oBIX network, naming the point and the priority level the write should land on, and the framework makes the link so that contention is resolved in one place rather than at the protocol edge. Only point types that have priority input slots are valid candidates.
| Point | Can it be exported as writable? |
|---|---|
| Boolean, Enum, Numeric and String writable points | Yes. These are the four types with priority input slots, which is what the descriptor needs in order to land the write at a chosen level. |
| Everything else, including read-only points | No. And a point being visible in the tree says nothing about whether a write to it will be accepted — visibility and writability are two separate acts of engineering. |
That is a gift to anybody integrating an agent, because it means the station already has a list of what an outside caller is allowed to move, maintained by whoever engineered it. The agent's job is not to decide what is writable. It is to not pretend the list does not exist.
Two gates, and a tool that is absent rather than refusing
A tool surface an agent is given at run time should express permission structurally, not conversationally. Two independent decisions, made by the operator and not by the model:
The first is whether this deployment may write at all. The second is where it may write — a path prefix, so a bridge pointed at a whole station can still be allowed to move exactly one subtree.
The detail that matters is what happens when writes are off: the write tool is not advertised. It does not appear in the list of available tools at all, rather than appearing and refusing. A tool that exists and says no is a negotiation, and a model that has been told no by a tool it can see will try a different phrasing, a different path, or a batch operation. A capability that is absent from the surface cannot be talked into existing.
Read-only by default, and off is the resting state. The interesting property of a two-gate design is not that it can be locked down. It is that the locked-down configuration is the one you get by doing nothing.
The type is the element name, so guessing is a fault
oBIX types a value by the element name it arrives in, and not by anything about the value. A set point of 21.5 sent as a string is a string write. Any client that infers the element from the shape of the value will be right most of the time, which is the worst possible failure rate for something that moves plant.
So the type belongs in the call, stated by whoever made the decision, and a tool that asks for it explicitly is not being awkward. More generally: an agent-facing tool should refuse to supply defaults for the arguments a person would have had to think about. This is what the protocol surface actually looks like once read out of a station's own code rather than the standard.
Polling floors exist for the controller's sake
A controller is not a server. The vendor documentation for the oBIX point extension says the watch interval defaults to two seconds and recommends raising it to ten or more to reduce load. An agent asking for a subscription has no idea it is talking to a device with a fraction of a laptop's headroom, and will happily ask for one second because the number sounded responsive.
The fix is not to document a floor. It is to enforce one — clamp the requested interval up, cap the number of concurrent subscriptions, cap how many points one subscription may carry. This installation contains no compiled-in ceiling on any of those, so nothing at the station will stop a client that behaves badly. The client has to stop itself, and the same logic covers every other poll rate on the station.
What the operator can see afterwards
Every write an agent makes should be answerable a week later: what was sent, to what,
at what level, and on whose authority. Two things make that cheap. The bridge — in
our case obix-mcp,
which is where everything in this note was worked out — runs under a named station
user, so the station's own audit trail carries the whose; and the raw request
and reply bodies can be dumped to a log for the run that matters, which is also the only honest way to confirm a client and a real
station agree about a body the documentation never shows.
The credentials must not travel with that log. Dumping the traffic is useful; dumping the header that authorises it is a way of turning a debugging session into an incident.
What none of this proves
Gates, floors and an audit trail make an agent's writes reviewable. They do not make the agent's judgement sound, and no amount of protocol design will. The question of whether a model should be moving a set point on an occupied building at all is a question for the people who own the building, and the honest answer today is that the read side is useful now and the write side is worth building carefully and switching on last.
It is also worth being precise about what has been tested. Writing a client against a station's own bytecode and proving it against a fixture built from that evidence shows that the client and the evidence agree. It does not show that a controller agrees. Until it has been pointed at one, that distinction is the whole difference between a working integration and a plausible one.
Sources
Where this is written down
- Model Context Protocol specification, revision 2024-11-05 — The tool-surface model an agent sees — what listing a tool means, and why an unadvertised capability is different from a refused call
- JSON-RPC 2.0 Specification — The request and notification semantics underneath that surface, including which messages are entitled to no reply at all
- oBIX Version 1.1, OASIS Committee Specification 01 — The write and invoke semantics an oBIX client is implementing, and the fault objects a refusal comes back as
Related
Where this comes up in the work
Station engineering
JACE controller and Niagara station setup end to end: platform commissioning, TLS, users, roles, BACnet and Modbus, tagging, histories, alarms, backups.
Protocols & data
Field protocol integration and the data behind it: BACnet, Modbus, M-Bus and MQTT, into a building system, a database or a dashboard.
More notes
Other things worth writing down
Authentication schemes: each Niagara user picks one
A station can run several login mechanisms at once, and the choice is made per user, not per station. What each scheme is actually for.
- Station security
- Permissions
- Station engineering
What a Niagara station's oBIX server actually exposes
The mount point, the three verbs, the twelve lobby branches, the five that appear in no listing, and where the open alarms actually are.
- Integration
- Agents
- Station security
Conversion links: what a mismatched wire really does
Link a boolean to a numeric and Niagara inserts a converter with its own properties. Here is what each one assumes on your behalf.
- Station engineering
- Wire sheet
- Workbench
Next step
Tell us the version, the hardware, and what it has to do.
You will get a written scope and a fixed price against it. If the honest answer is that you do not need us, you will get that instead.