QIM SDK Deploy Skill
Operating Principle — Fail Fast, Don't Fix
When anything goes wrong — a missing password, a failed SSH connection, a path that doesn't exist, a missing config field — stop immediately and report the exact error to the user. Do not attempt workarounds, retries, alternative paths, or recovery steps unless explicitly documented in this skill. The user is responsible for fixing environment and configuration issues. Once fixed, they re-invoke the skill.
This keeps the skill predictable and token-efficient. Silent fallbacks hide real problems.
ABSOLUTE RULES — NO EXCEPTIONS
These rules override any instinct to "be helpful by trying something else." They apply to every invocation, every mode, every step.
NEVER improvise. Run the reference scripts exactly as documented. Do not write ad-hoc commands to work around a failure.
STOP on any failure. Report the exact error message. Do not try an alternative path. The correct response to failure is: show the error, name the script/step that failed, and stop. "Let me try X instead" is NEVER the correct response.
ALWAYS ask for mode and artifact path before doing anything. Do not read
.env, do not run preflight, do not check anything until you know: (a) Mode A / B / C / P / D, (b) the artifact path. Then readconfigs/.envfor only the keys that mode needs.NEVER proceed to deploy without a passing preflight for the current mode and current
.env. Re-readconfigs/.envfrom disk immediately before every preflight and every deploy — never reuse values remembered from earlier in the conversation, and never pass device credentials as command-line flags (let the script read the file, so a mid-session edit always takes effect). If preflight has not been run: run it first. If deploying a different mode than preflight was last run for: re-run preflight for the current mode.NEVER commit anything. Leave git completely to the user.
NEVER hardcode credentials. All credentials come from
configs/.env.NEVER skip the config wizard. If
configs/.envis incomplete, the wizard runs before anything else, every time.NEVER install, fix, or modify anything on the device or linux workstation during a deploy or eval run. When a pipeline, build, or device error occurs, show the exact error output and stop — do not attempt to fix it. Exception: preflight and SSH connection setup (host key, key copy, config wizard) — these are one-time setup steps the skill guides the user through so they can get connected.
NEVER run two deploys in parallel if they compete for an exclusive hardware resource. The device has one of each piece of hardware — two pipelines sharing the same resource will fail or corrupt each other's output. Camera pipelines (e.g. ISP camera, USB camera) are the most common case and must always run sequentially. When in doubt, run sequentially.
NEVER ask the user to "press Enter" or "click" anything. This skill runs through a chat interface — the user answers by typing. Every prompt must end with a concrete question expecting a typed reply, never a keypress or button action.
Deploy flow
The skill always deploys. Preflight runs automatically before every deploy — the user never needs to ask for it explicitly. The two steps the skill runs internally are:
Step A — preflight (automatic, before every deploy)
All deploy scripts live in the references/ folder next to this SKILL.md file. Use whichever path this file was loaded from — do not hardcode a path. For example, if this SKILL.md is at .claude/skills/qimsdk-deploy/SKILL.md, the scripts are at .claude/skills/qimsdk-deploy/references/.
python <skill-references-dir>/preflight_check.py \
--mode <A|B|C|P|D> \
[--artifact-path <path>]
Checks: Python env, PuTTY tools, credentials, TCP reachability, SSH login, device OS confirmation, mode-specific tool/path checks (cmake, make, sudo, timeout, source tree, output dir, cam-server), and for Mode C/D: workstation SSH, SDK validation, disk space.
Always run preflight before the first deploy and whenever the device or environment changes.
Credentials are read from configs/.env — see below.
What preflight checks per mode — and what to do if it fails:
Mode A requires on device:
timeoutcommandgst-launch-1.0installed- Output directory writable
- All input files and model/label paths from
pipeline.shpresent on device
Mode B requires on device:
cmakeinstalledmakeinstalledsudoaccess for the device usertimeoutcommand- Internet access (for
apt-get sourceon first deploy if workspace not yet provisioned) SOURCE_ROOTin.envis optional — auto-discovered via glob if not set
Note: Mode B deploy auto-provisions the workspace from scratch (apt setup, cmake configure, build)
if SOURCE_ROOT does not exist. Preflight reports the current workspace state as informational;
it does NOT fail if cmake has not been run or the source tree is absent.
Mode C requires on linux workstation:
- SSH key auth to linux workstation
- ~8GB free disk on local (non-NFS) filesystem (for SDK + repo + build artifacts)
Note: Mode C deploy auto-provisions the workspace from scratch (SDK download+install, git clone,
cmake configure, host-build) if not already set up. The SDK zip
(x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip for x86_64 hosts) is downloaded from codelinaro.org if absent.
If the download fails (network blocked), place the zip manually at
{LINUX_WORKSTATION_BUILD_DIR}/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip and re-run.
Mode P requires on device:
python3installedqimsdkPython package installed (from qimsdk import Pipelinemust work)timeoutcommand- Wayland compositor running (for waylanksink pipelines)
Note: Mode P is fail-fast only — if qimsdk is not installed, preflight fails with a clear
message. There is no auto-install. The user must install the QIM SDK Python package on the device
before deploying. Unlike Modes B/C/D, Mode P does no workspace provisioning.
Mode D requires on the Linux/WSL workstation:
- SSH key or password auth to the workstation
- ~5GB free disk on local (non-NFS) filesystem (for the Yocto SDK)
Mode D requires on device:
libqimsdk-app-builder.soruntime library present (WARN only) — a Mode D binary dynamically links it; if preflight WARNs it's missing, install the C++ IMSDK runtime on the device before deploying or the binary will fail to start.
Note: Mode D deploy auto-provisions the Yocto SDK (unzip + run the installer into
{LINUX_WORKSTATION_BUILD_DIR}/qcom-sdk) if not already installed, then builds each app
standalone (out of tree) against the installed SDK's target sysroot — no shared source tree
is cloned or mutated, unlike Mode C. Workstation arch (x86_64 or aarch64) is auto-detected,
same as Mode C. See "Mode D — C++ Standalone Host Build" below.
Preflight failure means deploy will fail. Every [FAIL] in preflight output must be resolved before running deploy. Do not proceed with deploy if preflight exits non-zero.
Step B — deploy
All scripts are in references/ next to this SKILL.md — use the same path used for preflight above.
# Mode A (gst-launch)
python <skill-references-dir>/deploy_mode_a.py \
--artifact-path <path/to/artifact> \
[--output-dir outputs/deploy]
# Mode B (Ubuntu on-device C app build)
python <skill-references-dir>/deploy_mode_b.py \
--artifact-path <path/to/artifact> \
[--output-dir outputs/deploy]
# Mode C (host build on linux workstation)
python <skill-references-dir>/deploy_mode_c.py \
--artifact-path <path/to/artifact> \
[--output-dir outputs/deploy]
# Mode P (Python qimsdk app — main.py, or legacy app.py)
python <skill-references-dir>/deploy_mode_p.py \
--artifact-path <path/to/artifact> \
[--output-dir outputs/deploy]
# Mode D (host build on Linux/WSL workstation — cpp-app-builder C++ SDK app)
python <skill-references-dir>/deploy_mode_d.py \
--artifact-path <path/to/artifact> \
[--output-dir outputs/deploy]
Each script reads credentials from configs/.env (same as preflight).
Before You Start — configs/.env
configs/.env is the single source of truth for all credentials and device connection details. It is read fresh on EVERY invocation — no values are carried between sessions or conversation turns.
This file is gitignored and never committed. Create it once from the sample — cp configs/.env.sample configs/.env — then fill in your values. Both preflight and deploy scripts read it automatically.
If configs/.env is missing or incomplete — run the config wizard
When configs/.env does not exist, or when required keys for the selected mode are missing, run the interactive config wizard before doing anything else:
- Mode is already known from Step 0 — only ask for keys that mode needs (see table below)
- For each required key for that mode (in order), ask one at a time:
- State what the key is for
- Explain how to find its value
- Show an example
- Wait for the user's answer
- End every prompt with a concrete question expecting a typed answer. Never tell the user to "press Enter", "hit return", or "click" anything — this skill is driven through a chat interface where the user replies with text; there is no keypress or button to trigger.
- After collecting all values, write them to
configs/.envwith inline comments - Tell the user: "Config saved to
configs/.env. Running preflight for Mode now." - Proceed to preflight — do not skip it.
Mode C wizard — ask this first before collecting any LINUX_WORKSTATION_ keys:*
"For Mode C, I need a Linux x86_64 machine to run the host build. Two options:
- Remote Linux workstation — a Linux machine on the network (e.g.
hu-gaurmeht-lv)- WSL (Windows Subsystem for Linux) — Ubuntu running locally on this Windows machine
Which are you using?"
If they answer WSL:
Ask first: "What is your WSL username? Run
whoamiinside your WSL terminal and tell me the output."Once you have the username, tell them: "Before I can connect, WSL needs its SSH server running. Run these inside your WSL terminal (Ubuntu app):
sudo service ssh start sudo service ssh statusIt should say
active (running). Keep the WSL window open (minimize it — don't exit).Also confirm passwordless sudo is configured (needed for auto-installing build tools):
sudo whoamiIt should print
rootwithout a password prompt. If it asks for a password, run (replace<wsl-username>with the username you just told me):echo "<wsl-username> ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/<wsl-username>Let me know once SSH is running and sudo is passwordless."
Ask for port: "What port is WSL SSH on? Run inside WSL:
grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null || echo '22'"Verify connectivity from PowerShell — tell the user to run:
ssh -p <port> <wsl-username>@localhost "echo connected"If it asks for a password that's fine (key not copied yet). If connection is refused, sshd isn't running — go back to step 1.
Copy the SSH key to WSL. Tell the user to run in PowerShell:
Get-Content "$HOME\.ssh\id_ed25519_qimsdk.pub" | ssh -p <port> <wsl-username>@localhost "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh"Enter WSL password when prompted. No output = success. Then verify key auth works:
ssh -p <port> -i "$HOME\.ssh\id_ed25519_qimsdk" <wsl-username>@localhost "echo connected"Should print
connectedwith no password prompt.Ask for build dir: "Where should build files go in WSL? Pick a path on WSL local disk with ~10GB free, e.g.
/home/<wsl-username>/qimsdk-build"Write to
configs/.env:LINUX_WORKSTATION_HOST=localhostLINUX_WORKSTATION_USER=<wsl-username>LINUX_WORKSTATION_PORT=<port>LINUX_WORKSTATION_KEY=C:/Users/<windows-username>/.ssh/id_ed25519_qimsdkLINUX_WORKSTATION_BUILD_DIR=<chosen-path>
If they answer remote Linux workstation:
- Proceed with the keys table as normal (ask
LINUX_WORKSTATION_HOST,LINUX_WORKSTATION_USER, etc. one at a time).
configs/.env is the right place for ALL config including Mode C linux workstation details. The file is gitignored and never committed. It persists across sessions so you don't have to re-enter values every time.
Keys required per mode
Mode A — gst-launch (always required):
| Key | What it is | How to find it | Example |
|---|---|---|---|
DEVICE_IP |
IP of the target device | ip addr on device, or router DHCP |
192.168.1.100 |
DEVICE_USER |
SSH login username | ubuntu (Ubuntu) or root (QLI) |
ubuntu |
DEVICE_KEY |
SSH private key path (preferred) | Check existing keys: Get-ChildItem $HOME\.ssh\*.pub in PowerShell. If id_ed25519_qimsdk.pub exists, use C:/Users/<you>/.ssh/id_ed25519_qimsdk. Otherwise generate: ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk" |
C:/Users/<you>/.ssh/id_ed25519_qimsdk |
DEVICE_PASSWORD |
SSH password (fallback if no key) | Device administrator | <password> |
HOST_KEY |
SSH host key fingerprint | Preferred: leave blank and run preflight — it prints the fingerprint automatically. If you need it before preflight: ssh <DEVICE_USER>@<DEVICE_IP> "for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf \$f 2>/dev/null; done" — pick ed25519 if present, else ecdsa. |
SHA256:... |
DEPLOY_OUTPUT_DIR |
Local folder for logs and output | Default is fine | outputs/deploy |
Set either
DEVICE_KEYorDEVICE_PASSWORD(or both — key is tried first). If neither is set, preflight fails with instructions to seessh-setup.md.
Mode B — adds:
| Key | What it is | How to find it | Example |
|---|---|---|---|
SOURCE_ROOT |
QIMSDK source tree path on device | SSH in, ls ~ | grep gst-plugins |
/home/ubuntu/Downloads/qimsdk_samples/gst-plugins-qti-oss-<version> |
Mode C — adds (instead of SOURCE_ROOT):
| Key | What it is | How to find it | Example |
|---|---|---|---|
LINUX_WORKSTATION_HOST |
Linux host-build workstation hostname/IP (x86_64 or aarch64 — arch auto-detected) | Remote machine: hostname on the machine. WSL: localhost |
my-linux-workstation or localhost (WSL) |
LINUX_WORKSTATION_USER |
SSH username on linux workstation | whoami on the machine |
myusername |
LINUX_WORKSTATION_PASSWORD |
SSH password (used when key not set) | Your login password | mypassword |
LINUX_WORKSTATION_KEY |
SSH private key path for linux workstation (optional if LINUX_WORKSTATION_PASSWORD set) | Check existing keys: Get-ChildItem $HOME\.ssh\*.pub in PowerShell. If id_ed25519_qimsdk.pub exists, use C:/Users/<you>/.ssh/id_ed25519_qimsdk. Otherwise generate: ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk" |
C:/Users/<you>/.ssh/id_ed25519_qimsdk |
LINUX_WORKSTATION_BUILD_DIR |
Working directory on linux workstation (local disk, not NFS) | Pick a path on local disk with ~10GB free | /home/myusername/qimsdk-build |
LINUX_WORKSTATION_PORT |
SSH port on the linux workstation | Standard Linux: use 22. If you're not sure or using WSL, run on the machine: grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null || echo "22" |
22 |
SDK install and repo clone are auto-provisioned on first deploy if not already present. If internet is blocked, place the SDK zip at
{LINUX_WORKSTATION_BUILD_DIR}/sdk.zip(simplest) or use the arch-specific name:x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip(x86_64) orarm-qli-2.0-qimsdk-2.0.0-standardsdk.zip(aarch64/WSL on ARM). The script checks for all three.
All Mode C keys above are required. Preflight exits immediately with [FAIL] if any are missing.
Mode C — optional, bring-your-own SDK/repo:
| Key | What it is | Example |
|---|---|---|
LINUX_WORKSTATION_SDK_PATH |
Absolute path on the workstation to an SDK installer — a .zip (unzipped automatically to find the installer inside) or a .sh (already extracted, run directly). Only consulted when the SDK is not yet installed; ignored otherwise. If not set, falls back to the existing zip-lookup + codelinaro.org download. |
/local/mnt/sdk/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip or /home/user/sdk-installer.sh |
LINUX_WORKSTATION_IMSDK_PATH |
Absolute path on the workstation to an already-cloned qimsdk repo. When set, this path is used directly and the git clone step is skipped entirely. If the path is set but CMakeLists.txt is not found there, deploy fails with a clear message — it never silently falls back to cloning a fresh copy. If not set, falls back to cloning from GitHub into {LINUX_WORKSTATION_BUILD_DIR}/qimsdk. |
/local/mnt/repos/qimsdk |
Ask the user during the Mode C wizard, right after LINUX_WORKSTATION_BUILD_DIR:
- "Do you have the SDK installer already on the workstation (as a
.zipor.sh)?" — if yes: "What is the path on the workstation?" → save asLINUX_WORKSTATION_SDK_PATH— if no: nothing to do; SDK downloads automatically on first deploy - "Do you have
qimsdkalready cloned on the workstation?" — if yes: "What is the path on the workstation?" → save asLINUX_WORKSTATION_IMSDK_PATH— if no: nothing to do; repo clones automatically on first deploy
Mode D — adds (same LINUX_WORKSTATION_ keys as Mode C, no new required keys):*
Mode D reuses every LINUX_WORKSTATION_HOST/USER/KEY/PASSWORD/PORT/BUILD_DIR key above — it targets
the same class of Linux workstation as Mode C (x86_64 or aarch64 — arch is auto-detected), just
with a different SDK (the Yocto standard SDK, not qimsdk). Optional additions:
| Key | What it is | How to find it | Example |
|---|---|---|---|
LINUX_WORKSTATION_SDK_URL |
Source for the Yocto SDK zip (installer .sh inside) | A file:// path to a network share, or an http(s):// URL |
file:///mnt/share/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip |
LINUX_WORKSTATION_SDK_PATH |
Absolute path on the workstation to a Yocto SDK installer already present — a .zip (unzipped automatically to find the .sh installer inside) or a .sh (already extracted, run directly). Takes precedence over the build-dir zip lookup and LINUX_WORKSTATION_SDK_URL. Only consulted when the SDK is not yet installed; ignored otherwise. Same key as Mode C — set once, works for both modes. |
/local/mnt/sdk/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip or /local/mnt/sdk/installer.sh |
If a
qcom-yocto-sdk*.zip,sdk.zip, or the arch-specific zip name (see below) is already present inLINUX_WORKSTATION_BUILD_DIR, neitherLINUX_WORKSTATION_SDK_PATHnorLINUX_WORKSTATION_SDK_URLis needed — deploy uses the local zip. The SDK is installed once into{LINUX_WORKSTATION_BUILD_DIR}/qcom-sdkand reused for every app. SDK source precedence:LINUX_WORKSTATION_SDK_PATH→ build-dir zip →LINUX_WORKSTATION_SDK_URL→ default Artifactory zip for the detected workstation arch (viauname -m, same as Mode C):https://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/meta-qcom/qcom-distro/qcom-armv8a/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip(x86_64) orhttps://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/meta-qcom/qcom-distro/qcom-armv8a/arm-qli-2.0-qimsdk-2.0.0-standardsdk.zip(aarch64).
Ask the user during the Mode D wizard, right after LINUX_WORKSTATION_BUILD_DIR:
- "Do you have the Yocto SDK installer already on the workstation (as a
.zipor.sh)?" — if yes: "What is the path on the workstation?" → save asLINUX_WORKSTATION_SDK_PATH— if no: deploy will detect the workstation arch (uname -m) and download the matching default Yocto SDK zip (x64-qli-2.0-qimsdk-2.0.0-standardsdk.zipfor x86_64 orarm-qli-2.0-qimsdk-2.0.0-standardsdk.zipfor aarch64) from the Artifactory URL on first deploy. If you need a different source, setLINUX_WORKSTATION_SDK_URLto afile://path orhttp(s)://URL. (Mode D has no repo-clone step, so there is noIMSDK_PATHquestion — unlike Mode C.)
Mode P — same as Mode A (no extra keys needed):
Mode P uses the same device credentials as Mode A. No additional keys required — just DEVICE_IP, DEVICE_USER, DEVICE_KEY/DEVICE_PASSWORD, HOST_KEY, DEPLOY_OUTPUT_DIR. Never ask for SOURCE_ROOT or LINUX_WORKSTATION_* for Mode P. Artifact entry point is main.py (legacy app.py also accepted).
Full example — copy this and fill in your values
# configs/.env
# ─────────────────────────────────────────────────────────────────────────────
# Required for all modes
# ─────────────────────────────────────────────────────────────────────────────
DEVICE_IP=<device-ip>
# Why: The SSH target. Used by preflight and deploy to connect to the device.
DEVICE_USER=ubuntu
# Why: SSH login user. Usually 'ubuntu' on Ubuntu devices, 'root' on QLI 2.0.
DEVICE_PASSWORD=<password>
# Why: SSH password. Never put this on the command line or in a YAML config.
# Set DEVICE_PASSWORD here; the scripts read it via os.environ.
HOST_KEY=SHA256:cKvXMnoKhO6g+fnn17WzmrpnfSSXW+MwzwFNiUu/gC4
# Why: Prevents connecting to the wrong device.
# If you don't have it, run preflight without HOST_KEY first — the script
# will show you the actual fingerprint and tell you to save it here.
DEPLOY_OUTPUT_DIR=outputs/deploy
# Why: Where deploy scripts save per-artifact logs and pulled output files.
# ─────────────────────────────────────────────────────────────────────────────
# Mode B only (Ubuntu on-device build)
# ─────────────────────────────────────────────────────────────────────────────
SOURCE_ROOT=/home/ubuntu/Downloads/qimsdk_samples/gst-plugins-qti-oss-1.0.r1.06800
# ─────────────────────────────────────────────────────────────────────────────
# Mode C (host build on linux workstation)
# ─────────────────────────────────────────────────────────────────────────────
LINUX_WORKSTATION_HOST=<linux-workstation-hostname>
LINUX_WORKSTATION_USER=<username>
LINUX_WORKSTATION_KEY=C:/Users/<you>/.ssh/id_ed25519_qimsdk
LINUX_WORKSTATION_PORT=22
# Why: SSH port on the linux workstation. Default is 22 (standard Linux).
# WSL (Windows Subsystem for Linux) commonly uses port 2222.
# To find your port, run on the linux workstation:
# grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null || echo "22"
# ─────────────────────────────────────────────────────────────────────────────
# Mode D (host build on Linux/WSL workstation — cpp-app-builder C++ SDK app)
# Reuses ALL LINUX_WORKSTATION_* keys above — no new required keys.
# ─────────────────────────────────────────────────────────────────────────────
LINUX_WORKSTATION_SDK_URL=file:///path/to/qcom-yocto-sdk-deploy.zip
# Why: Source for the Yocto standard SDK (installer .sh inside). Accepts a
# file:// path (a share/mount visible on the workstation) or an
# http(s):// URL. Optional if a qcom-yocto-sdk*.zip is already present
# in LINUX_WORKSTATION_BUILD_DIR — deploy looks there first.
When invoked — mandatory startup sequence
Every invocation follows this exact sequence. Do not skip steps.
Step -1 — Verify Python is available
Before anything else, run:
python --version || python3 --version || py --version
If all three fail, Python is not installed. Tell the user:
"Python 3 is required to run deploy. Install it from https://www.python.org/downloads/ and ensure it is on PATH. On Windows, tick 'Add Python to PATH' during installation. Then restart your terminal and try again." Stop here until Python is available.
Step 0 — Ask what the user wants to do
Before reading any files or checking any config, ask the user to pick exactly one mode from this list — present each mode as its own separate, distinct option, never abbreviated or grouped (e.g. never phrase it as "P or D?"):
- Mode — pick one:
- A — gst-launch pipeline (
pipeline.sh), no build - B — Ubuntu on-device build (gstreamer-app-builder C app, QIMSDK source tree already on device)
- C — Host build (gstreamer-app-builder C app), device is QLI 2.0, needs a Linux workstation
- D — Host build (cpp-app-builder C++ SDK app), needs a Linux workstation
- P — Python qimsdk app (
main.py/app.py), no build
- A — gst-launch pipeline (
- Artifact path: path to the artifact folder
Do not proceed until you have both. Mode determines which config keys are required. The subcommand is always deploy — preflight runs automatically before every deploy.
Mode selection guide (show this if the user is unsure) — each mode below is a separate, distinct choice, not to be merged or abbreviated when relaying to the user:
- Mode A — artifact is a
pipeline.sh(gst-launch command). No build needed. - Mode B — artifact has
main.c+set(GST_EXAMPLE_BIN ...), device is Ubuntu with QIMSDK source tree on-device. - Mode C — artifact has
main.c+set(GST_EXAMPLE_BIN ...), device is QLI 2.0 / host build (no build tools). Needs a Linux workstation (x86_64 or aarch64 — arch is auto-detected; WSL on either Windows x86_64 or Windows ARM works). - Mode D — artifact has
main.cc+set(TEST_TARGET ...)usingqti::Pipeline/<qti/qimsdk.h>(cpp-app-builder). Needs a Linux workstation (x86_64 or aarch64 — arch is auto-detected; WSL on either Windows x86_64 or Windows ARM works). - Mode P — artifact has
main.py(or legacyapp.py) usingqimsdk.Pipeline. No build.
Mode C and Mode D both host-build on a workstation and push a binary to the device, but they target
different builder contracts (gstreamer-app-builder's C sample apps vs cpp-app-builder's standalone
C++ SDK apps) and use different toolchains (qimsdk source tree vs the Yocto standard SDK)
— check main.c+GST_EXAMPLE_BIN (Mode C) vs main.cc+TEST_TARGET (Mode D) in the artifact.
Step 1 — Read configs/.env for the selected mode
Read configs/.env from the repo root. Check only the keys required for the selected mode:
| Mode | Required keys |
|---|---|
| A | DEVICE_IP, DEVICE_USER, DEVICE_KEY or DEVICE_PASSWORD (at least one), DEPLOY_OUTPUT_DIR |
| B | All Mode A keys + SOURCE_ROOT |
| C | All Mode A keys + LINUX_WORKSTATION_HOST, LINUX_WORKSTATION_USER, LINUX_WORKSTATION_BUILD_DIR |
| P | All Mode A keys (no extra) |
| D | All Mode A keys + LINUX_WORKSTATION_HOST, LINUX_WORKSTATION_USER, LINUX_WORKSTATION_BUILD_DIR (same as Mode C) + optional LINUX_WORKSTATION_SDK_URL |
- If the file does not exist: run the config wizard for the selected mode (see below)
- If the file exists but is missing required keys for the selected mode: run the wizard for only the missing keys
- If all required keys are present: proceed to Step 1a (Mode C/D) or Step 2 (all other modes)
Step 1a — Mode C / Mode D only: ask about SDK and repo (REQUIRED, never skip)
After collecting LINUX_WORKSTATION_BUILD_DIR, always ask these questions before running preflight.
Do not skip to preflight without completing this step.
Mode D — SDK question (ask exactly this):
"Do you have the Yocto SDK installer already on the workstation — as a
.zipor a.shfile?"
- Yes → "What is the full path to it on the workstation?" → set
LINUX_WORKSTATION_SDK_PATH - No → leave unset; deploy will download the default Yocto SDK zip from Artifactory on first run
Mode C — SDK question (ask exactly this):
"Do you have the SDK installer already on the workstation — as a
.zipor a.shfile?"
- Yes → "What is the full path to it on the workstation?" → set
LINUX_WORKSTATION_SDK_PATH - No → leave unset; SDK downloads automatically from codelinaro.org on first deploy
Mode C — repo question (ask exactly this):
"Do you have
qimsdkalready cloned on the workstation?"
- Yes → "What is the full path to it on the workstation?" → set
LINUX_WORKSTATION_IMSDK_PATH - No → leave unset; repo clones automatically from GitHub on first deploy
Step 2 — Run preflight for the selected mode
Always run preflight before deploying — it takes ~5s and verifies the device is reachable and ready:
python <skill-references-dir>/preflight_check.py --mode <A|B|C|P|D>
If preflight fails with a connection or auth error, work through the recovery flow below before asking the user to do anything manually. Never proceed to Step 3 until preflight exits 0.
Preflight Recovery Flow
Case 1 — Host key mismatch or FATAL ERROR: Host key not in manually configured list
The device was likely re-imaged. Fetch the new fingerprint — run this in PowerShell (enter password when prompted):
ssh <DEVICE_USER>@<DEVICE_IP> "for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf \$f 2>/dev/null; done"
This prints one SHA256:... line per key type the device has. Pick the ed25519 line if present, otherwise use ecdsa. Update HOST_KEY in configs/.env with that value, then continue to Case 2 to re-copy the SSH key (a re-imaged device has lost all authorized_keys).
Case 2 — SSH key authentication failed
The SSH public key is not installed on the device (new or re-imaged device). Copy it now.
NEVER ask the user for their password through the chat interface — not even "just this once". Always give them the plink command to run themselves. Credentials stay out of the conversation.
First confirm the HOST_KEY in configs/.env is current (run Case 1 if unsure). Then copy the public key to the device — run these three commands one at a time (enter password at each prompt):
ssh <DEVICE_USER>@<DEVICE_IP> "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
Get-Content "$HOME\.ssh\id_ed25519_qimsdk.pub" | ssh <DEVICE_USER>@<DEVICE_IP> "cat >> ~/.ssh/authorized_keys"
ssh <DEVICE_USER>@<DEVICE_IP> "chmod 600 ~/.ssh/authorized_keys"
Replace <DEVICE_USER> and <DEVICE_IP> from configs/.env. Do not chain these with && — run each separately. You may be prompted for the password 2-3 times per command (PAM stacking — re-enter the same password each time). No output means success.
After running, re-run preflight. If it still fails with key auth, check that DEVICE_KEY in configs/.env points to the correct key file.
Case 3 — DEVICE_KEY file not found
The key file path in configs/.env is wrong or the file doesn't exist. Check:
ls -la <DEVICE_KEY>
If missing, generate a new key pair:
ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk"
Then update DEVICE_KEY in configs/.env and repeat Case 2.
Case 4 — TCP reachable but all auth fails
Try password auth as a fallback: uncomment DEVICE_PASSWORD in configs/.env and re-run preflight. Once preflight passes with password auth, run Case 2 to install the key, then switch back to key auth.
Step 3 — State what you're about to do, then run
Tell the user: mode, artifact path, script that will run. Then run it. Do not improvise.
Output review categories — sync vs async
Artifacts fall into two review categories based on how output is delivered:
| Category | Condition | Review timing |
|---|---|---|
| Sync | waylandsink present with no filesink (display-only), OR qtirtspbin as sink (RTSP-out) |
Must be reviewed live — output cannot be recovered after the run. Pause before AND after every sync run. |
| Async | filesink present (output written to file) |
File can be reviewed any time after the run. |
For sync artifacts, when acting as orchestrator (not called from harness):
- Before running: Tell the user the artifact name, what pipeline it is, what they should see on screen or stream, and ask them to confirm they are watching before you start.
- After running: Ask explicitly — (1) did you see the expected output? (2) did the inference results look correct? (bounding boxes on right objects, correct labels, etc.). Record their y/n answer as the G3 human score.
- Never skip this for display or RTSP-out pipelines. Running a display pipeline without the user watching is a wasted run with no scorable output.
Non-interactive callers (--no-confirm)
When called from the eval skill or another automated context with --no-confirm:
- All config values come from
configs/.env. If any required key is missing, stop and report which keys. - Never ask interactive questions.
- If the artifact has a
waylandsink, emit: "⚠️ Non-interactive call: pipeline writes to display — ensure Wayland compositor is running on device." Then proceed.
Hard Rules
- NEVER ask the user for a password through the chat interface — not to install a key, not "just this once", not for any reason. If a password is needed to copy an SSH key to the device, give the user the plink command to run themselves. Credentials must not appear in the conversation.
- All SSH/SCP in deploy scripts uses paramiko (Python) — not plink/pscp. PuTTY is not required by the deploy scripts (it is optional for interactive manual use only).
- Always pass
HOST_KEYfingerprint to every paramiko connection — never use AutoAddPolicy in the deploy scripts. - Always use
timeout --signal=SIGINT --kill-after=15 30for all gst-launch (Mode A) pipelines — both file-source and camera. 30 seconds allows for QNN/SNPE/TFLite model loading on first invocation (which can take 15-25s) plus time for PLAYING and initial inference. SIGINT (not SIGTERM) lets GStreamer flush the MP4 moov atom cleanly. - For Mode B/C/D C/C++ app file-source pipelines: run to natural EOS — no timeout (5 min SSH cap). The binary exits on its own when the file ends. Using timeout before EOS produces corrupt MP4.
- Camera pipeline retry logic (Mode A/B): the deploy script runs the camera pipeline first without restarting cam-server (the common case is fine). If attempt 1 fails (never reached PLAYING, or crashed, or no moov atom), it restarts cam-server, waits 3 seconds, and retries once. A second failure is a real problem — not retried again. Do not add unconditional pre-run restarts.
- Pull output immediately after each camera run before starting the next — camera pipelines (02, 03, c-app equivalents) share the same output filenames (
two_stream_obj_detect_out.mp4,three_stream_*). The next run overwrites the previous output. Always: run → pull → run next. - Camera pipeline run duration: 30s for all modes (Mode A/B/C/D). Use
timeout --signal=SIGINT --kill-after=15 30. File-source runs to natural EOS. - Always use
echo <PASSWORD> | sudo -S <cmd>for any write to root-owned paths - Never commit anything — leave git to the user
- Device config is always passed in as parameters — never hardcoded
- Never skip the upfront config stage when called standalone
- Check port availability with
/proc/net/tcpnotss—ssis not available on QLI 2.0 devices. Usecat /proc/net/tcp | grep <HEX_PORT>where port 8900 = 22C4, port 8901 = 22C5 (4-digit hex uppercase). - RTSP input pipeline timing: When you control both the RTSP source and the RTSP consumer pipeline, start source first, then start consumer exactly 3 seconds later (not more). Longer gaps allow the source to start looping or stall before the consumer connects. Verify source port is open before starting consumer.
- Wayland env is always set before every pipeline run (all modes, all sink types) —
qtivoverlayprobes for a display even in file-output pipelines; setting it is harmless when no display is present. The socket must be searched across all of/run, not just$(id -u)runtime dir, because the compositor may run as a different user (e.g.westonuid 1000 at/run/user/1000/wayland-1while the pipeline runs asroot). The deploy scripts handle this automatically; use this snippet if running manually:WS=$(find /run -maxdepth 3 -name "wayland-*" ! -name "*.lock" 2>/dev/null | head -1) export XDG_RUNTIME_DIR=$(dirname "$WS") export WAYLAND_DISPLAY=$(basename "$WS")
Structured Result
The deploy script always writes result.json to <DEPLOY_OUTPUT_DIR>/<artifact-name>/result.json,
even on failure. The schema and AI result card format are defined in:
references/result-format.md
Read that file before rendering any deploy result. Always show the result card first.
Output Collection
<DEPLOY_OUTPUT_DIR>/<artifact-name>/
├── <artifact-name>_device.log
├── <artifact-name>_build.log (Modes B/C/D only)
└── <artifact-name>_output.<ext>
When called standalone, pull to DEPLOY_OUTPUT_DIR (from configs/.env) and notify user.
SSH/SCP Command Templates
Windows host (paramiko — all deploy scripts)
The deploy scripts use paramiko internally. For interactive use from a terminal, use OpenSSH (included in Windows 10/11):
# SSH
ssh -i <KEY_FILE> <USER>@<IP> "<command>"
# SCP local → remote
scp -i <KEY_FILE> <local_file> <USER>@<IP>:<remote_path>
# SCP remote → local
scp -i <KEY_FILE> <USER>@<IP>:<remote_path> <local_path>
Linux host
ssh -i <KEY_FILE> <USER>@<HOST> "<command>"
scp -i <KEY_FILE> <local_file> <USER>@<HOST>:<remote_path>
scp -i <KEY_FILE> <USER>@<HOST>:<remote_path> <local_path>
Deploy scripts always use paramiko — see
deploy_mode_b.py,deploy_mode_a.py,deploy_mode_c.pyinreferences/. PuTTY (plink/pscp) is never needed.
Mode A — gst-launch (No Build)
Use when: artifact is a pipeline.sh (generated by qimsdk-gstreamer-dev). No compilation needed. Works on any device with GStreamer and QIMSDK plugins installed.
Script: references/deploy_mode_a.py
What the script does (step by step)
A0 — Parse pipeline.sh and read README.md locally before connecting to device:
- Read
README.mdin full if present — surface any prerequisites listed in "How to Run" (e.g.wpctl set-defaultfor audio/AV pipelines) to the user before proceeding so they can complete them on the device - Detect source type:
file-source(filesrc),camera(qtiqmmfsrc/qticamsrc), orrtsp(rtspsrc) - Extract output file path from
filesink location=<path>(if present — display-only pipelines have none) - Extract all input file paths from
filesrc location=<path>(may contain$HOMEshell variables) - Detect if
waylandsinkis present (sets Wayland env before run regardless of sink type) - Detect if
qtirtspbinis present (RTSP serving sink,rtsp_out=True) — this is orthogonal to source type and does not make the pipeline anrtspsource - RTSP input pipelines (
rtspsrc—source_type=rtsp): cannot be run automatically — stop, print exact manual instructions, and ask the user to run and report back. Instructions to show:- Push
pipeline.shto device:pscp pipeline.sh ubuntu@<ip>:~/pipeline.sh - SSH in, set Wayland env, run:
bash ~/pipeline.sh - Verify: check for
Setting pipeline to PLAYINGin output;ls -lh /home/ubuntu/Downloads/qimsdk_samples/media/output/for any output file; pull it withpscp - Ask user to paste the output and report what they observed
- Push
- RTSP output pipelines (
qtirtspbinserving sink fed by camera/file): run automatically like any live sink — handled by the normal run path (same aswaylandsink). Treated as a sync artifact (see review-timing table) — must be reviewed live.
A1 — Verify input files exist on device (file-source pipelines only, before pushing anything):
…(truncated)