--- 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 , --brain local --repeats 5 --arc python bakeoff.py --models --brain local --temps 0.0,0.3,0.5,0.7,1.0 --repeats 5 python bakeoff.py --models --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 `