Powered by AppSignal & Oban Pro

Lesson 06 Livebook: Engineering Org

livebooks/06_engineering_org.livemd

Lesson 06 Livebook: Engineering Org

Lesson 6 adds the execution layer: the CTO delegates through an engineering manager, engineers do the work, and delivery aggregates back up.

Mix.install([
  {:engineering_org, path: "../06_engineering_org"}
])

Application.ensure_all_started(:engineering_org)

Helpers

defmodule Lesson06Helpers do
  alias EngineeringOrg.StudioJido

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

  def wait_until(fun, attempts \\ 40)

  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 safe_stop_family(cto_id) do
    child_ids = [
      "#{cto_id}/engineering_manager/gameplay_engineer",
      "#{cto_id}/engineering_manager/ui_engineer",
      "#{cto_id}/engineering_manager"
    ]

    Enum.each(child_ids, fn child_id ->
      case StudioJido.whereis(child_id) do
        nil -> :ok
        _pid -> StudioJido.stop_agent(child_id)
      end
    end)

    case StudioJido.whereis(cto_id) do
      nil -> :ok
      _pid -> StudioJido.stop_agent(cto_id)
    end
  end
end

Fan Work Out And Aggregate It Back

alias EngineeringOrg.{CTOAgent, StudioJido}
alias Jido.AgentServer
alias Jido.Signal

cto_id = Lesson06Helpers.unique_agent_id("cto")
{:ok, cto_pid} = StudioJido.start_agent(CTOAgent, id: cto_id)

{:ok, _cto} =
  AgentServer.call(
    cto_pid,
    Signal.new!("studio.execution_layer_requested", %{}, source: "/cto")
  )

cto_state =
  Lesson06Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(cto_pid)

    if Map.has_key?(state.children, :engineering_manager) do
      state
    end
  end)

manager_pid = cto_state.children.engineering_manager.pid

{:ok, _manager} =
  AgentServer.call(
    manager_pid,
    Signal.new!("engineering.team_bootstrap_requested", %{milestone: "vertical slice"},
      source: "/cto"
    )
  )

manager_state =
  Lesson06Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(manager_pid)

    if Map.has_key?(state.children, :gameplay_engineer) and Map.has_key?(state.children, :ui_engineer) do
      state
    end
  end)

{:ok, _gameplay_engineer} =
  AgentServer.call(
    manager_state.children.gameplay_engineer.pid,
    Signal.new!(
      "engineer.task_assigned",
      %{task: "combat timing pass", deliverable: "combat loop feels responsive"},
      source: "/engineering_manager"
    )
  )

{:ok, _ui_engineer} =
  AgentServer.call(
    manager_state.children.ui_engineer.pid,
    Signal.new!(
      "engineer.task_assigned",
      %{task: "hud readability pass", deliverable: "combat hud is legible"},
      source: "/engineering_manager"
    )
  )

final_manager_state =
  Lesson06Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(manager_pid)

    if length(state.agent.state.completed_reports) == 2 do
      state
    end
  end)

final_cto_state =
  Lesson06Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(cto_pid)

    if state.agent.state.milestone_snapshots != [] do
      state
    end
  end)

Lesson06Helpers.safe_stop_family(cto_id)

%{
  engineer_reports: final_manager_state.agent.state.completed_reports,
  milestone_snapshots: final_cto_state.agent.state.milestone_snapshots
}