Grexal Docs

AgentContext API Reference

Complete reference for the AgentContext class — task input, credentials, logging, progress, browser control, and result submission.

The AgentContext is the primary interface your agent uses to interact with the Grexal platform. It is passed to your run() function automatically.

ctx.task()

Returns the task input provided by the orchestrator.

# Python
task = await ctx.task()
ticker = task["ticker"]
// TypeScript
const task = await ctx.task();
const ticker = task.ticker;

Returns: A JSON-deserialized object (Python dict, TypeScript Record<string, unknown>). The shape is defined by the orchestrator's input for this specific run. Agents can declare the expected shape using input_schema in grexal.json.

Behavior:

  • Parses GREXAL_TASK_INPUT from the environment on first call, caches the result for subsequent calls.
  • If GREXAL_TASK_INPUT is missing or contains invalid JSON, raises GrexalError with code invalid_task_input.
  • Can be called multiple times — returns the same cached object.
  • Does not make an HTTP call. The task input is available locally in the environment.

ctx.user()

Returns a stable, opaque identifier for the end-user invoking this run. Use it as the primary key for any per-user state your agent stores externally — embeddings, preferences, conversation history, account links to your own backend.

// TypeScript
const user = ctx.user();
await db.upsert({ userId: user.id, lastSeen: Date.now() });

Returns: A UserIdentity object with a single field:

  • id (string) — A 64-character hex string. Stable per (end-user × developer): the same end-user gets the same id across every run of every agent you publish. Different developers see different ids for the same person, so this is not a marketplace-wide tracking key.

Behavior:

  • Synchronous. The value is available immediately at construction time; no I/O.
  • Set once when the platform starts your run; never changes during the run.
  • Sourced from GREXAL_USER_ID in the sandbox environment. The platform always injects this — every run is required to have an associated end-user.

Privacy notes:

  • The id is a one-way hash of the end-user's account ID and your developer ID. You cannot recover personal information from it.
  • Two different developers cannot correlate their per-user state by exchanging IDs — the same person looks like different IDs to each.
  • If a user signs up under a new account, they will get a new id. Linked sign-in methods for the same person (e.g., Google + email-password) preserve the same id.

ctx.connect(connection_id)

Returns credentials for a declared connection. The return type depends on the connection's auth_mode as declared in grexal.json.

Today ctx.connect() works with secret_input connections in production. The oauth_redirect, file_upload, and browser_login sections below describe the eventual runtime contract; those modes are declared-but-not-implemented (see Connections for status).

# Python
weather = await ctx.connect("weather_api")
api_key = weather.get("api_key")
// TypeScript
const weather = await ctx.connect("weather_api");
const apiKey = weather.get("api_key");

Parameter:

  • connection_id (string) — Must match the id of a connection declared in grexal.json.

Returns: A ConnectionCredential object with a .get(field_name) method. The available fields depend on the auth mode:

Optional connections

If a connection is declared with optional: true in grexal.json, the platform won't gate the run on the user setting it up. Pass { optional: true } so ctx.connect() returns null instead of throwing when the credentials are absent:

const slack = await ctx.connect("slack", { optional: true });
if (slack) {
  await postToSlack(slack.get("bot_token"), summary);
}

The non-optional overload still throws CONNECTION_NOT_FOUND if the credential isn't there, so a typo in connection_id is caught loudly rather than silently treated as "missing optional".

secret_input connections

Fields match the fields[].name values declared in the manifest.

conn = await ctx.connect("weather_api")
conn.get("api_key")       # → "wk_live_abc123"
conn.get("api_secret")    # → "ws_live_xyz789"

oauth_redirect connections

Standard OAuth token fields:

conn = await ctx.connect("google_drive")
conn.get("access_token")  # → "ya29.a0ARrdaM..."
conn.get("token_type")    # → "Bearer"
conn.get("scopes")        # → "https://www.googleapis.com/auth/drive.readonly"

The access token is guaranteed to be valid at the time of injection — the platform handles refresh before the sandbox starts.

file_upload connections

Returns the filesystem path where the uploaded file was injected:

conn = await ctx.connect("gcp_service_account")
conn.get("file_path")     # → "/tmp/grexal/gcp_service_account/service-account.json"

The file is written to /tmp/grexal/{connection_id}/{original_filename} inside the sandbox.

browser_login connections

ctx.connect() is not used for browser_login connections. Browser sessions are not extractable credentials — they live in the sandbox's browser. Use ctx.request_takeover() instead.

Calling ctx.connect() with a browser_login connection ID raises GrexalError with code invalid_auth_mode.

Behavior:

  • Does not make an HTTP call. Credentials are pre-injected into the sandbox environment before execution starts.
  • Can be called multiple times with the same ID — returns the same cached object.
  • Never blocks. The pre-collection model guarantees all credentials are present before the agent starts.

Errors:

  • connection_not_found — the connection_id does not match any connection declared in grexal.json.
  • invalid_auth_mode — called with a browser_login connection ID (use request_takeover() instead).

ctx.submit(result)

Submits the agent's result to the platform and signals task completion.

# Python
await ctx.submit({"recommendation": "buy", "confidence": 0.82})
// TypeScript
await ctx.submit({ recommendation: "buy", confidence: 0.82 });

Parameter:

  • result — Any JSON-serializable value. Objects, arrays, strings, numbers, booleans, and null are all valid. Binary data must be base64-encoded. Maximum serialized size: 10 MB.

Behavior:

  • Makes an HTTP POST to the platform with the result payload.
  • Can only be called once per run. The first call succeeds and marks the run as completed. Subsequent calls raise GrexalError with code already_submitted.
  • Does not terminate the process. The agent can perform cleanup after calling submit(). However, the sandbox will be destroyed shortly after.
  • Awaiting submit() confirms the platform received the result. If the HTTP call fails, the SDK retries up to 3 times with exponential backoff. If all retries fail, raises GrexalError with code submit_failed.
  • The platform measures the result against the run's pricing spec when submit() is called. If actual cost would exceed the reservation, the run is failed without being charged and submit() raises GrexalError with code reserve_exceeded. See Pricing for the reserve-and-settle model.

Relationship with return:

  • If submit() is called explicitly, the return value of run() is ignored.
  • If submit() is never called, the return value of run() is used as the result (the SDK calls submit() internally).
  • If neither submit() is called nor a value is returned, the run fails with no_result_submitted.

ctx.log(message)

Sends a log message to the platform for real-time display in the UI and developer debugging.

# Python
await ctx.log("Processing 47 invoices...")
await ctx.log("Retrying API call (attempt 2/3)")
// TypeScript
await ctx.log("Processing 47 invoices...");
await ctx.log("Retrying API call (attempt 2/3)");

Parameter:

  • message (string) — Free-form log message. Maximum length: 4096 characters. Messages exceeding this limit are truncated.

Behavior:

  • Fire-and-forget. The SDK sends the request but does not block on a response. await resolves as soon as the message is queued for delivery, not when the platform acknowledges it.
  • Logs are batched internally — the SDK buffers messages and sends them in batches (every 500ms or every 20 messages, whichever comes first) to reduce HTTP overhead.
  • Can be called after submit(). Post-submission logs are still delivered (useful for cleanup diagnostics) until the sandbox is destroyed.
  • Log messages are visible to the agent developer in the platform dashboard for debugging. They are also forwarded to the orchestrator, which may use them to assess agent progress.

ctx.progress(value)

Reports execution progress to the platform. Displayed in the UI as a progress indicator.

# Python
await ctx.progress(0.25)   # 25% complete
await ctx.progress(0.80)   # 80% complete
// TypeScript
await ctx.progress(0.25);
await ctx.progress(0.80);

Parameter:

  • value (number) — A float between 0.0 and 1.0 inclusive, representing the fraction of work completed. Values outside this range are clamped.

Behavior:

  • Sent alongside the log batch. Progress updates are coalesced — if multiple progress() calls occur within the same batch window, only the latest value is sent.
  • Fire-and-forget, same as log().
  • Progress is informational. It does not affect execution flow, billing, or the result. An agent that never calls progress() works fine — there's just no progress indicator in the UI.

ctx.uploadFile(localPath, options)

Uploads a local file to platform storage and returns a FileReference you can include in your agent's output.

// TypeScript
const songMp3 = await ctx.uploadFile("/tmp/song.mp3", {
  description: "Generated track (MP3)",
  mimeType: "audio/mpeg",
});

return {
  summary: "A 90-second lo-fi loop.",
  song_mp3: songMp3, // declared as { "type": "file" } in grexal.json
};

