Skip to content

Online edits

nautilus has two planes. The cold plane — connections, the tag manifest, scan classes, server wiring — is Go, and changes ship through CI/CD as a new binary. The hot plane — the IEC program and tag values — can change live, the way you online-edit a traditional PLC. Because the program is data on a VM, a warm swap carries retained state (PID integrals, timers, counters) across by name and type; a failed compile leaves the running program untouched, so a typo can never fault the controller.

Enable it per controller (off by default — pushing logic is code execution on a control system):

srv := server.New(rt, server.Options{OnlineEdits: true})

Then from VS Code: Download Program to Controller warm-swaps your open program, Diff Program with Controller shows running-vs-workspace, and a status-bar indicator flags when the controller runs something other than the committed file.

Verified 29 Oct 2026, 11:50 UTC on b3fbc32 · VS Code 1.139.1 · PASS · rig nightly · C01 on the proof page

Without a retain store, a restart reverts to the program the binary embeds. With retain: configured, online edits survive a restart (or a failover) of the same build: the edited source is saved and reloaded before the first scan. A deploy of a different build replaces them, as a download does on a PLC: when the binary’s program for a task has changed since the edit was made, the edit is discarded (logged, and named in the diagnostics as retainDiscardedEdits) and the deployed program runs. The discarded source is kept in the retain store under discardedPrograms.<task> (the last one per task, never applied), so an edit deployed over by mistake can still be recovered:

Terminal window
jq -r '.discardedPrograms.main' retain.json # file store
kubectl get configmap <name>-retain -o jsonpath='{.data.state}' \
| jq -r '.discardedPrograms.main' # in a cluster

Pull an edit into git before you deploy over it; committing the program to git is the only way an edit becomes permanent. The rule of thumb falls out of the two planes: logic you want to tune online, write in IEC; infrastructure, write in Go.

Tag values are the other half of the hot plane: Set Live Value writes a tag once, and a force holds one against the field and the logic until it is removed — see Forcing, which also covers jumping a running SFC chart to a step.

Pulling a field edit back to git closes the loop. Pull Program from Controller (VS Code) or naut pull --host <controller> writes the running program back into your program file — the inverse of download — so you review it with git diff and commit. Only the program file is rewritten; generated type files are never touched. naut pull --check reports drift and exits non-zero, so CI can fail a build when a controller has un-pulled edits. Composition is a single definition shared by the runtime, the language server, download, and pull, so a program round-trips losslessly.

In VS Code, Pull previews the change and asks before writing. If the program file is open, the pull goes through its editor — the text and any diagram view show the controller’s program, and the file is saved — so the editor and the disk never disagree. If the file has unsaved edits, the confirmation says so: Overwrite replaces them with the controller’s program, and Show Diff puts the controller’s program beside your buffer instead, so you can take lines across by hand and pull nothing.

Verified 29 Oct 2026, 11:50 UTC on b3fbc32 · VS Code 1.139.1 · PASS · rig nightly · C09 on the proof page

What a download sends is the program’s composition: the project’s library prelude — every PROGRAM-less .st file in the root and under lib/, verbatim, then every PROGRAM-less .ld / .fbd file transpiled to ST — then the program file as written (a .ld program stays ladder; the controller transpiles it and reports the original). VS Code asks the CLI for it rather than re-deriving it, so a program that calls a block written in ladder downloads with that block; it needs naut 0.13.0 or newer. To see exactly what a download of a program will send, run naut compose <program file> (--json splits it into prelude, program, POU, and library paths).

All of this — live values, online edits, pull — works over the network, not just against a local process. A scaffolded controller binds loopback by default; set NAUTILUS_ADDR=0.0.0.0:8080 to expose the tag API to other machines, and point the editor at it with the nautilus.runtimeUrl setting (naut pull takes --host). Exposing the API on the network also exposes its write surface, so set NAUTILUS_TOKEN on the controller and the matching nautilus.token in the editor — reads and naut pull stay open, but tag writes and online edits then require the token.

Programs are addressed by POU name — PROGRAM <Name> is a program’s identity. GET /api/program lists the resource’s programs; a PUT routes automatically by the POU name in the submitted source, and ?pou= / ?task= select one explicitly for GET/rollback. In VS Code that means a workspace with one program file per task Just Works: open the file, Download/Diff/ Rollback target that task’s program, retained state carries across the swap. And naut pull reconciles the whole resource: every controller program pulls back into the workspace file declaring its POU (a new program lands in <POU>.st/.fbd), so a field edit to any task is reviewable and committable — --check fails CI on drift in any of them.

The controller also carries its own git provenance: every commit that touched the project, reviewable as diffs and warm-swappable as a whole — “run the logic from three commits ago” is one request, with the same gates, state migration, and rollback as any online edit. See Program history.