Skip to content

Fitness

Fitness is how you tell evolution what “success” looks like - it is the only guidance the evolution engine receives. The fitness block defines three things: gates (hard constraints), metrics (how to measure performance), and weights (how much each metric matters). Getting this right is the most impactful decision in any experiment. See the Fitness Design guide for practical advice.

fitness OperatorAssessment {
gate alive
gate route_progress = agent.reached_end ? 1.0 : agent.position / world.length
-- How many waypoints were properly served
metric waypoint_service {
per record waypoint_visit: stopped
aggregate: avg
}
-- Route completion
metric completion = agent.position / world.length
-- Speed compliance (lower is better)
metric overspeed = agent.speed > 0 ? 0.0 : 0.0
terminate when agent.reached_end
maximize completion: 100.0
maximize waypoint_service: 60.0
penalize engine.complexity: 0.001
}

Gates are hard prerequisites checked before any metric computation. If a gate fails, the consequences depend on the gate type.

-- Used in all three demos
gate alive

A boolean gate checks a single agent state. If the agent is dead (alive = false), total fitness is zero - all metrics and penalties are discarded. This is a hard zero, not a multiplier.

-- Human Factors: route progress as a multiplier
gate route_progress = agent.reached_end ? 1.0 : agent.position / world.length

An expression gate defines a multiplier applied to all maximize and reward metrics. The expression is evaluated at scenario end. Penalties bypass gates and always apply in full.

When multiple gates are defined, their values multiply together: gate_product = gate1 * gate2 * .... Boolean gates contribute 0.0 (fail) or 1.0 (pass). Expression gates contribute their computed value.

The Human Factors demo uses both a boolean gate (alive) and an expression gate (route_progress). The Network Security and Survival demos use only the boolean gate.


Metrics define how performance is measured. There are several patterns for defining metrics.

Evaluated once at scenario end from agent/world state.

-- Human Factors
metric completion = agent.position / world.length
-- Network Security
metric detection_rate = agent.connections_seen > 0 ?
agent.true_positives / max(1, agent.true_positives + agent.threats_missed) : 0
-- Survival
metric survival = agent.ticks_alive
metric health_avg = agent.health
metric foraging = agent.food_eaten + agent.water_drunk

Iterates over event records emitted by entity interaction handlers during the scenario.

-- Human Factors: average whether waypoints were stopped at
metric waypoint_service {
per record waypoint_visit: stopped
aggregate: avg
}
-- Network Security: average classification correctness
metric accuracy {
per record classification: correct
aggregate: avg
}

Record fields are accessible by name inside the expression. The aggregate function is applied after iterating all records of the given type. If no records of the type were emitted, the metric evaluates to 0.0.

Compiles to a running accumulator updated at step 7 of each tick. The aggregate function is applied at scenario end.

metric jerk {
per tick: abs(agent.accel - agent.prev_accel) / dt
aggregate: avg
transform: clamp((value - 0.5) / 1.5, 0, 1)
}
FunctionDescription
avgAverage over accumulated values
sumSum of accumulated values
minMinimum of accumulated values
maxMaximum of accumulated values

An optional post-processing step applied to the aggregated value before weighting. The keyword value refers to the aggregation result.

metric jerk {
per tick: abs(agent.accel - agent.prev_accel) / dt
aggregate: avg
transform: clamp((value - 0.5) / 1.5, 0, 1)
}

Weight verbs tell evolution how each metric contributes to fitness.

VerbMeaningGated?
maximizeHigher values produce higher fitnessYes (multiplied by gate product)
rewardAccumulative count contributes positivelyYes (multiplied by gate product)
penalizeValue is subtracted from fitnessNo (always applies in full)
maximize completion: 100.0
maximize waypoint_service: 60.0
penalize engine.complexity: 0.001

The number after the metric name is the weight. The fitness computation is:

total = sum(maximize_metrics * weights) * gate_product
+ sum(reward_metrics * weights) * gate_product
- sum(penalize_metrics * weights)

When a weight verb references a name without a preceding metric definition, the name resolves to agent.<name>. This means:

-- Survival uses this pattern
reward health_avg: 5.0

is equivalent to:

metric health_avg = agent.health_avg
reward health_avg: 5.0

This shorthand is only valid for simple agent state lookups. Complex metrics must use explicit metric definitions.

