Skip to main content

startOrEnqueueRun

@forge/monorepo


@forge/monorepo / backend/src / startOrEnqueueRun

Function: startOrEnqueueRun()

startOrEnqueueRun(coordinator, input): Promise<"queued" | "started">

Defined in: backend/src/runtime/serialization.ts:26

Start runId now if the conversation is free, else enqueue it FIFO. Returns whether it started. The claim-or-enqueue is atomic, so two runs racing to start the same conversation cannot both win, and a run can never slip into an idle-but-unclaimed slot and strand itself.

Parameters

coordinator

ConversationRunCoordinator

input

conversationId?

ConversationId

Absent for a run that belongs to no conversation — #198.

Then no slot is claimed at all, and the run is admitted directly. The SPEC first proposed a tenant-level slot for these; that was wrong and is corrected here. A tenant-level slot would serialise every automation a tenant owns — two unrelated webhooks would queue behind each other for no reason — and the reason there is a conversation slot in the first place is that turns in one conversation have an order a person can see. An automation has no such ordering requirement.

Concurrency for these runs is bounded where it should be: the worker's own limits, and quotas.

eventLog?

RunEventLog

The durable log, so admission is observable — #170.

run.queued was in RUN_EVENT_TYPES, mapped to a telemetry span, and mapped by the frontend reducer to the status queued — and nothing emitted it. So a client subscribing to a run saw nothing at all between sending a message and a worker picking it up: the one moment where "queued" is the only true thing to say, and the state the reducer had a case for could never be reached.

Emitted here rather than by each caller because admission is the event. Two hosts emitting their own would be two answers to "when was this queued", and the one that forgot would look like a hang.

Optional: a caller with no log still admits runs. Sequence 1 by definition — admission is the first thing that happens to a run, and the worker reconciles from the log before emitting, so its own first event continues from here rather than colliding.

now?

() => string

runId

RunId

tenantId

TenantId

Returns

Promise<"queued" | "started">