Skip to main content
This is the agent loop end to end: build a tiny world, play it forward, and experiment on a fork. Every command below is real and runnable against a local server.

Prerequisites

  • macOS or Linux
  • A Rust toolchain (rustup)
  • A clone of the engine repository

1. Start the server

The agent API is a small example server. From the repo root:
cargo run -q -p euca-agent --example headless_server -- 3917
It binds localhost:3917. Leave it running and open a second terminal for the commands below.
The local server is unauthenticated — run it on a machine you control. It is meant to be driven by an agent on the same host.
Every POST must send -H 'Content-Type: application/json'. Without it the JSON body is rejected and the request looks like it was ignored.
Check it is up:
curl -s http://localhost:3917/
# { "engine": "Euca Engine", "version": "1.2.0", "entity_count": 1, "archetype_count": 1, "tick": 0 }
The one entity at boot is an internal bookkeeping entity (id 0); your spawns start at id 1.

2. Build a world

Spawn two entities and add a rule. Entity A gets a velocity and a Kinematic physics_body so a system actually moves it; entity B is a stationary target on the other team.
# Entity A — team 1, moving along +x.
curl -s -X POST http://localhost:3917/spawn \
  -H 'Content-Type: application/json' \
  -d '{"position":[0,1,0],"velocity":{"linear":[1,0,0]},"physics_body":"Kinematic","health":100,"team":1}'
# { "entity_id": 1, "entity_generation": 0 }

# Entity B — team 2, stationary at x = 5.
curl -s -X POST http://localhost:3917/spawn \
  -H 'Content-Type: application/json' \
  -d '{"position":[5,1,0],"health":60,"team":2}'
# { "entity_id": 2, "entity_generation": 0 }
Add a rule — logic is data, not code. This one heals any entity that drops below 50 health:
curl -s -X POST http://localhost:3917/rule/create \
  -H 'Content-Type: application/json' \
  -d '{"when":"health-below:50","filter":"any","actions":["heal this 20"]}'
# { "ok": true, "rule_id": 0, "when": "health-below:50" }

3. Play it

Step the simulation forward, then read the world back:
curl -s -X POST http://localhost:3917/step \
  -H 'Content-Type: application/json' -d '{"ticks":30}'
# { "ticks_advanced": 30, "new_tick": 30, "entity_count": 3 }

curl -s -X POST http://localhost:3917/observe
Entity A has advanced along +x (30 ticks at 1 unit/s, with dt = 1/60), while B has not moved:
{
  "tick": 30,
  "entity_count": 3,
  "entities": [
    { "id": 0, "generation": 0 },
    { "id": 1, "generation": 0,
      "transform": { "position": [0.5000002, 1.0, 0.0], "rotation": [0.0,0.0,0.0,1.0], "scale": [1.0,1.0,1.0] },
      "velocity": { "linear": [1.0,0.0,0.0], "angular": [0.0,0.0,0.0] },
      "physics_body": "Kinematic", "health": [100.0,100.0], "team": 1 },
    { "id": 2, "generation": 0,
      "transform": { "position": [5.0,1.0,0.0], "rotation": [0.0,0.0,0.0,1.0], "scale": [1.0,1.0,1.0] },
      "health": [60.0,60.0], "team": 2 }
  ]
}
That dump is the whole world as a flat table — see The world as a table.

4. Experiment on a fork

Fork the world, run a what-if only on the copy, and confirm the main run never moved.
# Fork at the current tick.
curl -s -X POST http://localhost:3917/fork \
  -H 'Content-Type: application/json' -d '{"fork_id":"what-if"}'
# { "ok": true, "fork_id": "what-if", "parent_tick": 30 }

# Run the counterfactual 100 ticks forward — on the fork only.
curl -s -X POST http://localhost:3917/fork/what-if/step \
  -H 'Content-Type: application/json' -d '{"ticks":100}'
# { "ok": true, "fork_id": "what-if", "start_tick": 30, "new_tick": 130, "ticks_advanced": 100, "entity_count": 3 }

# The fork advanced to tick 130:
curl -s http://localhost:3917/fork/what-if/observe        # → "tick": 130

# The main world is untouched at tick 30:
curl -s -X POST http://localhost:3917/observe             # → "tick": 30

# Drop the fork. The main run never changed.
curl -s -X DELETE http://localhost:3917/fork/what-if
# { "ok": true, "deleted": "what-if" }
The fork advanced to tick 130 while the main world stayed at tick 30. That clean divergence — the same systems running on an isolated copy — is what makes a fork a true counterfactual.

Going further

  • Diff two moments — snapshot the world with POST /snapshot, change it, snapshot again, and compare with GET /snapshot/diff (it compares game-state summaries: counts, phase, roles).
  • Save and replay — export the whole world as a scenario with GET /scenario and rebuild it with POST /scenario. See Determinism & replay.
  • Full API — every endpoint, request, and response is in the API reference.
  • Grade a world model — the same engine doubles as an exact answer key; see World-model evaluation.