<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged actor-model</title>
<link>https://coderlegion.com/tag/actor-model</link>
<description>Powered by Question2Answer</description>
<item>
<title>actor-IaC: POJO-actor Workflow Based Infrastructure as Code for Cluster Management (Part 1)</title>
<link>https://coderlegion.com/10616/actor-iac-pojo-actor-workflow-based-infrastructure-as-code-for-cluster-management-part-1</link>
<description>&lt;p&gt;actor-IaC is an Infrastructure as Code (IaC) tool built on top of the POJO-actor workflow engine.&lt;br&gt;
It uses actor-based workflows written in Java and YAML to manage and operate compute clusters in a scalable and traceable way.&lt;/p&gt;
&lt;p&gt;In this article (Part 1), we introduce actor-IaC through a practical example: collecting system information from multiple compute nodes in parallel.&lt;br&gt;
This tutorial demonstrates how POJO-actor workflows can be applied to real-world cluster management tasks.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;h3 id=&quot;why-use-pojo-actor-workflow-for-iac-&quot;&gt;Why Use POJO-actor Workflow for IaC?&lt;/h3&gt;
&lt;p&gt;In previous posts, we explained the design of a framework that easily enables asynchronous parallel processing by turning any POJO into an actor, and introduced the Turing Machine-based workflow engine built on top of it.&lt;/p&gt;
&lt;p&gt;In this post, we demonstrate that this workflow mechanism is effective even for practical, real-world applications. As a concrete example, we built a full-featured Infrastructure as Code tool called &quot;actor-IaC&quot; using the POJO-actor workflow engine.&lt;/p&gt;
&lt;h4 id=&quot;consistent-notation&quot;&gt;Consistent Notation&lt;/h4&gt;
&lt;p&gt;In POJO-actor Workflow, all operations are written in a consistent format: &quot;call a method on a POJO.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;actions:
  - actor: this
    method: executeCommand
    arguments: [&quot;hostname -f&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The granularity of operations depends on your POJO design. You can freely define methods ranging from executing a single shell command to complex deployment procedures. You're not constrained by DSL limitations.&lt;/p&gt;
&lt;h4 id=&quot;natural-conditional-branching&quot;&gt;Natural Conditional Branching&lt;/h4&gt;
&lt;p&gt;POJO-actor Workflow is based on state machines (Turing machines). Each step is defined as a pair of &quot;current state&quot; and &quot;next state&quot;, and conditional branching is expressed as steps with multiple possible transitions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;steps:
  - states: [&quot;check&quot;, &quot;success&quot;]
    when: &quot;result.exitCode == 0&quot;
    actions: [...]
  - states: [&quot;check&quot;, &quot;failure&quot;]
    when: &quot;result.exitCode != 0&quot;
    actions: [...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No special control syntax is needed. Branching can be described naturally as state transitions.&lt;/p&gt;
&lt;h3 id=&quot;the-actor-tree-a-mental-model-for-cluster-management&quot;&gt;The Actor Tree: A Mental Model for Cluster Management&lt;/h3&gt;
&lt;p&gt;In actor-IaC, actors corresponding to each node defined in the inventory file are automatically generated.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IIActorSystem
├── nodeGroup                # Group actor that manages all nodes
│   ├── node-node13          #   Corresponds to each node in inventory
│   ├── node-node14          #   (Number of nodes depends on inventory.ini)
│   └── ...
├── outputMultiplexer        # Aggregates output from all nodes
├── loader                   # Dynamic plugin loading
└── logStore                 # Log DB writing (when --log-db specified)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The advantage of this structure is that &lt;strong&gt;when writing workflows, you only need to think about sequential processing on a single machine&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Sub-workflows describe only &quot;what to do on this one node.&quot; Parallelization is handled by &lt;code&gt;nodeGroup.apply()&lt;/code&gt;. You don't need to worry about synchronization or mutual exclusion across multiple nodes.&lt;/p&gt;
&lt;p&gt;Because the mental model is simple, workflow design, debugging, and maintenance all become easier.&lt;/p&gt;
&lt;h3 id=&quot;complete-traceability-of-execution-logs-cluster-operations-&quot;&gt;Complete Traceability of Execution Logs (Cluster Operations)&lt;/h3&gt;
&lt;p&gt;When the mental model is simple, checking logs also becomes easier.&lt;/p&gt;
&lt;p&gt;actor-IaC automatically saves execution logs from all nodes to an H2 database. Even logs from parallel execution can be filtered later to extract specific nodes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Show logs for node13 only
./actor_iac.java log-search --db ./actor-iac-logs --node node-node13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even in situations like &quot;1 out of 6 nodes failed. What happened?&quot;, you can immediately check the logs for that specific node.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;hands-on-collecting-cluster-information&quot;&gt;Hands-on: Collecting Cluster Information&lt;/h3&gt;
&lt;p&gt;Enough theory—let's actually use actor-IaC.&lt;/p&gt;
&lt;p&gt;When managing a cluster, you need to understand the configuration of each node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: How many cores? What model?&lt;/li&gt;
&lt;li&gt;Memory: How many GB?&lt;/li&gt;
&lt;li&gt;Disk: Capacity and usage?&lt;/li&gt;
&lt;li&gt;GPU: NVIDIA? AMD?&lt;/li&gt;
&lt;li&gt;Network: IP addresses?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manually checking this for 6 nodes is tedious. With actor-IaC, you can collect information from all nodes in parallel with a single command.&lt;/p&gt;
&lt;h4 id=&quot;1-inventory-file&quot;&gt;1. Inventory File&lt;/h4&gt;
&lt;p&gt;Define the target nodes. It uses the same INI format as Ansible.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[compute]
node13 actoriac_host=192.168.5.13
node14 actoriac_host=192.168.5.14
node15 actoriac_host=192.168.5.15
node21 actoriac_host=192.168.5.21
node22 actoriac_host=192.168.5.22
node23 actoriac_host=192.168.5.23

[compute:vars]
actoriac_user=youruser
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-sub-workflow-processing-executed-on-each-node-&quot;&gt;2. Sub-workflow (Processing Executed on Each Node)&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;collect-sysinfo.yaml&lt;/code&gt;. This is the workflow executed on each node.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: collect-sysinfo

steps:
  - states: [&quot;0&quot;, &quot;1&quot;]
    note: Get hostname and OS info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== HOSTNAME =====&quot;
            hostname -f
            echo &quot;===== OS INFO =====&quot;
            cat /etc/os-release | grep -E &quot;^(NAME|VERSION)=&quot;

  - states: [&quot;1&quot;, &quot;2&quot;]
    note: Get CPU info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== CPU INFO =====&quot;
            lscpu | grep -E &quot;^(Architecture|CPU\(s\)|Model name)&quot;

  - states: [&quot;2&quot;, &quot;3&quot;]
    note: Get memory info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== MEMORY INFO =====&quot;
            free -h

  - states: [&quot;3&quot;, &quot;4&quot;]
    note: Get disk info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== DISK INFO =====&quot;
            df -h | grep -E &quot;^(/dev|Filesystem)&quot;

  - states: [&quot;4&quot;, &quot;5&quot;]
    note: Get GPU info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== GPU INFO =====&quot;
            if command -v nvidia-smi &amp;amp;&amp;gt; /dev/null; then
                nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader
            else
                lspci | grep -i -E &quot;(vga|3d)&quot; || echo &quot;No GPU detected&quot;
            fi

  - states: [&quot;5&quot;, &quot;end&quot;]
    note: Get network info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== NETWORK INFO =====&quot;
            ip -4 addr show | grep -E &quot;(^[0-9]+:|inet )&quot; | head -20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this workflow is written as &lt;strong&gt;sequential processing for a single node&lt;/strong&gt;. There's no consideration for parallel execution.&lt;/p&gt;
&lt;h4 id=&quot;3-main-workflow-applying-to-all-nodes-&quot;&gt;3. Main Workflow (Applying to All Nodes)&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;main-collect-sysinfo.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: main-collect-sysinfo

steps:
  - states: [&quot;0&quot;, &quot;end&quot;]
    note: Execute sub-workflow in parallel on all nodes
    actions:
      - actor: nodeGroup
        method: apply
        arguments:
          actor: &quot;node-*&quot;
          method: runWorkflow
          arguments: [&quot;collect-sysinfo.yaml&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just one step. &lt;code&gt;nodeGroup.apply()&lt;/code&gt; executes the sub-workflow in parallel on all nodes matching the pattern &lt;code&gt;node-*&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;execution&quot;&gt;Execution&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./actor_iac.java run -w sysinfo/main-collect-sysinfo.yaml -i inventory.ini -g compute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When executed, it simultaneously connects via SSH to all 6 nodes and collects system information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[node-node13] ===== HOSTNAME =====
[node-node13] node13.local
[node-node14] ===== HOSTNAME =====
[node-node14] node14.local
[node-node13] ===== CPU INFO =====
[node-node13] Architecture:          x86_64
[node-node13] CPU(s):                32
[node-node13] Model name:            AMD Ryzen 9 5950X
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prefix like &lt;code&gt;[node-node13]&lt;/code&gt; at the beginning of each line indicates which node the output came from. Since execution is parallel, outputs arrive in mixed order, but you can distinguish them by the prefix.&lt;/p&gt;
&lt;h4 id=&quot;checking-results&quot;&gt;Checking Results&lt;/h4&gt;
&lt;p&gt;All logs are automatically saved to an H2 database (&lt;code&gt;actor-iac-logs.mv.db&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Show session list
./actor_iac.java log-search --db ./actor-iac-logs --list

# Show logs for a specific node
./actor_iac.java log-search --db ./actor-iac-logs --node node-node13
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;actor-iac-features&quot;&gt;actor-IaC Features&lt;/h3&gt;
&lt;p&gt;actor-IaC is not an experimental tool—it already has production-ready features.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Command &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;run&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Execute a workflow&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;list&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List available workflows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;describe&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Show workflow details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;log-search&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Search and display execution logs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;log-merge&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Merge distributed log DBs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;db-clear&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete log DB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The overlay feature allows you to use the same workflow across development, staging, and production environments.&lt;/p&gt;
&lt;p&gt;Plugin-based extensions are planned for the future.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;actor-IaC is the answer to &quot;Can POJO-actor Workflow express complex workflows?&quot;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Consistent notation&lt;/strong&gt;: A unified format of POJO method calls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural conditional branching&lt;/strong&gt;: State machine-based, no special syntax needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple mental model&lt;/strong&gt;: Just think about sequential processing on a single node&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete traceability&lt;/strong&gt;: All logs saved to DB, searchable anytime&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Part 2, we'll introduce how to format and analyze the collected information.&lt;/p&gt;
&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;You don't need a cluster to try actor-IaC. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-IaC-examples&quot;&gt;actor-IaC-examples&lt;/a&gt; repository includes examples that run on localhost:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install dependencies
git clone https://github.com/scivicslab/POJO-actor &amp;amp;&amp;amp; cd POJO-actor &amp;amp;&amp;amp; mvn install &amp;amp;&amp;amp; cd ..
git clone https://github.com/scivicslab/actor-IaC &amp;amp;&amp;amp; cd actor-IaC &amp;amp;&amp;amp; mvn install &amp;amp;&amp;amp; cd ..

# Clone examples and run
git clone https://github.com/scivicslab/actor-IaC-examples
cd actor-IaC-examples

# Hello World example
./actor_iac.java run -d ./hello -w main-hello -i localhost.ini -g all

# Collect system info from localhost
./actor_iac.java run -d ./sysinfo -w main-collect-sysinfo -i localhost.ini -g all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the repository's README for more examples and detailed instructions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;This article is part of the official actor-IaC documentation and tutorial series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Official Website&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com&quot;&gt;https://scivicslab.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Original Article&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/blog/2026-01-28-actor-iac-cluster-inventory&quot;&gt;https://scivicslab.com/blog/2026-01-28-actor-iac-cluster-inventory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/docs/actor-iac/introduction&quot;&gt;https://scivicslab.com/docs/actor-iac/introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-iac&quot;&gt;https://github.com/scivicslab/actor-iac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Reference (Javadoc)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.github.io/actor-IaC/&quot;&gt;https://scivicslab.github.io/actor-IaC/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor v1.0 Introduction (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;A Lightweight Actor Model Library for Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-1 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics?show=9131#q9131&quot;&gt;Workflow Language Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-2 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows?show=9132#q9132&quot;&gt;Creating Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-3 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation&quot;&gt;Improving the Workflow API — Introducing the @Action Annotation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10616/actor-iac-pojo-actor-workflow-based-infrastructure-as-code-for-cluster-management-part-1</guid>
<pubDate>Wed, 28 Jan 2026 06:47:58 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2-3: Improving the Workflow API — Introducing the @Action Annotation</title>
<link>https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation</link>
<description>&lt;p&gt;This article is a hands-on tutorial for the POJO-actor workflow API, focusing on the &lt;code&gt;@Action&lt;/code&gt; annotation introduced in POJO-actor v2.14.&lt;br&gt;
We explain how to define workflow-callable actions in Java actors without overriding &lt;code&gt;callByActionName()&lt;/code&gt;, using a practical example with conditional branching.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;p&gt;In Part 2-2, we implemented workflow actions by overriding callByActionName() and dispatching actions with a switch-case statement.&lt;br&gt;
While this approach works, the method grows quickly as the number of actions increases and becomes difficult to maintain.&lt;/p&gt;
&lt;p&gt;To solve this problem, POJO-actor v2.14 introduces the @Action annotation.&lt;br&gt;
With this annotation, workflow actions can be defined declaratively on actor methods, eliminating the need for explicit dispatch logic.&lt;/p&gt;
&lt;p&gt;In this tutorial, we reimplement the Turing machine example (turing87), which outputs an irrational binary sequence (0.001011011101111...), using the @Action-based workflow API.&lt;br&gt;
This example includes conditional branching and represents a more practical workflow scenario.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/2025-12-31-TutorialPart2-2&quot;&gt;Part 2-2&lt;/a&gt;, we explained how to extend &lt;code&gt;IIActorRef&lt;/code&gt; and override the &lt;code&gt;callByActionName()&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public ActionResult callByActionName(String actionName, String args) {
    switch (actionName) {
        case &quot;initMachine&quot;: ...
        case &quot;put&quot;: ...
        case &quot;move&quot;: ...
        default: return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but the &lt;code&gt;callByActionName()&lt;/code&gt; method grows larger as you add more workflow-callable actions.&lt;/p&gt;
&lt;p&gt;To solve this problem, POJO-actor v2.14 introduces the &lt;strong&gt;&lt;code&gt;@Action&lt;/code&gt; annotation&lt;/strong&gt;. You no longer need to explicitly write &lt;code&gt;callByActionName()&lt;/code&gt; — just annotate your methods.&lt;/p&gt;
&lt;p&gt;In this tutorial, we implement turing87 (a Turing machine that outputs the irrational number 0.001011011101111...) introduced in &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/2025-12-30-TutorialPart2-1&quot;&gt;Part 2-1&lt;/a&gt; using the &lt;code&gt;@Action&lt;/code&gt; annotation. This is a more practical example that includes conditional branching.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;h3 id=&quot;step-1-create-the-pojo-same-as-part-2-2-&quot;&gt;Step 1: Create the POJO (Same as Part 2-2)&lt;/h3&gt;
&lt;p&gt;The POJO requires no changes. It's a plain Java class that knows nothing about the workflow.&lt;/p&gt;
&lt;h4 id=&quot;turing-java&quot;&gt;Turing.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Turing {
    int currentPos = 0;
    Tape tape = new Tape();

    /** Initialize the machine */
    public void initMachine() {
        this.currentPos = 0;
        this.tape = new Tape();
    }

    /** Write a value to the tape */
    public void put(String value) {
        this.tape.setWithResizing(this.currentPos, value);
    }

    /** Move the head (&quot;L&quot;=left, &quot;R&quot;=right) */
    public void move(String direction) {
        if (direction.equalsIgnoreCase(&quot;L&quot;)) {
            this.currentPos--;
        } else if (direction.equalsIgnoreCase(&quot;R&quot;)) {
            this.currentPos++;
        }
    }

    /** Print the tape contents */
    public void printTape() {
        System.out.println(&quot;TAPE\t&quot; + this.tape.toString());
    }

    // Methods for conditional branching (used in turing87)

    /** Check if current position value matches expected */
    public boolean matchCurrentValue(String expected) {
        String current = this.tape.get(this.currentPos);
        return expected.equals(current);
    }

    /** Check if current position has any value */
    public boolean isAny() {
        String current = this.tape.get(this.currentPos);
        return current != null &amp;amp;&amp;amp; !current.isEmpty();
    }

    /** Check if current position is empty */
    public boolean isNone() {
        String current = this.tape.get(this.currentPos);
        return current == null || current.isEmpty();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-create-the-iiactorref-action-annotation-version&quot;&gt;Step 2: Create the IIActorRef — @Action Annotation Version&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;This is the main topic.&lt;/strong&gt; Let's first review the code from Part 2-2.&lt;/p&gt;
&lt;h4 id=&quot;part-2-2-code-switch-statement-version-&quot;&gt;Part 2-2 Code (switch statement version)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    private String parseFirstArg(String args) {
        if (args == null || args.isEmpty() || args.equals(&quot;[]&quot;)) {
            return &quot;&quot;;
        }
        JSONArray array = new JSONArray(args);
        return array.length() &amp;gt; 0 ? array.getString(0) : &quot;&quot;;
    }

    @Override
    public ActionResult callByActionName(String actionName, String args) {
        try {
            switch (actionName) {
                case &quot;initMachine&quot;:
                    this.tell(t -&amp;gt; t.initMachine()).get();
                    return new ActionResult(true, &quot;initialized&quot;);

                case &quot;put&quot;:
                    String putValue = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.put(putValue)).get();
                    return new ActionResult(true, &quot;put &quot; + putValue);

                case &quot;move&quot;:
                    String direction = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.move(direction)).get();
                    return new ActionResult(true, &quot;moved &quot; + direction);

                case &quot;printTape&quot;:
                    this.tell(t -&amp;gt; t.printTape()).get();
                    return new ActionResult(true, &quot;printed&quot;);

                case &quot;matchCurrentValue&quot;:
                    String expected = parseFirstArg(args);
                    boolean match = this.ask(t -&amp;gt; t.matchCurrentValue(expected)).get();
                    return new ActionResult(match, &quot;match=&quot; + match);

                case &quot;isAny&quot;:
                    boolean any = this.ask(t -&amp;gt; t.isAny()).get();
                    return new ActionResult(any, &quot;isAny=&quot; + any);

                case &quot;isNone&quot;:
                    boolean none = this.ask(t -&amp;gt; t.isNone()).get();
                    return new ActionResult(none, &quot;isNone=&quot; + none);

                default:
                    return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
            }
        } catch (Exception e) {
            return new ActionResult(false, &quot;Error: &quot; + e.getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To connect action names written in YAML workflow files to the Java program, we override &lt;code&gt;callByActionName()&lt;/code&gt; and dispatch actions using a switch statement.&lt;br&gt;
In turing87, we also need conditional branching actions (&lt;code&gt;matchCurrentValue&lt;/code&gt;, &lt;code&gt;isAny&lt;/code&gt;, &lt;code&gt;isNone&lt;/code&gt;), making the switch statement quite long.&lt;/p&gt;
&lt;h4 id=&quot;part-2-3-code-action-version-&quot;&gt;Part 2-3 Code (@Action version)&lt;/h4&gt;
&lt;p&gt;Rewriting the same functionality with the &lt;code&gt;@Action&lt;/code&gt; annotation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import static com.scivicslab.pojoactor.core.ActionArgs.*;
import com.scivicslab.pojoactor.core.Action;
import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorRef;
import com.scivicslab.pojoactor.workflow.IIActorSystem;

public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    @Action(&quot;initMachine&quot;)
    public ActionResult initMachine(String args) {
        this.object.initMachine();
        return new ActionResult(true, &quot;Machine initialized&quot;);
    }

    @Action(&quot;put&quot;)
    public ActionResult put(String args) {
        String value = getFirst(args);
        this.object.put(value);
        return new ActionResult(true, &quot;Put &quot; + value);
    }

    @Action(&quot;move&quot;)
    public ActionResult move(String args) {
        String direction = getFirst(args);
        this.object.move(direction);
        return new ActionResult(true, &quot;Moved &quot; + direction);
    }

    @Action(&quot;printTape&quot;)
    public ActionResult printTape(String args) {
        this.object.printTape();
        return new ActionResult(true, &quot;Tape printed&quot;);
    }

    // Conditional branching actions (used in turing87)

    @Action(&quot;matchCurrentValue&quot;)
    public ActionResult matchCurrentValue(String args) {
        String expected = getFirst(args);
        boolean match = this.object.matchCurrentValue(expected);
        return new ActionResult(match, &quot;match=&quot; + match);
    }

    @Action(&quot;isAny&quot;)
    public ActionResult isAny(String args) {
        boolean any = this.object.isAny();
        return new ActionResult(any, &quot;isAny=&quot; + any);
    }

    @Action(&quot;isNone&quot;)
    public ActionResult isNone(String args) {
        boolean none = this.object.isNone();
        return new ActionResult(none, &quot;isNone=&quot; + none);
    }

    // No need to override callByActionName()!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;You no longer need to write the &lt;code&gt;callByActionName()&lt;/code&gt; method.&lt;/strong&gt; Each action becomes an independent method, linked to the YAML via &lt;code&gt;@Action(&quot;actionName&quot;)&lt;/code&gt;. The base class uses reflection to discover &lt;code&gt;@Action&lt;/code&gt;-annotated methods and dispatches automatically.&lt;/p&gt;
&lt;h4 id=&quot;-action-method-requirements&quot;&gt;@Action Method Requirements&lt;/h4&gt;
&lt;p&gt;Methods annotated with &lt;code&gt;@Action&lt;/code&gt; must follow this format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Action(&quot;actionName&quot;)
public ActionResult methodName(String args) {
    // processing
    return new ActionResult(success/failure, message);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Return type&lt;/strong&gt;: &lt;code&gt;ActionResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter&lt;/strong&gt;: Single &lt;code&gt;String&lt;/code&gt; (YAML &lt;code&gt;arguments&lt;/code&gt; passed as JSON string)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visibility&lt;/strong&gt;: &lt;code&gt;public&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;argument-formats&quot;&gt;Argument Formats&lt;/h5&gt;
&lt;p&gt;YAML &lt;code&gt;arguments&lt;/code&gt; can be written in three formats:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;YAML notation &lt;/th&gt;&lt;th&gt;Value passed to &lt;code&gt;args&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Parse example&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: &quot;value&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[&quot;value&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONArray(args).getString(0)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: [&quot;a&quot;, &quot;b&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[&quot;a&quot;, &quot;b&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONArray(args)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: {key: &quot;value&quot;}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;{&quot;key&quot;: &quot;value&quot;}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONObject(args)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;args&lt;/code&gt; parameter receives a JSON string. Use &lt;code&gt;ActionArgs.parse()&lt;/code&gt; for unified access.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionArgs;

@Action(&quot;example&quot;)
public ActionResult example(String args) {
    var p = ActionArgs.parse(args);  // Parse in one line

    // For array format (arguments: &quot;value&quot; or arguments: [&quot;a&quot;, &quot;b&quot;])
    String first = p.get(0);
    String second = p.get(1);

    // For object format (arguments: {host: &quot;server&quot;, port: 8080})
    String host = p.get(&quot;host&quot;);
    int port = p.getInt(&quot;port&quot;, 80);  // With default value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-create-the-yaml-workflow&quot;&gt;Step 3: Create the YAML Workflow&lt;/h3&gt;
&lt;p&gt;Define the workflow in YAML. POJO-actor v2.14 allows you to write actions concisely using &lt;strong&gt;array format&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;array-format-vs-object-format&quot;&gt;Array Format vs Object Format&lt;/h4&gt;
&lt;p&gt;Traditional object format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- actor: turing
  method: put
  arguments: &quot;e&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Array format (introduced in v2.14):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- [turing, put, &quot;e&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Array elements are in order: &lt;code&gt;[actorName, methodName, arguments]&lt;/code&gt;. If there are no arguments, just &lt;code&gt;[actorName, methodName]&lt;/code&gt; is fine.&lt;/p&gt;
&lt;h4 id=&quot;turing87-yaml&quot;&gt;turing87.yaml&lt;/h4&gt;
&lt;p&gt;turing87 uses conditional branching to output the irrational number 0.001011011101111... Here is the state transition diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Turing87.png&quot; alt=&quot;Turing87 State Diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using array format makes it very concise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing87
steps:
- states: [&quot;0&quot;, &quot;100&quot;]
  actions:
  - [turing, initMachine]
- states: [&quot;100&quot;, &quot;1&quot;]
  actions:
  - [turing, printTape]
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - [turing, put, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;101&quot;, &quot;2&quot;]
  actions:
  - [turing, printTape]
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;1&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;x&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;0&quot;]
- states: [&quot;3&quot;, &quot;3&quot;]
  actions:
  - [turing, isAny]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - [turing, isNone]
  - [turing, put, &quot;1&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;4&quot;, &quot;3&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;x&quot;]
  - [turing, put, &quot; &quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;4&quot;, &quot;4&quot;]
  actions:
  - [turing, isNone]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;5&quot;, &quot;5&quot;]
  actions:
  - [turing, isAny]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;5&quot;, &quot;101&quot;]
  actions:
  - [turing, isNone]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Approximately 170 lines reduced to about 70 lines.&lt;/strong&gt; Array format greatly improves workflow readability.&lt;/p&gt;
&lt;h3 id=&quot;step-4-create-the-application-same-as-part-2-2-&quot;&gt;Step 4: Create the Application (Same as Part 2-2)&lt;/h3&gt;
&lt;p&gt;The application code is exactly the same. Just change the YAML filename.&lt;/p&gt;
&lt;h4 id=&quot;turingworkflowapp-java&quot;&gt;TuringWorkflowApp.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import com.scivicslab.pojoactor.workflow.Interpreter;
import java.io.InputStream;

public class TuringWorkflowApp {

    public static void main(String[] args) {
        String yamlPath = &quot;/code/turing87.yaml&quot;;
        new TuringWorkflowApp().runWorkflow(yamlPath);
    }

    public void runWorkflow(String yamlPath) {
        // 1. Create IIActorSystem
        IIActorSystem system = new IIActorSystem(&quot;turing-system&quot;);

        try {
            // 2. Create POJO
            Turing turing = new Turing();

            // 3. Wrap with IIActorRef and register with system
            TuringIIAR turingActor = new TuringIIAR(&quot;turing&quot;, turing, system);
            system.addIIActor(turingActor);

            // 4. Create Interpreter
            Interpreter interpreter = new Interpreter.Builder()
                    .loggerName(&quot;TuringWorkflow&quot;)
                    .team(system)
                    .build();

            // 5. Load YAML
            InputStream yamlStream = getClass().getResourceAsStream(yamlPath);
            interpreter.readYaml(yamlStream);

            // 6. Execute workflow (max 100 iterations)
            ActionResult result = interpreter.runUntilEnd(100);

            // 7. Display result
            System.out.println(&quot;Result: &quot; + result.getResult());

        } finally {
            // 8. Cleanup
            system.terminateIIActors();
            system.terminate();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;execution-result-same-as-part-2-2-&quot;&gt;Execution Result (Same as Part 2-2)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Loading workflow from: /code/turing87.yaml
Workflow loaded successfully
Executing workflow...

TAPE    C 0 1
TAPE    C C 0 1 1
TAPE    C C C 0 1 1 1
TAPE    C C C C 0 1 1 1 1
...

Workflow finished: Maximum iterations (100) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output pattern shows &quot;0 followed by an increasing number of 1s&quot; (C is a counter). The output is &lt;strong&gt;exactly the same&lt;/strong&gt; as Part 2-2. The only difference is the &lt;strong&gt;implementation method&lt;/strong&gt; of the IIActorRef.&lt;/p&gt;
&lt;h3 id=&quot;internal-mechanism-how-action-works&quot;&gt;Internal Mechanism: How @Action Works&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;callByActionName&lt;/code&gt; is called, the &lt;code&gt;IIActorRef&lt;/code&gt; base class processes in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Search for @Action methods&lt;/strong&gt; — Scans the wrapped object's (&lt;code&gt;this.object&lt;/code&gt;) class for methods annotated with &lt;code&gt;@Action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If found, invoke it&lt;/strong&gt; — Calls the method via reflection and returns the result&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If not found, fallback&lt;/strong&gt; — Tries subclass switch statements or built-in actions (like &lt;code&gt;putJson&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows &lt;strong&gt;switch-based code and @Action-based code to coexist&lt;/strong&gt;. Gradual migration is possible.&lt;/p&gt;
&lt;h3 id=&quot;summary-1&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The difference between Part 2-2 and Part 2-3 is &lt;strong&gt;only Step 2&lt;/strong&gt; (IIActorRef creation):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Step &lt;/th&gt;&lt;th&gt;Content &lt;/th&gt;&lt;th&gt;Part 2-2 &lt;/th&gt;&lt;th&gt;Part 2-3&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;Create POJO &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;Create IIActorRef &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Override &lt;code&gt;callByActionName()&lt;/code&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Just add &lt;code&gt;@Action&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;Create YAML &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4 &lt;/td&gt;&lt;td&gt;Create Application &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With the &lt;code&gt;@Action&lt;/code&gt; annotation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No need to override &lt;code&gt;callByActionName()&lt;/code&gt;&lt;/strong&gt; — Boilerplate disappears&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage by method&lt;/strong&gt; — Easy to add/remove actions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coexists with existing code&lt;/strong&gt; — Gradual migration possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Less boilerplate means cleaner, more readable code.&lt;/p&gt;
&lt;h3 id=&quot;try-it-yourself-1&quot;&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;All examples in this tutorial are available in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;actor-WF-examples&lt;/a&gt; repository. To run the &lt;code&gt;@Action&lt;/code&gt; annotation version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn install

cd ..
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing87-array --action&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the repository's README for detailed setup instructions and project structure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-1&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Official Manual&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/docs/pojo-actor/introduction&quot;&gt;https://scivicslab.com/docs/pojo-actor/introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub (POJO-actor)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/POJO-actor&quot;&gt;https://github.com/scivicslab/POJO-actor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub (Workflow examples)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maven Repository&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://mvnrepository.com/search?q=com.scivicslab&quot;&gt;https://mvnrepository.com/search?q=com.scivicslab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor v1.0 Introduction (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;A Lightweight Actor Model Library for Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-1 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics&quot;&gt;Workflow Language Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-2 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows&quot;&gt;Creating Workflows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation</guid>
<pubDate>Wed, 28 Jan 2026 06:29:19 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2 (Second Half): Creating Workflows</title>
<link>https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows</link>
<description>&lt;h3 id=&quot;recap-of-the-first-half&quot;&gt;Recap of the First Half&lt;/h3&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;../010_WorkflowBasics_241001_oo01/010_WorkflowBasics_241001_oo01_en.md&quot;&gt;first half&lt;/a&gt;, we introduced the basic concepts of the workflow engine (actor-WF) added in POJO-actor v2.x. actor-WF is described using a simple model of &quot;send this message to this actor,&quot; where all steps can be expressed uniformly with just three elements: actor, method, and arguments. Workflow behavior, conditional branching, and loops are expressed as state transitions, essentially having the same expressive power as a Turing machine.&lt;/p&gt;
&lt;p&gt;In the first half, we examined YAML workflow descriptions and state transition-based conditional branching using turing83 (a Turing machine that outputs the binary representation of 1/3) and turing87 (a Turing machine that outputs an irrational number) as examples. In this second half, we introduce how to implement IIActorRef, which is necessary for creating your own workflows.&lt;/p&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;This tutorial explains the complete process of making ordinary Java classes (POJOs) callable from workflows.&lt;/p&gt;
&lt;p&gt;Using turing83 (a Turing machine that outputs the binary representation of 1/3) as an example, we proceed through the following four steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create POJO&lt;/strong&gt;: An ordinary Java class with business logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create IIActorRef&lt;/strong&gt;: An adapter to call the POJO from workflows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create YAML&lt;/strong&gt;: The workflow definition&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;: Run the workflow with IIActorSystem&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[YAML] → [Interpreter] → [IIActorRef] → [POJO]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-1-creating-the-pojo&quot;&gt;Step 1: Creating the POJO&lt;/h3&gt;
&lt;p&gt;First, create an ordinary Java class containing the business logic you want to manipulate in the workflow. This class knows nothing about workflows. It's just a plain POJO.&lt;/p&gt;
&lt;h4 id=&quot;turing-java-excerpt-&quot;&gt;Turing.java (excerpt)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Turing {
    int currentPos = 0;
    Tape tape = new Tape();

    /** Initialize the machine */
    public void initMachine() {
        this.currentPos = 0;
        this.tape = new Tape();
    }

    /** Write a value to the tape */
    public void put(String value) {
        this.tape.setWithResizing(this.currentPos, value);
    }

    /** Move the head (&quot;L&quot;=left, &quot;R&quot;=right) */
    public void move(String direction) {
        if (direction.equalsIgnoreCase(&quot;L&quot;)) {
            this.currentPos--;
        } else if (direction.equalsIgnoreCase(&quot;R&quot;)) {
            this.currentPos++;
        }
    }

    /** Print the tape contents */
    public void printTape() {
        System.out.println(&quot;TAPE\t&quot; + this.tape.toString());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No knowledge of workflows or actors required&lt;/li&gt;
&lt;li&gt;Testable as an ordinary Java class&lt;/li&gt;
&lt;li&gt;Methods have simple inputs and outputs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-2-creating-the-iiactorref&quot;&gt;Step 2: Creating the IIActorRef&lt;/h3&gt;
&lt;p&gt;Next, create an adapter (&lt;code&gt;IIActorRef&lt;/code&gt;) to call the POJO from workflows. This class receives method names and arguments specified in YAML and invokes the actual POJO methods.&lt;/p&gt;
&lt;h4 id=&quot;turingiiar-java&quot;&gt;TuringIIAR.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorRef;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import org.json.JSONArray;

public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    // Parse arguments from JSON array: [&quot;0&quot;] → &quot;0&quot;
    private String parseFirstArg(String args) {
        if (args == null || args.isEmpty() || args.equals(&quot;[]&quot;)) {
            return &quot;&quot;;
        }
        JSONArray array = new JSONArray(args);
        return array.length() &amp;gt; 0 ? array.getString(0) : &quot;&quot;;
    }

    @Override
    public ActionResult callByActionName(String actionName, String args) {
        try {
            switch (actionName) {
                case &quot;initMachine&quot;:
                    this.tell(t -&amp;gt; t.initMachine()).get();
                    return new ActionResult(true, &quot;Machine initialized&quot;);

                case &quot;put&quot;:
                    String putValue = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.put(putValue)).get();
                    return new ActionResult(true, &quot;Put &quot; + putValue);

                case &quot;move&quot;:
                    String direction = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.move(direction)).get();
                    return new ActionResult(true, &quot;Moved &quot; + direction);

                case &quot;printTape&quot;:
                    this.tell(t -&amp;gt; t.printTape()).get();
                    return new ActionResult(true, &quot;Tape printed&quot;);

                default:
                    return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
            }
        } catch (Exception e) {
            return new ActionResult(false, &quot;Error: &quot; + e.getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;key-concepts&quot;&gt;Key Concepts&lt;/h4&gt;
&lt;h5 id=&quot;the-callbyactionname-method&quot;&gt;The callByActionName Method&lt;/h5&gt;
&lt;p&gt;The core part to implement when extending &lt;code&gt;IIActorRef&lt;/code&gt; is the &lt;code&gt;callByActionName&lt;/code&gt; method. The first argument &lt;code&gt;actionName&lt;/code&gt; receives the name specified in YAML's &lt;code&gt;method:&lt;/code&gt; (e.g., &lt;code&gt;&quot;put&quot;&lt;/code&gt;, &lt;code&gt;&quot;move&quot;&lt;/code&gt;), and the second argument &lt;code&gt;args&lt;/code&gt; receives the YAML's &lt;code&gt;arguments:&lt;/code&gt; converted to a JSON array string (e.g., &lt;code&gt;[&quot;0&quot;]&lt;/code&gt;, &lt;code&gt;[&quot;R&quot;]&lt;/code&gt;). Within this method, you call the appropriate POJO method based on actionName and return the result as an &lt;code&gt;ActionResult&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id=&quot;the-role-of-actionresult&quot;&gt;The Role of ActionResult&lt;/h5&gt;
&lt;p&gt;The return value &lt;code&gt;ActionResult&lt;/code&gt; controls workflow branching. Returning &lt;code&gt;ActionResult(true, message)&lt;/code&gt; is considered success, and the next action is executed, or if all actions succeed, transition to the to-state occurs. On the other hand, returning &lt;code&gt;ActionResult(false, message)&lt;/code&gt; is considered failure, and this Row is immediately aborted, and the next Row with the same from-state is tried. This mechanism enables conditional branching by listing multiple Rows with the same from-state.&lt;/p&gt;
&lt;h5 id=&quot;tell-and-ask-&quot;&gt;tell() and ask()&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;tell()&lt;/code&gt; is used for method calls that don't use the return value, and &lt;code&gt;ask()&lt;/code&gt; is used for method calls that use the return value (such as condition checking).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// tell: wait for result but don't use the value
this.tell(t -&amp;gt; t.put(&quot;0&quot;)).get();

// ask: use the result value
boolean result = this.ask(t -&amp;gt; t.matchCurrentValue(&quot;1&quot;)).get();
return new ActionResult(result, &quot;match=&quot; + result);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within IIActorRef, simply write &lt;code&gt;tell(action).get()&lt;/code&gt; or &lt;code&gt;ask(action).get()&lt;/code&gt;. You don't need to worry about which thread pool executes the action on the IIActorRef side. The Interpreter examines the Action class's &lt;code&gt;execution&lt;/code&gt; field (ExecutionMode) and selects the appropriate pool. ExecutionMode has two types: &lt;code&gt;POOL&lt;/code&gt; (execute on WorkStealingPool, default) and &lt;code&gt;DIRECT&lt;/code&gt; (direct call). Unless explicitly specified in YAML, POOL is used, so CPU-intensive processing is executed safely.&lt;/p&gt;
&lt;h3 id=&quot;step-3-creating-the-yaml-workflow&quot;&gt;Step 3: Creating the YAML Workflow&lt;/h3&gt;
&lt;p&gt;Once the POJO and IIActorRef are ready, describe the workflow in YAML.&lt;/p&gt;
&lt;h4 id=&quot;turing83-yaml&quot;&gt;turing83.yaml&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing83
steps:
- states: [&quot;0&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;yaml-structure&quot;&gt;YAML Structure&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Element &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;name&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Workflow name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;steps&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List of Rows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;states&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[from-state, to-state]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;actions&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List of actions to execute&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;actor&lt;/code&gt; &lt;/td&gt;&lt;td&gt;IIActorRef name (the name registered in Step 4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;method&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Action name passed to callByActionName&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Arguments passed to callByActionName (converted to JSON array)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;step-4-creating-the-execution-application&quot;&gt;Step 4: Creating the Execution Application&lt;/h3&gt;
&lt;p&gt;Finally, create an application that combines everything and executes it.&lt;/p&gt;
&lt;h4 id=&quot;turingworkflowapp-java-1&quot;&gt;TuringWorkflowApp.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import com.scivicslab.pojoactor.workflow.Interpreter;
import java.io.InputStream;

public class TuringWorkflowApp {

    public static void main(String[] args) {
        String yamlPath = &quot;/code/turing83.yaml&quot;;
        new TuringWorkflowApp().runWorkflow(yamlPath);
    }

    public void runWorkflow(String yamlPath) {
        // 1. Create IIActorSystem
        IIActorSystem system = new IIActorSystem(&quot;turing-system&quot;);

        try {
            // 2. Create POJO
            Turing turing = new Turing();

            // 3. Wrap with IIActorRef and register with system
            TuringIIAR turingActor = new TuringIIAR(&quot;turing&quot;, turing, system);
            system.addIIActor(turingActor);

            // 4. Create Interpreter
            Interpreter interpreter = new Interpreter.Builder()
                    .loggerName(&quot;TuringWorkflow&quot;)
                    .team(system)
                    .build();

            // 5. Load YAML
            InputStream yamlStream = getClass().getResourceAsStream(yamlPath);
            interpreter.readYaml(yamlStream);

            // 6. Execute workflow (max 50 iterations)
            ActionResult result = interpreter.runUntilEnd(50);

            // 7. Display result
            System.out.println(&quot;Result: &quot; + result.getResult());

        } finally {
            // 8. Cleanup
            system.terminateIIActors();
            system.terminate();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;execution-flow&quot;&gt;Execution Flow&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;1. Create IIActorSystem
       ↓
2. Create POJO (Turing)
       ↓
3. Wrap with IIActorRef (TuringIIAR)
   └─ Register with name &quot;turing&quot;
       ↓
4. Create Interpreter
   └─ Reference IIActorSystem
       ↓
5. Load YAML
   └─ actor: &quot;turing&quot; → Reference registered TuringIIAR
       ↓
6. Execute with runUntilEnd()
   └─ Repeat state transitions
       ↓
7. Finish
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-to-run&quot;&gt;How to Run&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Build POJO-actor
git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install

# Build and run actor-WF-examples
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing83&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
...

Workflow finished: Maximum iterations (50) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-2&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Steps to create a workflow:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Step &lt;/th&gt;&lt;th&gt;What to Create &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;POJO &lt;/td&gt;&lt;td&gt;Business logic (workflow-independent)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;IIActorRef &lt;/td&gt;&lt;td&gt;Bridge between YAML and POJO&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;YAML &lt;/td&gt;&lt;td&gt;Workflow definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4 &lt;/td&gt;&lt;td&gt;App &lt;/td&gt;&lt;td&gt;Assemble and execute everything&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This structure separates business logic (POJO) from workflow control (YAML), allowing each to be tested and modified independently.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Last updated&lt;/strong&gt;: 2025-12-31&lt;br&gt;
&lt;strong&gt;Related documents&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;../010_WorkflowBasics_241001_oo01/010_WorkflowBasics_241001_oo01_en.md&quot;&gt;Workflow Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;../../020_specs/020_workflow/025_IIActorRefSpecs_251231_oo01/&quot;&gt;IIActorRef Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows</guid>
<pubDate>Wed, 31 Dec 2025 10:51:18 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2 (First Half): Workflow Language Basics</title>
<link>https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics</link>
<description>&lt;h3 id=&quot;recap-of-part-1&quot;&gt;Recap of Part 1&lt;/h3&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;Part 1&lt;/a&gt;, we introduced the basic features of POJO-actor ver 1.x: &lt;code&gt;POJO-actor&lt;/code&gt; is a library that allows you to convert ordinary POJOs into actors simply by wrapping them with &lt;code&gt;ActorRef&amp;lt;T&amp;gt;&lt;/code&gt;, without modifying existing code. With Virtual Threads available since JDK 21, even commodity hardware can run tens of thousands of actors.&lt;/p&gt;
&lt;p&gt;Since most of this is implemented using standard JDK 21+ features, POJO-actor ver 1.x is realized in &lt;strong&gt;under 1000 lines of code&lt;/strong&gt;. POJO-actor ver 1.x was designed to explain the core implementation by implementing only the fundamental concepts.&lt;/p&gt;
&lt;p&gt;Version 2.x adds features for practical use.&lt;/p&gt;
&lt;h3 id=&quot;from-actor-to-agent-introducing-the-workflow-engine&quot;&gt;From Actor to Agent: Introducing the Workflow Engine&lt;/h3&gt;
&lt;p&gt;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 &quot;sequential internally, parallel externally&quot; structure allows programmers to achieve concurrent processing without worrying about locks.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;As a foundation for enabling actors to behave autonomously—that is, to act as &lt;strong&gt;Agents that observe their environment and act according to their state&lt;/strong&gt;—a &lt;strong&gt;workflow engine&lt;/strong&gt; has been added in POJO-actor v2.x.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;An agent is anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators.&lt;br&gt;
— Russell &amp;amp; Norvig, &quot;Artificial Intelligence: A Modern Approach&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;actor-wf-simplifying-workflows&quot;&gt;actor-WF: Simplifying Workflows&lt;/h3&gt;
&lt;p&gt;POJO-actor's workflow (actor-WF) is described using a simple model of &lt;strong&gt;&quot;send this message to this actor&quot;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  - states: [&quot;start&quot;, &quot;processed&quot;]
    actions:
      - actor: dataProcessor    # actor name
        method: process          # method name
        arguments: [&quot;data.csv&quot;]  # arguments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id=&quot;how-actor-wf-works&quot;&gt;How actor-WF Works&lt;/h3&gt;
&lt;p&gt;actor-WF is essentially a Turing machine. Here we reproduce examples from Turing's original paper using actor-WF.&lt;/p&gt;
&lt;p&gt;The following workflow is a Turing machine that computes the binary representation of the rational number 1/3. It writes symbols &quot;0 1 0 1 0 1...&quot; alternately on the tape while cycling through states 1→2→3→4→5→1.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1111576023169230765&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;— Charles Petzold, &quot;The Annotated Turing&quot;, Wiley Publishing, Inc. (2008)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing83
steps:
- states: [&quot;0&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;workflow-behavior&quot;&gt;Workflow Behavior&lt;/h4&gt;
&lt;p&gt;A workflow has multiple &lt;strong&gt;Rows&lt;/strong&gt; (combinations of &lt;code&gt;states&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt;) under &lt;code&gt;steps&lt;/code&gt;. The Interpreter maintains a current state (initially &lt;code&gt;&quot;0&quot;&lt;/code&gt;) and operates as follows.&lt;/p&gt;
&lt;h5 id=&quot;state-matching-and-transitions&quot;&gt;State Matching and Transitions&lt;/h5&gt;
&lt;p&gt;Each Row's &lt;code&gt;states&lt;/code&gt; is in the format &lt;code&gt;[from-state, to-state]&lt;/code&gt;. The Interpreter searches from top to bottom for a Row with a from-state matching the current state.&lt;/p&gt;
&lt;p&gt;Let's look at the turing83 example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Initial state&lt;/strong&gt;: current state = &lt;code&gt;&quot;0&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;First Row &lt;code&gt;states: [&quot;0&quot;, &quot;1&quot;]&lt;/code&gt; matches&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;actions&lt;/code&gt; (&lt;code&gt;initMachine&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;On success, update current state to &lt;code&gt;&quot;1&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Search for next matching Row → &lt;code&gt;states: [&quot;1&quot;, &quot;2&quot;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Continue cycling: &lt;code&gt;&quot;1&quot;&lt;/code&gt;→&lt;code&gt;&quot;2&quot;&lt;/code&gt;→&lt;code&gt;&quot;3&quot;&lt;/code&gt;→&lt;code&gt;&quot;4&quot;&lt;/code&gt;→&lt;code&gt;&quot;5&quot;&lt;/code&gt;→&lt;code&gt;&quot;1&quot;&lt;/code&gt;→...&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;action-execution-and-conditional-branching&quot;&gt;Action Execution and Conditional Branching&lt;/h5&gt;
&lt;p&gt;Each Row's &lt;code&gt;actions&lt;/code&gt; are executed from top to bottom. Actions return either success (true) or failure (false).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All succeed&lt;/strong&gt;: Transition to to-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failure midway&lt;/strong&gt;: Abort this Row and try the next Row with the same from-state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mechanism allows conditional branching by listing multiple Rows with the same from-state.&lt;/p&gt;
&lt;h5 id=&quot;termination-conditions&quot;&gt;Termination Conditions&lt;/h5&gt;
&lt;p&gt;The workflow terminates under any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current state becomes &lt;code&gt;&quot;end&quot;&lt;/code&gt; (success)&lt;/li&gt;
&lt;li&gt;No matching Row is found (failure)&lt;/li&gt;
&lt;li&gt;Maximum iteration count is reached (failure)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since turing83 has no &lt;code&gt;&quot;end&quot;&lt;/code&gt; state and is an infinite loop, it terminates when the maximum iteration count is reached.&lt;/p&gt;
&lt;h4 id=&quot;running-the-example&quot;&gt;Running the Example&lt;/h4&gt;
&lt;p&gt;Clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;, then build and run in the &lt;code&gt;actor-WF-examples&lt;/code&gt; directory (requires JDK 21 or later):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;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=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing83&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern &quot;0 1 0 1...&quot; is generated on the tape. Since it's an infinite loop, it terminates when maxIterations (50) is reached.&lt;/p&gt;
&lt;h3 id=&quot;a-more-complex-example&quot;&gt;A More Complex Example&lt;/h3&gt;
&lt;p&gt;The following is a Turing machine that outputs an irrational number. It outputs 001011011101111011111...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9233200913215203828&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;— Charles Petzold, &quot;The Annotated Turing&quot;, Wiley Publishing, Inc. (2008)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Written in actor-WF:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing87
steps:
- states: [&quot;0&quot;, &quot;100&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;100&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;101&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;x&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;0&quot;
- states: [&quot;3&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: isAny
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;4&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;x&quot;
  - actor: turing
    method: put
    arguments: &quot; &quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;5&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: isAny
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;101&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example includes &lt;strong&gt;conditional branching using multiple Rows with the same from-state&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# From state 2: if current value is &quot;1&quot;, stay in state 2
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
    - actor: turing
      method: matchCurrentValue
      arguments: &quot;1&quot;
    # ... subsequent actions

# From state 2: if current value is &quot;0&quot;, go to state 3
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
    - actor: turing
      method: matchCurrentValue
      arguments: &quot;0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;1&quot;)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; → Execute first Row, remain in state 2&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;1&quot;)&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; → Abort this Row, try next Row&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;0&quot;)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; → Transition to state 3&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;running-the-example-1&quot;&gt;Running the Example&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;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=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing87&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-3&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;What we covered in this tutorial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Basic workflow structure&lt;/strong&gt;: Combinations of &lt;code&gt;states&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State transitions&lt;/strong&gt;: Control flow through transitions from from-state to to-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditional branching&lt;/strong&gt;: Achieved by listing multiple Rows with the same from-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loops&lt;/strong&gt;: Achieved through state cycles (e.g., 1→2→3→4→5→1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;
&lt;p&gt;To create your own workflows, you need to implement an adapter (IIActorRef) to call POJOs from workflows. See the following for details:&lt;/p&gt;
&lt;h3 id=&quot;references-2&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/POJO-actor&quot;&gt;https://github.com/scivicslab/POJO-actor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actor-WF-examples&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics</guid>
<pubDate>Wed, 31 Dec 2025 10:48:21 +0000</pubDate>
</item>
</channel>
</rss>