DK Studio/Documentation
ProductChangelog

Command Reference

A command is a unit of work sent from the studio to an agent. The contract is exact: what each command takes, what it returns, and how its state moves.

The envelope

Every command travels inside the same message envelope:

{
  "v": 1,
  "type": "command",
  "kind": "request",
  "id": "0a3b…",
  "timestamp": "2026-10-21T08:14:22.101Z",
  "payload": {
    "command": "shell",
    "args": { "script": "uname -a", "cwd": "/tmp" },
    "timeout": 300000
  }
}
FieldMeaning
vThe protocol version. 3.
typeAlways command for a command.
kindrequest for the command the studio sends, response for the agent's answer.
idThe command's id β€” the key you read the result back with. It is the same value the studio stores in agent_commands.id.
timestampISO 8601, when the message was created.
payload.commandWhich command to run.
payload.argsThe command's arguments.
payload.timeoutOptional overall timeout, in milliseconds.

Command status

pending  ──▢  in_progress  ──▢  completed
StatusMeaning
pendingThe studio has accepted the command and written it to agent_commands. Nobody has run it yet.
in_progressThe agent has claimed it and is executing it.
completedThe result is stored.

The status lives in the database, not in memory, so both delivery paths β€” the socket and the polling fallback β€” write the same rows and the same states.

Nothing expires a command

There is no server-side timeout and no cancellation. A command that was accepted but never claimed stays pending until an agent picks it up.

Result envelope

The agent answers with the same envelope, kind set to response and the command's id:

{
  "v": 1,
  "type": "command",
  "kind": "response",
  "id": "0a3b…",
  "timestamp": "2026-10-21T08:14:22.229Z",
  "payload": {
    "ok": true,
    "data": {
      "status": "success",
      "output": {
        "stdout": "Linux bench-01 …",
        "stderr": "",
        "exitCode": 0,
        "durationMs": 128
      }
    }
  }
}
FieldMeaning
payload.okWhether the command succeeded.
payload.data.statussuccess, error or timeout.
payload.data.output.stdoutThe standard output.
payload.data.output.stderrThe standard error.
payload.data.output.exitCodeThe process exit code.
payload.data.output.durationMsHow long the command took.
payload.error.codePresent when ok is false.
payload.error.messageThe human-readable error.

A command that runs but ends badly β€” a non-zero exit code, say β€” still answers with ok: true; the failure shows up as data.status: "error". An ok: false answer means the command could not be run at all, and carries an error object instead of data:

{
  "v": 1,
  "type": "command",
  "kind": "response",
  "id": "0a3b…",
  "timestamp": "…",
  "payload": {
    "ok": false,
    "error": { "code": "MISSING_SCRIPT", "message": "No script provided" }
  }
}

The studio stores the whole result β€” including the error β€” as JSON.

Error codes

CodeRaised byMeaning
UNKNOWN_COMMANDAny unrecognised command nameThe agent does not know the command. The message is Unknown command: <name>.
MISSING_SCRIPTshellNo script argument was supplied.
MISSING_PATHopen_serial, close_serialNo path argument was supplied.
MISSING_ARGSserial_writepath or data was missing.
NOT_IMPLEMENTEDrun_test_suite, upload_artifactThe command is part of the contract but the agent does not implement it.
EXECUTION_ERRORAny commandThe handler threw. The message is the exception text.
COMMAND_TIMEOUTAny commandThe command exceeded its timeout.

The same set is produced by both delivery paths β€” the socket and the polling fallback β€” because both call the same handlers.

Timeout and spawn failures

Two failures are reported through the output rather than through the result's status.

FailureResult
The command exceeded its timeoutstatus is timeout, output.exitCode is -2, and output.stderr reads Command timed out after <ms>ms. The code COMMAND_TIMEOUT names the same condition.
The process could not be spawnedstatus is error, payload.error.code is EXECUTION_ERROR, and output.exitCode is -1.

The commands

CommandArgumentsWhat it does
shellscript, cwdRuns a script on the device under test.
get_system_infononeReturns a snapshot of the machine.
get_serial_portsnoneLists the serial ports on the machine.
open_serialpath, baudRateOpens a serial port on the agent.
close_serialpathCloses a port the agent has open.
serial_writepath, dataWrites to a port the agent has open.
run_test_suitesuiteId, projectId, deviceIdRuns a test suite on the device under test.
upload_artifactfilePathUploads an artifact from the bench to the studio.
screen_watchplatform, deviceId, enabled, intervalMsStarts or stops the screen watch on a device.

The argument list of each command is a contract, not a convention: the studio's Agents console builds its form from it, and the agent's local command test builds the same form from it.

