---
title: "A knowledge graph your agents build as they work, with every fact traced to its source"
slug: knowledge-layer-1-the-knowledge-graph
date: 2026-10-12
author: Semurg
section: how-to
tags: [knowledge-graph, memory, agents, provenance]
summary: Store entities, the relationships between them and the passages they came from in one call, recall them by name with each item cited back to its source, and add relationships later. The first post on Semurg's knowledge layer.
status: published
covers: [route:POST /v1/memory/remember, route:POST /v1/memory/recall, route:POST /v1/memory/relate, mcp:memory_remember, mcp:memory_recall, mcp:memory_relate]
---
An agent learns things in pieces: a customer's name in one conversation, their company in another, a contract clause in a document.
A knowledge graph keeps those pieces as entities and the relationships between them, so the next question can follow the
connections instead of searching the whole history again. In Semurg the graph is built from what you store, kept on your own disk, and
every fact it gives back says where it came from.

This post stores a few facts with the passage they came from, recalls them by name, and adds a relationship later.

## Store facts with their source

One call can carry entities, the relationships between them, and the passages of text they came from, with a label for the source and
the date it was true. The entities and relationships are the graph; the passages are the evidence.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/remember \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope": "kg-intro", "date": "2026-10-01", "source": "kickoff call notes",
       "facts": {"entities": [{"name": "Priya Raman", "type": "Person"},
                              {"name": "Northwind Retail", "type": "Company"},
                              {"name": "Invoice export", "type": "Feature"}],
                 "relations": [{"source": "Priya Raman", "target": "Northwind Retail", "type": "WORKS_AT"},
                               {"source": "Northwind Retail", "target": "Invoice export", "type": "USES"}]},
       "passages": [{"text": "Priya Raman from Northwind Retail said their finance team runs the invoice export every Monday.",
                     "mentions": ["Priya Raman", "Northwind Retail", "Invoice export"]}]}'
```

```expect
expect: the facts and the passage are stored
assert: json:.ok==true
```

Each entity is stored once, whatever number of calls name it later, and each relationship is its own record, linked to the entities at
its two ends. A relationship may also name an entity an earlier call stored; one that names an entity nobody stored is refused, by
name, and nothing is stored, so a misspelt name never slips into the graph.

## Recall by name, with every item cited

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/recall \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope": "kg-intro", "subject": "Priya Raman"}'
```

```expect
expect: an answer about Priya Raman, facts first, each item carrying its citation
assert: json:.ok==true and .state=="answered" and ([.items[] | select(has("citation"))] | length) > 0
```

Recall starts from the name you give it and returns its own facts first (Priya Raman works at Northwind Retail), then what is one step
away: the entities it connects to and the passages that mention it. To follow connections further, `memory_context` walks up to three
steps out; the graph RAG post covers it. Every item carries a citation: a fact or a passage cites the document it came from, when that
was stored and the exact span it quotes, and an entity cites itself, its node in the graph. The answer also says what it is:
`answered`, `nothing_stored` when nothing matched (an empty answer that says so, never an invented one), or `could_not_judge` with the
stage that could not run.

## Add a relationship later

Relationships learned later join the same graph. Here, the agent learns that Priya reported an issue with the feature:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/memory/relate \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope": "kg-intro", "from": "Priya Raman", "to": "Invoice export", "type": "REPORTED_ISSUE_WITH"}'
```

```expect
expect: the relationship is stored (edge_present true)
assert: json:.ok==true and .edge_present==true
```

`memory_relate` stores either end that is not there yet, as an entity without a type, so it never fails for a missing name; the
reply says the relationship is present.

The same three calls are MCP tools, `memory_remember`, `memory_recall` and `memory_relate`, so an agent connected over MCP builds and
reads the same graph as your code over HTTP.

## Raw text, read into entities by a model

Facts you send are stored as they are, with no model involved. Semurg can also read raw text into entities with an extraction model
running on your own node. How quickly that model reads a passage, and that it installs as the default on a fresh node, are not yet
measured on a published release, so this post does not show it; a later post will, with the measurement.

## Agent block

### For coding agents: Rules for an agent using Semurg's knowledge graph

- [ ] Send what you learn as facts: entities with a type, and relationships between named entities, in one memory_remember call, with the passage they came from and a source label.
- [ ] Spell names the same way every time: Semurg merges case and spacing variants and a few safe patterns; any other close spelling comes back as possible_same and is not merged.
- [ ] A relationship may name an entity stored earlier; if memory_remember is refused because an entity is unknown, store that entity first.
- [ ] Before answering about someone or something, call memory_recall with its name and read state before the items: nothing_stored means you do not know, not that the answer is no.
- [ ] Quote the citation of any fact you repeat to the user.

The check that proves it:

```bash
curl -sS http://localhost:4000/v1/memory/recall -H "Authorization: Bearer $SEMURG_API_TOKEN" -H "Content-Type: application/json" -d '{"scope": "kg-intro", "subject": "Priya Raman"}'
```

Passes when: it answers state answered, with a citation on its items

## What it does not do yet

- Read raw text into entities on a node you install: the extraction model does it when it is installed and serving, and the reply says which, but how it does on a fresh node has not been measured from outside yet.
- Answer as of a past date, or let a fact expire: both are coming.

## Next

- [One memory per end user](/blog/one-memory-per-end-user): the same graph, kept apart per user.
