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 aTask with associated units, transactions, and logs.
State Machine
Statuses
Transition Rules
BadRequestException. Same-status transitions are idempotent no-ops.
Status Recomputation
After eachTaskUnit 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 Circletransfer() 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
- 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:
- 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 longerassigned). 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:- Orchestrator catches the error.
TaskService.updateStatus(taskId, FAILED)is called (best-effort).- If the status update itself fails, a
TaskLogentry is written as fallback. - The original error is re-thrown to BullMQ for retry accounting.
Unit-Level Failure
When aTaskUnit is reported as FAILED:
- Unit status updated,
failedUnitscounter incremented. - Task status recomputed — typically transitions to
review. - A
TaskLogentry is written atERRORlevel.
Transaction-Level Failure
When aTaskTransaction poll returns failed:
- Transaction record updated with
errorReason. getTransactionAggregation()checks if all transactions are terminal.- If all terminal: task finalized based on completed/failed ratio.
Partial Success
Thepartial 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.