Ideally, under a capability-based OS, you can just have a unikernel that directly loads user-supplied(!) modules in a bytecode format where instructions have capability-checking as an intrinsic; and then, after performing static analysis on those modules to guarantee that they don’t do anything crazy, the kernel module loader can JIT them into native code that doesn’t need to do the capability checks. Basically like if the entire userland consisted of ePBF programs—but with higher-level exposed semantics that allow for rich access to kernel structures, rather than just their handles.
Plus, you also get the benefit of safe sharing of higher-level data abstractions. A “native” userland process has to make syscalls using, at most, registers containing pointers to strings or fixed-size non-polymorphic structs. A capability-based userland module, meanwhile, could interact with the kernel even through reads and writes to a shared-memory tree or hashmap.
Essentially, with capabilities, you get all the benefits of every program having the same low-overhead access to the kernel that a kernel driver does, with none of the drawbacks of having to guard against corrupting the kernel’s state.