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 lattice_supervisor.ex
Raw

lib/syntropy/lattice_supervisor.ex

defmodule Syntropy.LatticeSupervisor do
@moduledoc """
DynamicSupervisor-backed lattice container.
"""
use DynamicSupervisor
alias Syntropy.{Agent, ClusterInfo, ClusterInventory, KnowledgeStore, LatticeMath}
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts \\ []) do
DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
DynamicSupervisor.init(strategy: :one_for_one)
end
@spec add_agent(String.t(), String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
def add_agent(name, perspective, opts \\ []) do
id = Keyword.get(opts, :id, generate_id(name))
node_id = Keyword.get(opts, :node_id, ClusterInfo.node_id())
runtime_id = Keyword.get(opts, :runtime_id, ClusterInfo.runtime_id(id, node_id))
child_opts = [
id: id,
node_id: node_id,
runtime_id: runtime_id,
name: name,
perspective: perspective,
knowledge_items: Keyword.get(opts, :knowledge_items, []),
score_vector:
Keyword.get(opts, :score_vector, %{knowledge: 0.0, synthesis: 0.0, connections: 0.0}),
connections: Keyword.get(opts, :connections, %{}),
position: Keyword.get(opts, :position, 0.0),
temporary: Keyword.get(opts, :temporary, false),
parent_ids: Keyword.get(opts, :parent_ids, []),
parent_runtime_ids: Keyword.get(opts, :parent_runtime_ids, []),
lineage_ids: Keyword.get(opts, :lineage_ids, [id]),
source_names: Keyword.get(opts, :source_names, [name]),
perspective_labels: Keyword.get(opts, :perspective_labels, [perspective])
]
case DynamicSupervisor.start_child(__MODULE__, {Agent, child_opts}) do
{:ok, _pid} ->
ClusterInventory.notify_change(:topology_changed)
{:ok, id}
other ->
other
end
end
@doc """
Terminates a locally supervised agent and clears its retained knowledge.
Works for both permanent and temporary agents. Returns `{:error, :not_found}`
when no agent with the given id is registered on this node.
"""
@spec remove_agent(String.t()) :: :ok | {:error, :not_found}
def remove_agent(agent_id) do
case Registry.lookup(Syntropy.Registry, {:agent, agent_id}) do
[{pid, _value}] ->
:ok = DynamicSupervisor.terminate_child(__MODULE__, pid)
KnowledgeStore.remove_all(agent_id)
ClusterInventory.notify_change(:topology_changed)
:ok
[] ->
{:error, :not_found}
end
end
@spec list_agents() :: [Syntropy.Agent.state()]
def list_agents do
__MODULE__
|> DynamicSupervisor.which_children()
|> Enum.flat_map(fn {_id, pid, _type, _modules} ->
if Process.alive?(pid) do
try do
[GenServer.call(pid, :get_state)]
catch
:exit, _reason -> []
end
else
[]
end
end)
|> LatticeMath.sort_agents()
end
@spec get_agent(String.t()) :: Syntropy.Agent.state() | nil
def get_agent(agent_id) do
Agent.get_state(agent_id)
catch
:exit, _reason -> nil
end
@spec join(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def join(agent_a_id, agent_b_id) do
with agent_a <- Agent.get_state(agent_a_id),
agent_b <- Agent.get_state(agent_b_id) do
joined = LatticeMath.join(Map.from_struct(agent_a), Map.from_struct(agent_b))
add_agent_from_map(joined)
end
catch
:exit, reason -> {:error, reason}
end
@spec meet(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def meet(agent_a_id, agent_b_id) do
with agent_a <- Agent.get_state(agent_a_id),
agent_b <- Agent.get_state(agent_b_id) do
met = LatticeMath.meet(Map.from_struct(agent_a), Map.from_struct(agent_b))
add_agent_from_map(met)
end
catch
:exit, reason -> {:error, reason}
end
@spec apply_persistent_join(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def apply_persistent_join(agent_a_id, agent_b_id) do
with agent_a <- Agent.get_state(agent_a_id),
agent_b <- Agent.get_state(agent_b_id) do
agent_a
|> Map.from_struct()
|> then(&LatticeMath.join(&1, Map.from_struct(agent_b)))
|> Map.put(:temporary, false)
|> add_agent_from_map()
end
catch
:exit, reason -> {:error, reason}
end
@spec apply_persistent_meet(String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
def apply_persistent_meet(agent_a_id, agent_b_id) do
with agent_a <- Agent.get_state(agent_a_id),
agent_b <- Agent.get_state(agent_b_id) do
agent_a
|> Map.from_struct()
|> then(&LatticeMath.meet(&1, Map.from_struct(agent_b)))
|> Map.put(:temporary, false)
|> add_agent_from_map()
end
catch
:exit, reason -> {:error, reason}
end
defp generate_id(name) do
slug =
name
|> String.downcase()
|> String.replace(~r/[^a-z0-9]+/u, "-")
|> String.trim("-")
"#{slug}-#{System.unique_integer([:positive])}"
end
defp add_agent_from_map(agent) do
add_agent(agent.name, agent.perspective,
id: agent.id,
knowledge_items: agent.knowledge_items,
score_vector: agent.score_vector,
connections: agent.connections,
position: agent.position,
temporary: agent.temporary,
parent_ids: agent.parent_ids,
parent_runtime_ids: Map.get(agent, :parent_runtime_ids, []),
lineage_ids: agent.lineage_ids,
node_id: Map.get(agent, :node_id, ClusterInfo.node_id()),
runtime_id: Map.get(agent, :runtime_id, ClusterInfo.runtime_id(agent.id)),
source_names: agent.source_names,
perspective_labels: agent.perspective_labels
)
end
end