Motivation
First real-world use of maiko 0.3.1 (an orchestration tool dispatching containerised coding-agent runs; a runner and a reporter actor over topic routing) hit one place where the model pushed back: the application entry point needs the outcome of a run it requested — emit RunRequested, obtain RunCompleted/RunFailed in the caller.
Workarounds tried, and why they disappoint:
- Smuggling a
tokio::sync::mpsc sender into an actor's factory closure. Works, but it is hand-wired channel plumbing — the exact species maiko exists to eliminate — and forms a parallel subsystem next to the broker.
oneshot::Sender inside the event payload (the classic Actix/Erlang reply-to idiom). Blocked by maiko's own commitments — arguably correctly:
Event: Clone (needed for fan-out) vs oneshot::Sender: !Clone. Events are facts (cloneable, many readers); replies are capabilities (linear, exactly one consumer). Forcing a capability into a fact-type requires Arc<Mutex<Option<…>>> with subscribers racing to .take().
- It poisons observability: with the
serde/recorder features, events are recordable and replayable; a live channel in a payload is neither.
Proposal
Keep the flow unidirectional by reframing: a response is not a reply — it is a fact someone awaits. The runtime already tracks causality (parent IDs, send_child_event); the missing piece is an awaitable, causality-scoped query over the stream:
let envelope = sup
.send_and_await(MyEvent::RunRequested { config }, |e| {
matches!(e, MyEvent::RunCompleted { .. } | MyEvent::RunFailed { .. })
})
.await?;
Semantics: publish the event normally; resolve on the first event matching the predicate whose ancestry includes the sent event's id. Internally this is an ephemeral filtered subscriber — Akka's ask pattern via temporary actor, but keyed on the existing causality chain instead of an address. The outcome event remains an ordinary event: journaled, routed, observable by all other subscribers.
Design notes / open questions
- Timeout: either a parameter or caller-side
tokio::time::timeout as the documented pattern.
- The mechanism depends on handlers linking chains via
send_child_event; worth stating as a contract in its docs.
- Composes with actor-death observability: if the handling actor stops, the await should fail fast rather than hang — an actor-terminated event participating in the chain would give this for free (candidate for a separate issue).
- A
Context-level variant (ctx.send_and_await) may be wanted eventually; the supervisor-level verb covers the entry-point and testing cases that motivated this.
Drafted by Claude (the jidoka-ai agent) from first-use experience; posted by the maintainer.
Motivation
First real-world use of maiko 0.3.1 (an orchestration tool dispatching containerised coding-agent runs; a runner and a reporter actor over topic routing) hit one place where the model pushed back: the application entry point needs the outcome of a run it requested — emit
RunRequested, obtainRunCompleted/RunFailedin the caller.Workarounds tried, and why they disappoint:
tokio::sync::mpscsender into an actor's factory closure. Works, but it is hand-wired channel plumbing — the exact species maiko exists to eliminate — and forms a parallel subsystem next to the broker.oneshot::Senderinside the event payload (the classic Actix/Erlang reply-to idiom). Blocked by maiko's own commitments — arguably correctly:Event: Clone(needed for fan-out) vsoneshot::Sender: !Clone. Events are facts (cloneable, many readers); replies are capabilities (linear, exactly one consumer). Forcing a capability into a fact-type requiresArc<Mutex<Option<…>>>with subscribers racing to.take().serde/recorder features, events are recordable and replayable; a live channel in a payload is neither.Proposal
Keep the flow unidirectional by reframing: a response is not a reply — it is a fact someone awaits. The runtime already tracks causality (parent IDs,
send_child_event); the missing piece is an awaitable, causality-scoped query over the stream:Semantics: publish the event normally; resolve on the first event matching the predicate whose ancestry includes the sent event's id. Internally this is an ephemeral filtered subscriber — Akka's ask pattern via temporary actor, but keyed on the existing causality chain instead of an address. The outcome event remains an ordinary event: journaled, routed, observable by all other subscribers.
Design notes / open questions
tokio::time::timeoutas the documented pattern.send_child_event; worth stating as a contract in its docs.Context-level variant (ctx.send_and_await) may be wanted eventually; the supervisor-level verb covers the entry-point and testing cases that motivated this.Drafted by Claude (the jidoka-ai agent) from first-use experience; posted by the maintainer.