Spaces:
Paused
Paused
| 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`](SPEC.md) for the full contract. | |
| ## Links | |
| - **Demo video + post (X):** https://x.com/MrChonkyboi/status/2066654526963081589 | |
| ## How to play | |
| 1. Read the current challenge at the top. | |
| 2. Type instructions to the alien in plain language. | |
| 3. 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. | |
| 4. 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 onto `cuda` at | |
| module level; inference runs inside `@spaces.GPU`. | |
| - `modal` β optional dev/serving endpoint. Never the submission path; `requests` | |
| is 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) | |
| ```bash | |
| # 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). | |
| ```bash | |
| 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=local` as a Space | |
| variable. Put the HF token in **Space secrets** (never in code). | |
| - `sdk_version` is pinned to Gradio `6.16.0`; confirm it matches the current | |
| ZeroGPU template when you create the Space (HF will error clearly if it's off). | |
| In Gradio 6 `css`/`theme` moved off `Blocks()`, so `app.py` also 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. | |