Ran behavision-setup in a fresh Linux container: Python 3.12, nothing else, the release contents mounted read-only the way Program Files or a shared drive would be. It failed, and then it failed differently, and both failures would have been the client's first experience. 1. `pip install <folder>` makes setuptools write behavision.egg-info INTO the folder. The folder is read-only wherever a release is sensibly unzipped, so: "could not create 'behavision.egg-info': Read-only file system". The release now ships a wheel - pure Python, buildable anywhere, nothing to build on the shop PC, and pip never touches the unzipped folder. Source stays as a fallback and is copied somewhere writable first. 2. The engine's paths.py knows two worlds - frozen (ProgramData) and a checkout (the repo root) - and a pip-installed engine is neither. It resolved its state root to site-packages: database there, camera list there, and its generated API credential in a folder the app never reads, while the app looked in ProgramData. Every call would be 401 on a stock install, with nothing in either log saying why. The same disease as the Mac checkout two days ago, now in production shape. engine.ChildEnv is the one place the engine's environment is built, used by the desktop app, the headless agent and the installer's own smoke test. It passes BEHAVISION_DATA_DIR = this process's state root, which paths.py honours ahead of every other rule, so the two halves agree by construction however the engine was installed. It also seeds config/default.yaml into the state root: a package in site-packages has no config beside it to seed from. Re-run on the same clean container: seven steps, all pass, models downloaded, engine started and answered, and its data/ landed beside agent.json - not in site-packages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
Behavision desktop
The store-facing app: a tray icon, a window, and the supervisor for the Python recognition engine.
Why one process, not three
The tray, the window and the supervisor all need the same state, and a user who quits the tray expects recognition to stop. Splitting them means two things can disagree about whether the engine is running.
It is deliberately not a Windows service. A service runs in session 0 and cannot draw a tray icon — that is Windows session isolation, not a library limitation. Spawning a child process also needs no elevation, while controlling a service does, so this design never triggers UAC at runtime.
Layout
main.go wails.Run, window options
app.go the methods bound to the frontend
tray.go fyne.io/systray — wails v2 has no tray of its own
icons.go tray icons generated at run time, not embedded
internal/local client for the engine on 127.0.0.1:8010
internal/cloud client for https://mcp.loyaly.ai
frontend/ React + Vite
The supervisor, durable spool, broker client and path resolution come from
../agent/pkg/* — the same tested code the headless agent runs, imported
rather than copied.
Build
cd frontend && npm install && npm run build # then, from this directory:
wails build -platform windows/amd64
wails build needs the Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@v2.9.2
Without it, go build still type-checks everything provided
frontend/dist exists — the embed directive requires it.
What the frontend talks to
Nothing is imported from generated bindings. src/bridge.js calls
window.go.main.App.* directly, so npm run build works without running
wails generate, and there is one place that handles "the engine is not
running yet" — the state every screen has to survive on a fresh install.