Immutability in ilo came out of a token-cost calculation.
The question
Early in designing ilo I hit the obvious question: should variables be mutable? Python has x = 1; x = 2. Rust has let mut x. Haskell has no mutation at all. What should a language designed for AI agents do?
My first instinct was to add mutation. Agents process API responses, accumulate results, build strings - all of which feel like mutation. But when I looked at the actual patterns, the feeling was wrong.
What mutation looks like in ilo programs
The cases where you’d reach for mutation in Python:
# Accumulator pattern
total = 0
for item in items:
total += item.price
In ilo, this is a fold:
add a:n b:n>n;+a b
total=fld add items 0
No mutation needed. The accumulator is threaded through the function call.
# Building a result
result = []
for item in items:
if item.active:
result.append(item.name)
In ilo:
active x:t>b;x.active
names=xs >> flt active >> map .name
Still no mutation. Filter and map replace the loop-and-append pattern entirely.
The HTTP response case
The one that looked hardest: you fetch data, parse it, then need to transform it. Feels like you’d want to reassign a variable as you process each step.
r=get! url
parsed=jpar! r
result=parsed.items >> flt active >> map .name
Each step is a new binding. The intermediate names (r, parsed, result) cost tokens but they’re earned - each one names a distinct stage of the computation. You can read it top to bottom and understand what’s happening at each step.
Mutation would let you reuse a name (data = fetch; data = parse(data); data = filter(data)) but that’s worse - the same name means different things at different lines.
The real argument
Immutability fits the ilo model for a structural reason: ilo programs are small self-contained units. If a unit is wrong, you regenerate it - you don’t patch state. There’s no shared mutable global that an agent could corrupt.
Mutable variables also increase the error surface. An agent that reassigns x halfway through a function has to track what x means at each point. Immutable bindings mean a name has one meaning for its entire scope. Less to track, fewer wrong choices.
Where this lands
Ilo programs don’t need mutation. Patterns that feel like they need it (accumulators, building results, processing pipelines) map to fold, filter, map, or a chain of bindings.