git clone https://github.com/AppSprout-dev/RLE.git
cd RLE
uv sync --extra dev
pytest # should pass 326+ testsRequires Python 3.14+ and uv.
- Create a branch from
master - Make changes
- Run
pytest,ruff check src/ tests/ scripts/, andmypy src/ - Commit with a descriptive message
- Open a PR against
master
CI runs lint + type check + tests + smoke test on every push/PR.
| Service | Purpose | Default Port |
|---|---|---|
| LM Studio | LLM inference | 1234 |
| RimWorld + RIMAPI mod | Game state + actions | 8765 |
| Dashboard (optional) | Live visualization | 3000 |
| Tick data server (optional) | Dashboard data feed | 9000 |
| Docker (optional) | Headless benchmarks | 8765 |
Nemotron 3 Nano 4B (Q4_K_M, ~2.5GB VRAM). 100% parse rate, fits on 8GB cards.
LM Studio settings: Flash Attention ON, Context 10000, GPU Offload max, Keep in Memory ON.
# Smoke test — tests the full pipeline with fake game state
python scripts/run_benchmark.py --smoke-test --ticks 3
# Smoke test with real LLM (needs LM Studio running)
OPENAI_API_KEY=lm-studio python scripts/run_benchmark.py \
--smoke-test --provider openai \
--model unsloth/nvidia-nemotron-3-nano-4b \
--base-url http://localhost:1234/v1 \
--no-think --ticks 3# Start RimWorld with RIMAPI mod, load a colony, then:
OPENAI_API_KEY=lm-studio python scripts/run_scenario.py crashlanded_survival \
--provider openai \
--model unsloth/nvidia-nemotron-3-nano-4b \
--base-url http://localhost:1234/v1 \
--no-think --visualize --ticks 10# Build image (see docker/README.md for prerequisites)
docker compose -f docker/docker-compose.yml up -d
# Run benchmark against container
python scripts/run_benchmark.py --docker --runs 4 --output results/docker/OPENAI_API_KEY=<your-openrouter-key> python scripts/run_benchmark.py \
--provider openai \
--model nvidia/nemotron-3-super-120b-a12b \
--base-url https://openrouter.ai/api/v1 \
--no-think --ticks 10 --output results/- Python 3.14+ —
uvfor package management,hatchlingbuild backend - mypy strict — all code must pass
mypy src/withstrict = true - Async-first — httpx AsyncClient, async game loop
- Pydantic v2 — frozen models for all data structures
- No
Anytypes in metric contexts — useTYPE_CHECKINGimports to break circular deps - No scipy/numpy — stdlib only for statistics (see ADR-003)
- Parallel by default — agents deliberate concurrently via
asyncio.to_thread - JSON repair — LLM output goes through
json_repair.pybefore parsing - CentralPost for inter-agent context — not orchestrator-passed lists
- SSE events in agent context — each role agent gets relevant events in
filter_game_state()
- Create
src/rle/agents/your_agent.pysubclassingRimWorldRoleAgent - Set
ROLE_NAME,ALLOWED_ACTIONS,TEMPERATURE_RANGEclass vars - Implement
filter_game_state(),_get_task_description(),_get_role_description() - Add
"recent_events": self._format_events("relevant_event_type")tofilter_game_state() - Register in
src/rle/agents/__init__.py— add to_ROLE_AGENTSandAGENT_DISPLAY - Add tests in
tests/unit/test_role_agents.py
- Create
src/rle/scenarios/definitions/NN_your_scenario.yaml - Follow the schema: name, description, difficulty, expected_duration_days, initial_population, victory_conditions, failure_conditions, max_ticks, scoring_weights (include all 10 metrics)
- The loader auto-discovers YAML files — no registration needed
src/rle/
├── config.py # RLEConfig (env vars, provider, helix preset)
├── docker.py # Docker container lifecycle + RIMAPI health checks
├── rimapi/ # RIMAPI client + SSE + schemas
├── agents/ # 7 agents (MapAnalyst + 6 role) + base class + JSON repair
├── orchestration/ # Game loop, state manager, executor, resolver
├── scoring/ # 10 metrics, composite scorer, bootstrap CIs, CSV recorder
├── tracking/ # Cost tracking, event log, leaderboard, W&B/HF loggers
└── scenarios/ # YAML schema, loader, evaluator, 6 definitions
docker/ # HeadlessRim Dockerfile, compose, entrypoint
.github/workflows/ # CI (lint+test+smoke) and benchmark (Docker) workflows
- felix-agent-sdk >= 0.2.1
- RIMAPI C# mod
- httpx, pydantic >= 2.0, pyyaml
- Optional: wandb, huggingface-hub (
uv sync --extra tracking)
Open an issue or reach out on Discord.