---
title: "Knowledge graph: sources and concepts beside the Documents"
description: "The read-only KnowledgeGraph port with its search and neighbours routes, the Wissensgraph tab of the Explore panel, the memory adapter for tests and the @skaile/forge-editor-kgs add-on over the KGS SDK."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/knowledge-graph"
---
# Knowledge graph: sources and concepts beside the Documents

> The read-only KnowledgeGraph port with its search and neighbours routes, the Wissensgraph tab of the Explore panel, the memory adapter for tests and the @skaile/forge-editor-kgs add-on over the KGS SDK.

A **knowledge graph** is a graph the host keeps outside the Documents: ingested sources, concepts and notes with typed edges between them. The editor does not store it and does not write to it. The host passes one `KnowledgeGraph`; the editor adds two read routes, a client call and the **Wissensgraph** tab of the [Explore panel](https://skaile-ai.github.io/forge-editor/guides/explore).

<table>
<thead>
  <tr>
    <th>
      Piece
    </th>
    
    <th>
      What it does
    </th>
    
    <th>
      Port (capability)
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        KnowledgeGraph
      </code>
    </td>
    
    <td>
      <code>
        search(q, user)
      </code>
      
      , <code>
        neighbours(id, user)
      </code>
      
       and optionally <code>
        origin(id, user)
      </code>
    </td>
    
    <td>
      <code>
        knowledge
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /api/editor/knowledge/search?q=
      </code>
    </td>
    
    <td>
      the nodes <code>
        q
      </code>
      
       finds, best first
    </td>
    
    <td>
      <code>
        knowledge
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /api/editor/knowledge/nodes/:id/neighbours
      </code>
    </td>
    
    <td>
      a node, the nodes next to it and the edges between them
    </td>
    
    <td>
      <code>
        knowledge
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        EditorBackend['knowledge']
      </code>
    </td>
    
    <td>
      <code>
        search(query, signal?)
      </code>
      
       and <code>
        neighbours(id, signal?)
      </code>
    </td>
    
    <td>
      <code>
        knowledge
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Explore tab <code>
        knowledge
      </code>
    </td>
    
    <td>
      the nodes found for the open Block's title; a click shows the neighbours
    </td>
    
    <td>
      <code>
        index
      </code>
      
       and <code>
        knowledge
      </code>
    </td>
  </tr>
</tbody>
</table>

A node is **not a Block** and an edge is **not an Edge** of the [Block index](https://skaile-ai.github.io/forge-editor/guides/edges): the graph's `kind` (`Source`, `Concept`, …) and its edge types (`References`, `DerivedFrom`, …) are its own words, shown as written.

## The port

`EditorPorts.knowledge` (`@skaile/forge-editor/server`), the shapes in `@skaile/forge-editor/core`:

```ts
interface KnowledgeGraph {
  /** The nodes `q` finds, best first. */
  search(q: string, user: object): Promise<KnowledgeHit[]>
  /** The node `id` (first), the nodes next to it, and the edges that join them. An unknown id: both lists empty. */
  neighbours(id: string, user: object): Promise<KnowledgeNeighbours>
  /** Optional: the nodes `id` was made from (its sources). */
  origin?(id: string, user: object): Promise<KnowledgeNode[]>
}

interface KnowledgeNode { id: string, label: string, kind: string }
interface KnowledgeHit extends KnowledgeNode { score: number }
interface KnowledgeEdge { from: string, to: string, kind: string }
interface KnowledgeNeighbours { nodes: KnowledgeNode[], edges: KnowledgeEdge[] }
```

<table>
<thead>
  <tr>
    <th>
      Type
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        KnowledgeNode
      </code>
    </td>
    
    <td>
      <code>
        id
      </code>
      
       is stable; <code>
        label
      </code>
      
       is what the node is called (its id for a node without a title); <code>
        kind
      </code>
      
       is what it is, in the graph's words
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        KnowledgeHit
      </code>
    </td>
    
    <td>
      a node with the <code>
        score
      </code>
      
       the graph gave it. Higher is better; the scale is the graph's own and only orders the list
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        KnowledgeEdge
      </code>
    </td>
    
    <td>
      a directed edge between two node ids; every edge of a <code>
        KnowledgeNeighbours
      </code>
      
       has both ends in its <code>
        nodes
      </code>
    </td>
  </tr>
</tbody>
</table>

`origin` is not served by a route yet; an add-on or a host reads it from the port.

## Routes

<table>
<thead>
  <tr>
    <th>
      Route (<code>
        API
      </code>
      
       key)
    </th>
    
    <th>
      Request
    </th>
    
    <th>
      Answer
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        GET /api/editor/knowledge/search
      </code>
      
       (<code>
        knowledgeSearch
      </code>
      
      )
    </td>
    
    <td>
      <code>
        ?q=
      </code>
    </td>
    
    <td>
      <code>
        KnowledgeHit[]
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /api/editor/knowledge/nodes/:id/neighbours
      </code>
      
       (<code>
        knowledgeNeighbours
      </code>
      
      )
    </td>
    
    <td>
      none
    </td>
    
    <td>
      <code>
        KnowledgeNeighbours
      </code>
    </td>
  </tr>
</tbody>
</table>

Both routes are served only when `ports.knowledge` is present, for every signed-in user: the graph is the host's, and the `user` the port receives is where a host that keeps per-user access applies it.

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      <code>
        q
      </code>
      
       missing, blank or longer than <code>
        KNOWLEDGE_QUERY_MAX
      </code>
      
       (500) characters; an id blank or longer than <code>
        KNOWLEDGE_ID_MAX
      </code>
      
       (200)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401
      </code>
    </td>
    
    <td>
      no user
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        501
      </code>
    </td>
    
    <td>
      the host has no <code>
        knowledge
      </code>
      
       port
    </td>
  </tr>
</tbody>
</table>

## Provide a port

```ts
import { createMemoryKnowledgeGraph } from '@skaile/forge-editor/adapters/knowledge'
import type { EditorPorts } from '@skaile/forge-editor/server'

// Two nodes and one edge, for a test or a demo.
const knowledge: NonNullable<EditorPorts['knowledge']> = createMemoryKnowledgeGraph(
  [
    { id: 'n1', label: 'Wissensgraph Grundlagen', kind: 'Concept' },
    { id: 'n2', label: 'Wissensgraph Quelle', kind: 'Source' },
  ],
  [{ from: 'n1', to: 'n2', kind: 'DerivedFrom' }],
)
```

`createMemoryKnowledgeGraph(nodes, edges)` searches the words of the labels: a node is a hit when a word of its label starts with a word of the query (case and accents ignored), the `score` is the share of the query's words found (`1` = all), the best first, at most `MEMORY_KNOWLEDGE_LIMIT` (20). `neighbours` answers the node, the nodes joined to it in either direction and every edge among them; an edge to a node that is not listed is left out. `origin(id)` answers the nodes `id` has an outgoing `DerivedFrom` edge to.

A real graph is the host's. The add-on [`@skaile/forge-editor-kgs`](#the-kgs-add-on) wraps the KGS SDK.

## The Wissensgraph tab

With the backend capability `knowledge`, `exploreTabsOf(backend)` lists a fourth tab, `knowledge` („Wissensgraph“ / “Knowledge graph”), after `trail`. A backend without the capability has no such tab.

The `ExploreModel` fills it like the other lists: when the Block changes it reads the Block's title through `index.blockGet` and searches the graph for it (`state.query` is that text; a Block without a title searches nothing and the panel says so). `state.knowledge` is `{ status, items: KnowledgeHit[] }`, and each entry shows its label, its kind and its score.

Choosing a hit calls `model.focusNode(hit)`: `state.focus` is then `{ node, status, nodes, edges }` with the neighbours loading (`neighbours(node.id)`). The panel lists the nodes the graph joins to the focus (`exploreKnowledgeLinksOf(focus)`: node, edge type and `out` or `in`); choosing one of them moves the focus there, and „Zurück zu den Treffern“ calls `focusNode(null)`. A failing search or neighbourhood is `failed` and the other tabs stay usable. Opening a Block stays the host's `open`: knowledge nodes are no Blocks, so choosing one never calls it.

## The KGS add-on

`@skaile/forge-editor-kgs` is the `KnowledgeGraph` over the read side of the KGS SDK (`@kgs/sdk`, a daemon that holds a corpus of sources and concepts). `createKgsKnowledgeGraph(client, options?)` takes a `KgsReadClient`: the three methods of the SDK's `Client` that it calls (`get`, `search`, `neighbors`), typed in the add-on. The add-on does not import `@kgs/sdk` (it is not published on npm); the SDK is an optional peer that the host installs.

<table>
<thead>
  <tr>
    <th>
      <code>
        KnowledgeGraph
      </code>
    </th>
    
    <th>
      KGS call
    </th>
    
    <th>
      Answer
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        search(q)
      </code>
    </td>
    
    <td>
      <code>
        kgs.search
      </code>
    </td>
    
    <td>
      <code>
        { id, label, kind, score }
      </code>
      
      : the node's title, its node type, the daemon's score
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        neighbours(id)
      </code>
    </td>
    
    <td>
      <code>
        kgs.get
      </code>
      
       and <code>
        kgs.neighbors
      </code>
      
       (both directions)
    </td>
    
    <td>
      the node, the nodes next to it and the edges that join them
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        origin(id)
      </code>
    </td>
    
    <td>
      <code>
        kgs.neighbors
      </code>
      
       (<code>
        DerivedFrom
      </code>
      
      , outgoing)
    </td>
    
    <td>
      the nodes <code>
        id
      </code>
      
       was made from
    </td>
  </tr>
</tbody>
</table>

Deleted nodes never appear. Options: `mode` (the daemon's search mode, default `hybrid`), `limit` (hits, default 10), `scope` (a zone to search in).

```ts
import { createKgsKnowledgeGraph } from '@skaile/forge-editor-kgs'
import { Client, TcpTransport } from '@kgs/sdk'

const client = await Client.fromTransport(new TcpTransport('127.0.0.1', 7777))
const knowledge = createKgsKnowledgeGraph(client, { limit: 10 })
// provideEditorServer({ ports: { …, knowledge } })
```

The repository's document pages (the file-drawer layout, `app/layouts/default.vue`, whose Explore panel gets a fourth tab) show it behind `NUXT_PUBLIC_ADDONS_KGS=1` with `KGS_URL` (`tcp://host:port` or `unix:///path/to/.kgs/socket`); the kitchen sink's standalone pages have no workspace index and show no knowledge graph.


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
