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
syntropy lib syntropy_web live lattice_live.ex
Raw

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.Api.TaskRequest
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
max_prompt_bytes = TaskRequest.max_prompt_bytes()
cond do
String.trim(prompt) == "" ->
{:noreply, socket}
byte_size(prompt) > max_prompt_bytes ->
{:noreply,
assign(socket, task_error: "The prompt must be at most #{max_prompt_bytes} bytes.")}
true ->
{: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("seed_starter_lattice", _params, socket) do
:ok = Syntropy.Seeder.seed_demo_agents()
{:noreply,
socket
|> assign(task_error: nil)
|> refresh_dashboard(socket.assigns.current_params)}
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 %>
<div class="flash-toast flash-toast--error mb-4" role="alert">
<p id="dashboard-flash-error"><%= @flash["error"] %></p>
</div>
<% end %>
<section class="dashboard-header glass-panel p-4">
<div>
<h1>Mission control</h1>
<p class="body-copy">Live lattice, run observation, and replay evidence.</p>
</div>
<nav class="dashboard-nav">
<.link patch={~p"/"} class="text-link">Lattice</.link>
<span> · </span>
<.link navigate={~p"/tasks"} class="text-link">Tasks</.link>
<span> · </span>
<.link navigate={~p"/history"} class="text-link">History</.link>
<span> · </span>
<.link navigate={~p"/operations"} class="text-link">Operations</.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="body-copy">
<%= @operator_status.readiness_detail %>
</p>
<% end %>
<%= if @operator_status.cluster_detail do %>
<p id="cluster-status-detail" class="body-copy">
<%= @operator_status.cluster_detail %>
</p>
<% end %>
<div id="node-status" class="meta-copy" style="margin-top: 12px;">
<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="glass-panel detail-panel mb-4">
<header class="section-heading">
<div>
<h2>Cluster incidents</h2>
<p class="body-copy">Peer loss is fail-visible. Recover the node, then explicitly resubmit abandoned work.</p>
</div>
</header>
<div class="two-column-grid">
<%= 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 class="body-copy"> · <%= 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 class="body-copy"> · node <%= incident.node_id %></span>
<span class="body-copy"> · coordinator <%= incident.coordinator_node_id %></span>
</li>
<% end %>
</ul>
</div>
<% end %>
</div>
</section>
<% end %>
<div class="flex-col mb-4">
<article class="glass-panel detail-panel">
<header class="section-heading">
<div>
<h2>Task composer</h2>
</div>
</header>
<%= if @view_mode == "replay" do %>
<p id="task-replay-notice" class="body-copy">Return to live mode to run tasks.</p>
<% else %>
<%= if @operator_status.readiness_status != "ready" do %>
<p id="task-readiness-notice" class="body-copy">
Runtime is not ready. Finish the provider configuration shown above before running tasks.
</p>
<% else %>
<%= if @operator_status.live_agent_count == 0 do %>
<div id="task-empty-notice" class="empty-panel">
<h3>The lattice is empty</h3>
<p>
Deploy the starter perspectives to explore mission control right away,
or deploy a custom agent from the Operations page.
</p>
<div class="empty-panel-action flex-row">
<button
id="seed-starter-lattice"
type="button"
class="primary-button"
phx-click="seed_starter_lattice"
>
Deploy starter agents
</button>
<.link navigate={~p"/operations"} class="text-link">
Deploy a custom agent
</.link>
</div>
</div>
<% else %>
<form
id="task-form"
class="flex-col"
phx-submit={JS.push("submit_task") |> JS.focus(to: "#run-observation-panel")}
>
<textarea
name="task[prompt]"
rows="4"
maxlength={TaskRequest.max_prompt_bytes()}
placeholder={default_task_prompt()}
><%= @task_prompt %></textarea>
<div class="flex-row">
<select name="task[strategy]" style="max-width: 200px;">
<%= for {label, value} <- strategy_options() do %>
<option value={value} selected={@task_strategy == value}><%= label %></option>
<% end %>
</select>
<button type="submit" class="primary-button" disabled={@task_loading or not @live_connected}>
<%= task_button_label(@task_loading, @live_connected) %>
</button>
</div>
</form>
<% end %>
<% end %>
<% end %>
<%= if @task_error do %>
<p id="task-error" class="meta-label text-danger"><%= @task_error %></p>
<% end %>
</article>
<article id="run-observation-panel" class="glass-panel detail-panel result-panel" tabindex="-1">
<header class="section-heading">
<div>
<h2>Run observation</h2>
<p class="body-copy">
<%= 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">
<div class="four-column-grid mb-4">
<article class="run-context-card">
<span class="meta-label">Run</span>
<strong><%= @display_task_result.task_id %></strong>
<p class="body-copy">
<%= @display_task_result.strategy %> request resolved to
<%= @display_task_result.resolved_mode %> on <%= node_id_for(@display_task_result) %>.
</p>
</article>
<article class="run-context-card">
<span class="meta-label">Selected perspectives</span>
<strong><%= selected_agent_names(@display_task_result) %></strong>
</article>
<article class="run-context-card">
<span class="meta-label">Replay evidence</span>
<strong><%= snapshot_label(@display_snapshot) %></strong>
</article>
</div>
<section class="mb-4">
<h3 class="meta-label">
<%= if @operator_status.provider == "mock",
do: "Deterministic mock synthesis",
else: "Synthesis" %>
</h3>
<p class="body-copy" style="white-space: pre-wrap;"><%= @display_task_result.synthesis %></p>
</section>
<div id="selection-trace">
<h3 class="meta-label">Why these perspectives were selected</h3>
<div class="four-column-grid">
<%= for entry <- selected_trace_entries(@display_task_result) do %>
<article class="run-context-card">
<div class="flex-row" style="justify-content: space-between; margin-bottom: 8px;">
<strong><%= entry.agent_name %></strong>
<span class="meta-label" style="margin: 0;">Rank <%= entry.rank %></span>
</div>
<p class="body-copy" style="margin-bottom: 12px;"><%= selection_reason(entry) %></p>
<div class="two-column-grid" style="gap: 8px;">
<div class="meta-item">
<span class="meta-label">Prompt match</span>
<strong><%= format_score(entry.relevance) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Runtime position</span>
<strong><%= format_score(entry.position) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Capability</span>
<strong><%= format_score(entry.capacity) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Route score</span>
<strong><%= format_score(entry.composite) %></strong>
</div>
</div>
</article>
<% end %>
</div>
<details class="mt-4">
<summary class="meta-label" style="cursor: pointer;">Show considered agents and raw routing scores</summary>
<ul class="body-copy" style="margin-top: 8px;">
<%= 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 %>
<div class="empty-panel">
<p id="latest-result-empty" class="body-copy"><%= @result_empty_message %></p>
</div>
<% end %>
</article>
</div>
<section class="two-column-grid mb-4">
<article class="glass-panel detail-panel" style="grid-column: span 1;">
<header class="section-heading">
<div>
<h2>Lattice graph</h2>
<p class="body-copy">
<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" class="primary-button" phx-click="return_live">Return to live</button>
<% end %>
</header>
<%= if @graph_empty_message do %>
<p id="graph-empty-notice" class="body-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>
<% end %>
</article>
<article class="glass-panel detail-panel" style="grid-column: span 1;">
<header class="section-heading">
<div>
<h2>Focused agent</h2>
<p class="body-copy">
<%= if @focused_agent do %>
<%= @focused_agent.name %>
<% else %>
No agent selected
<% end %>
</p>
</div>
</header>
<%= if @focused_agent do %>
<div id="focused-agent-detail" class="flex-col">
<% latest_trace = focused_trace(@focused_agent, @display_task_result) %>
<% latest_thought = focused_thought(@focused_agent, @display_task_result) %>
<section class="mb-4">
<span class="meta-label">What this node means</span>
<h3><%= agent_run_role(latest_trace) %></h3>
<p class="body-copy"><%= agent_context_sentence(@focused_agent, latest_trace) %></p>
</section>
<div class="four-column-grid mb-4">
<article class="run-context-card">
<span class="meta-label">Perspective</span>
<strong><%= @focused_agent.perspective %></strong>
</article>
<article class="run-context-card">
<span class="meta-label">Lattice position</span>
<strong><%= format_score(@focused_agent.position) %></strong>
</article>
<article class="run-context-card">
<span class="meta-label">Latest route signal</span>
<strong><%= focused_route_signal(latest_trace) %></strong>
</article>
</div>
<%= if latest_trace do %>
<section class="mb-4 border-b pb-4">
<h3 class="meta-label">Why it ranked here</h3>
<p class="body-copy mb-4"><%= selection_reason(latest_trace) %></p>
<div class="two-column-grid" style="gap: 8px;">
<div class="meta-item">
<span class="meta-label">Prompt match</span>
<strong><%= format_score(latest_trace.relevance) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Runtime position</span>
<strong><%= format_score(latest_trace.position) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Capability</span>
<strong><%= format_score(latest_trace.capacity) %></strong>
</div>
<div class="meta-item">
<span class="meta-label">Route score</span>
<strong><%= format_score(latest_trace.composite) %></strong>
</div>
</div>
</section>
<% end %>
<section class="mb-4">
<h3 class="meta-label">How to act on this</h3>
<ul class="body-copy">
<%= for action <- focused_agent_actions(@focused_agent, latest_trace, @operator_status) do %>
<li><%= action %></li>
<% end %>
</ul>
</section>
<%= if latest_thought do %>
<section class="mb-4">
<h3 class="meta-label">Latest contribution</h3>
<p class="body-copy" style="white-space: pre-wrap;"><%= latest_thought.thought %></p>
</section>
<% end %>
<div class="mb-4">
<h3 class="meta-label">Connections</h3>
<%= if ordered_connections(@focused_agent) == [] do %>
<p id="focused-agent-connections-empty" class="body-copy">No retained connections.</p>
<% else %>
<ul id="focused-agent-connections" class="body-copy">
<%= 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="mt-4">
<summary class="meta-label" style="cursor: pointer;">Runtime metadata</summary>
<div class="body-copy" style="margin-top: 8px;">
<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}"} class="text-link">Agent page</.link>
</p>
<% else %>
<p><strong>Inspect:</strong> Remote agent state is shown in mission control only.</p>
<% end %>
</div>
</details>
</div>
<% else %>
<div class="empty-panel">
<p id="focused-agent-empty" class="body-copy"><%= @focused_agent_empty_message %></p>
</div>
<% end %>
</article>
</section>
<section class="glass-panel detail-panel mb-4">
<header class="section-heading">
<div>
<h2>Replay controls</h2>
</div>
</header>
<%= if @timeline_snapshots == [] do %>
<div class="empty-panel">
<p id="replay-empty" class="body-copy"><%= @replay_empty_message %></p>
</div>
<% else %>
<form phx-change="select_snapshot" class="flex-col">
<input
id="snapshot-scrubber"
type="range"
name="snapshot_index"
min="0"
max={max(length(@timeline_snapshots) - 1, 0)}
value={@selected_snapshot_index}
style="width: 100%;"
/>
</form>
<p id="replay-current" class="body-copy meta-label mt-2">
<strong>Current:</strong>
<%= if @display_snapshot do %>
<%= @display_snapshot.id %> · <%= @display_snapshot.trigger_kind %>
<% else %>
live runtime
<% end %>
</p>
<div class="flex-row" style="flex-wrap: wrap;">
<%= for snapshot <- Enum.take(Enum.reverse(@timeline_snapshots), 6) do %>
<.link
patch={graph_path(snapshot.id, @focused_agent_id)}
class={"text-link meta-label " <> if @selected_snapshot_id == snapshot.id, do: "text-primary", else: ""}
>
<%= snapshot.id %>
</.link>
<% end %>
</div>
<% end %>
</section>
<section class="two-column-grid">
<article class="glass-panel detail-panel">
<header class="section-heading">
<div>
<h2><%= if @view_mode == "replay", do: "Snapshot recommendation", else: "Pending approvals" %></h2>
</div>
</header>
<%= if @view_mode == "replay" do %>
<%= if @display_recommendation do %>
<article id="replay-recommendation-detail" class="body-copy">
<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 %>
<div class="empty-panel">
<p id="recommendations-empty" class="body-copy">No snapshot recommendation is associated with this view.</p>
</div>
<% end %>
<% else %>
<%= if pending_recommendations(@recommendations) == [] do %>
<div class="empty-panel">
<p id="recommendations-empty" class="body-copy">No pending structural recommendations.</p>
</div>
<% else %>
<ul id="recommendation-list" class="task-list">
<%= for recommendation <- pending_recommendations(@recommendations) do %>
<li id={"recommendation-#{recommendation.id}"} class="task-list-item">
<div class="task-title">
<strong><%= recommendation.kind %></strong>
<span> · <%= Enum.join(recommendation.candidate_agent_ids, " + ") %></span>
</div>
<div class="task-copy task-list-meta">
<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))
}
class="text-link"
>
focus in graph
</.link>
</span>
</div>
<div class="flex-row">
<button class="primary-button" phx-click="approve_recommendation" phx-value-id={recommendation.id}>
Approve
</button>
<button class="ds-button--destructive" phx-click="reject_recommendation" phx-value-id={recommendation.id}>
Reject
</button>
</div>
</li>
<% end %>
</ul>
<% end %>
<% end %>
</article>
<article class="glass-panel detail-panel">
<header class="section-heading">
<div>
<h2>Event feed</h2>
</div>
</header>
<%= if @events == [] do %>
<div class="empty-panel">
<p id="event-log-empty" class="body-copy">
<%= if @view_mode == "replay" do %>
Replay mode shows topology, results, and recommendation context without the live event stream.
<% else %>
<%= @event_empty_message %>
<% end %>
</p>
</div>
<% else %>
<ul id="event-log" class="task-list">
<%= for {event, index} <- Enum.with_index(@events) do %>
<li id={"event-#{index}"} class="task-list-item task-list-item--compact">
<strong class="task-title"><%= event.id %></strong>
<div class="task-copy">
<span>node <%= node_id_for(event) %></span>
<span> · <%= format_event_payload(event) %></span>
</div>
</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 live_graph_empty_message([]) do
"No agents are loaded yet. Deploy starter agents from the task composer or the Operations page 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(task_result) do
GraphViewModel.task_selected_runtime_ids(task_result)
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