Quickstart
Run your first autonomous simulation in under 2 minutes. Start with the engine-owned example — no Ollama, no API keys, no Unity required. Then layer in LLM cognition or Unity integration when you're ready.
Prerequisites
| Requirement | Minimum | Notes |
|---|---|---|
| Python | 3.11+ | Required for match statements and tomllib |
| pip | 23+ | For editable installs with [extras] |
| git | any | To clone the repository |
| Ollama | optional | Only needed for OllamaLLMBrain. Not required for this quickstart. |
| Unity | optional | Unity 6 only. Only needed for the C# SDK integration. |
Step 1 — Clone and install
git clone https://github.com/smithd36/biomata-engine.git cd biomata-engine # Core only — sufficient for this quickstart pip install -e . # Or: core + WebSocket server (for game engine integration) pip install -e ".[websocket]"
Verify the install:
python -c "from src.engine.simulation import Simulation; print('OK')" Step 2 — Run the engine-owned demo
The engine-owned example runs three agents (two guards, one merchant) with a deterministic brain. No external dependencies. The backend ticks whether or not a game client is connected.
biomata-ws --config examples/engine_owned/sim.yaml --port 8765
You should see agent decisions streaming tick by tick:
Tick 1 | Aldric | patrol → gate_post | holding position at gate Tick 1 | Berna | patrol → gate_post | holding position at gate Tick 1 | Silas | trade → market_stall | idle at market stall Tick 2 | Aldric | patrol → gate_post | holding position at gate ...
The engine-owned example uses IdleBrain — a deterministic brain that follows scripted
patterns. It produces real autonomous behavior without any LLM. Swap in OllamaLLMBrain
in sim.yaml when you're ready to add language model cognition — no Unity or client changes needed.
Both examples
Two examples ship with the engine. Both use IdleBrain by default — no Ollama required.
# Engine-Owned — 3 pre-declared agents; backend runs with or without Unity biomata-ws --config examples/engine_owned/sim.yaml --port 8765 # Host-Owned — backend starts empty; Unity registers agents at runtime biomata-ws --config examples/host_owned/sim.yaml --port 8765
Step 3 (optional) — Enable LLM cognition
Install Ollama, pull a model, then switch any agent's brain class in the YAML config:
# Pull a model (first time only) ollama pull qwen2.5:14b # Start Ollama (if not running as a service) ollama serve
Then in your YAML config, change the brain class and add an llm top-level block:
llm:
model: qwen2.5:14b
base_url: http://localhost:11434
temperature: 0.8
agents:
- id: agent_001
name: Guard-A
brain:
# From:
# class: src.plugins.builtin.idle_brain.brain.IdleBrain
# To:
class: src.plugins.builtin.ollama.brain.OllamaLLMBrain
personality:
traits: [vigilant, loyal]
goals: [protect the gate, report threats] Python API
Run simulations directly from Python without the CLI:
import asyncio
from src.engine.simulation import Simulation
sim = Simulation.from_config("examples/engine_owned/sim.yaml")
asyncio.run(sim.run()) WebSocket server
The WebSocket server exposes a running simulation over JSON-over-WebSocket so Unity, Unreal, browsers, or test harnesses can connect and drive agent cognition in real time.
The server binds to 127.0.0.1 (loopback) by default. For LAN or remote access
pass --host 0.0.0.0 explicitly.
Host-driven mode (default)
The client sends a tick request when ready. Use this when you need to synchronize
cognition with your game's physics or render loop (Unity FixedUpdate).
biomata-ws --config examples/engine_owned/sim.yaml --port 8765
Autonomous mode
The backend drives its own tick loop. Clients subscribe to events and use
pause/resume to control the loop. Use for headless research or batch runs.
biomata-ws --config examples/engine_owned/sim.yaml --port 8765 --mode autonomous
YAML configuration
Every simulation is described by a YAML config. The loader uses importlib to
dynamically import the class at each class: path and instantiate it with the
remaining fields as keyword arguments.
engine:
ticks: 9999 # number of ticks to run
seed: 42 # RNG seed — same seed = deterministic replay
scheduler: simultaneous # or "sequential"
world:
class: src.plugins.external.world.HostedWorld
agents:
- id: gate_guard_left
name: Aldric
capabilities: [patrol, authority]
brain:
class: src.plugins.builtin.idle_brain.brain.IdleBrain | Field | Required | Description |
|---|---|---|
engine.ticks | no (default 20) | Total ticks for sim.run(). Ignored by run_tick(). |
engine.seed | no (default 42) | Seeds random.Random. Same seed → identical run. |
engine.scheduler | no | simultaneous (default) or sequential. |
engine.scheduler_order | no | Explicit agent_id list for sequential mode; defaults to agent declaration order. |
world.class | yes | Fully-qualified Python dotted path to a World implementation. |
registry.class | no | Dotted path to a factory function returning an ActionRegistry. Defaults to an empty registry. |
observations.class | no | Dotted path to a factory returning an ObservationRegistry. |
llm | no | Top-level LLM config dict (model, base_url, etc.) forwarded to brain constructors. |
roles | no | Named role definitions with capabilities and brain block. Agents reference roles via agents[].role. |
agents[].role | no | Reference to a declared role; inherits capabilities and brain config. |
agents[].brain.class | yes (unless role supplies one) | Dotted path to a Brain implementation. |
agents[].state_ext.class | no | Dotted path to an AgentStateExtension implementation. |
agents[].capabilities | no | Capability tags (additive union with any role capabilities). |
agents[].metadata | no | Arbitrary dict attached to agent; not consumed by tick loop. |
Game engine integration (host-authoritative)
For Unity or any other engine, the typical setup is:
- Start the Python server:
biomata-ws --config your_sim.yaml --port 8765 - Connect the Unity SDK (see Unity SDK) or implement Protocol v1 directly (see Transport)
- Register agents via
register_agentwith a brain class - Each game tick: send observations, call
tick, read decisions and apply engine_commands
Minimal Python-side integration using HostedWorld:
import asyncio
from src.plugins.external.world import HostedWorld
from src.engine.simulation import Simulation
from src.engine.registry import ActionRegistry
from src.contracts.action import ActionSchema, ActionResult
from src.engine.agent import Agent
from src.plugins.builtin.idle_brain.brain import IdleBrain
from src.plugins.builtin.simple_memory.memory import SimpleMemory
class MoveHandler:
def execute(self, agent, intent, context) -> ActionResult:
direction = intent.parameters.get("direction", "north")
return ActionResult(
success=True,
outcome_text=f"moving {direction}",
engine_commands=[{"type": "navigate", "direction": direction}],
)
world = HostedWorld()
registry = ActionRegistry()
registry.register(
ActionSchema("move", "Move in a direction.", {"direction": "string"}),
MoveHandler(),
)
agent = Agent(
id="npc_001", name="Guard",
brain=IdleBrain(action="move", parameters={"direction": "north"}),
memory=SimpleMemory(),
)
sim = Simulation(agents=[agent], world=world, registry=registry)
async def game_loop():
for _ in range(3):
world.push_metadata({"time_of_day": "noon"})
world.push_observation("npc_001", {"location": "gatehouse", "health": 100})
summary = await sim.run_tick()
commands = summary.engine_commands()
print(f"Tick {summary.tick}: {commands}")
asyncio.run(game_loop()) Next steps
- Docker Quickstart — run the full stack with
docker compose up - Unity SDK — C# quickstart for Unity 6
- Examples — engine_owned, host_owned
- Core Engine — understand the tick loop, schedulers, and event bus
- Contracts — implement your own World, Brain, or ActionHandler
- Transport — WebSocket protocol reference for building your own client
- Contributing — how to write and submit plugins