Skip to content
Dashboard

Best Practices

Patterns for reliable, efficient, and cost-effective Lightcone automation.

These patterns will help you build reliable automations that handle real-world conditions. They’re distilled from common issues and proven workflows.

Always use with blocks for sessions. They guarantee cleanup even if your code raises an exception:

# Good — session always terminates
with client.computer.create(kind="browser") as computer:
computer.navigate("https://example.com")
# session terminates when block exits, even on error
# Avoid — session leaks if an error occurs before delete()
session = client.computers.create(kind="browser")
client.computers.navigate(session.id, url="https://example.com")
client.computers.delete(session.id) # never reached if navigate() throws
// Always use try/finally for cleanup
const computer = await client.computers.create({ kind: "browser" });
const id = computer.id!;
try {
await client.computers.navigate(id, { url: "https://example.com" });
} finally {
await client.computers.delete(id);
}

Choose timeouts based on your task, not defaults:

# Quick scrape — short timeout to avoid paying for idle sessions
with client.computer.create(
kind="browser",
timeout_seconds=120,
inactivity_timeout_seconds=30,
) as computer:
# ...
# Long-running agent — generous timeout with keepalive disabled
with client.computer.create(
kind="browser",
timeout_seconds=3600,
auto_kill=False,
) as computer:
# ...
// Quick scrape
const computer = await client.computers.create({
kind: "browser",
timeout_seconds: 120,
inactivity_timeout_seconds: 30,
});
// Long-running agent
const computer = await client.computers.create({
kind: "browser",
timeout_seconds: 3600,
auto_kill: false,
});

Don’t create a new session for every run when you can reuse one. Persistent sessions save cookies, local storage, and browser cache:

# First run: create and save
with client.computer.create(kind="browser", persistent=True) as computer:
computer.navigate("https://app.example.com/login")
# ... log in ...
session_id = computer.id
# Save session_id to a file or database
# Subsequent runs: restore
with client.computer.create(
kind="browser",
environment_id=session_id,
) as computer:
computer.navigate("https://app.example.com/dashboard")
# Already logged in — no need to re-authenticate
// First run
const session = await client.computers.create({
kind: "browser",
persistent: true,
});
// ... log in, then save session.id somewhere ...
await client.computers.delete(session.id!);
// Subsequent runs
const restored = await client.computers.create({
kind: "browser",
environment_id: savedSessionId,
});

Page layouts shift between loads. Never hardcode coordinates — always take a fresh screenshot to determine the current positions:

computer.navigate("https://example.com")
computer.wait(2)
# Screenshot to see the current layout
result = computer.screenshot()
url = computer.get_screenshot_url(result)
print(f"Check layout: {url}")
# Use coordinates from the screenshot, not from a previous run
computer.click(500, 300)
await client.computers.navigate(id, { url: "https://example.com" });
await new Promise((r) => setTimeout(r, 2000));
const result = await client.computers.screenshot(id);
console.log("Check layout:", result.result?.screenshot_url);
await client.computers.click(id, { x: 500, y: 300 });

Prefer selectors over coordinates when possible

Section titled “Prefer selectors over coordinates when possible”

For more reliable element targeting, use the Playwright integration with CSS selectors, or use the Responses API to let a vision model find click targets automatically.

After a critical action (clicking a button, submitting a form), take another screenshot to confirm it worked:

computer.click(400, 300) # Click "Submit"
computer.wait(2)
result = computer.screenshot()
url = computer.get_screenshot_url(result)
# Verify: did a confirmation page appear?

Pages need time to load. Always add a wait after navigate():

computer.navigate("https://example.com")
computer.wait(2) # Wait for page load
# For JavaScript-heavy SPAs, wait longer
computer.navigate("https://app.example.com/dashboard")
computer.wait(5)
await client.computers.navigate(id, { url: "https://example.com" });
await new Promise((r) => setTimeout(r, 2000));
// For SPAs
await client.computers.navigate(id, { url: "https://app.example.com/dashboard" });
await new Promise((r) => setTimeout(r, 5000));

