Skip to main content
Version: 2.0 prerelease

Activities

An activity is a unit of work that performs a specific task or operation (e.g. making an API request, processing data, sending an email) and can be executed by a workflow.

Durable Execution Contract

Ordinary v2 activities run as durable queued tasks. Local activities are an explicit same-process primitive for short activity work that still records durable activity history. Worker sessions are available when multiple durable activity steps need one worker lease, and sticky execution is a supported replay optimization, not a correctness contract. See Activity Execution Model, Local Activities, Worker Sessions, and Sticky Execution for the exact contracts.

You may use the make:activity artisan command to create a new activity:

php artisan make:activity MyActivity

It is defined by extending the Activity class and implementing the handle() method.

use Workflow\V2\Activity;

class MyActivity extends Activity
{
public function handle()
{
// Perform some work...
return $result;
}
}

Execution Contract

Every activity(...) call records an activity command on workflow history and creates a durable queued task for a worker to claim. Ordinary activities may be claimed by any compatible worker, while worker sessions can pin a sequence of activity attempts to one worker-session lease when the workflow explicitly opts into that contract.

If you need a replay-safe value without scheduling queued work, use sideEffect(...) instead. If you need a short retryable same-process activity with timeout, heartbeat, and activity history semantics, use Local Activities. For the full placement model, see Activity Execution Model. For sticky replay-cache behavior, see Sticky Execution.

Idempotency and Durable Identity

Activity execution is at-least-once. Retries, lease expiry, and redelivery can cause the same logical activity to be observed more than once, so the side effect or remote target must be safe to repeat.

Inside the activity, use the runtime's durable identifiers when you need correlation or remote dedupe:

  • activityId() identifies one logical activity execution across retries and is the default remote idempotency key.
  • attemptId() identifies one concrete try of that execution.
  • attemptCount() tells you which try is currently running.

Prefer activityId() when the remote system should treat retries as the same logical request. Reach for attemptId() only when the remote system truly needs to distinguish separate tries. If a worker finishes remote work, loses its lease, and reports late, the engine may reject that late completion because another worker already won the durable race, but the remote side effect may still have happened.

See Execution Guarantees and Idempotency, Heartbeats, and Failures and Recovery for the operational model behind those identifiers.

Per-Call Overrides

Routing and retries default to the activity class's own $connection, $queue, $tries, and backoff() properties. When a single call needs to override those — for example, routing one call to a higher-priority queue or giving it more retry attempts — pass an ActivityOptions instance:

use function Workflow\V2\activity;
use Workflow\V2\Support\ActivityOptions;

$result = activity(
MyActivity::class,
new ActivityOptions(queue: 'high-priority', maxAttempts: 5),
'Taylor',
);

See Activity options for the full list of fields, including timeouts and heartbeats.

Local Activity Calls

Use localActivity(...) when a short idempotent side effect should run inside the current workflow worker process but still needs activity retry, timeout, heartbeat, cancellation, and history semantics:

use Workflow\V2\Support\LocalActivityOptions;
use function Workflow\V2\localActivity;

$receipt = localActivity(
SendReceiptActivity::class,
new LocalActivityOptions(maxAttempts: 3, startToCloseTimeout: 10),
$orderId,
);

Local activities reject connection, queue, worker-session routing, and schedule-to-start options because they do not create ordinary activity tasks. See Local Activities for the complete contract.