In general you should seldom have to write your own stages as there is a very rich set of built in stages provided. For example if you have a request-response you want to perform with an actor you can use mapAsync to interact with the actor from a stream. Something like this:
val myProcessingActor: ActorRef = ???
val mySource: Source[Thing, NotUsed] = ???
val responses: Source[Response, NotUsed] =
mySource.mapAsync(1)(thing => (myProcessingActor ? Request(thing)).mapTo[Response])
responses.runForeach(println)
The first parameter to mapAsync specifies the maximum number of concurrent ongoing requests, if there is only one actor there is no need to allow more than 1 concurrent outstanding request as they will just queue up in the actor mailbox if you do.
When you materialize a stream using run it will be running inside one or more actors, but that is an internal implementation detail and the only part of that you will see is having to provide an ActorSystem to the ActorMaterializer which is needed to materialize a flow.