Skip to main content
Fareground

env-kernel

A deterministic simulation kernel for agent environments — LLMs are brains, code is physics.

CI Python Dependencies Engine


Overview

env-kernel is a continuous-time simulation kernel for agent environments: you describe a world as declarative data, and the kernel compiles it into an executable, deterministic, multi-agent simulation — with RK4 coupled-ODE integration for continuous dynamics and grounded, verifiable outcomes. It is pure Python with a single runtime dependency (pydantic) and no coupling to any game, domain, or LLM provider.

env-kernel is stewarded by Fareground and is one of six open-source building blocks alongside agent-id, agent-messaging, agent-knowledge, agent-memory, and agent-framework.

Agents make discrete, turn-based decisions; the kernel is the deterministic rule engine that resolves those decisions and evolves the world around them. It knows nothing about chess or markets or elections — those are just configurations. New mechanics plug in through registries and decorators, never by editing the engine.

Between agent turns the world does not have to sit still: an event-driven clock and a coupled-ODE physics integrator can evolve numeric state continuously, so action durations and reaction speed become part of the strategy.

Install

Note: the distribution name is fg-env-kernel and the import package is fg_env_kernel. These are unchanged — downstream projects depend on them, and renaming them would break those imports.

The package is not on PyPI — install from GitHub:

pip install "fg-env-kernel @ git+https://github.com/Fareground/env-kernel.git"