Browsers need time to process each action. Add short waits between interactions that modify the page:

computer.click(400, 300) # Click a dropdown
computer.wait(1) # Wait for dropdown to open
computer.click(400, 380) # Click an option
computer.wait(1) # Wait for selection to apply

When you have multiple actions that don’t need screenshots between them, use batch to execute them in a single request:

client.computers.batch(session.id, actions=[
{"type": "click", "x": 400, "y": 300},
{"type": "type", "text": "search query"},
{"type": "hotkey", "keys": ["Enter"]},
])
await client.computers.batch(id, {
actions: [
{ type: "click", x: 400, y: 300 },
{ type: "type", text: "search query" },
{ type: "hotkey", keys: ["Enter"] },
],
});

Sessions can time out, get terminated, or encounter network issues. Wrap session work in error handling:

import tzafon
try:
with client.computer.create(kind="browser") as computer:
computer.navigate("https://example.com")
computer.wait(2)
result = computer.screenshot()
except tzafon.APITimeoutError:
print("Request timed out — try increasing the SDK timeout")
except tzafon.APIStatusError as e:
if e.status_code == 404:
print("Session not found — it may have timed out")
elif e.status_code == 429:
print("Rate limited — reduce concurrency or add delays")
else:
raise
import Lightcone from "@tzafon/lightcone";
try {
const computer = await client.computers.create({ kind: "browser" });
// ...
} catch (e) {
if (e instanceof Lightcone.APITimeoutError) {
console.error("Request timed out");
} else if (e instanceof Lightcone.APIStatusError) {
console.error(`API error ${e.status}: ${e.message}`);
} else {
throw e;
}
}

The SDKs already retry on transient errors (429, 5xx) with exponential backoff — default 2 retries. Adding your own retry loop on top can cause excessive requests. Instead, increase the SDK’s retry limit if needed:

client = Lightcone(max_retries=5)
const client = new Lightcone({ maxRetries: 5 });

Vague instructions lead to unpredictable results. Be explicit about what “done” looks like:

# Vague — agent may give up or take wrong path
client.agent.tasks.start(
instruction="Find cheap flights",
kind="browser",
)
# Specific — agent knows exactly what to do and when it's done
client.agent.tasks.start(
instruction=(
"Go to google.com/travel/flights. "
"Search for round-trip flights from SFO to JFK, "
"departing March 15 and returning March 22. "
"Sort by price (lowest first). "
"You're done when the results page shows sorted flights."
),
kind="browser",
max_steps=30,
)

Always set max_steps to a reasonable limit for your task:

Task typeSuggested max_steps
Simple navigation and screenshot5–10
Form filling10–20
Multi-page workflow20–40
Complex research task40–100

Streaming lets you see what the agent is doing in real time and intervene if it goes off track:

for event in client.agent.tasks.start_stream(
instruction="...",
kind="browser",
max_steps=20,
):
if hasattr(event, "screenshot_url"):
print(f"Step screenshot: {event.screenshot_url}")
elif hasattr(event, "status"):
print(f"Status: {event.status}")
const stream = await client.agent.tasks.startStream({
instruction: "...",
kind: "browser",
max_steps: 20,
});
for await (const event of stream) {
console.log(event);
}

Use the advanced proxy for sensitive sites

Section titled “Use the advanced proxy for sensitive sites”

Sites with bot detection (e-commerce, social media, banking) need the advanced proxy:

with client.computer.create(
kind="browser",
use_advanced_proxy=True,
) as computer:
computer.navigate("https://protected-site.com")

Even with the proxy, unnaturally fast interactions can trigger bot detection. Add realistic pauses:

import random
computer.click(400, 300)
computer.wait(random.uniform(0.5, 1.5)) # Human-like pause
computer.type("search query")
computer.wait(random.uniform(0.3, 0.8))
computer.hotkey("enter")

Lightcone gives you the power to automate any website. Use that power responsibly — check a site’s robots.txt and terms of service before automating.