# Architecture

#### Default setup flows

[![worker_architecture.png](https://usermanual.vtenext.com/uploads/images/gallery/2026-09/worker-architecture.png)](https://usermanual.vtenext.com/uploads/images/gallery/2026-09/worker-architecture.png)

The Worker is composed of three processes that work together:

#### 1. Router

**File:** `include/Services/Worker/Router.php`

The Router listens on a Unix socket for incoming connections. When a browser tab or a PHP script connects, the Router identifies who is connecting, decides what to do with the request, and forwards it to the Zygote. It also keeps track of all connected clients and can push live updates (Server-Sent Events) back to browsers.

- Process name: `vte-worker-router`
- Listens on a socket (Unix `cache_local/worker.sock` with systemd, or a TCP/IP address for cluster setups). The address is configured in `config.inc.php` via `$worker_socket_URL` and must match the path in the systemd socket unit.
- Manages client subscriptions for real-time push events

#### 2. Zygote

**File:** `include/Services/Worker/Zygote.php`

The Zygote manages a pool of worker processes. When the Router forwards a job, the Zygote forks a Consumer process to execute it. Up to 10 Consumers can run concurrently; if all are busy, the job is queued and executed when a slot becomes available.

- Process name: `vte-worker-zygote`
- Maximum concurrent Consumers: `CONSUMERS_MAX = 10` (configurable in Zygote.php)
- Queues excess jobs until a Consumer is free

#### 3. Consumer

**File:** `include/Services/Worker/Consumer.php`

The Consumer is a short-lived process that performs the actual work. It connects to the database, loads the user context of the person who requested the task, executes the requested method, and terminates when done. Each Consumer handles exactly one job and then exits.

- Process name: `vte-worker-consumer`
- Process isolation: a crash in one Consumer does not affect others
- Database connection is established fresh for each job

#### Execution

The Worker can be started through systemd socket activation or directly from the command line for testing and custom setups:

```bash
php -f include/Services/Worker/run.php
```

With systemd socket activation:

1. systemd creates the Unix socket (`cache_local/worker.sock`)
2. On the first connection, systemd launches `php run.php`
3. `run.php` loads the CRM environment (config, database, etc.)
4. `Worker::start()` calls `pairedFork()`, splitting into two processes: 
    - The parent becomes the Router (socket listener)
    - The child becomes the Zygote (consumer pool manager)
5. When a job arrives, the Zygote forks a Consumer to execute it
6. The Consumer runs the job and exits

---

### Communication Between Processes

The three processes communicate through **Unix pipes** created by `stream_socket_pair()`. This is faster and lighter than running a full HTTP server inside the Worker.

#### Client Types

There are two kinds of clients that can connect to the Worker:

<table id="bkmrk-client-how-it-connec" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 13.826%;">Client</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 52.801%;">How It Connects</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 33.3731%;">Used By</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 13.826%;">`Web`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 52.801%;">Browser → Apache → `modules/Utilities/Worker.php` (handles web auth) → socket → Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 33.3731%;">Browser tabs (AI chat, notifications). Each web connection holds an Apache process slot.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 13.826%;">`Script`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 52.801%;">PHP code → `ScriptConnector` → Unix socket → Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 33.3731%;">CLI scripts, cron jobs, AJAX handlers</td></tr></tbody></table>

#### For Web Clients (SSE)

When a browser connects, the Router upgrades the connection to **Server-Sent Events (SSE)**. This allows the Consumer to push data back in real-time — for example, streaming an AI response word by word, or sending a notification as soon as it is created.

Relevant files: `WebConnector.php` (client side), `WebHandler.php` (server side), `SharedWorker.js` (browser — optional, reduces connections to one per browser).

#### For Script Clients

PHP code connects using `ScriptConnector` with a 30-second I/O timeout (long enough for background tasks). The connection supports both synchronous calls (wait for response) and asynchronous calls (fire and forget).

Relevant files: `ScriptConnector.php` (client side), `ScriptHandler.php` (server side).

#### Internal Protocol

Messages use a simple text-based format over the socket. Each message is a JSON object with an event type (`init`, `call`, `return`, `error`, `event`) and event-specific data. The `ProtocolTrait` handles encoding and decoding on both sides.

---

### Available Tasks

These are the methods the Worker can execute. Consumer methods are registered in `ScriptMethodsTrait` (`Methods.php`) and implemented in `Consumer.php`. Worker methods execute directly in the Zygote (no Consumer fork).

<table id="bkmrk-method-where-execute" style="width: 100%; border-collapse: collapse; margin: 12px 0;"><tbody><tr style="background: #eee;"><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 21.3349%;">Method</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 17.5331%;">Where Executed</th><th style="padding: 8px; border: 1px solid rgb(204, 204, 204); text-align: left; width: 61.132%;">Description</th></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`llmChat`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Send a message to an AI assistant (Agent, LLM, or External WebService). Streams the response back to the browser via SSE.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`elaborateRag`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Upload selected CRM documents to the external AI orchestrator and build the vector database for RAG retrieval.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`delegateProcess`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Execute a BPMN workflow process (ProcessMaker) in the background.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`resumeProcesses`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Resume queued workflow processes. Runs at Worker startup and on demand.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`sendToAll`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Push a custom event to a specific user or to all connected browser sessions.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`notifyNow`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Consumer</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Send a CRM notification to a specific user in real-time.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`workerStats`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Zygote + Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Returns uptime, number of active consumers, queued jobs.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`workerReload`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Reload the worker configuration from database.</td></tr><tr><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 21.3349%;">`workerRestart`</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 17.5331%;">Router</td><td style="padding: 8px; border: 1px solid rgb(204, 204, 204); width: 61.132%;">Restarts the entire Worker (Router + Zygote + all Consumers). Works independently of how the Worker was started (systemd or direct CLI), but only if the Worker is actually running.</td></tr></tbody></table>

---

### Adding a New Task

To add a new job that the Worker can execute, two files must be modified. Because Consumer processes load PHP classes after being forked from the Zygote, changes to existing methods take effect on the next consumer start without restarting the Router or Zygote. Adding a brand new method requires registering it in the trait (step 2) and may need a full restart only for the trait to be recognized.

#### Step 1: Implement the method in Consumer.php

Add your method inside the `//region Methods` section of `Consumer.php`:

```php
protected function convertPdf(int $documentId) {
    global $adb;
    // ... perform work ...
    $this->client->return($result);
}
```

Useful tools available inside the Consumer:

- `$this->client->return($data)` — send a successful response
- `$this->client->error("message")` — signal an error
- `$this->sendToAll(Roles::web, $userId, 'eventName', $data)` — push a live event to browsers
- `$this->later(function() { ... })` — schedule work for the next event-loop tick

#### Step 2: Register the method in Methods.php

Add the method signature to `ScriptMethodsTrait` in `Protocol/Methods.php`:

```php
/** @return void */
public function convertPdf(int $document_id) {
    return $this->call(__FUNCTION__, get_defined_vars(), ['wait' => false]);
}
```

#### Calling the new method

From PHP code (the connector is reused and reconnects on failure):

```php
$conn = ScriptConnector::reuseInstance();
if ($conn->tryConnect()) {
    $conn->convertPdf(42);
}
```

From the command line:

```bash
php -f include/Services/Worker/run.php call convertPdf 42
```

A method that belongs in `WorkerMethodsTrait` instead (executed by the Zygote without forking a Consumer) uses the same two-step process: add the signature to the trait and implement it in `Zygote.php` or `Router.php`.

---

### Broadcasting Events to the Browser

From any Consumer method, you can push real-time data to connected browsers:

```php
// Send to a specific user
$this->sendToAll(Roles::web, $userId, 'myEvent', ['progress' => 50]);

// Send to ALL users
$this->sendToAll(Roles::web, 0, 'myEvent', $data);
```

On the browser side, events are received through `SharedWorker.js` and `EventsClient.js`. The Router maintains a tree of connected clients indexed by user ID, session ID, and instance ID, and dispatches events to the matching tabs.

---

### File Reference

```mysql
include/Services/Worker/
├── run.php                          # Entry point (called by systemd)
├── Worker.php                       # Base Worker class + WorkerTrait
├── Router.php                       # Socket listener, client registry, SSE push
├── Zygote.php                       # Consumer pool manager, job queue
├── Consumer.php                     # Task executor (llmChat, elaborateRag, etc.)
├── utils.php                        # shared utilities
├── Protocol/
│   ├── Protocol.php                 # Wire protocol encode/decode
│   ├── Roles.php                    # Role constants (router, zygote, consumer, etc.)
│   ├── Methods.php                  # Method traits (ScriptMethods, WebMethods, WorkerMethods)
│   ├── ClientHandler.php            # Server-side connection handler
│   ├── BaseConnector.php            # Client-side connector base class
│   ├── ScriptHandler.php            # Handler for script connections
│   ├── ScriptConnector.php          # Connector for PHP scripts
│   ├── WebHandler.php               # Handler for web connections (SSE)
│   └── WebConnector.php             # Connector for browser (HTTP to socket bridge)
├── systemd/
│   ├── vte-worker@.service          # systemd service template
│   └── vte-worker@.socket           # systemd socket template
├── SharedWorker.js                  # Browser SharedWorker (multiplexes connections)
├── TabClient.js                     # Client for tab-to-tab messaging
└── EventsClient.js                  # Event subscription client in the browser

modules/Utilities/Worker.php         # HTTP bridge (Apache -> Worker socket)
tools/worker                         # CLI management tool
logs/worker.log                      # Log file
sse.php                              # Shim entrypoint, also for dedicated web servers configurations
```