Importing the package never scans the filesystem. Drop-in primitive discovery (kernel_primitives/*.py) is opt-in: call fg_env_kernel.discover() explicitly, or set the KERNEL_PRIMITIVES_DIR environment variable — an explicitly configured directory is honored at import time.

Quickstart

One line — the built-in seeded random agent plays every turn:

from fg_env_kernel import simulate

world = simulate("path/to/template.json")   # or a template dict
print(world.summary())

simulate(template, *, agent=None, seed=None, max_rounds=None, on_event=None, registry=None) loads the template (dict, WorldTemplate, or path to a JSON file), runs to completion, and returns the finished World. With no agent, a deterministic random-valid-action policy (random_policy) drives every turn — same seed, same run. Pass your own decision_fn as agent to plug in an LLM.

Bring your own agent

A world is a plain dict; an agent is a plain function. This is a complete, runnable program:

from fg_env_kernel import ActionInstance, Kernel

template = {
    "name": "Race to 10",
    "description": "Two runners sprint; first to distance 10 wins.",
    "entity_types": [
        {"name": "runner", "role": "agent", "properties": [
            {"name": "distance", "type": "float", "default": 0}
        ]}
    ],
    "entities": [
        {"id": "alice", "entity_type": "runner", "name": "Alice"},
        {"id": "bob", "entity_type": "runner", "name": "Bob"},
    ],
    "actions": [
        {"name": "sprint", "description": "Run forward.", "actor_type": "runner",
         "effects_on_success": [
             {"operation": "add", "target": "actor", "field": "distance",
              "value": "$random(1, 3)"}
         ]}
    ],
    "termination_conditions": [
        {"name": "finish_line", "check_type": "expr",
         "params": {"expr": "$state.entities.alice.distance >= 10 || "
                            "$state.entities.bob.distance >= 10"}}
    ],
    "temporal": {"max_rounds": 20},
}

def decision_fn(entity_id, perception, valid_actions):
    """Called once per agent turn. Swap in an LLM call here."""
    if "sprint" not in valid_actions:
        return None
    return ActionInstance(action_name="sprint", actor_id=entity_id)

world = Kernel(seed=42).load(template, decision_fn=decision_fn)
world.run()                       # or: while not world.finished: world.step()

print(world.terminated_by)        # "finish_line"
print(world.current_round)        # 5
print(world.events[-1].narrative) # "Simulation ended after 5 rounds."

Same seed, same template, same decision_fn → same run, every time. More in examples/ — including tic-tac-toe built from a domain module.

The agent contract

decision_fn(entity_id, perception, valid_actions) -> ActionInstance | None is the only interface between your agent (LLM or otherwise) and the kernel:

  • entity_id — id of the agent whose turn it is.
  • perception — a plain dict of what this agent can see, visibility-filtered. Always present: self (own id/name/properties), visible_entities, visible_relations, visible_resources, round, phase, location, faction. Present when the world provides them: world_brief (the template's name/description/rules markdown), incoming_messages, your_recent_actions, domain_data (board layout, hand contents, market state, ...), and more (roles, polls, time context, trade history).
  • valid_actions — names of the actions whose preconditions currently pass. Return an ActionInstance whose action_name is one of these (with actor_id=entity_id and any parameters the action declares), or None to skip the turn.

The engine is fully decoupled from the LLM — the same world runs with real agents, cheap heuristics, or a deterministic test stub.

Kernel(seed=..., registry=...) holds run configuration; Kernel.load(template, decision_fn=..., on_event=..., seed=..., max_rounds=...) accepts a template dict, WorldTemplate, or path to a JSON file, and returns a World with run(), step(), finished, terminated_by, current_round, events, state, seed, and a readable summary(). An on_event callback streams each event as it is emitted.

The ladder: simulate() for one-shot runs → Kernel/World for stepwise control → load_world for the raw engine.

Going lower level

The facade is a thin wrapper over load_world(template, *, seed=0, decision_fn=None, on_event=None, registry=None), which returns the raw (WorldState, SimulationEngine) pair — use it when you need direct engine or state access. Custom primitives register through the decorator surface (@effect, @resolution, @phase, @termination_decorator, @module) shown below.

The full template shape is documented in docs/template_schema.md; the machine-readable contract (including the live list of every registered effect operation, resolution archetype, termination check, and domain module) is docs/kernel_contract.json.

Continuous time and physics

A physics block on the world definition declares numeric variables and their rates of change. A dt-aware 4th-order Runge–Kutta integrator evolves them between turns — predator/prey, epidemics (SIR), price discovery. Variables can read entity aggregates and write values back onto the world. The result is deterministic and serializable.

"physics": {
    "params": {"alpha": 1.1, "beta": 0.4, "delta": 0.1, "gamma": 0.4},
    "variables": [
        {"name": "prey", "value": 10, "rate": "alpha*prey - beta*prey*pred", "min": 0},
        {"name": "pred", "value": 5,  "rate": "delta*prey*pred - gamma*pred", "min": 0}
    ]
}

Extending the engine

Register custom verbs, resolution archetypes, phases, and terminations with decorators — the engine looks everything up by string name through the registry. The module-level decorators register process-wide:

from fg_env_kernel import effect, EffectContext

@effect("grant_gold")
def grant_gold(ctx: EffectContext, spec: dict) -> None:
    gold = ctx.actor.properties.get("gold", 0)
    ctx.actor.properties["gold"] = gold + spec.get("value", 1)

For per-kernel isolation, fork the registry and register on the fork. A fork sees every built-in primitive (nothing is copied — unknown names fall back to the parent), but its own registrations are invisible to the global registry and to other forks:

from fg_env_kernel import Kernel, registry

mine = registry.fork()

@mine.effect("grant_gold")
def grant_gold(ctx, spec): ...

kernel = Kernel(seed=42, registry=mine)   # worlds resolve against `mine` only

Every namespace has an instance decorator (mine.effect, mine.precondition, mine.resolution, mine.phase, mine.termination, mine.module, mine.target_selector). Validation/reporting surfaces (lint_template, export_kernel_contract) read the global registry.

Concepts

  • Determinism — given a template, a seed, and a decision_fn, a run is fully reproducible. State is serializable end to end, so runs can be replayed step by step.
  • Declarative worlds — entities, properties, resources, relations, actions, effects, and terminations are all data. A safe expression grammar ($actor.gold >= 100 && $count(player, alive) > 1) powers guards, effects, and terminations without per-game Python.
  • Turn-based agents, continuous world — agents decide in discrete turns; an event-driven clock and the physics integrator evolve the world between those turns.
  • Registry extension points — custom verbs, resolution archetypes, phases, terminations, and domain modules register by name, keeping the engine core untouched.

Project Structure

src/fg_env_kernel/
  runtime/        the tick loop (discrete + continuous)
  physics.py      coupled-dynamics ODE integrator
  state.py        the world state graph
  action.py …     actions, effects, resolution archetypes
  predicates.py   the expression language
  domain/         optional game-genre modules (markets, boards, …)
  pipeline/       compile · lint · smoke · replay · package

See the CHANGELOG for what's new.

Contributing

See CONTRIBUTING.md for dev setup, running the test suite, and lint/format tooling.


Stewarded by Fareground.
Licensed under the Apache License 2.0.

Metadata

Release files for fg-env-kernel 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fg-env-kernel 0.2.0
File Size Uploaded
fg_env_kernel-0.2.0.tar.gz 361.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fg-env-kernel 0.2.0
File Interpreter ABI Platform
fg_env_kernel-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 701.8 kB

Release files / fg_env_kernel-0.2.0.tar.gz

Download URL fg_env_kernel-0.2.0.tar.gz
Size 361.9 kB
Tags Source
SHA-256 checksum
How to use checksums
200228f998d0d61e53eb5bfe93b0e8e6a1821a092b6ec14f1dea96d0eab5d717
BLAKE2b-256 checksum
How to use checksums
9d178c5ce3b16cd4f3933176ba465386843dfaceb16adbe9f89ff694adeba43c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release files / fg_env_kernel-0.2.0-py3-none-any.whl

Download URL fg_env_kernel-0.2.0-py3-none-any.whl
Size 339.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
38acbbd339e8101408601c4e498341e40ba2067d427988ce73ca91c2f990ef83
BLAKE2b-256 checksum
How to use checksums
be407c72718e39e6fd1b931fdec5136c65093b82530eb38c47dcff41fd883e41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page