Best Practices
Patterns for reliable, efficient, and cost-effective Lightcone usage.
These patterns will help you build reliable workflows that handle real-world conditions. They’re distilled from common issues and proven approaches.
Computer management
Section titled “Computer management”Use context managers (Python)
Section titled “Use context managers (Python)”Always use with blocks for computers. They guarantee cleanup even if your code raises an exception:
# Good — computer always terminateswith client.computer.create(kind="desktop") as computer: computer.click(100, 200) # computer terminates when block exits, even on error
# Avoid — computer leaks if an error occurs before delete()computer = client.computers.create(kind="desktop")client.computers.click(computer.id, x=100, y=200)client.computers.delete(computer.id) # never reached if click() throws// Always use try/finally for cleanupconst computer = await client.computers.create({ kind: "desktop" });const id = computer.id!;
try { await client.computers.click(id, { x: 100, y: 200 });} finally { await client.computers.delete(id);}Set appropriate timeouts
Section titled “Set appropriate timeouts”Choose timeouts based on your task, not defaults:
# Quick task — short timeout to avoid paying for idle computerswith client.computer.create( kind="desktop", timeout_seconds=120, inactivity_timeout_seconds=30,) as computer: pass # ... do the task
# Long-running work — generous timeout with keepalive disabledwith client.computer.create( kind="desktop", timeout_seconds=3600, auto_kill=False,) as computer: pass # ... do long-running work// Quick taskconst computer = await client.computers.create({ kind: "desktop", timeout_seconds: 120, inactivity_timeout_seconds: 30,});
// Long-running workconst computer = await client.computers.create({ kind: "desktop", timeout_seconds: 3600, auto_kill: false,});Reuse computers with persistence
Section titled “Reuse computers with persistence”Don’t spin up a new computer for every run when you can reuse one. Persistent computers save their full state — installed software, logged-in services, files:
# First run: create and savewith client.computer.create(kind="desktop", persistent=True) as computer: client.computers.exec.sync(computer.id, command="apt-get install -y libreoffice") # ... set up software, log in to services ... computer_id = computer.id # Save computer_id to a file or database
# Subsequent runs: restorewith client.computer.create( kind="desktop", environment_id=computer_id,) as computer: # Everything is still installed and configured pass// First runconst computer = await client.computers.create({ kind: "desktop", persistent: true,});// ... set up, then save computer.id somewhere ...await client.computers.delete(computer.id!);
// Subsequent runsconst restored = await client.computers.create({ kind: "desktop", environment_id: savedComputerId,});Screenshots and coordinates
Section titled “Screenshots and coordinates”Always screenshot before clicking
Section titled “Always screenshot before clicking”Desktop layouts shift between loads and after actions. Never hardcode coordinates — always take a fresh screenshot to determine the current positions:
client.computers.exec.sync(computer.id, command="firefox https://example.com &")computer.wait(3)
# 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.exec.sync(id, { command: "firefox https://example.com &" });await new Promise((r) => setTimeout(r, 3000));
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 AI or selectors over coordinates when possible
Section titled “Prefer AI or selectors over coordinates when possible”For more reliable element targeting, use the Responses API to let Northstar find click targets automatically from screenshots, or use the Playwright integration with CSS selectors for browser-mode computers.
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 dialog appear?Waits and timing
Section titled “Waits and timing”Wait after launching applications
Section titled “Wait after launching applications”Applications need time to start. Always add a wait after launching software:
client.computers.exec.sync(computer.id, command="firefox https://example.com &")computer.wait(3) # Wait for the app to launch and render
# For heavier applications, wait longerclient.computers.exec.sync(computer.id, command="libreoffice --calc &")computer.wait(5)await client.computers.exec.sync(id, { command: "firefox https://example.com &" });await new Promise((r) => setTimeout(r, 3000));
// For heavier applicationsawait client.computers.exec.sync(id, { command: "libreoffice --calc &" });await new Promise((r) => setTimeout(r, 5000));Wait between rapid actions
Section titled “Wait between rapid actions”Desktop environments need time to process each action. Add short waits between interactions that modify the screen:
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(computer.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 errors gracefully
Section titled “Catch errors gracefully”Computers can time out, get terminated, or encounter network issues. Wrap your work in error handling:
import tzafonfrom tzafon import Lightcone
client = Lightcone()
try: with client.computer.create(kind="desktop") as computer: computer.click(100, 200) 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("Computer 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: "desktop" }); // ...} catch (e) { if (e instanceof Lightcone.APIConnectionTimeoutError) { console.error("Request timed out"); } else if (e instanceof Lightcone.APIError) { 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 });Write specific instructions
Section titled “Write specific instructions”Vague instructions lead to unpredictable results. Be explicit about what “done” looks like:
# Vague — Northstar may give up or take the wrong pathclient.agent.tasks.start( instruction="Find cheap flights", kind="desktop",)
# Specific — Northstar knows exactly what to do and when it's doneclient.agent.tasks.start( instruction=( "Open Firefox and 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="desktop", max_steps=30,)Set max_steps to prevent runaway tasks
Section titled “Set max_steps to prevent runaway tasks”Always set max_steps to a reasonable limit for your work:
| Task type | Suggested max_steps |
|---|---|
| Simple navigation and screenshot | 5–10 |
| Form filling | 10–20 |
| Multi-step workflow | 20–40 |
| Complex research | 40–100 |
Use streaming to monitor progress
Section titled “Use streaming to monitor progress”Streaming lets you see what Northstar is doing in real time and intervene if it goes off track:
for event in client.agent.tasks.start_stream( instruction="...", kind="desktop", 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: "desktop", max_steps: 20,});
for await (const event of stream) { console.log(event);}Anti-detection (browser mode)
Section titled “Anti-detection (browser mode)”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 interact with 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 — computer lifecycle, actions, and configuration
- Troubleshooting — solutions for common issues
- Tasks — managed computer operation
- How Lightcone works — architecture and computer model