Process groups aren't hierarchical because it is a two-level namespace: process groups (jobs) are members of a session and all processes in a job must be members of the same session. All child processes inherit their parent's process group and session. If they didn't then such a program would not work correctly in any shell pipeline and a bunch of other behavior would break.
I'm assuming all processes are under your control in this scenario. It should be very rare for any process that isn't a shell or daemon to create its own process group or session.
Sessions can optionally have a controlling terminal, one (and only one) foreground process group, plus any number of background process groups.
The foreground job is how the system decides who receives signals generated by the TTY. If the controlling TTY disconnects or is closed the session leader gets SIGHUP which it usually uses to signal all the process groups it created. If the session leader dies or exits then all processes in the foreground process group get a SIGHUP.
If you exit an interactive shell or close your SSH session the system generally cleans everything up as you would expect. The processes involved generally don't implement any special handling code to make this happen: the session leader (your login shell) uses the standard Unix job control APIs to manage the session + process groups and the expected behavior falls out naturally.
Unless there is a good reason to implement this differently: don't. Use the standard Unix job control APIs as they were designed to be used.