Spaces:
Paused
A newer version of the Gradio SDK is available: 6.20.0
title: First Contact
emoji: πΈ
colorFrom: green
colorTo: indigo
sdk: gradio
sdk_version: 6.16.0
app_file: app.py
pinned: false
license: mit
short_description: Teach an alien that knows words but has never lived a life.
tags:
- track:wood
- sponsor:modal
- achievement:offgrid
- achievement:offbrand
First Contact
A small-model game for the Build Small hackathon β An Adventure in Thousand Token Wood track. You teach an alien that knows human words but has never experienced human life. It acts in a tiny sandbox world, accumulates concepts as you teach them, and eventually generalizes a learned concept to a brand-new situation on its own. That "it finally understood me" moment is the payoff.
The model never learns in the weights sense. The alien's growing understanding
lives in a plain-Python concept ledger injected into the prompt every turn.
The model is a stateless function: given (ledger + world + your words) it returns
(one action + an in-character reply + structured notes). The host code applies
the action deterministically, checks the win condition mechanically (never the
model judging "success"), and gates whether a new concept is learned. That loop β
not the model β is the game. See SPEC.md for the full contract.
Links
- Demo video + post (X): https://x.com/MrChonkyboi/status/2066654526963081589
How to play
- Read the current challenge at the top.
- Type instructions to the alien in plain language.
- It can only do one thing from a small, closed action set, but it can say anything β and it tells you honestly what it could not understand.
- When it proposes a new concept, confirm "it learned that" to add it to its ledger. Later challenges test whether it can apply what it learned without being re-taught.
Architecture
gr.State (per session) βββΊ build_prompt βββΊ Brain.respond (@spaces.GPU)
ledger / world / challenge β β strict JSON
β² β βΌ
βββββ learn (gated) ββ check_win ββ apply_action ββ parse + validate
(mechanical) (deterministic) (retry once β safe wait)
| module | role |
|---|---|
game/models.py |
dataclasses: Concept, Obj, Agent, WorldState, Action, Challenge, GameSession |
game/world.py |
apply_action (deterministic), check_win (mechanical), initial world |
game/ledger.py |
seed primitives, gated concept add, times_applied tracking |
game/challenges.py |
the 5-challenge arc + win predicates (2 generalization beats) |
game/prompt.py |
build_prompt(ledger, world, challenge, utterance) |
game/parsing.py |
tolerant JSON extract + validate + Β§4 retry / safe fallback |
game/brain.py |
Brain protocol + StubBrain | LocalBrain | ModalBrain |
game/engine.py |
the turn loop (Gradio-free, fully testable) |
app.py |
Gradio Blocks UI + wiring (the Space entrypoint) |
The model is swappable (protect GPU quota)
Selected via the BRAIN env var:
stub(default locally) β deterministic, zero GPU. The entire loop and the whole challenge arc are playable and testable against it.local(set this on the Space) β a β€32B instruct model loaded ontocudaat module level; inference runs inside@spaces.GPU.modalβ optional dev/serving endpoint. Never the submission path;requestsis imported lazily so Modal is never a hard dependency.
Pick the local model with MODEL_ID (default Qwen/Qwen2.5-14B-Instruct) and the
sampler heat with LOCALBRAIN_TEMPERATURE (default 0.9; 0 = greedy). Both
defaults come from the bake-off below: the JSON envelope held 100% at every
temperature for every candidate, so the model pick was decided by arc completion
plus concept invention (14B was the only one strong at both), and 0.9 buys
near-peak voice at zero measured reliability cost.
Develop / test (no GPU)
# run the full test suite (loop, parsing/fallback, world) against StubBrain
uv run --with pytest pytest -q
# run the app locally on the stub brain
uv run --with gradio python app.py
Model selection (bake-off)
bakeoff.py picks the local model empirically β which β€32B model emits clean,
schema-valid JSON reliably β without burning quota blind. It calls respond()
for raw text and parses once, with no retry (the Β§4 retry path would mask the
failures we're counting).
python bakeoff.py --self-test # prove the scorer (zero GPU)
python bakeoff.py --make-battery battery.jsonl # battery from the arc (zero GPU)
# on the Space (or via a Modal endpoint with --brain modal):
python bakeoff.py --models <id1>,<id2> --brain local --repeats 5 --arc
python bakeoff.py --models <id> --brain local --temps 0.0,0.3,0.5,0.7,1.0 --repeats 5
python bakeoff.py --models <id> --brain local --arc-transcript # eyeball the arc
The --temps sweep is the decision tool: per temperature it reports JSON
reliability and two voice-liveliness proxies and arc-win, so you can see
whether one temperature serves both jobs β or whether you need constrained
decoding to keep the voice warm while guaranteeing the JSON envelope.
Deploy notes
- Set hardware to ZeroGPU in the Space settings and
BRAIN=localas a Space variable. Put the HF token in Space secrets (never in code). sdk_versionis pinned to Gradio6.16.0; confirm it matches the current ZeroGPU template when you create the Space (HF will error clearly if it's off). In Gradio 6css/thememoved offBlocks(), soapp.pyalso injects the CSS via an inline<style>tag β styling holds however Spaces launches the app.@spaces.GPU(duration=20)declares the inference budget β sized from the measured ~5s p90/call (bake-off, Qwen2.5-14B) with ~4x headroom; shorter declared durations get better queue priority. Bump it if you switch models.