Install
openclaw skills install @huaweiclouddev/huawei-cloud-cloudrobo-dispatchManage CloudRobo embodied-agent task dispatch (robo-dispatcher) — create embodied tasks that drive a robot via an exec/model and constraints, list/show tasks in a session, cancel tasks, and retrieve task results. Dispatch orchestrates robots (robot_id from cloudrobo-robot) with inference models (exec_model_id from cloudrobo-infer / cloudrobo-asset) inside a session. Triggers include: dispatch, dispatcher, task dispatch, embodied task, agent task, run task on robot, task scheduling, cancel task, task result, robo-dispatcher, 调度, 智能体调度, 任务下发, 机器人任务, 取消任务, 任务结果, 会话任务.
openclaw skills install @huaweiclouddev/huawei-cloud-cloudrobo-dispatchWindows / PowerShell: Examples use bash syntax. To run on Windows PowerShell:
- Flatten
\line continuations to a single line, or end lines with a backtick.- Set env vars with
$env:NAME="value"instead ofexport NAME="value".- Single-quoted JSON
'{"a":"b"}'works as-is.
The cloudrobo-dispatch skill manages embodied task dispatch via the robo-dispatcher
service. It lets the agent create a task that runs on a robot (identified by robot_id) under
an execution model (exec_model_id) inside a session (session_id), monitor it, cancel it, and
retrieve the natural-language task result plus log items.
Applicable scenarios:
exec_constraints: default max_run_time=10 min, max_iter_num=100 steps)RUNNING without manual
polling (wait-task, the preferred way to wait for a task to finish)cloudrobo-robot) and inference models
(cloudrobo-infer / cloudrobo-asset) into concrete robot-executable tasks.Architecture:
Agent / LLM
│
├── CLI → cloudrobo dispatch <command>
└── SDK → DispatchClient (Python) — domain RoboDispatcherTaskManagement
│
▼
cloudrobo-service (REST API)
/v1/robo-dispatcher/sessions/{session_id}/tasks*
All operations target the cloudrobo-service backend, are scoped to a session_id, and require
a valid workspace_id. A session is a stable execution context; multiple tasks can be created
within one session across time.
session_id: In the current version,
session_idis identical to the current workspace'sworkspace_id. You can obtain it by reading the active workspace (cloudrobo workspace current) or the configuredworkspace_id, and reuse it directly as the--session-id. If a caller explicitly provides a differentsession_id, prefer confirming it against the current workspace.
references/cli-installation-guide.md for CLI installation, AK/SK authentication, and
workspace configuration.session_id for all task operations. In the current version session_id equals the
current workspace's workspace_id — read it via cloudrobo workspace current or the configured
workspace_id (there is no separate session-create API).robot_id (from cloudrobo-robot) and exec_model_id for the constrained model service
that drives the task. The robot must be ONLINE (i.e. its R2C edge client is connected) for a task
to run — otherwise the backend rejects it with failure_reason: "Robot offline".How to obtain
exec_model_id(important) —exec_model_idis the execution-model handle that drives the task. Do not put the raw model/asset id (e.g.8b804b51-…) into it; the backend cannot resolve the asset id and the task will fail.Two kinds of
exec_model_id(verified 2026-08 on this platform):
- User-created inference services (deployed via
cloudrobo infer createincloudrobo-infer):exec_model_idequals the inference service'sservice_id— i.e. theidreturned bycloudrobo infer createand bycloudrobo infer list. Practical derivation: runcloudrobo infer list, pick the service you deployed (e.g.so101-real-demo-0828), and use itsid(<uuid>) directly asexec_model_id. No separate "model-service UUID" lookup is needed for user-deployed services.- Platform prebuilt / external model-services show an
ext_-prefixed opaque handle, e.g.ext_2dcc1539e72040c78650c554102debaf(name e.g.Claude-3.5-Updated) orext_1ee0bcc7…(name e.g.cloudrobo-real-rl-model-…). These are NOT infer-list services; obtain them from an existing task'sconstraints.model.exec_model_id.At any time you can confirm the exact value against an existing successful task in the same session:
cloudrobo dispatch list-tasks --session-id <sid> --content-match "<skill prompt>"and read itsconstraints.model.exec_model_id(the returnedexec_model_nameequals the source service name).
Every workflow below starts from a user intent (1-2 sentences), not from manual CLI/SDK orchestration. The skill then drives the matching command chain and reports state feedback.
Scenario: "Tell robot A to go pick up the red cube and place it in the bin."
session_id from the current workspace: in the current version
session_id == workspace_id (obtain via cloudrobo workspace current / configured workspace_id).robot_id (from robot registration) and exec_model_id.
exec_model_id is the execution-model handle that drives the task — for a service you deployed,
it is simply that service's service_id (see the "How to resolve exec_model_id" note below). Both
are required in constraints.Robot offline failure reason.
See cloudrobo-r2c to bring a (dummy/real) robot online before dispatching.task text (e.g. "pick up the red cube and
place it in the bin") and a name. If the inference service was deployed with strict:true,
the task text MUST match the predefined skill prompt; otherwise the service rejects it. (See
cloudrobo-infer for deploying a service with skill_config.strict.)cloudrobo dispatch create-task --session-id <sid> --name <name> --task "<task>" --constraints-json '<json>'. The --constraints-json is required and carries model,
robot_id, and the stop condition exec_constraints. Always set a stop condition
(max_run_time and max_iter_num) so the task cannot run unboundedly. Defaults if the user does
not specify: max_run_time=10 (minutes), max_iter_num=100 (steps).
Confirm before submitting (mutating).cloudrobo dispatch wait-task --session-id <sid> --task-id <task_id> [--timeout <secs>].
This polls internally every 5s and returns once the task status leaves RUNNING
(i.e. reaches COMPLETED/FAILED/CANCELLED or any non-RUNNING state). Prefer wait-task
over manual show-task polling — it replaces the old 20-30s manual polling loop.cloudrobo dispatch show-task-result --session-id <sid> --task-id <task_id> to read the natural-language result and log items.Note:
robot_idandexec_model_idare required insideconstraints. Do not hardcode them; resolve from robot and infer/asset outputs.
How to resolve
exec_model_id—exec_model_idis NOT the model asset ID (the8b804b51-...-style asset UUID fromcloudrobo-asset). It is the execution-model handle the task drives. For a service you deployed yourself viacloudrobo infer create,exec_model_idequals that service'sservice_id(read it fromcloudrobo infer list/ thecreateresponse). Platform prebuilt / external model-services use anext_-prefixed opaque handle instead. Resolve it by:
- If the target model is one of your deployed inference services →
cloudrobo infer list, take the serviceidasexec_model_id(verified equivalent on 2026-08).- Otherwise, confirm the exact value against an existing successful task in the same session:
cloudrobo dispatch list-tasks --session-id <sid> --content-match "<deployed skill prompt>"and read itsconstraints.model.exec_model_id(exec_model_nameis the source service name, informational). Using the raw asset UUID inexec_model_idis a common root cause of dispatch failures.
Scenario: "What tasks are running in my session?"
cloudrobo dispatch list-tasks --session-id <sid> [--status <status>] [--robot-id <rid>] [--infer-service-id <iid>] [--start-time <ms>] [--end-time <ms>] [--content-match <text>]
with pagination (--limit/--offset) and sorting (--sort-key/--sort-dir).cloudrobo dispatch show-task --session-id <sid> --task-id <task_id> for detail.Scenario: "Show me the outcome of that pick-and-place task."
wait-task (or confirm it is terminal via show-task).cloudrobo dispatch show-task-result --session-id <sid> --task-id <task_id> — returns the
task object plus log_items. Supports --inverse, --limit, --offset for pagination.Scenario: "Abort that task — the robot picked the wrong object."
show-task.cloudrobo dispatch cancel-task --session-id <sid> --task-id <task_id> (mutating; confirm).show-task that the task moved to a cancelled state.session_id. In the current version session_id is the same as
the current workspace's workspace_id; obtain it from cloudrobo workspace current. You may
create many tasks within one session over time.constraints.exec_constraints so it stops
on time — see the Create a Task section for the required object shape and defaults.skill_config.strict:true,
the task you pass MUST match one of its predefined skill prompts; a mismatched prompt is rejected.create-session / exec_task / create-session-task interfaces are deprecated
and must not be used. All operations use session_id directly on the task endpoints.cloudrobo dispatch <command> [OPTIONS]
| Feature | Description |
|---|---|
| Command group | dispatch |
| Subcommand | kebab-case: create-task, list-tasks, show-task, cancel-task, show-task-result, wait-task |
| Session | --session-id <id> (all task operations; equals workspace_id in current version) |
| JSON params | --constraints-json '<object>' (create-task; required; holds model/robot_id/exec_constraints) |
| Dry-run | --dry-run (create-task/cancel-task) |
| Result pagination | --inverse, --limit, --offset (show-task-result) |
| Wait timeout | --timeout <secs> (wait-task; default 600, IntRange 1–3600) |
Full coverage: SDK exposes 6 methods, CLI exposes 6 commands (0 gaps). See
references/cli-installation-guide.mdand the acceptance criteria for the coverage mapping.
SDK Direct Calls: When CLI is inconvenient (dynamic JSON, cross-package queries), use the Python SDK directly.
DispatchClient(domain RoboDispatcherTaskManagement) exposes the 6 methods below.
cloudrobo dispatch create-task --session-id <session-id> --name <task-name> --task "<natural language task>" --constraints-json '{"model":{"exec_model_id":"<model-id>"},"robot_id":"<robot-id>","exec_constraints":{"max_run_time":10,"max_iter_num":100}}' [--dry-run]
Stop condition is required in practice (
constraints.exec_constraints). If the user does not specify values, default tomax_run_time=10(minutes) andmax_iter_num=100(steps) to avoid unbounded/long-running debug tasks. Valid ranges:max_run_time1–300 minutes,max_iter_num1–300000 steps.
modelobject shape (important) — increate-task'sconstraints.model, only includeexec_model_id. Do not addexec_model_name: the create API rejects it with400 Invalid parameter: exec_model_name.exec_model_nameis a response-only field (present inshow-task/list-tasks/show-task-resultoutput), never a request field. See the "How to resolveexec_model_id" note for whatexec_model_idshould be.
from cloudrobo_core.sdk import Config, HttpClient
from cloudrobo_dispatch import DispatchClient
config = Config()
http_client = HttpClient(config)
client = DispatchClient(http_client)
# session_id equals the current workspace_id in the current version
req = {
"name": "pick-red-cube",
"task": "pick up the red cube and place it in the bin",
"constraints": {
"model": {"exec_model_id": "<mid>"},
"robot_id": "<robot-id>",
# stop condition: use the given values, else default max_run_time=10, max_iter_num=100
"exec_constraints": {"max_run_time": 10, "max_iter_num": 100},
},
}
task = client.create_dispatcher_task("<session-id>", req)
print(task) # includes task_id
cloudrobo dispatch list-tasks --session-id <sid> [--status <status>] [--limit 20] [--offset 0]
tasks = client.list_dispatcher_tasks("<session-id>", status="RUNNING")
for t in tasks.get("items", []):
print(t["id"], t["status"], t["name"])
cloudrobo dispatch show-task --session-id <sid> --task-id <task-id>
task = client.show_dispatcher_task("<session-id>", "<task-id>")
print(task["name"], task["status"], task["robot_id"])
cloudrobo dispatch cancel-task --session-id <sid> --task-id <task-id> [--dry-run]
client.cancel_dispatcher_task("<session-id>", "<task-id>")
cloudrobo dispatch show-task-result --session-id <sid> --task-id <task-id> [--inverse] [--limit 100] [--offset 0]
result = client.show_dispatcher_task_result("<session-id>", "<task-id>")
print(result["task"]["result"])
for item in result.get("log_items", []):
print(item)
Purpose: Block until a dispatched task finishes (its status leaves RUNNING), so you do not
have to manually poll show-task in a loop. This is the preferred way to wait for a task in
non-interactive / automation scenarios.
cloudrobo dispatch wait-task --session-id <sid> --task-id <task-id> [--timeout <seconds>]
result = client.wait_dispatcher_task("<session-id>", "<task-id>", timeout=600)
# result is the full task dict once status != "RUNNING" (e.g. COMPLETED / FAILED / CANCELLED)
status = (result.get("task") or {}).get("status")
Behavior (authoritative, from source):
- Method:
wait_dispatcher_task(session_id, task_id, timeout=600).- Polling: every 5 seconds (
POLL_INTERVAL = 5); it callsshow_dispatcher_taskinternally (GET /v1/robo-dispatcher/sessions/{session_id}/tasks/{task_id}).- Return condition: returns the task dict as soon as
data["task"]["status"] != "RUNNING"— regardless of whether it reachedCOMPLETED,FAILED,CANCELLED, or another non-RUNNINGstate. Terminal states:COMPLETED,FAILED,CANCELLED.- Default timeout: 600 seconds.
--timeoutis anIntRange(1, 3600)— max 3600 s (1 hour).- On timeout: raises
TimeoutError(client); the CLI prints[ERROR] <msg>to stderr and exits with code 1. Always check the return status to distinguish success, failure, and timeout.- No independent REST API:
wait-taskis a client-side polling helper — it has no dedicated backend endpoint and simply wrapsshow-task.- Must create the task first:
wait-taskdoes not create a task. Callcreate-taskto get thetask_idfirst, thenwait-taskon it.
Recommendation (when to use wait-task):
wait-task right after create-task, then fetch the outcome with show-task-result — this
replaces the old manual show-task polling every 20-30s.show-task to inspect progress — but use wait-task when you simply need to wait for the outcome.| Parameter | Source | Required | Confirmation Needed |
|---|---|---|---|
--session-id | Current workspace (workspace_id) | Yes (all) | In current version session_id == workspace_id; verify against cloudrobo workspace current |
--task | User | Yes (create) | Natural-language task description; if strict service, must match predefined skill prompt |
--name | User | Yes (create) | Task name |
--constraints-json | User/derived | Yes (create) | JSON object {model, robot_id, exec_constraints}; holds stop condition |
exec_constraints (in --constraints-json) | User, else default | Yes in practice (create) | Default max_run_time=10 (min), max_iter_num=100 (steps); ranges 1–300 / 1–300000 |
--task-id | User or prior output | Yes (show/cancel/result/wait) | Verify before cancel; must exist (from create-task) before wait-task |
--timeout | Derivable | No (wait) | wait-task timeout in seconds; default 600, IntRange 1–3600 |
--dry-run | — | No | Preview without executing |
Mutating operations (create-task / cancel-task) must prompt the user for confirmation before execution.
| Scenario | Handling |
|---|---|
Missing session_id | In current version session_id == workspace_id; obtain from cloudrobo workspace current |
Missing robot_id / exec_model_id | create-task requires both inside constraints; resolve from robot and the deployed model service (not the asset id) |
exec_model_id is wrong (asset id used) | For a service you deployed, exec_model_id equals that service's service_id (read from cloudrobo infer list); using the raw asset id (8b804b51-...) is a common failure. Prebuilt/external model-services use an ext_-prefixed handle. Verify against an existing successful task via list-tasks --content-match "<prompt>" |
exec_model_name passed in create request | Do not include exec_model_name in constraints.model — the create API returns 400 Invalid parameter: exec_model_name. exec_model_name is response-only; drop it and retry with only exec_model_id |
| Robot offline / not connected | A task on an offline robot fails with failure_reason: "Robot offline". Bring the robot ONLINE first via cloudrobo-r2c (dummy/real edge client) before dispatching |
| Missing stop condition | create-task should set constraints.exec_constraints; default max_run_time=10, max_iter_num=100 to avoid unbounded tasks |
| Over-long task | Respect ranges: max_run_time 1–300 min, max_iter_num 1–300000 steps; don't exceed to keep debug tasks bounded |
| strict:true service | If the inference service was deployed with skill_config.strict:true, the task MUST match a predefined skill prompt; otherwise rejected |
| Task not found | show/cancel/result return ResourceNotFoundError; verify task_id/session_id |
| Path traversal | validate_safe_id(session_id) / validate_safe_id(task_id) block ../ input |
| Cancelling a finished task | backend rejects; confirm status before cancel |
| Long-running task | Prefer wait-task (polls every 5s, returns once status leaves RUNNING); use show-task only to observe intermediate states, not in a tight manual loop |
wait-task timeout | --timeout max is 3600s (1h); on timeout the CLI prints [ERROR] and exits 1 — raise/inform the user and re-check with show-task |
wait-task on a terminal task | Returns immediately (status already non-RUNNING); safe to call after a task finished |
wait-task needs an existing task | It does not create a task — call create-task first to obtain task_id, or it fails on an invalid/unknown task |
| Natural-language task content | Sanitize/inject-protect; do not echo raw content into logs unescaped |
| AK/SK not set | Operations fail at HTTP signing; set HUAWEI_CLOUD_AK/HUAWEI_CLOUD_SK |
| Deprecated interfaces | Do not use old create-session/exec_task/create-session-task/list-sessions |
| session_id / task_id / robot_id / exec_model_id | Never hardcoded; resolved dynamically (session_id from current workspace) |
| Cross-skill invocation | This skill does not call other skills; it consumes robot_id (robot) and exec_model_id (infer/asset) and reports task results |
| Mutating operations | create-task / cancel-task should be confirmed by the user |
bash scripts/test-cli-commands.sh
bash scripts/test-cli-commands.sh
See templates/test-vars.json for the full test case list covering dispatch, monitoring,
wait-for-completion, cancellation, result retrieval, and safety scenarios.
create-task, task appears in list-tasks with correct statusexec_constraints.max_run_time / max_iter_num);
when the user gave none, they default to 10 min / 100 stepssession_id used equals the current workspace's workspace_idstrict:true, the task matched the predefined skill promptwait-task, the command blocked (polling every 5s) and returned only once status was
non-RUNNING; no manual 20-30s polling loop was usedwait-task with an explicit --timeout respects the timeout and reports a clear error on expirywait-task was called with a task_id already created by create-task (it does not create tasks)show-task, detail returns the task with robot/model/statusshow-task-result, natural-language result and log_items are returnedcancel-task, show-task reflects the cancelled state../) is blocked by validate_safe_idcreate-session/exec_task) are not usedsession_id from the current workspace (cloudrobo workspace current; in the
current version session_id == workspace_id), and resolve robot_id / exec_model_id dynamically;
never hardcodeconstraints.exec_constraints) when creating a task; if the user
gives none, use the defaults max_run_time=10 (minutes) and max_iter_num=100 (steps) to keep
debug tasks bounded (respect ranges 1–300 / 1–300000)strict:true, make the task match the
predefined skill promptcreate-task (it triggers real robot action) and cancel-taskcreate-task, wait with wait-task instead of manually polling show-task — it blocks
(polling every 5s) until the status leaves RUNNING, then fetch the outcome with show-task-result.
Use show-task only to inspect intermediate states when needed. In an agent setting, run wait-task
as a background process and wait for it to return (it exits on its own once the task is
terminal), rather than sleeping + show-task in a manual loop.--timeout on wait-task when the task may run long; on timeout, re-check state
with show-task and report the outcome rather than retrying blindlyconstraints.model only takes exec_model_id — never pass exec_model_name in the create
request (400 error); it is a response-only fieldcloudrobo infer create), exec_model_id = that service's
service_id (see cloudrobo infer list); only platform prebuilt/external model-services use
ext_-prefixed handles. Prefer cloudrobo infer list over guessing when resolving it--dry-run on create-task/cancel-task to validate params before actinglist-tasks filters (--status, --robot-id, --infer-service-id, --content-match)
to quickly locate taskstask content; do not echo raw content into logs unescapedcloudrobo robot list/show) and infer (cloudrobo infer list) skills to
resolve robot/model IDs needed for task creation