Skip to main content
Version: 2.0 prerelease

Workflow API

This page is the complete reference. For an easier to read version, please look at the individual feature page.

More Info for AI

Most readers can skip the disclosure below. Open it when you need exact signatures, return contracts, machine-operable notes, or the full API surface in one place.

More Info for AI

Base workflow object

use Workflow\V2\Workflow;

abstract class Workflow
{
public ?string $connection = null;
public ?string $queue = null;

public function workflowId(): string;
public function runId(): string;
public function lastChild(): ?ChildWorkflowHandle;
public function children(): array;
public function historyLength(): int;
public function historySize(): int;
public function shouldContinueAsNew(): bool;
}
MemberUse whenReturn contract
workflowId()The workflow needs its stable public instance id.Instance id string, unchanged across continue-as-new.
runId()The workflow needs the currently executing run id.Run id string for the selected execution.
lastChild()The workflow needs to signal the most recently spawned child.ChildWorkflowHandle or null.
children()The workflow needs handles for children visible to the current replay sequence.List of ChildWorkflowHandle.
historyLength()The workflow needs a count-based history budget signal.Current history event count.
historySize()The workflow needs a byte-based history budget signal.Approximate persisted history size in bytes.
shouldContinueAsNew()The workflow should rotate before history becomes expensive.true when configured history budgets recommend rotation.

Durable commands

The static facade delegates to namespaced helpers in Workflow\V2. The two forms are equivalent:

use Workflow\V2\Workflow;
use function Workflow\V2\activity;

