Observability: live view, screencast, and traces
Watch sessions live in the dashboard, stream video and events over SSE, record runs to MP4, and trace every action.
Every computer is fully observable while it runs: a live view in the dashboard, a raw video stream, an event stream of every action, and per-step task traces. This page covers each surface and when to use it.
Live view in the dashboard
Section titled “Live view in the dashboard”Every computer has a live view at:
https://lightcone.ai/c/{computer_id}Open it in a browser to watch the session in real time, no code required. After the session ends, the same URL serves the replay.
When you run a Task, the started event in the stream carries the computer_id, so you can link to the live view as soon as the run begins:
stream = client.agent.tasks.start_stream(instruction="...", kind="browser")
for event in stream: if event.get("type") == "started": print(f"Watch live: https://lightcone.ai/c/{event['computer_id']}")const stream = await client.agent.tasks.startStream({ instruction: "...", kind: "browser" });
for await (const event of stream) { if (event.type === "started") { console.log(`Watch live: https://lightcone.ai/c/${event.computer_id}`); }}Screencast stream
Section titled “Screencast stream”GET /computers/{id}/screencast is a Server-Sent Events stream of the session’s video. The event shapes depend on the session kind:
| Session kind | SSE events | Payload |
|---|---|---|
| Browser | Unnamed (default) events | JSON with a base64-encoded JPEG frame in image_data, plus metadata (timestamp, viewport dimensions) |
| Desktop | event: h264 | JSON with nalu_data, base64-encoded raw H.264 NAL units, suitable for a WebCodecs VideoDecoder |
| Desktop | event: cursor_update | Cursor sprite bitmap and hotspot; emitted only when the cursor shape changes |
| Desktop | event: cursor_position | Cursor x/y; emitted once per video frame while the cursor moves |
A : heartbeat SSE comment is sent every 30 seconds to keep intermediaries from closing the connection. Action responses are deliberately excluded; those live on /events.
import json
with client.computers.with_streaming_response.retrieve_screencast(computer.id) as response: for line in response.iter_lines(): if line.startswith("data: "): frame = json.loads(line[len("data: "):]) # browser: frame["image_data"] is a base64 JPEGconst response = await client.computers.retrieveScreencast(computer.id!);// Parse the SSE body: unnamed events carry JPEG frames (browser),// named "h264" events carry NAL units (desktop).Recording a session to MP4
Section titled “Recording a session to MP4”Pipe the screencast into ffmpeg to produce a video file. For a desktop session, decode the base64 NAL units and write them to ffmpeg’s stdin:
import base64import jsonimport subprocess
from tzafon import Lightcone
client = Lightcone()computer = client.computers.create(kind="desktop")
ffmpeg = subprocess.Popen( ["ffmpeg", "-y", "-i", "pipe:", "-c:v", "copy", "recording.mp4"], stdin=subprocess.PIPE,)
try: with client.computers.with_streaming_response.retrieve_screencast(computer.id) as response: for line in response.iter_lines(): if not line.startswith("data: "): continue # skips "event: ..." lines and ": heartbeat" comments payload = json.loads(line[len("data: "):]) if "nalu_data" in payload: ffmpeg.stdin.write(base64.b64decode(payload["nalu_data"]))finally: ffmpeg.stdin.close() ffmpeg.wait() client.computers.delete(computer.id)For a browser session the frames are JPEGs, so use image2pipe instead and write the decoded image_data bytes:
ffmpeg = subprocess.Popen( ["ffmpeg", "-y", "-f", "image2pipe", "-framerate", "10", "-i", "pipe:", "recording.mp4"], stdin=subprocess.PIPE,)# ... ffmpeg.stdin.write(base64.b64decode(payload["image_data"]))Event stream and WebSocket
Section titled “Event stream and WebSocket”GET /computers/{id}/events is an SSE stream of every action on the session (video excluded), which makes it a step-by-step trace of programmatic control. Each data: line is a JSON object in one of three categories:
| Category | What it carries |
|---|---|
| Action request | An action has been queued for execution |
| Action response | A completed action, with a request_id for correlation; shell commands include exit_code, stdout, stderr |
| Command output chunk | Streaming output from a long-running /exec command: request_id, base64 data, is_stderr |
GET /computers/{id}/ws is the WebSocket variant of the same stream, with one addition: it’s bidirectional, so you can send actions over the same connection instead of making HTTP calls. Use it when you want a single connection for both control and observation.
Task traces
Section titled “Task traces”For Tasks, the SSE stream is itself the trace. Each step of the run emits events you can log or replay:
| Event | What it carries |
|---|---|
screenshot | URL of the screenshot the agent saw at this step |
executed | The action the agent took |
thinking / status | The agent’s reasoning and progress updates |
plan | The agent’s current plan |
stream = client.agent.tasks.start_stream(instruction="...", kind="browser")
for event in stream: print(event) # persist these; they are the full run traceconst stream = await client.agent.tasks.startStream({ instruction: "...", kind: "browser" });
for await (const event of stream) { console.log(event); // persist these; they are the full run trace}Fused act-and-observe
Section titled “Fused act-and-observe”For a tight see-act loop you don’t need a separate screenshot call after each action. Add ?screenshot_after=true to any action endpoint and the response includes a post-action screenshot taken after the UI settles:
result = client.computers.click( computer.id, x=100, y=200, extra_query={"screenshot_after": "true", "settle_ms": "500"},)const result = await client.computers.click(computer.id!, { x: 100, y: 200 }, { query: { screenshot_after: "true", settle_ms: "500" },});| Query param | Effect |
|---|---|
screenshot_after=true | Return a post-action screenshot in the same response |
settle_ms=N | Wait up to N ms for the UI to settle before capturing |
base64=true | Return the fused frame as base64 in the response body instead of a URL |
This halves the round trips in a screenshot-act-screenshot loop and guarantees the frame reflects the action you just took.
See also
Section titled “See also”- Computers: computer lifecycle and actions
- Tasks: autonomous runs and their event streams
- Limits and behaviors: platform behaviors and constraints
- Troubleshooting: common issues and solutions