Second option is what I use as well: a main sequencer actor which sends requests and receives replies from a range of zero-coupled actors. The replies from actors are of two main types: (1) An acknowledgement that a command has been carried out (or an error that something could not be done), or (2) Some data, for example a measurement value from a test instrument actor.
For replies, I always override the Actor Core of the sequencer actor and use events to receive the reply (or replies) from the sub-actor(s). The replies are then verified and a decision is made on the next state to go to. The sequencer actor always uses messages to self to move to the next state.
My sequencer is a bit more elaborate in that, depending on the sequencer state, some actions can be carried out concurrently. An example from a current program I'm working on is a 'Configure Instruments' state, where (for example) I will send commands to an oscilloscope actor and AWG actor at the same time, and receive the replies using a single event case. This is more complex though as it means the sequencer must have a way of differentiating the replies it gets. I do this by embedding a message ID in each message, which the sub-actors extract and send back to the sequencer with each reply. (I'm not saying you would need to do this for your application though, as you may not need the complexity).
The design is still asynchronous (outside of the fact that it is implementing synchronous state-machine-like functionality) i.e. when it sends commands it waits for asynchronous replies. I also have full pause and abort functionality in my programs. But one thing you may still need is time-out functionality. I've been playing with a time-out actor which I can kick off when I send commands to sub-actors; this sends an event to the sequencer if a sub-actor doesn't reply within a time limit, and the sequencer can then abort (or carry out some other operation).