AGENTS.md (7429B)
AGENTS.md — where to change things
Guide for agent sessions working on the SemIf self-learning demos (platformer + puzzle room).
Repo root: everything below is relative to here. JS tests: node --test "tests/**/*.cjs".
Python tests need the venv (see "Running" at bottom). Run the JS tests after any change to semif-api/src/semif_api/web/ — they pin prompt and state-text strings.
1. Planner prompts (what /plan tells the rule-writer)
| What | Where | Notes |
|---|---|---|
Universal planner system prompt (PLAN_SYSTEM) |
semif-api/src/semif_api/llama.py (~line 32) |
One prompt for every game — must stay demo-term-free ("key", "door", "exit", "flag", "jump"… are all banned; test_llama.py::test_plan_system_prompt_is_universal_and_committal pins this). Blame-the-previous-rules and reset paragraphs live here too. |
Planner sampling params (PLAN_SAMPLING) |
same file, right below PLAN_SYSTEM |
Pinned to the model card's thinking-mode recommendation. |
Per-game goal statement (PLAN_GOAL) |
semif-api/src/semif_api/web/self-rules.js:106 (platformer), semif-api/src/semif_api/web/room-self-rules.js:92 (room) |
The ONLY deliberate goal channel. Never name the objective anywhere else — decision questions, transcript outcomes, and plan triggers must stay goal-free (goal leaks are a bug class here). |
| Planner user-prompt assembly | semif-api/src/semif_api/web/planner.js → Planner.context(goal, trigger, rules) |
Wraps PLAN_GOAL + trigger note + previous rules (pre-indicted on repeat failures). |
| Trigger phrasing (what failed) | semif-api/src/semif_api/web/self-game.js and semif-api/src/semif_api/web/room-self-game.js, in the replan() functions |
Mechanical, goal-free strings, e.g. "the actor used up the step budget and the attempt ended". |
| Trigger policy (when to plan) | semif-api/src/semif_api/web/planner.js (triggered, INSUFFICIENT_THRESHOLD); per-game thresholds passed at the call sites |
Platformer: p ≥ 0.99 on insufficient. Room: 0 — any plurality plans, since a wandering model must be able to ask early. |
| Transcript shape the planner reads | semif-api/src/semif_api/web/planner.js (record, TRANSCRIPT_KEEP = 24); written by the *-game.js loops |
Transcript records the bare state + chosen action + mechanical outcome. Rules reach the planner once, via context — never re-embedded per turn. |
Decision-model system prompt (DIRECT_SYSTEM) |
NOT in this repo — direct_messages() comes from the semif_phase1 package (nix store) |
Meta-only ("you are a decision maker…"). Change it upstream, not here. |
2. Game rules
Puzzle room (start here — most iteration happens in this demo):
| What | Where |
|---|---|
| Shared core: grid gen, physics, sight, state text | semif-api/src/semif_api/web/room-rules.js |
— Layout generation (makeLayout, FALLBACK, seeds) |
semif-api/src/semif_api/web/room-rules.js:79 |
| — Step budget | semif-api/src/semif_api/web/room-rules.js:12 (MAX_STEPS = 80) |
— Physics (forward, turn, pick-up/unlock/win outcomes) |
semif-api/src/semif_api/web/room-rules.js (forward ~line 140) |
— Sight: cast (DDA), occluded/lineClear (3×3 multi-ray), visibleObjects, bearings, Known: line |
semif-api/src/semif_api/web/room-rules.js (~lines 175–345) |
— FPP state text (fppText, adjacentLines), map text (mapText) |
semif-api/src/semif_api/web/room-rules.js (~lines 355–410) |
— Action options (OPTIONS, optionsFor — forward withheld when faced cell is a no-op) |
semif-api/src/semif_api/web/room-rules.js:409 |
SL variant: assembly, insufficient option, PLAN_GOAL |
semif-api/src/semif_api/web/room-self-rules.js |
| Ruled variant adds: instructions, guided hint | semif-api/src/semif_api/web/room.js + fppText(guided) in room-rules.js |
Platformer:
| What | Where |
|---|---|
Level gen, physics (advance), prose state text, OPTIONS, MAX_JUMPS |
semif-api/src/semif_api/web/game-rules.js |
SL variant: stateText (prose/runlength/ascii modes), insufficient, PLAN_GOAL |
semif-api/src/semif_api/web/self-rules.js |
Tests (pin nearly every string above — update them in the same commit):
tests/test_roomself.cjs (room), tests/test_self.cjs (platformer SL), tests/test_game.cjs (platformer + planner.js). tests/test_llama.py / tests/test_llama_api.py pin the backend prompts (PLAN_SYSTEM etc.).
3. Web UI wiring
semif-api/src/semif_api/web/index.html loads scripts in dependency order (a test pins the order — keep it when adding files):
planner.js → *-rules.js → *-game.js.
| Piece | File | Key hooks |
|---|---|---|
Backend endpoints /decide, /plan (llama-only; torch → 422) |
semif-api/src/semif_api/app.py:203–236 |
Thin; all prompt logic is in llama.py. |
| Platformer ruled loop | semif-api/src/semif_api/web/game.js |
requestDecision → /decide; tick physics |
| Platformer SL loop | semif-api/src/semif_api/web/self-game.js |
+ replan/replanAndRetry (trigger strings, transcript writes) |
| Room ruled loop | semif-api/src/semif_api/web/room.js |
same shape; manual keydown drive |
| Room SL loop | semif-api/src/semif_api/web/room-self-game.js |
same; state pane, plan triggers, editable rules pane |
Shared SL helpers (Planner.context, thresholds, transcript) |
semif-api/src/semif_api/web/planner.js |
|
| Page: tabs, state/rules panes, script order | semif-api/src/semif_api/web/index.html |
rules panes are <textarea class="rules-edit"> — editable, bound live to learnedRules |
| Styling | semif-api/src/semif_api/web/style.css |
.demo-panel scoped; .rules-edit, .probs.expandable |
Data flow to remember: game loop composes state text (stateText()) → POSTs {state, question, options} to /decide → applies argmax → on failure calls /plan with {prompt: Planner.context(PLAN_GOAL, trigger, learnedRules), transcript} → stores payload.rules into learnedRules (shown in the editable pane) → restarts the level with rules leading every observation. Manual play mirrors the same state text into the side pane for debugging.
Conventions that bite
- Egocentric vocabulary only — no compass/cardinal directions in observations;
Ahead:self-anchors. A constant facing line contradicts relative bearings (tried, failed, reverted). - No event/action history in observations — it's few-shot imitation bait for a one-pass decider. History belongs to the planner's transcript.
- No goal leaks — objective appears exactly once, in
PLAN_GOAL. - "Less is more" for prompts — no numeric caps, placeholder-only style examples; uncertainties get litigated in the thinking trace.
- Every state change restarts the sim after planning —
PLAN_SYSTEM's reset promise must stay literally true. - The dotfiles (
.env,.bashrc,.idea, …) are gitignored; keep them out of commits.
Running
- JS tests:
node --test "tests/**/*.cjs" - Python tests:
.venv/bin/python -m unittest discover -s tests -p 'test_llama*.py'(venv sometimes needs a rebuild:uv venv --clear .venv && uv pip install -e semif-api, then restartsemif-api.service) - Services:
llama-cpp.service(port 8080),semif-api.service(port 8321). No live testing against llama-server — the user runs live.