The problem with Nix is that it doesn't properly encapsulate imperative behavior (read: most existing software).
For example, running pip (python package manager) fails because it tries to change the current installation environment behind Nix's back. Instead, Nix should allow it to run, but monitor any changes it makes in a functional way. This may require running programs inside an emulation environment (catching all the kernel calls that make modifications), or perhaps use a specialized filesystem, which sucks, but you can't have a free lunch here.
The problem of figuring out the dependencies of a package given that is was installed by an external package manager is unsolvable. For the pip example, you would have to wait for all runtime possibilities to occur to know what dynamic libraries can be loaded. It's not even clear how the main library should find these given that it probably has /usr/lib/libwhatever.so.13 hard coded inside - should nix try to intercept the syscall for this when it happens?
The way I go about this these days is to keep a nix derivation that builds a container image, and then I run pip installs freely in containers that I spin up. I use mach-nix.
If a package is deemed important, then I add it in the derivation. So there is a kind of flow from "runtime" to "build".
For example, running pip (python package manager) fails because it tries to change the current installation environment behind Nix's back. Instead, Nix should allow it to run, but monitor any changes it makes in a functional way. This may require running programs inside an emulation environment (catching all the kernel calls that make modifications), or perhaps use a specialized filesystem, which sucks, but you can't have a free lunch here.