Skip to main content

How Actyx works

Actyx implements local-first cooperation, which in short means that it allows programs on different computers to work together — directly between those computers without the cloud.

The example process

This conceptual guide explains how Actyx works with a simple example process: A workpiece is processed by a machine, and then packaged by a robot:


The traditional programming model

Traditionally, you would write two apps, one dealing with each asset. These apps would then be connected to each other with a database or broker:


The local-twin programming model

With the Actyx Platform, you program the process as autonomous local twins, that is a digital twin that resides, perceives, and acts locally. These local twins publish and consume events, and develop states based on these events:


There are two different ways in which the business logic can be formulated:

  • You can use stateless logic; using the SDK, emit events as they occur in the real world and retrieve information using the Actyx Query Language
  • Otherwise, if the local twin models something where your application shall control or coordinate the progression through a sequence of different life cycle states than you can use machine-runner; please find its docs in the library documentation.

Stateless Local Twins

In the example above, the machine typically manages its own life cycle and the local twin statelessly follows suit:

// first declare tags, optionally with enforced event types
const machine = Tag('machine:4711')
const idle = Tag<{ idle: boolean }>('machineIdle')
const counter = Tag<{ goodPieces: number; scrapPieces: number }>('machineCounter')

machineConnector // assuming this is an EventEmitter
.on('idleStatus', (idle: boolean) => sdk.publish(machine.and(idle).apply({ idle })))
.on('counterUpdate', (goodPieces: number, scrapPieces: number) =>
sdk.publish(machine.and(counter).apply({ goodPieces, scrapPieces })),
.on('machined', (workPieceId: string, result: 'good' | 'scrap') =>
// WorkPieceTag introduced below
type: 'machined', machine: '4711', result,
error handling

The publish() calls return a Promise that you would typically attach an error handler to. We leave that out here to focus on the main part.

Obtaining the latest idle state of a machine is then only a matter of running the following AQL query:

const maybeIdle: { idle: boolean } | undefined = (await sdk.queryAql({
query: `
PRAGMA features := aggregate
FROM "machineIdle" & "machine:4711" AGGREGATE LAST(_)
.map(e => e.payload)

The local computing environment

After you have programmed the local twins, you create local computing environments using edge devices. The local computing environment provides the infrastructure necessary for running local twins. It is a hardware/software combination.

The hardware can be any mobile device, PLC, or PC running Linux, Android, Windows or Docker:

  • Tablets: Panasonic, Zebra, Samsung
  • PLCs: Phoenix, Beckhoff, Weidmüller
  • PCs: any

The software is Actyx. It runs on each device and acts as a decentralized infrastructure which provides data dissemination, data persistence, and runtimes.


In this example, you could deploy Actyx to a small industrial PC that is connected to the machine (or directly to the machine's PLC) and deploy Actyx to a small industrial PC that you connect to the robot.

Deployment of twins as apps

Twins are packaged into apps that are deployed to the edge devices. Apps are the unit of deployment and contain twins as well as code that interacts with them:

  • User interfaces for human interaction
  • Machine integrations (e.g. OPC UA, I/Os)
  • Software integrations (e.g. ERP, Cloud)


Local interaction

After you have deployed the apps to the edge devices running Actyx, Twins interact and cooperate locally:


Due to the local interaction of the twins, there is no dependency between environments.


Synchronization of local twins

When edge devices are connected, Actyx automatically synchronizes the twins in real-time:


The twins' history is consistent and forever accessible:


Add new twins to the process

To extend or scale the process, you simply add new local twins:


Where next?