Skip to main content
Agent-native is designed around a simple workflow: snapshot the UI to get refs, interact with elements using those refs, then re-snapshot when the UI changes. This pattern works for any macOS app and handles dynamic interfaces gracefully.

The core pattern

Here’s the fundamental cycle:
1

Snapshot

Capture the current UI state and assign refs to elements:
2

Interact

Use refs to click, type, or inspect elements:
3

Re-snapshot

After the page loads (or any UI change), take a fresh snapshot:
This pattern mirrors how humans use computers: observe → act → observe again. The snapshot gives you a moment-in-time view of what’s possible.

When to re-snapshot

You need a fresh snapshot whenever the UI changes significantly:

After navigation

Browsers, settings panels, and multi-screen apps:

After dialogs open

Dialogs, sheets, and popovers add new elements:

After content loads

Dynamic content loading in browsers or document apps:

When refs fail to resolve

If you get an error like this:
Solution: Take a new snapshot. The element either moved, changed attributes, or was removed.
When in doubt, snapshot. Extra snapshots don’t hurt, but stale refs will cause errors.

Working with dynamic UIs

Dynamic interfaces require careful timing:

Wait for changes to settle

After triggering an action, wait before snapshotting:
The wait command (from WaitCommand.swift) simply sleeps for the specified duration.

Check element state

Use get to read element values without re-snapshotting:
Or use is to check boolean conditions:

Use —interactive for cleaner snapshots

Large apps have hundreds of structural elements. Focus on what matters:
This filters to only interactive elements and removes empty containers. From SnapshotCommand.swift:66-74:

Element resolution strategies

You don’t always need refs. agent-native supports two resolution strategies:

Ref-based (snapshot first)

Pros:
  • Fast interactions after initial snapshot
  • You see exactly what you’re clicking
  • Refs work across multiple commands
Cons:
  • Requires a snapshot step
  • Refs become stale when UI changes
From ElementResolver.swift:6-52, this searches the tree on-demand using filters:
Pros:
  • No snapshot needed
  • Works with dynamic UIs where refs would be stale
  • Good for one-off commands
Cons:
  • Slower (searches the tree every time)
  • Less visibility into what exists
  • Ambiguous if multiple elements match
For agent workflows, prefer ref-based resolution. It’s faster and gives your agent visibility into the full UI context.

Best practices

1. Start with an interactive snapshot

This gives you a clean, actionable view of the UI.

2. Use wait after UI-changing actions

Don’t snapshot too soon—animations and loads need time.

3. Re-snapshot liberally

Whenever you’re unsure if the UI has changed, just snapshot again:
Snapshots are cheap and prevent ref resolution errors.

4. Use —compact for complex UIs

This removes noise and focuses on actionable elements.

5. Check snapshots in JSON for parsing

JSON output is structured and easy to parse for agents:

6. Handle ref resolution failures gracefully

Always be ready to re-snapshot if a ref fails:

Example: Multi-step workflow

Here’s a complete example of opening a file in TextEdit:
1

Open the app

2

Snapshot to see what's available

3

Click the File menu

4

Re-snapshot to see the menu

5

Click 'Open...'

6

Wait for the file dialog

7

Snapshot the file picker

Notice the pattern: snapshot → interact → wait → snapshot → interact. This is how you navigate complex UIs reliably.

See also

Accessibility tree

Understanding the tree structure

Refs and snapshots

Deep dive into the ref system