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.
Session management
Section titled “Session management”Use context managers (Python)
Section titled “Use context managers (Python)”Always use with blocks for sessions. They guarantee cleanup even if your code raises an exception:
# Good — session always terminateswith 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 cleanupconst 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);}Set appropriate timeouts
Section titled “Set appropriate timeouts”Choose timeouts based on your task, not defaults:
# Quick scrape — short timeout to avoid paying for idle sessionswith client.computer.create( kind="browser", timeout_seconds=120, inactivity_timeout_seconds=30,) as computer: # ...
# Long-running agent — generous timeout with keepalive disabledwith client.computer.create( kind="browser", timeout_seconds=3600, auto_kill=False,) as computer: # ...// Quick scrapeconst computer = await client.computers.create({ kind: "browser", timeout_seconds: 120, inactivity_timeout_seconds: 30,});
// Long-running agentconst computer = await client.computers.create({ kind: "browser", timeout_seconds: 3600, auto_kill: false,});Reuse sessions with persistence
Section titled “Reuse sessions with persistence”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 savewith 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: restorewith 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 runconst session = await client.computers.create({ kind: "browser", persistent: true,});// ... log in, then save session.id somewhere ...await client.computers.delete(session.id!);
// Subsequent runsconst restored = await client.computers.create({ kind: "browser", environment_id: savedSessionId,});Screenshots and coordinates
Section titled “Screenshots and coordinates”Always screenshot before clicking
Section titled “Always screenshot before clicking”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 layoutresult = computer.screenshot()url = computer.get_screenshot_url(result)print(f"Check layout: {url}")
# Use coordinates from the screenshot, not from a previous runcomputer.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.
Verify actions with follow-up screenshots
Section titled “Verify actions with follow-up screenshots”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?Waits and timing
Section titled “Waits and timing”Wait after navigation
Section titled “Wait after navigation”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 longercomputer.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 SPAsawait client.computers.navigate(id, { url: "https://app.example.com/dashboard" });await new Promise((r) => setTimeout(r, 5000));Wait between rapid actions
Section titled “Wait between rapid actions”Browsers need time to process each action. Add short waits between interactions that modify the page:
computer.click(400, 300) # Click a dropdowncomputer.wait(1) # Wait for dropdown to opencomputer.click(400, 380) # Click an optioncomputer.wait(1) # Wait for selection to applyUse batch actions for speed
Section titled “Use batch actions for speed”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"] }, ],});Error handling
Section titled “Error handling”Catch session errors gracefully
Section titled “Catch session errors gracefully”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: raiseimport 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; }}Don’t retry blindly
Section titled “Don’t retry blindly”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 });Agent Tasks
Section titled “Agent Tasks”Write specific instructions
Section titled “Write specific instructions”Vague instructions lead to unpredictable results. Be explicit about what “done” looks like:
# Vague — agent may give up or take wrong pathclient.agent.tasks.start( instruction="Find cheap flights", kind="browser",)
# Specific — agent knows exactly what to do and when it's doneclient.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,)Set max_steps to prevent runaway agents
Section titled “Set max_steps to prevent runaway agents”Always set max_steps to a reasonable limit for your task:
| Task type | Suggested max_steps |
|---|---|
| Simple navigation and screenshot | 5–10 |
| Form filling | 10–20 |
| Multi-page workflow | 20–40 |
| Complex research task | 40–100 |
Use streaming to monitor progress
Section titled “Use streaming to monitor progress”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);}Anti-detection
Section titled “Anti-detection”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")Add human-like delays
Section titled “Add human-like delays”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 pausecomputer.type("search query")computer.wait(random.uniform(0.3, 0.8))computer.hotkey("enter")Respect robots.txt and terms of service
Section titled “Respect robots.txt and terms of service”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.
See also
Section titled “See also”- Computers — session lifecycle, actions, and configuration
- Troubleshooting — solutions for common issues
- Agent Tasks — autonomous AI agents
- How Lightcone works — architecture and session model