evolution 9 min read

Mycelial Network Architecture: Bio-Inspired System Design

Bio-inspired mycelial network topology for cross-domain signal propagation, nutrient routing, pattern extraction, and adaptive load distribution in a distributed platform.

Tomas Korcak (korczis) 377 words

Mycelial networks in nature solve a hard problem: distributing nutrients and signals across vast, decentralized organisms with no central coordinator. The Prismatic Platform borrows this metaphor for cross-domain coordination. When an OSINT discovery affects a DD case, when a security scan reveals a risk that impacts an investigation, or when a pattern in one domain matches anomalies in another, the mycelial network propagates these signals to the right consumers.

The Biological Metaphor

In forest ecosystems, mycorrhizal networks connect trees through underground fungal filaments. Trees share nutrients, warn each other of threats, and allocate resources to where they are needed most. The platform's mycelial network mirrors these properties:

Biological ConceptPlatform AnalogImplementation
Hyphae (filaments)PubSub channelsPhoenix.PubSub topics
Nutrient transferData propagationStructured signal messages
Chemical signalsDomain eventsEvent structs with metadata
Mycelium nodesDomain routersGenServer signal processors
Nutrient allocationLoad distributionDemand-based routing
Threat warningsAlert propagationPriority signal escalation

Network Topology

The mycelial network is organized in three layers: the substrate layer (raw PubSub), the routing layer (domain-scoped signal processors), and the intelligence layer (pattern extraction and correlation):

defmodule PrismaticMycelium.Network do
  @moduledoc """
  Core mycelial network coordinator.

  Manages the three-layer signal propagation topology:
  substrate (PubSub), routing (domain processors),
  and intelligence (pattern extraction).
  """

  use GenServer
  require Logger

  @type signal :: %{
    id: String.t(),
    source_domain: atom(),
    target_domains: [atom()] | :broadcast,
    type: atom(),
    payload: map(),
    priority: :low | :normal | :high | :critical,
    timestamp: DateTime.t(),
    ttl: non_neg_integer()
  }

  @domains ~w(osint dd security investigation perimeter academy)a

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @spec propagate(signal()) :: :ok
  def propagate(signal) do
    GenServer.cast(__MODULE__, {:propagate, signal})
  end

  @spec subscribe(atom()) :: :ok
  def subscribe(domain) when domain in @domains do
    Phoenix.PubSub.subscribe(PrismaticWeb.PubSub, "mycelium:#{domain}")
  end

  @impl GenServer
  def init(_opts) do
    state = %{
      signal_count: 0,
      domain_stats: Map.new(@domains, fn d -> {d, %{sent: 0, received: 0}} end),
      active_patterns: []
    }

    {:ok, state}
  end

  @impl GenServer
  def handle_cast({:propagate, signal}, state) do
    targets = resolve_targets(signal)
    enriched = enrich_signal(signal, state)

    for domain <- targets do
      Phoenix.PubSub.broadcast(
        PrismaticWeb.PubSub,
        "mycelium:#{domain}",
        {:mycelial_signal, enriched}
      )
    end

    new_state = update_stats(state, signal.source_domain, targets)
    {:noreply, new_state}
  end

  defp resolve_targets(%{target_domains: :broadcast}), do: @domains
  defp resolve_targets(%{target_domains: targets}), do: targets

  defp enrich_signal(signal, state) do
    Map.merge(signal, %{
      hop_count: 0,
      network_load: state.signal_count,
      propagation_id: generate_propagation_id()
    })
  end

  defp generate_propagation_id do
    Base.encode16(:crypto.strong_rand_bytes(8), case: :lower)
  end
end

Domain-Scoped Signal Processing

Each domain has a signal processor that filters, transforms, and acts on incoming mycelial signals. The processor implements domain-specific logic for how signals are consumed:

defmodule PrismaticMycelium.Processors.DDProcessor do
  @moduledoc """
  DD domain signal processor.

  Processes mycelial signals relevant to due diligence:
  entity risk changes from OSINT, security findings,
  investigation updates, and perimeter alerts.
  """

  use GenServer
  require Logger

  alias PrismaticMycelium.Network

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl GenServer
  def init(_opts) do
    Network.subscribe(:dd)
    {:ok, %{processed: 0, actions_taken: 0}}
  end

  @impl GenServer
  def handle_info({:mycelial_signal, signal}, state) do
    case process_signal(signal) do
      {:action, action} ->
        execute_action(action)
        {:noreply, %{state | processed: state.processed + 1, actions_taken: state.actions_taken + 1}}

      :ignored ->
        {:noreply, %{state | processed: state.processed + 1}}
    end
  end

  defp process_signal(%{type: :entity_risk_change, payload: payload}) do
    case PrismaticDD.Cases.find_by_entity(payload.entity_id) do
      [] -> :ignored
      cases ->
        {:action, %{
          type: :update_case_risk,
          cases: cases,
          new_risk: payload.new_risk_score,
          source: payload.source
        }}
    end
  end

  defp process_signal(%{type: :security_finding, priority: priority})
       when priority in [:high, :critical] do
    {:action, %{type: :escalate_to_analyst, priority: priority}}
  end

  defp process_signal(_signal), do: :ignored

  defp execute_action(%{type: :update_case_risk} = action) do
    for dd_case <- action.cases do
      PrismaticDD.Cases.update_risk_from_signal(dd_case, action.new_risk, action.source)
      Logger.info("DD case #{dd_case.id} risk updated via mycelial signal")
    end
  end
end

Nutrient Routing

The nutrient routing system implements demand-based resource allocation. Domains that need more processing capacity signal their demand, and the network redistributes resources:

