Skip to content

Multi-Project Graphs

One Memgraph instance can hold the graphs of several repositories at once. Each indexed repository becomes a Project node, every qualified name is prefixed with that project's name, and retrieval tools read source files through each node's recorded absolute path, so answers stay correct no matter which directory you launch from.

Indexing several repositories

Index each repository separately; every run adds (or refreshes) one project in the shared graph:

cgr start --repo-path ~/services/user-service --update-graph
cgr start --repo-path ~/services/order-service --update-graph

cgr start --update-graph indexing user-service and then order-service into the same graph

Project names are derived from the directory name plus a short hash of the full path (for example user-service__a1b2c3d4), so two checkouts with the same folder name never overwrite each other. Pass --project-name to choose a name yourself. A chosen name cannot contain .: it separates the parts of a qualified name, so a project acme.web would share nodes with the web package of a project acme.

Each Project node records the repository root it was indexed from (root_path), and every code node stores the absolute path of its source file, which is what allows cross-project retrieval from any working directory.

Querying across projects

cgr start scopes queries to the selected repository by default. To query several projects in one session:

# Explicit list of project names
cgr start --repo-path ~/services/user-service --projects "user-service__a1b2c3d4,order-service__e5f6a7b8"

# Or a saved workspace
cgr workspace create backend
cgr workspace add-repo backend ~/services/user-service
cgr workspace add-repo backend ~/services/order-service
cgr start --workspace backend

cgr workspace create, add-repo and show for the backend workspace, cgr export --workspace backend, and cgr stats --workspace backend counting both projects

cgr start --workspace backend opens the LLM chat and is not part of the recording.

--projects overrides --project-name; --workspace expands to every repository saved in the workspace. See cgr help workspace for the full workspace command set.

cgr start --workspace backend --update-graph syncs each of the workspace's repositories under its own project name, then opens the assistant scoped to the workspace, which -a, --output-format and --projects reach as they do without --update-graph; --no-sync is refused. --repo-path and the current directory play no part in it. --clean, -o and --interactive-setup apply to one repository, so they are refused with --workspace --update-graph. A sync of your home directory or the filesystem root is refused unless you pass --yes. A workspace with no repositories is refused too, since it has nothing to sync and no project to scope the assistant to; without --update-graph, --projects can still name the projects to chat about.

A workspace is saved as ~/.cgr/workspaces/<name>.toml, so its name is an identifier, not a path: it starts with a letter or digit and uses only letters, digits, ., _ and -. Any other name (empty, a/b, ../x) is refused with exit 1 before a file is touched.

Semantic search within one project

When several projects share the graph, semantic search can be confined to a single project: the agent's semantic_search tool accepts an optional project name and then only returns matches whose qualified names belong to that project. Without it, results are ranked across every indexed project.

Tracing calls between services

With the io capture group enabled, a route handler such as @app.get("/users/{id}") becomes an endpoint resource (resource::ENDPOINT::GET /users/{id}) connected to its handler by an EXPOSES edge, and a client call like requests.get("http://user-service:8000/users/42") becomes a network resource connected to its caller by READS_FROM/WRITES_TO. After each indexing run, literal client URLs are matched against the endpoint path templates in the graph ({id} matches one path segment) and linked with RESOLVES_TO, so a request in one service traces to the handler in another:

cgr start --repo-path ~/services/user-service --update-graph --capture io
cgr start --repo-path ~/services/order-service --update-graph --capture io
MATCH (caller)-[:READS_FROM|WRITES_TO]->(:Resource {kind: 'NETWORK'})
      -[:RESOLVES_TO]->(:Resource {kind: 'ENDPOINT'})<-[:EXPOSES]-(handler)
RETURN caller.qualified_name, handler.qualified_name

Both services indexed with --capture io, then the query in mgconsole linking order-service's fetch_user to user-service's get_user handler

Matching uses the URL path only: dynamic (non-literal) URLs and requests whose paths match no known template stay unlinked.

Cross-service edges

What the route extractors recognise, and what the links can and cannot say:

Language Routes recognised
Python FastAPI and Flask style decorators (@app.get, @router.post, @app.route), with include_router mount prefixes resolved
JavaScript / TypeScript Express and express.Router handlers (app.get(...), router.post(...)), including handlers registered as options. The handler is the last argument after the path (earlier ones are middleware); an inline function or arrow is its own handler node
Go http.HandleFunc / Handle, and the echo, gin, chi and mux router factories

A client URL links to an endpoint when its path matches the endpoint's template. Python and JS/TS URLs may be built: an f-string or template literal substitution, or any non-literal operand of a +, stands for one path segment ("/orders/" + id reads /orders/{id}), and a module-level string constant bound once (BASE = "http://localhost:5000", a JS/TS const) folds in as literal text, so f"{BASE}/users/{uid}" and BASE + "/users" link. A URL whose base is a parameter, a local or any other computed value stays unlinked and shows up as an unresolved dependency rather than as a wrong link. RPC and dispatch resources join their callers directly (READS_FROM/WRITES_TO on the resource itself) and need no RESOLVES_TO.

Three deterministic MCP tools read these edges, all project-scoped like the other graph tools: endpoints lists what a project exposes with how many call sites in the whole graph reach each one; endpoint_callers lists the call sites in any project that reach one endpoint, by handler name or by identity (GET /users/{id}); remote_dependencies lists every network access a project makes with the handler it resolves to, keeping the unresolved ones. These edges exist only where the io group was captured, and each sync records its capture on the Project node: on a project whose last sync left out an io relationship the tool needs (endpoint_callers needs EXPOSES, READS_FROM, WRITES_TO and RESOLVES_TO), an empty answer comes back as an error naming the group, the missing relationships and the re-index command rather than as [] (a graph synced before the record existed answers as before). cgr dead-code --no-endpoint-roots stops rooting a decorator-routed handler (FastAPI, Flask) by its decorator alone: such a handler whose endpoint no indexed call site reaches is reported. A handler registered by a call (Go HandleFunc, Express app.get(path, handler)) stays live through that registration, whatever the switch. On a graph holding one project the report reads as "no callers indexed", which the command says; index the calling services first.

Two analyses follow the link across the boundary. flow_verdict continues a FLOWS_TO walk from a client's network resource into the handler it resolves to (and from an RPC or dispatch resource into its handler), loads that handler's project's own flow edges once the walk reaches the hop, and reports the pairs where the path crossed into another project as remote_hops (a service calling itself over HTTP is not a boundary). A NO_FLOW verdict needs full coverage of every project the walk entered, not only the asked one. The structural delta after a write lists, on a signature change, the remote_callers: call sites in other projects reaching the changed handler's endpoint, which no CALLS edge would ever name.

Housekeeping

# Remove one project without touching the others
cgr delete-project --project user-service__a1b2c3d4

cgr delete-project removing user-service from the graph and its vectors, while order-service's fetch_user still resolves

Deleting a project also removes its embeddings from the vector store.