Finding
Temporal interrogatives are keyed by source line, and a line executed N times retains only the Nth state. The reversibility layer therefore cannot answer "what was x on iteration 2" — which is the main reason to reach for it when debugging a loop.
Reproduction
v0.38.0, release build at 078e759.
x is 0
for i in range of 5:
x is i * 10
print of ("what is x at 3 -> " + (str of (what is x at 3)))
print of ("state_at of 3 -> " + (str of (state_at of 3)))
print of ("prev of x -> " + (str of (prev of x)))
what is x at 3 -> 40
state_at of 3 -> {"x": 40, "i": 4}
prev of x -> 30
Line 3 executed five times, producing 0, 10, 20, 30, 40. Only the last is reachable. prev of x gives one more step back, and that is the entire depth available.
Two problems with line-keyed addressing
1. Non-injective for anything in a loop or a called function. The address space is source lines; the event space is executions. Every loop body and every function called more than once collapses. Since the interesting temporal questions are almost always about a loop iteration or a particular call, the addressable set excludes most of what one would want to ask.
2. Edit-brittle. A query is written against a line number, so inserting a line anywhere above it silently changes what every downstream query means. This is why tests/test_temporal.eigs is line-number sensitive (flagged in CLAUDE.md) — the test suite is already paying this cost, which is the signal that the addressing model rather than the test is the awkward part.
Suggested direction
An occurrence index alongside the line would make the loop case addressable without changing the existing surface:
what is x at 3 — keep current meaning (most recent execution of line 3).
what is x at [3, 2] — the 2nd execution of line 3.
- Or an event-counter form keyed off
when is x (which already counts assignments): what is x when 2.
Either is additive. The when counter already exists and is per-binding, so the assignment index is the cheaper of the two to wire up.
Worth weighing against #827, which found the assignment history is already unbounded and OOMs long-running programs — any per-occurrence retention needs a bound decided at the same time. That interaction is probably the main design constraint here.
Related: #827, docs/TRACE.md.
Finding
Temporal interrogatives are keyed by source line, and a line executed N times retains only the Nth state. The reversibility layer therefore cannot answer "what was
xon iteration 2" — which is the main reason to reach for it when debugging a loop.Reproduction
v0.38.0, release build at
078e759.Line 3 executed five times, producing
0, 10, 20, 30, 40. Only the last is reachable.prev of xgives one more step back, and that is the entire depth available.Two problems with line-keyed addressing
1. Non-injective for anything in a loop or a called function. The address space is source lines; the event space is executions. Every loop body and every function called more than once collapses. Since the interesting temporal questions are almost always about a loop iteration or a particular call, the addressable set excludes most of what one would want to ask.
2. Edit-brittle. A query is written against a line number, so inserting a line anywhere above it silently changes what every downstream query means. This is why
tests/test_temporal.eigsis line-number sensitive (flagged inCLAUDE.md) — the test suite is already paying this cost, which is the signal that the addressing model rather than the test is the awkward part.Suggested direction
An occurrence index alongside the line would make the loop case addressable without changing the existing surface:
what is x at 3— keep current meaning (most recent execution of line 3).what is x at [3, 2]— the 2nd execution of line 3.when is x(which already counts assignments):what is x when 2.Either is additive. The
whencounter already exists and is per-binding, so the assignment index is the cheaper of the two to wire up.Worth weighing against #827, which found the assignment history is already unbounded and OOMs long-running programs — any per-occurrence retention needs a bound decided at the same time. That interaction is probably the main design constraint here.
Related: #827,
docs/TRACE.md.