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/task_router.ex
defmodule Syntropy.TaskRouter do
@moduledoc """
Deterministic semantic routing using the shared runtime scoring contract.
"""
import Bitwise
alias Syntropy.Contract
@type routing_candidate :: %{
agent: map(),
relevance: float(),
position: float(),
capacity: float(),
score: float()
}
@routing_keyword_limit 18
@spec rank_agents(String.t(), [map()], MapSet.t(String.t())) :: [routing_candidate()]
def rank_agents(prompt, agents, busy_agent_ids \\ MapSet.new()) do
scoring_contract = Contract.runtime_scoring_contract()
prompt_context = %{
vector: embed(prompt, scoring_contract),
tokens: prompt |> tokenize(scoring_contract) |> MapSet.new()
}
agents
|> Enum.map(&candidate_for_agent(&1, prompt_context, busy_agent_ids, scoring_contract))
|> Enum.sort_by(fn candidate ->
{-candidate.score, -candidate.relevance, -candidate.position, candidate.agent.name,
Map.get(candidate.agent, :runtime_id, candidate.agent.id)}
end)
end
defp candidate_for_agent(agent, prompt_context, busy_agent_ids, scoring_contract) do
corpus =
Map.get(agent, :knowledge_items, [])
|> Enum.flat_map(&routing_terms/1)
|> Kernel.++([agent.perspective])
|> Enum.uniq()
|> Enum.sort()
corpus_text = Enum.join(corpus, " ")
corpus_tokens = corpus_text |> tokenize(scoring_contract) |> MapSet.new()
lexical_overlap = lexical_overlap(prompt_context.tokens, corpus_tokens)
cosine_similarity =
prompt_context.vector
|> cosine_similarity(embed(corpus_text, scoring_contract))
raw_relevance = if lexical_overlap > 0.0, do: cosine_similarity * lexical_overlap, else: 0.0
relevance =
min(
1.0,
max(raw_relevance, 0.0) + source_focus_bonus(agent, prompt_context, scoring_contract)
)
position = agent.position
capacity = capacity(agent, busy_agent_ids, scoring_contract)
weights = get_in(scoring_contract, ["routing", "weights"])
score =
max(
0.0,
relevance * weights["relevance"] + position * weights["position"] +
capacity * weights["capacity"]
)
%{
agent: agent,
relevance: relevance,
position: position,
capacity: capacity,
score: score
}
end
defp source_focus_bonus(agent, prompt_context, scoring_contract) do
perspective_tokens =
agent.perspective
|> tokenize(scoring_contract)
|> MapSet.new()
if MapSet.size(perspective_tokens) > 0 and
MapSet.subset?(perspective_tokens, prompt_context.tokens) do
0.28
else
0.0
end
end
defp capacity(agent, busy_agent_ids, scoring_contract) do
capacity_scores = get_in(scoring_contract, ["routing", "capacity_scores"])
runtime_id = Map.get(agent, :runtime_id, agent.id)
cond do
MapSet.member?(busy_agent_ids, runtime_id) or MapSet.member?(busy_agent_ids, agent.id) ->
capacity_scores["busy"]
Map.get(agent, :temporary, false) ->
capacity_scores["temporary"]
true ->
capacity_scores["idle"]
end
end
defp tokenize(text, scoring_contract) do
token_pattern = get_in(scoring_contract, ["routing", "embedding", "token_pattern"])
regex = Regex.compile!(token_pattern)
text
|> String.downcase()
|> then(&Regex.scan(regex, &1))
|> List.flatten()
end
defp routing_terms(item) when is_binary(item) do
if String.length(item) > 180 or String.contains?(item, "\n") do
keywords =
item
|> String.downcase()
|> then(&Regex.scan(~r/[a-z0-9][a-z0-9_-]+/, &1))
|> List.flatten()
|> Enum.reject(&routing_stopword?/1)
|> Enum.uniq()
|> Enum.take(@routing_keyword_limit)
case keywords do
[] -> []
terms -> [Enum.join(terms, " ")]
end
else
[item]
end
end
defp routing_terms(_item), do: []
defp routing_stopword?(token) do
token in ~w[
a an and are as at be by can do for from has if in into is it its keep more no not of on
one or should so that the their them this to under use when while with without you your
action actions evidence perspective risks verdict review selected response task
]
end
defp embed(text, scoring_contract) do
width = get_in(scoring_contract, ["routing", "embedding", "vector_width"])
vector =
text
|> tokenize(scoring_contract)
|> Enum.reduce(List.duplicate(0.0, width), fn token, acc ->
Enum.zip_with(acc, token_vector(token, width), &Kernel.+/2)
end)
norm =
vector
|> Enum.reduce(0.0, fn value, acc -> acc + value * value end)
|> :math.sqrt()
if norm == 0.0 do
vector
else
Enum.map(vector, &(&1 / norm))
end
end
defp token_vector(token, width) do
digest = :crypto.hash(:sha256, token)
for index <- 0..(width - 1) do
byte = :binary.at(digest, div(index, 8))
if band(byte, 1 <<< rem(index, 8)) != 0, do: 1.0, else: -1.0
end
end
defp lexical_overlap(prompt_tokens, corpus_tokens) do
if MapSet.size(prompt_tokens) == 0 or MapSet.size(corpus_tokens) == 0 do
0.0
else
overlap =
prompt_tokens
|> MapSet.intersection(corpus_tokens)
|> MapSet.size()
if overlap == 0 do
0.0
else
overlap / :math.sqrt(MapSet.size(prompt_tokens) * MapSet.size(corpus_tokens))
end
end
end
defp cosine_similarity(left, right) do
if Enum.any?(left, &(&1 != 0.0)) and Enum.any?(right, &(&1 != 0.0)) do
Enum.zip_with(left, right, fn left_value, right_value -> left_value * right_value end)
|> Enum.sum()
else
0.0
end
end
end