Skip to main content
Run a Claude Code browser agent on a managed Kernel browser pool with a single SDK call. Environment Pools handle provisioning, agent execution, and teardown — you provide a task prompt and a browser profile.

Architecture

What happens under the hood:
  1. You call create_rollout() with an AgentSpec and browser config
  2. Environment Pools claims a browser from your Kernel pool
  3. Claude Code + agent-browser launches inside the browser session
  4. The agent executes your task, interacting with the live website
  5. Results (reward, screenshots, traces) are returned

Prerequisites

  • Python 3.11+
  • uv or pip
  • API keys:
    • SYNTH_API_KEY — your Synth platform key
    • KERNEL_API_KEY — your Kernel API key
    • ANTHROPIC_API_KEY — for Claude Code agent
  • Kernel CLI: npm install -g @onkernel/cli

1. Install the SDK

pip install synth-ai
# or
uv add synth-ai

2. Create a Kernel browser profile

Create a persistent browser profile with saved authentication state:
# Create a named profile
kernel profiles create --name my-site

# Launch a browser to log in manually
kernel browsers create --stealth --profile-name my-site --save-changes
Open the live-view URL that Kernel prints, log in to your target site, then close the browser:
kernel browsers delete <session-id>
Your login cookies are now saved in the my-site profile.

3. Create a browser pool

kernel browser-pools create \
  --name agent-pool \
  --profile-name my-site \
  --stealth \
  --size 5
This pre-warms 5 browser instances with your saved auth state. Rollouts claim one browser each.

4. Run a single agent rollout

import os
from synth_ai.sdk.environment_pools import (
    AgentSpec,
    create_rollout,
    get_rollout,
)
import time

# --- Config ---
SYNTH_API_KEY = os.environ["SYNTH_API_KEY"]
BACKEND_URL = "https://api.usesynth.ai"

# --- Create the rollout ---
result = create_rollout(
    backend_base=BACKEND_URL,
    api_key=SYNTH_API_KEY,
    request={
        "task_ref": {
            "dataset": "my-computer-use-task",
            "task_id": "check-dashboard",
        },
        "agent": AgentSpec.claude_code(
            model_id="claude-sonnet-4-20250514",
        ).model_dump(exclude_none=True),
        "environment": {"backend": "browser"},
        "browser": {
            "task_prompt": "Navigate to the analytics dashboard and export last week's revenue report as CSV.",
            "profile": "my-site",
            "timeout_sec": 120,
            "headless": True,
            "capture_screenshot": True,
        },
        "pool_tags": ["browser", "kernel"],
        "timeouts": {"agent_sec": 120},
    },
)

rollout_id = result.get("rollout_id") or result.get("trial_id")
print(f"Rollout started: {rollout_id}")

# --- Poll until complete ---
while True:
    status = get_rollout(
        rollout_id,
        backend_base=BACKEND_URL,
        api_key=SYNTH_API_KEY,
    )
    state = status.get("status")
    print(f"  Status: {state}")
    if state in ("succeeded", "failed", "cancelled", "error", "completed"):
        break
    time.sleep(5)

# --- Results ---
reward = status.get("reward_primary", 0.0)
print(f"Reward: {reward}")
print(f"Final status: {status.get('status')}")

5. Run multiple seeds in parallel

Environment Pools handles concurrency for you — just submit multiple rollouts:
import concurrent.futures

seeds = [0, 1, 2, 3, 4]

def run_seed(seed: int) -> dict:
    result = create_rollout(
        backend_base=BACKEND_URL,
        api_key=SYNTH_API_KEY,
        request={
            "task_ref": {"dataset": "my-task", "task_id": f"task-{seed}"},
            "agent": AgentSpec.claude_code().model_dump(exclude_none=True),
            "environment": {"backend": "browser"},
            "browser": {
                "task_prompt": f"Complete task variant {seed}",
                "profile": "my-site",
                "timeout_sec": 120,
            },
            "pool_tags": ["browser", "kernel"],
        },
    )
    rollout_id = result.get("rollout_id")
    # poll until done...
    return {"seed": seed, "rollout_id": rollout_id}

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as pool:
    futures = [pool.submit(run_seed, s) for s in seeds]
    for f in concurrent.futures.as_completed(futures):
        print(f.result())
Keep max_workers at or below your Kernel pool size. If you submit more rollouts than available browsers, they will queue until a browser is released.

6. Add LLM verification

Include a verifier model and expected output to get automated reward scoring:
"browser": {
    "task_prompt": "Find the total revenue for Q4 2025 on the dashboard.",
    "profile": "my-site",
    "timeout_sec": 120,
    "verifier_model": "claude-sonnet-4-20250514",
    "expected": "The agent should navigate to the revenue dashboard and report a numeric total.",
},
The verifier compares the agent’s output against the expected description and assigns a reward between 0.0 and 1.0.

Key Concepts

ConceptDescription
Environment PoolPre-provisioned set of browser instances with saved auth state
AgentSpecWhich agent harness to use (claude-code, codex, opencode)
Browser configTask prompt, profile name, timeouts, screenshot capture
Pool tagsRoute rollouts to specific pool types (browser, kernel)
RolloutA single agent execution against one browser instance

Troubleshooting

  • “Pool not found” — Ensure your Kernel pool has matching tags (browser, kernel) or pass pool_id directly in the request.
  • “Auth expired” — Re-login to the Kernel profile and recreate the browser pool.
  • Timeouts — Increase timeout_sec in the browser config and agent_sec in timeouts.
  • Rate limits — Reduce concurrency or increase pool size.

Next Steps

  • Optimize with GEPA: Use this same setup as a task app backend for GEPA optimization to evolve agent skills automatically.
  • Add custom rewards: Implement verifiers that check specific page state or downloaded files.

Ready to get started?