Tasks and Runs
A Task is work you write once. A Run is one execution of it. A worker is the application process that runs Task code.
Task
A Task is a handler function plus an ID. The handler takes a context and an input, and returns an output and an error. Input and output are JSON.
A worker registers its Tasks with the server. This code is from the hello example. It builds a worker with the worker ID hello and adds one Task, hello:
worker, err := runcoil.NewWorker(client, runcoil.WorkerID("hello"))
if err != nil {
return err
}
err = worker.NewTask("hello", runcoil.HandlerFunc(tasks.hello))A Task is identified by the worker ID and the Task ID together. The Task above is hello/hello.
Run
A Run is one execution of a Task. Something creates it: an invoke, a schedule, or a rerun. A Run has a status.
| Status | Meaning |
|---|---|
queued | The Run waits: for a worker, for a free slot, or for a retry delay. |
running | A worker holds the Run and runs the handler. |
succeeded | The handler returned an output. |
failed | The Run is final. It failed. |
A failed Run is final. To try again, rerun it. A rerun creates a new Run from any finished Run, from its start or from a Step you choose.
Worker and worker ID
A worker is the application process that runs Task code. It connects out to the server, registers its Tasks, and receives Runs. By default a worker runs up to 10 Runs at once. Change the limit with runcoil.Slots(n).
Every process that runs the same program uses the same worker ID. The server sends each Run to one ready worker with that worker ID.
WARNING
Registration replaces the Tasks and schedules stored for a worker ID. Two different programs must not share a worker ID.
When you deploy a new version, its registration replaces what the previous version stored for the worker ID.
A Run that has no worker
If no worker is ready, a Run stays queued. The server fails it after 24 hours by default, so no Run waits forever. The server setting is engine.worker_wait_limit.
A Task that no worker has registered is not found, and the invoke is refused with task_not_found.
To fail a Run that starts late, set runcoil.StartDeadline(d) on the Task. The server then fails a Run that has not started d after it was due.
Invoke a Task
A caller invokes a Task by worker ID and Task ID. The Quickstart shows the curl call. The Go SDK has the same call. This code is from the SDK README:
invocation, err := client.Invoke(ctx, runcoil.InvokeRequest{
WorkerID: "accounts",
TaskID: "create-account",
Input: onboardingInput{AccountID: "acct-ada", Name: "Ada", Email: "ada@example.com"},
IdempotencyKey: "onboarding/acct-ada",
})
if err != nil {
return err
}
run, err := client.WaitRun(ctx, runcoil.WaitRunRequest{RunID: invocation.RunID, IncludeSteps: true})
if err != nil {
return err
}
if run.Status == runcoil.RunFailed {
return fmt.Errorf("%s: %s", run.Failure.Code, run.Failure.Message)
}A failed Run is not an error. WaitRun returns it with a nil error and Status set to runcoil.RunFailed. Check Status before you read Output.
Idempotency keys
A repeat of an invoke with the same key and input returns the first Run, marked deduplicated. The same key with other input is refused with idempotency_conflict. The server holds a key for 24 hours by default.
If you leave IdempotencyKey empty, the SDK generates a key for that call. That key protects the call's own retries and nothing else. When your trigger can repeat, derive the key from the business identity, such as onboarding/acct-ada.
Retention
The server keeps a finished Run for 7 days after it finishes. A cleanup job runs every hour. Both are server settings: WORKER__CLEANUP__RUN_RETENTION and WORKER__CLEANUP__INTERVAL.
Shut down a worker
Pass Start a context that ends on Ctrl-C or SIGTERM. Then call Shutdown with a drain context. Shutdown closes the stream so the server stops sending Runs, then waits for running handlers.
If the drain context ends first, Shutdown cancels the handlers still running and gives their Runs back to the server. The server queues them again.
drainCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return worker.Shutdown(drainCtx)