docs / env-kernel / concepts

Concepts

The mental model behind env-kernel, in the order you'll meet it.

The environment model — everything is data

A world is a WorldTemplate (Pydantic, extra="allow") split into three logical layers:

Plus an instance layer (entities, resource_holdings, initial_relations, factions, adjacency) and optional subsystems (goals, skills, recipes, connectors, cognitive/social/crowd config, runtime parameters).

The runtime object is a WorldState graph: entity types + entities, resource pools, a relation graph, a spatial model (NoSpace / GridSpace / GraphSpace / Continuous2DSpace), a temporal model, and registered subsystem modules. build_world_state(schema) is the single canonical JSON→state builder (~30 focused _apply_* section-appliers); load_world wraps it and returns (state, engine).

The time model

Two modes, selected by temporal.mode:

Determinism under continuous time is protected in two ways: ScheduledEvent has a total ordering (time → priority → entity_id → id), so float-collision ties never resolve arbitrarily; and safety rails (MIN_ACTION_DURATION = 1e-9, DEFAULT_MAX_EVENTS = 1_000_000, max_time) force forward progress and guarantee termination.

Physics — coupled ODEs, "code is physics"

A template's physics block declares params, substeps, and variables. Each integrated variable carries a rate expression — the right-hand side of an ODE d(var)/dt = f(all vars, params, t) — and every variable's rate can reference every other, so genuinely coupled systems (Lotka–Volterra predator/prey, SIR epidemics, logistic growth, price discovery) are first-class. Integration is classic 4th-order Runge–Kutta with configurable sub-stepping (capped at MAX_SUBSTEPS = 4096); min/max clamp after each step. This is distinct from property_dynamics, which handles independent per-property drift/cascade/spawn.

Rate expressions run through a safe evaluator (_CompiledExpr): a whitelisted-AST numeric evaluator, not eval. It rejects attribute access, comprehensions, lambdas, non-numeric constants, and unknown symbols at build time (PhysicsExprError). Whitelisted functions: sin, cos, tan, exp, log, sqrt, abs, min, max, pow, floor, ceil, tanh, sigmoid, sign, clamp; constants pi, e, tau; time symbol t.

Graceful degradation: numeric blow-ups (division by zero, domain errors, overflow, non-finite results) never crash the sim — the step is skipped and a physics_error change is emitted. Physics is an enhancement layer, not a failure mode.

Grounding — binding physics to the world

"Grounding" is the binding of abstract numeric state to the concrete entity graph:

Agents are grounded too: the world's rules markdown is injected into every agent's perception as the world_brief, anchoring decisions in the declared premise and win conditions.

Perception — what an agent sees

Each turn the engine builds a plain perception dict (no kernel imports needed to read it) and passes it to decision_fn(entity_id, perception, valid_actions), where valid_actions is a list of action-name strings. The visibility-filtered base (visibility.py) always contains self (the agent's own entity as a dict), round, phase, visible_entities, visible_resources, visible_relations, location, active_world_events, and faction. The engine then layers optional keys on top when the relevant subsystem is active: world_brief, incoming_messages, domain_data, cognitive_state, crowd_trends, time_context, your_role / visible_roles / teammates, open_polls, your_recent_actions, trade_history, and (in richer worlds) inventory, goals, skills, location_info, and more.

Outcomes — resolution archetypes and termination

There is no single "OutcomeSpec" object; outcomes live at two levels.

Action outcomes are decided by resolution archetypes. An action declares resolution_archetype + resolution_params; the archetype returns a ResolutionResult(success, magnitude, narrative, details), and the action's effects_on_success / failure / partial are then applied. The 13 built-ins:

Archetype Semantics
deterministic Always succeeds
skill_check Probabilistic skill check
contest Opposed actor-vs-target contest
deterministic_math Success from a computed expression
voting Vote tally
evidence_chain Evidence accumulation
order_book Order-book matching
cpmm Constant-product market maker
market_impact Price-impact trade
auction_sealed_first / auction_sealed_second Sealed-bid auctions (first/second price)
auction_english / auction_dutch Open ascending / descending auctions

Custom archetypes register with @resolution("name").

Game outcomes are termination conditions with paired winner resolvers. A TerminationSpec names a check_type (17 built-ins, from expr and round_limit to last_one_standing, bankruptcy, faction_win, vote_threshold); when one fires, resolve_winner computes {"winner_id": ..., "winner_name": ...}. Winner resolvers ship for the competitive check types and are pluggable via register_winner_resolver.

The expression language

Guards, effects, and terminations share one safe expression grammar (predicates.evaluate / predicates.resolve):

$actor.gold >= 100 && $count(player, alive) > 1

$-expressions resolve against live state: $actor.x, $target.x, $random(...), $count(...), $lookup against schema tables. Every effect value, precondition, and termination expression can be grounded this way.

Authoring workflow

  1. Emit a JSON/dict WorldTemplate — an env-builder agent never writes Python.
  2. Reference genre mechanics by string name: registered domain modules, archetypes, effects.
  3. Where the built-ins run out, extend with zero kernel edits: @effect, @precondition, @resolution, @phase, @termination, register_winner_resolver, a custom KernelModule (state.register_module), or a custom DomainModule (DomainModuleRegistry.register). Files in kernel_primitives/*.py (repo + env-var directory) are auto-imported at startup, so downstream apps drop in primitives without touching kernel imports.
  4. Iterate: raw JSON → compile_template → lint → smoke_test → real-LLM playtest → pack into a .simworld env package.

Domain modules

A DomainModule bundles game-genre physics as a reusable unit, with hooks across the whole action lifecycle: tick, validate_action, filter_valid_actions, modify_resolution, post_resolution, get_perception_data, get_constraints, plus declared required_properties and custom_actions. Modules are discovered through the singleton DomainModuleRegistry and managed by DomainModuleManager. Two full implementations ship — PredictionMarketModule and SecuritiesTradingModule — with Economic/Political/Ecological/Health stubs marking the intended library.

Determinism

A first-class design promise, not a side effect: