When you say permissionless what exactly do you mean? Obviously there has to be something to stop you overloading the other MCU with too much data it can't process before new data comes in.
SPI can be used for bidirectional coms if you keep something being transmitter constantly so the clock is always running a bit stream between the MCUs.
Since it's a bit loop, so if you transmit to the other MCU that is also transmitting bits back to you at the same time.
Or, another options is to roll your own parallel bus, it can be any width, but 2 or 4 or 8 or 16 bits is nice. For bi-directional coms you could have two buses that are totally separate, one for each direction.
Basically you have 2 bits for handshaking and 1,2,4,8,16 bits for data depending how much IO you want to allocate to it and the speed required. The 2 handshaking bits are one input and one output.
They basically just mean, Transmitter has loaded something onto the bus y/n, and Receiver has read data on bus y/n.
The transmitter and receiver respond to those bits changing and load or read the data then update their handshaking bits to reflect the new situation and that prompt the other MCU to read/load another block of data and the cycle continues.
This process can be triggered form a pin change interrupt for the handshaking input bit for super fast speed. Or form a timer. Or even from main().
The system clocks itself based on how quickly the receiving MCU reads the data and how fast the transmitter detects this and loads new data onto the bus.
I've done this before for a 16bit bus and it worked great.
If you're doing a 1,2,4 bit bus obviously you need some code to extract blocks of 1,2,4 bits out of your data buffer to be sent, but that is pretty simple
Using 8 wires/pins between the MCUs you could have two totally separate 2 bit wide buses. So 1 byte per 4 clocks.
(eg... 2 bits handshaking and 2 bits data for one direction bus and 2 bits handhsaking and 2 bits data for the other direction bus.)