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.