Graph
kyma includes a native graph layer over the same columnar store. Entities and relationships are first-class — indexed, pruned, and served through the same Arrow execution path as every other query surface. There is no separate graph database; the graph is a view over tables you already have.
Two kinds of graph exist side-by-side in every deployment.
The schema graph
The schema graph is synthetic and always available — nothing to register, nothing to configure. kyma derives it from the catalog at query time:
- Every table in a database becomes a node.
- Inferred
REFERENCESedges connect tables whose columns share value domains (foreign-key relationships, data-source-produced*_idcolumns, and so on). - Column details are available behind a depth toggle in the web UI.
The realm of a schema-graph node is the database name, so a multi-database deployment produces one schema graph per database, merged on the unified canvas.
The schema graph has no storage tables of its own. The <graph>_nodes and <graph>_edges tables that stored graphs use are deliberately excluded from the schema graph to keep the view clean.
Stored graphs
A stored graph is a property-graph you register against two tables — one for nodes, one for edges. Once registered, kyma reads those tables on every graph query, deduplicating append-only data source rows to one canonical node per id.
Register a graph
kyma create-graph \
--db <database-name> \
--name <graph-name> \
--nodes <node-table> \
--edges <edge-table>Key flags and their defaults:
| Flag | Default | Purpose |
|---|---|---|
--id-col | id | Node identity column in the nodes table. |
--label-col | labels | Node label / kind column. |
--src-col | src | Edge source column in the edges table. |
--dst-col | dst | Edge destination column. |
--type-col | type | Edge type / relationship column. |
--realm-col | (none) | Optional column used to scope nodes into sub-realms. |
If your tables already use those column names, you only need the four required flags. Override individual names if they differ.
List and drop
kyma list-graphs --db <database-name>
kyma drop-graph --db <database-name> --name <graph-name>Drop removes the registration; it does not delete the underlying tables.
What node and edge tables look like
A minimal nodes table:
id | labels | … any other columns … |
|---|---|---|
svc-a | service | … |
A minimal edges table:
src | dst | type | … |
|---|---|---|---|
svc-a | svc-b | CALLS | … |
Any extra columns on both tables are carried through as properties and are accessible in KQL graph-match predicates and in the HTTP response.
Querying graphs
KQL graph operators
KQL exposes three graph operators documented in full at KQL functions: make-graph, graph-match, and graph-traverse (with optional edge-type filtering), plus graph-shortest-path.
A quick graph-traverse example — walk outbound edges from svc-a up to two hops:
context_edges
| graph-traverse source "svc-a" from src to dst max-hops 2The result is a row per reachable node with its hop depth. Add | where and | project downstream to filter or reshape the output.
See KQL for the broader operator reference.
Cypher
For graph-shaped queries you can send Cypher instead of KQL. POST /v1/query with Content-Type: application/x-cypher, the raw Cypher as the body, and an X-Graph header naming the graph ("<db>/<graph>", or just "<graph>" when it falls back to X-Database). Cypher is translated to KQL and runs through the same Arrow execution path — so it inherits the pruning cascade and the persisted CSR snapshot for deep traversals.
curl -sS localhost:8080/v1/query \
-H 'Content-Type: application/x-cypher' \
-H 'X-Graph: obs/kg' \
--data-binary "MATCH (a:service)-[:CALLS*1..3]->(b)
WHERE a.name STARTS WITH 'api-' AND (b.tier = 'db' OR NOT b.healthy = 'true')
RETURN a.name, b.name ORDER BY a.name LIMIT 50"Supported surface:
| Area | Supported |
|---|---|
| Patterns | MATCH with forward -->, backward <--, and undirected hops; multi-hop chains; comma-separated and multiple MATCH clauses (star / tree / disjoint, joined on shared variables); relationship type + node label filters (-[r:CALLS]->, (a:service)). |
| Variable length | -[r*min..max]-> (bounded), lowered to a depth-bounded recursive traversal that preserves both endpoints. |
| Paths | shortestPath((a)-[*..n]->(b)) with length(p); endpoints pinned by WHERE id equality. |
| Optional | OPTIONAL MATCH (lowered as a LEFT JOIN). |
| WHERE | =, <>, <, >, <=, >=; IN [..]; STARTS WITH / ENDS WITH / CONTAINS; IS NULL / IS NOT NULL; AND / OR / NOT with parentheses and correct precedence. |
| RETURN | properties, AS aliases, DISTINCT, aggregates count(*) / count(x) / sum / avg / min / max / collect(x), ORDER BY … [ASC|DESC], LIMIT. |
| WITH | aggregation + HAVING: WITH <keys>, <agg> AS <alias> [WHERE <pred on alias>] RETURN … — group, filter on the aggregate, then project/order/limit. Grouping keys are n.prop (no AS). A bare node WITH a, … is carried through — any a.prop you reference afterward becomes a grouping key (e.g. WITH a, count(b) AS deg WHERE deg > 5 RETURN a.name, deg). |
| Computed RETURN | arithmetic over properties + numeric literals: RETURN a.score + b.score AS total (+ - * /, parentheses, standard precedence). The expression must start with <var>.<prop> and requires AS <alias>. |
Example HAVING — services with more than five callers:
MATCH (a)-[:CALLS]->(b)
WITH b.name, count(a) AS callers
WHERE callers > 5
RETURN b.name, callers ORDER BY callers DESC LIMIT 20Writes — CREATE / MERGE
CREATE and MERGE append rows to the graph's node/edge tables via the ingest path (the graph is a view over append-only tables, so a write is an append, not an in-place edit). Send them to POST /v1/query exactly like a read, with Content-Type: application/x-cypher and the X-Graph header; they require the write role. The response is a write-ack, e.g. {"created":{"nodes":2,"edges":1},"merged_existing":0}.
CREATE (a:Service {id:'svc-a', name:'A'})-[:CALLS {weight:5}]->(b:Service {id:'svc-b'})- Nodes:
CREATE (n:Label {id:'x', …})— one or more comma-separated. Theidproperty (the graph's id column) is required. - Inline edges:
(a {id:'x'})-[:TYPE {props}]->(b {id:'y'})— both endpoints fully specified inline (noMATCHbinding); appends 2 node rows + 1 edge row. MERGE: insert-if-absent — an existence check on the id (node) or (src, dst, type) (edge); already-present rows are skipped (merged_existing).
Not supported (returns 400): in-place mutation SET / DELETE / REMOVE (append-only graph — there is nothing to mutate in place), MATCH … CREATE (read+write in one statement), edge endpoints without an inline id; plus the read gaps RETURN *, RETURN expressions that don't begin with <var>.<prop>, and string/function expressions. Use KQL or the ingest API for anything outside this surface.
MCP tools
When querying through the MCP server, two graph tools are available:
graph_traverse— walks from a source node across an edge table with a configurable hop limit.find_references_to— locates every node that references a given value across all columns in the catalog.
Both tools are wired to the same graph engine as KQL; the MCP layer adds natural-language routing on top.
HTTP API
All graph endpoints are documented at HTTP API. The database is selected with the X-Database header on every request. The short list:
GET /v1/graph— list registered graphs.GET /v1/graph/:graph/overview|stats|schema— metadata.GET /v1/graph/:graph/nodes/:id— single node.GET /v1/graph/:graph/nodes/:id/subgraph— node + neighbourhood.POST /v1/graph/:graph/search— predicate-filtered node search.POST /v1/graph/:graph/neighbors— neighbours of a node.GET /v1/graph/:graph/analytics— whole-graph analytics over the persisted CSR topology (stored graphs only).?kind=pagerank|communities|components;pagerankalso takes&seeds=<id,id,…>(empty ⇒ global PageRank) and&limit=N(top-N by score, default 100). Returns{"kind", "count", "results":[{"id", "value"}]}—valueis the PageRank score, community id, or component id.
The graph in the web UI
The /graph route in the web app renders a unified canvas that merges every source at once:
- The schema graph for every connected database.
- Every registered stored graph across all databases.
- The agentic memory layer (see below), which stores its own node and edge tables and appears as a registered graph with cross-graph edges.
The canvas is deliberately not scoped to a single database or session — the value of the graph view is seeing the full topology of your deployment in one place.
How memory uses the graph
Agentic Memory persists memory_nodes and memory_edges tables and links individual memories to catalog graph nodes via REFERENCES and RESOLVES_TO edges. This means memories surface on the graph canvas alongside your service and data topology — a memory about a table appears adjacent to that table's node, and traversals can cross the memory–catalog boundary in both directions.
See Agentic Memory for how memories are created, searched, and linked, and Workers & nodes for the background processes that populate and maintain the memory graph.
Artifacts on the graph
Artifacts — CI job logs, object-store blobs, agent-contributed files, filesystem-watch snapshots — are first-class graph nodes labeled Artifact.
CI logs (GitHub data source)
Every GitHub CI job log captured by the GitHub data source produces an Artifact node in the github graph, linked from its Job node by a HAS_ARTIFACT edge. Node properties:
| Property | Value |
|---|---|
object_path | Object-store path of the log blob. |
sha256 | SHA-256 hex digest of the blob. |
size_bytes | Blob size in bytes. |
artifact_class | log |
source | github |
retrievable | true — set at capture (a blob was stored for this log). |
artifact_id | Catalog row id (UUID). |
Forward-only relabel note: CI logs captured before this change keep their old LogFile label in the append-only tables; only newly captured logs carry the Artifact label. Re-capture a job to get the new label.
Redaction note: The GitHub data source redacts secrets from CI log text before writing the blob to object-store (kyma-redact runs at capture time; raw log text is never persisted). What you retrieve from the viewer is already redacted.
Other artifact sources (server / Postgres mode)
Object-store blobs, agent-contributed files, and filesystem-watch snapshots that have no matching producer node appear as Artifact nodes in a dedicated artifacts graph. This graph is materialized on startup and kept current by a periodic sync running in the server process.
Availability: server + Postgres mode only. kyma local has no artifact catalog and no artifacts graph.
Viewing artifacts on the graph page
- Open
/graph(the unified canvas). - Filter by the
Artifactnode type using the label filter in the sidebar — this narrows the canvas to all artifact nodes across both thegithubgraph and theartifactsgraph. - Click any
Artifactnode to open the sidebar viewer. The viewer streams the stored blob bytes as-is (viaGET /v1/artifacts/by-path?path=<object_path>, paged with Load more).
Searching for artifacts
Graph search matches on node id and label. Searching artifact returns all Artifact nodes. Searching a repo name, job name, or log path substring surfaces matching nodes if they appear in the node id.
Content search
Artifact blob contents are searchable through the unified search endpoint (POST /v1/search) and the Explore page, not the graph page.
On the same cadence as the artifact-graph sync (KYMA_ARTIFACT_GC_POLL_SECS, default 300s), the server indexes text-like artifacts into the artifacts.artifact_chunks table:
- Which artifacts: classes
log,file,text,doc,config; up to 4 MB per blob; binary content is sniffed and skipped. - How: the blob is split into line-aligned ~1500-char chunks (max 64 per artifact), embedded with the process-shared embedding backend, and appended with the artifact's
artifact_id,object_path,source, andtable_reffor provenance. - Search:
artifact_chunksis a regular table in theartifactsdatabase, so default data-mode search ({"query": "..."}) hits it with both the lexical and the vector (cosine) leg. Hits carry"source": "artifacts.artifact_chunks"and the chunk row, includingartifact_idto retrieve the full blob. - Idempotent: each artifact is indexed once per content hash (
sha256); re-syncs skip already-indexed artifacts before fetching the blob.
Availability: server + Postgres mode only, same as the artifact catalog.
Where to go next
- KQL graph operators in detail: KQL functions.
- Full graph HTTP endpoints: HTTP API.
- How the agent uses the graph: Agentic Memory.
- Background workers that keep graphs current: Workers & nodes.
- Why traversals are fast: The pruning cascade.