Guides

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.

PieceWhat it doesPort (capability)
KnowledgeGraphsearch(q, user), neighbours(id, user) and optionally origin(id, user)knowledge
GET /api/editor/knowledge/search?q=the nodes q finds, best firstknowledge
GET /api/editor/knowledge/nodes/:id/neighboursa node, the nodes next to it and the edges between themknowledge
EditorBackend['knowledge']search(query, signal?) and neighbours(id, signal?)knowledge
Explore tab knowledgethe nodes found for the open Block's title; a click shows the neighboursindex and knowledge

A node is not a Block and an edge is not an Edge of the Block index: 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:

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[] }
TypeMeaning
KnowledgeNodeid is stable; label is what the node is called (its id for a node without a title); kind is what it is, in the graph's words
KnowledgeHita node with the score the graph gave it. Higher is better; the scale is the graph's own and only orders the list
KnowledgeEdgea directed edge between two node ids; every edge of a KnowledgeNeighbours has both ends in its nodes

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

Routes

Route (API key)RequestAnswer
GET /api/editor/knowledge/search (knowledgeSearch)?q=KnowledgeHit[]
GET /api/editor/knowledge/nodes/:id/neighbours (knowledgeNeighbours)noneKnowledgeNeighbours

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.

StatusWhen
400q missing, blank or longer than KNOWLEDGE_QUERY_MAX (500) characters; an id blank or longer than KNOWLEDGE_ID_MAX (200)
401no user
501the host has no knowledge port

Provide a port

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 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.

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

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

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.

Copyright © 2026