Commands & Actions
Every command type a node can execute and every action remote.control exposes, with parameters, responses, scopes, and risk.
A node implements 37 command types. Twenty-eight of them are exposed to an agent as actions it can put in a Control Run; the rest are driven by other parts of the platform.
Action names and command types are not always the same word. mouse.click travels as input.mouse.click. Actions are what an agent sends; command types are what the node receives, what a scope gates, and what the audit log records. See Actions and command types.
Action and command-type map
| Category | Action | Command type | Scope | Risk |
|---|---|---|---|---|
| Screen | screen.capture | screen.capture | screen.capture | Low |
| Screen | screen.region | screen.region | screen.capture | Low |
| Screen | screen.monitors | screen.monitors | screen.capture | None |
| Screen | screen.queryElements | screen.queryElements | screen.capture | None |
| Screen | screen.context | screen.context | screen.capture | None |
| Mouse | mouse.move | input.mouse.move | input.control | Low |
| Mouse | mouse.click | input.mouse.click | input.control | Medium |
| Mouse | mouse.scroll | input.mouse.scroll | input.control | Medium |
| Keyboard | keyboard.type | input.keyboard.type | input.control | Medium |
| Keyboard | keyboard.press | input.keyboard.press | input.control | Medium |
| Keyboard | keyboard.hotkey | input.keyboard.hotkey | input.control | Medium |
| Applications | app.list | app.list | app.manage | None |
| Applications | app.launch | app.launch | app.manage | High |
| Applications | app.close | app.close | app.manage | High |
| Applications | app.focus | app.focus | app.manage | High |
| Windows | window.list | window.list | window.manage | None |
| Windows | window.move | window.move | window.manage | Medium |
| Windows | window.minimize | window.minimize | window.manage | Medium |
| Windows | window.maximize | window.maximize | window.manage | Medium |
| Clipboard | clipboard.read | clipboard.read | clipboard.access | Low |
| Clipboard | clipboard.write | clipboard.write | clipboard.access | Medium |
| Files | files.list | filesystem.list | filesystem.access | None |
| Files | files.read | filesystem.read | filesystem.access | Low |
| Files | files.write | filesystem.write | filesystem.access | High |
| System | system.info | system.info | system.info | None |
| System | system.processes | system.processes | system.info | None |
| System | shell.execute | system.execute | system.execute | Critical |
| Notifications | notify.show | notify.show | notify.send | Medium |
Risk decides what gets asked. None and Low run inside the scope you granted; Medium is visible in the run; High always raises an approval; Critical raises one that no run-level consent can pre-cover. See Approval Gates.
Command types with no action
Nine command types exist that an agent cannot put in a run. They are driven by other parts of the platform instead:
| Command type | Driven by |
|---|---|
canvas.present | The node.present_canvas tool. See Canvas on a Node |
activity.query | The desktop.activity tool, and the Dashboard's activity timeline. See Desktop Awareness |
activity.settings | The Dashboard's activity settings panel, which reads and writes the recorder's own configuration |
activity.screenshot | The timeline's inline screenshots, and desktop.activity's screenshotFor parameter |
activity.delete | Per-entry delete in the timeline, which removes the entry's saved image along with it |
voice.session.start, voice.session.stop, voice.session.interrupt, voice.output.speak | The voice runtime and the CLI |
All four activity.* command types are gated by the single activity.read scope. There is no activity.read command.
Screen
screen.capture
Captures the full primary display.
Scope: screen.capture · Risk: Low
| Param | Type | Default | Notes |
|---|---|---|---|
quality | int | 80 | JPEG quality (1–100) |
maxWidth | int | none | Optional max width; aspect ratio preserved |
Response: { image: "<base64-jpeg>", sizeBytes, format: "jpeg" }, plus the origin and size of the screen region it came from so image coordinates can be scaled back to real pixels. That geometry is emitted on Windows only. See Platform Support & Limits.
screen.region
Captures a rectangular region.
Scope: screen.capture · Risk: Low
| Param | Type | Default | Notes |
|---|---|---|---|
x, y, width, height | int | required | Region bounds |
quality | int | 80 | JPEG quality |
Response: { image, sizeBytes, format, region: { x, y, width, height } }
screen.monitors
Lists the attached displays and their geometry.
Scope: screen.capture · Risk: None
Response: { count, monitors: [ { id, isPrimary, x, y, width, height, scaleFactor, name } ] }
screen.queryElements
Returns on-screen UI elements, for automation that names controls instead of guessing at pixels.
Scope: screen.capture · Risk: None
| Param | Type | Default | Notes |
|---|---|---|---|
includeBackground | bool | false | Include unfocused windows |
maxDepth | int | 8 | Maximum tree depth |
roleFilter | string | none | Only elements of a given role |
nameFilter | string | none | Only elements whose name matches |
Response: { elements: [...], count, focusedWindowTitle, warning? }
Capped at 300 elements; a truncated result comes back with a warning suggesting a narrower filter or a smaller depth. On Windows, password fields are dropped at the source, so the element never leaves the machine at all. Coverage varies sharply by platform: full element access inside windows on Windows, shallower on macOS, top-level windows only on Linux under X11, and nothing under Wayland.
screen.context
Reports what the desktop is doing right now, without taking a picture of it.
Scope: screen.capture · Risk: None
Response: { foregroundApp, activeWindow, focusedElement: { role, name, value }, monitorCount, capturedAt, warning? }
Best-effort: if something cannot be determined it returns a result with a warning rather than failing.
This is on demand. The agent asks for screen.context as an action inside a run when it needs to know what is in front of the user. It is not handed a desktop context block on every turn: that is on the roadmap and does not ship in this release. See Desktop Awareness.
Mouse
input.mouse.move
Scope: input.control · Risk: Low
| Param | Type | Default | Notes |
|---|---|---|---|
x, y | int | required | Target coordinates |
input.mouse.click
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
x, y | int | required | Click coordinates |
button | string | "left" | "left", "right", "middle" |
doubleClick | bool | false | Double-click |
input.mouse.scroll
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
x, y | int | required | Scroll anchor |
deltaX, deltaY | int | 0 | Scroll amount in pixels |
Keyboard
input.keyboard.type
Types a string character by character.
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
text | string | required | Text to type |
input.keyboard.press
Presses a single named key.
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
keys | string | required | enter, escape, tab, f5, backspace, delete, home, end, pageup, pagedown, arrows |
input.keyboard.hotkey
Modifier combinations.
Scope: input.control · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
keys | string | required | "ctrl+c", "alt+f4", "cmd+space", "win+d", "ctrl+shift+t" |
The parameter is keys, not key, for both of the above.
Applications
app.launch
Scope: app.manage · Risk: High
| Param | Type | Default | Notes |
|---|---|---|---|
name | string | required | App name (chrome, notepad) or a full path |
args | string[] | none | Command-line arguments |
Response: { pid, app }
app.close
Closes gracefully, then forces after a few seconds.
Scope: app.manage · Risk: High
| Param | Type | Default | Notes |
|---|---|---|---|
name | string | either | App name |
pid | int | either | Process ID |
app.focus
Brings a window to the foreground.
Scope: app.manage · Risk: High
| Param | Type | Default | Notes |
|---|---|---|---|
name | string | either | App name |
pid | int | either | Process ID |
app.list
Scope: app.manage · Risk: None
Response: { count, apps: [ { name, pid, windowTitle } ] }
Windows
window.list
Scope: window.manage · Risk: None
{
"count": 3,
"windows": [
{ "id": "0x00010234", "title": "Untitled - Notepad",
"x": 100, "y": 100, "width": 800, "height": 600,
"processName": "notepad.exe" }
]
}window.move
Scope: window.manage · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
windowId | string | required | From window.list |
x, y | int | required | New position |
width, height | int | current | Resize as well |
window.minimize, window.maximize
Scope: window.manage · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
windowId | string | required | From window.list |
Clipboard
clipboard.read
Scope: clipboard.access · Risk: Low
Response: { text, length }
clipboard.write
Scope: clipboard.access · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
text | string | required | Text to write |
Files
filesystem.list, filesystem.read, and filesystem.write are documented with their containment rules and size caps in File Access, because the parameters make little sense without the roots that bound them.
The scope is not granted by default, and an empty roots list denies everything.
System
system.info
Scope: system.info · Risk: None
{
"osDescription": "Microsoft Windows 11.0.26200",
"machineName": "DESKTOP-ABC",
"processorCount": 8,
"totalMemoryMb": 16384,
"uptime": 3600,
"platform": "windows",
"architecture": "x64"
}system.processes
Scope: system.info · Risk: None
Response: { count, processes: [ { pid, name, cpuPercent, memoryMb } ] }
system.execute
Runs a shell command. Always approval-gated, whatever the scope configuration says.
Scope: system.execute · Risk: Critical
| Param | Type | Default | Notes |
|---|---|---|---|
command | string | required | The complete command line, as one string |
timeoutMs | int | none | Execution timeout |
Runs through the platform shell: cmd.exe /c on Windows, /bin/zsh -c on macOS, /bin/bash -c on Linux. Response: { exitCode, stdout, stderr, durationMs }, with each stream truncated at 1 MB.
On Windows a timed-out or cancelled command has its entire process tree killed, so a hung command does not leave children behind.
There is no args parameter. The complete command must be in command, so that approval, the denylist, the audit record, and execution are all reviewing the same text. A separate argument list recombined later is exactly where the reviewed command and the executed one drift apart.
Notifications
notify.show
Scope: notify.send · Risk: Medium
| Param | Type | Default | Notes |
|---|---|---|---|
title | string | "Notification" | Notification title |
body | string | "" | Notification body |
icon | string | none | Optional icon path |
Canvas
canvas.present opens the session's live Canvas on the node's own screen. It is consent-gated on the device by default and is documented, with its parameters, in Canvas on a Node.
Activity
Four command types serve the node's local activity history. All four are gated by the activity.read scope, which is not granted on pairing, and none of them returns anything useful until recording has been enabled for that device.
activity.queryreads back entries within a bounded time window. An entry that has a saved screenshot is reported as having one, and that image can then be viewed:desktop.activity'sscreenshotForparameter fetches a single moment throughactivity.screenshot. The image is put in front of the model itself only on Claude models reached through the Anthropic API or a connected Claude subscription. On every other provider path — OpenAI-compatible, Gemini, Bedrock, Copilot — the model receives the text result alone.activity.settingsreads the recorder's configuration and, with an update, changes it: on or off, paused, sample interval, retention, both exclusion lists, the screenshot settings, and erasing the history outright. It reports live recorder state alongside the settings, so the Dashboard shows what is actually running rather than what was configured. Changes apply without restarting the node.activity.screenshotreturns the saved image for one entry, by id, one entry per call.activity.deleteremoves named entries from the local history and deletes their screenshots with them.
See Desktop Awareness.
Voice runtime
Four command types let a node speak: voice.session.start, voice.session.stop, voice.session.interrupt, and voice.output.speak. They are gated by the voice.runtime scope, which is not granted by default, and are driven by the voice runtime and the CLI rather than by an agent run.
voice.output.speak reads text aloud through the operating system's own speech synthesiser. Note that these are output only. The node does not listen, and there is no wake word or microphone capture on the node.
The Gateway's node-token voice routes are off until an operator turns them on. Set Sophon:Voice:NodeWake:Enabled, which defaults to off; until it is set, every one of those routes answers as if it did not exist. Nothing shipped calls them today.
A node's reported voice state follows what actually happened rather than what was asked for: speaking reverts to armed or idle when the speech process finishes, is interrupted, or the session is stopped.
On macOS, setting a language used to silence every spoken reply. The node now speaks through the system voice, and a reply is no longer passed to the speech tool as a command-line argument, so text beginning with a dash can neither fail to speak nor be read as an option.
Breaking changes in this release
| Change | Before | Now | What to update |
|---|---|---|---|
| The passthrough tool | node.command tool dispatched single commands | Removed. Use remote.control runs | Any stored prompt, skill, or script naming node.command as a tool |
app.launch parameter | nameOrPath | name | Callers passing nameOrPath |
| Result casing | Some responses leaked PascalCase keys | All results serialize camelCase | Consumers reading window.list, app.list, system.info, screen.monitors, screen.queryElements |
system.execute arguments | Accepted a separate args array | Rejected. The whole command goes in command | Callers splitting a command into arguments |
Errors
Command failures return a structured error with a code. The wire-level list, including busy, cancelled, and node_offline, is in the node protocol. For why a command was refused before it ever ran, see audit outcomes.
Where to go next
- Control Runs — how these actions batch into one approved unit
- Permissions & Scopes — which scopes gate what
- Gates, Limits & Audit — rate limits and consent
- Platform Support & Limits — where each command behaves differently