Powered by AppSignal & Oban Pro

Lesson 02 Livebook: Signal Window

livebooks/02_signal_window.livemd

Lesson 02 Livebook: Signal Window

The wake cycle is the same. The boundary around it is not.

Mix.install([
  {:helios_fleet, path: "../02_signal_window"}
])

Application.ensure_all_started(:helios_fleet)

Helpers

defmodule Lesson02Helpers do
  alias HeliosFleet.Runtime
  alias Jido.AgentServer

  def unique_agent_id(prefix) do
    "#{prefix}-#{System.unique_integer([:positive])}"
  end

  def safe_stop(agent_id) do
    case Runtime.whereis(agent_id) do
      nil -> :ok
      _pid -> Runtime.stop_agent(agent_id)
    end
  end

  def wait_until(fun, attempts \\ 20)

  def wait_until(fun, attempts) when attempts > 0 do
    case fun.() do
      nil ->
        Process.sleep(50)
        wait_until(fun, attempts - 1)

      value ->
        value
    end
  end

  def wait_until(_fun, 0) do
    raise "condition was not met before the timeout"
  end

  def runtime_snapshot(pid) do
    {:ok, state} = AgentServer.state(pid)
    HeliosFleet.mission_snapshot(state.agent)
  end
end

Run A Short Contact Window

alias HeliosFleet
alias Jido.AgentServer
alias Jido.Signal

probe_id = Lesson02Helpers.unique_agent_id("khepri")
{:ok, pid} = HeliosFleet.start_probe(probe_id)

{:ok, _probe} =
  AgentServer.call(
    pid,
    Signal.new!(
      "fleet.probe.subsystem_checked",
      %{
        subsystem: "power_bus",
        status: "nominal",
        detail: "reactor stable at hotel load"
      },
      source: "/relay/sol-echo"
    )
  )

{:ok, _probe} =
  AgentServer.call(
    pid,
    Signal.new!(
      "fleet.probe.subsystem_checked",
      %{
        subsystem: "thermal_loop",
        status: "nominal",
        detail: "radiator petals holding shadow-side equilibrium",
        thermal_state: "shadow_stable"
      },
      source: "/relay/sol-echo"
    )
  )

{:ok, _probe} =
  AgentServer.call(
    pid,
    Signal.new!(
      "fleet.probe.subsystem_checked",
      %{
        subsystem: "attitude_control",
        status: "nominal",
        detail: "reaction wheels trimmed against drift"
      },
      source: "/relay/sol-echo"
    )
  )

{:ok, _probe} =
  AgentServer.call(
    pid,
    Signal.new!(
      "fleet.probe.subsystem_checked",
      %{
        subsystem: "sensor_mast",
        status: "nominal",
        detail: "lidar aperture clean after dust shake"
      },
      source: "/relay/sol-echo"
    )
  )

{:ok, _probe} =
  AgentServer.call(
    pid,
    Signal.new!(
      "fleet.probe.target_assigned",
      %{
        name: "ice fragment 6R",
        safe_standoff_m: 1_500,
        scan_profile: "passive_then_lidar"
      },
      source: "/orpheus/mission"
    )
  )

:ok =
  AgentServer.cast(
    pid,
    Signal.new!("fleet.probe.survey_burn_requested", %{}, source: "/orpheus/mission")
  )

runtime_state =
  Lesson02Helpers.wait_until(fn ->
    snapshot = Lesson02Helpers.runtime_snapshot(pid)

    if snapshot.mission_phase == "survey_ready" do
      snapshot
    end
  end)

Lesson02Helpers.safe_stop(probe_id)

runtime_state