diff --git a/docs/concepts/CLQL.md b/docs/concepts/CLQL.md
index 1b9f27d..9b05160 100644
--- a/docs/concepts/CLQL.md
+++ b/docs/concepts/CLQL.md
@@ -277,6 +277,153 @@ method(depth = any):
+# Edge
+
+Facts in AST lexicons refer to nodes in an AST, and the parent/child relationship between facts refers to the parent/child relationship of nodes in the AST. These nodes can have other parent/child relationships that are orthogonal to AST, such as calls. These relationships can be queried with the `edge` keyword.
+
+The following query finds function calls at the top level of a file and follows the `calls` edge to their definition:
+
+```
+common.func_call:
+ edge("calls"):
+ common.func
+```
+
+
+
+# Path
+
+Path statements encapsulate CLQL trees. These subtrees can be repeated with a single argument allowing succint repition of complex patterns. Branched paths can rejoin allowing a fact to match nodes with different kinds of parents.
+
+## Linear
+
+Say we wanted to find triply nested if statements, our query would look like the following:
+
+```clql
+common.if_stmt:
+ common.if_stmt:
+ common.if_stmt
+```
+
+With paths, we can express the same thing like so:
+
+```clql
+path(repeat = 3):
+ common.if_stmt:
+ pathcontinue
+```
+
+Once a query reaches a `pathcontinue` statement it continues from the `path` statement until the path has been repeated the specified number of times.
+
+## Repeat range
+
+Some queries cannot be written with `path` statements. Say we wanted to find all functions called by `someFunc()` and an arbitrarily long chain of calls. Our query would have to explicitly match either directly called functions, or functions with 1, 2, 3 etc intermediaries to infinity.
+
+```clql
+common.func:
+ name == "someFunc"
+ any_of:
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func:
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func
+ ...
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func:
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func:
+ ...
+```
+
+With paths the same query is trivial:
+
+```clql
+common.func:
+ name == "someFunc"
+ path(repeat = 1:):
+ common.func_call(depth = any):
+ edge("calls"):
+ common.func:
+ pathcontinue
+```
+
+`repeat = 1:` is a range specifying that the path should be repeated one or more times.
+
+## Complex subtrees
+
+Say we wanted to match triply nested if statements that all check the same value, our query would look like the following:
+
+```clql
+common.if_stmt:
+ common.condition:
+ common.var:
+ name as varName
+ common.if_stmt:
+ common.condition:
+ common.var:
+ name == varName
+ common.if_stmt:
+ common.condition:
+ common.var:
+ name == varName
+```
+
+With paths our query has much less repitition:
+
+```clql
+common.func:
+ path(repeat = 3):
+ common.if_stmt:
+ common.condition:
+ common.var:
+ name as varName
+ pathcontinue
+```
+
+Note that CLQL elements that are children of `path`, not just the `if_stmt`. Also note that repeated definitions of `varName` are replaced with assertions.
+
+## Pathend
+
+Suppose we wanted to match triply nested if statements with a function call inside the innermost if statement. Without paths our query looks like:
+
+```clql
+common.if_stmt:
+ common.if_stmt:
+ common.if_stmt:
+ common.func_call
+```
+
+with paths our query looks like:
+
+```clql
+path(repeat = 3):
+ common.if_stmt:
+ pathcontinue
+ pathend:
+ common.func_call
+```
+
+## Caveats
+
+Branching, where `path` statement has multiple `pathcontinue` statements is currently not supported.
+
+Nested paths are not supported.
+
+Using `any_of` inside a path statement is not supported.
+
+## Decorators
+
+Some decorators such as `@review comment` can only be used once per query. Using them in a repeated path will cause an error.
+
+
+
# Variables
Facts that do not have a parent-child relationship can be compared by assigning their properties to variables. A query with a variable will only match a pattern in the code if all properties representing that variable are equal.