Compass stores project knowledge as entities connected by directed, attributed relationships. This page develops the model from a small example to the node-link JSON and multigraph details used by advanced queries.
Who this page is for: anyone interpreting Compass output or writing CompassQL and graph integrations.
You will learn: nodes, stable IDs, labels, directed relationships, attributes, parallel edges, hyperedges, communities, snapshots, and common interpretation mistakes.
Prerequisites: How Compass works is helpful but not required.
Reading time: 10–12 minutes.
The smallest useful graph statement is:
checkout() --CALLS--> authorize_payment()
checkout() and authorize_payment() are nodes. CALLS is the
relationship type. The arrow gives the relationship a direction.
The graph can add evidence:
checkout() --CALLS [EXTRACTED, app.py:L18]--> authorize_payment()
That evidence is stored as attributes on the node or edge.
A node represents one identifiable project entity. Depending on input and extractor, that may be:
- a source file or module;
- a function, method, class, trait, interface, type, or variable;
- a configuration key or project manifest item;
- a database table, column, function, or relation;
- a document section or semantic concept;
- an image, media segment, or external integration entity;
- a structural placeholder that resolution may later connect or replace.
Every serialized node has a stable string id within the graph document:
{
"id": "src/payments.py::authorize_payment",
"label": "authorize_payment()",
"file_type": "Function",
"source_file": "src/payments.py",
"source_location": "L12"
}The exact ID construction is an implementation and compatibility concern. Consumers should treat IDs as opaque strings, not parse undocumented segments out of them.
idis the stable graph identity used by edges and exact consumers.labelis the human-facing display name.
Labels can repeat. Two files may each define Config, and several methods may
be called run(). A query interface may accept labels for convenience, but
automation should return and persist the full string ID when it needs one
particular node.
CompassQL's id(n) is different: it returns a snapshot-local integer index.
Use n.id for a portable stable Compass ID.
A serialized relationship has a source, target, and attributes:
{
"source": "src/checkout.py::checkout",
"target": "src/payments.py::authorize_payment",
"relation": "calls",
"confidence": "EXTRACTED",
"context": "call"
}Direction is part of the meaning:
caller --CALLS--> callee
child --INHERITS--> parent
file --IMPORTS_FROM--> module
scope --CONTAINS--> member
When looking for callers, you usually traverse incoming CALLS relationships.
When looking for downstream calls, you traverse outgoing ones.
Relation names depend on the extractor and input type. Common families include:
| Family | Examples | Typical meaning |
|---|---|---|
| Structure | contains, declares, member_of |
Ownership or hierarchy |
| Dependency | imports_from, uses, references |
One entity relies on another |
| Execution | calls, dispatches |
Potential control transfer |
| Type | inherits, implements, mixes_in |
Type-system relationship |
| Configuration | configures, depends_on |
Manifest or deployment linkage |
| Semantic | relates_to, source-specific relations |
Provider-extracted conceptual linkage |
Do not assume every relation implies runtime execution. imports_from and
references express different kinds of dependency from calls.
Nodes and edges retain open-ended JSON attributes. Common attributes include:
label;file_type;source_file;source_location;relation;confidence;context;community;- language or extractor-specific metadata.
Unknown attributes are retained by the graph document loader. This supports compatible extensions without forcing every consumer to understand every field.
Treat fields in the documented output contract as stable. Treat undocumented attributes as extensible data: preserve them when round-tripping and tolerate new ones.
The node-link document declares whether it is directed and whether it is a multigraph:
{
"directed": true,
"multigraph": true,
"graph": {},
"nodes": [],
"links": []
}A multigraph can keep more than one relationship between the same two nodes:
barrel.ts --IMPORTS_FROM--> module.ts
barrel.ts --RE_EXPORTS----> module.ts
Those edges share endpoints but carry different meanings. A consumer that collapses them into a single untyped connection loses information.
Compass promotes graph JSON to multigraph: true automatically whenever the
emitted links contain repeated endpoint pairs. Direction is part of the pair
in directed graphs, so reciprocal A -> B and B -> A links alone do not
trigger promotion. In undirected graphs they do; repeated self-loops always do.
Some human renderers list the neighboring node once while preserving the parallel-edge contribution to degree. Exact CompassQL patterns and graph JSON can inspect distinct relationships.
The graph model ensures every edge endpoint can be indexed. When loading a compatible document, the implementation can create a minimal endpoint node for an edge whose referenced ID is not already in the node list. Validation and command-specific loaders still reject malformed or unsafe inputs according to their contracts.
Graph loading is guarded by:
- a
.jsonextension on normal query paths; - a size cap;
- valid JSON;
- indexable endpoints;
- directed/multigraph handling;
- cache signatures before a binary cache is reused.
This protects query commands from treating arbitrary, oversized, or stale cache content as a valid graph.
Compass publishes only records that satisfy the closed graph vocabulary and endpoint rules. If one extracted node or edge is invalid, the normal build quarantines that record instead of weakening validation for the artifact:
- an invalid node is omitted with all incident edges;
- an invalid edge is omitted without inventing a generic relationship;
- a conflicting stable identity keeps one deterministic survivor;
- a route whose handler edge is omitted becomes unresolved;
- the remaining
compass.graph/1document passes the full strict validator.
The graph records bounded examples in graph.diagnostics and an exact
publication_omission_summary. Build output reports retained counts and warns
when the artifact is partial. Search, callers, callees, impact, explore, and
node-trail responses surface the same condition as incomplete_coverage.
Missing topology in such a response may reflect quarantine, so it is not proof
that no relationship exists in source.
Invalid document metadata, unsafe file inventory, an empty usable graph, and publication I/O failures remain hard failures. Atomic publication preserves the last known good generation when one of those failures occurs.
Relationships can carry:
EXTRACTED— directly supported by parser/source evidence;INFERRED— resolved from evidence across scopes, files, or systems;AMBIGUOUS— several plausible interpretations remain.
Provenance qualifies the evidence, not the importance of the relationship. An inferred link can be very useful; an extracted link can still describe code that is dead or unreachable at runtime.
Read Provenance and confidence for a decision framework.
Community detection assigns densely connected nodes to groups. A node can carry a numeric community identifier, and reports can associate a human-readable label with a group.
Community 2
├── LoginHandler
├── SessionStore
├── verify_token()
└── AuthMiddleware
Community IDs are properties of a particular graph snapshot and clustering result. Do not store business logic that assumes “community 2 always means authentication.”
Use communities to:
- choose an entry point for architecture reading;
- identify subsystem boundaries;
- find surprising cross-community dependencies;
- divide a large review into coherent regions.
Degree counts incident relationships. In a directed graph, Compass can consider incoming plus outgoing edges for hub analysis.
low degree high degree
---------- -----------
LeafParser --USES--> Token AppContext
^ ^ ^ ^
| | | |
many dependents
A god node is highly connected relative to the graph. It can reveal a critical hub or a design smell, but it can also be a legitimate shared abstraction. Compass's report filters some common built-in noise; it does not make a final architectural judgment.
Some facts involve more than a source and target. For example, a call may bind a caller, a target, an argument position, and a type constraint. Compass extraction and historical realizations can retain hyperedges for such multi-part facts.
The ordinary node-link links array remains the main query and visualization
surface. Do not assume that reconstructing only pairwise edges captures every
authoritative historical fact; history export preserves the full artifact set
according to its format contract.
A graph is meaningful only together with the inputs and configuration that produced it.
For the working tree:
snapshot identity ~= files seen + build options + extractor/analyzer versions
For versioned history, Compass makes that explicit:
realization = exact commit + extraction fingerprint + canonical artifact roots
The same commit can have multiple realizations—for example, a code-only profile and a semantic profile. They are not silently treated as equivalent.
Different query surfaces expose different projections:
queryreturns a focused, budgeted neighborhood;explainrenders one node's incoming and outgoing neighborhood;pathreturns a shortest known connection;affecteduses a compact projection with identity, location, and relevant relation fields;- CompassQL returns typed tabular values, paths, nodes, relationships, maps, or lists under explicit limits.
A projection is not a second graph truth. It is a task-specific view over a saved snapshot.
Not necessarily. An import, reference, containment edge, or semantic relation does not imply a runtime call.
It means direct static evidence exists. Dead code and conditionally loaded code can still be extracted.
It means a resolver connected structural evidence that was not expressed as one direct relation in one file.
It means fewest known hops under the command's traversal semantics. Importance and runtime frequency are separate questions.
It means a node is in the incoming impact neighborhood for configured relation families and depth.
It is a connectivity-derived cluster and can change with the graph.
When Compass returns an edge, ask:
1. What are the exact source and target IDs?
2. Which direction does the relation point?
3. What does the relation type claim?
4. What provenance/confidence is attached?
5. Which source locations support it?
6. Is this current-tree or exact-revision data?
7. Is the result complete or a budgeted projection?
Those seven questions prevent most over-interpretation.
Next step: read Provenance and confidence to learn how to weigh graph evidence during investigation and review.