Computer Use Agent
This recipe creates a full virtual desktop sandbox and connects it to Claude's Computer Use capability — letting the agent see the screen, click, type, and launch applications.
Prerequisites
- Caged CLI installed (
brew install caged-dev/tap/cagedorcurl -fsSL https://get.caged.dev | sh) - Caged account with API key (
caged login) - An
ANTHROPIC_API_KEYfor Claude Computer Use
Quick Start
CLI
# Create a desktop sandbox
caged up --template desktop --budget 5
# Get sandbox ID
SANDBOX_ID=$(caged list --format json | jq -r '.[0].id')
# Take a screenshot via API
curl "https://api.caged.dev/v1/sandboxes/$SANDBOX_ID/screen/screenshot?format=base64" \
-H "Authorization: Bearer $CAGED_API_KEY"
Python SDK
import caged
# Create desktop sandbox
sandbox = caged.create(template="desktop", budget=5.0)
# Take a screenshot
result = sandbox.mcp.call("screen_screenshot", {})
print(f"Screen: {result['width']}x{result['height']}")
# Launch browser
sandbox.mcp.call("screen_launch", {"command": "chromium-browser https://example.com"})
sandbox.mcp.call("screen_wait", {"seconds": 3})
# Click on something
sandbox.mcp.call("screen_click", {"x": 640, "y": 300})
# Type text
sandbox.mcp.call("screen_type", {"text": "Hello from Caged!"})
sandbox.mcp.call("screen_key", {"key": "Return"})
Full Computer Use Agent
This example creates a complete agent loop that connects Claude's Computer Use to a Caged desktop sandbox:
import anthropic
import caged
import base64
import json
from typing import Any
def run_computer_use_agent(task: str, budget: float = 10.0):
"""Run a Computer Use agent in an isolated Caged sandbox."""
# Create desktop sandbox
sandbox = caged.create(
template="desktop",
budget=budget,
resources={"memory": 4096, "cpu": 2}
)
print(f"Sandbox created: {sandbox.id}")
# Initialize Claude
client = anthropic.Anthropic()
# Computer Use tool definition
computer_tool = {
"type": "computer_20241022",
"name": "computer",
"display_width_px": 1280,
"display_height_px": 800,
"display_number": 0
}
def execute_computer_action(action: str, **kwargs) -> dict:
"""Execute a computer action in the sandbox."""
if action == "screenshot":
result = sandbox.mcp.call("screen_screenshot", {})
return {
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": result["image_base64"]
}
}
elif action == "mouse_move":
x, y = kwargs["coordinate"]
sandbox.mcp.call("screen_click", {"x": x, "y": y})
return {"success": True}
elif action == "left_click":
sandbox.mcp.call("screen_click", {"x": kwargs.get("x", 0), "y": kwargs.get("y", 0)})
return {"success": True}
elif action == "right_click":
sandbox.mcp.call("screen_click", {"x": kwargs.get("x", 0), "y": kwargs.get("y", 0), "button": "right"})
return {"success": True}
elif action == "double_click":
sandbox.mcp.call("screen_click", {"x": kwargs.get("x", 0), "y": kwargs.get("y", 0), "double": True})
return {"success": True}
elif action == "type":
sandbox.mcp.call("screen_type", {"text": kwargs["text"]})
return {"success": True}
elif action == "key":
sandbox.mcp.call("screen_key", {"key": kwargs["key"]})
return {"success": True}
elif action == "scroll":
direction = "down" if kwargs.get("coordinate", [0, 1])[1] > 0 else "up"
info = sandbox.mcp.call("screen_info", {})
sandbox.mcp.call("screen_scroll", {
"x": info.get("mouse_x", 640),
"y": info.get("mouse_y", 400),
"direction": direction,
"amount": abs(kwargs.get("coordinate", [0, 3])[1])
})
return {"success": True}
else:
return {"error": f"Unknown action: {action}"}
# Start agent loop
messages = [{"role": "user", "content": task}]
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=[computer_tool],
messages=messages
)
# Check for end conditions
if response.stop_reason == "end_turn":
# Extract final text response
for block in response.content:
if hasattr(block, "text"):
print(f"\nAgent completed: {block.text}")
break
# Process tool calls
tool_results = []
for block in response.content:
if block.type == "tool_use" and block.name == "computer":
action = block.input.get("action")
print(f"Action: {action}")
result = execute_computer_action(action, **block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": [result] if action == "screenshot" else json.dumps(result)
})
# Continue conversation
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
# Check budget
if sandbox.cost > budget * 0.9:
print(f"Warning: Approaching budget limit (${sandbox.cost:.2f}/${budget})")
# Cleanup
sandbox.destroy()
print(f"Total cost: ${sandbox.cost:.2f}")
# Run the agent
run_computer_use_agent(
task="Open Chrome, go to news.ycombinator.com, and summarize the top 3 stories",
budget=5.0
)
Config File
For reproducible setups, use .caged.yaml:
# .caged.yaml
template: desktop
resources:
memory: 4096 # 4GB recommended for desktop
cpu: 2
disk: 10240 # 10GB for browser cache, downloads
timeout: 7200 # 2 hours max
budget: 10.00 # $10 hard limit
env:
DISPLAY: ":99"
SCREEN_WIDTH: "1280"
SCREEN_HEIGHT: "800"
Then run:
caged up
Available Screen Tools
| Tool | Description |
|---|---|
screen_screenshot |
Capture PNG screenshot (base64) |
screen_click |
Click at coordinates (left/right/double) |
screen_type |
Type text with optional delay |
screen_key |
Press keys (Return, Tab, ctrl+c, etc.) |
screen_scroll |
Scroll up/down at coordinates |
screen_drag |
Drag from start to end coordinates |
screen_launch |
Launch an application |
screen_info |
Get screen size, mouse position, active window |
screen_wait |
Wait for animations/page loads (max 30s) |
WebSocket Streaming
For real-time screen updates, use the WebSocket endpoint:
const ws = new WebSocket(
`wss://api.caged.dev/v1/sandboxes/${sandboxId}/screen`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
// Request continuous screenshots
setInterval(() => {
ws.send(JSON.stringify({ type: "screenshot" }));
}, 500);
// Handle responses
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "screenshot") {
updateScreenDisplay(msg.image_base64);
}
};
// Send interactions
function click(x, y) {
ws.send(JSON.stringify({ type: "click", x, y }));
}
Observability
All screen actions are captured in session replay:
# View session replay
caged sessions list
caged replay sess_xxxxx
# Or via API
curl "https://api.caged.dev/v1/sessions/sess_xxxxx/replay" \
-H "Authorization: Bearer $CAGED_API_KEY"
The replay includes:
- Screenshots before and after each action
- Click coordinates and button states
- Typed text (sensitive data redacted)
- Application launches
- Trust score changes
Trust Scoring
Desktop actions affect the session trust score:
| Action | Score Impact |
|---|---|
| Taking screenshots | +1 (observation before action) |
| Launching browser | +1 (normal application use) |
| Typing credential-like text | -10 (potential credential exposure) |
| Running suspicious commands | -15 (potentially malicious) |
Set a minimum trust threshold in your policy:
# Policy requiring high trust
policy:
trust:
min_score: 70
action: pause # pause for approval if score drops
Best Practices
Always take a screenshot before interacting to understand screen state
Use screen_wait after launching apps or loading pages
Desktop sandboxes use more resources — always set a budget limit
For real-time control, use WebSocket instead of REST
Limitations
- Resolution: Default 1280×800 (configurable via env vars)
- GPU: Software rendering only (no hardware acceleration)
- Audio: Not supported
- Clipboard: Use
screen_typeandscreen_keyfor copy/paste - Multiple monitors: Single display only
Related
- Desktop Sandboxes Guide — Full reference for desktop tools
- Session Replay — Viewing agent actions
- Trust Scoring — Understanding trust scores
- Policies — Configuring security policies