EventLoop::loop() exits when its reference count reaches zero. In serve() mode, the parent connection is the only reference keeping the loop running. Other CapnpProtocol methods called from threads not handling parent IPC requests do not hold their own references and therefore depend on the parent connection remaining alive.
Add loopRef() (code suggested by @ryanofsky in https://github.com/bitcoin-core/libmultiprocess/pull/376#discussion_r4268952676), which returns a new reference to the event loop or throws if the loop is not running or is shutting down, and use it in the methods that need the loop. Protect m_loop with a new m_loop_mutex to prevent serve() from destroying the loop while loopRef() is accessing it.