DEV Community

Cover image for Building a Distributed AI Task Scheduler
Derek Mwale
Derek Mwale

Posted on

Building a Distributed AI Task Scheduler

There is a moment when an AI application stops being an application.

At first, it looks simple.

A user sends a prompt.

Your API receives it.

A model generates an answer.

You return the response.

Everything happens inside one request.

Then people start asking for more.

“Analyze this document.”

“Watch these prices every hour.”

“Summarize my inbox every morning.”

“Run this workflow when a customer signs up.”

“Research this topic using multiple sources.”

“Generate the report tonight.”

“Try again if the model fails.”

“Use the output from task A as the input to task B.”

Suddenly, your AI application is no longer just an API.

It is a distributed execution system.

And one of the most important pieces of that system is the scheduler.

A distributed AI task scheduler decides what should run, when it should run, where it should run, how many times it should run, what it depends on, and what happens when something goes wrong.

That sounds like a queue.

It isn't.

A queue answers:

“What should a worker process next?”

A scheduler answers a much larger question:

“What work exists in the system, what state is that work in, and what should happen next?”

That distinction becomes extremely important when AI workloads enter the picture.

AI tasks are unusual.

They can be slow.

They can be expensive.

They can be probabilistic.

They can consume enormous amounts of compute.

They can call external APIs.

They can spawn additional tasks.

They can depend on previous model outputs.

And they can fail in ways that traditional background jobs don't.

Building a distributed AI task scheduler is therefore not simply about putting jobs into Redis and starting workers.

It is about designing a control plane for intelligence.

And that is where the interesting engineering begins.


1. The Problem

Imagine we are building an AI platform.

A user asks the system:

“Research five competitors, compare their pricing, analyze their positioning, and create a report.”

That single request may become:

Research Request
       |
       v
+----------------+
| Create Tasks   |
+----------------+
       |
       +------> Research Competitor A
       |
       +------> Research Competitor B
       |
       +------> Research Competitor C
       |
       +------> Research Competitor D
       |
       +------> Research Competitor E
                    |
                    v
             Collect Results
                    |
                    v
              Analyze Data
                    |
                    v
              Generate Report
Enter fullscreen mode Exit fullscreen mode

We have already created several problems.

Which task runs first?

The competitor research tasks can run concurrently.

The analysis task cannot run until the research tasks finish.

The report cannot run until analysis finishes.

What happens if competitor C fails?

Do we retry it?

How many times?

What happens if the model provider is unavailable?

What happens if two workers accidentally execute the same task?

What happens if a worker crashes halfway through execution?

What happens if the scheduler itself crashes?

What happens if the user cancels the workflow?

What happens if 100,000 users submit workflows simultaneously?

And perhaps the most interesting question:

How do we prevent expensive AI tasks from overwhelming the infrastructure?

This is the real problem.

We need something like:

                 AI TASK SCHEDULER
                       |
       +---------------+---------------+
       |               |               |
    Planning        Scheduling       State
       |               |               |
   dependencies      priority       persistence
   DAGs              fairness       retries
   workflows         capacity      recovery
       |               |               |
       +---------------+---------------+
                       |
                       v
                  Task Queue
                       |
        +--------------+--------------+
        |              |              |
      Worker         Worker         Worker
        |              |              |
      LLM API        GPU Model      Tool API
Enter fullscreen mode Exit fullscreen mode

The scheduler becomes the brain of the execution infrastructure.


2. Start With a Task Model

Before thinking about Kubernetes, Redis, Kafka, Postgres, GPUs, or cloud infrastructure, define what a task actually is.

A task might look like this:

{
  "id": "task_98231",
  "workflow_id": "workflow_123",
  "type": "llm.generate",
  "priority": 70,
  "status": "pending",
  "attempt": 0,
  "max_attempts": 3,
  "created_at": "2026-09-17T20:00:00Z"
}
Enter fullscreen mode Exit fullscreen mode

But this is not enough.

We also need execution information.

{
  "id": "task_98231",
  "type": "llm.generate",
  "payload": {
    "model": "reasoning-model",
    "prompt": "Analyze the following...",
    "temperature": 0.2
  },
  "requirements": {
    "gpu": false,
    "memory_mb": 1024
  },
  "timeout_seconds": 300
}
Enter fullscreen mode Exit fullscreen mode

Now we have a useful abstraction.

A task has:

  • identity
  • type
  • payload
  • priority
  • state
  • retry policy
  • resource requirements
  • timeout
  • dependencies
  • execution history

The scheduler operates on these properties.


3. Task State Is the Real System

Many distributed systems become easier to understand once you stop thinking about processes and start thinking about state transitions.

For example:

PENDING
   |
   v
READY
   |
   v
RUNNING
   |
   +---------> SUCCEEDED
   |
   +---------> FAILED
   |
   +---------> RETRY_WAIT
   |
   +---------> CANCELLED
Enter fullscreen mode Exit fullscreen mode

This state machine matters.

Suppose a worker says:

RUNNING
Enter fullscreen mode Exit fullscreen mode