The engine provides built-in metrics that are always available regardless of domain. These use the engine.* namespace and do not need metric definitions.

MetricDescription
engine.complexityGenome connection count
engine.nodesGenome node count

All three demos penalize complexity to encourage simple brains:

penalize engine.complexity: 0.001

The fitness block can define conditions that end a scenario early. There are two mechanisms:

Death termination: Setting agent.alive = false (via entity handlers, action logic, or dynamics death conditions) kills the agent. The engine skips dead agents at the top of each tick. The gate alive multiplier zeroes the reward portion of the fitness score.

Success termination: The terminate when directive ends the scenario with the agent still alive. This is for conditions where continued evaluation is pointless - the agent has reached its goal.

-- Human Factors
terminate when agent.reached_end
-- Network Security
terminate when agent.position >= world.length

Terminate conditions are checked after step 7 of each tick. If any terminate when expression returns a truthy (non-zero) value, the scenario ends immediately. The agent is considered alive for gate purposes, and all metrics evaluate against the final state.

The Survival demo does not use terminate when - scenarios run for the full tick budget, and the agent can die through the dynamics death block if health or energy reaches zero.


  • Metric names referenced by weight verbs must either be defined with metric or resolve to agent state
  • per record references must match record types emitted by entity interaction handlers
  • per tick expressions are compiled to running accumulators
  • gate expressions are evaluated at scenario end
  • terminate when expressions are checked every tick

All keywords and constructs available inside a fitness block.

FormSyntaxDescription
Boolean gategate aliveChecks agent.alive. If false, total fitness is zero
Expression gategate name = exprEvaluated at scenario end. Value multiplies all maximize and reward metrics

Multiple gates multiply: gate_product = gate1 * gate2 * .... Boolean gates contribute 0.0 or 1.0.

TypeSyntaxEvaluationDescription
Simplemetric name = exprOnce at scenario endEvaluates an expression against final agent/world state
Per-recordmetric name { per record type: field ... aggregate: func }After scenarioIterates over event records emitted during the scenario
Per-tickmetric name { per tick: expr ... aggregate: func }Running accumulatorExpression sampled every tick, aggregated at scenario end
metric completion = agent.position / world.length
metric survival = agent.ticks_alive
metric foraging = agent.food_eaten + agent.water_drunk

Evaluated once at scenario end. Has access to the full expression language including ternary:

metric idle_rate = agent.ticks_alive > 0 ?
agent.idle_ticks / agent.ticks_alive : 1.0
metric accuracy {
per record decision: correct
aggregate: avg
}
FieldRequiredDescription
per record <type>: <field>YesRecord type name and the field to extract
aggregateYesAggregation function (avg, sum, min, max)
transformNoPost-processing expression applied to the aggregated value

If no records of the given type were emitted, the metric evaluates to 0.0.

metric jerk {
per tick: abs(agent.accel - agent.prev_accel) / dt
aggregate: avg
transform: clamp((value - 0.5) / 1.5, 0, 1)
}
FieldRequiredDescription
per tick: <expr>YesExpression sampled every tick
aggregateYesAggregation function applied at scenario end
transformNoPost-processing; value refers to the aggregation result
FunctionDescription
avgMean of accumulated values
sumSum of accumulated values
minMinimum of accumulated values
maxMaximum of accumulated values
<verb> <metric_name>: <weight>
VerbEffectGated?Description
maximize+ metric * weight * gate_productYesHigher metric values produce higher fitness
reward+ metric * weight * gate_productYesAccumulative positive contribution to fitness
penalize- metric * weightNoValue subtracted from fitness (bypasses gates)

The fitness formula:

total = sum(maximize_metrics * weights) * gate_product
+ sum(reward_metrics * weights) * gate_product
- sum(penalize_metrics * weights)
terminate when <expr>

Checked after step 7 of each tick. If the expression returns a truthy (non-zero) value, the scenario ends immediately. The agent is considered alive for gate purposes.

Always available without explicit metric definitions.

MetricDescription
engine.complexityGenome connection count
engine.nodesGenome node count
penalize engine.complexity: 0.001

When a weight verb references a name without a preceding metric definition, it resolves to agent.<name>:

reward health_avg: 5.0
-- equivalent to: metric health_avg = agent.health_avg