Calling it a "shell" and a "core" makes it sound more cohesive than it actually is. The pattern is more that your user management has an IO layer and a pure layer, and your session management has an IO layer and a pure layer, and your API calls have an IO layer and a pure layer, then you kind of have some glue that sticks all the IO layers together. Sometimes people treat pervasive things like logging or random numbers as okay to be impure because they aren't "in the main path," but there are concepts like the writer and state monads to handle those sorts of concerns as well.
This pattern is more and more common in imperative programming too, due to wanting to mock out the IO for testing. If you've ever made a minimal interface with one production implementation that does real IO and a test implementation that returns dummy data, and you push as much logic as possible to code that depends on this interface, then you've done the "impure core" architectural pattern, at least in a small part. Functional programmers just take it a step or two further.