Before You Start
- Complete Quickstart to understand basic operations
- Read Pipelines and Tasks for how a custom pipeline is assembled from tasks
- Set
CODE_GRAPH_REPO_PATHto the repository you want to index — it defaults to the current working directory - No LLM Providers or embedding configuration is required: both the pipeline and
SearchType.CODEare deterministic
Code in Action
What Just Happened
Step 1: Choose the Repository and Start Clean
CODE_GRAPH_REPO_PATH, falling back to the directory you run the script from. Pruning first means the graph you inspect afterwards contains only what this run extracted.
Step 2: Run the Code Graph Pipeline
get_code_graph_tasks() returns the three ordered tasks the pipeline runs: extract (run enola over the repository and map its facts to DataPoints), load the graph nodes, then load the typed relations as edges. Because nothing here calls an LLM or an embedding model, skip_connection_test=True skips the first-run provider checks so the pipeline runs without any API key.
Step 3: Query the Graph with SearchType.CODE
SearchType.CODE is driven by the structured code_query argument rather than by query_text, which stays empty here. The query_facts operation filters the extracted facts — by kinds in this case — and returns the first limit matches, so the result is a deterministic listing rather than a similarity ranking.
Step 4: Draw the Architecture and Read the Insights
architecture operation rolls symbol-level edges up to the modules that declare them and returns a ready-to-render diagram alongside the nodes — Mermaid by default, Graphviz with "diagram": "dot". The insights operation returns the findings enola’s explainers produced over the same graph, each with the evidence facts behind it; min_confidence separates the structural findings that always score 1.0 from the heuristic ones below it.
Step 5: Reuse the Same Shape for Other Operations
cognee.search() call with a different code_query. Take a fact id from the query_facts output above and feed it to explore to see a fact’s neighborhood, traverse to walk edges in one direction, find_path to connect two facts, or impact_analysis to see what depends on a fact. query_facts also filters on a fact property with prop and prop_value; the dependency example above lists the packages declared in the repository’s manifests. There is also a delta operation, which needs no fact id: code_query={"operation": "delta"} reports what the last ingestion changed in each repository.
When you don’t have a fact id yet, start from the navigation operations. search ranks symbols, routes, flows and modules against free text (BM25 over names, docstrings and file paths). hubs lists the most connected symbols, neighbors lists every edge of one node, and subgraph returns a bounded graph around up to 20 seeds. flows lists the program’s entry points and what their calls reach. ask takes a plain-language question and returns matching seeds, the flows through them, their one-hop neighbourhood and file:line citations to read; no model writes any of it. capabilities reports whether the graph has docstrings and flows, its node, symbol and flow counts, and every operation’s limits. Test code is left out of search, hubs and ask unless you pass "include_tests": true. Every operation’s full argument list, defaults, and the fact kinds a query can filter on are in the SearchType reference.
Advanced Usage
Make the Facts Available to Semantic Retrievers
Make the Facts Available to Semantic Retrievers
get_code_graph_tasks(repo_path, index_vectors=True) also writes the extracted facts to the vector store, so semantic and LLM-backed retrievers can reach them. It is opt-in because SearchType.CODE reads the graph only; enabling it adds embedding calls and therefore needs an embedding provider configured.Query from an MCP Client
Query from an MCP Client
The Cognee MCP server exposes the same operations to agents:
code_search takes the operation and its code_query fields as arguments, and recall accepts code in scope with a code_query to combine a code-graph query with other memory sources in one call.Docstrings and Flows
Docstrings and Flows
When enola records a symbol’s doc comment, it becomes the node’s
doc field and its description, so search and ask match on it.Each flow in enola’s flows.json becomes a CodeFlow node, linked to its entry symbol by flow_entry, to its stages by flow_stage, and to the routes it serves by flow_route. flows.json is only read when the snapshot’s receipt.json says this run wrote it (a snapshot without a receipt reads it as is): an enola run without the flows renderer leaves an earlier run’s file in place, and those flows would describe other code. A snapshot without docstrings or flows ingests as before, and the flows operation then raises a feature_unavailable error saying the graph has no flows. Flows that share a name in one repository, such as two services serving GET /health, are named after their entry as well, for example GET /health (svc_a/app.health), so they stay separate nodes.A repository whose code did not change is still mapped again on its next ingestion when cognee’s fact-to-graph mapping has changed since it was last indexed, so an existing graph picks up docstrings and flows without a code change.Query Across Repositories
Query Across Repositories
Graph paths only exist inside a single dataset. To follow paths across repositories, generate one Enola append/multi-repository snapshot covering all of them and ingest that into one dataset. Repositories indexed into separate datasets are searched independently, and no path can connect them.One snapshot covering several repositories is also what turns an HTTP call into a path. See HTTP call relations for how each edge is resolved and what it carries.
makes_request links the symbol that makes a call to that call’s route node, and ordinary per-repository ingestion produces it. reaches_route links that route node to the server route it reaches in the other repository, and it only appears when both repositories are in the same snapshot. Today that means generating the multi-repository snapshot yourself: repositories added separately, even to the same dataset, get no reaches_route edges. With both in place, impact_analysis on a server handler reaches the client functions that call it, across the repository boundary:Index a Public Repository by URL
Index a Public Repository by URL
A GitHub/GitLab repository URL passed to plain
add() or remember() — with no content_type — is recognised as a repository by its shape, shallow-cloned, and ingested as one code-repo item that takes the same code graph route, plus the repository’s own documents:branch works only on this route: remember() raises ValueError if it is combined with a content_type (so it cannot be paired with repo_credentials) or a session_id. Each branch gets its own clone, but a dataset holds one branch of a URL at a time — adding the URL on another branch re-indexes the code graph, while documents ingested from the previous branch stay until you remove them with forget(). See add() for the errors a missing or invalid branch raises.Use remember(url, content_type="code") instead when you want only the code graph (no repository documents), several repositories in one call, or repo_credentials for a private remote. Clones land under COGNEE_REPOS_DIR (default ~/.cognee/repos) either way, and both paths need git on PATH and ALLOW_HTTP_REQUESTS enabled. Deeper forge URLs (/blob/, /tree/, /issues, GitLab’s /-/ pages) are still treated as web pages; see add() for the exact detection rules.Tag a Repository with node_set
Tag a Repository with node_set
A
node_set passed with a whole repository — a GitHub/GitLab URL or a local code project directory handed to plain add() or remember() — tags the code graph too, not just the repository’s documents. Every code node, the CodeRepository node included, gets the same belongs_to_set NodeSet edges and source_node_set (the tag names joined with , ) that a document gets, so node-set-scoped queries and the graph view group the repository’s code under its tag:- Only this whole-repository route is tagged. Code files added individually and
remember(content_type="code")leave code nodes untagged, exactly as before. - For a tagged repository the sorted tag names are part of each node’s
fact_hash, so reordering tags does not trigger a rewrite. The next timecognify()processes the repository after a tag is added or changed, it rewrites that repository’s code nodes once — no LLM calls, no migration — even when the enola snapshot itself is unchanged. Re-adding a repository whose code has not changed, with a differentnode_set, does not start that run: itscode_repoitem is already marked processed, socognify()skips it. The new tag is stored and applied the next time the repository’s code changes andcognify()reprocesses it. Untagged repositories keep their existing hashes. - Tags the repository no longer carries are removed: their
belongs_to_setedges are deleted and the names dropped from the nodes. Edges are matched by NodeSet id, so a rename that only changes case, spacing, or apostrophes ("Team-A"→"team-a") keeps the shared edge and only drops the old spelling from the nodes.
Index a Private Repository
Index a Private Repository
remember(url, content_type="code", repo_credentials="<token>") clones a private https remote using an out-of-band token — a GitHub App installation token, for example. It is sent as HTTP basic auth under the x-access-token username, the scheme GitHub expects. The token reaches git through environment-level config rather than the URL, so nothing derived from the URL can carry it: the clone directory name, the persisted git remote, log lines, and git’s own error output all use the credential-free URL. repo_credentials is code-only — passing it with any other content_type raises ValueError.Credentials embedded in the URL userinfo (https://x-access-token:<token>@github.com/org/repo.git) still work, but are the legacy path. Either way, the source recorded on each result item is redacted, so a token never surfaces in a remember() result.To connect a whole organization instead of one repository at a time, see the GitHub integration.One Data Row per Remembered Repository
One Data Row per Remembered Repository
Each repository handed to
remember(content_type="code") is stored as one code_repo data item in the target dataset before its graph is built, and that row’s id comes back on the repository’s result item:id and dataset_id come back as strings; forget() takes UUIDs, so convert them before handing them over — a string reaches forget() as a dataset name and fails the lookup.The id is pinned to (user, dataset, repository path), so remembering the same repository again updates that one row and hands back the same id instead of accreting rows. With run_in_background=True the items fill in as the repositories run, so read them after await result.What the row buys you:forget()can target one repository. The code graph pipeline runs over the stored row, so its graph writes record that row as their owner andforget(data_id=UUID(item["id"]), dataset_id=UUID(result.dataset_id))removes that repository and the nodes it wrote. One caveat: code node ids are keyed on the repository’s directory basename, so two remembered repositories whose directories share a name share nodes, and only the first writer owns the unchanged ones.- The repository shows up on the dataset. The row lists under
GET /api/v1/datasets/{dataset_id}/datalike any other document. A finished repository is also stamped as completed for bothcognify_pipelineandcode_graph_pipeline, so it reads as done onGET /api/v1/datasets/{dataset_id}/processing-status?pipeline=code_graph_pipeline(that is the endpoint carrying per-item status; the data listing carries none), and a latercognify()over the dataset does not rerun enola for it.
{"status": "errored", "error": ...} on its own item and the batch carries on — that is what background runs and raise_on_error=False callers get, the POST /api/v1/remember route and the GitHub organization sync among them. The result’s status becomes errored with the per-repository messages joined into its error, and the HTTP route answers 409 with that body rather than failing the whole request. A blocking SDK call keeps the default raise_on_error=True and still raises on the first repository that fails outright — a clone, an authentication, or a row write. A graph build that comes back errored never raises: it is recorded on its item and the batch continues under either setting. Either way, a repository that failed after its row was stored still carries its id.Query the Code Graph Through recall()
Query the Code Graph Through recall()
recall() reaches the same deterministic operations without assembling a search() call. The "code" scope is explicit opt-in — neither "auto" nor "all" includes it — and code_query takes the same operation dict this guide uses:code_query runs explore seeded with the query text, and entries come back with source="code", so a scope=["graph", "code"] call keeps the two lanes apart in the results. For the full parameter behavior, see recall().Provide Your Own enola Binary
Provide Your Own enola Binary
Cognee installs the pinned
enola-cli wheel as a core dependency and runs the binary from the current environment’s scripts directory. ENOLA_PATH always wins over that bundled binary, so point it at your own build to control the version — and note the pin exists because the code graph’s known answers are validated against it. Nothing is downloaded at run time; a missing binary raises EnolaNotInstalledError asking you to reinstall cognee. For the full resolution order, see Where the enola binary comes from.Pipelines
How tasks are orchestrated into a pipeline.
run_custom_pipeline()
The full parameter surface of the call this guide uses.
Custom Tasks and Pipelines
Write your own tasks and assemble them into a pipeline.