Both found by operating the stack rather than writing it: the local processes were OOM-killed and bringing them back hit two gaps. The headless agent had no way to be claimed at all. Bootstrap lived only in desktop/internal/cloud, so the one configuration the agent binary exists for - a back-office PC with no window - could only be onboarded by hand-editing agent.json, which is the state the desktop's Setup screen was built to end. `behavision-agent claim <code>` closes it; the CLI joins its arguments because the code is printed in groups for reading aloud and an operator pasting it will paste the spaces too. Second: after the site's broker password was re-rolled, mosquitto logged "not authorised" while the agent logged "timed out". Those need opposite actions - re-link this PC, or go and look at the network - and paho's SetConnectRetry collapses them, because it retries internally and the connect token never completes. describeStall asks whether a TCP socket opens at all, and says what is known rather than guessing at a reason the broker never gives. Verified end to end: minted a code from the platform as the owner, claimed with the new command, broker connected, and the shop went to online: true with 1/1 cameras on w600k_r50. Also corrects this machine's memory in CLAUDE.md from 16 GB to 8 GB. It feeds the model-fallback reasoning, and the local gallery already holds 17 embeddings tagged w600k_mbf beside 19 tagged w600k_r50 - the fallback has silently fired before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
Behavision agent (Go)
The half of the edge install that touches the network. The Python engine keeps the cameras and the models; this keeps the tray icon, the UI shell, the MQTT connection and the offline queue.
┌─ agent (Go) ───────────────────┐ ┌─ engine (Python) ────────┐
│ tray icon + WebView2 window │ │ RTSP capture │
│ supervises the engine process │───────▶│ YuNet / ArcFace / FAISS │
│ MQTT publish + offline spool │◀───────│ SQLite (biometric) │
│ S3 handoff, tenant config │ local │ localhost API + events │
└────────────────────────────────┘ HTTP └──────────────────────────┘
Why the split
Go cannot run ONNX, OpenCV or FAISS, so the engine stays Python and ships frozen. Go is here for what it is actually better at: a durable queue that survives a store's internet dropping, a supervised child process, and one language shared with the server so the MQTT contract has a single definition.
Why 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. Since the product is "user starts and stops it from the tray", the agent is a normal user-session process that spawns the engine as a child. That also means it never needs elevation at runtime: starting a child process does not, controlling a service does.
internal/engine is written so a service wrapper can be added later without
touching the supervision logic.
Layout
main.go entry point, mode dispatch
internal/engine start/stop/supervise the Python engine, health polling
internal/spool durable event queue (survives restart and outage)
internal/mqtt broker client, publishes from the spool
internal/config tenant identity, broker settings, credentials
frontend/ React UI served into the WebView
Build
go build ./... # agent alone
wails build # agent + frontend, once the UI is added