Skip to main content

Task System

The task system is the persistence and state management layer for all payment operations. Every operation — payroll, swap, bridge, FX, liquidity — is represented as a Task with associated units, transactions, and logs.

State Machine

Statuses

Transition Rules

Invalid transitions throw BadRequestException. Same-status transitions are idempotent no-ops.

Status Recomputation

After each TaskUnit report, TaskUnitService.recomputeTaskStatus() derives the next status:

Data Model

Task

Root entity. One per execution request.

TaskUnit

A discrete unit of work. For payroll: one batch of recipients. Legacy swap and liquidity tasks are single-step records, but both paths are disabled by default during the official StableFX cutover.

TaskTransaction

Tracks individual on-chain transactions. One record per Circle transfer() call.

TaskLog

Append-only audit log. Every state transition and significant event produces a log entry.

Retry Semantics

BullMQ Level

Task execution jobs:
  • 3 attempts with exponential backoff (1s base, 5s for bridge)
  • On permanent failure: job marked failed, task status set to failed
Transaction poll jobs:
  • 1 BullMQ attempt — the poller manages its own re-enqueue logic
  • Each poll checks Circle API, then either finalizes or re-enqueues with delay
  • Maximum poll attempts enforced by TransactionPollerService

Idempotency

OrchestratorService.executeTask() contains an idempotency guard:
This means:
  • If BullMQ retries a job that already executed, it is silently skipped.
  • If the worker crashes after marking in_progress, the retry will also skip (status is no longer assigned). Manual intervention is required.
TaskLogService.hasLogStep() provides deduplication at the log level — processors check before writing duplicate log entries.

Error Handling

Task-Level Failure

When an agent throws during execution:
  1. Orchestrator catches the error.
  2. TaskService.updateStatus(taskId, FAILED) is called (best-effort).
  3. If the status update itself fails, a TaskLog entry is written as fallback.
  4. The original error is re-thrown to BullMQ for retry accounting.

Unit-Level Failure

When a TaskUnit is reported as FAILED:
  1. Unit status updated, failedUnits counter incremented.
  2. Task status recomputed — typically transitions to review.
  3. A TaskLog entry is written at ERROR level.

Transaction-Level Failure

When a TaskTransaction poll returns failed:
  1. Transaction record updated with errorReason.
  2. getTransactionAggregation() checks if all transactions are terminal.
  3. If all terminal: task finalized based on completed/failed ratio.

Partial Success

The partial status is a terminal state. It indicates:
  • At least one transfer succeeded (has a txHash).
  • At least one transfer failed (has an errorReason).
  • The task cannot be retried as a whole — individual failed transfers require manual intervention or a new task.
Aggregation logic: