Skip to main content

GL Computer Use

Description

A typed Python SDK for desktop automation via natural-language prompts. GL Computer Use wraps cloud desktop sandboxes and computer-use agents into a clean async API with live streaming, human-in-the-loop takeover, structured observability, and swappable providers.

Key Features

  • Streaming and non-streaming run modes: run() for live events, run_once() for a single result, run_sync() for non-async scripts and Jupyter notebooks.
  • agent="openai" (default): drives OpenAI's Responses API "computer" tool directly (the current GA tool — not the retired computer_use_preview) — see gl_computer_use/agent/openai_agent.py. Added alongside Agent-S after cua was removed, to keep a direct OpenAI computer-use option available without a per-provider routing layer to fall behind new models. Requires GLCU_OPENAI_API_KEY and a model that supports the computer tool (default openai/gpt-5.6-sol). The only agent that implements supports_trajectory_persistence (resume seeds previous_response_id, subject to OpenAI's response-retention window). Verified end-to-end against a real E2B sandbox + OpenAI account with cost tracking (5 steps, ~$0.017).
  • Agent-S (simular-ai) temporarily disabled: agent="agents" is not registered, because gui-agents declares Requires-Python <=3.12. The code stays in gl_computer_use/agent/agent_s.py. Register a custom agent via register_agent() to use a different backend.
  • Swappable execution environments: e2b (E2B Desktop, default), opensandbox (Alibaba OpenSandbox), or local for explicit, unsandboxed control of the active host desktop.
  • Live desktop URL: noVNC streaming URL surfaced via the SANDBOX_READY event or StreamClient.stream_url.
  • Human-in-the-loop takeover: pause an agent loop and hand control to a human, then resume with optional guidance.
  • Artifact storage: local disk by default, MinIO/S3 via the minio extra.
  • Structured logging with optional OpenTelemetry tracing/metrics and Sentry via the observability extra.
  • Custom provider registration: plug in your own sandbox, agent, or artifact store without modifying the SDK.

Installation

Install the core SDK:

pip install gl-computer-use

This includes the default agent="openai" (just the openai package, lightweight). Agent-S (agent="agents") is temporarily disabled and gui-agents is not installed: it declares Requires-Python <=3.12, so pip refuses it on Python 3.12.1+ and 3.13.

Install optional extras only when you need them:

pip install "gl-computer-use[recording]"     # WebM session recording via Playwright
pip install "gl-computer-use[opensandbox]"   # Alibaba OpenSandbox support
pip install "gl-computer-use[minio]"         # MinIO / S3-compatible artifact store
pip install "gl-computer-use[observability]" # OTLP tracing/metrics + Sentry via gl-observability
pip install "gl-computer-use[all]"           # all of the above

API keys required at runtime:

  1. E2B API key — e2b.dev (when using sandbox="e2b")
  2. OpenAI API key (for the default agent="openai" / gpt-5.6-sol model)

Session recording setup (optional, one-time)

WebM recordings require Playwright's Chromium binaries (~130 MB, stored under ~/.cache/ms-playwright/):

pip install "gl-computer-use[recording]"
gl-computer-use-setup

If you skip this step, the SDK falls back to GIF recording via screenshot stitching.


Quick Start

Streaming events

run() returns a StreamClient; iterate it to receive events. The terminal TASK_COMPLETED event carries the final TaskResult.

import asyncio
from gl_computer_use import GLComputerUseClient


async def main() -> None:
    client = GLComputerUseClient()
    stream = await client.run("Open Firefox and navigate to google.com")

    async for event in stream:
        if event.event_type == "SANDBOX_READY" and event.stream_url:
            print(f"Watch live at: {event.stream_url}")
        elif event.event_type == "STEP_COMPLETED":
            print(f"Step {event.step_index}: {event.action.type if event.action else '—'}")
        elif event.event_type == "TASK_COMPLETED":
            print(f"Status: {event.result.status}")
            print(f"Output: {event.result.output}")


asyncio.run(main())

Fire-and-forget async

run_once() returns a TaskResult directly when the task finishes. Raises TaskFailedError / TaskCancelledError on non-COMPLETED outcomes.

import asyncio
from gl_computer_use import GLComputerUseClient


async def main() -> None:
    client = GLComputerUseClient()
    result = await client.run_once("Open a terminal and check Python version")
    print(result.status, result.output, len(result.steps))


asyncio.run(main())

Synchronous / Jupyter

run_sync() is a plain synchronous method — no asyncio.run(), no await. It detects whether an event loop is already running and dispatches via ThreadPoolExecutor when needed, so it works in regular scripts and Jupyter notebooks (no nest_asyncio required).

from gl_computer_use import GLComputerUseClient

result = GLComputerUseClient().run_sync("Open the file manager")
print(result.status)

Configuration

Configuration is read from environment variables (prefix GLCU_) or by passing a GLComputerUseConfig object directly. Create a .env file in your working directory:

GLCU_E2B_API_KEY=sk-e2b-...
GLCU_OPENAI_API_KEY=sk-...

# Optional overrides
GLCU_MODEL=openai/gpt-5.6-sol
GLCU_TASK_TIMEOUT=300
GLCU_MAX_STEPS=50

Critical fields:

Variable Default Description
GLCU_E2B_API_KEY None E2B Desktop API key (required when sandbox="e2b")
GLCU_OPENAI_API_KEY None OpenAI API key (required for the default agent="openai" and for openai/* models)
GLCU_ANTHROPIC_API_KEY None Anthropic API key (required for anthropic/* models with a custom agent)
GLCU_MODEL "openai/gpt-5.6-sol" LLM in provider/name format; must support the computer tool when agent="openai"
GLCU_AGENT "openai" Agent provider — "openai" (direct OpenAI computer-use tool; Agent-S "agents" is temporarily disabled); pass a custom name registered via register_agent() to use a different backend
GLCU_SANDBOX "e2b" Execution provider: "e2b", "opensandbox", or "local"; local directly controls the active host desktop without isolation
GLCU_ARTIFACT "local" Artifact store: "local" or "minio"
GLCU_TASK_TIMEOUT 300.0 How long one run may take, in seconds
GLCU_SANDBOX_TIMEOUT 300 Sandbox lifetime in seconds, measured from create (E2B clamps to 3600) — equal to GLCU_TASK_TIMEOUT by default (a deliberate shipped default; a task only loses its tail when it runs nearly the full budget); raise it above GLCU_TASK_TIMEOUT for headroom if provisioning and bring-up are slow on your backend
GLCU_SANDBOX_IMAGE "" Image/template to boot; empty means the backend default (desktop for E2B, public.ecr.aws/c1z4u5m0/external/desktop-sandbox:latest for OpenSandbox)
GLCU_SANDBOX_PROVISION_TIMEOUT 300 HTTP timeout for create/resume calls, and the budget for polling a snapshot to Ready — raise it when the server is slow to answer
GLCU_SANDBOX_READY_TIMEOUT 30 OpenSandbox health-check timeout after create/restore; an unready sandbox is killed when this expires — raise it when cold image pulls are slow
GLCU_SANDBOX_REQUEST_TIMEOUT 60 HTTP timeout for actions and screenshots once the sandbox is up
GLCU_MAX_STEPS 50 Maximum agent loop iterations; also the Agent-S ceiling unless GLCU_AGENTS_MAX_STEPS is set
GLCU_OPENAI_REASONING_EFFORT unset OpenAI reasoning effort for agent="openai": none, minimal, low, medium, high, xhigh
GLCU_AGENTS_TEMPERATURE None Sampling temperature override for Agent-S LLM calls; unset leaves gui-agents' own default (0.0), which some newer reasoning-tier OpenAI models reject outright
GLCU_LOCAL_ARTIFACT_DIR "./artifacts" Directory for saved screenshots and recordings
GLCU_LOG_LEVEL "INFO" DEBUG, INFO, WARNING, or ERROR
GLCU_LOG_FORMAT "json" "json" (structured) or "console" (human-readable)
GLCU_DEFAULT_DISPOSITION "destroy" Teardown when no disposition is passed: "destroy" or "snapshot" (see Snapshot & Resume)
GLCU_STRICT_SNAPSHOT False Turn best-effort snapshot/resume failures into hard ConfigError/SnapshotError
GLCU_TRAJECTORY_MAX_IMAGES None Reserved for a future agent with image-capped trajectory replay; no built-in agent implements it, so any value other than None raises ConfigError at config time (see Agent support)
GLCU_TRAJECTORY_MAX_BYTES None Hard ceiling (bytes) on persisted trajectory JSON
GLCU_TRAJECTORY_PII_ANONYMIZATION False Reserved for a future agent with trajectory PII anonymization; no built-in agent implements it, so True raises ConfigError at config time (see Agent support) — not to be confused with GLCU_PII_REDACTION_ENABLED, which still works and redacts PII from log lines, not trajectories
GLCU_KEEP_SNAPSHOT_HISTORY False Retain every snapshot instead of rolling-GC'ing predecessors (E2B)
GLCU_ALLOW_MODEL_DRIFT False Allow resuming a token whose model differs from the configured model

OpenSandbox, MinIO, Agent-S, and observability (OTLP/Sentry/PII) have additional GLCU_* env vars — see GLComputerUseConfig in gl_computer_use/config.py for the full list.

Timeouts

The five timeout knobs nest, and three of them are named *_SANDBOX_*, so it is easy to reach for the wrong one:

Knob Bounds Raise it when
GLCU_SANDBOX_TIMEOUT Total sandbox lifetime (wall clock, from create) Long tasks die mid-run as transport errors
GLCU_TASK_TIMEOUT One run (wall clock) The agent legitimately needs more steps
GLCU_SANDBOX_PROVISION_TIMEOUT A single HTTP request while creating/resuming; also the snapshot-to-Ready polling budget The server is slow to answer
GLCU_SANDBOX_READY_TIMEOUT The OpenSandbox health check, after which the SDK kills the sandbox Cold image pulls are slow
GLCU_SANDBOX_REQUEST_TIMEOUT A single HTTP request once the desktop is up Actions or screenshots time out on a healthy box

PROVISION_TIMEOUT and REQUEST_TIMEOUT bound one request each, not a phase — a phase issuing twenty calls can far outlast either. Only SANDBOX_TIMEOUT and TASK_TIMEOUT bound elapsed time.

READY_TIMEOUT is the one to size against your own server: run a task on an uncached node once and check how long provisioning takes before the health check passes.


Provider Agnosticism

Swap sandboxes (and custom-registered agents) via config alone — no code changes:

Agent Sandbox Config
OpenAI (default) E2B (default) GLComputerUseClient()
OpenAI OpenSandbox GLComputerUseConfig(sandbox="opensandbox")
OpenAI Local host desktop GLComputerUseConfig(sandbox="local")
from gl_computer_use import GLComputerUseClient, GLComputerUseConfig

client = GLComputerUseClient(GLComputerUseConfig(sandbox="opensandbox"))  # default agent="openai"

Direct local desktop execution

sandbox="local" runs computer actions against the active desktop of the machine running Python. It does not create a container or VM, and cleanup never shuts down or logs out the host. Keep the desktop unlocked and visible throughout the run.

from gl_computer_use import GLComputerUseClient, GLComputerUseConfig

config = GLComputerUseConfig(
    sandbox="local",
    agent="openai",
    model="openai/gpt-5.6-sol",
    openai_api_key="sk-...",
)
result = GLComputerUseClient(config).run_sync("Open the calculator application")
print(result.output)

This mode is intentionally explicit because it has no isolation: the agent can see the screen, operate applications, and affect real accounts and files. Use a dedicated OS account, close sensitive applications, and keep PyAutoGUI's corner fail-safe enabled. macOS requires Screen Recording and Accessibility permissions; Linux requires an X11 session for PyAutoGUI input. On Ubuntu, install PyAutoGUI's screenshot dependency before running locally:

sudo apt install scrot

On macOS, provision() preflights the Screen Recording grant and fails with an actionable error when it is missing, because a denied grant makes every screenshot blank without raising anything.

Only one local run may be active on a host at a time — two agents sharing one keyboard and pointer would corrupt each other's runs. The claim is held in a lock file under the system temporary directory and is released by the operating system even if the owning process crashes, so a second run provisioned while the first is still alive fails immediately with the owner's process id.

Local runs have no VNC stream, so there is no page for Playwright to record. The runner falls back to stitching the per-step screenshots into a GIF, which is the recording artifact a local run produces.


Runtime API

The client exposes three run methods:

Method Returns Use when
await client.run(prompt, ...) StreamClient You need live event streaming or the SANDBOX_READY URL before the task finishes
await client.run_once(prompt, ...) TaskResult You only need the final result, async context
client.run_sync(prompt, ...) TaskResult You only need the final result, non-async script or Jupyter notebook

All three methods accept the same parameters:

Parameter Type Default Description
prompt str — Task description
config GLComputerUseConfig | None None Per-call config override
timeout float | None None Max seconds (falls back to config.timeout)
files list[File] | None None Files to upload to the sandbox before the task
retrieve_files list[str] | None None Sandbox paths to download after completion
on_takeover_needed Callable | None None Takeover callback

run_once() and run_sync() raise TaskFailedError / TaskCancelledError directly instead of returning a result with a non-COMPLETED status.


Live Desktop (noVNC)

When using the E2B sandbox, a noVNC HTTP endpoint is started alongside the desktop. The SDK waits until that endpoint is reachable before surfacing the URL.

# Option A — pre-iteration attribute
stream = await client.run("do something")
print(stream.stream_url)

# Option B — first SANDBOX_READY event
async for event in stream:
    if event.event_type == "SANDBOX_READY" and event.stream_url:
        webbrowser.open(event.stream_url)

Takeover

Pass on_takeover_needed to run() / run_once() / run_sync(). The agent pauses when a takeover condition is detected, and your callback receives a TakeoverContext with the session state; return TakeoverResponse(resume=True, message=...) to continue (with optional guidance for the agent) or TakeoverResponse(resume=False) to cancel the task outright.

When a callback is supplied, TakeoverRequiredError is raised directly out of the event stream (async for event in stream), rather than surfaced as a terminal event, in two cases: the callback returned resume=False, or the callback itself raised. In the second case, the original exception is chained as __cause__. The sandbox is torn down before the error propagates. Wrap your loop in try/except TakeoverRequiredError to handle a deliberate cancellation cleanly, and check exc.__cause__ to tell a decline apart from a callback bug. See examples/takeover.py (which does this) and examples/takeover_caller_initiated.py.

With no callback, behavior depends on the entry point:

  • run() yields the TAKEOVER_STARTED event to your loop, and the agent stays paused. Resume it yourself with await client.takeover_resume(task_id, message=...). Otherwise no further events arrive, and the stream raises GLTimeoutError (tearing down the sandbox) once timeout elapses. No TakeoverRequiredError is raised.
  • run_once() / run_sync() auto-resume by default (auto_resume_on_takeover=True). With auto_resume_on_takeover=False, they behave like run() above: the paused agent ends in GLTimeoutError.

Sensitive Data

Pass sensitive_data to give an agent a credential to type without the raw value ever reaching the LLM:

config = GLComputerUseConfig(sensitive_data={"x_username": "alice", "x_password": "hunter2"})
client = GLComputerUseClient(config=config)

async for event in await client.run("Log in with <secret>x_username</secret> / <secret>x_password</secret>"):
    ...

The model is told only the placeholder key names (x_username, x_password), never their values. When the model types <secret>x_password</secret> — or, as a fallback for when it drops the tags, the bare exact key name x_password on its own — the SDK swaps in the real value right before the keystrokes reach the sandbox. This mirrors gl-browser-use's sensitive_data field of the same name and shape.

This is a real gap, not a nice-to-have caveat: sensitive_data is flat and global, with no domain/app scoping. A browser page has a URL to scope a secret against; a desktop session has no equivalent boundary (a window title was considered and rejected — a malicious app could set its own title). Any configured secret is usable by any type action for the whole task, regardless of which window or app it lands in. Do not combine sensitive_data with a task that also has the agent interact with untrusted or attacker-controlled content (e.g. an arbitrary webpage), since nothing stops a prompt-injected instruction from directing a configured secret into it.

Neither the tagged nor the bare-fallback form is foolproof — a model that paraphrases instead of using one of the two recognized forms (e.g. describing the password in prose) won't trigger substitution, and there's no detection for that failure mode today. Validate empirically against your chosen model/agent before relying on this for a real credential.

TOTP/MFA live code generation is not handled by sensitive_data — tracked separately, see GDP-ADMIN/gl-sdk#6867.

sensitive_data is excluded from GLComputerUseConfig's repr() and model_dump(), so it won't leak into logs or config serialization.


Snapshot & Resume

A session can be paused — its sandbox state, and the agent's conversation trajectory where the agent supports it (see Agent support below) — and later resumed from a ResumeToken. This is fully backward-compatible: the teardown disposition defaults to destroy, so existing callers are unaffected.

Pass disposition="snapshot" to capture a token, then pass it back via resume_from:

client = GLComputerUseClient()

# 1. Run and snapshot instead of destroying the sandbox.
result = await client.run_once("Open Firefox and log into the dashboard", disposition="snapshot")
token = result.resume_token            # a ResumeToken; token.to_json() to persist it

# 2. Later — resume from where it left off.
result = await client.run_once("Now download this month's report", resume_from=token)

resume_from accepts a ResumeToken, its dict, or its JSON-string form. Set GLCU_DEFAULT_DISPOSITION=snapshot to snapshot by default without passing the argument each call.

Artifact store requirement

Snapshot history is written through the artifact store, so the store must support history persistence. The built-in local and minio stores do; a custom store must set the class attribute supports_history = True and implement save_history / load_history / delete_history.

If the store does not support history, the snapshot still captures the desktop but the token is emitted with history_ref=None — the restored sandbox has no agent memory. By default this is logged at ERROR and the run continues. Set strict_snapshot=True (GLCU_STRICT_SNAPSHOT=true) to turn it — and any history-persistence or snapshot-capture failure — into a hard ConfigError / SnapshotError instead. A task that already failed or was cancelled keeps its original error; the snapshot failure is then reported in TaskResult.warnings instead of replacing it.

Agent support

Trajectory capture/replay is opt-in per agent (BaseAgent.supports_trajectory_persistence), not automatic. agent="openai" (the default) implements it — resume seeds previous_response_id from the prior run, so the model retains memory of the conversation, subject to OpenAI's response-retention window (~30 days; a resume attempted after that fails with a 404 from OpenAI). Agent-S (agent="agents", temporarily disabled) does not implement it. With Agent-S, snapshotting still preserves the desktop and emits a token, but history_ref is always None and the agent starts the next run_once() with no memory of the prior conversation. By default this is logged at WARNING and the run continues (agent_trajectory_not_persisted on snapshot, agent_trajectory_not_supported on resume of a token that does carry history from another agent). Set strict_snapshot=True to turn both into a hard error instead — SnapshotError on snapshot, ResumeError on resume — rather than silently continuing with no agent memory; the sandbox is still torn down cleanly either way. A custom agent can opt in by setting supports_trajectory_persistence = True and wiring ctx._resume_history (seed) / ctx._captured_history (capture) itself.

Trajectory growth

Every resume re-feeds the full trajectory to the model, and it grows without bound by default. To keep cost and payload size in check:

  • trajectory_max_images (GLCU_TRAJECTORY_MAX_IMAGES, default None = unbounded) — reserved for a future agent that implements image-capped trajectory replay. No built-in agent does today, so setting this to anything other than None raises ConfigError at config time rather than silently no-op'ing.
  • trajectory_max_bytes (GLCU_TRAJECTORY_MAX_BYTES, default None) — a hard ceiling on the persisted trajectory JSON. When exceeded, the snapshot path fails before writing (raising SnapshotError under strict_snapshot, otherwise skipping history). Only takes effect for an agent that captures trajectory in the first place (see Agent support).
  • trajectory_pii_anonymization (GLCU_TRAJECTORY_PII_ANONYMIZATION, default False) — reserved for a future agent that anonymizes PII within the captured trajectory itself. No built-in agent does today, so setting this to True raises ConfigError at config time. This is unrelated to GLCU_PII_REDACTION_ENABLED (see Observability), which is implemented and redacts PII from log lines, not from the trajectory replayed back to the model.

Provider semantics

  • E2B takes true copy-on-write snapshots; predecessor snapshots are garbage-collected as a thread advances (set keep_snapshot_history=True to retain every snapshot for branching/forking).
  • OpenSandbox pauses and resumes the same container — delete_snapshot is a no-op there. Paused threads accumulate and remain billable until explicitly released, so release sessions you no longer intend to resume.

Resuming a token whose model differs from the configured model raises ResumeError, because trajectory/image/caching formats differ across providers. Set allow_model_drift=true (GLCU_ALLOW_MODEL_DRIFT=true) to bypass that check at your own risk; a compatibility warning is logged.


Errors

Every SDK exception is a gllm_core.exceptions.BaseError rooted at GLComputerUseError, and carries:

  • code — a stable, machine-readable GL-CUA-* code. Branch on it (or the class), never on the message text.
  • str(error) / error.message — a reviewed message: what happened, then what to do next. It never contains provider output, internal URLs, or typed text.
  • error.debug_info — sanitized diagnostics for engineers and agents: class_name, error_type, original_message, operation, and (when an exception was caught) traceback. The run's sensitive_data values are masked as ***.

TaskFailedError chains the SDK error that failed the task as __cause__; read its code with error.cause_code. TaskFailedError, TaskCancelledError, TakeoverRequiredError (all TaskError), and GLTimeoutError (not a TaskError) also carry result (partial usage and progress) and resume_token when one was captured. On the streaming API, a TASK_FAILED event carries the same information as event.error_code and event.exception.

Code Class Retried by the SDK Raised when
GL-CUA-INTERNAL InternalError no An unclassified failure escaped the agent loop (original exception chained)
GL-CUA-CONFIG ConfigError no Configuration is missing, invalid, or out of range
GL-CUA-MISSING-DEPENDENCY MissingDependencyError (also ImportError) no The selected provider's optional package is not installed
GL-CUA-SANDBOX-PROVISION SandboxProvisionError yes Sandbox creation timed out, hit a 5xx, or a transport error
GL-CUA-SANDBOX-AUTH SandboxAuthError no The sandbox provider rejected the credentials (401/403)
GL-CUA-SANDBOX-RATE-LIMIT SandboxRateLimitError yes The sandbox provider rate limited creation (429)
GL-CUA-SANDBOX-REJECTED SandboxRejectedError no The sandbox provider rejected the request (other 4xx: template, image, quota)
GL-CUA-SNAPSHOT SnapshotError — A snapshot or trajectory persistence failed (raised only under strict_snapshot)
GL-CUA-RESUME ResumeError — A resume token could not be restored (missing snapshot, model drift)
GL-CUA-RESUME-TOKEN-INVALID InvalidResumeTokenError (also ValueError, TypeError) — A resume token could not be parsed
GL-CUA-PROVIDER-MISMATCH ProviderMismatchError — A resume token belongs to a different sandbox provider
GL-CUA-AGENT AgentError — The agent could not complete the task
GL-CUA-AGENT-STEP-LIMIT AgentStepLimitError — The agent exhausted its step budget
GL-CUA-DESKTOP DesktopError (also RuntimeError) once A desktop action, sandbox command, or file transfer failed
GL-CUA-TASK-FAILED TaskFailedError — The task ended with TASK_FAILED (see cause_code)
GL-CUA-TASK-CANCELLED TaskCancelledError — The task was cancelled
GL-CUA-TASK-TIMEOUT GLTimeoutError — No task event arrived within timeout; the agent was cancelled
GL-CUA-TAKEOVER-REQUIRED TakeoverRequiredError — on_takeover_needed returned resume=False or raised (exception chained); see Takeover
GL-CUA-TAKEOVER-IN-PROGRESS TakeoverInProgressError — A takeover is already active for the task
GL-CUA-TAKEOVER-NOT-ACTIVE TakeoverNotActiveError — Resuming a takeover that is not active
GL-CUA-SESSION-BUSY SessionBusyError — The session is already running a task
GL-CUA-SESSION-NOT-READY SessionNotReadyError — The session sandbox is not prepared yet
GL-CUA-SESSION-NOT-FOUND SessionNotFoundError (also KeyError) — No session owns the task, or it was released
GL-CUA-TASK-NOT-FOUND TaskNotFoundError (also KeyError) — The task id is not registered in the session
GL-CUA-SESSION-STATE-INVALID InvalidStateTransitionError — The session cannot perform the operation in its current state
GL-CUA-ARTIFACT-STORE ArtifactStoreError yes An object-store transfer failed (transport, 5xx)
GL-CUA-ARTIFACT-CONFIG ArtifactConfigError no The object store rejected the credentials, bucket, or prefix
GL-CUA-CONNECTION GLConnectionError — Deprecated; never raised

Degraded outcomes that do not fail the task — a recording or retrieved file that could not be uploaded, or a snapshot that failed on an already-failed task — are reported in TaskResult.warnings as TaskWarning(code, message, operation).

from gl_computer_use import GLComputerUseClient, GLComputerUseError, TaskFailedError

try:
    result = await GLComputerUseClient().run_once("do something", timeout=60.0)
except TaskFailedError as e:
    print(e.code, e.cause_code, e)        # GL-CUA-TASK-FAILED GL-CUA-DESKTOP The desktop action could not ...
    report(e.debug_info)                  # sanitized; safe for logs and Sentry
except GLComputerUseError as e:
    print(e.code, e)

Observability

The SDK uses structlog for structured logging (JSON by default; set GLCU_LOG_FORMAT=console for human-readable output). Every line carries session_id, task_id, and component. Distributed tracing and metrics via OTLP, plus Sentry error tracking, are available through the observability extra and delegated to GDP Labs' gl-observability SDK. SDK log lines go through stdlib logging under gl_computer_use.<component> and propagate to your handlers; the SDK attaches only a NullHandler, and when your app has not configured logging it falls back to a plain console handler on stderr. The run's sensitive_data values are always masked as *** in log lines, debug_info, and span exceptions; regex PII redaction from gl-observability is on by default (GLCU_PII_REDACTION_ENABLED, needs the observability extra). Failed spans carry an error.code attribute.

Logging contract: one failure, one record, at your boundary.

Who Does
SDK Raises one GL-CUA-* error (provider failures are translated once, where the provider is called). Logs failures it re-raises as WARNING breadcrumbs with the code and no traceback (task_failed). Never logs at ERROR for them.
SDK, degraded outcome (task succeeded) Logs a WARNING and returns it in TaskResult.warnings (snapshot, history, recording, file retrieval). Whether that warrants an Issue is your call.
Provider SDKs (OpenSandbox, E2B, botocore) May log their own ERROR before raising; sentry_before_send drops those events, so they stay breadcrumbs.
Your app Records the error once where it stops propagating, as logger.error(error.code, exc_info=error, extra={"reference_id": ...}), and shows the user str(error) plus the reference id.

Sentry. With GLCU_SENTRY_ENABLED=true and GLCU_SENTRY_DSN, the SDK initialises Sentry through gl-observability and installs sentry_before_send on it (with include_local_variables=False, so frame locals such as typed text never leave the process). Your app does not import sentry_sdk: log the error once at your boundary and Sentry's logging integration turns it into an Issue.

import logging, uuid
from gl_computer_use import GLComputerUseError

try:
    result = await client.run_once(prompt)
except GLComputerUseError as error:
    reference_id = f"ref_{uuid.uuid4().hex[:12]}"
    logging.getLogger("my_app").error(
        error.code, exc_info=error, extra={"reference_id": reference_id, "error_code": error.code}
    )
    show_user(f"{error} Reference: {reference_id}")  # reviewed message only

sentry_before_send keeps one failure to one Issue by dropping the wrapped provider SDKs' own ERROR logs (OpenSandbox, E2B, botocore; they stay as breadcrumbs), groups each Issue by root cause (fingerprint [error.code, error.cause_code], also for app errors that wrap an SDK error), adds searchable error.code / error.cause_code / reference_id tags and the sanitized debug_info context, and masks the run's secret values and PII in exception messages, log messages, breadcrumbs, extra, and contexts — error events that gl-observability's own masking does not cover. If your app initialises Sentry itself, pass it there: SentryBackendConfig(dsn=..., before_send=sentry_before_send) (call it from your own hook if you already have one). See examples/sentry_tracing.py.


Custom Providers

Plug in alternative sandboxes, agents, or artifact stores without modifying the SDK:

from gl_computer_use import register_sandbox, GLComputerUseClient, GLComputerUseConfig
from gl_computer_use.sandbox.base import BaseSandbox


class MyCustomSandbox(BaseSandbox):
    ...  # implement abstract methods


register_sandbox("my-sandbox", MyCustomSandbox)
client = GLComputerUseClient(config=GLComputerUseConfig(sandbox="my-sandbox"))

register_agent and register_artifact work the same way for custom agents and artifact stores.


Local Development Setup

git clone git@github.com:GDP-ADMIN/gl-sdk.git
cd gl-sdk/libs/gl-computer-use
uv sync --all-extras
uv run gl-computer-use-setup
source .venv/bin/activate

Run checks:

uv run pytest           # tests
uv run ruff check .     # lint
uv run ruff check --fix # auto-fix lint
uv run mypy gl_computer_use/  # type-check

Contributing

Please refer to the Python Style Guide for code style, documentation standards, and SCA requirements.

Metadata

Release files for gl-computer-use-binary 0.0.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for gl-computer-use-binary 0.0.7
File
gl_computer_use_binary-0.0.7-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
gl_computer_use_binary-0.0.7-cp313-cp313-manylinux_2_31_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.31+ x86-64 Details
gl_computer_use_binary-0.0.7-cp313-cp313-macosx_13_0_arm64.whl CPython 3.13 CPython 3.13 macOS 13.0+ ARM64 Details
gl_computer_use_binary-0.0.7-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
gl_computer_use_binary-0.0.7-cp312-cp312-manylinux_2_31_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.31+ x86-64 Details
gl_computer_use_binary-0.0.7-cp312-cp312-macosx_13_0_arm64.whl CPython 3.12 CPython 3.12 macOS 13.0+ ARM64 Details
gl_computer_use_binary-0.0.7-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
gl_computer_use_binary-0.0.7-cp311-cp311-manylinux_2_31_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.31+ x86-64 Details
gl_computer_use_binary-0.0.7-cp311-cp311-macosx_13_0_arm64.whl CPython 3.11 CPython 3.11 macOS 13.0+ ARM64 Details

Total release size: 11.1 MB

Release files / gl_computer_use_binary-0.0.7-cp313-cp313-win_amd64.whl

Download URL gl_computer_use_binary-0.0.7-cp313-cp313-win_amd64.whl
Size 1.0 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
46620736bb1aa42e2675290defd4c79e3d43e5ea5aea4cd409024b0085bd71ab
BLAKE2b-256 checksum
How to use checksums
ad93776aa3334e946c1061d0e24dd590d845ac2de01568141bff902506dae41e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / gl_computer_use_binary-0.0.7-cp313-cp313-manylinux_2_31_x86_64.whl

Download URL gl_computer_use_binary-0.0.7-cp313-cp313-manylinux_2_31_x86_64.whl
Size 1.5 MB
Tags CPython 3.13 Linux glibc 2.31+ x86-64
SHA-256 checksum
How to use checksums
a2189370410d720f62a570d3a02efd5a582c16d7c02a8bdd5ad1c782b53ea1c8
BLAKE2b-256 checksum
How to use checksums
9790fac493208051018964c9bb8a8591240ec1d987356101e143a930a4d6d9ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.8.24

Release files / gl_computer_use_binary-0.0.7-cp313-cp313-macosx_13_0_arm64.whl

Download URL gl_computer_use_binary-0.0.7-cp313-cp313-macosx_13_0_arm64.whl
Size 1.2 MB
Tags CPython 3.13 macOS 13.0+ ARM64
SHA-256 checksum
How to use checksums
2d464b297d6feb5abcf71d1e60ceb6e2a1d5bf820b8af1ec3cf4a96545fa9f9c
BLAKE2b-256 checksum
How to use checksums
c7639b75289b6a31a54cf4abe4eef43ddb73e35802de6dfa32b8e9bb45ece479
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / gl_computer_use_binary-0.0.7-cp312-cp312-win_amd64.whl

Download URL gl_computer_use_binary-0.0.7-cp312-cp312-win_amd64.whl
Size 1.0 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
4525c3930e7bffc4e0e68802fb79e6284aad6109b85ff6b34ba8400a8b7d4401
BLAKE2b-256 checksum
How to use checksums
87189dd459ca8c2b58f7b55f57ddb03bf2e1e11bc5b535d575df848029c37c49
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / gl_computer_use_binary-0.0.7-cp312-cp312-manylinux_2_31_x86_64.whl

Download URL gl_computer_use_binary-0.0.7-cp312-cp312-manylinux_2_31_x86_64.whl
Size 1.5 MB
Tags CPython 3.12 Linux glibc 2.31+ x86-64
SHA-256 checksum
How to use checksums
9aa60e8aa80ba7118bd22d1f70cb7af5e273060e6367abc3cf05d9dc3c3a26d7
BLAKE2b-256 checksum
How to use checksums
94d5b2caaa94d20b338782ed3930f3f44c59524c9090b72798e7d554c9031b9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.8.24

Release files / gl_computer_use_binary-0.0.7-cp312-cp312-macosx_13_0_arm64.whl

Download URL gl_computer_use_binary-0.0.7-cp312-cp312-macosx_13_0_arm64.whl
Size 1.2 MB
Tags CPython 3.12 macOS 13.0+ ARM64
SHA-256 checksum
How to use checksums
11320e8b5e2b5cfc0a22422b404bd5a17545bb8bcb834ed6585671a2892db33d
BLAKE2b-256 checksum
How to use checksums
defe3763cb76da46f8ae564f4bf5a8b05c59b858aea3a947834619bc0243602b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / gl_computer_use_binary-0.0.7-cp311-cp311-win_amd64.whl

Download URL gl_computer_use_binary-0.0.7-cp311-cp311-win_amd64.whl
Size 1.1 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
824b817c50ef186c1f2a63fa1894477caaae257b54576b5836415eba551a4d3d
BLAKE2b-256 checksum
How to use checksums
aba394e5a302725d8dc9412b0faaa62ced0fc711e807be4b691f52df93fc91ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / gl_computer_use_binary-0.0.7-cp311-cp311-manylinux_2_31_x86_64.whl

Download URL gl_computer_use_binary-0.0.7-cp311-cp311-manylinux_2_31_x86_64.whl
Size 1.4 MB
Tags CPython 3.11 Linux glibc 2.31+ x86-64
SHA-256 checksum
How to use checksums
9e2f531a7b1f0b6b76b2ebfcfbb56bfcdf6bf6b50c7b5da8caf10429ffa4d5a3
BLAKE2b-256 checksum
How to use checksums
84b6f0b5b31007bd05b2fff4eda47e26ba8c5cffbeb13060fd77d07228c8af3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.8.24

Release files / gl_computer_use_binary-0.0.7-cp311-cp311-macosx_13_0_arm64.whl

Download URL gl_computer_use_binary-0.0.7-cp311-cp311-macosx_13_0_arm64.whl
Size 1.2 MB
Tags CPython 3.11 macOS 13.0+ ARM64
SHA-256 checksum
How to use checksums
780ef5a8f268536d33432fcfb74b82692c6fd7d1a4eed3741f49db36d4ca59d2
BLAKE2b-256 checksum
How to use checksums
f847a1cd20429ea0cf713dde33c07fba5a51c64d108fda4d2669423598c8370f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page