Blog / How-to guides

Put the knowledge layer in your app: from a LiveView screen, from any language, or from an agent

12 October 2026 · Semurg · 4 min read · Markdown

Verified: every command below ran on a fresh node of r11.78.20261012.bc0d94f (how)

knowledge-graphgraph-ragelixirliveviewsdkmcp

Semurg's knowledge layer is one store with three ways in: your app's own screens, any program that can make an HTTP call, and AI agents over MCP. They all read and write the same graph in the same scope, so a fact an agent stores during a conversation is on your app's screen a moment later, and a record your app writes is in the agent's next answer.

From a Phoenix LiveView screen

In the build pattern this series follows, the screens are Phoenix LiveView and all data lives in Semurg. A screen that asks the memory a question and shows the cited answer needs one call. With the Elixir client from Semurg's client archive (see your first Semurg calls for getting it):

elixir
defmodule MyAppWeb.AskLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    client = Semurg.Client.new("http://localhost:4000", token: System.fetch_env!("SEMURG_API_TOKEN"))
    {:ok, assign(socket, client: client, answer: nil)}
  end

  def handle_event("ask", %{"q" => question}, socket) do
    case Semurg.Client.context(socket.assigns.client, %{scope: "support-desk", query: question}) do
      {:ok, answer} -> {:noreply, assign(socket, answer: answer)}
      {:error, error} -> {:noreply, put_flash(socket, :error, "Memory refused: #{inspect(error)}")}
    end
  end

  def render(assigns) do
    ~H"""
    <form phx-submit="ask"><input name="q" placeholder="Ask the desk's memory" /></form>
    <%= if @answer && @answer["grounding"]["state"] == "grounded" do %>
      <ul><li :for={item <- @answer["items"]}><%= item["text"] %> <small><%= inspect(item["citation"]) %></small></li></ul>
    <% else %>
      <p :if={@answer}>The memory does not know this yet.</p>
    <% end %>
    """
  end
end

Semurg.Client.context/2 returns {:ok, map} with string keys, or {:error, %Semurg.Error{}} carrying the refusal's kind, status and message, which the screen shows rather than hides. The screen shows an answer only when the memory says it is grounded, meaning every content word of the question is covered by what came back and at least one item can be cited, and says plainly when it is not. Show the items' citations beside them too: grounded is a check on the question's words, not proof that the answer is right. The screen holds the last answer only to render it and keeps no store of its own; the next question asks the memory again, which is the build pattern's rule: Semurg is the only place the data lives.

From any language

Every memory call is plain HTTP with the node's API token, so any language reaches the same store. The graph RAG post shows the context call with curl, and connecting over REST covers every route. Client libraries in eleven languages are one download beside the node (/dl/clients.tar.gz), built from the same commit as the release; each sends the same JSON bodies to the same routes.

From an agent

An agent connected over MCP calls the same memory as tools: memory_remember to store what it learns and memory_context to read before it answers. Give your agent persistent memory with MCP in five minutes connects one, and the agents page has a tested program for each agent framework.

One scope, or one per user

Everything above shares the scope it names. When your app serves many users, give each user a memory of their own with user_id on every call (leave the key out entirely for your own scope). Each user's memory is kept apart, and a call reaches only the user_id it names. So set user_id in your app's server code from the signed-in user, never from what a user or an agent sends: anyone holding your token can name any of your users. The one memory per end user guide shows how.

Next

How these commands were checked

Our test farm ran each of the 1 command block marked runnable, exactly as written, with the answer checked against the expectation printed under it, against a fresh node of the published release r11.78.20261012.bc0d94f. A post is published only when every one of them passed there.

Related