Run contract

the universal “how a run works” — same for every simulator

Simulators are a base, you run anything

A stack is a base environment — a simulator or runtime installed, and nothing task-specific. You decide what to run on it, what to install, what to feed in, what to get out, and how much resource to use. Nothing is hardcoded: no baked-in task, no fixed metrics. You express a run either through the web Run form or a polygon.yaml in your project.

The run config (what you control)

# polygon.yaml — lives in your project, OR filled via the web Run form
base: mujoco                 # base image: mujoco | pybullet | python-cuda | openfoam | ...
setup:                       # commands run BEFORE run (install deps) — optional
  - pip install mujoco imageio
run: python main.py          # the command that runs YOUR code (required)
inputs:                      # downloaded into /work before the run — optional
  - { url: "https://…/scene.xml", path: "assets/scene.xml" }
outputs:                     # files/dirs to collect + make downloadable — optional
  - out/
  - render.mp4
resources: { cpu: 4, ram_gb: 8, gpu: 1 }   # requested; capped to the node's free share
display: false               # true → start a headless X (Xvfb) + allow GUI/video capture
env: { FOO: "bar" }

Everything is optional except base and run. Defaults: no setup, collect /outputs, a small default resource slice within the node share, display off.

Mount points (inside the container)

  • /work — your project (your code), and the working directory. Writable.
  • /inputs — files you asked to download, read-only.
  • /outputs — write results here; declared outputs are collected + made downloadable.
  • /config — materialized run config (incl. /config/run.json).

The ##metric protocol (live charts, optional)

Logs always stream. If you want live charts, print a line to stdout:

print(f"##metric loss={loss} step={i}")

Симолёт parses ##metric key=value [step=n] and draws it in real time. You can emit several keys per line (##metric ep_rew=231.5 success=0.8 step=40). No metrics? You still get logs plus artifacts. (You may instead write /outputs/metrics.jsonl.)

The base-image contract (how a run executes)

Every base image ships a generic entrypoint that:

  1. cd /work.
  2. If display is on (display: true / $POLYGON_DISPLAY): start Xvfb/EGL so

headless GUI/render works, and export DISPLAY.

  1. Run setup (bash -lc "$POLYGON_SETUP") to install your dependencies.
  2. Materialize inputs the agent already downloaded.
  3. Exec the run command — the container args if given, else $POLYGON_RUN, else a

sensible default (python main.py if present).

  1. Stream stdout/stderr — the agent forwards them as live logs.
  2. You write results to /outputs; the agent uploads the declared outputs.

The image never runs a task pipeline of its own — it is a base plus a generic runner.

Hooks

Lifecycle hooks run around the main command, interpreted by the base's runner:

  • hooks.setup — install/prepare (equivalent to setup: / POLYGON_SETUP).
  • hooks.pre — commands run just before the run command.
  • hooks.post — commands run after it (e.g. post-process, pack /outputs).

Streams (live telemetry, optional)

Beyond logs and metrics, a run can declare streams — named realtime channels (telemetry, frames) a client subscribes to. List them with GET /v1/jobs/{id}/streams, mint a ticket to …/subscribe, then consume …/data over SSE. A finished run also exposes a full replay timeline at GET /v1/jobs/{id}/recording.

Resource fairness

You request resources, but each node advertises only its shared slice. The scheduler filters to nodes that fit and the runner caps the container to min(requested, node share). A run can never exceed what a machine offered to the pool.

How the pieces carry it

  • Web form / `polygon.yaml` → a JobSpec: base image, command, env (incl.

POLYGON_SETUP, POLYGON_DISPLAY), workspace (your code: git / zip / inline), inputs, outputs, requires/limits.

  • Orchestrator stores the bases, accepts the full spec, and schedules by resources.
  • Node agent materializes the workspace, runs the base image (setup → command),

streams logs/metrics, collects + uploads the declared outputs, enforces the caps.

Ready to try it? Open Run, or browse the per-simulator guides for copy-paste examples.