and then disappears.

The scheduler needs to determine:

Is the task still running?

Or:

Did the worker die?

This is why distributed systems need leases, heartbeats, timestamps, and recovery mechanisms.

A task should not simply be marked “running.”

It should have an execution lease.

For example:

task_id: task_98231

worker_id: worker_17

lease_expires_at:
2026-09-17T20:05:00Z
Enter fullscreen mode Exit fullscreen mode

The worker periodically renews the lease.

If it stops renewing:

lease expired
     |
     v
task considered abandoned
     |
     v
scheduler may retry
Enter fullscreen mode Exit fullscreen mode

This is one of the foundations of reliable distributed execution.


4. The Scheduler and the Queue Are Different

This distinction deserves its own section.

A queue might contain:

task_A
task_B
task_C
task_D
Enter fullscreen mode Exit fullscreen mode

A scheduler contains knowledge about:

task dependencies
task priorities
task deadlines
resource requirements
retry policies
workflow state
worker capacity
task history
Enter fullscreen mode Exit fullscreen mode

The queue is about delivery.

The scheduler is about decision-making.

Think about it like this:

                CONTROL PLANE
                     |
               Scheduler
                     |
          +----------+----------+
          |          |          |
       Priority   Capacity   Dependencies
          |          |          |
          +----------+----------+
                     |
                     v
                Task Queue
                     |
                     v
                 Workers
Enter fullscreen mode Exit fullscreen mode

This architectural distinction becomes especially important when workloads become large.


5. Represent Workflows as DAGs

AI workflows often form dependency graphs.

Suppose we want:

A = collect documents
B = summarize document 1
C = summarize document 2
D = summarize document 3

E = combine summaries
F = generate final report
Enter fullscreen mode Exit fullscreen mode

The dependency graph becomes:

          A
      /   |   \
     v    v    v
     B    C    D
      \   |   /
       \  |  /
        \ | /
          v
          E
          |
          v
          F
Enter fullscreen mode Exit fullscreen mode

This is a Directed Acyclic Graph.

DAGs are extremely useful because they give the scheduler a mathematical representation of the workflow.

A task becomes executable when all required predecessors have succeeded.

Formally:

ready(task) =
    all(dependency.status == SUCCEEDED)
Enter fullscreen mode Exit fullscreen mode

For task E:

ready(E) =
    B.success &&
    C.success &&
    D.success
Enter fullscreen mode Exit fullscreen mode

This is dramatically better than hard-coding workflow sequences.

The scheduler can calculate readiness dynamically.


6. Dependency Tracking

A relational representation might use:

