POJO-actor Tutorial Part 2 (First Half): Workflow Language Basics
POJO-actor v1.x delivers a complete actor model foundation—ActorSystem, ActorRef with tell()/ask() messaging, virtual thread-based concurrency, and work-stealing pools—all in under 800 lines of code. Virtual threads enable even an ordinary laptop to handle tens of thousands of actors effortlessly. With no reflection and full GraalVM Native Image support, it turns any POJO into an actor without modification.
Version 2.x introduces a workflow engine that enables actors to become autonomous agents --- entities that observe their environment and act according to their state.
From Actor to Agent: Introducing the Workflow Engine
In the actor model, each actor has a mailbox (message queue). Each actor retrieves messages from its queue one at a time and processes them sequentially. Meanwhile, multiple actors can operate independently and concurrently. This "sequential internally, parallel externally" structure allows programmers to achieve concurrent processing without worrying about locks.
The actor model is intuitive for humans, so it inherently has various applications. However, traditionally, implementations where one actor occupies one OS thread were common. This constrained the number of actors to the number of CPU cores, making large-scale applications difficult. With Virtual Threads since JDK 21, this constraint is being lifted. Being able to handle tens of thousands of actors opens up applications such as Agent-Based Simulations with many agents, or infrastructure-as-code platforms that monitor and autonomously repair computing resources.
As a foundation for enabling actors to behave autonomously—that is, to act as Agents that observe their environment and act according to their state—a workflow engine has been added in POJO-actor v2.x.
An agent is anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators. — Russell & Norvig, "Artificial Intelligence: A Modern Approach"
actor-WF: Simplifying Workflows
POJO-actor's workflow (actor-WF) is described using a simple model of "send this message to this actor":
- states: ["start", "processed"]
actions:
- actor: dataProcessor # actor name
method: process # method name
arguments: ["data.csv"] # arguments
With just three elements—actor, method, and arguments—all steps can be expressed uniformly. This follows the same mental model as tell()/ask() in Java code, and since actors operate independently, parallel execution can be described naturally.
Workflow behavior, conditional branching, and loops are expressed as state transitions. actor-WF is essentially a Turing machine, and complex conditional branching that traditional YAML/JSON-based workflow languages struggle with can be expressed without introducing custom syntax.
How actor-WF Works
actor-WF is essentially a Turing machine. Here we reproduce examples from Turing's original paper using actor-WF.
The following workflow is a Turing machine that computes the binary representation of the rational number 1/3. It writes symbols "0 1 0 1 0 1..." alternately on the tape while cycling through states 1→2→3→4→5→1.

— Charles Petzold, "The Annotated Turing", Wiley Publishing, Inc. (2008)
name: turing83
steps:
- states: ["0", "1"]
actions:
- actor: turing
method: initMachine
- states: ["1", "2"]
actions:
- actor: turing
method: printTape
- states: ["2", "3"]
actions:
- actor: turing
method: put
arguments: "0"
- actor: turing
method: move
arguments: "R"
- states: ["3", "4"]
actions:
- actor: turing
method: move
arguments: "R"
- states: ["4", "5"]
actions:
- actor: turing
method: put
arguments: "1"
- actor: turing
method: move
arguments: "R"
- states: ["5", "1"]
actions:
- actor: turing
method: move
arguments: "R"
Workflow Behavior
A workflow has multiple Rows (combinations of states and actions) under steps. The Interpreter maintains a current state (initially "0") and operates as follows.
State Matching and Transitions
Each Row's states is in the format [from-state, to-state]. The Interpreter searches from top to bottom for a Row with a from-state matching the current state.
Let's look at the turing83 example:
- Initial state: current state =
"0" - First Row
states: ["0", "1"]matches - Execute
actions(initMachine) - On success, update current state to
"1" - Search for next matching Row →
states: ["1", "2"] - Continue cycling:
"1"→"2"→"3"→"4"→"5"→"1"→...
Action Execution and Conditional Branching
Each Row's actions are executed from top to bottom. Actions return either success (true) or failure (false).
- All succeed: Transition to to-state
- Failure midway: Abort this Row and try the next Row with the same from-state
This mechanism allows conditional branching by listing multiple Rows with the same from-state.
Termination Conditions
The workflow terminates under any of the following:
- Current state becomes
"end"(success) - No matching Row is found (failure)
- Maximum iteration count is reached (failure)
Since turing83 has no "end" state and is an infinite loop, it terminates when the maximum iteration count is reached.
Running the Example
Clone https://github.com/scivicslab/actor-WF-examples, then build and run in the actor-WF-examples directory (requires JDK 21 or later):
git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass="com.scivicslab.turing.TuringWorkflowApp" -Dexec.args="turing83"
Output:
Loading workflow from: /code/turing83.yaml
Workflow loaded successfully
Executing workflow...
TAPE 0 value
TAPE 0 value 0 1
TAPE 0 value 0 1 0 1
TAPE 0 value 0 1 0 1 0 1
TAPE 0 value 0 1 0 1 0 1 0 1
...
Workflow finished: Maximum iterations (50) exceeded
The pattern "0 1 0 1..." is generated on the tape. Since it's an infinite loop, it terminates when maxIterations (50) is reached.
A More Complex Example
The following is a Turing machine that outputs an irrational number. It outputs 001011011101111011111...

