Building on kglite¶
The out-of-the-box playbook for a producer — a library that computes domain data (SEC filings, a parsed codebase, an audio analysis, a distilled PDF) and turns it into a queryable kglite graph. This page tells a third party how to connect their library to kglite, what contract they’re building against, and how to test it.
If you’re querying an existing graph rather than producing one, the Rust guide and the Python guide are your starting points instead.
The one-question chooser¶
There is a single question that picks your tier:
Does your build logic need to query the graph while it is being built?
No — the graph is a pure function of your source data → P3 (engine-free). You compute nodes and edges and hand kglite a structured description; kglite builds the graph. This is the default, and where most producers land.
Yes — a build pass has to read back what earlier passes wrote (resolving references, deduping against existing nodes, cross-linking) → P1 (embedded engine). You link the
kglitecrate, build the graph natively, and hand it off as a.kglfile.
P3 — engine-free |
P1 — embedded engine |
|
|---|---|---|
kglite crate dependency |
none |
links |
Query graph mid-build? |
no |
yes |
Handoff |
|
save |
Version coupling |
python |
crate pin + format-floor rule + pin hygiene |
Producer language |
any |
Rust (until P2) |
Wheel carries engine? |
no |
yes |
Reference |
kglite-datasets, sonagram (music) |
codingest |
When unsure, start at P3. You only pay for P1 if you can name the build pass that must query the half-built graph.
The seam inventory — what you build against¶
Everything a producer depends on is one of five seams. Each has a defined stability posture; kglite’s CI locks against accidental drift on all of them.
Seam |
What it is |
Stability |
|---|---|---|
Engine facade — |
The curated Rust surface: |
Exact-baseline-locked in CI (cargo-public-api, pinned nightly). Additive within a minor line; deliberate breaks ship on a MINOR bump with a migration guide. See the API reference. |
MCP server library — |
|
Public-API baseline + hook-semantics unit tests. Same MINOR-break posture as the engine facade. |
|
The persisted graph format that P1 handoff and all persistence use. |
Versioned ( |
Python top-level — |
|
Contract-tested + stubtest against |
C ABI — |
The |
cbindgen header-drift check in CI; see the C ABI guide. |
One caveat CI cannot lock for you: version pins across repos. Your producer
pins kglite (and, for an MCP producer, kglite-mcp-server) to a minor line,
and a P1 producer must keep transitive pins — notably rmcp / rmcp-macros —
in lockstep with kglite’s. Re-check these at every kglite bump; nothing
machine-enforces them from a single repo.
P3 recipe — engine-free¶
You emit a structured description of your graph; kglite builds it. Three input shapes, all public API:
Blueprint + CSVs. Describe the graph once in blueprint.json — node types,
primary keys, titles, properties, and connections (FK edges, timeseries) — with
each node type pointing at a CSV. Then:
import kglite
g = kglite.from_blueprint("blueprint.json")
g.cypher_query("MATCH (c:Company) RETURN c.name LIMIT 5")
{
"nodes": {
"Company": {
"csv": "processed/company.csv",
"pk": "cik",
"title": "name",
"properties": { "name": "string", "sic": "string" },
"connections": {
"fk_edges": { "IN_INDUSTRY": { "target": "SicCode", "fk": "sic" } }
}
},
"SicCode": { "csv": "processed/sic.csv", "pk": "sic", "title": "description",
"properties": { "description": "string" } }
}
}
Inline records — kglite.from_records(spec) — carry nodes and connections
inline as JSON instead of pointing at CSVs; column types are inferred and array
values become native list properties. This is the no-CSV-on-disk / agent-authored
path.
Imperative — KnowledgeGraph.add_nodes / add_connections (and the _bulk
variants) build a graph node-by-node from Python.
Your library links zero kglite crate code; its only kglite tie is the wheel’s
runtime floor, kglite>=X, chosen for the input format you emit. The living
template is kglite-datasets (pip install kglite-datasets): its SEC loader
computes a 34-CSV processed/ layout, describes it with one blueprint.json,
and calls from_blueprint — engine-free end to end. Full blueprint semantics are
in the blueprint guide.
P1 recipe — embedded engine¶
Link the crate, build a DirGraph, hand off through .kgl:
# Cargo.toml
[dependencies]
kglite = "0.14" # a version whose reader understands the format you write
use kglite::api::io::save_graph;
let mut graph = build_my_graph()?; // your builder; may query `graph` mid-build
save_graph(&mut graph, "out.kgl") // → the handoff artifact
.map_err(anyhow::Error::msg)?;
The Python handoff pattern (see codingest for the reference): run the pure-Rust
builder with the GIL released, save_graph to a .kgl (a temp file when the
caller gave no path, deleted once the load completes), then py.import("kglite")
and call its top-level load(path) — the returned object is a real
kglite.KnowledgeGraph, so every downstream kglite API works unchanged.
The format-floor rule. Your declared floor kglite>=X must name a version
whose reader understands the format your linked engine writes. If your crate
links an engine that writes .kgl v4, kglite>=X must be a version that reads
v4 — otherwise the handoff load() fails at runtime for exactly the users who
took the floor literally.
Pin hygiene. Re-check your kglite pin — and transitive pins that must move in
lockstep with it (rmcp / rmcp-macros) — at every kglite bump. There is a
measured cost to the round-trip P1 pays and P3 avoids: roughly 12% on
parse-heavy builds, up to ~50% on very fast builds (serialization is fixed-cost
against graph size, so it dominates a cheap build).
The reference is codingest (cargo add codingest / pip install codingest) — its resolution passes query the half-built graph, which is exactly
why it is P1. See embedding.md and
implementing-a-binding.md for the full embedder
surface.
MCP recipe¶
A producer that wants an MCP server wraps
kglite_mcp_server::run_with_code_tree_hooks. The seam is code-tree-named but
generically shaped — a triple of hooks:
build:
path → graph(single build),build_revs:
path → graphover multiple git revisions (the hook owns rev canonicalization), andis_code_file: the watch predicate — is a change to this path build-relevant?
use kglite_mcp_server::CodeTreeHooks;
fn main() -> anyhow::Result<()> {
let hooks = CodeTreeHooks {
build: Box::new(|dir, include_docs| my_builder::build(dir, include_docs)),
build_revs: Box::new(|dir, revs, include_docs| {
let revs = my_builder::dedup_revs(revs);
let graph = my_builder::build_revs(dir, &revs, include_docs)?;
Ok((graph, revs))
}),
is_code_file: Box::new(|p| my_builder::language_for_path(p).is_some()),
};
kglite_mcp_server::run_with_code_tree_hooks(std::env::args_os(), Some(hooks))
}
The server crate owns the entire tool surface, Cypher pipeline, set_root_dir
activation, and file watching; you inject only the builder. Drop-in property:
your server takes the same flags as the kglite MCP server — operators switch
the binary, not their config. codingest-mcp is the ~40-line reference main.
Testing pattern¶
Golden-digest parity, frozen. While a reference producer exists, freeze a golden
.kgldigest of a fixture graph and assert it in CI. A build change that shifts the digest is either a bug or an intended graph-shape change that must re-bless the golden in the same commit. (kglite’s own writer side is pinned bytest_phase4_parity.py::GOLDEN_V3_DIGEST, refreshed per release; your producer freezes its own goldens the same way.)Offline-first gates. A producer’s default test suite must run with no network — fetchers hit cached fixtures, not live registries. Gate any network-touching test behind an explicit marker so the common
make testpath stays hermetic.
Domain math at build time¶
Bake domain computation into node properties and edges at build time; keep the Cypher layer generic. Bucket mappings, edge kinds, segment structure, block hierarchy — compute them in your producer and store them as graph data. Do not push domain logic into kglite.
A helper graduates into kglite Cypher only when it is domain-independent (sequence/array math, date helpers, graph algorithms, statistics) and a second domain wants it — the same use-case test the boundary principle applies to lifts. There is no UDF plugin mechanism: producers do not register custom Cypher functions. Compute at build time, store, and let generic Cypher read it back.
Where to go next¶
API reference — the
kglite::api::*inventory and the stability policy that governs it.Embedding kglite / implementing a binding — the full P1 embedder surface.
Boundary principle — why the engine stays generic and domain math lives in producers.
C ABI — the non-Rust producer surface.