Traversal
What graph traversal is and how it works in Omnigraph.
Graph traversal is how you move through connected data. Instead of joining tables on foreign keys, you follow typed edges from one node to the next. This is the fundamental operation that makes a graph database different from a relational one.
Why traversal matters
Consider finding all colleagues of a person named Alice. In SQL:
SELECT p2.name
FROM people p1
JOIN works_at w1 ON p1.id = w1.person_id
JOIN works_at w2 ON w1.company_id = w2.company_id
JOIN people p2 ON w2.person_id = p2.id
WHERE p1.name = 'Alice' AND p2.id != p1.id;In Omnigraph:
query colleagues($name: String) {
match {
$alice: Person { name: $name }
$alice worksAt $c
$colleague worksAt $c
$colleague != $alice
}
return { $colleague.name }
}The graph query reads like the question you're asking. No join keys, no intermediate IDs, no aliased table references.
One hop
The simplest traversal follows a single edge from one node to another.
query where_does_alice_work($name: String) {
match {
$p: Person { name: $name }
$p worksAt $c: Company
}
return { $c.name }
}$p worksAt $c is a one-hop traversal: start at a Person node, follow a WorksAt edge, arrive at a Company node.
Two hops
Chain two edge traversals to reach nodes two relationships away.
query friends_of_friends($name: String) {
match {
$p: Person { name: $name }
$p knows $friend
$friend knows $fof
$fof != $p
}
return { $fof.name }
}Variable depth
When the number of hops is unknown -- like finding all ancestors in a hierarchy -- use recursive traversal with *.
query all_ancestors($name: String) {
match {
$child: Category { name: $name }
$child subcategoryOf* $ancestor
}
return { $ancestor.name }
}The * operator follows the SubcategoryOf edge zero or more times, returning every node reachable along that path.
Cross-type traversal
Traversal isn't limited to one node type. A single query can cross multiple edge and node types.
query what_do_alice_friends_work_on($name: String) {
match {
$p: Person { name: $name }
$p knows $friend
$friend worksAt $c: Company
}
return { $friend.name, $c.name }
}This starts at a Person, traverses a Knows edge to another Person, then traverses a WorksAt edge to a Company.
Negation: reasoning about absence
Negation lets you find things that are missing expected relationships. Wrap the traversal in not { }.
query unassigned_tickets() {
match {
$t: Ticket { status: "open" }
not { $t assignedTo $a }
}
return { $t.slug, $t.title }
}This returns open tickets with no AssignedTo edge -- something that requires a LEFT JOIN and NULL check in SQL.
Traversal with filtering
You can filter on properties at any point in the traversal path.
query senior_colleagues($name: String) {
match {
$p: Person { name: $name }
$p worksAt $c
$colleague worksAt $c
$colleague != $p
$colleague.age > 40
}
return { $colleague.name, $colleague.age }
}Traversal with search
Combine traversal with search expressions to narrow results by content relevance.
query expert_colleagues($name: String, $topic: String) {
match {
$p: Person { name: $name }
$p worksAt $c
$colleague worksAt $c
$colleague != $p
search($colleague.bio, $topic)
}
order bm25($colleague.bio, $topic)
return { $colleague.name, $colleague.bio }
}How agents use traversal
| Agent task | Traversal pattern |
|---|---|
| Build context for a prompt | One-hop: fetch entity + immediate neighbors |
| Find related entities | Two-hop: entity -> shared connection -> related entity |
| Trace dependencies | Variable-depth: follow dependency edges recursively |
| Detect gaps | Negation: find entities missing expected edges |
| Scope a search | Traversal + search: search within a subgraph |
| Impact analysis | Multi-hop: trace all paths from a changed entity |