> For the complete documentation index, see [llms.txt](https://engineering.umh.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://engineering.umh.app/engineering/development-process/how-to-build/coding-standards/architecture/control-loops.md).

# Control loops

How to control things with UMH.

Owner: Jeremy

"[The only reliable way to control anything is a loop](https://www.theocharis.dev/blog/the-only-reliable-way-to-control-anything-is-a-loop/)." Control loops are used everywhere, in all types of applications, when there is something that reliably needs to be controlled. For more background, read the full essay.

## Rules

* In UMH, we control anything only with control loops.
* Every cycle (one pass through the loop), the loop compares the desired state (the target it is given) with the observed state (what it measures).
* State machines run as [level-based](#edge-triggered-versus-level-based) loops. In every cycle they act on the observed state (level-based) instead of on single events (edge-triggered).

## What a control loop is

You have a setpoint. You have a sensor measuring whatever you have. You make the difference. It goes into a controller. Then you control some plant or system, where you can have disturbances.

For example, a room thermostat. You set a target temperature of 21 degrees, that is the setpoint. The thermometer in the room measures 18 degrees. You subtract: 21 minus 18, that is an error of 3. Then, based on some mathematics, the controller says: we need to heat more, set the heating to 80 percent.

The heating influences the room. Other things are happening, someone opens the window, so you have disturbances. Then you measure again with the thermometer, and you have a loop.

![Room thermostat](https://752401833-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FozTDfS43CDdASC9M0OwV%2Fuploads%2FLz2JptCelUOskksIZUf3%2Fthermostat.png?alt=media)

A PLC (programmable logic controller) works the same way. Every few milliseconds it reads all its inputs, runs the whole program from top to bottom, writes all its outputs and starts again, which is called the scan cycle.

## Desired state and observed state

**Kubernetes calls the desired state spec and the observed state status.** If you draw this as a diagram, you have seen it before.

![Reconcile loop](https://752401833-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FozTDfS43CDdASC9M0OwV%2Fuploads%2Ff1gPWYpM3HXct3KmLN5V%2Freconcile-loop.png?alt=media)

A control loop is the same thing as the thermostat, the same thing as the PLC on the production line. The spec is where the setpoint was. The status is where the sensor was.

You have the spec: replicas 3. The API server tells you the status: replicas 2. You calculate the error, one replica. That goes into the Kubernetes controller, which says: one pending pod.

Then you have your cluster, with node failures and other disturbances. Then you have two pods running, which get measured again, and so on. It is way more complex than this in reality, but this is how Kubernetes in its core works.

On the PLC it is called the scan cycle. In Kubernetes it is called the reconcile loop.

### The UMH loop

At UMH we use the same loop principle as well. You give umh-core the desired state in a config file: the data is flowing from this IP address. Then we observe it: no flow exists inside the Docker container, the flow is at zero messages per second.

So we calculate the difference, which means we need to add a data flow as its own process under S6, a process supervisor. Then you have disturbances: restarts, hardware failures, network failures, process failures. The observed state gets measured through `CollectObservedState`, and it goes in a loop.

![The UMH loop](https://752401833-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FozTDfS43CDdASC9M0OwV%2Fuploads%2Fsd3itlwRID3zSfb9ThRv%2Fumh-loop.png?alt=media)

Read: [Kubernetes, controllers](https://kubernetes.io/docs/concepts/architecture/controller/).

## Edge-triggered versus level-based

**Edge-triggered means you fire on the transition. Level-based means that whenever you look, the information is still there.**

Let's take the thermostat again. The code below is event-driven, edge-triggered. On a thermostat this looks quite stupid, but I have seen this logic pop up in a lot of programs. You take incoming events, and for each event you have a rule: if the window opened, set the heating to 80 percent; if the person left, set it to 60 percent. Every new cause needs its own rule.

But should one of those functions fail, the event is lost and we get a permanent drift.

```go
events := make(chan Event)

for event := range events {
	switch event {
		case WindowOpened:
			setHeating(80)
		case PersonLeft:
			setHeating(60)
		case SunCameOut:
			setHeating(30)
	}
}
```

Below, how a normal thermostat works: a loop. You read the current temperature, you get the desired temperature, and then a very simple logic: if current is below desired, increase the heating, otherwise decrease it. You can have a lot of CPU contention or other disruptions. It will only delay, but it will eventually reconcile.

```go
for {
	current := readTemperature()
	desired := getDesiredTemperature()

	if current < desired {
		increaseHeating()
	} else {
		decreaseHeating()
	}
}
```

Kubernetes goes through cycles. Every cycle it compares spec against status. If you miss a cycle, it does not matter; the next one fixes it.

Read: [Kubernetes, design principles](https://github.com/kubernetes/design-proposals-archive/blob/main/architecture/principles.md).

## After a restart

**After a restart, the loop fetches the current observed state again and acts on it.** It does not need to recover any state. If the loop needs to know something it cannot measure again, such as a command it already sent, that fact belongs in the observed state as well. `CollectObservedState` can fetch it from a cache.

## Control through choreography

**Kubernetes went for many small control loops. It calls this "control through choreography".**

![Control through choreography](https://752401833-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FozTDfS43CDdASC9M0OwV%2Fuploads%2F6b6JWy8sLImt6Khizjl9%2Fcontrol-through-choreography.png?alt=media)

On the left, Borg. In Borg it was all the single Borgmaster. At the top you give it "replicas: 3", and the master does everything.

On the right, Kubernetes. At the top you give it "replicas: 3". The Deployment controller reads the Deployment spec, does its own control loop, and sets the ReplicaSet spec. The ReplicaSet controller reads that spec, does its own control loop, and sets Pod objects. And so on.

When the Deployment controller crashes, the rest of the system keeps going. Nobody needs the Deployment controller to be alive. Everyone just reads their spec and reconciles.

In the factory you can also take the MES (manufacturing execution system) offline, and the line keeps running. For a while at least. Same as in Kubernetes: it keeps running as long as you do not need the logic of the loop that crashed because you have changed something, for example the number of replicas.

## Where we use it

* [FSMv2 workers](/engineering/development-process/how-to-build/coding-standards/architecture/fsmv2-workers.md): FSMv2 is the framework in umh-core that runs our control loops.
* [Data integrity](/engineering/development-process/how-to-build/coding-standards/architecture/data-integrity.md): process locking is a control task. It therefore runs as a loop.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://engineering.umh.app/engineering/development-process/how-to-build/coding-standards/architecture/control-loops.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
