20  The total so far

running_total adds a column up as it goes down the rows, so each row carries the total of everything to that point.

diary |> sort(on_) |> add(so_far = running_total(x))
g on_ x so_far
a 2026-01-02 10 10
a 2026-01-05 20 30
b 2026-01-06 30 60
diary >> sort(col.on_) >> add(so_far = running_total(col.x))
g on_ x so_far
a 2026-01-02 10 10.0
a 2026-01-05 20 30.0
b 2026-01-06 30 60.0

by restarts it for each group, and previous and following look one row back and one row on:

diary |>
  sort(on_) |>
  add(so_far = running_total(x), before = previous(x), after = following(x), by = g)
g on_ x so_far before after
a 2026-01-02 10 10 NA 20
a 2026-01-05 20 30 10 NA
b 2026-01-06 30 30 NA NA
(diary
 >> sort(col.on_)
 >> add(so_far = running_total(col.x), before = previous(col.x),
        after = following(col.x), by = col.g))
g on_ x so_far before after
a 2026-01-02 10 10.0 <NA> 20
a 2026-01-05 20 30.0 10 <NA>
b 2026-01-06 30 30.0 <NA> <NA>

The first row of a group has nothing before it and the last has nothing after it, so those are missing.

All three need a sort in front of them, and say so if they do not get one. A total so far means nothing until something has said so far in what order:

collect(diary |> add(so_far = running_total(x)))
Error:
! 
illegal: `running_total(...)` reads the rows in the order they are in, and nothing has said what that order is. Sort before it: `then sort [when] then add [so_far] as running_total(...)`. `rank([revenue] descending)` is the one that says what it goes by, so it needs no sort
  |
2 |   then add [so_far] as running_total([x])
  |                        ^^^^^^^^^^^^^^^^^^
try:
    collect(diary >> add(so_far = running_total(col.x)))
except GodError as refusal:
    print(refusal)

illegal: `running_total(...)` reads the rows in the order they are in, and nothing has said what that order is. Sort before it: `then sort [when] then add [so_far] as running_total(...)`. `rank([revenue] descending)` is the one that says what it goes by, so it needs no sort
  |
2 |   then add [so_far] as running_total([x])
  |                        ^^^^^^^^^^^^^^^^^^

rank is the exception, because its argument is the order: rank(x) says what it goes by, so it needs no sort in front of it.