DoAnything
DoAnything is WebAgent's open-ended API. You give one natural-language instruction, and the agent picks its own tools, decides its own path, runs the steps, and does its best to reach the goal in a browser environment.
Unlike DeepResearch / WebSearch / Track — those three are shaped APIs with a fixed artifact shape and a quality contract — DoAnything has no fixed artifact shape: you may not fully know what the goal looks like in the end, so the agent improvises. It therefore makes no quality contract; when you want a contract, use the matching shaped API. The 4 APIs are peers — pick whichever fits what you want.
When to use it
- The goal is undefined or compound, and the agent needs to decide how to do it.
- The task spans multiple steps and pages, and may need login state, human confirmation, or long-running execution.
- You accept "best effort" rather than a fixed-shape result contract.
When you want a fixed-shape artifact, use DeepResearch / WebSearch / Track.
Resource model
A session is one runtime container — it owns the browser, the profile (cookies / login state), and the workspace (file system). A run is one instruction that runs inside a session. You can fire follow-up runs at the same session; they share the state the previous run left behind.
project
└── session id: sess_…
├── browser, profile, workspace
└── run id: run_…
├── instructions "Search Hacker News..."
├── status running | done | …
└── events (SSE) run.status_changed, run.message, …Lifecycle (seven states)
| State | Meaning | Next moves |
|---|---|---|
pending | Accepted, queued for an agent slot | → running, canceled |
running | Agent is actively working | → done, failed, awaiting_input, paused, canceled |
awaiting_input | Agent paused itself; needs you to answer | → running (via intervene) |
paused | You paused it (manual) | → running (via resume), canceled |
done | Completed successfully; output populated | terminal |
failed | Hit an error; error.code and error.detail populated | terminal |
canceled | You canceled (or scheduled max-duration tripped) | terminal |
A run in any non-terminal state holds session resources. Cap with max_duration_minutes to bound that.
Submitting a run
The first run is submitted together with the CreateSessionRequest when you create a session. Request fields:
| Field | Type | Required | Notes |
|---|---|---|---|
instructions | string | Yes | The instruction in plain English. Up to 10 000 chars. |
max_duration_minutes | int | No | 1–10 080 (one week). |
recording | object | No | {enabled, quality, capture_during_take_control}; omit for off. |
keep_alive | bool | No | When the run ends, keep the session warm for follow-up runs. |
allowed_actions | string[] | No | Whitelist of tool actions the agent may call. Empty = all allowed. |
profile_id | string | No | Reuse cookies/auth from a Profile. |
The full schema is in the OpenAPI spec.
Example:
from web_agent import Client
from web_agent.v1.types import CreateSessionRequest, RecordingConfigRequest
session = await client.sessions.create(CreateSessionRequest(
instructions="Find the top 5 Show HN posts from the last 24 hours.",
max_duration_minutes=10,
recording=RecordingConfigRequest(enabled=True),
keep_alive=True,
))
run = session.runs[0]Follow-up runs
from web_agent.v1.types import CreateRunRequest
followup = await client.sessions.create_run(
session.id,
CreateRunRequest(
instructions="Now click into the first post and summarise the discussion.",
),
)The follow-up runs in the same browser, with the same cookies, against the same DOM the previous run left.
Events
Every run emits a Server-Sent Events stream:
GET /v1/projects/{pid}/do_anything/sessions/{sid}/runs/{rid}/events
Authorization: Bearer wa_…Eleven event types (the envelope is the same; data shape varies):
| Type | When |
|---|---|
run.status_changed | State transition |
run.message | Agent or user message in the chat thread |
run.action.started | Agent invoked a tool |
run.action.completed | Tool returned |
run.action.failed | Tool threw |
run.screenshot | New browser frame (url is short-lived) |
run.input_request | Agent paused; needs you to answer |
run.input_request_resolved | Your intervene was accepted |
run.cost_update | Per-step cost delta |
run.completed | Terminal; output populated |
stream.heartbeat | Every ~15 s; harmless |
Reconnect cleanly:
GET …/events
Last-Event-ID: 142The server replays events with id > 142 so you don't miss anything.
Input request (human in the loop)
When the agent hits a captcha, a 2FA prompt, or any judgment call, it emits run.input_request:
{
"type": "run.input_request",
"data": {
"input_request_id": "ir_01HXX…",
"prompt": "I see a 'Verify you're human' challenge. Solve it for me?",
"schema": { "type": "object", "properties": { "solved": { "type": "boolean" } } }
}
}You answer via POST /intervene:
await client.messages.intervene(
session.id, run.id,
input_request_id="ir_01HXX…",
response={"solved": True},
)The run transitions back to running. The whole cycle is one round-trip; no polling.
Take Control
Some steps the agent cannot finish on its own — a captcha, 2FA, a password only a person should type. For these you can hand the browser to a human: an interactive live browser opens, the person clicks and types, and control is then handed back to the agent.
This differs from an input request above: an input request is the agent asking a question that you answer with JSON; Take Control is a human directly operating the agent's browser.
Take Control uses the same intervene endpoint, with kind selecting the action.
1. Request control
POST /v1/projects/{pid}/do_anything/sessions/{sid}/runs/{rid}/intervene
Content-Type: application/json
{ "kind": "take_control", "reason": "captcha" }It is commonly triggered after the agent emits a run.input_request (captcha / login needed), but you can request it at any time.
2. Get the control URL, hand it to a person
The backend issues a standalone control URL, delivered via the run.take_control_pending event (data carries standalone_control_url / exp / reason). It is an unbranded live-browser page — you can forward it straight to your end user. In that page, the person clicks and types directly on the agent's browser: solving captchas, logging in, handling judgment calls.
After you request control the agent does not stop immediately — only once someone actually opens the URL and connects does the run move to paused and the agent pause (event run.user_paused). If no one connects, the URL expires after about 5 minutes and run.take_control_expired fires.
3. Release control
When the person is done, call intervene again:
{ "kind": "release_control", "trigger": "client_release" }The run returns to running, and the agent re-observes the current page and continues (event run.user_released). The control page also auto-releases after about 30s idle. To re-issue an expired URL, use { "kind": "refresh_control_url" }.
Related events
| Event | Meaning |
|---|---|
run.take_control_pending | The control URL is issued, awaiting a connection; data.standalone_control_url is the page for the human |
run.user_paused | Someone connected and is operating; the agent is paused |
run.user_released | Control handed back; the agent continues |
run.take_control_expired | No one connected; the URL timed out |
SDK note: the Python / TypeScript SDK
intervene()currently covers onlyanswer_input_request. Take Control'stake_control/release_control/refresh_control_urlgo through the HTTP intervene endpoint directly. See the OpenAPI spec for the full fields.
Profiles
A profile is a single, whole browser identity (one per user, accumulating their login state across every site). Reference one when creating a session:
await client.sessions.create(CreateSessionRequest(
instructions="Open my LinkedIn inbox and reply to the latest message.",
profile_id="prof_alice",
))The first time, set up the Profile manually in the Console (log into whichever sites you need). Future sessions reuse the same one. See Profiles.
Workspaces
A workspace is a persistent file system. The agent can read and write files; you fetch them via signed URL after the run is done. Useful for "scrape this site, write a CSV, hand it back."
Next steps
- API Reference — every field.
- Authentication — keys, scopes, rotation.
- Vibecoding — how to provide all of this to your IDE's LLM.