Status
This is currently a feature proposal open for discussion. The concept (cross-supervisor communication) is needed, but it can be implemented multiple way. This is a proposal of one of them.
Summary
Support multiple in-process subsystems with distinct event/topic types, connected via bridge actors. Each subsystem runs its own Supervisor<T>, and a bridge actor forwards events from one subsystem to another.
Motivation
Some applications have naturally distinct event domains - e.g., a USB subsystem and a UI subsystem - that need to exchange information without sharing a single event type. Today the only option is nesting one supervisor inside an actor of another, which creates an artificial hierarchy and awkward lifecycle coupling.
The goal is to support equal, peer subsystems with a minimal API surface, reusing existing concepts (actors, contexts, subscriptions, backpressure) rather than introducing new runtime primitives.
Proposed Design
API
// Two independent supervisors with their own event/topic types
let mut sup_a = Supervisor::<TopicA>::default();
let mut sup_b = Supervisor::<TopicB>::default();
// Bridge: an actor in A that can produce into B
sup_a.build_bridge("Bridge", &sup_b, |ctx, ext_ctx| ABBridge::new(ctx, ext_ctx))
.subscribe(&[TopicA::SomeTopic])
.build()?;
Bridge actor
The bridge is a regular actor in supervisor A. The user controls event conversion - the framework doesn't impose any mapping:
struct ABBridge {
ctx: Context<EventA>,
ext_ctx: ProducerContext<EventB>,
}
impl Actor for ABBridge {
type Event = EventA;
async fn handle_event(&mut self, envelope: &Envelope<Self::Event>) -> Result {
let event_b: EventB = envelope.event().into(); // user-defined conversion
self.ext_ctx.send(event_b).await
}
}
ProducerContext<E>
A new narrow type - essentially a wrapped Sender<Arc<Envelope<E>>> plus an ActorId for stamping envelopes. It exposes only:
send(event) - send an event into the external subsystem
send_child_event(event, parent_id) - send with causality tracking
is_sender_full() - check backpressure on the external channel
Notably excluded: stop() (no actor to stop in B), stop_runtime() (too dangerous to expose implicitly - users who need cross-subsystem shutdown can hold a supervisor handle directly).
Key properties
- Direction is explicit. A→B and B→A are separate bridge actors with separate subscriptions. Asymmetric bridging is natural.
- Backpressure propagates. The bridge participates in B's backpressure as a producer. If B is overloaded, the bridge slows down, which slows A's processing of bridged topics.
- Identity is visible on both sides. The bridge actor name is validated for uniqueness in both supervisors. Events arriving in B carry the bridge's
ActorId, making them identifiable in monitoring and test harnesses.
- No new runtime machinery. The bridge is a regular actor in A. It gets lifecycle hooks, monitoring, subscriptions, and graceful shutdown for free.
- Event conversion is user-controlled. The framework doesn't require
From<EventA> for EventB - users implement whatever mapping makes sense.
Alternatives considered
- Shared event bus: Loses type safety, forces a single event type across subsystems.
- Supervisor hierarchy (nesting): Creates artificial parent-child relationship, complicates lifecycle.
- Standalone
Bridge type (not owned by either supervisor): Loses actor lifecycle benefits - no subscriptions, no backpressure, no monitoring, no graceful shutdown.
Status
This is currently a feature proposal open for discussion. The concept (cross-supervisor communication) is needed, but it can be implemented multiple way. This is a proposal of one of them.
Summary
Support multiple in-process subsystems with distinct event/topic types, connected via bridge actors. Each subsystem runs its own
Supervisor<T>, and a bridge actor forwards events from one subsystem to another.Motivation
Some applications have naturally distinct event domains - e.g., a USB subsystem and a UI subsystem - that need to exchange information without sharing a single event type. Today the only option is nesting one supervisor inside an actor of another, which creates an artificial hierarchy and awkward lifecycle coupling.
The goal is to support equal, peer subsystems with a minimal API surface, reusing existing concepts (actors, contexts, subscriptions, backpressure) rather than introducing new runtime primitives.
Proposed Design
API
Bridge actor
The bridge is a regular actor in supervisor A. The user controls event conversion - the framework doesn't impose any mapping:
ProducerContext<E>A new narrow type - essentially a wrapped
Sender<Arc<Envelope<E>>>plus anActorIdfor stamping envelopes. It exposes only:send(event)- send an event into the external subsystemsend_child_event(event, parent_id)- send with causality trackingis_sender_full()- check backpressure on the external channelNotably excluded:
stop()(no actor to stop in B),stop_runtime()(too dangerous to expose implicitly - users who need cross-subsystem shutdown can hold a supervisor handle directly).Key properties
ActorId, making them identifiable in monitoring and test harnesses.From<EventA> for EventB- users implement whatever mapping makes sense.Alternatives considered
Bridgetype (not owned by either supervisor): Loses actor lifecycle benefits - no subscriptions, no backpressure, no monitoring, no graceful shutdown.