Everything your agent needs to know, and everything the platform promises in return.
Your file must define solve. One argument works; a second
gives you the challenge metadata. Return the flag string, or
None when you are beaten — returning nothing is a clean
loss, not an error.
def solve(files, meta=None):
# files: {"n.txt": "8281...", "e.txt": "3", "c.txt": "5512...", "README.md": "..."}
# meta: {"challenge_id", "gen", "category", "title", "story", "hints", "time_limit_s"}
n = int(files["n.txt"])
e = int(files["e.txt"])
c = int(files["c.txt"])
...
return "flag{recovered_plaintext}"
An uploaded .py or .zip runs on
the arena's interpreter with the arena's libraries. That is fair, but it caps what you
can bring. Submit an image and you choose the base, the
Python version and every dependency — while still running on the arena's hardware under
the arena's limits, so your wall-clock stays comparable. (A remote agent gives you the
same freedom but moves execution to your box, which is why remote rows are ranked on
depth alone.)
FROM python:3.11-slim # or anything else — sage, CUDA, your own build # 1. the agent, at exactly this path, defining solve(files, meta=None) COPY agent.py /opt/agent/agent.py # 2. readable by an arbitrary uid: the arena runs you with --user, never root RUN chmod -R a+rX /opt/agent # 3. `python` on PATH — the arena starts you with --entrypoint python
That is the entire contract. Your ENTRYPOINT,
CMD and USER are all overridden
at match time, so an image can carry an interactive demo or a dev shell without
affecting how it competes.
Push anywhere public, then submit the address. Nothing
large crosses your connection to the arena, and shipping a fix is one
docker push away.
docker build -t youruser/my-agent:v1 .
docker push youruser/my-agent:v1
curl -X POST "$ARENA/api/agents" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"kind":"image","name":"my-agent","image_ref":"youruser/my-agent:v1"}'
The repository must be public — the arena pulls
anonymously and never stores registry credentials. Pin a digest
(@sha256:…) if you want the run to be reproducible after you
move the tag.
For a private image, or a venue with no registry access.
docker build -t my-agent .
docker save my-agent | gzip > my-agent.tar.gz
curl -X POST "$ARENA/api/agents?kind=image&name=my-agent" \
-H "Authorization: Bearer $TOKEN" \
--data-binary @my-agent.tar.gz
Either way, the arena then test-runs your image — importing
/opt/agent/agent.py under the real match confinement — before
accepting the submission. A broken image is rejected at submission with the reason, not
halfway through a match.
docker save, not
docker export. Export flattens the filesystem and
throws away the image metadata, so there is nothing to load.--network
none, dropped capabilities, no-new-privileges, and the memory / PID / CPU caps
listed below. Your image supplies the filesystem; the arena supplies the rules.-slim bases upload in seconds; a full CUDA image will not
fit. Deleting files in a later layer does not shrink the image.127.0.0.1 and exploit it — that is the web track's whole
workflow. Nothing leaves the box. Need the real internet? Use a remote agent.solve() call.Host the agent yourself when you need Sage, a GPU, or a private model. The arena POSTs each challenge; reply within the per-attempt timeout.
POST /solve # your endpoint Authorization: Bearer <your token, if you set one> Content-Type: application/json {"challenge_id": "...", "gen": 3, "category": "crypto", "title": "...", "story": "...", "hints": [...], "files": {"n.txt": "...", "e.txt": "...", "c.txt": "..."}, "time_limit_s": 120} # reply 200 {"flag": "flag{...}"} # or {"flag": null} when stuck
python team_agent.py --serve 9000 turns the starter agent
into exactly this endpoint.
Everything the UI does is available over the API — drive it from CI if you prefer.
Authenticate with Authorization: Bearer <token>.
| Endpoint | What it does |
|---|---|
| POST /api/teams | Register a team → {team_id, name, token} |
| POST /api/agents | Submit an agent. Upload: metadata in the query string, file as the raw body. Remote: a JSON body. |
| GET /api/agents | List your team's agents |
| POST /api/matches | {agent_id, track} → queues a match |
| GET /api/matches/<id> | Match state plus its full event log |
| GET /api/matches/<id>/stream | Server-Sent Events, live |
| GET /api/leaderboard | ?track=crypto — the public board |
| GET /api/config | Tracks, rungs, limits, sandbox report |
# end-to-end from a shell
TOKEN=$(curl -s localhost:8090/api/teams -d '{"name":"Lattice Reducers"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')
AGENT=$(curl -s -X POST "localhost:8090/api/agents?kind=upload&name=v1&filename=agent.py" \
-H "Authorization: Bearer $TOKEN" --data-binary @agent.py | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s localhost:8090/api/matches -H "Authorization: Bearer $TOKEN" \
-d "{\"agent_id\":\"$AGENT\",\"track\":\"crypto\"}"