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/agent_live.ex
defmodule SyntropyWeb.AgentLive do
use SyntropyWeb, :live_view
import SyntropyWeb.RuntimeViewHelpers
alias Syntropy.{HistoryStore, LatticeSupervisor, RecommendationStore, TaskScheduler}
@history_limit 20
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(Syntropy.PubSub, "lattice:events")
end
{:ok,
assign(socket,
agent: nil,
task_history: [],
recommendation_history: [],
snapshot_history: []
)}
end
@impl true
def handle_params(%{"id" => agent_id}, _uri, socket) do
{:noreply, load_agent(socket, agent_id)}
end
@impl true
def handle_info({:lattice_event, _event}, socket) do
agent_id = socket.assigns.agent && socket.assigns.agent.id
{:noreply, load_agent(socket, agent_id)}
end
@impl true
def render(assigns) do
~H"""
<main class="dashboard">
<section>
<p class="eyebrow">// SYNTROPY</p>
<h1>Agent Inspection</h1>
<nav>
<.link navigate={~p"/"}>Lattice</.link>
<span> · </span>
<.link navigate={~p"/tasks"}>Tasks</.link>
<span> · </span>
<.link navigate={~p"/history"}>History</.link>
</nav>
</section>
<%= if @agent do %>
<section id="agent-summary">
<h2><%= @agent.name %></h2>
<p><strong>ID:</strong> <%= @agent.id %></p>
<p><strong>Node:</strong> <%= Syntropy.node_id() %></p>
<p><strong>Perspective:</strong> <%= @agent.perspective %></p>
<p><strong>Position:</strong> <%= format_score(@agent.position) %></p>
<p><strong>Temporary:</strong> <%= @agent.temporary %></p>
<p>
<strong>Graph:</strong>
<.link navigate={graph_path(nil, @agent.id)}>Focus in live graph</.link>
</p>
</section>
<section>
<h2>Connections</h2>
<%= if map_size(@agent.connections) == 0 do %>
<p id="agent-connections-empty">No retained connections.</p>
<% else %>
<ul id="agent-connections">
<%= for {target_id, connection} <- Enum.sort_by(@agent.connections, fn {target_id, _} -> target_id end) do %>
<li id={"connection-#{target_id}"}>
<span><%= target_id %></span>
<span> · <%= format_score(connection.weight) %></span>
<span> · <%= connection.interactions %> interactions</span>
</li>
<% end %>
</ul>
<% end %>
</section>
<section>
<h2>Recent Task Participation</h2>
<%= if @task_history == [] do %>
<p id="agent-tasks-empty">No recent task participation.</p>
<% else %>
<ul id="agent-task-history">
<%= for entry <- @task_history do %>
<li id={"agent-task-#{entry.task.task_id}"}>
<span><%= entry.task.task_id %></span>
<span> · node <%= node_id_for(entry.task) %></span>
<span> · <%= entry.task.strategy %> -> <%= entry.task.resolved_mode %></span>
<%= if entry.snapshot do %>
<span>
·
<.link navigate={graph_path(entry.snapshot.id, @agent.id)}><%= entry.snapshot.id %></.link>
</span>
<% end %>
</li>
<% end %>
</ul>
<% end %>
</section>
<section>
<h2>Recent Recommendation Involvement</h2>
<%= if @recommendation_history == [] do %>
<p id="agent-recommendations-empty">No recent recommendation involvement.</p>
<% else %>
<ul id="agent-recommendations">
<%= for recommendation <- @recommendation_history do %>
<li id={"agent-recommendation-#{recommendation.id}"}>
<span><%= recommendation.id %></span>
<span> · node <%= node_id_for(recommendation) %></span>
<span> · <%= recommendation.kind %></span>
<span> · <%= recommendation.status %></span>
</li>
<% end %>
</ul>
<% end %>
</section>
<section>
<h2>Recent Snapshots</h2>
<%= if @snapshot_history == [] do %>
<p id="agent-snapshots-empty">No recent snapshots for this agent.</p>
<% else %>
<ul id="agent-snapshots">
<%= for snapshot <- @snapshot_history do %>
<li id={"agent-snapshot-#{snapshot.id}"}>
<.link navigate={graph_path(snapshot.id, @agent.id)}><%= snapshot.id %></.link>
<span> · node <%= node_id_for(snapshot) %></span>
<span> · <%= snapshot.trigger_kind %></span>
</li>
<% end %>
</ul>
<% end %>
</section>
<% else %>
<section>
<p id="agent-not-found">Agent not found.</p>
</section>
<% end %>
</main>
"""
end
defp load_agent(socket, nil),
do:
assign(socket,
agent: nil,
task_history: [],
recommendation_history: [],
snapshot_history: []
)
defp load_agent(socket, agent_id) do
agent = Enum.find(LatticeSupervisor.list_agents(), &(&1.id == agent_id))
assign(socket,
agent: agent,
task_history: recent_tasks_for(agent_id),
recommendation_history: recent_recommendations_for(agent_id),
snapshot_history: recent_snapshots_for(agent_id)
)
end
defp recent_tasks_for(nil), do: []
defp recent_tasks_for(agent_id) do
snapshots_by_task_id =
HistoryStore.recent(@history_limit)
|> Map.new(fn snapshot ->
{snapshot.task_id, snapshot}
end)
TaskScheduler.recent(@history_limit)
|> Enum.filter(fn task ->
agent_id in task.selected_agent_ids or agent_id in task.contributing_agent_ids
end)
|> Enum.map(fn task ->
%{
task: task,
snapshot: Map.get(snapshots_by_task_id, task.task_id)
}
end)
end
defp recent_recommendations_for(nil), do: []
defp recent_recommendations_for(agent_id) do
RecommendationStore.recent(@history_limit)
|> Enum.filter(fn recommendation ->
agent_id in recommendation.candidate_agent_ids or
recommendation.applied_agent_id == agent_id
end)
end
defp recent_snapshots_for(nil), do: []
defp recent_snapshots_for(agent_id) do
HistoryStore.recent(@history_limit)
|> Enum.filter(fn snapshot ->
Enum.any?(snapshot.agents, &(&1.id == agent_id))
end)
end
defp graph_path(snapshot_id, agent_id) do
params =
%{}
|> maybe_put("snapshot", snapshot_id)
|> maybe_put("agent", agent_id)
~p"/?#{params}"
end
defp maybe_put(params, _key, nil), do: params
defp maybe_put(params, key, value), do: Map.put(params, key, value)
end