Raxol. AgentClientProtocol. Session. Emitter. Journal
(Raxol Agent Client Protocol v0.1.0)
Copy Markdown
View Source
The durable emitter — reroutes updates and turn boundaries through the
single-publisher Writer (Ext.Journal.Writer) so the session's durable,
offset-addressed journal is FILLED by live turns, and every attached
reattacher replays it.
What lands durably
emit/2→Writer.append("session_update", SessionUpdate.to_json(update), taint). The Writer publishes the stamped record to every subscriber (reattachers). Taint is stamped here and NEVER filters delivery.turn_started/3→Writer.append("turn_started", %{turnId, prompt}, "user")— acquires the Writer's cross-connection turn latch.turn_completed/3→Writer.append("turn_completed", payload, "system")— clears the latch atomically. Built from the single render value, so a reattacher'sturn_completedframe equals the live prompt response.
Delivery and edge cases
- Origin delivery is append-then-notify (two call sites).
emit/2durably appends the update, then delivers it to the origin connection with a directConnection.notify(the same FIFO lane as the prompt reply). Reattachers on OTHER connections get the identical stamped stream via the Writer subscription. Every delivered update is durable and reattachers see the same stream; append order is the single-publisher Writer's arrival order, so ordering is guaranteed for the Writer's sequence, NOT for interleaved live delivery from concurrent turn tasks. - No Writer ⇒ degrade to notify-only. If the session has no live Writer
(
Writer.whereis == nil),emit/2still notifies (never crashes a turn); the update is simply non-durable. Turn boundaries no-op. - Born-cancelled turns append neither
turn_startednorturn_completed: that path resolves in the Session without reaching the emitter. promptis recorded as[].turn_startedrecordsturnId(the turn-totality key) with an empty prompt-block list.