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 controllers api agent_controller.ex
Raw

lib/syntropy_web/controllers/api/agent_controller.ex

defmodule SyntropyWeb.Api.AgentController do
use SyntropyWeb, :controller
alias Syntropy.{ClusterInventory, EventRecorder, LatticeSupervisor, TaskScheduler}
alias SyntropyWeb.Api.{AgentJSON, AgentRequest, ControllerHelpers, Listing}
@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
def index(conn, params) do
case Listing.parse_opts(params, [:q, :perspective]) do
{:ok, opts} ->
agents = Listing.filter_agents(ClusterInventory.cluster_agents(), opts)
limit = Keyword.fetch!(opts, :limit)
json(conn, %{
data: agents |> Enum.take(limit) |> Enum.map(&AgentJSON.agent/1),
meta: %{has_more: length(agents) > limit, next_cursor: nil}
})
{:error, :invalid_cursor} ->
ControllerHelpers.invalid_request(conn, "Invalid cursor.", [])
end
end
@spec show(Plug.Conn.t(), map()) :: Plug.Conn.t()
def show(conn, %{"id" => agent_id}) do
case ClusterInventory.find_agent(agent_id) do
nil -> ControllerHelpers.not_found(conn, "Agent", agent_id)
agent -> json(conn, %{data: AgentJSON.agent(agent)})
end
end
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
def create(conn, params) do
with {:ok, request} <- AgentRequest.parse(params),
{:ok, agent_id} <-
LatticeSupervisor.add_agent(request.name, request.perspective, request.opts) do
agent = LatticeSupervisor.get_agent(agent_id)
EventRecorder.record("agent_created", %{
agent_id: agent.id,
runtime_id: agent.runtime_id,
node_id: agent.node_id,
agent_name: agent.name,
perspective: agent.perspective,
source: "api"
})
conn
|> put_status(:created)
|> json(%{data: AgentJSON.agent(agent)})
else
{:error, errors} when is_list(errors) ->
ControllerHelpers.invalid_request(conn, "Invalid agent request.", errors)
{:error, {:already_started, _pid}} ->
ControllerHelpers.conflict(
conn,
"agent_exists",
"An agent with this id is already running."
)
{:error, _reason} ->
ControllerHelpers.conflict(conn, "agent_not_started", "The agent could not be started.")
end
end
@spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t()
def delete(conn, %{"id" => agent_id}) do
case LatticeSupervisor.get_agent(agent_id) do
nil ->
ControllerHelpers.not_found(conn, "Agent", agent_id)
agent ->
if agent.runtime_id in TaskScheduler.active_agent_ids() do
ControllerHelpers.conflict(
conn,
"agent_busy",
"Agent #{agent_id} is executing an active run and cannot be removed."
)
else
remove_agent(conn, agent)
end
end
end
defp remove_agent(conn, agent) do
case LatticeSupervisor.remove_agent(agent.id) do
:ok ->
EventRecorder.record("agent_removed", %{
agent_id: agent.id,
runtime_id: agent.runtime_id,
node_id: agent.node_id,
agent_name: agent.name,
perspective: agent.perspective,
source: "api"
})
json(conn, %{data: %{id: agent.id, removed: true}})
{:error, :not_found} ->
ControllerHelpers.not_found(conn, "Agent", agent.id)
end
end
end