Alastair
07/06/2026, 3:52 PMMarvin
07/06/2026, 3:52 PMMarvin
07/06/2026, 3:55 PMqueue-a
- worker 2 listens only to queue-b
- deployment X submits to queue-a
- deployment Y submits to queue-b
That gives you indirect targeting.
Example:
bash
prefect worker start --pool my-pool --work-queue queue-a
prefect worker start --pool my-pool --work-queue queue-b
Then configure deployment A to use queue-a, deployment B to use queue-b.
Important caveat
If two workers listen to the same queue, you are not guaranteed which one will pick up a given run. It’s effectively first-available / first-to-claim.
So if you need deterministic routing:
- use different queues, and/or
- use different work pools
Recommended pattern
If your intent is something like:
- one worker on machine A
- one worker on machine B
- certain flows must go to A, others to B
then use either:
1. One work pool, multiple work queues
- worker A -> queue-a
- worker B -> queue-b
or
2. Separate work pools
- pool-a
- pool-b
Separate pools are often cleaner if the infrastructure/environment is meaningfully different.
CLI reference
Worker startup supports binding to a pool and queue(s):
prefect worker start --help
Docs:
- Work pools
- Workers
- Work queue management
If you want, I can also show you exactly how to configure a deployment to land on a specific work queue in prefect.yaml or in Python.Alastair
07/06/2026, 3:57 PMMarvin
07/06/2026, 3:57 PMMarvin
07/06/2026, 3:57 PMqueue-a
- worker B -> listens only on queue-b
then assigning a deployment to queue-a is a reasonable way to make sure it runs on worker A’s infrastructure.
And yes, your understanding is correct:
- one worker per queue -> effectively deterministic routing
- multiple workers on the same queue -> any of those workers may pick up the run
A couple practical notes:
- Keep both workers in the same work pool if the only difference is routing
- Use different work pools if the base infrastructure or job template differs substantially
- Queue-based routing is a normal pattern in Prefect 3.x
If you want, I can give you a minimal example of:
1. creating the queues,
2. starting each worker against its queue, and
3. assigning a deployment to the right queue in prefect.yaml or Python.Alastair
07/06/2026, 4:09 PMMarvin
07/06/2026, 4:09 PMMarvin
07/06/2026, 4:11 PMSIGTERM / SIGINT for graceful shutdown
- the worker stops polling and lets active runs finish
2. Pausing the work pool
- prevents workers in that pool from picking up new runs
- does not stop the worker process itself
So the nicest built-in pattern is usually:
- pause the pool (or otherwise stop new work from arriving)
- send SIGTERM to the worker process from your process manager / host environment
Example:
bash
prefect work-pool pause my-pool
kill -TERM <worker-pid>
CLI reference:
- Work pools
- Workers
I also verified the pause command exists:
bash
prefect work-pool pause my-pool
About “a flow that kills its own worker”
Technically, yes, a flow could do something crude like os.kill(...) if it can discover the right PID and has permission, but I would not recommend that pattern:
- brittle
- tightly couples flow code to worker process internals
- easy to kill the wrong process
- not a Prefect-supported lifecycle mechanism
Also, a flow run is not given an official “shutdown my worker” API.
Recommended approaches
If you want a worker to terminate after some condition, better options are:
- run the worker under systemd, Docker, Kubernetes, supervisord, etc.
- have that supervisor decide when to stop/restart it
- optionally pause the pool first so no new work is claimed
If your goal is “worker A should do one thing, then go away”, you might also consider:
- launching the worker externally for a bounded period
- or using --run-once-style behavior if your workflow fits that model
If you want, I can help sketch a safe self-draining pattern for your setup, e.g.:
- worker listens on queue A
- flow requests drain
- external controller notices and sends SIGTERM
- worker exits after current run completes
That’s usually the cleanest approximation of “remote shutdown.”Alastair
07/06/2026, 4:15 PMMarvin
07/06/2026, 4:16 PMMarvin
07/06/2026, 4:16 PMsession-abc123
- start one ECS worker task listening only on that queue
- deployments / triggered runs for that session target only that queue
- every operator action updates a TTL record
- an external reaper checks TTL expiry
- on expiry:
- stop sending work to that queue
- optionally pause the queue/pool or mark session closed in your app
- wait for any active run to finish
- stop the ECS task
- optionally delete the queue if it was ephemeral
This gives you deterministic routing and a nice cleanup boundary.
About EventBridge schedule per interaction
That can work, but I’d consider whether a single periodic reaper is simpler than constantly updating schedules.
Two possible designs:
Option A: reschedule EventBridge every interaction
- on each command, push expiry out by N minutes
- EventBridge fires at expiry and stops the ECS task
Pros:
- direct
- low conceptual overhead
Cons:
- more moving pieces
- repeated schedule mutation
- possible race conditions around updates near firing time
Option B: periodic sweeper / reaper
- store last_activity_at or expires_at somewhere durable
- run a small periodic checker every minute
- checker stops any ECS worker session whose expires_at < now and is idle
Pros:
- simpler state model
- easier to reason about
- better if you’ll have many concurrent sessions
- easier to add “don’t kill if run is active”
Cons:
- timeout precision is to the polling interval, not exact second
For most session systems, Option B is usually more robust.
Very important design point: how do you know whether it is safe to stop?
You’ll want your controller to know at least:
- ECS task ID / ARN for the worker session
- associated Prefect work pool + queue
- session expiry time
- whether a flow run is currently active
If you can detect active work, then your shutdown logic becomes:
- if expired and idle -> stop ECS task now
- if expired and busy -> mark draining, then stop after completion
How to make “draining” work well
Since Prefect does not let you stop a single worker via API, draining usually means preventing more work from being submitted to that session.Marvin
07/06/2026, 4:16 PMprefect work-pool pause because it affects the whole pool, not just one session worker.
In your architecture, that suggests:
- don’t use work-pool pause for per-session draining if many sessions share the pool
- instead use:
- one queue per session, and
- application-level routing control, and
- ECS stop logic per session
That seems much more aligned with your goal.
Practical recommendation
Given what you described, I would recommend:
- one shared work pool for this worker type
- one dedicated work queue per ad hoc session
- one ECS worker task per session listening only to that queue
- MCP tracks expires_at
- periodic reaper checks for expired sessions
- if expired:
- block new submissions for that session
- if no active flow run, stop ECS task
- if active flow run, wait and stop when complete
That’s a solid design.
When to use EventBridge schedule mutation anyway
If you only ever have a small number of sessions and you already have scheduling logic in place, your current plan is perfectly acceptable. I’d just add protections for:
- schedule fires while a run is still active
- operator sends a new command right as the shutdown fires
- ECS task is already gone
- Prefect run is stuck / long-running
Bottom line
Yes — using ECS/EventBridge to enforce worker session timeout is a good approach, and likely the right one.
If you want the most robust version, I’d lean toward:
- session-specific queues
- application-managed expiry state
- periodic reaper instead of constantly rewriting EventBridge schedules
If you want, I can help you design the exact control flow for:
1. session startup,
2. run submission to the right queue,
3. idle timeout extension, and
4. safe shutdown of the ECS worker task.