CREATE TABLE tasks (
    id UUID PRIMARY KEY,
    workflow_id UUID NOT NULL,
    type TEXT NOT NULL,
    status TEXT NOT NULL,
    priority INTEGER DEFAULT 0,
    attempt INTEGER DEFAULT 0,
    max_attempts INTEGER DEFAULT 3,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Then:

CREATE TABLE task_dependencies (
    task_id UUID NOT NULL,
    depends_on UUID NOT NULL,
    PRIMARY KEY (task_id, depends_on)
);
Enter fullscreen mode Exit fullscreen mode

Now the scheduler can ask:

SELECT depends_on
FROM task_dependencies
WHERE task_id = :task_id;
Enter fullscreen mode Exit fullscreen mode

Then inspect the dependency states.

But there is a performance problem.

Imagine a workflow with 100,000 tasks.

Checking every dependency individually becomes expensive.

This leads us toward a better design.

Maintain a counter:

remaining_dependencies
Enter fullscreen mode Exit fullscreen mode

When a dependency completes:

remaining_dependencies -= 1
Enter fullscreen mode Exit fullscreen mode

When:

remaining_dependencies == 0
Enter fullscreen mode Exit fullscreen mode

the task becomes ready.

This changes dependency evaluation from repeated graph traversal into incremental state updates.

That is an important distributed-systems optimization.


7. Scheduling Is a Policy

Now we reach the heart of the system.

Suppose 10,000 tasks are ready.

Which one should execute first?

FIFO is easy:

oldest task first
Enter fullscreen mode Exit fullscreen mode

But AI systems usually need more nuance.

We could consider:

priority
deadline
tenant
cost
resource requirements
retry status
workflow importance
model availability
Enter fullscreen mode Exit fullscreen mode

A scheduling score might conceptually look like:

score =
    priority_weight * priority
  + age_weight * waiting_time
  + deadline_weight * urgency
  - cost_weight * estimated_cost
Enter fullscreen mode Exit fullscreen mode

We don't necessarily need to literally implement this equation.

The important insight is that scheduling is a policy engine.

For example:

Critical production task
        >
Interactive user task
        >
Scheduled workflow
        >
Batch analysis
        >
Low-priority experiments
Enter fullscreen mode Exit fullscreen mode

Different workloads can therefore coexist without fighting over the same infrastructure.


8. AI Makes Scheduling More Interesting

Traditional jobs might consume:

CPU
RAM
disk
network
Enter fullscreen mode Exit fullscreen mode

AI tasks might consume:

CPU
RAM
GPU
VRAM
model capacity
API quota
tokens
money
latency budget
Enter fullscreen mode Exit fullscreen mode

This changes everything.

Consider two tasks:

Task A
Model: large
VRAM: 40 GB
Expected tokens: 100,000
Estimated cost: $3

Task B
Model: small
VRAM: 4 GB
Expected tokens: 2,000
Estimated cost: $0.02
Enter fullscreen mode Exit fullscreen mode

If you only schedule based on queue order, you could accidentally allow Task A to block resources needed by hundreds of smaller tasks.

Therefore the scheduler needs resource-aware scheduling.

A task can declare:

{
  "resources": {
    "cpu": 2,
    "memory_mb": 4096,
    "gpu": 1,
    "vram_mb": 24576,
    "tokens": 12000
  }
}
Enter fullscreen mode Exit fullscreen mode

Workers advertise capabilities:

{
  "worker_id": "gpu-worker-12",
  "resources": {
    "cpu": 16,
    "memory_mb": 65536,
    "gpu": 2,
    "vram_mb": 49152
  }
}
Enter fullscreen mode Exit fullscreen mode

The scheduler matches them.

Now we are no longer building a simple task queue.

We are building a small resource allocation system.


9. Worker Registration

Workers should register themselves.

For example:

worker-1
    CPU: 8
    GPU: 0
    models:
      llama-small
      embedding-v2

worker-2
    CPU: 32
    GPU: 2
    models:
      llama-large
      vision-model
Enter fullscreen mode Exit fullscreen mode

The scheduler maintains worker heartbeats.

worker
   |
   | heartbeat
   v
scheduler
Enter fullscreen mode Exit fullscreen mode

If a heartbeat disappears:

worker unavailable
       |
       v
stop assigning new work
       |
       v
wait for leases to expire
       |
       v
recover abandoned tasks
Enter fullscreen mode Exit fullscreen mode

This creates fault tolerance without requiring the scheduler to know every detail about the worker's internal process.


10. Pull-Based Workers

There are two common models.

Push

The scheduler says:

Worker 17, execute Task 983.
Enter fullscreen mode Exit fullscreen mode

Pull

The worker says:

I am ready.
Give me work.
Enter fullscreen mode Exit fullscreen mode

Pull-based systems are often easier to scale.

A worker can request work according to its capacity:

POST /scheduler/claim
Enter fullscreen mode Exit fullscreen mode

The scheduler responds:

{
  "task_id": "task_98231",
  "lease_seconds": 60
}
Enter fullscreen mode Exit fullscreen mode

The worker processes it.

Then:

POST /tasks/task_98231/complete
Enter fullscreen mode Exit fullscreen mode

This architecture allows workers to naturally control how much work they accept.

That is valuable for AI workloads because GPUs and model servers often have very specific capacity constraints.


11. The Claim Problem

Suppose two workers ask for work simultaneously.

Both see:

task_98231 = READY
Enter fullscreen mode Exit fullscreen mode

If both claim it, we have duplicate execution.

Distributed systems have a beautiful phrase for this:

race condition.

We need atomic claiming.

With PostgreSQL, one strategy can use row locking:

SELECT *
FROM tasks
WHERE status = 'READY'
ORDER BY priority DESC, created_at ASC
FOR UPDATE SKIP LOCKED
LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

Then update:

UPDATE tasks
SET
    status = 'RUNNING',
    worker_id = :worker_id,
    lease_expires_at = :expiry
WHERE id = :task_id;
Enter fullscreen mode Exit fullscreen mode

The database becomes part of the coordination mechanism.

SKIP LOCKED is particularly useful because workers don't have to wait for rows currently being claimed by other workers.

This can produce a surprisingly capable scheduler without introducing a dozen distributed infrastructure components on day one.


12. But Exactly Once Is a Trap

Here is one of the most important lessons in distributed systems.

You will often hear:

“We need exactly-once execution.”

It sounds perfect.

But across networks, workers, databases, APIs, and external services, true exactly-once execution is extremely difficult.

Consider:

Worker executes task
        |
        v
LLM provider returns result
        |
        v
Worker sends "SUCCESS"
        |
        X
network failure
Enter fullscreen mode Exit fullscreen mode

The scheduler never receives the success message.

What does it believe?

RUNNING
Enter fullscreen mode Exit fullscreen mode

The lease eventually expires.

The scheduler retries.

Now the model call happens twice.

We have duplicate execution.

This is why practical systems usually aim for:

at-least-once execution + idempotency

rather than pretending distributed execution can magically become exactly once.


13. Idempotency

Give every task an execution identity.

For example:

workflow_id
task_id
attempt_id
Enter fullscreen mode Exit fullscreen mode

An external side effect can use an idempotency key:

workflow_123:task_42
Enter fullscreen mode Exit fullscreen mode

If the worker repeats the operation, the downstream system can recognize the key.

For example:

POST /payments

Idempotency-Key:
workflow_123:task_42
Enter fullscreen mode Exit fullscreen mode

The same principle can apply to:

  • database writes
  • document creation
  • email sending
  • billing
  • external API calls

AI applications frequently orchestrate side effects, so idempotency is not an optional luxury.

It is part of the architecture.


14. Retry Policies

Not every failure deserves a retry.

Consider:

Invalid prompt
Authentication failure
Rate limit
Network timeout
Provider outage
Model overload
Malformed response
Internal application bug
Enter fullscreen mode Exit fullscreen mode

Retrying an invalid prompt three times is pointless.

Retrying a temporary network failure makes sense.

So tasks should have retry policies.

{
  "retry_policy": {
    "max_attempts": 5,
    "backoff": "exponential",
    "initial_delay": 2,
    "max_delay": 120
  }
}
Enter fullscreen mode Exit fullscreen mode

A basic exponential backoff looks like:

delay = min(
    max_delay,
    initial_delay * 2^attempt
)
Enter fullscreen mode Exit fullscreen mode

So:

2s
4s
8s
16s
32s
Enter fullscreen mode Exit fullscreen mode

Add jitter:

delay = exponential_delay + random_jitter
Enter fullscreen mode Exit fullscreen mode

Why?

Because thousands of failed workers retrying simultaneously can create a thundering herd.

The system fails.

Then everyone retries.

The system gets hit again.

Then everyone retries again.

Backoff turns synchronized failure into distributed recovery.


15. Dead-Letter Queues

Eventually some tasks should stop retrying.

Suppose:

attempt 1 -> failure
attempt 2 -> failure
attempt 3 -> failure
attempt 4 -> failure
attempt 5 -> failure
Enter fullscreen mode Exit fullscreen mode

The scheduler should stop.

The task can move to:

DEAD_LETTER
Enter fullscreen mode Exit fullscreen mode

But dead-lettering should not mean:

“Forget the task.”

It should mean:

“This task requires investigation or a deliberate recovery decision.”

Store:

failure reason
stack trace
provider response
attempt history
worker
timestamps
input metadata
Enter fullscreen mode Exit fullscreen mode

Now operators can inspect what happened.

Observability begins at the task model.


16. Scheduled Tasks

Now let's add time.

A user says:

“Run this every day at 08:00.”

The scheduler needs to understand:

schedule
next_run_at
timezone
misfire policy
Enter fullscreen mode Exit fullscreen mode

A scheduled task might look like:

{
  "schedule": {
    "type": "cron",
    "expression": "0 8 * * *",
    "timezone": "Africa/Lusaka"
  }
}
Enter fullscreen mode Exit fullscreen mode

The scheduler periodically checks:

next_run_at <= now
Enter fullscreen mode Exit fullscreen mode

Then creates an execution instance.

This distinction is important:

Schedule
   |
   +---- Execution 1
   |
   +---- Execution 2
   |
   +---- Execution 3
Enter fullscreen mode Exit fullscreen mode

The schedule is not the execution.

The schedule defines when work should exist.

The task instance represents a particular execution.


17. Delayed Tasks

Not everything needs cron.

Sometimes:

Run this task 10 minutes from now.
Enter fullscreen mode Exit fullscreen mode

The scheduler can store:

run_at = now + 10 minutes
Enter fullscreen mode Exit fullscreen mode

Then maintain a priority structure ordered by time.

Conceptually:

2026-09-17 21:00 -> task A
2026-09-17 21:05 -> task B
2026-09-17 21:17 -> task C
Enter fullscreen mode Exit fullscreen mode

A scheduler tick can efficiently find tasks whose run_at has arrived.

At larger scale, a timing wheel or priority queue can reduce scanning overhead.

Again, the system becomes less like a web application and more like an operating system.


18. AI Workflows Can Spawn Work

This is where things become really interesting.

Imagine an agent receives:

“Research everything relevant to this company.”

The agent may decide:

search website
search news
search financial records
search competitors
analyze employees
Enter fullscreen mode Exit fullscreen mode

The original task dynamically generates more tasks.

We get:

Parent Task
    |
    +---- Child A
    +---- Child B
    +---- Child C
    +---- Child D
Enter fullscreen mode Exit fullscreen mode

The scheduler therefore needs to support dynamic task creation.

But this introduces danger.

What if an agent keeps generating tasks?

Task
  -> Task
      -> Task
          -> Task
              -> Task
Enter fullscreen mode Exit fullscreen mode

A poorly designed agent could accidentally create an infinite workload.

So the scheduler needs execution budgets.

For example:

max_tasks = 500
max_depth = 10
max_cost = $20
max_runtime = 30 minutes
max_tokens = 2,000,000
Enter fullscreen mode Exit fullscreen mode

Now the scheduler is enforcing computational boundaries.


19. AI Cost Becomes a Scheduling Dimension

Traditional schedulers mostly care about compute.

AI schedulers must care about money.

Suppose:

Workflow budget = $5
Enter fullscreen mode Exit fullscreen mode

Tasks estimate:

Task A = $0.10
Task B = $0.50
Task C = $1.20
Task D = $0.80
Enter fullscreen mode Exit fullscreen mode

The scheduler tracks:

estimated_cost
actual_cost
remaining_budget
Enter fullscreen mode Exit fullscreen mode

A task might be rejected if:

estimated_cost > remaining_budget
Enter fullscreen mode Exit fullscreen mode

Or delayed until a cheaper model is available.

This creates a new concept:

cost-aware scheduling

The scheduler isn't simply deciding whether the system has capacity.

It is deciding whether the system can afford to execute the work.


20. Multi-Tenant Scheduling

Imagine your platform has:

Customer A -> 10,000 tasks
Customer B -> 20 tasks
Customer C -> 300 tasks
Enter fullscreen mode Exit fullscreen mode

If you use a single FIFO queue:

Customer B waits behind Customer A
Enter fullscreen mode Exit fullscreen mode

This is bad.

One customer can dominate the system.

A distributed scheduler therefore needs fairness.

One strategy is weighted fair scheduling:

Customer A -> weight 5
Customer B -> weight 2
Customer C -> weight 3
Enter fullscreen mode Exit fullscreen mode

Or per-tenant quotas:

max_concurrent_tasks = 20
Enter fullscreen mode Exit fullscreen mode

Now each customer receives controlled access to shared resources.

This is especially important for SaaS AI systems.


21. Priority Is Not Enough

A naive scheduler might always choose:

highest priority first
Enter fullscreen mode Exit fullscreen mode

But this creates starvation.

Imagine:

High-priority tasks keep arriving.
Enter fullscreen mode Exit fullscreen mode

Then:

Low-priority task waits forever.
Enter fullscreen mode Exit fullscreen mode

A better system can use aging.

For example:

effective_priority =
    base_priority + waiting_time_factor
Enter fullscreen mode Exit fullscreen mode

A task becomes increasingly important as it waits.

This is a classic scheduling idea from operating systems.

AI infrastructure is rediscovering many ideas that operating systems solved decades ago.


22. Backpressure

Now imagine the scheduler receives:

100,000 tasks
Enter fullscreen mode Exit fullscreen mode

But the workers can process:

1,000
Enter fullscreen mode Exit fullscreen mode

Where do the other 99,000 go?

This is where backpressure matters.

The system should communicate capacity limitations upstream.

For example:

API
 |
 | submit
 v
Scheduler
 |
 | queue full
 X
Enter fullscreen mode Exit fullscreen mode

The API might return:

429 Too Many Requests
Enter fullscreen mode Exit fullscreen mode

or:

202 Accepted
Enter fullscreen mode Exit fullscreen mode

with a delayed execution guarantee.

Backpressure prevents the scheduler from becoming a giant memory leak with an HTTP interface.


23. The Scheduler Should Not Execute the Work

This is a subtle architectural principle.

The scheduler should decide:

WHAT
WHEN
WHERE
Enter fullscreen mode Exit fullscreen mode

Workers should decide:

HOW
Enter fullscreen mode Exit fullscreen mode

The scheduler should not contain model-specific execution logic.

Bad:

scheduler.run_openai()
scheduler.run_anthropic()
scheduler.run_local_llama()
Enter fullscreen mode Exit fullscreen mode

Better:

scheduler
     |
     v
task.type = "llm.generate"
     |
     v
worker
     |
     +---- provider adapter
     |
     +---- local model
     |
     +---- external API
Enter fullscreen mode Exit fullscreen mode

The scheduler is infrastructure.

Workers are execution environments.

That separation allows the platform to evolve.


24. Architecture

A production architecture might look like:

                         CLIENTS
                            |
                            v
                     +-------------+
                     | API Gateway |
                     +-------------+
                            |
                            v
                     +-------------+
                     | Task API    |
                     +-------------+
                            |
                            v
                  +---------------------+
                  | Distributed         |
                  | Scheduler           |
                  +---------------------+
                     |       |       |
                     |       |       |
                     v       v       v
                  State    Queue   Timers
                    DB      |       |
                            |       |
                +-----------+-------+-----------+
                |           |       |           |
                v           v       v           v
             Worker      Worker  Worker      Worker
                |           |       |           |
                v           v       v           v
              LLM API     GPU     Tools     Databases
Enter fullscreen mode Exit fullscreen mode

The scheduler is a control plane.

The workers form the data plane.

That distinction is powerful.


25. Persistence

Do not make the queue your source of truth.

The database should contain durable task state.

For example:

PostgreSQL
    |
    +---- tasks
    +---- workflows
    +---- dependencies
    +---- schedules
    +---- workers
    +---- executions
Enter fullscreen mode Exit fullscreen mode

The queue becomes a transport mechanism.

This means if the queue disappears, the scheduler can reconstruct ready work from persistent state.

That is a huge reliability advantage.

The database answers:

“What should exist?”

The queue answers:

“What should a worker process next?”

Those are different questions.


26. Scheduler Failure

What happens if the scheduler crashes?

If task state exists only in memory:

scheduler crashes
       |
       v
system forgets everything
Enter fullscreen mode Exit fullscreen mode

Terrible.

If state is durable:

scheduler crashes
       |
       v
new scheduler starts
       |
       v
scan persistent state
       |
       v
recover leases
       |
       v
rebuild ready queue
       |
       v
continue
Enter fullscreen mode Exit fullscreen mode

This is why stateless schedulers are attractive.

You can run:

scheduler-1
scheduler-2
scheduler-3
Enter fullscreen mode Exit fullscreen mode

behind a load balancer.

But now we introduce another distributed-systems problem:

Who is the leader?


27. Leader Election

You don't necessarily need a single leader for every scheduling operation.

But some responsibilities may benefit from leadership:

schedule scanning
cleanup
lease recovery
global coordination
Enter fullscreen mode Exit fullscreen mode

A distributed lock or consensus system can elect one scheduler as leader.

For example:

scheduler A -> leader
scheduler B -> follower
scheduler C -> follower
Enter fullscreen mode Exit fullscreen mode

If A disappears:

B -> leader
Enter fullscreen mode Exit fullscreen mode

However, don't add leader election simply because it sounds distributed.

If database transactions can provide the required correctness, use them.

Distributed systems become dangerous when complexity is introduced without a concrete consistency requirement.


28. Event-Driven Scheduling

Another architecture is event-driven.

Instead of constantly polling:

scheduler -> database -> database -> database
Enter fullscreen mode Exit fullscreen mode

we can emit events:

TaskCreated
TaskCompleted
TaskFailed
WorkerAvailable
DependencyResolved
Enter fullscreen mode Exit fullscreen mode

Then:

event
  |
  v
scheduler
  |
  v
recalculate readiness
Enter fullscreen mode Exit fullscreen mode

For large systems, event streams can become useful.

But there is a tradeoff.

Polling is simple.

Events are fast and scalable but introduce:

ordering
duplication
delivery guarantees
replay
consumer state
Enter fullscreen mode Exit fullscreen mode

The correct architecture depends on scale and requirements.


29. Observability

A scheduler without observability is a black box.

You need metrics such as:

tasks_submitted
tasks_ready
tasks_running
tasks_succeeded
tasks_failed
tasks_retried
tasks_dead_lettered

queue_depth
task_wait_time
execution_time

worker_utilization
GPU_utilization

tokens_consumed
estimated_cost
actual_cost
Enter fullscreen mode Exit fullscreen mode

One particularly useful metric is:

queue_wait_time
Enter fullscreen mode Exit fullscreen mode

If:

execution_time = 2 seconds
queue_wait_time = 5 minutes
Enter fullscreen mode Exit fullscreen mode

your workers aren't necessarily the problem.

Your scheduling policy or capacity is.

Metrics reveal where time actually disappears.


30. Tracing AI Workflows

A single AI workflow may contain dozens of tasks.

Without tracing:

workflow failed
Enter fullscreen mode Exit fullscreen mode

is almost useless.

With tracing:

workflow_123
   |
   +-- task_A 120ms
   |
   +-- task_B 4.2s
   |
   +-- task_C 1.1s
   |
   +-- task_D failed
          |
          +-- provider timeout
Enter fullscreen mode Exit fullscreen mode

Now debugging becomes possible.

Every task should therefore carry:

workflow_id
task_id
parent_task_id
attempt_id
trace_id
Enter fullscreen mode Exit fullscreen mode

This creates a lineage graph.

AI systems benefit enormously from lineage because outputs often become inputs to subsequent tasks.


31. Security

A scheduler is a very powerful component.

It controls execution.

That makes it a security boundary.

Tasks should not be allowed to request arbitrary capabilities.

Instead, define permissions.

For example:

{
  "permissions": [
    "internet.search",
    "database.read",
    "llm.generate"
  ]
}
Enter fullscreen mode Exit fullscreen mode

A worker executes only what the task is authorized to do.

Secrets should not be embedded directly in task payloads.

Instead:

task
 |
 v
secret reference
 |
 v
secret manager
Enter fullscreen mode Exit fullscreen mode

This prevents API keys from spreading through queues and logs.


32. Cancellation

Users will eventually press:

Cancel.

Cancellation in distributed systems is surprisingly difficult.

The task may already be running.

The worker may be calling an external API.

The network may be unreliable.

So cancellation is often cooperative.

Task
 |
 | cancellation requested
 v
CANCEL_REQUESTED
 |
 v
worker observes signal
 |
 v
stop execution
 |
 v
CANCELLED
Enter fullscreen mode Exit fullscreen mode

But what if the worker never responds?

The lease eventually expires.

The scheduler can recover the task.

Cancellation therefore becomes another state transition rather than a magical kill command.


33. Timeouts

Every distributed task needs a timeout.

Without one:

RUNNING
Enter fullscreen mode Exit fullscreen mode

can become:

RUNNING forever
Enter fullscreen mode Exit fullscreen mode

A task might define:

soft_timeout = 60 seconds
hard_timeout = 120 seconds
Enter fullscreen mode Exit fullscreen mode

The soft timeout can trigger:

warning
Enter fullscreen mode Exit fullscreen mode

The hard timeout can trigger:

termination
retry
Enter fullscreen mode Exit fullscreen mode

Timeouts are especially important for AI tasks because model inference, tool calls, and external services can have unpredictable latency.


34. The Minimal Implementation

You don't need to build the entire architecture immediately.

A first version could be:

PostgreSQL
Redis
Scheduler
Workers
Enter fullscreen mode Exit fullscreen mode

Database:

tasks
workflows
dependencies
workers
Enter fullscreen mode Exit fullscreen mode

Redis:

ready queue
Enter fullscreen mode Exit fullscreen mode

Scheduler:

1. find pending tasks
2. check dependencies
3. mark READY
4. enqueue
5. recover expired leases
6. schedule retries
Enter fullscreen mode Exit fullscreen mode

Worker:

1. claim task
2. execute
3. heartbeat
4. report result
Enter fullscreen mode Exit fullscreen mode

This is enough to create a functional distributed AI task scheduler.

Then complexity can be added when actual requirements justify it.


35. Pseudocode

The scheduler loop might conceptually look like:

while True:

    recover_expired_tasks()

    activate_scheduled_tasks()

    resolve_dependencies()

    ready_tasks = get_ready_tasks()

    for task in select_tasks(ready_tasks):

        worker = find_capable_worker(task)

        if worker:
            lease = create_lease(task, worker)

            dispatch(task, worker, lease)

    sleep(100)
Enter fullscreen mode Exit fullscreen mode

The worker:

while True:

    task = claim_task()

    if not task:
        sleep(1)
        continue

    start_heartbeat(task)

    try:
        result = execute(task)

        complete(task, result)

    except RetryableError as error:
        retry(task, error)

    except Exception as error:
        fail(task, error)
Enter fullscreen mode Exit fullscreen mode

Simple.

But underneath this small loop are years of distributed-systems theory.


36. The Hidden Mathematics

Scheduling can be expressed mathematically.

Suppose we have tasks:

T = {t1, t2, ..., tn}
Enter fullscreen mode Exit fullscreen mode

Each task has:

priority p_i
resource requirement r_i
deadline d_i
estimated cost c_i
execution time e_i
Enter fullscreen mode Exit fullscreen mode

Workers have capacities:

W = {w1, w2, ..., wm}
Enter fullscreen mode Exit fullscreen mode

The scheduler attempts to construct an assignment:

f(t_i) = w_j
Enter fullscreen mode Exit fullscreen mode

subject to:

resources(t_i) <= capacity(w_j)
Enter fullscreen mode Exit fullscreen mode

and:

dependencies(t_i) completed
Enter fullscreen mode Exit fullscreen mode

while optimizing some objective:

minimize waiting time
minimize cost
maximize utilization
maximize fairness
Enter fullscreen mode Exit fullscreen mode

Usually we cannot optimize everything simultaneously.

That means scheduling is fundamentally a multi-objective optimization problem.

AI makes that problem even more interesting because cost, quality, latency, and compute are connected.


37. Model Routing

Imagine three models:

Small model
cost = $0.01
latency = 200ms

Medium model
cost = $0.10
latency = 1s

Large model
cost = $1.00
latency = 8s
Enter fullscreen mode Exit fullscreen mode

A scheduler could potentially route tasks based on requirements.

Simple classification?

Small model
Enter fullscreen mode Exit fullscreen mode

Complex reasoning?

Large model
Enter fullscreen mode Exit fullscreen mode

This means scheduling can eventually evolve into model-aware orchestration.

The scheduler becomes capable of reasoning about not just:

Where can this task run?
Enter fullscreen mode Exit fullscreen mode

but:

What is the appropriate execution environment for this task?
Enter fullscreen mode Exit fullscreen mode

That is a much more interesting system.


38. Quality-Aware Scheduling

AI introduces another dimension:

quality.

Suppose the task has a minimum quality requirement.

quality_target = 0.90
Enter fullscreen mode Exit fullscreen mode

The scheduler might select:

cheap model
Enter fullscreen mode Exit fullscreen mode

for easy tasks and:

expensive model
Enter fullscreen mode Exit fullscreen mode

for difficult ones.

This creates an architecture where scheduling policies can incorporate:

cost
latency
quality
availability
Enter fullscreen mode Exit fullscreen mode

You are effectively scheduling intelligence as a resource.

That is one of the defining architectural problems of AI infrastructure.


39. What Makes This System Distributed?

Not the number of servers.

The system becomes distributed because:

state
execution
coordination
communication
failure
Enter fullscreen mode Exit fullscreen mode

are spread across independent processes.

The scheduler cannot assume:

if I sent it, they received it
Enter fullscreen mode Exit fullscreen mode

It cannot assume:

if they received it, they executed it
Enter fullscreen mode Exit fullscreen mode

It cannot assume:

if they executed it, I received the result
Enter fullscreen mode Exit fullscreen mode

And it cannot assume:

if I don't hear from them, they are dead
Enter fullscreen mode Exit fullscreen mode

Those assumptions are where distributed systems break.

The network creates uncertainty.

The scheduler exists partly to manage that uncertainty.


40. The Architecture Eventually Looks Like an Operating System

At some point, the analogy becomes difficult to ignore.

An operating system manages:

processes
memory
CPU
priorities
scheduling
resources
permissions
timeouts
signals
Enter fullscreen mode Exit fullscreen mode

A distributed AI scheduler manages:

tasks
context
models
workers
priorities
compute
tokens
permissions
timeouts
cancellation
Enter fullscreen mode Exit fullscreen mode

The similarity is remarkable.

We are effectively building an operating system for AI workloads.

The task is the process.

The worker is the CPU.

The model is the execution environment.

The queue is the run queue.

The scheduler is the kernel-like control plane.

The database is persistent system state.

The workflow is the process graph.

And the resource budget is the system's finite capacity.


41. The Most Important Design Principle

Don't start by asking:

“Which queue should I use?”

Ask:

“What guarantees does my system need?”

Do you need:

at-least-once execution?
ordering?
fairness?
durability?
low latency?
high throughput?
exactly-once side effects?
cost limits?
resource isolation?
workflow dependencies?
Enter fullscreen mode Exit fullscreen mode

Then choose infrastructure.

Otherwise you end up assembling fashionable technologies around an undefined problem.

A scheduler is not:

Redis + workers
Enter fullscreen mode Exit fullscreen mode

It is a set of execution guarantees implemented through distributed coordination.

That distinction is the difference between infrastructure that merely works and infrastructure that can be trusted.


42. A Production Evolution Path

A sensible evolution might look like this.

Stage 1

PostgreSQL
+
Worker
Enter fullscreen mode Exit fullscreen mode

Simple background execution.

Stage 2

PostgreSQL
+
Redis
+
Multiple workers
Enter fullscreen mode Exit fullscreen mode

Parallel execution.

Stage 3

Scheduler
+
DAG workflows
+
Retries
+
Leases
Enter fullscreen mode Exit fullscreen mode

Reliable orchestration.

Stage 4

Resource-aware workers
+
GPU scheduling
+
Tenant quotas
Enter fullscreen mode Exit fullscreen mode

Infrastructure-aware execution.

Stage 5

Cost-aware scheduling
+
Model routing
+
Budgets
Enter fullscreen mode Exit fullscreen mode

AI-native orchestration.

Stage 6

Multi-region schedulers
+
Failover
+
Event streams
+
Advanced fairness
Enter fullscreen mode Exit fullscreen mode

Global infrastructure.

The important part is not reaching Stage 6.

The important part is knowing why you are moving from one stage to another.


43. The Scheduler Is the Brain of the Control Plane

AI applications are often described in terms of models.

People talk about:

context windows
parameters
tokens
agents
RAG
vector databases
Enter fullscreen mode Exit fullscreen mode

But large AI systems eventually run into a different problem.

There is simply too much work.

Thousands of agents.

Millions of tasks.

External APIs.

Long-running workflows.

Scheduled jobs.

Retries.

Failures.

Dependencies.

GPU capacity.

Token budgets.

Financial budgets.

At that scale, intelligence alone isn't enough.

You need coordination.

You need a system capable of answering:

What should happen next?
Enter fullscreen mode Exit fullscreen mode

And answering it reliably when the world is messy.

That is the job of the distributed scheduler.


Conclusion: Build the Control Plane

The interesting part of AI infrastructure isn't always the model.

Sometimes it is the machinery around the model.

A distributed AI task scheduler sits in that machinery.

It transforms:

user intention
Enter fullscreen mode Exit fullscreen mode

into:

durable tasks
Enter fullscreen mode Exit fullscreen mode

then:

tasks
Enter fullscreen mode Exit fullscreen mode

into:

scheduled execution
Enter fullscreen mode Exit fullscreen mode

then:

execution
Enter fullscreen mode Exit fullscreen mode

into:

observable results
Enter fullscreen mode Exit fullscreen mode

while continuously handling:

failure
retry
capacity
priority
cost
dependencies
fairness
timeouts
cancellation
Enter fullscreen mode Exit fullscreen mode

The architecture can start surprisingly small.

A database.

A queue.

A scheduler.

A few workers.

But the ideas underneath it are enormous.

You are designing a system that must coordinate computation across unreliable machines while the workload itself can dynamically generate more computation.

That is not merely a background-job system.

It is a distributed execution engine for intelligence.

And eventually, when AI applications become less like chat interfaces and more like autonomous software systems, the scheduler may become one of the most important pieces of the architecture.

Because the future AI system won't simply answer questions.

It will plan.

It will delegate.

It will wait.

It will retry.

It will call tools.

It will create subtasks.

It will monitor results.

It will schedule future work.

And behind all of that activity will be a deceptively simple question:

What should run next?

Building a distributed AI task scheduler is the engineering discipline of answering that question at scale.

And that is where AI infrastructure starts looking less like an API...

and more like an operating system for thought.

Top comments (0)