AGENTS.md
August 14, 2026 · View on GitHub
Cursor Cloud specific instructions
This repo is the PyDevices examples, documentation, and PyScript gallery. The
shareable product libraries live in sibling pydevices. There is no
build step for examples. .site/pyscript/lib is a symlink to ../../lib, so
editing lib/ updates the PyScript gallery too.
Environment
displaydev, audiodev, optional eventsys, multimer, events, keys,
and portable hardware utilities live in sibling
pydevices, which
also owns TestPyPI/MIP publishing. Non-LVGL examples explicitly import
runtime from app_runtime; LVGL examples import it from display_driver.
Board configs never own a runtime. AutoDisplay is imported from
displaydev.auto only.
- Cursor Cloud (multi-repo workspace): do not use a local
.cursor/environment.jsonin this repo. The canonical cloud environment lives in PyDevices/.github — start Cloud Agents from that repo (orcmods) with the saved Pydevices Cloud Workspace environment. Its install command isbash scripts/cloud-workspace-install.sh(relative to the.githubcheckout), which symlinks/agent/repos/*into~/gh/pydevices/. See AGENTS.md there. - Use the repo-root virtualenv at
.venvfor all Python tooling (.venv/bin/python,.venv/bin/ruff). The systempython3has no project dependencies installed. - Desktop matrices use repo
.venv(cpython-venv) plus interpreters onPATH/bin/(micropython,circuitpython, and when presentmicropython.exe/python.exe)../bin/jupyter.sh,./bin/pyscript.sh, andandroid.sh(pydevices-android-template/scripts/, usually via~/bin) aid Jupyter, PyScript, and Android (adb stage ontoorg.pydevices.launcher; cwd paths like CLI Python — not PyScript gallery). Opt-in matrix:tools/example_test_kit.py --only-runtime android …. - The desktop display backend on CPython on Windows is
PGDisplay(pygame-ce;import pygame). Preferpython.exefor PG work. Do not install pygame-ce into.venv/ systempython3on this laptop — those stay SDL-primary;board_configfalls back toSDLDisplaywhen pygame-ce's publicpygame.WindowAPI is missing.pygame-ceis intentionally not inrequirements-dev.txt.
Tests and lint
- Unit tests (stdlib
unittest, no third-party runner needed):.venv/bin/python -m unittest discover -s tests - Lint/format:
.venv/bin/ruff check lib tests board_configsand.venv/bin/ruff format. Notepyproject.tomlexcludeslib/examples/**(and a few others) from ruff, so example files are not linted/formatted; do not be surprised whenruff format --checkon an example path reports a diff. - The pre-commit hooks (
.pre-commit-config.yaml) areruff-check,ruff-format(python/pyi only), andnbstripoutfor notebooks.ruffdoes not lint*.ipynbunder the hook config, so pre-existing notebook findings fromruff checkon the whole tree can be ignored.
Running examples headlessly (GUI smoke tests)
-
Read
tools/README.md— Example test matrix first — agent runbook for the cross-runtime example test system. Canonical runtime list:tools/example_runtimes.toml; per-example metadata:tools/example_test_manifest.toml. -
Preferred thorough gate: example-by-example with all selected runtimes in parallel (
--jobs 0): 5 desktop for sync, 7 for async; bothPYDEVICES_TIMER_ASYNC=0and=1,--fail-fast, line-buffered live log, fix after a failed example wave then resume — see Preferred method and Windows PE under WSL. Do not forwardSDL_*to*.exe(PE windows should appear; unix stays headless from the shell export). A PEhangwith a live window means quit failed, not that PE failed to start.--curated-onlyis smoke only. -
--only-example/--only-runtimetake space-separated ids on one flag (--only-runtime circuitpython python.exe). Repeating the flag keeps only the last list — see tools/README.md. -
Quick headless CPython smoke:
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ .venv/bin/python tools/example_test_kit.py --no-unit-tests --only-runtime cpython-venv -
PyScript hangs / CDP: prefer Playwright helpers and Headless / CDP troubleshooting before poking the IDE browser.
PYDEVICES_TIMER_ASYNC (agents / matrix)
Host defaults and env semantics:
Runtime — timer_async.
Examples never read this variable — only library board_config and harnesses
that call displaydev.env_set.
Preferred for agents / matrix: pass wrapper --timer-async (the example
kit does this). That uses env_set and works for Windows PE under WSL without
relying on OS environ. Shell export remains a valid host shortcut:
PYDEVICES_TIMER_ASYNC=1 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
.venv/bin/python tools/example_test_kit.py --no-unit-tests --only-runtime cpython-venv
lv_test_timer.py follows runtime.timer_async and does not set env vars.
To force async on desktop for that example (or the LVGL kit), set
PYDEVICES_TIMER_ASYNC=1 on the parent process before launch, or use a kit that
passes --timer-async.
micropython.exe matrix: no threading / _thread. See
tools/README.md — Interpreters and binaries.
Architecture note: timers and refresh
- Non-LVGL examples opt into
eventsys.Runtimeinlib/utils/app_runtime.py. LVGL's frozen/bundleddisplay_driverowns an independent coordinator and does not importeventsys. Both consume neutral board-config callables and usemultimer; display drivers remain policy-free.
MCU: no _thread for network / blocking work
Full guidance:
MicroPython — Background work (_thread).
App pattern: queue work and drain on the main tick — see roku_widgets /
roku_lvgl / roku_graphics (_run_bg + _drain_bg). Do not “fix” this in
eventsys with speculative reentrancy guards — keep the pattern in the app.
LVGL
- Install the CPython LVGL binding from TestPyPI (import name
lvgl):.venv/bin/pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pydevices-lvgl(see https://github.com/PyDevices/lvgl-python). The update script installs it. display_driver(frozen in MP/CP LVGL firmwares; bundled withpydevices-lvgl) owns the LVGLevent_loop(tick viaruntime.on_tick,asynciofrommultimer) and claims runtime display refresh so LVGL presents frames fromtask_handler. SoT: lvgl-bindingspython/display_driver.py— not shipped from this repo.- Test LVGL timers with
tools/lv_timer_test_kit.py(modes:sync,async). Headless:SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy .venv/bin/python tools/lv_timer_test_kit.py --only cpython-venv. - Non-obvious: the sync
multimer.Timerbackend on CPython/Linux delivers via a main-thread signal handler. LVGL is not re-entrant, so the app loop must not touch LVGL/pygame concurrently while that tick runs; LVGL examples use cooperative deadline/time.sleep(sync) orasyncio.sleep(async). The LVGL timer kit covers dedicated click checks — its daemon-thread quit injection is incompatible with the generic example matrix for some ports. multimeris fragile — before editing hardwarelib/multimer/, read multimer concepts and follow the local Cursor rulemultimer-fragile(thinking model, small diffs, revert failures). Do not duplicate that rule text in this repo.
MCU board bring-up (displayif / soft-reset)
When bringing up or debugging a MicroPython board_configs/fbdisplay/* board
that uses displayif (mipidsi, rgbframebuffer, picodvi, …), especially with
LVGL and mpftp soft-reset:
- Soft-reset + re-import is the acceptance test (no hard reset).
- Prefer
mip.installover Wi‑Fi for large Python trees; mpftp for thin files and firmware. - Symptom table, wrap architecture, and bring-up methods live in the sibling
displayif repo:
soft-reset-and-bring-up.md(local:../cmods/displayif/docs/soft-reset-and-bring-up.mdor~/gh/pydevices/cmods/displayif/…). Start at displayifAGENTS.md. - Do not leave flash-backed debug logs on the touch/refresh path (looks like flicker). Fix displayif/bindings root causes rather than board_config workarounds.