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

CommandWhat it doesApproval
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 codeMeaning
0Done.
2Usage or record error: a bad argument, a missing or invalid record, an offline target given a remote verb.
3ssh could not reach or log in to the device, or no ssh client was found.
4Bundle refused: digest mismatch, unlisted or missing file, unsafe archive member, results from another bundle, or a gate refusal of the bundle.
5The device refused the command, or the gate refused the command itself.
6The run failed on the device: run --follow saw a non-zero runner exit, or import found a failed device check or author check.
7Pull found nothing: no return manifest, no listed files, or every file left behind.

The target record

FieldMeaning
schema_versionAlways 1.
nameLowercase letters, digits and -, up to 63 characters, starting with a letter or digit.
ssh_aliasA 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.
tieropen, gated or offline.
roles{"run": true, "build": false}. The device runs bundles and does not build them.
stage_rootWhere bundles live on the device: an absolute path or one starting with ~/, never ... Default ~/wingman-stage.
policy.max_run_secondsThe longest a run may take. Default 3600.
policy.media_pull_max_mbMedia files larger than this stay on the device at pull. Default 200.
policy.sudoAlways false. A record that sets it is refused.
profileThe sanitized probe report plus host: arch, os_release, libc, python, disk_free_mb, mem_total_mb, root_readonly, device_node_ok, sshd.
profiled_atWhen probe last ran.

Files Wingman writes

PathWritten byHolds
.wingman/targets/<name>.jsonadd, probeThe target record. No secrets, so it can live in git.
.wingman/targets/<name>.audit.jsonlEvery commandOne line per call: time, verb, bundle id, exit code and the arguments. Never the streamed bytes.
.wingman/targets/<name>.runs/<id>.jsonpush, run, stop, pullThe local bundle folder and the push, start, stop and pull times.
WINGMAN_BUNDLE.sha256, WINGMAN_BUNDLE.jsonpackThe digest list and the manifest, in the bundle folder.
results/PULL_SUMMARY.jsonpullThe device check verdict, the file count and the media left behind.
results/IMPORT_SUMMARY.json, results/author-check.logimportThe device and author check verdicts, and the author check’s log.
<stage>/.wingman/axelera_probe.pyprobe (open tier)The probe script, on the device.
<stage>/<id>/push, then the runnerThe bundle, run.log, run.pid, run.exit and results/, on the device. The gate also writes run.cmd.

Tiers

TierHow commands reach the deviceWhat a security reviewer gets
openPlain 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.
gatedAn 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.
offlineNone. 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

VerbOpen tier commandGated tier command
probemkdir -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 scriptwingman-target probe
pushmkdir -p S/ID && tar -x -C S/ID (the bundle on stdin)wingman-target push ID
runcd 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.pidwingman-target run ID MAX CAP
statuscd S/ID, then exit=N from run.exit, running while run.pid is alive, else unknownwingman-target status ID
logstail -c +OFFSET+1 S/ID/run.logwingman-target logs ID OFFSET
stopkill -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.pidwingman-target stop ID
pullcat S/ID/results/RETURN_MANIFEST.json, then tar -c -C S/ID --exclude=<media over the cap and media the runner left behind> resultswingman-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 modeprobe, push, run, stopEverything else
AskA card every time.A card every time, as for any command.
AutoA 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 accessA 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 exitReason in DEVICE_CHECK.json
3voyager_sdk_not_found
4python_or_sdk_environment_missing
5runtime_or_device_check_failed
6model_artifacts_missing, or model_artifacts_not_in_bundle_offline with WINGMAN_OFFLINE=1
9no_canonical_output
10metis_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

  1. 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 its sha256sum, and copy it to the device, for example to /usr/local/bin/wingman-target-gate.sh.

  2. 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 pack and push print.

  3. 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 of axelera_probe.py (a bundle carries one in runtime/).

    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...
  4. Add the target as gated

    Record the target with wingman target add <name> --ssh <alias> --tier gated. Every call logs one syslog line tagged wingman-gate, such as push <id> admitted, run <id> max=3600, or refused (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 seeFix
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=6The 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 6The 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 emptyThe 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 charactersThe id is malformed.
id <id> is not listed, id <id> is no longer listedThe owner has not listed the id, or removed it.
id <id> is already staged, id <id> is not stagedPush once per id; run only what was pushed.
the stream is not a tar archive, the archive did not extractThe push stream was damaged.
a member is not a regular file or directory, member <name> escapes the stage directoryThe archive held a link, a device or a path outside the stage folder.
no bundle manifest, manifest digest <hex> is not the listed idThe 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 lineThe 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 bundleThe bundle on the device is not the bundle that was listed.
id <id> is already runningWait for the run, or stop it.
the run maximum must be a positive number of seconds, the log offset must be a number of bytesAn argument is out of range.
id <id> has no resultsNothing to pull yet.
no pinned probe is installedThe gate line has no fourth argument, or the file is missing.