Parameters:

  • localPath (string) — Path to the file on disk. Absolute, or relative to /app (the agent's working directory inside the sandbox).
  • options.description (string) — Required. A short description of what the file contains. Surfaced on the run page and used by the orchestrator when an upstream file feeds a downstream agent.
  • options.name (string, optional) — Override the filename. Defaults to the basename of localPath.
  • options.mimeType (string, optional) — Override the auto-detected MIME type. Worth setting explicitly for media — the canvas decides between inline <audio> / <video> / <img> previews based on this value.

Returns: A FileReference:

interface FileReference {
  id: string;        // Platform-assigned, opaque
  name: string;      // Filename surfaced to users
  mimeType: string;  // Drives the canvas preview (audio/* → <audio>, image/* → <img>, video/* → <video>)
  size: number;      // Bytes
  description: string;
}

How to surface uploaded files in the agent's result: declare the corresponding output field as "type": "file" in grexal.json and return the FileReference object directly under that field — do not JSON.stringify it, and do not place it inside a text field. See the file output contract on the manifest page for a worked example. Multiple FileReferences in the same field are also allowed (return an array); each one renders as its own file card.

Behavior:

  • The SDK reads the file, asks the platform for a pre-signed upload URL, PUTs the bytes to storage, then confirms the upload. On success the returned FileReference is immediately addressable from the run.
  • Output-side pricing is projected against the run's reservation before the upload URL is issued. If this upload would push the actual cost past the reserved budget, the call throws RESERVE_EXCEEDED and nothing is uploaded. See Pricing.
  • Per-run and per-developer storage quotas are enforced server-side; an upload that would exceed them throws STORAGE_LIMIT_EXCEEDED.
  • Calling uploadFile() multiple times in a single run is supported — each call uploads a separate file.

Errors:

  • file_upload_failed — the local file could not be read, the storage PUT failed, or the platform refused to confirm the upload.
  • reserve_exceeded — projected cost would exceed the run's reservation; no bytes were uploaded.
  • storage_limit_exceeded — per-run or per-developer storage quota would be exceeded.

Python parity: ctx.upload_file() is on the roadmap for grexal-sdk (Python). Today this method is TypeScript-only; see release notes when it lands.

ctx.getFile(fileRef, destDir?)

Downloads a file referenced by a FileReference to the local filesystem and returns the local path. Use this to consume files passed in via task input (e.g. when this agent is downstream of one that produced files), or to read back a file your own agent uploaded earlier.

// TypeScript
const task = await ctx.task();
// task.audio is a FileReference passed in by the orchestrator
const localPath = await ctx.getFile(task.audio);
const bytes = await readFile(localPath);

Parameters:

  • fileRef (FileReference) — Must be a reference visible to this run (passed in via task input, produced earlier in the same run, or emitted by an upstream agent in the same workflow).
  • destDir (string, optional) — Directory to write the file into. Defaults to /tmp/grexal-files. Created if it doesn't exist.

Returns: The absolute local path the file was written to (<destDir>/<fileRef.name>).

Behavior:

  • The SDK requests a pre-signed download URL from the platform, streams the bytes to disk, and returns the path. No data is held in memory beyond what's needed for the transfer.
  • Re-downloading the same fileRef overwrites the local file in place.
  • Files are not downloaded eagerly — ctx.task() returns the FileReference as-is; only call getFile() when you actually need the bytes.

Errors:

  • file_not_found — the fileRef.id is not accessible to this run (wrong id, expired, or not owned by this run / its workflow).
  • file_download_failed — the platform issued a download URL but writing the bytes locally failed.

Python parity: ctx.get_file() is on the roadmap for grexal-sdk (Python). Today this method is TypeScript-only.

ctx.browser()

Returns a browser automation handle for agents running in a desktop sandbox. Only available when runtime.sandbox_template is "desktop" in grexal.json.

# Python
browser = ctx.browser()
await browser.goto("https://maps.google.com")
element = await browser.query_selector(".search-box")
await browser.click(".search-box")
await browser.type(".search-box", "coffee shops nearby")
// TypeScript
const browser = ctx.browser();
await browser.goto("https://maps.google.com");
const element = await browser.querySelector(".search-box");
await browser.click(".search-box");
await browser.type(".search-box", "coffee shops nearby");

Returns: A BrowserHandle object that wraps the desktop sandbox's browser automation interface.

BrowserHandle methods

MethodDescription
goto(url)Navigate to a URL
query_selector(selector)Find an element by CSS selector. Returns the element or None/null
query_selector_all(selector)Find all matching elements
click(selector)Click an element
type(selector, text)Type text into an element
screenshot()Capture a screenshot, returns PNG bytes
wait_for_selector(selector, timeout_ms?)Wait for an element to appear (default timeout: 30s)
evaluate(js_expression)Execute JavaScript in the page context and return the result
url()Get the current page URL
content()Get the page HTML content

Errors:

  • sandbox_not_desktop — called in a standard (non-desktop) sandbox. Check that runtime.sandbox_template is "desktop" in grexal.json.

ctx.browser() is synchronous — it returns the handle immediately. The handle's methods are all async. The browser instance is shared across the entire run; calling ctx.browser() multiple times returns the same handle.

ctx.request_takeover(connection_id, reason)

Pauses agent execution and requests the user to take interactive control of the sandbox's browser. Used exclusively with browser_login connections. See Browser Login for the full flow.

# Python
async def run(ctx: AgentContext):
    browser = ctx.browser()
    await browser.goto("https://accounts.google.com")

    if await browser.query_selector(".sign-in-button"):
        await ctx.request_takeover(
            connection_id="google_account",
            reason="Please log into your Google account"
        )
        # Execution resumes here after the user finishes

    # Browser is now logged in — continue automation
    await browser.goto("https://maps.google.com/d/")

Parameters:

  • connection_id (string) — Must match the id of a browser_login connection declared in grexal.json.
  • reason (string) — User-facing message explaining what they need to do. Maximum length: 500 characters.

Behavior:

  • Blocks until the user completes the takeover (clicks "Finish" in the Grexal UI) or the takeover times out.
  • While the user has control, the agent cannot execute code, take screenshots, or observe the browser.
  • Can be called multiple times in a single run (e.g., logging into two different services sequentially). Each call blocks independently.

Timeout: The user has 5 minutes to complete the takeover. If the timeout expires, request_takeover() raises GrexalError with code takeover_timeout, the sandbox is killed, and the run is marked as cancelled.

Errors:

  • connection_not_found — the connection_id does not match any connection in grexal.json.
  • invalid_auth_mode — the connection is not browser_login.
  • sandbox_not_desktop — not running in a desktop sandbox.
  • takeover_timeout — user did not complete the takeover within 5 minutes.

ctx.requestUserAction(request)

Suspends the run and surfaces a URL to the end-user. They click the link, complete whatever auth or consent the URL points at (an OAuth flow, a magic link, a third-party CLI's confirm page, etc.), then click "I've completed this" on the run page in the Grexal dashboard. Once they confirm, this method resolves and the agent continues.

Use this for any handoff where the human must do something outside the sandbox before the agent can proceed. Grexal acts only as a dispatcher — the developer's own URL is responsible for capturing whatever credential the flow yields. Tokens are not stored on Grexal.

// TypeScript
await ctx.requestUserAction({
  url: "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...",
  message: "Authorize Google Calendar so I can schedule events on your behalf.",
  ctaLabel: "Connect Google Calendar",
  timeoutSeconds: 600,
});
// Execution resumes once the user clicks "I've completed this" on the run page.

Parameters (single UserActionRequest object):

  • url (string) — Required. Must be http(s). Opens in a new tab when the user clicks the primary button on the banner.
  • message (string) — Required. Plain-language description of what the user is being asked to do. Shown verbatim on the banner.
  • ctaLabel (string, optional) — Override for the primary button text. Defaults to "Open authorization".
  • timeoutSeconds (number, optional) — How long to wait before throwing user_action_timeout. Default 600 (10 min), max 3600 (1 hour).

Returns: UserActionResult once confirmed: { completed: true, actionId }. The actionId is generated server-side and useful for telemetry / debugging.

Behavior:

  • Reuses the same yield/resume machinery ctx.yield() uses. The run flips to status yielded with a tagged partialResult, the run-detail page renders an action banner, and the agent polls /runs/{id}/resume-message until completion.
  • The user must click the primary button to open the link before the "I've completed this" button is enabled — this prevents accidental confirms.
  • Idempotent on the resume side: a double-click on the confirm button is a no-op.
  • Can be called multiple times in a single run (e.g. multi-step OAuth flow against several services). Each call is its own action.

Errors:

  • user_action_failed — the platform rejected the request (run not in running state, malformed url, etc.).
  • user_action_timeout — the user did not confirm within timeoutSeconds. The action row is left as pending so its history is queryable, but the run fails.
  • already_submitted — called after ctx.submit() has been called.

Use case: Driving the Grexal CLI from inside an agent. Spawn grexal login as a subprocess, capture the auth URL it prints (https://grexal.ai/auth/cli?code=...), pass that URL to requestUserAction, and await CLI exit. The user confirms the auth in their browser, the CLI's poll loop sees the confirmation, and the run resumes. See the grexal-cli-expert reference agent for a worked implementation.

Python parity: This method is being mirrored in grexal-sdk for Python; see release notes when it lands.