yc_
cd ../projects
Active 2026 Solo — backend, Bridge, Solver page

Unstuck

A browser agent calls solve(page) when it hits a challenge it cannot pass. A person clears it from a phone, and the agent carries on.

Call

solve(page)

Hackathon price

0.01 USDC / solved task

Public demo

Free · no sign-in

Client SDKs

TS · Python · Go · not started

The stuck part

A browser agent can fill a form. It still stops on the widget built to tell a person from a program: a reCAPTCHA image grid, a slider, a checkbox. Another model call usually spends tokens and lands on the same widget.

Unstuck is the handoff for that moment. The agent calls solve(page). A person, the solver, claims the task, sees the agent's page, and clears the challenge. solve() returns, and the agent continues in the same browser.

import { solve } from "../src/index.ts";

await solve(page, {
  obstacle: "Pass the reCAPTCHA at the bottom of the page.",
});
The Bridge is TypeScript source in the repo, imported by path.

A minute on Indiana's business search

The clip is the hackathon demo. Jev Ultrafast, a Python browser agent, looks up a company on Indiana's business registry. It fills the form, hits the reCAPTCHA, and hires a person for 0.01 USDC. The solver is on an iPad. Taps on the iPad land in the agent's Chromium window. Once the token is issued, Jev finishes the search. Stuck to done is about a minute.

Jev on Indiana's business search, blocked on a reCAPTCHA, cleared from an iPad.

What the solver actually controls

The solver sees a JPEG screencast of the page, taken over Chrome's DevTools protocol at about ten frames a second. Their taps, drags, scrolls, and keystrokes are applied with Playwright's mouse and keyboard, which the page treats as real input. Typing is text plus a short list of named keys: Enter, Backspace, Tab, the arrows. Shortcuts are dropped.

Frames prefer a WebRTC data channel opened when the task is claimed. The backend gives both sides a peer token and ICE servers, then stays off the picture bytes. If that channel cannot connect, the same frames and input go through the backend's WebSockets. The bridge socket stays open on either path and carries the lifecycle: claimed, solved, expired, failed.

A task ends once. Solved means the agent's cleared check passed — by default, reCAPTCHA has issued a token — or the solver tapped Done and the agent's own check agreed. Expired means nobody claimed it in time. Failed means the solve window ran out, the solver gave up, or the bridge disconnected. On the public queue that is 30 seconds to claim and 5 minutes to clear.

  1. 01

    solve(page)

    The Bridge opens a task and a screencast of the agent's Chromium page.

  2. 02

    Queue

    The task appears live for every connected solver. No account.

  3. 03

    Claim

    The first solver to tap Claim gets it. A task is claimed once.

  4. 04

    Session

    Frames go out. Taps, drags, and keys come back through Playwright.

  5. 05

    Solved

    The cleared check passes. solve() returns. The agent keeps the page.

Direct · WebRTC

Preferred. At claim, both sides get a peer token and ICE servers. Frames and input then travel on a data channel, and the backend leaves that path.

Relay · WebSocket

Fallback. If the data channel cannot connect, the same frames and input go through the backend. The bridge socket stays open either way and carries claimed, solved, expired, and failed.

One task, from the call to the agent continuing. WebRTC carries the session when it can; the backend relays it when it cannot.

The hackathon payment, then the demo without it

The hackathon build charged for the handoff. Creating a task placed a hold of 0.01 USDC on a prepaid balance. A solved task settled the hold, and a solver could withdraw earnings in USDC on Solana. A customer registered by signing with a wallet. There was no API key.

I then took that payment path out of the running demo. The queue at unstuck.yuchia.dev is the handoff by itself: an agent creates a task with no key, and a solver is an id the page keeps in the browser. I wanted to know whether anyone needs the handoff before charging for it. A later payout, with USDC on Solana as one method a solver can choose, is written down and not built.

The SDKs have not been started

What runs today is a Go backend and one TypeScript Bridge that lives in the repo. An agent imports it by path. The Python demo reaches that same Bridge through a small handoff process: Jev is Python, the Bridge is TypeScript, so the handoff attaches to Jev's Chrome over the debugging port and calls solve() on Jev's tab.

The client SDKs are a separate piece of work, in TypeScript, Python, and Go, and none of them has been started. Until they exist, the way to call Unstuck is to clone the repo and import the TypeScript source.

What I would do next

  • Publish the three client SDKs, with the same solve() in each language
  • Crop the session to the challenge, so a solver sees and can touch only that region of the page
  • Bring payout back as an optional method, USDC on Solana among them, once the free demo says the handoff is wanted

Stack

GoTypeScriptPlaywrightWebRTCSQLiteSolana