My solution to the problem:
"distributor" is gen_server, "worker" is gen_server.
"distributor" launches "workers" using a slave: start_link, each "worker" starts with the parameter max_processes,
"distributor" behavior: handle_call(submit,...) * put job to the queue, * cast itself check_queue handle_cast(check_queue,...) * gen_call all workers for load (current_processes / max_processes), * find the least busy, * if chosen worker load is < 1 gen_call(submit,...) worker with next job if any, remove job from the queue, "worker" behavior (trap_exit = true): handle_call(report_load, ...) * return current_process / max_process, handle_call(submit, ...) * spawn_link job, handle_call({'EXIT', Pid, Reason}, ...) * gen_cast distributor with check_queue
Actually, this is more complicated, because I need to monitor the tasks that are being performed, kill them if necessary, but they are easy to implement in such an architecture.
This is not a dynamic set of nodes, but you can start a new node with a distributor when you need to.
PS It looks like a pool, but in my case I send port processes, so I need to limit them and better control what happens where.