File size: 3,948 Bytes
92d9567 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | defmodule PerplexityMacro.ControlPlane do
@moduledoc """
Elixir/OTP CONTROL PLANE β top of `Agent Console` stack.
Elixir / OTP
CONTROL PLANE (this module)
β
βββββββββββ΄βββββββββββ
βΌ βΌ
Python workers Elixir services
AI / ML / data concurrency / state
(Adapters, WASM: (VM, TraceHub 50k,
Python execution) ROM mailbox, ResearchBroker,
Phoenix LiveView)
β β
ββββββββββββ¬ββββββββββ
βΌ
Tool layer
APIs / WASM / DB
(Tavily, Wiki, WASM Box,
Swift Gateway typed JSON)
Browser WebLLM is the *instruction model* that drives this plane:
Browser WebLLM ββinstruction ABIββ> ControlPlane ββ> Python workers | Elixir services ββ> Tool layer
ControlPlane owns: fuel, transcript_hash, WORM seal, capability policy (CONFIRM 0x2A),
and routes `java.execute` β WASM Box vs `tool.call` β local or Swift gateway (optional).
"""
use GenServer
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
@impl true
def init(_opts) do
{:ok, %{python_workers: :pool, elixir_services: %{vm: PerplexityMacro.VM, trace_hub: PerplexityMacro.TraceHub, broker: PerplexityMacro.ResearchBroker},
gateway_status: :offline, state: %{fuel: 128, transcript_hash: 0, history: []}}}
end
# Single entry: instruction ABI {id, op: java.execute|tool.call|vm.execute|final}
def dispatch(instr), do: GenServer.call(__MODULE__, {:dispatch, instr}, 30_000)
def gateway_status, do: GenServer.call(__MODULE__, :gateway_status)
@impl true
def handle_call({:dispatch, %{op: "java.execute"}=instr}, _from, s) do
# Route to Python workers when Java/WASM is Python-backed (Pyodide/WASM Box)
# or to WASM Box when Java/WASM artifact is available β same capability bridge
result = case PerplexityMacro.WASMBox.execute(instr.code, %{}) do
{:ok, r} -> r
{:error, e} -> %{error: inspect(e)}
end
tlv = PerplexityMacro.Evidence.encode_tlv(result)
PerplexityMacro.VM.commit_evidence(tlv)
{:reply, {:ok, result, s.state}, s}
end
def handle_call({:dispatch, %{op: "tool.call"}=instr}, _from, s) do
# Capability bridge: local β Elixir services | external β Swift gateway (optional) β Tool layer
cap = instr.name |> String.to_atom()
route = if PerplexityMacro.Capability.sensitive?(cap) and s.gateway_status == :offline do
# sensitive caps require gateway or CONFIRM β fallback to local typed mock
{:local, :mock}
else
{:gateway_or_local, cap}
end
result = case route do
{:local, _} -> mock_tool(instr)
{:gateway_or_local, _} ->
case PerplexityMacro.ToolRouter.route(%{capability: cap, query: instr.arguments["query"] || ""}) do
{:ok, tlv} -> tlv
{:error, :confirm_required} -> %{error: "CONFIRM 0x2A required"}
end
end
tlv = PerplexityMacro.Evidence.encode_tlv(result)
PerplexityMacro.VM.commit_evidence(tlv)
{:reply, {:ok, result, s.state}, s}
end
def handle_call({:dispatch, %{op: "vm.execute"}}, _from, s), do: {:reply, {:ok, PerplexityMacro.VM.snapshot(), s.state}, s}
def handle_call({:dispatch, %{op: "final", content: c}}, _from, s), do: {:reply, {:ok, %{final: c}, s.state}, s}
def handle_call(:gateway_status, _from, s), do: {:reply, s.gateway_status, s}
defp mock_tool(%{name: name, arguments: args}) do
%{title: name, url: "#{name}://mock", content: inspect(args), source_hash: :erlang.phash2(args, 0xFFFFFFFF)}
end
end
|