Powered by AppSignal & Oban Pro

Lesson 08 Livebook: AI Studio Mode

livebooks/08_ai_studio_mode.livemd

Lesson 08 Livebook: AI Studio Mode

This notebook keeps the AI example deterministic by using the lesson’s fake adapter instead of a real model call.

Mix.install([
  {:ai_studio_mode, path: "../08_ai_studio_mode"}
])

Application.ensure_all_started(:ai_studio_mode)

Helpers

defmodule Lesson08Helpers do
  alias AIStudioMode.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(ceo_id) do
    child_ids = [
      "#{ceo_id}/product_manager/designer",
      "#{ceo_id}/product_manager/qa",
      "#{ceo_id}/product_manager",
      "#{ceo_id}/cto"
    ]

    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(ceo_id) do
      nil -> :ok
      _pid -> StudioJido.stop_agent(ceo_id)
    end
  end
end

Run One AI-Assisted Planning Round

alias AIStudioMode.{AIAdapters, CEOAgent, StudioJido}
alias Jido.AgentServer
alias Jido.Signal

ceo_id = Lesson08Helpers.unique_agent_id("ceo-ai")

{:ok, ceo_pid} =
  StudioJido.start_agent(CEOAgent,
    id: ceo_id,
    initial_state: %{ai_adapter: AIAdapters.FakeAdapter}
  )

{:ok, _ceo} =
  AgentServer.call(
    ceo_pid,
    Signal.new!("studio.ai_staff_requested", %{}, source: "/ceo")
  )

staffed_ceo_state =
  Lesson08Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(ceo_pid)

    if Map.has_key?(state.children, :cto) and Map.has_key?(state.children, :product_manager) do
      state
    end
  end)

pm_pid = staffed_ceo_state.children.product_manager.pid

_staffed_pm_state =
  Lesson08Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(pm_pid)

    if Map.has_key?(state.children, :designer) and Map.has_key?(state.children, :qa) do
      state
    end
  end)

{:ok, _ceo} =
  AgentServer.call(
    ceo_pid,
    Signal.new!(
      "studio.ai_planning_round_requested",
      %{
        round: 1,
        milestone: "vertical slice",
        player_promise: "make combat onboarding readable"
      },
      source: "/ceo"
    )
  )

final_ceo_state =
  Lesson08Helpers.wait_until(fn ->
    {:ok, state} = AgentServer.state(ceo_pid)

    if length(state.agent.state.alignment_packets) == 1 do
      state
    end
  end)

Lesson08Helpers.safe_stop_family(ceo_id)

%{
  alignment_packets: final_ceo_state.agent.state.alignment_packets
}