— Charles Petzold, "The Annotated Turing", Wiley Publishing, Inc. (2008)
Written in actor-WF:
name: turing87
steps:
- states: ["0", "100"]
actions:
- actor: turing
method: initMachine
- states: ["100", "1"]
actions:
- actor: turing
method: printTape
- states: ["1", "2"]
actions:
- actor: turing
method: put
arguments: "e"
- actor: turing
method: move
arguments: "R"
- actor: turing
method: put
arguments: "e"
- actor: turing
method: move
arguments: "R"
- actor: turing
method: put
arguments: "0"
- actor: turing
method: move
arguments: "R"
- actor: turing
method: move
arguments: "R"
- actor: turing
method: put
arguments: "0"
- actor: turing
method: move
arguments: "L"
- actor: turing
method: move
arguments: "L"
- states: ["101", "2"]
actions:
- actor: turing
method: printTape
- states: ["2", "2"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "1"
- actor: turing
method: move
arguments: "R"
- actor: turing
method: put
arguments: "x"
- actor: turing
method: move
arguments: "L"
- actor: turing
method: move
arguments: "L"
- actor: turing
method: move
arguments: "L"
- states: ["2", "3"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "0"
- states: ["3", "3"]
actions:
- actor: turing
method: isAny
- actor: turing
method: move
arguments: "R"
- actor: turing
method: move
arguments: "R"
- states: ["3", "4"]
actions:
- actor: turing
method: isNone
- actor: turing
method: put
arguments: "1"
- actor: turing
method: move
arguments: "L"
- states: ["4", "3"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "x"
- actor: turing
method: put
arguments: " "
- actor: turing
method: move
arguments: "R"
- states: ["4", "5"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "e"
- actor: turing
method: move
arguments: "R"
- states: ["4", "4"]
actions:
- actor: turing
method: isNone
- actor: turing
method: move
arguments: "L"
- actor: turing
method: move
arguments: "L"
- states: ["5", "5"]
actions:
- actor: turing
method: isAny
- actor: turing
method: move
arguments: "R"
- actor: turing
method: move
arguments: "R"
- states: ["5", "101"]
actions:
- actor: turing
method: isNone
- actor: turing
method: put
arguments: "0"
- actor: turing
method: move
arguments: "L"
- actor: turing
method: move
arguments: "L"
This example includes conditional branching using multiple Rows with the same from-state.
# From state 2: if current value is "1", stay in state 2
- states: ["2", "2"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "1"
# ... subsequent actions
# From state 2: if current value is "0", go to state 3
- states: ["2", "3"]
actions:
- actor: turing
method: matchCurrentValue
arguments: "0"
- If
matchCurrentValue("1")returnstrue→ Execute first Row, remain in state 2 - If
matchCurrentValue("1")returnsfalse→ Abort this Row, try next Row - If
matchCurrentValue("0")returnstrue→ Transition to state 3
Running the Example
git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass="com.scivicslab.turing.TuringWorkflowApp" -Dexec.args="turing87"
Output:
Loading workflow from: /code/turing87.yaml
Workflow loaded successfully
Executing workflow...
TAPE 0 value
TAPE 0 value ee0 0 1 0
TAPE 0 value ee0 0 1 0 1 1 0
TAPE 0 value ee0 0 1 0 1 1 0 1 1 1 0
TAPE 0 value ee0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0
Workflow finished: Maximum iterations (200) exceeded
Summary
What we covered in this tutorial:
- Basic workflow structure: Combinations of
statesandactions - State transitions: Control flow through transitions from from-state to to-state
- Conditional branching: Achieved by listing multiple Rows with the same from-state
- Loops: Achieved through state cycles (e.g., 1→2→3→4→5→1)
actor-WF is based on the same design philosophy as Turing machines. Any complexity of processing can be expressed through combinations of state transitions and actions.
Next Steps
To create your own workflows, you need to implement an adapter (IIActorRef) to call POJOs from workflows. See the following for details:
References
- Documentation: POJO-actor Docs
- GitHub: scivicslab/POJO-actor
- Javadoc: API Reference
- actor-WF-examples: https://github.com/scivicslab/actor-WF-examples
- POJO-actor v1.0 Introduction (blog): A Lightweight Actor Model Library for Java
