Packages
A Fair Source multi-agent runtime with deterministic agent scoring and replayable run history.
Retired package: Deprecated - superseded — operator console moved to the Syntropy app
Current section
Files
Jump to
Current section
Files
lib/syntropy_web/live/lattice_live.ex
defmodule SyntropyWeb.LatticeLive do
use SyntropyWeb, :live_view
import SyntropyWeb.RuntimeViewHelpers
alias Syntropy.{
ClusterInventory,
EventRecorder,
HistoryStore,
RecommendationStore,
RuntimeStatus,
TaskScheduler
}
alias SyntropyWeb.GraphViewModel
@event_limit 20
@timeline_limit 100
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(Syntropy.PubSub, "lattice:events")
Phoenix.PubSub.subscribe(Syntropy.PubSub, "lattice:cluster")
end
{:ok,
assign(socket,
current_params: %{},
view_mode: "live",
live_connected: connected?(socket),
task_prompt: default_task_prompt(),
task_strategy: "parallel",
task_loading: false,
task_error: nil,
timeline_snapshots: [],
selected_snapshot_id: nil,
selected_snapshot_index: 0,
focused_agent_id: nil,
focused_agent: nil,
cluster_info: Syntropy.cluster_info(),
cluster_summary: %{
status: "disabled",
local_node_id: Syntropy.node_id(),
local_node_name: Map.get(Syntropy.cluster_info(), :node_name),
expected_peers: [],
connected_peers: [],
nodes: [],
unavailable_nodes: [],
abandoned_task_incidents: []
},
operator_status: %{
provider: "mock",
provider_status: "ready",
provider_reasons: [],
persistence_status: "disabled",
persistence_reasons: [],
readiness_status: "ready",
readiness_detail: nil,
cluster_reasons: [],
cluster_detail: nil,
cluster_status: "disabled",
seeding_posture: "empty",
live_agent_count: 0
},
graph_empty_message: nil,
focused_agent_empty_message: "No agent state is loaded yet.",
replay_empty_message:
"No snapshots yet. Run a task in live mode to create the first replay checkpoint.",
result_empty_message: "No completed tasks yet.",
event_empty_message: "No retained events yet.",
graph_json: GraphViewModel.to_json(GraphViewModel.from_live([])),
events: [],
recommendations: [],
display_task_result: nil,
display_snapshot: nil,
display_recommendation: nil
)}
end
@impl true
def handle_params(params, _uri, socket) do
{:noreply, refresh_dashboard(socket, params)}
end
@impl true
def handle_event(
"submit_task",
%{"task" => %{"prompt" => _prompt, "strategy" => _strategy}},
%{assigns: %{view_mode: "replay"}} = socket
) do
{:noreply, assign(socket, task_error: "Return to live mode to run tasks.")}
end
def handle_event(
"submit_task",
%{"task" => %{"prompt" => prompt, "strategy" => strategy}},
socket
) do
if String.trim(prompt) == "" do
{:noreply, socket}
else
{:noreply,
socket
|> assign(
task_prompt: default_task_prompt(),
task_strategy: strategy,
task_loading: true,
task_error: nil
)
|> start_async(:submit_task, fn ->
TaskScheduler.submit(prompt, strategy: parse_strategy(strategy))
end)}
end
end
@impl true
def handle_event("approve_recommendation", _params, %{assigns: %{view_mode: "replay"}} = socket) do
{:noreply,
assign(socket, task_error: "Replay mode is read-only. Return to live to approve changes.")}
end
def handle_event("approve_recommendation", %{"id" => recommendation_id}, socket) do
case TaskScheduler.approve_recommendation(recommendation_id) do
{:ok, _recommendation} ->
{:noreply,
socket |> assign(task_error: nil) |> refresh_dashboard(socket.assigns.current_params)}
{:error, reason} ->
{:noreply, assign(socket, task_error: format_task_error(reason))}
end
end
@impl true
def handle_event("reject_recommendation", _params, %{assigns: %{view_mode: "replay"}} = socket) do
{:noreply,
assign(socket, task_error: "Replay mode is read-only. Return to live to reject changes.")}
end
def handle_event("reject_recommendation", %{"id" => recommendation_id}, socket) do
case TaskScheduler.reject_recommendation(recommendation_id) do
{:ok, _recommendation} ->
{:noreply,
socket |> assign(task_error: nil) |> refresh_dashboard(socket.assigns.current_params)}
{:error, reason} ->
{:noreply, assign(socket, task_error: format_task_error(reason))}
end
end
@impl true
def handle_event("focus_agent", %{"id" => agent_id}, socket) do
{:noreply,
push_patch(socket,
to: graph_path(socket.assigns.selected_snapshot_id, normalize_param(agent_id))
)}
end
@impl true
def handle_event("select_snapshot", %{"snapshot_index" => index}, socket) do
snapshot =
socket.assigns.timeline_snapshots
|> snapshot_at_index(index)
case snapshot do
nil ->
{:noreply, socket}
selected_snapshot ->
{:noreply,
push_patch(socket,
to: graph_path(selected_snapshot.id, socket.assigns.focused_agent_id)
)}
end
end
@impl true
def handle_event("return_live", _params, socket) do
{:noreply, push_patch(socket, to: graph_path(nil, socket.assigns.focused_agent_id))}
end
@impl true
def handle_info({:lattice_event, _event}, %{assigns: %{view_mode: "replay"}} = socket) do
{:noreply, socket}
end
def handle_info({:lattice_event, _event}, socket) do
{:noreply, refresh_dashboard(socket, socket.assigns.current_params)}
end
@impl true
def handle_info({:cluster_refresh, _payload}, %{assigns: %{view_mode: "replay"}} = socket) do
{:noreply, socket}
end
def handle_info({:cluster_refresh, _payload}, socket) do
{:noreply, refresh_dashboard(socket, socket.assigns.current_params)}
end
@impl true
def handle_async(:submit_task, {:ok, {:ok, _result}}, socket) do
{:noreply,
socket
|> assign(task_loading: false, task_error: nil)
|> refresh_dashboard(socket.assigns.current_params)}
end
@impl true
def handle_async(:submit_task, {:ok, {:error, reason}}, socket) do
{:noreply,
socket
|> assign(task_loading: false, task_error: format_task_error(reason))
|> refresh_dashboard(socket.assigns.current_params)}
end
@impl true
def handle_async(:submit_task, {:exit, reason}, socket) do
{:noreply,
socket
|> assign(task_loading: false, task_error: format_task_error(reason))
|> refresh_dashboard(socket.assigns.current_params)}
end
@impl true
def render(assigns) do
~H"""
<main class="dashboard">
<%= if @flash["error"] do %>
<section class="panel">
<p id="dashboard-flash-error"><%= @flash["error"] %></p>
</section>
<% end %>
<section class="dashboard-header">
<div>
<p class="eyebrow">// SYNTROPY</p>
<h1>Syntropy Mission Control</h1>
<p>Run a task first, then inspect topology, replay, and trust evidence.</p>
</div>
<nav class="dashboard-nav">
<.link patch={~p"/"}>Lattice</.link>
<span> · </span>
<.link navigate={~p"/tasks"}>Tasks</.link>
<span> · </span>
<.link navigate={~p"/history"}>History</.link>
</nav>
<div class="status-chip-row">
<span class={chip_class(@operator_status.readiness_status)}>
Runtime: <%= humanize_status(@operator_status.readiness_status) %>
</span>
<span class={chip_class(@operator_status.provider)}>
Provider: <%= @operator_status.provider %>
</span>
<span class={chip_class(@operator_status.cluster_status)}>
Cluster: <%= humanize_status(@operator_status.cluster_status) %>
</span>
<span class={chip_class(@operator_status.seeding_posture)}>
Lattice: <%= humanize_status(@operator_status.seeding_posture) %>
</span>
</div>
<%= if @operator_status.readiness_detail do %>
<p id="runtime-readiness-detail" class="status-detail">
<%= @operator_status.readiness_detail %>
</p>
<% end %>
<%= if @operator_status.cluster_detail do %>
<p id="cluster-status-detail" class="status-detail">
<%= @operator_status.cluster_detail %>
</p>
<% end %>
<div id="node-status" class="node-status">
<span><strong>Node:</strong> <%= @cluster_info.node_id %></span>
<span> · <strong>BEAM:</strong> <%= @cluster_info.node_name %></span>
<span> · <strong>Cluster:</strong> <%= humanize_status(@cluster_summary.status) %></span>
<span> · <strong>Peers:</strong> <%= cluster_peer_summary(@cluster_summary) %></span>
<span> · <strong>Durability:</strong> <%= repo_status(@cluster_info) %></span>
</div>
</section>
<%= if show_cluster_incident_panel?(@cluster_summary) do %>
<section id="cluster-incident-panel" class="panel">
<header class="panel-header">
<div>
<h2>Cluster Incidents</h2>
<p>Peer loss is fail-visible. Recover the node, then explicitly resubmit abandoned work.</p>
</div>
</header>
<%= if @cluster_summary.unavailable_nodes != [] do %>
<div>
<h3>Unavailable Peers</h3>
<ul id="cluster-unavailable-list">
<%= for node <- @cluster_summary.unavailable_nodes do %>
<li id={"cluster-unavailable-#{node.node_id}"}>
<strong><%= node.node_id %></strong>
<span> · <%= node.reason %></span>
</li>
<% end %>
</ul>
</div>
<% end %>
<%= if @cluster_summary.abandoned_task_incidents != [] do %>
<div>
<h3>Abandoned Tasks</h3>
<ul id="cluster-abandoned-task-list">
<%= for incident <- @cluster_summary.abandoned_task_incidents do %>
<li id={"cluster-abandoned-task-#{incident.task_id}"}>
<strong><%= incident.task_id %></strong>
<span> · node <%= incident.node_id %></span>
<span> · coordinator <%= incident.coordinator_node_id %></span>
</li>
<% end %>
</ul>
</div>
<% end %>
</section>
<% end %>
<section class="mission-primary-grid">
<article class="panel task-composer-panel">
<header class="panel-header">
<div>
<h2>Task Composer</h2>
<p>
<%= task_composer_hint(@view_mode, @operator_status) %>
</p>
</div>
</header>
<%= if @view_mode == "replay" do %>
<p id="task-replay-notice">Return to live mode to run tasks.</p>
<% else %>
<%= if @operator_status.readiness_status != "ready" do %>
<p id="task-readiness-notice">
Runtime is not ready. Finish the provider configuration shown above before running tasks.
</p>
<% else %>
<%= if @operator_status.live_agent_count == 0 do %>
<p id="task-empty-notice">
No agents are loaded. Set <code>SYNTROPY_DEMO_SEED=true</code> or add agents from IEx/API to start the lattice.
</p>
<% else %>
<form
id="task-form"
phx-submit={JS.push("submit_task") |> JS.focus(to: "#run-observation-panel")}
>
<textarea name="task[prompt]" rows="4" placeholder={default_task_prompt()}><%= @task_prompt %></textarea>
<select name="task[strategy]">
<%= for {label, value} <- strategy_options() do %>
<option value={value} selected={@task_strategy == value}><%= label %></option>
<% end %>
</select>
<button type="submit" disabled={@task_loading or not @live_connected}>
<%= task_button_label(@task_loading, @live_connected) %>
</button>
</form>
<% end %>
<% end %>
<% end %>
<%= if @task_error do %>
<p id="task-error"><%= @task_error %></p>
<% end %>
</article>
<article id="run-observation-panel" class="panel result-panel" tabindex="-1">
<header class="panel-header">
<div>
<h2>Run Observation</h2>
<p>
<%= if @display_snapshot do %>
<%= @display_snapshot.trigger_kind %> · <%= format_timestamp(@display_snapshot.inserted_at) %>
<% else %>
Latest live result
<% end %>
</p>
</div>
</header>
<%= if @display_task_result do %>
<article id="latest-result">
<p class="run-purpose">
This is an operator trace. Syntropy received the decision, routed it to selected
agents, synthesized their outputs, and retained replay evidence.
</p>
<%= if @operator_status.provider == "mock" do %>
<section class="quality-callout quality-callout--warn">
<div>
<span>Decision answer quality</span>
<strong>Deterministic mock mode, useful for workflow review.</strong>
</div>
<p>
The synthesis below is generated without a real model. Use it to evaluate the
execution shape, routing, supervision, replay, and evidence. Connect OpenAI or
Ollama for provider-backed reasoning.
</p>
</section>
<% else %>
<section class="quality-callout">
<div>
<span>Decision answer quality</span>
<strong>Provider-backed response from <%= @operator_status.provider %>.</strong>
</div>
<p>
Treat the synthesis as a review draft: read the recommendation, verify the
selected perspectives, then inspect replay and trust evidence before acting.
</p>
</section>
<% end %>
<div class="run-summary-grid">
<article class="run-fact">
<span>Run</span>
<strong><%= @display_task_result.task_id %></strong>
<p>
<%= @display_task_result.strategy %> request resolved to
<%= @display_task_result.resolved_mode %> on <%= node_id_for(@display_task_result) %>.
</p>
</article>
<article class="run-fact">
<span>Selected perspectives</span>
<strong><%= selected_agent_names(@display_task_result) %></strong>
<p>These agents are the observable participants in the decision review.</p>
</article>
<article class="run-fact">
<span>Replay evidence</span>
<strong><%= snapshot_label(@display_snapshot) %></strong>
<p>The checkpoint lets you replay the topology and inspect the retained path.</p>
</article>
</div>
<section class="outcome-block">
<h3>
<%= if @operator_status.provider == "mock",
do: "Deterministic mock synthesis",
else: "Synthesis" %>
</h3>
<p><%= @display_task_result.synthesis %></p>
</section>
<section class="action-block" aria-labelledby="run-action-title">
<h3 id="run-action-title">How to use this result</h3>
<ol>
<li>
Check whether the selected perspectives match the decision risk.
</li>
<li>
In mock mode, validate the workflow shape. With a real provider, use the
synthesis as the decision-review draft.
</li>
<li>
Open the graph, replay checkpoint, or focused agent to inspect evidence.
</li>
<li>
Act by refining the prompt, teaching missing agents, connecting a model, or
escalating structural recommendations.
</li>
</ol>
</section>
<div id="selection-trace">
<h3>Why these perspectives were selected</h3>
<p class="notice-copy">
Rank is the routing order. Prompt match measures fit to the submitted decision.
Runtime position is the agent's current lattice weight. Capability is whether the
agent is eligible and available. Route score combines those signals.
</p>
<div class="selection-card-grid">
<%= for entry <- selected_trace_entries(@display_task_result) do %>
<article class="selection-card">
<div class="selection-card-header">
<strong><%= entry.agent_name %></strong>
<span>Rank <%= entry.rank %></span>
</div>
<p><%= selection_reason(entry) %></p>
<div class="score-explainer">
<div>
<span>Prompt match</span>
<strong><%= format_score(entry.relevance) %></strong>
</div>
<div>
<span>Runtime position</span>
<strong><%= format_score(entry.position) %></strong>
</div>
<div>
<span>Capability</span>
<strong><%= format_score(entry.capacity) %></strong>
</div>
<div>
<span>Route score</span>
<strong><%= format_score(entry.composite) %></strong>
</div>
</div>
</article>
<% end %>
</div>
<details class="raw-routing-detail">
<summary>Show considered agents and raw routing scores</summary>
<ul>
<%= for entry <- @display_task_result.selection_trace do %>
<li>
<strong><%= entry.agent_name %></strong>
<span> · rank <%= entry.rank %></span>
<span> · prompt match <%= format_score(entry.relevance) %></span>
<span> · position <%= format_score(entry.position) %></span>
<span> · capability <%= format_score(entry.capacity) %></span>
<span> · route score <%= format_score(entry.composite) %></span>
<span><%= if entry.selected, do: " · selected", else: " · considered" %></span>
</li>
<% end %>
</ul>
</details>
</div>
</article>
<% else %>
<p id="latest-result-empty"><%= @result_empty_message %></p>
<% end %>
</article>
</section>
<section class="mission-grid">
<article class="panel graph-panel">
<header class="panel-header">
<div>
<h2>Lattice Graph</h2>
<p>
<strong>Mode:</strong> <%= String.capitalize(@view_mode) %>
<%= if @selected_snapshot_id do %>
<span> · <%= @selected_snapshot_id %></span>
<% end %>
<span> · node <%= graph_node_id(@display_snapshot, @display_task_result, @cluster_info) %></span>
</p>
</div>
<%= if @view_mode == "replay" do %>
<button type="button" phx-click="return_live">Return to Live</button>
<% end %>
</header>
<%= if @graph_empty_message do %>
<p id="graph-empty-notice" class="notice-copy"><%= @graph_empty_message %></p>
<% else %>
<div
id="lattice-graph"
phx-hook="LatticeGraph"
phx-update="ignore"
data-graph={@graph_json}
data-mode={@view_mode}
data-snapshot-id={@selected_snapshot_id || ""}
>
</div>
<div class="graph-guidance">
<strong>What this graph means</strong>
<p>
Nodes are live agents. Lines appear only after agents contribute together in a
retained run or synthesis. Unconnected nodes are still available; they just have no
retained interaction edge in this view yet. Click a node to inspect its latest-run
role and connection evidence.
</p>
</div>
<% end %>
</article>
<article class="panel focus-panel">
<header class="panel-header">
<div>
<h2>Focused Agent</h2>
<p>
<%= if @focused_agent do %>
<%= @focused_agent.name %>
<% else %>
No agent selected
<% end %>
</p>
</div>
</header>
<%= if @focused_agent do %>
<div id="focused-agent-detail">
<% latest_trace = focused_trace(@focused_agent, @display_task_result) %>
<% latest_thought = focused_thought(@focused_agent, @display_task_result) %>
<section class="agent-meaning">
<span>What this node means</span>
<h3><%= agent_run_role(latest_trace) %></h3>
<p><%= agent_context_sentence(@focused_agent, latest_trace) %></p>
</section>
<div class="agent-role-grid">
<article>
<span>Perspective</span>
<strong><%= @focused_agent.perspective %></strong>
</article>
<article>
<span>Lattice position</span>
<strong><%= format_score(@focused_agent.position) %></strong>
</article>
<article>
<span>Latest route signal</span>
<strong><%= focused_route_signal(latest_trace) %></strong>
</article>
</div>
<%= if latest_trace do %>
<section class="focused-score-card">
<h3>Why it ranked here</h3>
<p><%= selection_reason(latest_trace) %></p>
<div class="score-explainer">
<div>
<span>Prompt match</span>
<strong><%= format_score(latest_trace.relevance) %></strong>
</div>
<div>
<span>Runtime position</span>
<strong><%= format_score(latest_trace.position) %></strong>
</div>
<div>
<span>Capability</span>
<strong><%= format_score(latest_trace.capacity) %></strong>
</div>
<div>
<span>Route score</span>
<strong><%= format_score(latest_trace.composite) %></strong>
</div>
</div>
</section>
<% end %>
<section class="agent-action-block">
<h3>How to act on this</h3>
<ul>
<%= for action <- focused_agent_actions(@focused_agent, latest_trace, @operator_status) do %>
<li><%= action %></li>
<% end %>
</ul>
</section>
<%= if latest_thought do %>
<section class="thought-block">
<h3>Latest contribution</h3>
<p><%= latest_thought.thought %></p>
</section>
<% end %>
<div>
<h3>Connections</h3>
<%= if ordered_connections(@focused_agent) == [] do %>
<p id="focused-agent-connections-empty">No retained connections.</p>
<% else %>
<ul id="focused-agent-connections">
<%= for connection <- ordered_connections(@focused_agent) do %>
<li id={"focused-connection-#{connection.target_agent_id}"}>
<span><%= connection.target_agent_id %></span>
<span> · <%= format_score(connection.weight) %></span>
<span> · <%= connection.interactions %> interactions</span>
</li>
<% end %>
</ul>
<% end %>
</div>
<details class="metadata-detail">
<summary>Runtime metadata</summary>
<p><strong>ID:</strong> <%= @focused_agent.id %></p>
<p><strong>Runtime ID:</strong> <%= Map.get(@focused_agent, :runtime_id, @focused_agent.id) %></p>
<p><strong>Node:</strong> <%= Map.get(@focused_agent, :node_id, graph_node_id(@display_snapshot, nil, @cluster_info)) %></p>
<p><strong>Temporary:</strong> <%= @focused_agent.temporary %></p>
<%= if @view_mode == "live" and Map.get(@focused_agent, :node_id, @cluster_info.node_id) == @cluster_info.node_id do %>
<p>
<strong>Inspect:</strong>
<.link navigate={~p"/agent/#{@focused_agent.id}"}>Agent page</.link>
</p>
<% else %>
<p><strong>Inspect:</strong> Remote agent state is shown in mission control only.</p>
<% end %>
</details>
</div>
<% else %>
<p id="focused-agent-empty"><%= @focused_agent_empty_message %></p>
<% end %>
</article>
</section>
<section class="panel replay-panel">
<header class="panel-header">
<div>
<h2>Replay Controls</h2>
<p>Scrub retained snapshots without mutating live state.</p>
</div>
</header>
<%= if @timeline_snapshots == [] do %>
<p id="replay-empty"><%= @replay_empty_message %></p>
<% else %>
<form phx-change="select_snapshot" class="scrubber-form">
<input
id="snapshot-scrubber"
type="range"
name="snapshot_index"
min="0"
max={max(length(@timeline_snapshots) - 1, 0)}
value={@selected_snapshot_index}
/>
</form>
<p id="replay-current">
<strong>Current:</strong>
<%= if @display_snapshot do %>
<%= @display_snapshot.id %> · <%= @display_snapshot.trigger_kind %>
<% else %>
live runtime
<% end %>
</p>
<div class="snapshot-links">
<%= for snapshot <- Enum.take(Enum.reverse(@timeline_snapshots), 6) do %>
<.link
patch={graph_path(snapshot.id, @focused_agent_id)}
class={if @selected_snapshot_id == snapshot.id, do: "active-link", else: nil}
>
<%= snapshot.id %>
</.link>
<% end %>
</div>
<% end %>
</section>
<section class="mission-secondary-grid">
<article class="panel">
<header class="panel-header">
<div>
<h2><%= if @view_mode == "replay", do: "Snapshot Recommendation", else: "Pending Approvals" %></h2>
<p>
<%= if @view_mode == "replay" do %>
Structural recommendation state is pinned to the selected snapshot context.
<% else %>
Structural changes remain approval-gated.
<% end %>
</p>
</div>
</header>
<%= if @view_mode == "replay" do %>
<%= if @display_recommendation do %>
<article id="replay-recommendation-detail">
<p><strong>ID:</strong> <%= @display_recommendation.id %></p>
<p><strong>Node:</strong> <%= node_id_for(@display_recommendation) %></p>
<p><strong>Kind:</strong> <%= @display_recommendation.kind %></p>
<p><strong>Status:</strong> <%= @display_recommendation.status %></p>
<p>
<strong>Candidates:</strong>
<%= Enum.join(@display_recommendation.candidate_agent_ids, ", ") %>
</p>
</article>
<% else %>
<p id="recommendations-empty">No snapshot recommendation is associated with this view.</p>
<% end %>
<% else %>
<%= if pending_recommendations(@recommendations) == [] do %>
<p id="recommendations-empty">No pending structural recommendations.</p>
<% else %>
<ul id="recommendation-list">
<%= for recommendation <- pending_recommendations(@recommendations) do %>
<li id={"recommendation-#{recommendation.id}"}>
<strong><%= recommendation.kind %></strong>
<span> · <%= Enum.join(recommendation.candidate_agent_ids, " + ") %></span>
<span> · node <%= node_id_for(recommendation) %></span>
<span> · rel <%= format_score(recommendation.evidence.mean_relevance) %></span>
<%= if recommendation.evidence[:connection_weight] do %>
<span> · conn <%= format_score(recommendation.evidence.connection_weight) %></span>
<% end %>
<%= if recommendation.evidence[:knowledge_overlap] do %>
<span> · overlap <%= format_score(recommendation.evidence.knowledge_overlap) %></span>
<% end %>
<span>
·
<.link
patch={
graph_path(@selected_snapshot_id, recommendation_focus_id(recommendation))
}
>
focus in graph
</.link>
</span>
<button phx-click="approve_recommendation" phx-value-id={recommendation.id}>
Approve
</button>
<button phx-click="reject_recommendation" phx-value-id={recommendation.id}>
Reject
</button>
</li>
<% end %>
</ul>
<% end %>
<% end %>
</article>
<article class="panel">
<header class="panel-header">
<div>
<h2>Event Feed</h2>
<p>
<%= if @view_mode == "replay" do %>
Historical event playback is not retained yet, so live events are hidden here.
<% else %>
Live PubSub stream from the lattice runtime.
<% end %>
</p>
</div>
</header>
<%= if @events == [] do %>
<p id="event-log-empty">
<%= if @view_mode == "replay" do %>
Replay mode shows topology, results, and recommendation context without the live event stream.
<% else %>
<%= @event_empty_message %>
<% end %>
</p>
<% else %>
<ul id="event-log">
<%= for {event, index} <- Enum.with_index(@events) do %>
<li id={"event-#{index}"}>
<strong><%= event.id %></strong>
<span> · node <%= node_id_for(event) %></span>
<span> · <%= format_event_payload(event) %></span>
</li>
<% end %>
</ul>
<% end %>
</article>
</section>
</main>
"""
end
defp refresh_dashboard(socket, params) do
snapshots = HistoryStore.recent()
timeline_snapshots = Enum.reverse(snapshots)
snapshot_id = normalize_param(Map.get(params, "snapshot"))
focused_agent_id = normalize_param(Map.get(params, "agent"))
case snapshot_by_id(snapshots, snapshot_id) do
nil when snapshot_id != nil ->
socket
|> put_flash(:error, "Snapshot not found; showing live lattice.")
|> load_live_dashboard(snapshots, timeline_snapshots, focused_agent_id)
nil ->
socket
|> clear_flash()
|> load_live_dashboard(snapshots, timeline_snapshots, focused_agent_id)
snapshot ->
socket
|> clear_flash()
|> load_replay_dashboard(snapshot, snapshots, timeline_snapshots, focused_agent_id)
end
end
defp load_live_dashboard(socket, _snapshots, timeline_snapshots, focused_agent_id) do
cluster_summary = Syntropy.cluster_report()
agents = ClusterInventory.cluster_agents()
operator_status = build_operator_status(length(agents), cluster_summary)
latest_task_result = List.first(TaskScheduler.recent(1))
latest_task_snapshot = snapshot_for_task(latest_task_result)
active_agent_ids = ClusterInventory.busy_runtime_ids() |> MapSet.to_list()
selected_agent_ids = task_selected_runtime_ids(latest_task_result)
resolved_focus_id =
choose_focus_id(
agents,
focused_agent_id,
selected_agent_ids,
Enum.map(agents, &agent_identifier/1)
)
graph =
GraphViewModel.from_live(agents,
selected_agent_ids: selected_agent_ids,
active_agent_ids: active_agent_ids,
focused_agent_id: resolved_focus_id,
task_id: latest_task_result && latest_task_result.task_id
)
assign(socket,
current_params: current_params(nil, resolved_focus_id),
view_mode: "live",
timeline_snapshots: timeline_snapshots,
selected_snapshot_id: nil,
selected_snapshot_index: default_snapshot_index(timeline_snapshots, nil),
focused_agent_id: resolved_focus_id,
focused_agent: find_agent(agents, resolved_focus_id),
cluster_info: Syntropy.cluster_info(),
cluster_summary: cluster_summary,
operator_status: operator_status,
graph_empty_message: live_graph_empty_message(agents),
focused_agent_empty_message: live_focus_empty_message(agents),
replay_empty_message:
"No snapshots yet. Run a task in live mode to create the first replay checkpoint.",
result_empty_message: live_result_empty_message(operator_status),
event_empty_message: live_event_empty_message(operator_status),
graph_json: GraphViewModel.to_json(graph),
events: EventRecorder.recent(@event_limit),
recommendations: RecommendationStore.recent(@event_limit),
display_task_result: latest_task_result,
display_snapshot: latest_task_snapshot,
display_recommendation: nil
)
end
defp load_replay_dashboard(socket, snapshot, _snapshots, timeline_snapshots, focused_agent_id) do
cluster_summary = Syntropy.cluster_report()
operator_status =
build_operator_status(length(ClusterInventory.cluster_agents()), cluster_summary)
selected_task = snapshot_task(snapshot)
selected_recommendation = snapshot_recommendation(snapshot)
selected_agent_ids =
cond do
selected_task -> task_selected_runtime_ids(selected_task)
selected_recommendation -> recommendation_candidate_runtime_ids(selected_recommendation)
true -> []
end
resolved_focus_id =
choose_focus_id(
snapshot.agents,
focused_agent_id,
selected_agent_ids,
snapshot_ranked_runtime_ids(snapshot)
)
graph =
GraphViewModel.from_replay(snapshot,
selected_agent_ids: selected_agent_ids,
focused_agent_id: resolved_focus_id
)
assign(socket,
current_params: current_params(snapshot.id, resolved_focus_id),
view_mode: "replay",
timeline_snapshots: timeline_snapshots,
selected_snapshot_id: snapshot.id,
selected_snapshot_index: default_snapshot_index(timeline_snapshots, snapshot),
focused_agent_id: resolved_focus_id,
focused_agent: find_agent(snapshot.agents, resolved_focus_id),
cluster_info: Syntropy.cluster_info(),
cluster_summary: cluster_summary,
operator_status: operator_status,
graph_empty_message: replay_graph_empty_message(snapshot.agents),
focused_agent_empty_message: replay_focus_empty_message(snapshot.agents),
replay_empty_message:
"No snapshots yet. Run a task in live mode to create the first replay checkpoint.",
result_empty_message:
"This snapshot does not retain a completed task result. Pick another replay point or return to live mode.",
event_empty_message:
"Replay mode hides the live event stream. Return to live mode to watch retained runtime events.",
graph_json: GraphViewModel.to_json(graph),
events: [],
recommendations: [],
display_task_result: selected_task,
display_snapshot: snapshot,
display_recommendation: selected_recommendation
)
end
defp snapshot_for_task(nil), do: nil
defp snapshot_for_task(task_result) do
task_result.task_id
|> HistoryStore.for_task()
|> List.first()
end
defp snapshot_task(%{task_id: nil}), do: nil
defp snapshot_task(snapshot),
do: Enum.find(TaskScheduler.recent(@timeline_limit), &(&1.task_id == snapshot.task_id))
defp snapshot_recommendation(%{proposal_id: nil}), do: nil
defp snapshot_recommendation(snapshot), do: RecommendationStore.get(snapshot.proposal_id)
defp graph_node_id(%{} = snapshot, _task_result, _cluster_info), do: node_id_for(snapshot)
defp graph_node_id(nil, %{} = task_result, _cluster_info), do: node_id_for(task_result)
defp graph_node_id(nil, nil, cluster_info), do: cluster_info.node_id
defp repo_status(cluster_info) do
case Enum.find(cluster_info.services, &(&1.name == "Syntropy.Repo")) do
nil -> "disabled"
service -> service.status
end
end
defp build_operator_status(live_agent_count, cluster_summary) do
readiness = RuntimeStatus.readiness()
provider_details = get_in(readiness, [:checks, :provider, :details]) || %{}
persistence_details = get_in(readiness, [:checks, :persistence, :details]) || %{}
cluster_details = get_in(readiness, [:checks, :cluster, :details]) || %{}
provider_reasons = Map.get(provider_details, :reasons, [])
persistence_reasons = Map.get(persistence_details, :reasons, [])
cluster_reasons = Map.get(cluster_details, :reasons, [])
%{
provider: Map.get(provider_details, :provider, "mock"),
provider_status: get_in(readiness, [:checks, :provider, :status]) || "ready",
provider_reasons: provider_reasons,
persistence_status: Map.get(persistence_details, :status, "disabled"),
persistence_reasons: persistence_reasons,
readiness_status: Map.get(readiness, :status, "ready"),
readiness_detail: build_readiness_detail(provider_reasons, persistence_reasons),
cluster_reasons: cluster_reasons,
cluster_detail: build_cluster_detail(cluster_reasons),
cluster_status: Map.get(cluster_summary, :status, "disabled"),
seeding_posture: if(live_agent_count == 0, do: "empty", else: "seeded"),
live_agent_count: live_agent_count
}
end
defp build_readiness_detail([], []), do: nil
defp build_readiness_detail(provider_reasons, persistence_reasons) do
provider_reasons
|> Kernel.++(persistence_reasons)
|> Enum.join(" ")
end
defp build_cluster_detail([]), do: nil
defp build_cluster_detail(reasons), do: Enum.join(reasons, " ")
defp default_task_prompt do
"Should this system use a monolith, modular monolith, or microservices for the next product stage? Review reliability, security, product speed, cost, and operational risk."
end
defp selected_trace_entries(task_result) do
Enum.filter(task_result.selection_trace, & &1.selected)
end
defp selected_agent_names(task_result) do
task_result
|> selected_trace_entries()
|> Enum.map_join(", ", & &1.agent_name)
end
defp snapshot_label(nil), do: "No snapshot yet"
defp snapshot_label(snapshot), do: snapshot.id
defp selection_reason(entry) do
cond do
entry.relevance > 0.0 and entry.position >= 0.5 ->
"Selected because it matched the prompt and already had strong live lattice position."
entry.relevance > 0.0 ->
"Selected because it matched the submitted decision prompt."
entry.position >= 0.5 ->
"Selected because the lattice currently ranks this perspective as structurally useful."
true ->
"Selected to preserve multi-perspective coverage under the requested routing strategy."
end
end
defp focused_trace(_agent, nil), do: nil
defp focused_trace(agent, task_result) do
runtime_id = agent_identifier(agent)
local_id = Map.get(agent, :id)
task_result
|> Map.get(:selection_trace, [])
|> Enum.find(fn entry ->
Map.get(entry, :runtime_id) == runtime_id or Map.get(entry, :agent_id) == local_id
end)
end
defp focused_thought(_agent, nil), do: nil
defp focused_thought(agent, task_result) do
runtime_id = agent_identifier(agent)
local_id = Map.get(agent, :id)
task_result
|> Map.get(:thoughts, [])
|> Enum.find(fn thought ->
Map.get(thought, :runtime_id) == runtime_id or Map.get(thought, :agent_id) == local_id
end)
end
defp agent_run_role(%{selected: true}), do: "Selected in latest run"
defp agent_run_role(%{selected: false}), do: "Considered, not selected"
defp agent_run_role(nil), do: "Available agent"
defp agent_context_sentence(agent, %{selected: true} = trace) do
"#{agent.name} contributed the #{agent.perspective} perspective to the latest run. #{selection_reason(trace)}"
end
defp agent_context_sentence(agent, %{selected: false} = trace) do
"#{agent.name} was eligible for the latest run, but ranked below the selected set. #{selection_reason(trace)}"
end
defp agent_context_sentence(agent, nil) do
"#{agent.name} is part of the current lattice, but this view has no latest routing trace for it yet."
end
defp focused_route_signal(%{rank: rank, composite: composite}) do
"rank #{rank} · score #{format_score(composite)}"
end
defp focused_route_signal(nil), do: "No latest trace"
defp focused_agent_actions(agent, trace, operator_status) do
base_actions =
cond do
trace && trace.selected ->
[
"Use this perspective as part of the decision evidence, then compare it with the synthesis and replay.",
"If the selected perspective feels wrong for the prompt, refine the task or adjust the agent's knowledge/profile."
]
trace ->
[
"If this perspective should have been selected, inspect its prompt match, position, and knowledge coverage.",
"Improve the route by adding relevant knowledge or making the prompt name this risk explicitly."
]
true ->
[
"Run a task or open a replay snapshot to see whether this agent participates in routing.",
"Use its connections and position to understand whether it is isolated or structurally central."
]
end
provider_action =
if operator_status.provider == "mock" do
[
"Connect OpenAI or Ollama before judging final answer quality; mock mode validates workflow and deterministic fallback behavior."
]
else
[]
end
base_actions ++ provider_action ++ connection_actions(agent)
end
defp connection_actions(agent) do
case ordered_connections(agent) do
[] ->
[
"No retained connections yet. Run multi-agent tasks to create observable interaction edges."
]
_connections ->
[
"Use retained connections to see which perspectives repeatedly collaborate with this one."
]
end
end
defp task_button_label(true, _live_connected), do: "Running..."
defp task_button_label(_task_loading, false), do: "Connecting..."
defp task_button_label(_task_loading, _live_connected), do: "Run Task"
defp task_composer_hint("replay", _operator_status), do: "Replay mode is read-only."
defp task_composer_hint(_view_mode, %{readiness_status: status}) when status != "ready" do
"Provider configuration is incomplete. Syntropy will not dispatch live tasks until readiness is green."
end
defp task_composer_hint(_view_mode, %{live_agent_count: 0}) do
"Seed the lattice or add agents before running tasks."
end
defp task_composer_hint(_view_mode, _operator_status) do
"Submit a task against the current live lattice."
end
defp live_graph_empty_message([]) do
"No agents are loaded yet. Set SYNTROPY_DEMO_SEED=true or add agents from IEx/API to render the live lattice."
end
defp live_graph_empty_message(_agents), do: nil
defp replay_graph_empty_message([]) do
"This replay snapshot does not retain any agent state."
end
defp replay_graph_empty_message(_agents), do: nil
defp live_focus_empty_message([]) do
"No agent state is loaded yet. Seed the lattice or add agents to inspect the runtime."
end
defp live_focus_empty_message(_agents) do
"Select an agent from the live graph to inspect its retained state."
end
defp replay_focus_empty_message([]) do
"This snapshot has no retained agent state to inspect."
end
defp replay_focus_empty_message(_agents) do
"Select an agent from the retained graph to inspect the replay state."
end
defp live_result_empty_message(%{live_agent_count: 0}) do
"No task results yet because the lattice is empty. Load agents first, then run a task."
end
defp live_result_empty_message(_operator_status) do
"No completed tasks yet. Run a task to inspect selection trace, synthesis, and replay state."
end
defp live_event_empty_message(%{live_agent_count: 0}) do
"No retained events yet. Load agents, then run a task to start the live event stream."
end
defp live_event_empty_message(_operator_status) do
"No retained events yet. Run a task to start the live event stream."
end
defp snapshot_by_id(_snapshots, nil), do: nil
defp snapshot_by_id(snapshots, snapshot_id), do: Enum.find(snapshots, &(&1.id == snapshot_id))
defp choose_focus_id(agents, preferred_focus_id, selected_agent_ids, ranked_agent_ids) do
valid_ids = MapSet.new(Enum.map(agents, &agent_identifier/1))
cond do
preferred_focus_id && MapSet.member?(valid_ids, preferred_focus_id) ->
preferred_focus_id
focus_id = Enum.find(selected_agent_ids, &MapSet.member?(valid_ids, &1)) ->
focus_id
focus_id = Enum.find(ranked_agent_ids, &MapSet.member?(valid_ids, &1)) ->
focus_id
first_agent = List.first(agents) ->
agent_identifier(first_agent)
true ->
nil
end
end
defp find_agent(_agents, nil), do: nil
defp find_agent(agents, agent_id), do: Enum.find(agents, &(agent_identifier(&1) == agent_id))
defp current_params(snapshot_id, agent_id) do
%{}
|> maybe_put_param("snapshot", snapshot_id)
|> maybe_put_param("agent", agent_id)
end
defp graph_path(snapshot_id, agent_id) do
params = current_params(snapshot_id, agent_id)
~p"/?#{params}"
end
defp maybe_put_param(params, _key, nil), do: params
defp maybe_put_param(params, key, value), do: Map.put(params, key, value)
defp normalize_param(nil), do: nil
defp normalize_param(""), do: nil
defp normalize_param(value), do: value
defp snapshot_at_index([], _index), do: nil
defp snapshot_at_index(snapshots, index) do
with {parsed_index, ""} <- Integer.parse(index),
true <- parsed_index >= 0 do
Enum.at(snapshots, parsed_index)
else
_other -> nil
end
end
defp default_snapshot_index([], _snapshot), do: 0
defp default_snapshot_index(snapshots, nil), do: max(length(snapshots) - 1, 0)
defp default_snapshot_index(snapshots, snapshot) do
Enum.find_index(snapshots, &(&1.id == snapshot.id)) || max(length(snapshots) - 1, 0)
end
defp show_cluster_incident_panel?(cluster_summary) do
cluster_summary.unavailable_nodes != [] or cluster_summary.abandoned_task_incidents != []
end
defp cluster_peer_summary(%{expected_peers: []} = cluster_summary) do
Integer.to_string(length(cluster_summary.connected_peers))
end
defp cluster_peer_summary(cluster_summary) do
"#{length(cluster_summary.connected_peers)}/#{length(cluster_summary.expected_peers)}"
end
defp agent_identifier(agent) do
case Map.get(agent, :runtime_id) do
runtime_id when is_binary(runtime_id) and runtime_id != "" -> runtime_id
_other -> Map.fetch!(agent, :id)
end
end
defp task_selected_runtime_ids(nil), do: []
defp task_selected_runtime_ids(task_result) do
cond do
is_list(Map.get(task_result, :selected_agents)) and task_result.selected_agents != [] ->
Enum.map(task_result.selected_agents, &Map.get(&1, :runtime_id, &1.id))
is_list(Map.get(task_result, :selection_trace)) and task_result.selection_trace != [] ->
task_result.selection_trace
|> Enum.filter(& &1.selected)
|> Enum.map(&Map.get(&1, :runtime_id, &1.agent_id))
true ->
task_result
|> Map.get(:selected_agent_ids, [])
|> Enum.map(&runtime_identifier_or_local/1)
end
end
defp recommendation_candidate_runtime_ids(nil), do: []
defp recommendation_candidate_runtime_ids(recommendation) do
case get_in(recommendation, [:evidence, :candidate_agents]) do
candidate_agents when is_list(candidate_agents) and candidate_agents != [] ->
Enum.map(candidate_agents, &Map.get(&1, :runtime_id, &1.id))
_other ->
recommendation
|> Map.get(:candidate_agent_ids, [])
|> Enum.map(&runtime_identifier_or_local/1)
end
end
defp recommendation_focus_id(recommendation) do
recommendation
|> recommendation_candidate_runtime_ids()
|> List.first()
end
defp snapshot_ranked_runtime_ids(snapshot) do
runtime_ids_by_local_id = Map.new(snapshot.agents, &{&1.id, agent_identifier(&1)})
snapshot.ranked_agent_ids
|> Enum.map(&Map.get(runtime_ids_by_local_id, &1, &1))
end
defp runtime_identifier_or_local(agent_id) do
case Syntropy.ClusterInfo.parse_runtime_id(agent_id) do
{:ok, ref} -> ref.runtime_id
:error -> agent_id
end
end
end