Signal protocol: when, and why, is X3DH run again between the same two users? This question concerns the Signal protocol's X3DH key agreement protocol as documented here.

Obviously, if Alice and Bob have never contacted each other before, they need to run X3DH at least once to agree on a key.

However, it is unclear to me why X3DH needs to be run more than once between the same two users: now that they have agreed on a key, and given the Double Ratchet algorithm is used to generate new keys for each message to ensure perfect forward secrecy, why would they ever need to run X3DH again?

Without the need to run a key agreement protocol a second time between the same two users, a simple DH between Alice and Bob's identity keys should suffice, and perfect forward secrecy is not an issue since the protocol would never be run again. Only if the protocol needs to be run more than once would X3DH be called for, to restore perfect forward secrecy.

I imagine this is due to an implementation decision of not committing any keys beyond the bare minimum (private keys for the identity key, signed prekey and one-time prekeys) to non-volatile storage, so that when the app is restarted, the protocol would need to be run again. Can anyone confirm?
it is unclear to me why X3DH needs to be run more than once


They don't. X3DH is only used to initialise the session between Alice amd Bob. Once Root key is derived, X3DH is no longer required until one of them reinstalls the app or changes device.

Together with Diffie Hellman ratchet,  using KDF


Root key derives Chain key and Root key is updated.
Chain key derives Message key and Chain key is updated.


Message key is the one which encrypts the message. For every subsequent message, Chain key is updated to derive new Message key.

The process looks something like this:



A1 and B1 are Message keys.

Even though the session is not reinitialised periodically, Signal documentation actually recommends this but currently it is not used in practice by either WhatsApp or Signalapp.


  4.2. Session expiration
  It may be desirable for devices to periodically replace old sessions with new sessions, for security purposes.


However, long-term keys of the prekey bundle are still rotated but are not reinitialised with the current recipients.

// Time before rotation of signed prekeys (measured in seconds)
//
// Currently we rotate signed prekeys every 2 days (48 hours).
// We generate 100 one-time prekeys at a time.  We should
replenish
// whenever ~2/3 of them have been consumed.


Source

Identity key is kept same unless the app is reinstalled or the device is changed.

As @Natanael, Signal has an option to restart secure session which reintialises X3DH. There is also a multi-device support in Signal which reintialises X3DH between the recepient and the device. It only happens once with each new device until the app is reinstalled.