Navigation Patterns
Purpose
Show how agents compose open_app, open_uri, wait_for_navigation, and snapshot_ui into deterministic navigation workflows.
This page focuses on composition. For full per-action parameter rules, use Actions.
Sources
- Builders:
apps/node/src/domain/actions/openApp.ts,apps/node/src/domain/actions/openUri.ts,apps/node/src/domain/actions/waitForNav.ts - Validation:
apps/node/src/domain/executions/validateExecution.ts - Shared contract:
apps/node/src/contracts/execution.ts - Limits:
apps/node/src/contracts/limits.ts - CLI command surface:
apps/node/src/cli/registry.ts - Selector parsing:
apps/node/src/cli/selectorFlags.ts
Why Compose Navigation
Neither open_app nor open_uri proves that the target screen is ready. In current Clawperator, deterministic navigation usually means:
- trigger navigation
- wait for the expected package or expected node
- observe the resulting screen
The normal three-step shape is:
open_apporopen_uriwait_for_navigationsnapshot_ui
Verification pattern - preview a composed navigation payload without dispatching:
clawperator exec --dry-run --execution '{"commandId":"settings-nav-1","taskId":"settings-nav-1","source":"docs","expectedFormat":"android-ui-automator","timeoutMs":30000,"actions":[{"id":"open","type":"open_app","params":{"applicationId":"com.android.settings"}},{"id":"wait","type":"wait_for_navigation","params":{"expectedPackage":"com.android.settings","timeoutMs":5000}},{"id":"snap","type":"snapshot_ui"}]}'
open_app
open_app requires:
params.applicationId
Accepted raw JSON parameter aliases for open_app:
application_idappapp_idappIdpackagepackage_idpackageId
Validation rule:
- blank or missing
applicationIdfails withEXECUTION_VALIDATION_FAILED
Builder example:
{
"commandId": "open_app_1700000000000",
"taskId": "cli-action-open-app",
"source": "clawperator-cli",
"timeoutMs": 15000,
"expectedFormat": "android-ui-automator",
"actions": [
{
"id": "a1",
"type": "open_app",
"params": {
"applicationId": "com.android.settings"
}
}
]
}
Exact builder literals from buildOpenAppExecution():
taskId: "cli-action-open-app"source: "clawperator-cli"timeoutMs: 15000- action id:
a1
CLI routing pattern:
clawperator open com.android.settings
The open command treats a non-URI target as an app package and dispatches to open_app.
Accepted CLI aliases for the same intent:
clawperator open-app com.android.settingsclawperator open_app com.android.settingsclawperator open --package com.android.settings
Exact classification rule from isOpenCliUriTarget():
openroutes toopen_urionly when the target matches[a-z][a-z0-9+\\-.]*://- otherwise
opentreats the target as an app package and routes toopen_app
Verification:
clawperator open com.android.settings --json
Expected live success shape:
{
"envelope": {
"status": "success",
"stepResults": [
{
"id": "a1",
"actionType": "open_app",
"success": true,
"data": {
"application_id": "com.android.settings"
}
}
]
}
}
If you want to verify the builder shape without dispatching, use the example payload above with clawperator exec --validate-only --execution '<json>'.
What to verify after open_app:
- do not rely on the open action alone
- follow with
wait_for_navigationusingexpectedPackage - then confirm the target screen with
snapshot_uior another read action
Error cases:
- missing or blank
applicationIdin raw JSON:EXECUTION_VALIDATION_FAILED - missing
opentarget on the CLI:MISSING_ARGUMENT - both positional target and
--appon the CLI:EXECUTION_VALIDATION_FAILED
open_uri
open_uri requires:
params.uri
Accepted raw JSON parameter alias for open_uri:
url
Validation rules:
- blank or missing
urifails - maximum length is
2048characters (LIMITS.MAX_URI_LENGTH)
Builder example:
{
"commandId": "open_uri_1700000000000",
"taskId": "cli-action-open-uri",
"source": "clawperator-cli",
"timeoutMs": 15000,
"expectedFormat": "android-ui-automator",
"actions": [
{
"id": "a1",
"type": "open_uri",
"params": {
"uri": "https://clawperator.com"
}
}
]
}
Exact builder literals from buildOpenUriExecution():
taskId: "cli-action-open-uri"source: "clawperator-cli"timeoutMs: 15000- action id:
a1
CLI routing pattern:
clawperator open https://clawperator.com
The open command uses isOpenCliUriTarget() and routes to open_uri when the target matches a URI-with-scheme pattern.
Accepted CLI aliases for the same intent:
clawperator open --url https://clawperator.comclawperator open --uri https://clawperator.com
Verification:
clawperator open https://clawperator.com --json
Expected live success shape:
{
"envelope": {
"status": "success",
"stepResults": [
{
"id": "a1",
"actionType": "open_uri",
"success": true,
"data": {
"uri": "https://clawperator.com"
}
}
]
}
}
If you want to verify the builder shape without dispatching, use the example payload above with clawperator exec --validate-only --execution '<json>'.
What Node validates versus what it does not:
- Node validates presence and max length
- Node does not enforce a URI scheme whitelist in
validateExecution.ts
What to verify after open_uri:
- use
wait_for_navigationif you expect a package or visible node - use
snapshot_uito confirm the actual screen reached
Error cases:
- blank or missing URI in raw JSON:
EXECUTION_VALIDATION_FAILED - URI longer than
2048characters:EXECUTION_VALIDATION_FAILED - missing
opentarget on the CLI:MISSING_ARGUMENT
wait_for_navigation
wait_for_navigation is the navigation-specific confirmation step.
CLI aliases accepted for agent-first ergonomics:
- command alias:
wait_for_navigation - target-package flags:
--package,--package-id,--application-id - selector flag aliases from Selectors:
--resource-id,--content-desc,--content-desc-contains
Current public parameters:
| Field | Valid values | Meaning |
|---|---|---|
expectedPackage |
optional non-blank string, <= 512 characters |
wait until that package is foreground |
expectedNode |
optional NodeMatcher |
wait until a target node is present |
timeoutMs |
required number, > 0, <= 30000 |
wait window for navigation confirmation |
Validation rules:
- at least one of
expectedPackageorexpectedNodeis required timeoutMsis requiredtimeoutMsmust be positivetimeoutMsmust not exceed30000
Accepted raw JSON parameter aliases for wait_for_navigation:
expected_package->expectedPackageexpected_nodeandwait_for->expectedNodetimeout_ms->timeoutMs
Builder inflation rule:
- execution timeout becomes
max(timeoutMs + 5000, 30000)
Exact builder literals from buildWaitForNavExecution():
source: "clawperator-action"- action id:
wait-for-nav - action type:
wait_for_navigation - default execution timeout when
navTimeoutMsis omitted:30000
Example:
{
"id": "wait-for-nav",
"type": "wait_for_navigation",
"params": {
"expectedPackage": "com.android.settings",
"expectedNode": {
"textEquals": "Connected devices"
},
"timeoutMs": 5000
}
}
CLI rule for wait-for-nav:
--timeoutis required- at least one of
--appor a selector is required
CLI validation failures:
- missing
--timeout:MISSING_ARGUMENT - non-finite
--timeoutor--timeout <= 0:EXECUTION_VALIDATION_FAILED --timeout > 30000:EXECUTION_VALIDATION_FAILED- blank
--appvalue:EXECUTION_VALIDATION_FAILED - no
--appand no selector:MISSING_ARGUMENT
Verification:
clawperator wait-for-nav --app com.android.settings --timeout 5000 --validate-only
Expected validated execution shape:
{
"ok": true,
"validated": true,
"execution": {
"source": "clawperator-action",
"timeoutMs": 30000,
"actions": [
{
"id": "wait-for-nav",
"type": "wait_for_navigation",
"params": {
"expectedPackage": "com.android.settings",
"timeoutMs": 5000
}
}
]
}
}
Common Navigation Sequence
The recommended pattern for deterministic app navigation is:
open_appwait_for_navigationsnapshot_ui
Example payload:
{
"commandId": "settings-nav-1",
"taskId": "settings-nav-1",
"source": "agent",
"expectedFormat": "android-ui-automator",
"timeoutMs": 30000,
"actions": [
{
"id": "open",
"type": "open_app",
"params": {
"applicationId": "com.android.settings"
}
},
{
"id": "wait",
"type": "wait_for_navigation",
"params": {
"expectedPackage": "com.android.settings",
"timeoutMs": 5000
}
},
{
"id": "snap",
"type": "snapshot_ui"
}
]
}
Machine-checkable success conditions:
envelope.status == "success"stepResults[0].actionType == "open_app"andstepResults[0].success == truestepResults[0].data.application_id == "com.android.settings"stepResults[1].actionType == "wait_for_navigation"andstepResults[1].success == true- when waiting by
expectedPackage,stepResults[1].data.resolved_package == "com.android.settings" stepResults[2].actionType == "snapshot_ui"andstepResults[2].success == truestepResults[2].data.textexists
Verification command:
clawperator exec --execution '{"commandId":"settings-nav-1","taskId":"settings-nav-1","source":"docs","expectedFormat":"android-ui-automator","timeoutMs":30000,"actions":[{"id":"open","type":"open_app","params":{"applicationId":"com.android.settings"}},{"id":"wait","type":"wait_for_navigation","params":{"expectedPackage":"com.android.settings","timeoutMs":5000}},{"id":"snap","type":"snapshot_ui"}]}' --device <device_serial> --json
Complete JSON Example
{
"envelope": {
"commandId": "settings-nav-1",
"taskId": "settings-nav-1",
"status": "success",
"stepResults": [
{
"id": "open",
"actionType": "open_app",
"success": true,
"data": {
"application_id": "com.android.settings"
}
},
{
"id": "wait",
"actionType": "wait_for_navigation",
"success": true,
"data": {
"resolved_package": "com.android.settings",
"elapsed_ms": "412"
}
},
{
"id": "snap",
"actionType": "snapshot_ui",
"success": true,
"data": {
"text": "<hierarchy rotation=\"0\">...</hierarchy>"
}
}
],
"error": null
},
"deviceId": "<device_serial>",
"terminalSource": "clawperator_result",
"isCanonicalTerminal": true
}
Common Failure Modes
App not installed
There is no dedicated Node validation code for "target app is missing" on open_app. This is a runtime problem and usually surfaces as a failed step or unexpected navigation result.
What to do:
- inspect
stepResults[0] - follow with
snapshot_uiorwait_for_navigationto see where the device actually landed - if the Operator package itself is missing instead of the target app, the execution fails earlier with
OPERATOR_NOT_INSTALLED
Navigation timeout
When wait_for_navigation never sees the expected package or node inside its timeout:
- the wait step fails at runtime
- the overall envelope becomes
status: "failed"
Recovery:
- increase the action timeout only if the app truly needs more time
- verify the expectation is correct
- use Snapshot Format or Selectors to inspect the actual screen
Typical failure branch:
{
"envelope": {
"status": "failed",
"stepResults": [
{
"actionType": "wait_for_navigation",
"success": false,
"data": {
"error": "NAVIGATION_TIMEOUT",
"last_package": "com.android.settings"
}
}
]
}
}
Runtime detail:
wait_for_navigationtimeout failures usedata.error: "NAVIGATION_TIMEOUT"- when the runtime observed a foreground package before the timeout expired, it may also include
data.last_package - on success,
data.resolved_packageis present when the runtime had a foreground package value to report
Wrong package on screen
If open_uri hands off to a chooser, browser, or another app, expectedPackage may not match what actually reached the foreground.
Recovery:
- use
expectedNodewhen package alone is too coarse - capture a snapshot and branch on the observed foreground UI
Envelope timeout during navigation
If the entire payload runs too long, the caller gets a top-level RESULT_ENVELOPE_TIMEOUT error rather than a failed wait_for_navigation step.
Recovery:
- check whether the execution-level timeout was budgeted correctly
- use the builder rule
max(actionTimeout + 5000, 30000)as the minimum - verify device health with Doctor if the timeout is unexpected
Recommended Pattern
- use
open_apporopen_urionly as the trigger - use
wait_for_navigationas the readiness gate - use
snapshot_uias the final confirmation step
That three-step flow is the most reliable current navigation pattern for agents.