Device targets reference
Every command, file, exit code and refusal of the device target lane: build here, run on an Axelera machine you cannot install Wingman on, and bring the results back.
Axelera Wingman runs a pipeline on a device target with wingman target, a small Python tool that uses your own ssh client, config and keys. The device can carry a card of either generation in Hardware generations & product names, Metis or Europa. The tool takes no free-form command: every remote command comes from the fixed grammar on this page. The task-level walkthrough is Run on a device you cannot install Wingman on. Outside the Linux desktop package the tool ships as dist/wingman-target.pyz and as source inside the wingman-launch skill: on macOS or Windows run it as python3 wingman-target.pyz … or python3 <skill>/scripts/runtime/wingman_target ….
What the device needs
Commands
| Command | What it does | Approval |
|---|---|---|
wingman target add <name> --ssh <alias> [--tier open|gated|offline] [--stage-root PATH] | Writes the target record. The default tier is open and the default stage root is ~/wingman-stage. An offline target takes no --ssh. Refuses a name that already has a record. | No card |
wingman target probe <name> [--interactive] | Copies the probe script into the stage folder, runs it, and saves the report and the host facts into the record. --interactive drops BatchMode for the first connection so you can answer a password, second-factor or network check prompt. | Card in every mode |
wingman target pack --bundle <dir> [--target <name>] [--runtime-release R] [--arch A] [--source-commit C] [--artifacts <sdk-build-dir>] | Writes the digest list and the manifest and prints the bundle id. --artifacts copies <sdk-build-dir>/<model>/ into build/<model>/ first, using the model named in the package’s manifest.json. | No card |
wingman target push <name> --bundle <dir> | Verifies the bundle against its manifest, then streams it into <stage>/<id>/ on the device. | Card in every mode |
wingman target run <name> --bundle <id> [--timeout SECONDS] [--follow] | Starts ./RUN_HERE.sh --target-check detached on the device with the record’s media_pull_max_mb as the runner’s media cap. --timeout may only lower the record’s max_run_seconds. --follow streams the log to stderr until the run exits or the limit plus one minute has passed; then it returns with state running and you re-check with status. | Card in every mode |
wingman target status <name> --bundle <id> | Reports running, exited with the runner’s exit code, or unknown. | No card |
wingman target logs <name> --bundle <id> [--since OFFSET] [--follow] | Reads run.log from a byte offset and returns next_offset for the next read. | No card |
wingman target stop <name> --bundle <id> | Sends TERM to the runner’s own process group (the timeout group), KILL two seconds later, then sweeps the wrapper’s group; the wrapper records the signalled exit, so status reads exit=N. | Card in every mode |
wingman target pull <name> --bundle <id> [--into DIR] | Brings results/ back into the bundle folder push recorded, or into --into, which must hold the manifest of this very bundle. Media over the record’s cap and media the runner left behind stay on the device; the transfer is bounded by the sizes the manifest promises, clamped to fixed ceilings. | No card |
wingman target export <name> --bundle <dir> --out <dir> | Writes <bundle>-<id12>.tar.gz and an operator sheet <bundle>-<id12>.OPERATOR.md into --out, a directory outside the bundle, for the offline tier. Packs the bundle first when it has no manifest. | No card |
wingman target import --bundle <dir> <results-archive | results/> [--no-author-check] | Checks carried-back results against their return manifest, installs them, runs ./RUN_HERE.sh --author-check and writes results/IMPORT_SUMMARY.json. | No card |
Options for every command
--workspace DIR sets the workspace root (the current folder by default). Every command prints one JSON object on stdout; --plain prints key: value lines instead. A failure carries error and exit. --version prints the tool version. In the Linux app, wingman target works without a display.
Exit codes
| Exit code | Meaning |
|---|---|
| 0 | Done. |
| 2 | Usage or record error: a bad argument, a missing or invalid record, an offline target given a remote verb. |
| 3 | ssh could not reach or log in to the device, or no ssh client was found. |
| 4 | Bundle refused: digest mismatch, unlisted or missing file, unsafe archive member, results from another bundle, or a gate refusal of the bundle. |
| 5 | The device refused the command, or the gate refused the command itself. |
| 6 | The run failed on the device: run --follow saw a non-zero runner exit, or import found a failed device check or author check. |
| 7 | Pull found nothing: no return manifest, no listed files, or every file left behind. |
The target record
| Field | Meaning |
|---|---|
schema_version | Always 1. |
name | Lowercase letters, digits and -, up to 63 characters, starting with a letter or digit. |
ssh_alias | A host name or an alias from your ssh config, with no options. null for the offline tier. User, port, jump host, keys and host identity stay in your ssh config and known_hosts. |
tier | open, gated or offline. |
roles | {"run": true, "build": false}. The device runs bundles and does not build them. |
stage_root | Where bundles live on the device: an absolute path or one starting with ~/, never ... Default ~/wingman-stage. |
policy.max_run_seconds | The longest a run may take. Default 3600. |
policy.media_pull_max_mb | Media files larger than this stay on the device at pull. Default 200. |
policy.sudo | Always false. A record that sets it is refused. |
profile | The sanitized probe report plus host: arch, os_release, libc, python, disk_free_mb, mem_total_mb, root_readonly, device_node_ok, sshd. |
profiled_at | When probe last ran. |
Files Wingman writes
| Path | Written by | Holds |
|---|---|---|
.wingman/targets/<name>.json | add, probe | The target record. No secrets, so it can live in git. |
.wingman/targets/<name>.audit.jsonl | Every command | One line per call: time, verb, bundle id, exit code and the arguments. Never the streamed bytes. |
.wingman/targets/<name>.runs/<id>.json | push, run, stop, pull | The local bundle folder and the push, start, stop and pull times. |
WINGMAN_BUNDLE.sha256, WINGMAN_BUNDLE.json | pack | The digest list and the manifest, in the bundle folder. |
results/PULL_SUMMARY.json | pull | The device check verdict, the file count and the media left behind. |
results/IMPORT_SUMMARY.json, results/author-check.log | import | The device and author check verdicts, and the author check’s log. |
<stage>/.wingman/axelera_probe.py | probe (open tier) | The probe script, on the device. |
<stage>/<id>/ | push, then the runner | The bundle, run.log, run.pid, run.exit and results/, on the device. The gate also writes run.cmd. |
Tiers
| Tier | How commands reach the device | What a security reviewer gets |
|---|---|---|
open | Plain key-based ssh. The tool sends the fixed commands below. | Nothing installed beyond the stage folder. The audit trail is the local audit file plus the sshd login log; sshd does not log commands on this tier. |
gated | An authorized_keys forced command runs the gate script, which reads only the verb string. | One reviewed POSIX sh script. It admits only the listed verbs, keeps every path inside the stage folder, runs only bundles whose id the owner listed, and logs every call to syslog. Needs OpenSSH sshd. |
offline | None. A person carries the archive and the results. | The operator sheet says what to run. The bundle writes only inside its folder and opens no network connection. |
The bundle
pack lists every regular file in the package in WINGMAN_BUNDLE.sha256 as <sha256> <path>, with RUN_HERE.sh first. It leaves out the manifest, the digest list and the runner’s outputs (results/, run.log, run.pid, run.exit). WINGMAN_BUNDLE.json records the name, the creation time, the target and a hash of its profile, the runtime release, the architecture, the entry point, the source commit, the digest of the digest list and the file count. The bundle id is the SHA-256 of the manifest file’s exact bytes, so editing any listed file after pack changes nothing in the manifest but fails verification at push. A bundle with a symlink, a control character in a name or no RUN_HERE.sh is refused. The compiled model travels inside the bundle under build/<model>/, so the device downloads nothing.
The commands sent to the device
| Verb | Open tier command | Gated tier command |
|---|---|---|
| probe | mkdir -p S/.wingman && cat > S/.wingman/axelera_probe.py (the probe on stdin), then python3 S/.wingman/axelera_probe.py --json --surface wingman-target --host-os linux followed by a fixed host facts script | wingman-target probe |
| push | mkdir -p S/ID && tar -x -C S/ID (the bundle on stdin) | wingman-target push ID |
| run | cd S/ID && rm -f run.exit run.job && { setsid sh -c 'WINGMAN_RETURN_MEDIA_MAX_MB=CAP timeout -k 10 MAX ./RUN_HERE.sh --target-check > run.log 2>&1 & echo $! > run.job; wait $!; echo $? > run.exit' < /dev/null > /dev/null 2>&1 & echo $! > run.pid; } && cat run.pid | wingman-target run ID MAX CAP |
| status | cd S/ID, then exit=N from run.exit, running while run.pid is alive, else unknown | wingman-target status ID |
| logs | tail -c +OFFSET+1 S/ID/run.log | wingman-target logs ID OFFSET |
| stop | kill -TERM then, two seconds later, kill -KILL to the job’s process group named in S/ID/run.job (the process itself when it leads no group), then kill -KILL to the wrapper group in S/ID/run.pid | wingman-target stop ID |
| pull | cat S/ID/results/RETURN_MANIFEST.json, then tar -c -C S/ID --exclude=<media over the cap and media the runner left behind> results | wingman-target pull ID |
S is the record’s stage_root, ID is the 64-character bundle id and MAX is the run limit. Every call uses BatchMode=yes (except an interactive probe), ConnectTimeout=15, StrictHostKeyChecking=yes with your own known_hosts, ClearAllForwardings=yes, and ControlMaster=auto with ControlPersist=10m on a socket in a private folder the tool owns. Stdin is closed except while the probe script or a bundle is being sent. Windows OpenSSH has no connection sharing, so there each call logs in again. A run survives a closed laptop or a dropped connection; status and pull pick it up later.
Approval by permission mode
| Permission mode | probe, push, run, stop | Everything else |
|---|---|---|
| Ask | A card every time. | A card every time, as for any command. |
| Auto | A card every time, naming the target and the bundle. | status, logs, pull, add, pack, export and import are judged like any other command and normally run without one. A plain ssh line is judged by what it runs on the other machine, where every path counts as outside your workspace: ssh kit rm -rf build asks, ssh kit ls does not. |
| Full access | A card every time. Running code on another machine is never silent. | Only privilege escalation asks, on this machine or in an ssh line. |
The device check and the author check
On the device the runner runs as ./RUN_HERE.sh --target-check. It needs no Node.js and no browser. It uses the model under build/<model>/ when the bundle carries one and downloads nothing; with WINGMAN_OFFLINE=1 it stops instead of downloading. It writes results/DEVICE_CHECK.json, results/METIS_PROOF.json, results/EVALUATION.json, results/RUN_SUMMARY.json and the inference logs, then results/RETURN_MANIFEST.json and <package>-results.tar.gz beside the bundle, even when the check fails. On this machine, after pull, ./RUN_HERE.sh --author-check runs the viewer checks and the screenshot on the pulled results. It writes VALIDATED.txt only when the device check and the viewer check both passed. import runs the author check for you unless you pass --no-author-check.
| Runner exit | Reason in DEVICE_CHECK.json |
|---|---|
| 3 | voyager_sdk_not_found |
| 4 | python_or_sdk_environment_missing |
| 5 | runtime_or_device_check_failed |
| 6 | model_artifacts_missing, or model_artifacts_not_in_bundle_offline with WINGMAN_OFFLINE=1 |
| 9 | no_canonical_output |
| 10 | metis_proof_failed |
The return manifest and the media cap
results/RETURN_MANIFEST.json holds the bundle id, the device check verdict, every returned file with its size and SHA-256, the media left behind with their sizes, the runtime probe and the time it was written. Media are files ending in .mp4, .mkv, .avi, .mov, .webm, .jpg, .jpeg, .png or .gif, except results/viewer-screenshot.png. The runner leaves media larger than WINGMAN_RETURN_MEDIA_MAX_MB (default 200) out of the manifest and the archive unless WINGMAN_RETURN_MEDIA=1. pull also skips media larger than the record’s media_pull_max_mb: on the open tier they never leave the device, and on the gated tier the gate sends all of results/ and Wingman drops them before writing. Logs and JSON always come back. Every file that arrives must match its listed SHA-256, or the pull is refused.
Install the gate
Review and copy the script
The Linux package ships the script as
/usr/share/wingman/wingman-target-gate.sh. It is POSIX sh and runs under dash and BusyBox ash. Read it whole, record itssha256sum, and copy it to the device, for example to/usr/local/bin/wingman-target-gate.sh.Create the allow file
Create the allow file: one bundle id per line. An empty file admits nothing. Add an id after you have reviewed that bundle; the id is what
packandpushprint.Add the authorized_keys line
Add a forced-command line for the key Wingman’s user logs in with. The arguments are the stage root (absolute), the allow file, the longest run in seconds (default 3600) and, for
probe, the path to a pinned copy ofaxelera_probe.py(a bundle carries one inruntime/).authorized_keys
restrict,from="203.0.113.10",command="/usr/local/bin/wingman-target-gate.sh /var/lib/wingman-gate/wingman-stage /var/lib/wingman-gate/wingman-allowed-ids 3600 /var/lib/wingman-gate/axelera_probe.py" ssh-ed25519 AAAA...Add the target as gated
Record the target with
wingman target add <name> --ssh <alias> --tier gated. Every call logs one syslog line taggedwingman-gate, such aspush <id> admitted,run <id> max=3600, orrefused (3): <reason> [command: ...].
How the gate decides
The gate admits by content. On push it refuses links, devices, absolute names and .. before extracting, then checks that WINGMAN_BUNDLE.json hashes to a listed id, that the manifest’s digest_list matches WINGMAN_BUNDLE.sha256, and that every file matches its digest with nothing unlisted. It checks the same again before every run, so a RUN_HERE.sh edited on the device is refused. The runner’s outputs and run.cmd are outside that check, so a bundle can run twice. The gate exits 2 for a refused command and 3 for a refused bundle; Wingman reports these as exit 5 and exit 4, and exit 7 for a pull with no results.
What Wingman never does
Troubleshooting
| What you see | Fix |
|---|---|
ssh could not reach or log in to the device: Host key verification failed. (exit 3) | The device’s host key is not in your known_hosts. Run ssh <alias> once in the Build terminal, check the fingerprint with the device owner, and accept it. Wingman never accepts a host key for you. |
ssh could not reach or log in to the device: Permission denied (publickey). (exit 3) | Your key is not authorized, or the login needs a password or second factor. Run wingman target probe <name> --interactive once in the Build terminal. |
no ssh client found on PATH; install OpenSSH (exit 3) | Install the OpenSSH client on this machine. |
ssh control directory <path> must be mode 0700; fix it or remove it (exit 3) | Run chmod 700 <path> or remove the folder; the tool recreates it. |
no target record at <path>; run: wingman target add <name> --ssh <alias> (exit 2) | Add the target, or run from the workspace that holds .wingman/targets/, or pass --workspace. |
an offline target has no ssh; use wingman target export and import (exit 2) | Carry the archive by hand, or add the target again with an ssh alias. |
no bundle manifest; run: wingman target pack (exit 4) | Run wingman target pack --bundle <dir> before push. |
bundle content does not match its manifest; re-run: wingman target pack (exit 4) | A file changed, appeared or disappeared after pack. The error lists mismatched, unlisted and missing files. Pack again; the bundle id changes. |
the device refused push with wingman-gate: refused: id <id> is not listed (exit 4) | The device owner has not listed this bundle id. Send them the id and the bundle for review. |
status reports exit=6 | The model is not in the bundle and the device could not download it. Pack with --artifacts <sdk-build-dir> so build/<model>/ travels with the bundle. |
run --follow ends with exit 6 | The runner exited non-zero. Read results/DEVICE_CHECK.json after pull for the reason. |
no return manifest on the device; has the run finished? (exit 7) | The run is still going or ended before writing results. Check status, then logs. |
the return manifest lists no files; did the run finish? (exit 7) | The runner wrote nothing to return. Read the run log with logs. |
every listed file was left behind on the device (exit 7) | Every returned file was media over the cap. Raise policy.media_pull_max_mb in the record; run passes it to the runner, so media up to that size is kept and returned. |
status reports exit=127 and logs is empty | The device lacks timeout or bash, so the runner never started. Install them on the device, or on the gated tier accept the unbounded fallback. |
the destination holds another bundle (exit 2) | --into names a directory whose manifest hashes to another id. Pull into the directory of the bundle you pushed. |
--out must be outside the bundle directory (exit 2) | Export into a sibling directory, for example <bundle>-dist, never into the bundle. |
the pulled results belong to another bundle id (author check exit 12) | The package was re-packed after the device run, or the results came from another bundle. Pack, push and run again. |
the device’s results belong to another bundle (exit 4) | The stage folder holds results from a different bundle. Pull with the id push printed. |
pulled files do not match the return manifest (exit 4) | A file changed between the manifest and the transfer. Pull again; if it repeats, run the bundle again. |
no local bundle directory is recorded for this id; pass --into DIR (exit 2) | This machine did not push the bundle. Pass --into with the bundle folder. |
these results belong to another bundle (exit 4) | import was given results from a different bundle. Import them into the bundle they came from. |
the device check or the author check did not pass (exit 6) | results/IMPORT_SUMMARY.json still records both verdicts. Read results/DEVICE_CHECK.json and results/author-check.log. |
Gate refusals
| wingman-gate: refused: | Meaning |
|---|---|
not a wingman-target verb, unknown verb <verb>, <verb> takes N argument(s) | The command is outside the grammar. The gate runs nothing else. |
the bundle id must be 64 hex characters | The id is malformed. |
id <id> is not listed, id <id> is no longer listed | The owner has not listed the id, or removed it. |
id <id> is already staged, id <id> is not staged | Push once per id; run only what was pushed. |
the stream is not a tar archive, the archive did not extract | The push stream was damaged. |
a member is not a regular file or directory, member <name> escapes the stage directory | The archive held a link, a device or a path outside the stage folder. |
no bundle manifest, manifest digest <hex> is not the listed id | The manifest is missing or is not the one the owner listed. |
the digest list does not match the manifest’s digest_list, malformed digest list line | The digest list was replaced or damaged. |
listed file <path> is missing (mismatch), <path> does not match its digest (mismatch), an unlisted or duplicated file is in the bundle | The bundle on the device is not the bundle that was listed. |
id <id> is already running | Wait for the run, or stop it. |
the run maximum must be a positive number of seconds, the log offset must be a number of bytes | An argument is out of range. |
id <id> has no results | Nothing to pull yet. |
no pinned probe is installed | The gate line has no fourth argument, or the file is missing. |