$resultFromFacade = Workflow::activity(SendReceipt::class, $orderId);
$resultFromHelper = activity(SendReceipt::class, $orderId);
FacadeHelperSignatureDurable effect
Workflow::activity()activity()activity(string $activity, mixed ...$arguments): mixedSchedules an activity and waits for its result.
Workflow::executeActivity()activity()executeActivity(string $activity, mixed ...$arguments): mixedAlias for activity().
Workflow::localActivity()localActivity()localActivity(string $activity, mixed ...$arguments): mixedRuns a short activity in the current workflow worker process and records activity history with execution_mode=local.
Workflow::executeLocalActivity()localActivity()executeLocalActivity(string $activity, mixed ...$arguments): mixedAlias for localActivity().
Workflow::child()child()child(string $workflow, ChildWorkflowOptions? $options = null, mixed ...$arguments): mixedStarts a child workflow and waits for its result. Pass a ChildWorkflowOptions as the first argument to set the parent-close policy (default ParentClosePolicy::Abandon) or override child routing.
Workflow::executeChildWorkflow()child()executeChildWorkflow(string $workflow, ChildWorkflowOptions? $options = null, mixed ...$arguments): mixedAlias for child().
Workflow::async()async()async(callable $callback): mixedRuns a callable as an auto-generated child workflow.
Workflow::all()all()all(iterable $calls): mixedWaits for concurrent calls and returns results in iteration order.
Workflow::parallel()all()parallel(iterable $calls): mixedAlias for all().
Workflow::await()await()`await(callablestring $condition, int
Workflow::awaitWithTimeout()await()`awaitWithTimeout(intstring
Workflow::awaitSignal()await()awaitSignal(string $name): mixedWaits for a named signal.
Workflow::timer()timer()`timer(intstring
Workflow::sideEffect()sideEffect()sideEffect(callable $callback): mixedRecords a non-deterministic result in history and replays it.
Workflow::uuid4()uuid4()uuid4(): mixedGenerates a replay-stable UUIDv4.
Workflow::uuid7()uuid7()uuid7(): mixedGenerates a replay-stable UUIDv7.
Workflow::continueAsNew()continueAsNew()continueAsNew(mixed ...$arguments): mixedEnds the current run and starts a new run for the same instance.
Workflow::getVersion()getVersion()getVersion(string $changeId, int $minSupported = WorkflowStub::DEFAULT_VERSION, int $maxSupported = 1): mixedNegotiates a replay-safe workflow-code version.
Workflow::patched()patched()patched(string $changeId): mixedReturns whether the run crossed a named patch marker.
Workflow::deprecatePatch()deprecatePatch()deprecatePatch(string $changeId): mixedKeeps a patch marker alive after old code is removed.
Workflow::upsertMemo()upsertMemo()upsertMemo(array $entries): voidUpdates non-indexed run metadata.
Workflow::upsertSearchAttributes()upsertSearchAttributes()upsertSearchAttributes(array $attributes): voidUpdates indexed operator-visible metadata.
Workflow::now()now()now(): CarbonInterfaceReads deterministic workflow time.

activity() and executeActivity() schedule durable queued activity tasks. localActivity() and executeLocalActivity() run in the current workflow worker process and record normal activity history with the local marker. Use sideEffect() for replay-safe snapshots that should not use activity retry, timeout, heartbeat, or cancellation semantics. Use Workflow::workerSession() or Workflow\V2\workerSession() when multiple ordinary activity steps need the supported worker-session affinity contract. See Activity Execution Model, Local Activities, and Worker Sessions for the full execution contract.

Timer helpers

Timer helpers are shorthand for timer() and Workflow::timer():

use Workflow\V2\Workflow;

Workflow::seconds(30);
Workflow::minutes(5);
Workflow::hours(2);
Workflow::days(1);
Workflow::weeks(1);
Workflow::months(1);
Workflow::years(1);
HelperEquivalent
seconds(int $seconds)timer($seconds)
minutes(int $minutes)timer($minutes * 60)
hours(int $hours)timer($hours * 3600)
days(int $days)timer($days * 86400)
weeks(int $weeks)timer($weeks * 604800)
months(int $months)timer("{$months} months")
years(int $years)timer("{$years} years")

Use explicit timer() calls when the duration comes from configuration or workflow input. Use timer helpers when the source code should read as a fixed business wait.

Message streams

Open durable message streams from the workflow instance:

use Workflow\V2\MessageStream;
use Workflow\V2\Workflow;

final class AssistantWorkflow extends Workflow
{
public function handle(string $targetWorkflowId): array
{
$message = $this->inbox('ai.user')->receiveOne();

if ($message === null) {
return ['status' => 'waiting'];
}

$reply = $this->outbox('ai.assistant')->sendReference(
targetInstanceId: $targetWorkflowId,
payloadReference: 'app://payloads/reply-123',
correlationId: $this->workflowId(),
idempotencyKey: 'reply-123',
metadata: ['kind' => 'assistant_reply'],
);

return [
'status' => 'sent',
'stream' => $reply->stream_key,
'sequence' => $reply->sequence,
];
}
}
MethodSignatureContract
$this->messages()messages(?string $streamKey = null, ?MessageService $messages = null): MessageStreamOpens the stream for reading or sending.
$this->inbox()inbox(?string $streamKey = null, ?MessageService $messages = null): MessageStreamAlias for inbound authoring code.
$this->outbox()outbox(?string $streamKey = null, ?MessageService $messages = null): MessageStreamAlias for outbound authoring code.
MessageStream::key()key(): stringReturns the stream key.
MessageStream::cursor()cursor(): intReturns the durable cursor position for this run.
MessageStream::hasPending()hasPending(): boolReturns whether unconsumed messages exist on the stream.
MessageStream::pendingCount()pendingCount(): intReturns the number of unconsumed messages on the stream.
MessageStream::peek()peek(int $limit = 100): CollectionReads pending messages without consuming them.
MessageStream::receive()receive(int $limit = 1, ?int $consumedBySequence = null): CollectionReads and consumes messages, recording cursor advancement.
MessageStream::receiveOne()receiveOne(?int $consumedBySequence = null): ?WorkflowMessageReads and consumes one message.
MessageStream::sendReference()`sendReference(string $targetInstanceId, ?string $payloadReference = null, MessageChannelstring $channel = MessageChannel::WorkflowMessage, ?string $correlationId = null, ?string $idempotencyKey = null, array $metadata = [], ?DateTimeInterface $expiresAt = null): WorkflowMessage`

Use message streams for repeated ordered messages with cursor semantics. Use Signals for one-shot external events and Updates for request/return mutations.

Attributes and public contracts

use Workflow\QueryMethod;
use Workflow\UpdateMethod;
use Workflow\V2\Attributes\Signal;
use Workflow\V2\Attributes\Type;
use Workflow\V2\Workflow;

#[Type('order-approval')]
#[Signal('approved-by', [
['name' => 'approvedBy', 'type' => 'string', 'allows_null' => false],
])]
final class OrderApprovalWorkflow extends Workflow
{
private string $stage = 'waiting';

public function handle(): void
{
$this->stage = Workflow::awaitSignal('approved-by');
}

#[QueryMethod('current-stage')]
public function currentStage(): string
{
return $this->stage;
}

#[UpdateMethod('mark-ready')]
public function markReady(): string
{
return $this->stage = 'ready';
}
}
AttributeTargetStable contract
#[Type('type-key')]Workflow or activity classDeclares the language-neutral durable type key.
#[Signal('signal-name', [...])]Workflow class, repeatableDeclares accepted signal names and optional ordered parameter contracts.
#[QueryMethod('query-name')]Workflow methodDeclares a replay-safe query name. Omit the name to use the PHP method name.
#[UpdateMethod('update-name')]Workflow methodDeclares a replay-safe update name. Omit the name to use the PHP method name.

Signals, queries, and updates are public workflow contracts. Prefer explicit names so PHP method renames do not become API breaks.

Failure surface

Authoring API failures are durable workflow failures unless the command is rejected before execution:

SurfaceTypical failureOperator meaning
activity()Activity throws, times out, or exhausts retry policy.The run records activity failure history and follows workflow error handling.
child()Child workflow fails, cancels, terminates, or times out.The parent observes a child failure outcome at the waiting command.
await()Timeout elapses before the condition or signal is satisfied.The wait returns or fails according to the selected await form.
timer()Invalid duration input after normalization.Authoring code should pass positive durations or explicit zero-duration waits.
continueAsNew()New run cannot be created.Current run remains the evidence point for the failed transition.
upsertSearchAttributes()Attribute key, count, or total size exceeds limits.The run fails before invalid indexed metadata is persisted.
MessageStream::receive()No positive workflow history sequence is available.Receive must occur from workflow execution, not direct service code.
MessageStream::sendReference()Payload reference, route, or storage contract is invalid downstream.Message ordering remains separate from payload-store integrity.

For payload and history limits, see Structural Limits. For command rejection responses outside PHP workflow code, see Server API Reference.

Determinism rules

Workflow code must be replay-safe. Keep irreversible or non-deterministic work behind durable commands:

use Workflow\V2\Workflow;

final class DeterministicWorkflow extends Workflow
{
public function handle(): array
{
$workflowTime = Workflow::now();
$stableId = Workflow::uuid7();
$remoteQuote = Workflow::activity(FetchQuote::class);

return [
'time' => $workflowTime->toIso8601String(),
'id' => $stableId,
'quote' => $remoteQuote,
];
}
}
  • Use Workflow::now() instead of wall-clock time in workflow branches.
  • Use Workflow::uuid4() or Workflow::uuid7() instead of direct randomness.
  • Put network calls, filesystem writes, email sends, and external side effects in activities.
  • Use Workflow::sideEffect() only when the value must be captured in history and the side effect itself is not the business action.
  • Use Workflow::getVersion(), Workflow::patched(), and Workflow::deprecatePatch() to evolve workflow code without breaking replay.