Actually, wrapping around out_channel is possible, with a little bit of care: use a Buffer.t to accumulate bytes, and flush right after writing the contents of the Buffer.t to the out_channel. The out_channel will sometimes be flushed more than once in this operation but it doesn't matter.
In general, this function is "read one whole message from the transit buffer":
val read_msg : Buffer.t -> msg option