---
title: "Put the knowledge layer in your app: from a LiveView screen, from any language, or from an agent"
slug: knowledge-layer-7-include-it-in-your-app
date: 2026-10-12
author: Semurg
section: how-to
tags: [knowledge-graph, graph-rag, elixir, liveview, sdk, mcp]
summary: The same memory, reached three ways: from a Phoenix LiveView screen with the Elixir client, from any language over HTTP or a client library, and from an AI agent over MCP. What one writes, the others read.
status: published
covers: [route:POST /v1/memory/remember, route:POST /v1/memory/context, route:POST /v1/mcp, mcp:memory_remember, mcp:memory_context]
---
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](/blog/hello-semurg-first-calls-from-a-client) 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](/blog/knowledge-layer-4-graph-rag) shows the context call with `curl`, and
[connecting over REST](/blog/connect-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](/blog/agent-memory-mcp-five-minutes) connects one, and
the [agents page](/agents) 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](/blog/one-memory-per-end-user) guide shows how.

### For coding agents: Rules for including Semurg's knowledge layer in an app

- [ ] Use one scope name per shared memory, and the same name from every screen, program and agent that should share it.
- [ ] Give each end user their own memory with user_id on every memory call when the app serves many users, set by the app's server from the signed-in user, never chosen by the agent or the browser.
- [ ] Show or use an answer from memory_context only when grounding.state is grounded; otherwise say the memory does not know.
- [ ] Keep no copy of the answer beyond the request or event that asked for it; ask the memory again next time.

The check that proves it:

```bash
curl -sS -X POST http://localhost:4000/v1/mcp -H "Authorization: Bearer $SEMURG_API_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "MCP-Protocol-Version: 2025-06-18" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"memory_remember","arguments":{"scope":"include-intro","facts":{"entities":[{"name":"Dana","type":"Person"},{"name":"Acme","type":"Company"}],"relations":[{"source":"Dana","target":"Acme","type":"WORKS_AT"}]}}}}' > /dev/null && curl -sS http://localhost:4000/v1/memory/context -H "Authorization: Bearer $SEMURG_API_TOKEN" -H "Content-Type: application/json" -d '{"scope":"include-intro","query":"Dana Acme"}'
```

Passes when: a fact an agent stored over MCP comes back from the context call your app makes over HTTP

## Next

- [Graph RAG that says when it does not know](/blog/knowledge-layer-4-graph-rag): what the context call returns.
