Build an end-to-end {{OBJECT}}-analytics stack on Intel hardware in
./{{STACK_DIR}}/ with Docker Compose. Vertical-agnostic: the same
seven-container topology serves any DL Streamer / OpenVINO CV pipeline — only the
model, class filter, alert rule, dashboard, and topics 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 analysis). Data-flow and
routing are detailed in Reference architecture below.
When to use this skill
Use when building an object-detection, classification, object-counting, or zone/line-crossing alerting pipeline for any vertical (see table below). Optionally adds a Scenescape multi-camera path, or a lightweight demo/PoC single app 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, 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}}, {{DASHBOARD_SLUG}}
— nothing else changes.
How to use this skill
- Read this file end-to-end.
- Ask Question 0 (packaging) first. If demo/function/port (single-app
path), branch to Demo/PoC mode + load
references/DEMO_POC.md, skip questions 1–7; else (microservice, the default full stack) 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 — not all up front (per the Reference
files table): Scenescape when
{{SCENESCAPE}}=yes; PIPELINE for GPU/NPU, RTSP//dev/video, or classifier; NODE_RED for</<=/>=rules or a non-empty{{CLASS_FILTER_IDS}}. - Verify against completion criteria before declaring success (record
throughput/latency vs
benchmark.md);validate_env.shis step 0 ofinstall.sh.
Reference files (load on demand)
| File | Load when authoring |
|---|---|
references/PIPELINE.md |
DLSPS config.json, GPU/NPU variants (_gpu/_npu + group_add), input sources (file/RTSP/device), gvaclassify wiring, 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 + rules, install.sh, docker-compose.yml volumes |
references/TESTS.md |
conftest.py, test_webrtc_stream.py, assertion contracts |
references/SCENESCAPE.md |
{{SCENESCAPE}}=yes only — multi-camera scene-fusion via scenescape-setup skill |
references/DEMO_POC.md |
single-app packaging only ({{PACKAGING}}∈demo/function/port) — lightweight single-app path (DL Streamer or OpenVINO); no full stack |
Parameters (from invoking prompt)
| Param | Purpose |
|---|---|
{{PACKAGING}} |
demo | function | microservice | port (default microservice). demo/function/port = single-app path (DEMO_POC); microservice = full stack (rows below are microservice-only) |
{{MODE}} |
Back-compat alias derived from {{PACKAGING}}: demo (single-app) | production (= microservice) |
{{HW_TARGET}} |
Intel inference device: CPU|GPU|NPU|AUTO (default CPU; AUTO = pick). Drives sample_start.sh <cpu|gpu|npu> + _gpu/_npu variants; no platform/generation naming |
{{OBJECT}} |
class label in dashboard/alerts (e.g. person, vehicle, 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; when auto, suggest from OpenVINO / Intel / Metro Analytics Catalog HF collections per a performance goal |
{{PIPELINE_NAME}} |
canonical DLSPS pipeline name (e.g. yolov11s); variants <name>/_gpu/_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; parses to {{RULE_OP}}∈>,>=,<,<=, {{RULE_N}}, {{RULE_WINDOW_S}} (see NODE_RED) |
{{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 = multi-camera path (SCENESCAPE) |
{{SCENE_NAME}} |
(Scenescape only) 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 — Packaging [microservice]: demo/function/port
(single-app: PoC, one-shot job, or migrate a pipeline) or microservice (full
stack, default). For demo/function/port, STOP → follow
Demo/PoC mode; skip questions 1–7 (microservice only).
- Model [
{{DEFAULT_MODEL}}] (also:{{OTHER_MODELS}}; orauto— give a performance goal, I'll suggest one from the OpenVINO / Intel / Metro Analytics Catalog HF collections viamodel-download) - Classifier [
{{CLASSIFIER}}] (ornone) - Target hardware [CPU] — Intel
CPU/GPU/NPU/AUTO(I pick). Multi-vendor noted as suggestions only; no platform/generation naming. - Inputs [{{NUM_SOURCES}}× sample-video] (or RTSP URLs /
/dev/videoN/ local paths); setsINPUT_TYPE. RTSP/device are continuous → no sample-video download, no file:// watchdog (see PIPELINE). - 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. The script body and full validation rules table (PACKAGING/MODE,
HOST_IP, NUM_SOURCES, HW_TARGET/DEVICE, PIPELINE_NAME, topics, TURN
creds, inputs, Scenescape params, …) are in
references/INSTALL.md; validate {{PACKAGING}} ∈
demo/function/microservice/port and {{HW_TARGET}} ∈ CPU/GPU/NPU/AUTO.
Reference architecture
Single Compose network app_network. Nginx publishes 80/443; Coturn also
publishes 3478/udp (WebRTC TURN). Nginx reverse-proxies DLSPS REST, Grafana,
Node-RED, and MediaMTX WHEP/WHIP signalling (ICE 8189) — full route map in
references/PROXY_UI.md. Data:
DLSPS→MQTT→Mosquitto→Node-RED→Grafana; 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/function/port (single-app packaging), do not
build the full stack — produce one lightweight app proving a model runs on
Intel HW (DL Streamer app or minimal OpenVINO script). Follow
references/DEMO_POC.md (load only on this branch);
production criteria (1–11) do not apply.
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 scenescape-setup, passing {{SCENE_NAME}} + {{CAMERA_IDS}}.
Architecture, images, validation, criteria in
references/SCENESCAPE.md; load only on this branch.
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), pin it, 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.2.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 kills the container → Nginx 502intel/dlstreamer:2026.2.0-ubuntu24(one-shot ininstall.sh: model dl + INT8 quantize + TLS cert)
Layout (flat)
Generate a flat {{STACK_DIR}}/: README.md, docker-compose.yml, .env,
validate_env.sh, install.sh, sample_*.sh/update_dashboard.sh, a src/
tree (per-service dirs), and tests/. Full annotated tree in
references/INSTALL.md.
README.md (required content)
The generated README.md MUST document, at minimum:
- Architecture — the data + video flow from Reference architecture above; include the ASCII diagram + seven containers.
- Quick start —
./install.sh→docker compose up -d→./sample_start.sh <cpu|gpu|npu>, plus stop/status scripts. - Access URLs + credentials — dashboard
https://<HOST_IP>/grafana/(Grafana loginadmin/admin, change on first login), DLSPS RESThttps://<HOST_IP>/api/pipelines, WHEPhttps://<HOST_IP>/mediamtx/{{DETECTIONS_TOPIC_PREFIX}}_N/. - Configuration — key
.envvalues (HOST_IP, GIDs, TURN creds) + how to swap the video source for RTSP.
Template variable substitution
Substitute every {{VAR}} with its concrete value BEFORE writing any file — a
literal {{...}} left in nginx.conf, config.json, flows.json, the
dashboard JSON, or a test 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 -ltnshows:80/:443free,ss -lunshows:3478free (Coturn TURN). - Bypass host proxy for all localhost/LAN curl — corporate proxies route
https://localhost/...through an unreachable proxy (→Could not resolve host/ 502). Every curl insample_*.shMUST use--noproxy '*'+--cacert src/nginx/ssl/server.crt(generated byinstall.sh); tests setNO_PROXY=*inconftest.py. - Test WebRTC signalling with the criterion-7 curl (expect
200; stream exists only aftersample_start.sh). Test MQTT viadocker run --rm --network <project>_app_network eclipse-mosquitto:2.1.2-alpine mosquitto_sub -h broker -t '#' -v(tag2.1.2-alpineis an intentional pin — never:latest). - pytest venv at
./.venvinside stack dir (python -m venv .venv) — system pip is PEP-668 blocked;/tmpmay benoexec.
Optional external skills
If available, invoke; otherwise write files from the reference templates.
dlstreamer-coding-agent— pipeline JSON (+ single-app DL Streamer app when{{PACKAGING}}∈demo/function/port)dlsps-user— DLSPS deploy/config/REST; defaultproductionpath (PIPELINE)model-download— OMZ model IRscenescape-setup— only when{{SCENESCAPE}}=yes(SCENESCAPE)
Reference implementation
The upstream
smart-parking/src/
recipe uses the same path — consult it for 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../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(incl.mediamtx-server,coturn).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 HTML) once pipelines run; MediaMTX logs show the WHIP publisher connected perpeer-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 breaks Grafana plotting). - Grafana at
https://localhost/grafana(admin/admin) shows live {{OBJECT}} counts + alert data and{{NUM_SOURCES}}<iframe>WebRTC panels (GF_SECURITY_ALLOW_EMBEDDING=true). MQTT datasource health (/grafana/api/datasources/uid/mqtt_ds/health) MUST return"MQTT Connected"— broker injsonData.uri(tcp://broker:1883), noturl:. Blank-panel / redirect-loop fixes:references/PROXY_UI.md. 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}}RUNNING= missing watchdog dedup guard).
Final summary — surface the proof (don't just name files)
Graders see only your final message + tool names, not file contents. An
expectation counts as met only if you state it and quote the one decisive
line in your closing summary — walk every completion criterion plus the
proof-point checklist (topology, WebRTC, pinned tags, MQTT/class filter, no
literal {{...}}, certs, curl guards, watchdog, rule, classifier, Scenescape)
detailed in references/INSTALL.md →
Final-summary proof points. A claim with no quoted evidence is treated as unmet.