docs / env-kernel / getting started

Getting started

Install

pip install fg-env-kernel

The distribution name is fg-env-kernel; the import package is fg_env_kernel (deliberately distinct — downstream projects depend on both names, so they are not renamed). Requires Python ≥ 3.11. The only runtime dependency is pydantic>=2.0.

For development:

pip install "fg-env-kernel[dev]"   # pytest, ruff, build
python -m pytest                    # 28 test files, ~353 tests

The CLI

A console script fg-env-kernel is installed (also runnable as python -m fg_env_kernel). Subcommands cover the whole authoring pipeline:

Command What it does
compile Validate, lint, and optionally smoke-test a JSON template
lint Lint a template without building it
contract / capabilities / versions Dump the kernel's JSON Schema contract and live capabilities
primitives List registered primitives (effects, resolutions, terminations, …)
new-primitive / scaffold-env Scaffold a new primitive file or environment directory
pack / unpack / env-info Work with .simworld env-package archives
replay Produce a deterministic step-by-step trace of a run

First simulation in three lines

A world definition is a plain dict (or WorldTemplate). Load it, attach an agent brain, run:

from fg_env_kernel import load_world

state, engine = load_world(my_world_definition, seed=42, decision_fn=my_agent_brain)
engine.run()

decision_fn is the entire agent interface:

decision_fn(entity_id: str, perception: dict, valid_actions: list) -> ActionInstance | None

valid_actions is a list of action-name strings (the actions currently legal for that entity). Return an ActionInstance (from fg_env_kernel.action) to act, or None to pass. The kernel is fully decoupled from any LLM — the same world runs identically with a real model, a heuristic, or a deterministic test stub.

A complete programmatic example

Worlds can also be built in Python directly against the state API. Two warriors, an attack action resolved by a strength contest, and a rest action:

from fg_env_kernel.state import WorldState
from fg_env_kernel.engine import SimulationEngine
from fg_env_kernel.entity import EntityType, Entity
from fg_env_kernel.resource import ResourceType
from fg_env_kernel.action import (
    ActionDefinition, ActionInstance, Precondition, Effect, Operator, EffectOperation,
)
from fg_env_kernel.types import PropertySchema, PropertyType

state = WorldState()

# 1. Types: what kinds of things exist, and what properties they carry
state.register_entity_type(EntityType(
    name="warrior", role="agent",
    properties=[
        PropertySchema(name="health", type=PropertyType.FLOAT, default=100.0,
                       min_value=0, max_value=100),
        PropertySchema(name="strength", type=PropertyType.INT, default=10),
        PropertySchema(name="stamina", type=PropertyType.FLOAT, default=50.0,
                       min_value=0, max_value=100),
    ],
))
state.register_resource_type(ResourceType(name="gold", conservation=True, discrete=True))
state.resources["gold"].holdings = {"w1": 50, "w2": 50}

# 2. Instances
state.spawn_entity(Entity(id="w1", name="Alice", entity_type="warrior",
                          properties={"health": 100.0, "strength": 15, "stamina": 50.0}))
state.spawn_entity(Entity(id="w2", name="Bob", entity_type="warrior",
                          properties={"health": 100.0, "strength": 10, "stamina": 50.0}))

# 3. Rules: an action = precondition + resolution archetype + effects
state.register_action(ActionDefinition(
    name="attack", description="Strike an opponent (opposed strength check)",
    actor_type="warrior", target_type="warrior",
    preconditions=[Precondition(subject="actor", operator=Operator.GTE,
                                field="stamina", value=10)],
    resolution_archetype="contest",
    resolution_params={"attacker_property": "strength", "defender_property": "strength"},
    effects_on_success=[
        Effect(target="target", operation=EffectOperation.SUBTRACT, field="health", value=20),
        Effect(target="actor",  operation=EffectOperation.SUBTRACT, field="stamina", value=10),
    ],
    effects_on_failure=[
        Effect(target="actor", operation=EffectOperation.SUBTRACT, field="stamina", value=5),
    ],
))
state.register_action(ActionDefinition(
    name="rest", description="Recover stamina",
    actor_type="warrior", resolution_archetype="deterministic",
    effects_on_success=[
        Effect(target="actor", operation=EffectOperation.ADD, field="stamina", value=15),
    ],
))

# 4. An agent brain (here: a stub that always rests)
def always_rest(entity_id, perception, valid_actions):
    if "rest" in valid_actions:
        return ActionInstance(action_name="rest", actor_id=entity_id,
                              reasoning="I need to rest.")
    return None

engine = SimulationEngine(state=state, decision_fn=always_rest, max_rounds=3, seed=42)
result = engine.run()
# result.get_entity("w1").get("stamina") == 95.0   (50 + 15 * 3, clamped at 100 later)

What just happened

Where to go next