SunoAI

A browser agent you talk to in Hinglish — that stops and asks before it ever writes anything.

You “Analytics jobs dikhao.”
SunoAI Opens Chrome, reads the top three back to you.
You “Doosre wale pe apply karo.”
Held Drafts it, reads it back — and waits.
You “Haan, kar do.”
SunoAI Only now does it submit.
The harness

TrueForge is the agent

Not a wrapper around one. TrueForge owns the tool-calling loop, the model connection, the MCP connection, and the approval gate. This repository never calls Gemini directly and never drives Playwright directly.

gemini-3-flash-preview is registered as a provider through TrueForge's settings API. A real Playwright MCP server is registered as a connector — TrueForge's connector manifest has only a remote variant, with no stdio transport, so the MCP server runs as its own HTTP process and the harness dials it.

The gate itself is require_approval_for_tools in the agent manifest. The harness emits tool.approval_required and will not proceed until the client resumes the turn with a user.tool_approval decision.

The policy

Three tiers, not two

Most of the thinking in this project went here. Splitting tools into “allowed” and “gated” is not enough, because some tools can reach a write without looking like one.

Denied Never available to the model, at any point

These execute arbitrary JavaScript in the page. The model could submit a form with a scripted click and the harness would only ever see a read-shaped tool call.

  • browser_evaluate
  • browser_run_code_unsafe
  • browser_file_upload
  • browser_drop
Held for approval Pauses the turn until a human decides

Everything left that can change a page. Listed by name rather than through TrueForge's @write preset, whose meaning comes from tool metadata we do not control.

  • browser_click
  • browser_type
  • browser_fill_form
  • browser_press_key
  • browser_select_option
Ungated Runs freely, on purpose

Reads are not gated. Asking a person to approve every page view trains them to approve on reflex — which is how approval gates quietly stop working.

  • browser_navigate
  • browser_snapshot
  • browser_find
  • browser_take_screenshot

A gate only means something if every route to a write passes through a tool it covers.

Code review

Qodo found the hole

Qodo reviews every pull request through a pr-agent workflow. On PR #4 it landed on the exact line the whole project rests on.

The retry logic refuses to re-run a turn that already called a gated tool, so a transient error can never cause a double submission. It decided that by reading the turn's transcript — and swallowed request failures.

// An empty event list reads as "no gated tools were called".
const events = await this.get(/* … */)
  .catch(() => []);   ← fails open

So a network blip while checking whether a turn had already submitted something would let the retry proceed. The guard against double submission could be removed by exactly the kind of transient error it was written to survive.

// Fixed: not knowing is not the same as knowing it was safe.
} catch (error) {
  console.warn(/* … could not read the transcript … */);
  return true;   ← fails closed
}

Being wrong in that direction costs one un-retried turn. Being wrong in the other costs a real, unretractable write.

Three further findings were accepted in the same review: an unbounded retry wait, an unguarded turn id, and URL casing on the search keyword.

Status

What actually runs

Stated plainly, because a judge is going to clone the repository and find out anyway.

#MilestoneState
1TrueForge harness + Gemini 3 Flash respondingWorks
1bBrowser-control MCP navigating and extracting textWorks
2Read the top listings back, typed inputRuns; blocked on naukri by a cookie modal
3Draft → approval gate → submitPolicy enforced; runtime flow unscripted
4Voice: speech in, speech outNot built
5“Jarvis” status UINot built
The voice layer is not built

The name says voice and the architecture is designed around it, but the audio client is not written. What exists is the agent and the safety model underneath it, driven by typed text.

Obstacle one

TrueForge does not start on Windows

Its migrations hand import() a bare C:\ path; Node's ESM loader rejects it and the server dies before it ever listens. The repository ships a postinstall patch that routes the path through pathToFileURL().

Obstacle two

20 requests per day, per model

Not per minute. One agent turn spent three to five, so a model lasted about six turns. Two were exhausted in a single afternoon. Getting a turn from nine requests down to three is why milestones four and five are not here.

Running it

Local by design

There is no hosted instance, and there should not be. TrueForge's standalone mode prints its own warning that it is not hardened for shared internet access, and the agent drives a real Chrome carrying a real logged-in session. Putting that behind a public URL would hand strangers a signed-in browser.

# three terminals
npm run harness        # TrueForge      → localhost:8790
npm run browser        # Playwright MCP → localhost:8931
npm run bootstrap      # register model + connector

# then
npm run smoke:browser  # agent drives Chrome through MCP
npm run read -- 3      # reads the top three listings back

tools called:   browser_navigate, browser_snapshot
model requests: 3      # free tier allows 20/day/model