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:

  1. :runtime_tick – Dispatched every 1,000 milliseconds by :timer.send_interval/3 to update the local clock used for runtime calculations (assign(:now, DateTime.utc_now())).

  2. :observability_updated – Broadcast by the orchestrator whenever the internal state snapshot changes, triggering a complete payload reload via load_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/0PubSub disseminationDashboardLive.handle_info/2 receives :observability_updatedPresenter.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 /dashboard and subscribes to ObservabilityPubSub for real-time updates.
  • State snapshots are generated by Presenter.state_payload/2 pulling from Orchestrator.snapshot/2, ensuring consistent data aggregation.
  • PubSub broadcasts via ObservabilityPubSub.broadcast_update/0 push :observability_updated messages to all connected clients instantly.
  • Dual message handling in handle_info/2 combines 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →