wavereader / AGENTS.md
lucashudsn's picture
deploy: auto-deploy main to the Space via Actions; script ships any branch
d4c9a3c
|
Raw History Blame Contribute Delete
6.92 kB
# AGENTS.md
Guidance for AI coding agents working in this repo. The active surface is
`app.py` + `ui/` + `wavereader/` + `data/` (the v2 rebuild). `legacy/`
holds the frozen v1 front end (`main.py` + `app/`) β€” don't extend it; it
survives only as the fallback layer in `ui/_compat.py`.
## What is live now
- `app.py` β€” v2 entrypoint: `build()` + `gr.api` tools +
`demo.launch(css=APP_CSS, mcp_server=True)`. Run `uv run python app.py`.
- `ui/` β€” the single-page front end (no tabs): `app.py` (layout + wiring,
≀3 `gr.State`; Bells Beach auto-selected on load so there are no empty
states), `panels/{lens,story,intel,agent}.py` (map-lens column | forecast
story | intel rail | full-width agent bar), `charts/{strip,score,seafloor,climate,map,_style}.py`,
`contracts.py` (`SELECT_KEYS`/`FETCH_KEYS`/`AGENT_KEYS` + `fill()` β€”
handlers return via `fill(KEYS, …)` so output ordering can't drift),
`_compat.py` (backend access: v2 core β†’ `legacy.app.*` β†’ stubs; also
wetsuit hints, engine chips, surf cams), `theme.py` (`APP_CSS`, IBM Plex
Mono lo-fi identity β€” don't restyle without asking).
- `wavereader/` β€” pure core, typed + tested, no Gradio imports:
`breaks.py` (catalogue, memoized `load_breaks`), `openmeteo.py`
(versioned/atomic disk cache, seaward grid-snap), `scoring.py` (deterministic
0–10 engine β€” the LLM never owns numbers), `seafloor.py` (GEBCO world model),
`climate.py` (ERA5 5-yr profiles + audit; `break_climate` reads the
profile embedded on each break record, per-slug file fallback), `tools.py`
(the typed tool functions shared by agent + MCP), `agent.py`
(smolagents `ToolCallingAgent`,
native tool calls, budget: 6 steps / 2 score_week / 700 tokens),
`narrate.py` + `llm.py` (one InferenceClient factory:
Nemotron 3 Ultra via deepinfra, `WR_*` env overrides).
- `scripts/warm_caches.py` β€” pre-fill forecast + seafloor caches (demo hot
start). `scripts/build_climate.py` β€” climatology builder (re-embeds built
profiles into the catalogue after each run, backup first).
- `scripts/check_coords.py` + `scripts/check_coast.py` β€” break-coordinate
maintenance: `check_coords` validates/fixes state/region/duplicate
placement against OSM; `check_coast` audits GEBCO elevation at every
break point and (`--fix`) snaps non-coastal ones to the nearest 0 m
shoreline crossing (both write a timestamped backup first).
- `data/` β€” `australia-surf-breaks-enriched.json` (238 breaks; each record
embeds its ERA5 profile under `climate` β€” 236 of 238, the two duplicate
Gnaraloo rows have no profile),
`surf-break-schema.json` (contract), `climate/*.json` (per-slug ERA5
profiles β€” `build_climate.py` output and `break_climate`'s fallback;
also `index.json` manifest), `surf-cams.json` (camera links keyed
`"name | state | region"`), `surf-break-example-bells.json` (prompt worked
example).
## Deploying to HF Spaces
- The Space `lucashudsn/wavereader` (public) *is* this repo β€” remote
`space`. Every push to `main` auto-deploys via
`.github/workflows/deploy-space.yml` (needs the GitHub repo secret
`HF_TOKEN`) β€” merging to main ships it; no manual step. For any other
branch, ship manually with `scripts/deploy_space.sh [ref]` (builds a
throwaway commit that prepends `space-config.yaml` to `README.md` and
force-pushes it to `space:main` β€” Spaces need YAML frontmatter, GitHub
doesn't have to see it; the ref defaults to your current branch), then
`git push origin <branch>` to keep GitHub in sync. The Space builds
from the pushed commit; watch *Logs β†’ Build* on the Space page or poll
`HfApi().get_space_runtime("lucashudsn/wavereader")` until `RUNNING`.
- `space-config.yaml` is the Space config. Keep
`sdk_version` in lockstep with the gradio pin in `requirements.txt`
(regenerate that file only via `uv export --no-hashes
--format requirements-txt --no-dev -o requirements.txt` β€” other
invocations can leave stray progress lines that break pip), and
`python_version` in lockstep with `pyproject.toml`'s `requires-python`.
If you change Space settings via the web UI, HF rewrites the Space's
`README.md` β€” mirror anything you care about back into the yaml.
- `HF_TOKEN` is a Space secret (set once via *Settings β†’ Variables and
secrets* or `HfApi().add_space_secret`); `WR_*` overrides can be added
the same way. `.cache/` is ephemeral on the Space and rebuilds lazily β€”
no persistent storage, no warm step.
## Conventions that matter
- One spot drives everything: `selected` (gr.State) is the single source of
truth β€” picking a spot re-aims the map, badges, forecast strip, world
model and climatology together. Keep it that way.
- Staged spin-ups: `ui/panels/story.py::fetch_forecast` is a **generator**
that yields stage 1 (charts, ~ms) then stage 2 (seafloor world model from
a parallel thread). Status lines name the engine + latency. Keep that
pattern for anything slow.
- Charts: theme through `ui/charts/_style.py::style_fig`; weekend shading
via `add_weekend_shading`. The week strip lives in `charts/strip.py`
(score/swell/wind rows); window slicing (weekend/mornings/arvos) re-charts
from state via `filter_hours` β€” no refetch. Seafloor display is bicubically
smoothed (scipy) and its wave animation is period-driven β€” smoothing and
animation are display-only; stats stay on the raw grid.
- MCP surface = exactly `score_week` / `rank_region_week` / `explain_score`
(`gr.api` in `app.py`). All event listeners are registered with
`api_name=False` β€” keep it that way.
- The LLM never owns numbers. Scores/forecasts/analysis stay deterministic;
the model narrates and cites tools.
## Data contracts
- Required break fields: `name`, `state`, `region`, `location`,
`skillLevel`, `breakType`, `peakType`, `idealSwell`, `idealWind`,
`idealTide`. Optional: `description`, `id`, `bestSeason`, `hazards`,
`crowdFactor`. Never invent enum values β€” check
`data/surf-break-schema.json` first.
- Canonical `state`/`region` always come from the input list, never the
model. `location.country` is `"Australia"`.
- New prompts must load schema + example from `data/` (don't paste copies).
## Rules for edits
1. Everything is tracked in this repo (no nested git β€” don't re-init one).
2. Never commit `.env`, `.venv/`, `__pycache__/`, `.DS_Store`, `.cache/`
(all ignored).
3. Never read `.env` or print secrets/tokens. Use `.env.example` for var
names; check presence with `printenv HF_TOKEN | wc -c` or
`[ -n "$HF_TOKEN" ]` style checks that never echo the value.
4. Keep prompt files in `data/`; keep `app.py` free of embedded schema text.
5. Test cheaply: `uv run pytest -q` (offline fixtures) +
`uv run python -c "import app"` for import health. Live LLM calls cost
inference β€” dry-run the agent via the injected fake model
(`tests/test_agent_dryrun.py`) before any live turn.