Joins and arithmetic work row by row. Aggregation goes the other way: it collapses many rows into one summary value — how many neighbours a node has, the total weight in a graph, the maximum score per player. In XLOG you write an aggregate directly in a rule head.

Where aggregates go

An aggregate may appear only in a rule head, never in a body. This is a deliberate restriction: the head is where the summary lands, and keeping aggregates out of bodies keeps evaluation well-defined. The two rules below are the canonical shapes:
The first counts, for each X, how many distinct edge(X, Y) rows exist. The second sums the second column of every weight fact into a single total.

The five aggregates

XLOG provides exactly five aggregate operators — no others:
There is no built-in avg. Compute an average as sum divided by count — derive each with its own rule, then combine them:

Group-by is implicit

You never write a GROUP BY clause. The non-aggregate variables in the head are the grouping key. In:
X is not aggregated, so the engine forms one group per distinct X and computes count(Y) within each. When the head has no non-aggregate variable, the whole relation is a single group — that is exactly what total(sum(W)) does, producing one global total.

Value types

An aggregate’s result type follows from the operator and the type of the column it consumes: count always returns a u64 regardless of what it counts. sum emits u64, widening u32 inputs and preserving u64 storage. min and max return the same type they consume because they select an existing value rather than producing a widened total.

Aggregation is stratified

When this matters: only if an aggregate’s inputs could loop back to its own result — usually recursive rules; otherwise you can skip this section. Stratified means xlog settles those inputs in order, computing the aggregate only after everything it reads is final. An aggregate introduces a stratification boundary: a relation defined by aggregation cannot depend, recursively, on itself through that aggregate. Concretely, you cannot have a rule whose head aggregates a predicate that (directly or transitively) depends on the head. The engine computes each aggregate only once its inputs are fully determined, so results are well-defined rather than chasing a moving target. This mirrors how negation is stratified in Facts and rules: both need their inputs settled before they can produce a sound answer.
Under exact probabilistic inference, aggregates over uncertain rows have finite domain caps — beyond them you switch to the Monte Carlo engine. That is a property of the probabilistic engines, not of deterministic aggregation, and is covered separately.

Probabilistic engines

Exact versus Monte Carlo inference, and the finite caps on exact probabilistic aggregation.

Lists and meta

Build and pattern-match lists, and collect results with the meta-predicates.