shell

Runs a shell script on the machine under test.

ArgumentTypeDefaultMeaning
scriptstringβ€”The script body. Required.
cwdstring/tmpWhere the script file is written and executed.

The script's timeout is the envelope's payload.timeout, in milliseconds. It defaults to 300000.

The agent writes the script to <cwd>/.agent_script_<timestamp>.sh, makes it executable and spawns /bin/bash on it with cwd as the working directory.

Exit codeMeaning
0Success.
otherThe script's own failure code.
-1The process could not be spawned.
-2The command hit its timeout and was killed with SIGTERM.

On timeout, stderr reads Command timed out after <ms>ms.

{
  "command": "shell",
  "args": {
    "script": "#!/bin/bash\necho hello",
    "cwd": "/tmp"
  },
  "timeout": 300000
}

get_system_info

Takes no arguments. Returns a snapshot of the machine.

{
  "command": "get_system_info",
  "args": {}
}
The fields are inside `output.stdout`

The result has no top-level system fields. output.stdout holds a JSON string, so a consumer has to parse it a second time.

{
  "ok": true,
  "data": {
    "status": "success",
    "output": {
      "stdout": "{\n  \"hostname\": \"bench-01\",\n  \"platform\": \"linux\",\n  \"arch\": \"x64\",\n  \"cpuCount\": 8,\n  \"totalMemory\": 16654786560,\n  \"freeMemory\": 9123409920,\n  \"uptime\": 864021,\n  \"loadAverage\": [0.4, 0.3, 0.2],\n  \"networkInterfaces\": []\n}",
      "durationMs": 0
    }
  }
}

The parsed object holds hostname, platform, arch, cpuCount, totalMemory, freeMemory, uptime, loadAverage and networkInterfaces.

get_serial_ports

Takes no arguments. Returns the serial ports available on the machine, each with its path, manufacturer, serialNumber, vendorId and productId where the platform reports them.

Like get_system_info, the answer is a JSON string in output.stdout β€” an array of port objects, or [] when the serialport native bindings are missing.

open_serial

Opens a serial port on the agent.

ArgumentTypeDefaultMeaning
pathstringβ€”The port path, e.g. /dev/ttyUSB0 or COM3. Required.
baudRatenumber115200The baud rate to open at.
{
  "command": "open_serial",
  "args": { "path": "/dev/ttyUSB0", "baudRate": 115200 }
}
The socket path carries two arguments

path and baudRate are the whole contract of this command. dataBits, stopBits and parity exist only on the agent's own local HTTP surface, POST /api/serial, which runs on the machine under test and is not reachable from the studio.

The local endpoint takes them:

{ "action": "open", "path": "/dev/ttyUSB0", "baudRate": 115200, "dataBits": 8, "stopBits": 1, "parity": "none" }

close_serial

Closes a port the agent has open. Takes path.

serial_write

Writes to a port the agent already has open.

ArgumentTypeDefaultMeaning
pathstringβ€”The port to write to. Required.
datastringβ€”The bytes to write. Required.

The result reports whether the write succeeded: stdout reads Data sent to <path> or Failed to send to <path>. A write to a port the agent does not have open is refused and the result is status: "error".

screen_watch

Starts or stops the screen watch on a device under test. While the watch is on, the bench sends a screen_frame event for every frame it takes.

ArgumentTypeDefaultMeaning
platformstringβ€”ios, android or harmonyos. Required.
deviceIdstringβ€”The device to watch. Required.
enabledstringβ€”"true" starts the watch, "false" stops it. Required.
intervalMsnumber1000How often to take a frame, in milliseconds.

The bench takes one frame per device per tick, and skips a tick when the previous capture is still running β€” a slow device ends up at a lower frame rate rather than a growing queue.

The frames themselves travel as screen_frame events, not as the result of this command. The command's own result says only whether the watch was started or stopped.

run_test_suite

Runs a test suite on the device under test. This is the command the platform's test runner issues when it executes a suite.

ArgumentTypeDefaultMeaning
suiteIdstringβ€”The suite to run. Required.
projectIdstringβ€”The project the run belongs to. Required.
deviceIdstringβ€”The device the suite runs on. The platform checks that this device can automate before it hands the task over; if it cannot, the platform prepares the device and the task goes out when the device is ready.

The result is a normal command result: status, the output of the runner, and β€” on failure β€” an error with a code and a message.

upload_artifact

Uploads an artifact from the bench to the studio.

ArgumentTypeDefaultMeaning
filePathstringβ€”The absolute path of the file on the device under test. Required.

The file is read on the agent and transferred to the studio, where it is kept with the artifacts of the project. The result is a normal command result.

See also