Phoenix LiveView Dashboard Observability in OpenAI Symphony: A Deep Dive
The Phoenix LiveView dashboard provides real-time observability by subscribing to PubSub events and automatically re-rendering whenever the Symphony orchestrator broadcasts state updates, eliminating the need for manual refreshes.
The openai/symphony repository implements a reactive observability interface using Phoenix LiveView to expose the runtime state of the orchestration engine. By leveraging Elixir's PubSub capabilities and LiveView's reactive programming model, the dashboard delivers a live, read-only view of system health, session counts, and rate-limiting metrics without polling or page reloads.
How the LiveView Initializes the Observability Stream
When a client connects to the dashboard, the LiveView process mounts and immediately establishes a subscription to the runtime's state changes.
Mounting and Subscribing to PubSub
In elixir/lib/symphony_elixir_web/live/dashboard_live.ex, the mount/3 callback initializes the connection by loading the current state payload and subscribing to update notifications:
def mount(_params, _session, socket) do
if connected?(socket) do
ObservabilityPubSub.subscribe()
:timer.send_interval(1_000, self(), :runtime_tick)
end
socket =
socket
|> assign(:now, DateTime.utc_now())
|> load_payload()
{:ok, socket}
end
The ObservabilityPubSub.subscribe/0 function (defined in elixir/lib/symphony_elixir_web/observability_pubsub.ex) registers the LiveView process to receive messages broadcast on the "observability:dashboard" topic, establishing the real-time data pipeline.
Reactive State Updates Without Page Refreshes
The dashboard updates automatically through message-passing rather than HTTP polling, ensuring minimal latency and server load.
Handling Runtime Ticks and State Changes
The handle_info/2 callback in dashboard_live.ex processes two distinct message types:
-
:runtime_tick– Dispatched every 1,000 milliseconds by:timer.send_interval/3to update the local clock used for runtime calculations (assign(:now, DateTime.utc_now())). -
:observability_updated– Broadcast by the orchestrator whenever the internal state snapshot changes, triggering a complete payload reload viaload_payload/0.
def handle_info(:runtime_tick, socket) do
{:noreply, assign(socket, :now, DateTime.utc_now())}
end
def handle_info(:observability_updated, socket) do
{:noreply, load_payload(socket)}
end
This architecture ensures the UI reflects the current runtime state within milliseconds of any state transition.
Generating the Observability Snapshot
The dashboard consumes structured data produced by the Presenter module, which aggregates information from the core orchestrator.
Presenter.state_payload/2 and Orchestrator.snapshots
In elixir/lib/symphony_elixir_web/presenter.ex, the state_payload/2 function generates a consistent snapshot by calling Orchestrator.snapshot/2. This payload includes:
- Session counts – Active and completed sessions
- Retry queue entries – Failed operations awaiting reprocessing
- Codex token totals – Aggregate token consumption metrics
- Rate-limit data – Current throttle status and limits
The presenter ensures data consistency by pulling from the orchestrator's single source of truth while formatting raw values for human readability.
The PubSub Broadcast Mechanism
State changes propagate through the system via explicit broadcast calls embedded in the orchestrator's mutation paths.
Broadcasting State Changes from the Orchestrator
Whenever the orchestrator updates its internal state, it invokes ObservabilityPubSub.broadcast_update/0 (located in elixir/lib/symphony_elixir_web/observability_pubsub.ex):
def broadcast_update do
Phoenix.PubSub.broadcast(
SymphonyElixir.PubSub,
"observability:dashboard",
:observability_updated
)
end
This broadcasts the :observability_updated atom to all subscribed LiveView processes, triggering immediate UI synchronization across all connected dashboard clients.
Rendering the Real-Time Dashboard UI
The render/1 function in dashboard_live.ex transforms the payload into HTML displaying summary metrics, rate-limit status, and detailed tables for active sessions and retry queues.
Helper functions like format_int/1, format_runtime_seconds/1, and state_badge_class/1 convert raw Erlang terms into formatted strings and CSS classes, presenting runtime duration, integer counters, and session state indicators without requiring client-side JavaScript processing.
End-to-End Data Flow
The complete observability pipeline operates as follows:
- Orchestrator updates (state change) → ObservabilityPubSub.broadcast_update/0 → PubSub dissemination → DashboardLive.handle_info/2 receives
:observability_updated→ Presenter.state_payload/2 reloads snapshot → LiveView re-renders the template instantly.
Concurrently, the :runtime_tick message ensures the "Now" timestamp remains current for runtime calculations via total_runtime_seconds/2, keeping elapsed time displays accurate without requiring state mutations.
Implementation Examples
To expose the dashboard in your router, mount the LiveView module:
# elixir/lib/symphony_elixir_web/router.ex
live "/dashboard", SymphonyElixirWeb.DashboardLive, :index
Broadcast updates from custom orchestrator logic:
defmodule SymphonyElixir.Orchestrator do
def handle_state_change(state) do
# ... process state update ...
SymphonyElixirWeb.ObservabilityPubSub.broadcast_update()
{:ok, state}
end
end
Force a manual refresh via the API controller:
defmodule SymphonyElixirWeb.Controllers.ObservabilityApiController do
def refresh(conn, _params) do
{:ok, _payload} = SymphonyElixirWeb.Presenter.refresh_payload(orchestrator())
SymphonyElixirWeb.ObservabilityPubSub.broadcast_update()
send_resp(conn, 200, "refreshed")
end
end
Summary
- The Phoenix LiveView dashboard in Symphony mounts at
/dashboardand subscribes toObservabilityPubSubfor real-time updates. - State snapshots are generated by
Presenter.state_payload/2pulling fromOrchestrator.snapshot/2, ensuring consistent data aggregation. - PubSub broadcasts via
ObservabilityPubSub.broadcast_update/0push:observability_updatedmessages to all connected clients instantly. - Dual message handling in
handle_info/2combines periodic tick updates (for timestamps) with observability events (for state changes). - Zero client-side polling is required, as LiveView's reactive model handles all UI synchronization server-side.
Frequently Asked Questions
How does the dashboard update without manual refreshes?
The dashboard uses Phoenix PubSub to push state changes directly to the LiveView process. When the orchestrator calls ObservabilityPubSub.broadcast_update/0, all subscribed dashboard instances receive the :observability_updated message and immediately reload their payloads, triggering a server-side re-render of the HTML diff.
What metrics does the Symphony dashboard display?
According to the Presenter.state_payload/2 implementation in elixir/lib/symphony_elixir_web/presenter.ex, the dashboard displays running and retrying session counts, Codex token usage totals, rate-limit snapshots, detailed active session tables, and retry queue entries with precise runtime calculations.
Can I customize the update frequency or subscribe to specific events?
The :runtime_tick interval is hardcoded to 1,000 milliseconds in the mount/3 function using :timer.send_interval/3. While the current implementation broadcasts all observability events on a single topic, the PubSub architecture in ObservabilityPubSub can be extended to support topic partitioning or custom broadcast patterns by modifying the topic string and subscription logic.
What happens if the orchestrator restarts while the dashboard is open?
The LiveView maintains its subscription to the PubSub topic independently of the orchestrator process. When the orchestrator restarts and resumes broadcasting updates via broadcast_update/0, connected dashboards will receive the new :observability_updated messages and refresh their payloads accordingly, though there may be a brief gap in updates during the orchestrator's restart sequence.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →