Powered by AppSignal & Oban Pro

Jidoka: Phoenix LiveView Consumer Boundary

18_phoenix_liveview_consumer.livemd

Jidoka: Phoenix LiveView Consumer Boundary

Run in Livebook

Jidoka does not need to render Phoenix components. The reusable boundary is an AgentView: it chooses the agent, conversation id, runtime context, and projection shape that a LiveView can render.

Setup

Mix.install(
  [
    {:jidoka, git: "https://github.com/mikehostetler/jidoka.git", ref: "924a486f3c1b7e7a943cb3d5ceee0de65f158467"},
    {:kino, "~> 0.19.0"}
  ],
  config: [
    jidoka: [
      model_aliases: %{fast: "anthropic:claude-haiku-4-5"}
    ]
  ]
)
Jidoka.Kino.setup()

Define An AgentView

defmodule LivebookDemo.AgentView.SupportAgent do
  use Jidoka.Agent

  agent do
    id :livebook_agent_view_support

    schema Zoi.object(%{
      channel: Zoi.string() |> Zoi.default("livebook"),
      session: Zoi.string()
    })
  end

  defaults do
    model :fast
    instructions "You are a support assistant."
  end
end

defmodule LivebookDemo.AgentView.SupportView do
  use Jidoka.AgentView,
    agent: LivebookDemo.AgentView.SupportAgent

  @impl true
  def conversation_id(session) do
    session
    |> Map.get("conversation_id", "demo")
    |> Jidoka.AgentView.normalize_id("demo")
  end

  @impl true
  def agent_id(session), do: "livebook-agent-view-#{conversation_id(session)}"

  @impl true
  def runtime_context(session) do
    %{
      channel: "livebook",
      session: conversation_id(session),
      actor: %{id: Map.get(session, "actor_id", "demo-user")}
    }
  end
end

Project Runtime State

session = %{"conversation_id" => "onboarding-demo", "actor_id" => "user-1"}

{:ok, pid} = LivebookDemo.AgentView.SupportView.start_agent(session)
{:ok, view} = LivebookDemo.AgentView.SupportView.snapshot(pid, session)

Map.take(view, [
  :agent_id,
  :conversation_id,
  :runtime_context,
  :visible_messages,
  :streaming_message,
  :status
])

A LiveView can optimistically append the user’s message before the async agent turn completes.

pending = LivebookDemo.AgentView.SupportView.before_turn(view, "My invoice is confusing.")

Map.take(pending, [:status, :visible_messages, :streaming_message])

The lower-level projection remains available for debugging.

{:ok, projection} = Jidoka.Agent.View.snapshot(pid)

projection
|> Map.take([:visible_messages, :streaming_message, :llm_context, :events])

Optional Provider Turn

Jidoka.Kino.chat("AgentView-backed chat", fn ->
  LivebookDemo.AgentView.SupportAgent.chat(
    pid,
    "Explain the AgentView boundary in one sentence.",
    context: LivebookDemo.AgentView.SupportView.runtime_context(session)
  )
end)