Control which workers execute which tasks using sticky assignment and priority.
By default, Hatchet dispatches each task to any available worker that has registered the corresponding workflow. You can override this behavior to pin tasks to specific workers or control execution order.
Sticky assignment ensures that all tasks within a workflow run execute on the same worker. This is useful when your tasks share in-process state — for example, a loaded ML model or an in-memory cache — that would be expensive to reconstruct on a different worker.Set sticky on the workflow or standalone task definition. Two strategies are available:
Strategy
Behavior
StickyStrategy.SOFT
Prefer the same worker. Falls back to any available worker if the original is unavailable.
StickyStrategy.HARD
Require the same worker. The task will not run unless the original worker is available.
from hatchet_sdk import Context, EmptyModel, Hatchet, StickyStrategyhatchet = Hatchet(debug=True)sticky_workflow = hatchet.workflow( name="StickyWorkflow", # Specify a sticky strategy when declaring the workflow sticky=StickyStrategy.SOFT,)@sticky_workflow.task()def step1a(input: EmptyModel, ctx: Context) -> dict: return {"worker": ctx.worker.id()}@sticky_workflow.task()def step1b(input: EmptyModel, ctx: Context) -> dict: return {"worker": ctx.worker.id()}
When sticky=StickyStrategy.SOFT is set on the workflow, Hatchet will try to route all tasks in the run to the same worker that picked up the first task.
You can propagate sticky assignment to child workflows spawned from within a task. Pass sticky=True when spawning the child to request that it run on the same worker as the parent.
Python
TypeScript
Go
worker.py
from hatchet_sdk import Context, EmptyModel, Hatchet, StickyStrategy, TriggerWorkflowOptionshatchet = Hatchet(debug=True)sticky_workflow = hatchet.workflow(name="StickyWorkflow", sticky=StickyStrategy.SOFT)sticky_child_workflow = hatchet.workflow(name="StickyChildWorkflow", sticky=StickyStrategy.SOFT)@sticky_workflow.task(parents=[step1a, step1b])async def step2(input: EmptyModel, ctx: Context) -> dict: # Run a child workflow on the same worker ref = await sticky_child_workflow.aio_run_no_wait( options=TriggerWorkflowOptions(sticky=True) ) await ref.aio_result() return {"worker": ctx.worker.id()}@sticky_child_workflow.task()def child(input: EmptyModel, ctx: Context) -> dict: return {"worker": ctx.worker.id()}
workflow.ts
import { StickyStrategy } from '@hatchet/v1';import { hatchet } from './hatchet-client';import { child } from '../child_workflows/workflow';export const sticky = hatchet.task({ name: 'sticky', retries: 3, sticky: StickyStrategy.SOFT, fn: async (_, ctx) => { // Specify a child workflow to run on the same worker const result = await child.run( { N: 1 }, { sticky: true }, ); return { result }; },});
main.go
func Sticky(client *hatchet.Client) *hatchet.StandaloneTask { sticky := client.NewStandaloneTask("sticky-task", func(ctx worker.HatchetContext, input StickyInput) (*StickyResult, error) { // Run a child workflow on the same worker childWorkflow := Child(client) childResult, err := childWorkflow.Run(ctx, ChildInput{N: 1}, hatchet.WithRunSticky(true)) if err != nil { return nil, err } var childOutput ChildResult err = childResult.Into(&childOutput) if err != nil { return nil, err } return &StickyResult{ Result: fmt.Sprintf("child-result-%s", childOutput.Result), }, nil }, ) return sticky}
For StickyStrategy.HARD, the task will fail with a scheduling error rather than fall back to another worker if the original worker is unavailable. Use SOFT unless your correctness requirements demand the same worker.
When multiple tasks are queued, Hatchet uses priority to determine which ones are dispatched first. Higher values win.Set a default priority on the workflow or task definition:
Python
TypeScript
Go
high_priority_workflow = hatchet.workflow( name="HighPriorityWorkflow", default_priority=3, # 1 (low) to 3 (high), default is 1)# Or on a standalone task@hatchet.task(default_priority=3)def urgent_task(input: EmptyModel, ctx: Context) -> dict: return {}
You can target a specific worker at the point of triggering a task run by passing desired_worker_labels (Python / TypeScript) or WithDesiredWorkerLabels (Go). The scheduler will prefer — or require, if required=True — a worker whose labels match.