# How Teach / discovery mode works Teach mode is where the Oracle **learns**. When it meets something not in its database, the player tells it what they were thinking of, and the model reasons out that item's attributes and saves them — so next time, the Oracle just knows. All of this lives in `discovery.py`, exposed through `app.py`'s `@app.api("learn")` endpoint and the Teach UI in `index.html`. ## When it triggers 1. **The Oracle runs out of candidates** — `engine.filter_candidates()` returns 0, so `next_turn` returns `action: "giveup"` and the UI shows the Teach panel. 2. **The Oracle guesses wrong** — the player clicks "No", which opens Teach. 3. **The + Teach button** — the player adds something any time, no game needed (a category picker appears so they can choose animal / fruit / vegetable). ## The learn flow ```mermaid flowchart TD A[player names the item] --> B[_canonical: resolve aliases] B --> C{already in DB?} C -->|yes| D[find_contradictions → explain wrong answers] C -->|no| E[gather attributes from 3 sources] E --> F[add_item → write JSON, persist, refresh cache] F --> G[learned!] ``` `discovery.learn_item(category, name, history)` is the entry point. ### 1. Resolve the name `_canonical()` maps aliases and spelling variants to the canonical DB name — `chilli`/`chili` → `chili pepper`, `aubergine` → `eggplant`, `courgette` → `zucchini`, etc. This stops the app from creating junk duplicates of things it already knows. ### 2. If it's already known → explain the mistake If the item exists, the Oracle didn't lack the data — the player's answers steered it wrong. `find_contradictions()` compares each in-game answer to the item's **true** attributes and reports the genuine mismatches with a plain-English reason from `ATTR_REASON`: > ✗ You answered **No** to "Is it starchy?", but a potato is starchy — it should > have been **Yes**. Only real contradictions are flagged; correct answers and "I'm not sure" are ignored. ### 3. If it's new → fill every attribute from three sources A learned item must be **complete** (define every attribute) or it can't be told apart from others. Attributes are gathered in order of trust: 1. **The player's own in-game answers** — `attributes_from_history()`. They were thinking of it, so their yes/no answers are ground truth for their item. 2. **The model** — `derive_attributes()` asks the Llama model (via `llm.py`) to fill the category's attribute table as true/false/unknown, optionally grounded by a short **Wikipedia** summary (`fetch_web_context()`, controlled by `ORACLE_DISCOVERY_WEB`). The model's answers overlay the player's. 3. **The existing database** — `complete_attributes()` fills anything still unknown by majority vote among the most similar known items (nearest-neighbour). Fully offline and deterministic. The result is always a full attribute set, even with no model and no network. ### 4. Save it `add_item()` appends the record to the category's JSON file, persists it (in `ORACLE_DATA_DIR` / the mounted bucket, so it survives restarts), and clears the engine cache so the next game sees the new item immediately — no restart. ## Why this design - **The model does real reasoning here** — turning "rambutan" + a Wikipedia blurb into a structured attribute profile is genuine work a lookup table can't do. This is the part of the app where the tiny model is most load-bearing. - **It never corrupts the truth** — known items are explained, not overwritten; new items are completed from multiple sources and then validated. ## Validate after teaching Run the database checker any time you've added items (it's also a great guard for hand-edits): ```bash python check_db.py # COMPLETE · UNIQUE · GUESSABLE · BALANCED ``` It confirms every item defines every attribute, no two items are identical, and a simulated game can still guess each one. ## Related env vars | Var | Effect | |-----|--------| | `ORACLE_QUESTION_LLM=1` | use the model to derive attributes for new items | | `ORACLE_DISCOVERY_WEB=1` | allow Wikipedia grounding while learning | | `ORACLE_DATA_DIR=/data` | persist learned items to a mounted Storage Bucket |