Practice
A typed answer arrives in the right shape whether or not the question deserved it. These seven techniques are about the part the type system cannot check: what you put in the question, what you leave in your own code, and what you check afterwards.
Written here in our own words, with the source for each one listed underneath. TypeSafe’s documentation is the authority on the current API; this page is about how to use it well.
01
An option list is a snapshot. Build it once at the start of a multi-step run and it starts drifting away from reality immediately — the row got archived, the button moved, the ticket was closed by someone else. The answer will still be well-typed, and it will still name something that is no longer there.
Browser Use’s jev-ultrafast regenerates the list of page controls before every decision, so the model only ever picks among things that exist right now. Regenerating costs you one cheap step and removes a whole class of failure that is otherwise very hard to see in a log.
02
Questions asked in one batch cannot read each other’s answers. That is the whole rule, and most of the mistakes come from forgetting it: a second question written as though the first one already answered it simply gets no answer to work from.
So split the work in two. Anything one question needs from another is a lookup, and lookups belong in your code, before the batch. What is left is genuinely independent, and all of it goes in a single round trip instead of a chain of them.
03
The model reads what you wrote. It does not read the variable holding the option, the enum it came from, or the comment above the function. An option called tier_2 carries no meaning at all; “needs a specialist, not the general queue” carries the meaning you actually had in mind.
The same is true of the question itself. Whatever rule a trained person would apply — the deadline, the exception, the thing that makes this a borderline case — belongs in the sentence. If you find yourself explaining the answer afterwards, that explanation was the question.
question: "classify"
options: ["tier_1", "tier_2", "tier_3"]Three identifiers and a verb. Nothing here says what separates one tier from the next, so the answer is a guess dressed as a category.
question: "Who should handle this? Anything
about billing goes to accounts even if it
also mentions a bug."
options: [
"front line — answerable from the help centre",
"specialist — needs someone who knows the product",
"accounts — anything touching money or invoices",
]The requirement and the tie-break are in the text, and each option says what it means. The same words are what you would give a new colleague.
04
When you compress the state into one tidy paragraph before asking, you have already made the judgment — in code that nobody reviews and no test covers. Whatever your summariser dropped is now invisible to the decision that depends on it.
Pass the pieces instead, as separate fields: what you found, where each piece came from, and what you looked for and did not find. The gaps matter as much as the findings, and they are the first thing a summary throws away. It also makes a wrong answer readable afterwards, because you can see exactly what the question was holding.
state: {
summary: "Customer seems frustrated about
a late delivery and wants a refund.",
}One sentence, already interpreted. Whether the order was actually late, and whether anyone checked, has been quietly decided upstream.
state: {
message: "<the text, as received>",
orderStatus: "shipped 9 days ago, not delivered",
refundPolicy: "30 days, unused items",
priorContacts: 2,
notFound: ["delivery scan after leaving depot"],
}Findings, their sources, and the gap all travel separately. The question can weigh them; nothing has been decided on the way in.
05
A confidence of 0.9 is the model’s own report about its answer. It is not a measured hit rate, and it does not mean nine out of ten answers at that level were right on your data. Picking 0.9 as a review threshold because it looks high is guessing with a decimal point in it.
Label a few hundred of your own examples, bucket the answers by reported confidence, and look at how often each bucket was actually right. That table tells you where to draw the line for the cost you are willing to carry. Redraw it when the traffic changes, because the curve moves with your inputs, not with the model.
06
A Choice holds up to 255 options. That is generous, and still far smaller than most real catalogues, inventories, or user lists. The answer is not a bigger question — it is three smaller stages.
Cut the list with the constraints you can state exactly: availability, region, permissions, anything a WHERE clause already knows. Rank what survives with a Score. Then put the handful at the top into a Choice for the judgment that genuinely needs one. Each stage is cheaper than one enormous question and, more usefully, each can be tested on its own.
07
Deciding to click the button and the button having been clicked are two different facts, and only one of them is in the answer. Confidence describes the judgment, never the side effect that followed it.
jev-ultrafast checks its outcome separately, after the run reports that it is done. Do the same with anything that can spend money, send something, or delete something: verify the result against the world, not against the decision. And keep the guardrails where they can be read and tested — how many attempts are allowed, how much may be spent, and where the run got to — in your code, not in the question.
if (decision.confidence > 0.95) {
await refund(order)
markComplete(order)
}The confidence is about the judgment. Nothing here observes whether the refund actually went through, and the run is marked complete either way.
if (decision.confidence > threshold
&& spend.remaining() >= order.total
&& attempts.allow(order.id)) {
await refund(order)
}
const seen = await readRefundStatus(order.id)
if (seen !== "settled") escalate(order, seen)The limits are ordinary code, and the outcome is read back from the system that owns it. The decision opens the door; it does not report what came through it.
Every technique above is really one question asked again: which of these three columns does this belong in? Getting the split right is most of the work, and it is the part you can check without running anything.
Anything that has to be exact, repeatable, and reviewable.
Anything whose output is language a person will read.
The narrow judgments in between, one question at a time.
The boundary is the pointEach column is testable on its own
This atlas is independent and not affiliated with TypeSafe. Where a technique comes from someone’s published work, that work is linked above rather than restated here.
What a stream of these decisions would cost