defmodule PrismaticMycelium.NutrientRouter do
  @moduledoc """
  Demand-based resource allocation across domains.

  Monitors domain processing queues and redistributes
  worker capacity based on real-time demand signals.
  Implements exponential decay for demand smoothing.
  """

  use GenServer
  require Logger

  @check_interval :timer.seconds(10)
  @decay_factor 0.9

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl GenServer
  def init(_opts) do
    schedule_check()
    {:ok, %{demand: %{}, allocations: %{}, history: []}}
  end

  @impl GenServer
  def handle_info(:check_demand, state) do
    current_demand = measure_demand()
    smoothed = smooth_demand(state.demand, current_demand)
    new_allocations = compute_allocations(smoothed)

    apply_allocations(new_allocations, state.allocations)

    schedule_check()
    {:noreply, %{state | demand: smoothed, allocations: new_allocations}}
  end

  defp measure_demand do
    for domain <- PrismaticMycelium.Network.domains(), into: %{} do
      queue_depth = PrismaticMycelium.Processors.queue_depth(domain)
      processing_rate = PrismaticMycelium.Processors.processing_rate(domain)
      {domain, %{queue: queue_depth, rate: processing_rate}}
    end
  end

  defp smooth_demand(previous, current) do
    Map.merge(current, previous, fn _key, curr, prev ->
      %{
        queue: curr.queue * (1 - @decay_factor) + prev.queue * @decay_factor,
        rate: curr.rate * (1 - @decay_factor) + prev.rate * @decay_factor
      }
    end)
  end

  defp compute_allocations(demand) do
    total_demand = demand |> Map.values() |> Enum.map(& &1.queue) |> Enum.sum()

    if total_demand == 0 do
      Map.new(demand, fn {domain, _} -> {domain, 1.0 / map_size(demand)} end)
    else
      Map.new(demand, fn {domain, %{queue: q}} -> {domain, q / total_demand} end)
    end
  end

  defp schedule_check, do: Process.send_after(self(), :check_demand, @check_interval)
end

Pattern Extraction

The intelligence layer detects recurring signal patterns across the network. When the same type of signal appears from multiple domains within a time window, it may indicate a systemic issue or a cross-domain correlation:

defmodule PrismaticMycelium.PatternExtractor do
  @moduledoc """
  Detects recurring patterns in mycelial signal flows.

  Uses sliding time windows to identify signal clusters,
  cross-domain correlations, and anomalous propagation
  patterns that may indicate systemic issues.
  """

  use GenServer
  require Logger

  @window_size :timer.minutes(5)
  @min_cluster_size 3

  @type pattern :: %{
    id: String.t(),
    signal_type: atom(),
    domains: [atom()],
    frequency: non_neg_integer(),
    first_seen: DateTime.t(),
    last_seen: DateTime.t(),
    confidence: float()
  }

  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @spec detected_patterns() :: [pattern()]
  def detected_patterns do
    GenServer.call(__MODULE__, :get_patterns)
  end

  @impl GenServer
  def init(_opts) do
    for domain <- PrismaticMycelium.Network.domains() do
      PrismaticMycelium.Network.subscribe(domain)
    end

    schedule_analysis()
    {:ok, %{signals: [], patterns: []}}
  end

  @impl GenServer
  def handle_info({:mycelial_signal, signal}, state) do
    entry = %{
      type: signal.type,
      source: signal.source_domain,
      timestamp: signal.timestamp,
      payload_hash: :erlang.phash2(signal.payload)
    }

    pruned = prune_window(state.signals ++ [entry])
    {:noreply, %{state | signals: pruned}}
  end

  @impl GenServer
  def handle_info(:analyze, state) do
    patterns = extract_patterns(state.signals)

    for pattern <- patterns, pattern.confidence > 0.7 do
      Logger.info("Mycelial pattern detected: #{pattern.signal_type} across #{inspect(pattern.domains)}")
    end

    schedule_analysis()
    {:noreply, %{state | patterns: patterns}}
  end

  defp extract_patterns(signals) do
    signals
    |> Enum.group_by(& &1.type)
    |> Enum.filter(fn {_type, group} -> length(group) >= @min_cluster_size end)
    |> Enum.map(fn {type, group} ->
      domains = group |> Enum.map(& &1.source) |> Enum.uniq()
      %{
        id: Base.encode16(:crypto.strong_rand_bytes(4), case: :lower),
        signal_type: type,
        domains: domains,
        frequency: length(group),
        first_seen: group |> Enum.map(& &1.timestamp) |> Enum.min(DateTime),
        last_seen: group |> Enum.map(& &1.timestamp) |> Enum.max(DateTime),
        confidence: length(domains) / length(PrismaticMycelium.Network.domains())
      }
    end)
  end

  defp prune_window(signals) do
    cutoff = DateTime.add(DateTime.utc_now(), -@window_size, :millisecond)
    Enum.filter(signals, fn s -> DateTime.compare(s.timestamp, cutoff) == :gt end)
  end

  defp schedule_analysis, do: Process.send_after(self(), :analyze, :timer.seconds(30))
end
MetricValueDescription
Signal latency< 5msTime from propagate/1 to processor receipt
Pattern detection window5 minutesSliding window for cluster analysis
Min cluster size3 signalsMinimum signals to form a pattern
Confidence threshold0.7Min cross-domain ratio for alert
Demand check interval10 secondsNutrient routing rebalance frequency

The mycelial network provides a coordination layer that would be difficult to achieve with direct module-to-module dependencies. Domains remain loosely coupled while still responding to events across the platform. The biological metaphor guides design decisions toward resilient, self-organizing patterns that scale naturally with platform growth.