Before You Start
- This is a low-level infrastructure exercise, not a replacement for
cognee.remember()or Cognee pipelines — direct graph writes like this skip vector indexing, relational records, dataset provenance, and normal access-control behavior. - This guide defines a custom
Person(DataPoint)model — see DataPoints for the concept, or Custom Data Models and Custom Graph Model to go deeper.
Code in Action
What Just Happened
Step 1: Configure an Isolated Graph and Get the Engine
graph_file_path points Ladybug at a throwaway directory so this exercise never touches your default Cognee graph. graph_database_subprocess_enabled=False keeps Ladybug running inside your Python process instead of as a separate background process — simpler for a short script like this. get_graph_engine() is async and returns whatever adapter the configuration selected — here, a Ladybug adapter — typed as GraphDBInterface. Everything from this point on is written against that interface, not against Ladybug specifically.
Step 2: Define a Minimal Node Model
Person inherits from DataPoint, Cognee’s base model for graph nodes. Declaring "identity_fields": ["name"] gives each Person a deterministic id based on its name, so Person(name="Alice") always resolves to the same node id instead of a random one.
Step 3: Create Nodes and an Edge
add_nodes(nodes) takes a list of DataPoint instances and writes them as one bulk operation. The Ladybug, Postgres, and Turso adapters split a large list into several idempotent bulk statements rather than sending one statement per call, so a single write cannot outgrow the backend’s per-statement limits; the chunk size is an internal, per-adapter detail and is not configurable. add_edge(source_id, target_id, relationship_name, edge_properties) creates one directed edge — here, Alice -[knows]-> Bob with a small property dictionary. Both take plain string ids, so DataPoint.id (a UUID) is converted with str(...) before being passed in.
Step 4: Read Data Back
get_node(node_id) returns a single node’s properties as a dictionary, or None if it does not exist. get_neighbors(node_id) returns the properties of every node connected to the given node — here, Bob’s only neighbor is Alice. has_edge(source_id, target_id, relationship_name) checks whether a specific directed, labeled edge exists, returning a plain bool.
Step 5: Clean Up
delete_nodes(node_ids) removes the listed nodes and any edges attached to them. Running it in a finally block ensures the example nodes are removed even if an earlier step raises, leaving the isolated graph empty again.
One Interface, Many Databases
Cognee can store its graph in several different backends — Ladybug (the default local, file-based engine), Postgres, Neo4j, and others (see Graph Stores for how to configure each one). Application code that queries or writes to the graph should not need a different code path for each one. Cognee solves this with three pieces:- Configuration picks which provider is active (e.g.
"ladybug"or"postgres_demo"). get_graph_engine()is a factory function. It reads the configuration and returns an object implementing the interface — you never construct an adapter yourself.GraphDBInterfaceis an abstract base class that declares the methods every adapter must provide (add_node,get_node,has_edge, and so on).- Concrete adapters (
LadybugAdapter,PostgresDemoAdapter, …) implement that interface against a specific database.
get_graph_engine() works unchanged no matter which provider is configured.
Seed selection: get_top_degree_node_ids
GraphDBInterface.get_top_degree_node_ids(top_k) — the method that picks seed nodes for the default graph visualization — is declared on the interface but, unlike most of its methods, is not abstract: it ships with a working inherited implementation, so an adapter that predates it keeps loading. Its contract:
- It returns node ids only (
list[str]), never node objects or degree counts. - It is approximate by contract: an adapter may sample instead of counting exactly, and the order of near-equal nodes may vary between calls. Do not treat the result as a ranking — only as “up to
top_knodes worth starting from”. - Isolated nodes are valid seeds, so a graph with nodes but no edges still yields a non-empty result.
top_kmust be ≥ 1; a non-positive value raisesValueErrorbefore the graph is read.
get_top_degree_node_ids — a third-party adapter from cognee-community, for instance — still works: it inherits the default, which reads the whole graph and counts degree in Python. That is O(graph) in memory and can exhaust a worker on a multi-million-node graph, so the fallback logs a warning once per adapter type per process:
Comparing Adapters
LadybugAdapter and PostgresDemoAdapter both implement add_nodes, add_edge, get_node, get_neighbors, and has_edge from GraphDBInterface — but the two implementations look nothing alike internally. Ladybug builds parameterized Cypher-style statements against an embedded Kuzu database; Postgres issues SQL against relational tables that model nodes and edges. Neither difference is visible to code written against the interface, which is the point: swapping graph_database_provider from "ladybug" to "postgres_demo" (with matching connection settings) does not require changing any of the code above.
DataPoints
Learn more about DataPoint, the base model Person builds on
BaseRetriever Guide
See the same abstract-contract pattern applied to retrievers