Build an end-to-end {{OBJECT}}-analytics stack on Intel hardware in
./{{STACK_DIR}}/ with Docker Compose. Vertical-agnostic: the same
seven-container topology (Nginx, DLSPS, Mosquitto, Node-RED, Grafana, MediaMTX,
Coturn) serves any DL Streamer / OpenVINO CV pipeline — only the model, class
filter, alert rule, dashboard, and topic names differ. Follows the
open-edge-platform
Metro Vision AI App Recipe
MediaMTX + Coturn + WebRTC path, streamlined (no Prometheus/OTel).
SceneScape is off by default — opt-in multi-camera spatial analysis
(references/SCENESCAPE.md). Detection metadata
flows DLSPS→MQTT→Node-RED→Grafana; video is decoupled (DLSPS overlays via
gvawatermark, pushes each source to MediaMTX over WHIP, ENABLE_WEBRTC=true,
per-source peer-id) — see architecture below.
When to use this skill
Use when building or deploying an object-detection, classification, object-counting, or zone/line-crossing alerting pipeline for any vertical — smart city/ITS, retail, industrial, logistics, healthcare, or a custom OpenVINO/ONNX model. Optionally adds a SceneScape multi-camera spatial-analysis path, or a lightweight demo/PoC single app (one DL Streamer pipeline or a minimal OpenVINO inference script) when no full stack is needed.
Not for: non-Intel or cloud-only deployments, Prometheus/OpenTelemetry metrics stacks, or training/exporting models.
Supported verticals & use-cases
| Vertical | Example use-cases (each = one invoking prompt) |
|---|---|
| Smart city / ITS | person/vehicle detection, ANPR, smart-parking, wrong-way |
| Retail | customer counting, queue-length, shelf out-of-stock, dwell-time |
| Industrial / logistics | surface-defect, PPE compliance, zone intrusion, pallet/forklift tracking |
| Healthcare / facilities | fall detection, hand-hygiene, occupancy, perimeter intrusion |
| Custom | any OpenVINO IR / ONNX detector + optional classifier |
The invoking prompt maps its vertical to concrete {{OBJECT}},
{{PIPELINE_NAME}}, {{DEFAULT_MODEL}}, {{DEFAULT_RULE}}, and
{{DASHBOARD_SLUG}} — nothing else changes.
How to use this skill
- Read this file end-to-end.
- Ask Question 0 (mode) first. If Demo/PoC, branch to
Demo/PoC mode + load
references/DEMO_POC.md— skip questions 1–7. Else (Full-stack production, default) continue. - Ask the 7 questions in ONE batched message (defaults in brackets); accept
go/defaults/empty. Question 7 selects the SceneScape path. - Run parameter validation (below); refuse to proceed on any failure.
- Load reference file(s) on demand per component — not all up front. Load
references/SCENESCAPE.mdonly when{{SCENESCAPE}}=yes. - Verify against completion criteria before declaring success; record
throughput/latency against the
benchmark.mdbaselines.
Reference files (load on demand)
| File | Load when authoring |
|---|---|
references/PIPELINE.md |
DLSPS config.json, GPU/NPU variants, REST launcher, watchdog |
references/PROXY_UI.md |
nginx.conf proxy (WHEP/WHIP + WebRTC-TCP), Grafana iframe panels, dashboard provisioning, Mosquitto |
references/NODE_RED.md |
flows.json, MQTT wildcard, gva_meta probe, alert flow |
references/INSTALL.md |
file layout, .env, validate_env.sh + validation rules, install.sh, docker-compose.yml (MediaMTX + Coturn) volumes |
references/TESTS.md |
conftest.py, test_webrtc_stream.py, assertion contracts for other tests |
references/SCENESCAPE.md |
Only when {{SCENESCAPE}}=yes — opt-in multi-camera scene-fusion path, delegating to the scenescape-setup skill |
references/DEMO_POC.md |
Only when {{MODE}}=demo — lightweight single-app path (DL Streamer or OpenVINO); no full stack |
Parameters (from invoking prompt)
| Param | Purpose |
|---|---|
{{MODE}} |
demo | production (default production). demo = lightweight single-app path (references/DEMO_POC.md); params below apply to production only |
{{OBJECT}} |
class label in dashboard/alerts (e.g. person, vehicle, hardhat, defect, fall) — any MQTT/Grafana-safe string |
{{STACK_DIR}} |
e.g. person-detect-stack, ppe-compliance-stack, anpr-stack |
{{DEFAULT_MODEL}}, {{OTHER_MODELS}} |
allowed model options |
{{PIPELINE_NAME}} |
canonical DLSPS pipeline name (e.g. yolov11s); variants <name>/<name>_gpu/<name>_npu; topic {{DETECTIONS_TOPIC_PREFIX}}_X/<name> |
{{CLASSIFIER}} |
secondary model or none; if set, also {{CLASSIFIER_URL}} + {{CLASSIFIER_XML}} |
{{CLASS_FILTER_IDS}} |
JSON array of class IDs to keep ([]=all). Filtered in Node-RED |
{{DEFAULT_RULE}} |
e.g. count>2 in 10s |
{{RULE_SCOPE}} |
per-source | aggregate (default per-source) |
{{ALERT_TOPIC}} |
e.g. alerts/{{OBJECT}} |
{{DETECTIONS_TOPIC_PREFIX}} |
e.g. object_detection (per-source _1, _2, …) |
{{COUNT_TOPIC}} |
e.g. stats/{{OBJECT}}_count |
{{LABEL_RULE_NOTE}} |
model-specific classification note for Node-RED |
{{DASHBOARD_SLUG}} |
e.g. smart-parking |
{{NUM_SOURCES}} |
default 4 |
{{SCENESCAPE}} |
yes | no (default no). yes = opt-in multi-camera spatial-analysis path (references/SCENESCAPE.md) |
{{SCENE_NAME}} |
(SceneScape only) human-readable scene name, e.g. intersection-1 |
{{CAMERA_IDS}} |
(SceneScape only) unique IDs (no /), one per input stream, in input order |
{{TURN_USER}}, {{TURN_PASS}} |
Coturn / MediaMTX ICE credentials (default turnuser / a generated secret) |
Questions (single batched prompt)
Question 0 — Mode [production]: demo (lightweight single-app PoC) or
production (full end-to-end stack). If demo, STOP here and follow
Demo/PoC mode; do not ask questions 1–7. Questions 1–7
apply only to production.
- Model [
{{DEFAULT_MODEL}}] (also:{{OTHER_MODELS}}) - Classifier [
{{CLASSIFIER}}] (ornone) - Device [CPU] (GPU, NPU, AUTO)
- Inputs [{{NUM_SOURCES}}× sample-video] (or RTSP URLs /
/dev/videoN/ local paths) - Node-RED rule [
{{DEFAULT_RULE}},{{RULE_SCOPE}}] - Alert channel [MQTT
{{ALERT_TOPIC}}] - SceneScape multi-camera spatial analysis? [
{{SCENESCAPE}}, defaultno] (ifyes, also collect{{SCENE_NAME}}+ one unique{{CAMERA_IDS}}per input stream →references/SCENESCAPE.md)
Parameter validation (enforce BEFORE install.sh runs)
Ship validate_env.sh and call it as step 0 of install.sh; reject on any
failure. Both the script body and the full validation rules table
(MODE, HOST_IP, NUM_SOURCES, DEVICE, PIPELINE_NAME, topics, TURN
creds, inputs, SceneScape params, …) are in
references/INSTALL.md.
Reference architecture
Single Compose network app_network. Nginx publishes 80/443; Coturn also
publishes 3478/udp (WebRTC TURN). Nginx routes: /api/→DLSPS REST,
/grafana/→Grafana, /nodered/→Node-RED, /mediamtx/<pid>/→WHEP iframe,
/<pid>/whep|whip→WHEP/WHIP signalling, /webrtc/→MediaMTX local TCP (ICE 8189).
Data: DLSPS→MQTT→Mosquitto→Node-RED→Grafana (mqtt datasource); DLSPS→WHIP→MediaMTX
(peer-id {{DETECTIONS_TOPIC_PREFIX}}_N, ICE/TURN via Coturn); Grafana embeds
<iframe src="/mediamtx/{{DETECTIONS_TOPIC_PREFIX}}_N/">.
Demo/PoC mode
When Question 0 selects demo, do not build the full stack (no Compose
topology, no MediaMTX/Coturn/Node-RED/Grafana/Nginx, no SceneScape). Produce a
single lightweight app proving a model runs on Intel hardware. Two sub-paths
(ask which):
- DL Streamer app — a simple DL Streamer / GStreamer pipeline; delegate to
the
dlstreamer-coding-agentskill. - OpenVINO app — a minimal Python script (load →
compile_model→ infer → post-process); no dedicated skill, follow the OpenVINO 2026 docs.
Full guidance and lightweight completion criteria are in
references/DEMO_POC.md — load only on this branch;
production criteria (1–11) do not apply in demo mode.
SceneScape spatial-analysis path (optional, {{SCENESCAPE}}=yes)
When Question 7 selects SceneScape, branch: keep the DLSPS detection
pipeline but replace the MediaMTX/WebRTC + Node-RED + Grafana-MQTT tail with an
Intel® SceneScape multi-camera scene-fusion stack. Do not re-implement by
hand — delegate to the scenescape-setup skill, passing {{SCENE_NAME}} and
{{CAMERA_IDS}}. Architecture, images, validation, and completion criteria are
in references/SCENESCAPE.md; load only on this
branch. The default ({{SCENESCAPE}}=no) path is unchanged.
Images — pin to the latest available tag (never :latest)
Resolve each image to the newest published stable tag on Docker Hub (query
the repo's tags API with ordering=last_updated), then pin that concrete
version — never floating :latest, and ignore *-weekly pre-releases.
Grafana is the one exception: keep it pinned to 11.5.4 (the MQTT
datasource plugin only works on that tag).
intel/dlstreamer-pipeline-server:2026.1.0-ubuntu24eclipse-mosquitto:2.1.2-alpinenodered/node-red:5.0.4nginx:1.31.3-alpinebluenviron/mediamtx:1.20.0(WebRTC: WHIP in, WHEP out)coturn/coturn:4.17.0(ICE/TURN)grafana/grafana:11.5.4(pinned — do not upgrade) withGF_INSTALL_PLUGINS="grafana-mqtt-datasource 1.3.3,yesoreyeram-infinity-datasource 3.11.1"— a bad plugin version (plugin.versionNotFound) kills the container → Nginx 502intel/dlstreamer:2026.1.0-ubuntu24(one-shot ininstall.sh: model download + INT8 quantize + TLS cert)
Layout (flat)
Generate a flat {{STACK_DIR}}/ — top-level README.md, docker-compose.yml,
.env, validate_env.sh, install.sh, the sample_*.sh/update_dashboard.sh
scripts, a src/ tree (dlstreamer-pipeline-server/, mosquitto/,
node-red/, grafana/, nginx/), and tests/. The full annotated file
tree is in references/INSTALL.md.
README.md (required content)
The generated README.md MUST document, at minimum:
- Architecture — data path
DLSPS → MQTT (Mosquitto) → Node-RED → Grafanaplus decoupled videoDLSPS WHIP → MediaMTX → browser WHEP (Grafana <iframe>), Coturn for ICE/TURN, all behind the Nginx TLS proxy. Include the reference ASCII diagram and the seven containers (nginx, dlstreamer-pipeline-server, broker, node-red, grafana, mediamtx, coturn). - Quick start —
./install.sh→docker compose up -d→./sample_start.sh <cpu|gpu|npu>, plus stop/status scripts. - Access URLs and credentials — dashboard
https://<HOST_IP>/grafana/, default Grafana loginadmin/admin(state explicitly; prompts to change on first login), DLSPS REST basehttps://<HOST_IP>/api/pipelines, WHEP playerhttps://<HOST_IP>/mediamtx/{{DETECTIONS_TOPIC_PREFIX}}_N/. - Configuration — key
.envvalues (HOST_IP, GIDs, TURN creds) and how to swap the video source for RTSP.
Template variable substitution
Every {{VAR}} in code blocks MUST be substituted with its concrete value
BEFORE writing the file — a literal {{...}} left in nginx.conf,
config.json, flows.json, the dashboard JSON, or a test file is a syntax
error.
Execution guardrails
- Hard timeouts: model dl+INT8 300 s; video dl 120 s/file;
compose pull300 s;compose up -d120 s + 180 s healthy; each pytest 60 s. - Max 2 retries per step, then STOP and print last 30 log lines from the failing container. Never loop.
- Before
compose up:ss -ltnmust show:80and:443free, andss -lunmust show:3478free (Coturn TURN). - Bypass host proxy for all localhost/LAN curl — corporate
http_proxy/https_proxyrouteshttps://localhost/...through an unreachable proxy (→Could not resolve host/ 502). Every curl insample_*.shMUST use--noproxy '*'and--cacert src/nginx/ssl/server.crt(self-signed cert); tests setNO_PROXY=*inconftest.py. - Test WebRTC signalling:
curl --cacert src/nginx/ssl/server.crt --noproxy '*' -sf -o /dev/null -w '%{http_code}' https://<HOST>/mediamtx/{{DETECTIONS_TOPIC_PREFIX}}_1/(expect200; stream exists only aftersample_start.sh). - Test MQTT:
docker run --rm --network <project>_app_network eclipse-mosquitto:2.1.2-alpine mosquitto_sub -h broker -t '#' -v. - pytest venv at
./.venvinside stack dir (python -m venv .venv) — system pip is PEP-668 blocked;/tmpmay benoexec.
Optional external skills
If available in the session, invoke; otherwise write files from the reference templates.
dlstreamer-coding-agent— pipeline JSON authoring (and the demo/PoC DL Streamer app when{{MODE}}=demo)dlsps-user(oep/skills) — DLSPS deploy/config/REST; default{{MODE}}=productionpath (references/PIPELINE.md)model-download(oep/edge-ai-libraries) — OMZ model IRscenescape-setup(oep/skills) — only when{{SCENESCAPE}}=yes(references/SCENESCAPE.md)
Reference implementation
The upstream
smart-parking/src/
recipe uses the same MediaMTX + Coturn + WebRTC path — consult it for the
config.json, mosquitto.conf, nginx.conf, datasources.yml,
dashboards.yml, flows.json shapes; drop prometheus/otel-collector/metrics-manager.
Completion criteria (all must pass)
When
{{SCENESCAPE}}=yes, criteria 3–11 are superseded by the SceneScape criteria inreferences/SCENESCAPE.md; 1–2 still apply.
./install.shsucceeds:.envpopulated; INT8 model + optional classifier IR undersrc/dlstreamer-pipeline-server/models/…; videos downloaded; TLS cert with SAN generated../validate_env.sh cpuexits 0 with a valid.env;HOST_IP=127.0.0.1 ./validate_env.sh cpuexits non-zero.docker compose up -d→ all containersrunning/healthy(includingmediamtx-serverandcoturn).curl --cacert src/nginx/ssl/server.crt https://localhost/api/pipelines/statusreturns 3 variants../sample_start.sh <cpu|gpu|npu>launches{{NUM_SOURCES}}pipelines; noneQUEUED, allRUNNING.- Detections arrive on
{{DETECTIONS_TOPIC_PREFIX}}_1..{{NUM_SOURCES}}/{{PIPELINE_NAME}}(or_gpu/_npu) within 30 s. curl --cacert src/nginx/ssl/server.crt https://localhost/mediamtx/{{DETECTIONS_TOPIC_PREFIX}}_1/returns 200 (WHEP player HTML) once pipelines run; MediaMTX logs show the WHIP publisher connected for eachpeer-id.- Node-RED publishes JSON
{{ALERT_TOPIC}}and scalar{{COUNT_TOPIC}}/stats/alert_active/stats/alert_totalper{{RULE_SCOPE}}.mosquitto_sub -t '{{COUNT_TOPIC}}/#' -C 1MUST parse asint()— JSON here silently breaks Grafana plotting. - Grafana at
https://localhost/grafana(admin/admin) shows live {{OBJECT}} counts + alert data and{{NUM_SOURCES}}<iframe>WebRTC panels (needsGF_SECURITY_ALLOW_EMBEDDING=true). The MQTT datasource health endpoint (/grafana/api/datasources/uid/mqtt_ds/health) MUST return"MQTT Connected"— broker address goes injsonData.uri(tcp://broker:1883), NOTurl:/jsonData.host/port(→ blank panels). Root redirect-loop → drop the trailing slash from/grafana/proxy_pass. pytest -q tests/passes;pytest --collect-only -q tests/ | tail -1reports ≥ 9 tests collected (no empty stubs).- Watchdog continuity (file:// sources): after
video-length + 30 s,/api/pipelines/statusshows{{NUM_SOURCES}}RUNNING(COMPLETEDhistory is fine), WebRTC re-establishes, MQTT still flowing.{{NUM_SOURCES}}RUNNINGmeans the watchdog dedup guard is missing.