When I first considered this problem, my initial reaction was to add the access list to the block header. It would then be the proposer’s additional duty to provide the block’s access list, and the state transition function would check its correctness. I liked this approach because it allows for somewhat optimistic parallel execution, enabling client implementations to have multiple transaction pipelines. However, I understand that this approach is more involved and complex.
Alternatively, you could use a ring buffer, as you mentioned, or implement some kind of caching metadata system contract. If user-space contracts need this information, a precompile could also be provided. I do agree with you that I think we need to persist caching information across blocks and it probably needs to be part of consensus