createAgent:
createToolLoopExecutor: the same loop as the built-in agent, with your prompt and your tool vocabulary. You keep the verdict tool, budget hard stops, loop guards, wind-down near the clock, model accounting, and--debugtranscripts.StepExecutor: any object withrunStep(ctx). No AI SDK, no model, any decision process you like.
Keep the loop, change the vocabulary
This example gives a browser agent two tools: read the screen and tap a button by its accessible name. The same loop can wrap a device SDK.e2e.config.ts
buildPrompt returns a string, or a message history to carry a conversation
across steps. prepareMessages runs between turns for compaction and may
return { messages, stop } when your own history shows the step is not
progressing.
guard() stops a tool from running once the step is concluding and turns
budget and timeout stops into a clean end of the loop. runTool reserves the
action budget before the body runs, serializes mutations, and records their
outcomes. A mutating body must call its engine directly; queuing another
grammar operation from inside it would wait on itself. Keep tool bodies short
and interruptible: the step deadline settles the verdict either way, but a
device operation that ignores cancellation finishes on its own time.
Replace the executor
This model-free executor supports one assertion,agent.assert('the screen says Ready'). Other instructions return a blocked
verdict so the executor cannot claim to have checked something it does not
understand.
e2e.config.ts
Secrets in params arrive as name-only placeholders.
actions.typeSecret(target, name)
performs the authorized fill without the plaintext passing through your code.
A custom executor can run both agent.act and agent.assert without a
model. Its agent.assert dispatches to runStep under the act budgets and
config.timeout; vision and screenshot are UNSUPPORTED_CAPABILITY
there.
Without a configured executor, the runner checks for a model when agent
is first acquired, after the engine attempt starts. A missing model raises
MODEL_UNAVAILABLE then. A custom executor with no model passes that check.
Its agent.waitFor and agent.extract calls still use the built-in judgment
tier and raise MODEL_UNAVAILABLE when they need a model and none is
configured.
Driving the page yourself
An executor that brings its own browser tooling needs the page e2e opened, not a second one.@e2edev/playwright exports surfaceOf(handle): the live
Page and BrowserContext of the current attempt behind the handle you
passed to the target. Both accessors throw INVALID_STATE before an attempt
is running.
ctx.budgets so the step keeps its metrics.
The verdict
Whatever the executor,runStep resolves with passed, failed, or
blocked and a summary. A blocked verdict carries the error code that names
the cause; How agent steps work lists them
and the exit each one produces.
agent reference
Every method, option, and budget.
Writing an engine
Plug in a whole new surface below the executor.
