- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerNew
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Give your agent a computer
Your agent must do a task on a web page that has no API, for example fill in a form or find a price. computerUse() gives the agent a screen, a mouse, and a keyboard. Each thread gets its own browser page. Each screenshot goes to the media store of the session, so the log stays small.
To use computer use in a chat() route with no harness, read Computer Use.
1. Add the plugin#
npm i @tanstack/ai-harness @tanstack/ai-computer-use @tanstack/ai-computer-playwright playwright-core zodpnpm add @tanstack/ai-harness @tanstack/ai-computer-use @tanstack/ai-computer-playwright playwright-core zodyarn add @tanstack/ai-harness @tanstack/ai-computer-use @tanstack/ai-computer-playwright playwright-core zodbun add @tanstack/ai-harness @tanstack/ai-computer-use @tanstack/ai-computer-playwright playwright-core zodThen download Chromium one time:
npx playwright-core install chromiumimport { createHarnessHost, defineHarness } from '@tanstack/ai-harness'
import { permissions } from '@tanstack/ai-harness/plugins'
import { computerUse } from '@tanstack/ai-computer-use/harness'
import { playwrightComputer } from '@tanstack/ai-computer-playwright'
import { openaiText } from '@tanstack/ai-openai'
import { memoryPersistence } from '@tanstack/ai-persistence'
// One time, outside `plugins`, so the sessions share one Chromium.
const browser = computerUse({
driver: playwrightComputer({ startUrl: 'https://tanstack.com' }),
})
export const agent = defineHarness({
name: 'acme/browser-agent',
adapter: openaiText('gpt-6.1-sol'),
plugins: () => [permissions(), browser],
})
const host = createHarnessHost({ persistence: memoryPersistence() })
const session = await host.open(agent, { threadId: 'browser-1' })
await session.send('Go to https://tanstack.com/ai and tell me the title of the page.')- The model gets the computer tool. With it, the model clicks, types, scrolls, and takes screenshots.
- The model also gets the navigate tool when the driver can open a URL. playwrightComputer() can.
- Chromium starts on the first action. Each thread gets a new page, and the page opens startUrl.
To use Anthropic, change only the adapter, for example to anthropicText('claude-opus-5-5').
2. Answer a safety check#
OpenAI can mark an action as risky, for example when the page tells the model to do something else. Before that action runs, permissions() asks the user. The turn stops, and you answer the approval:
// wait.message is, for example: Allow computer {"action":{"type":"click",...},"pending_safety_checks":[...]}?
const waits = session.snapshot().pendingWaits
await session.resolve({
inputId: 'approve-1',
resume: waits.map((wait) => ({
interruptId: wait.waitId,
status: 'resolved',
payload: 'once',
})),
})
await session.settled({ inputId: 'approve-1' })- once runs the action. The model gets the screenshot, and OpenAI gets the acknowledged check.
- always runs the action too, but it saves no rule. The next safety check asks again.
- reject does not run the action. The model gets an error result.
The other actions run with no question. Anthropic sends no safety checks, so its actions never ask.
In the plan mode, the computer tool does not run. To ask before every action, add a rule: permissions({ rules: [{ tool: 'computer', decision: 'ask' }] }).
CAUTION: Keep permissions() in the plugins. Without it, an action with a safety check does not run. In the bypass mode, it runs with no question. See Pick a mode.
3. Choose how many screenshots the model sees#
Each action returns a screenshot. The plugin stores it in the media store of the session. The transcript and the log keep a small harness-media: reference to each one.
The model gets only the 3 newest screenshots. An older screenshot goes to the model as a 1x1 image, so a long task does not send every old screenshot again. To change the number, set keepScreenshots:
export const browserWithHistory = computerUse({
driver: playwrightComputer({ startUrl: 'https://tanstack.com' }),
keepScreenshots: 5,
})The media store keeps every screenshot, so a client can still show all of them. To keep the files after a restart, give the host a blob store. See Keep media across restarts.
When the pages close#
- All the sessions of a host that use the same computerUse() object share one Chromium.
- A thread that stays idle for idleTimeout loses its page. The default is 10 minutes. The next action of that thread opens a new page at startUrl.
- host.close() closes every page, then Chromium.
- The pages live in the memory of the host. When another host opens the thread, the thread gets a new page there.
The browser can open every address that your server can reach. See Keep the browser away from private addresses. To use a Linux desktop or your own computer, pass another driver from Drivers.
What you have now#
Run the session from step 1. The model calls navigate, reads the screenshot, and answers with the title of the page. Your harness agent now uses a browser, and each screenshot is in the media store of the session.