Skip to main content
A tool is TypeScript code the model may call while it works. Use tools for actions such as searching an API, looking up an order, or updating an external system. The model decides when a tool fits; your run function controls what happens.

Define your first tool

Create a tool beside the agent, for example opencomputer/agents/support/tools/lookup-order.ts:
Then attach it from agent.ts:
The model sees the tool’s name, description, and input schema. When it calls the tool, OpenComputer runs run() in the managed agent runtime and returns the result to the model.

Tool definition

* A tool has either run, or preview and apply — never both, and never just one of the pair. Descriptions are the model’s documentation. Include the action, when it is appropriate, and any important precondition.

Execution context

The run function receives: Use the cancellation signal with fetch() and report progress before slow steps:
Tool results and progress metadata must be JSON-compatible values. For an external write, ${sessionId}:${toolCallId} can identify the invocation in a downstream idempotency key. It stays the same when that invocation is retried. A new call from the model has a new ID, even if it requests the same action; use a business-level key when those calls must also converge.

The session result

An agent may mark one tool as its result tool. Its latest committed output is the session’s result: the structured outcome an application reads instead of parsing the final message. This tool checks a URL itself rather than accepting a claimed status from the model:
The build enforces three rules: an agent declares at most one result tool, a result tool declares output, and it runs with run() rather than waiting for a person. The schema is recorded with the deployment, and every value the tool returns is validated against it. A call to the result tool succeeds only once its output is committed to the session:
  • The value run() returns is validated against output and committed before the model is told the call succeeded. The session’s result becomes { turnId, callId, reportedAt, data }, and the call’s tool.completed event carries result: true (Session events).
  • A value that does not match output, or exceeds 8 KiB of JSON, fails the call; the model sees the reason and can call again. The previous result stands.
  • A call from a turn that was interrupted or has ended, or from a runtime the session no longer runs on, is refused the same way.
  • A later call replaces the entire value. Reporting does not finish the turn. A subsequent turn that fails or is cancelled does not clear the result, and the event log keeps every call.
The result says what the agent reported, not that the work succeeded. Verify claims inside run(), where a tool can check that a branch exists or a pull request is open, and throw to reject them; let the application check what the result references. Read it from GET /sessions/<id>. For a review-ready UI, also check current activity and whether result.turnId belongs to the last settled turn. An existing result may describe earlier work. If the commit acknowledgement is lost, the tool can fail even though its result was saved. OpenComputer retries the commit for the same tool call; that deduplicates the saved result, not external writes inside run(). Prefer tools that verify and report existing work. Give any external write its own idempotency key.

Schema declarations

The result tool’s output is copied into the deployment without evaluating code. Use one of these forms:
  • An object literal in the defineTool() call.
  • A const object literal in the same module, as above.
  • A named import of such a const from a module inside the agent directory.
Function calls, let bindings, namespace members such as schemas.report, package imports and re-exports are not supported for this output schema. The build error names the unsupported form. input may use these static forms or a schema constructed when the tool module loads; it is not subject to result-output extraction. Neither schema infers the TypeScript type of run’s input, which remains Record<string, unknown>. as const preserves the schema’s literal types for your own typing and validation. Write name, result and output as ordinary properties on the defineTool() object. Spreads, computed property names, and a result value other than literal true or false fail the build. A tool module whose result declaration disagrees with its deployment fails to load.

Conditional tools

useTool() may be conditional. This keeps sensitive or specialized actions out of the tool set until they are relevant:
useTool() also accepts a tool ID such as useTool("web-search") when the runtime already provides that tool.

Give an agent a sandbox shell

Select the built-in sandbox_exec tool when an agent needs to run commands, inspect files, or use installed CLI packages:
The agent runtime starts without a VM. The first sandbox_exec call lazily acquires an isolated computer for the session, and later calls in that session reuse it. Files that must survive restarts belong in /workspace; temporary paths such as /tmp do not provide that persistence guarantee. If your organization has an Enterprise custom package image, sandbox_exec uses that image automatically. The agent definition still selects the same tool ID, and cannot choose or override the image itself. You can see the package image assigned to your organization under Settings → Customize VM packages.

Call external APIs safely

For authenticated HTTP APIs, declare a connection, store the key as an OpenComputer secret, and call the connection’s fetch() method. This keeps the credential outside the agent runtime; see Secrets and outbound requests. Managed GitHub connections are the exception: they supply short-lived environment credentials so Git and gh work directly. Never embed credentials in tool source or return them to the model. For Google services, GitHub, or Linear, the credential belongs to whoever connected the account and the platform refreshes it — call those with callService instead.

Ask a question

ask is the platform’s question tool. Select it with useTool("ask") when a turn may need to stop and ask a person before going on:
The model calls it with: Calling ask ends the turn with an open question. The turn completes with outcome: "question", the session’s question holds what was asked, and question.asked is recorded in the event log. The reply arrives as the next input with answer set, and anything written meanwhile arrives with it as steering, within bounds. A session has at most one open question; the turn that interprets the answer may ask again. How the question reaches a person depends on where the session lives: an application answers it through the API or the React hook, and a Linear session shows it in the thread with the options as buttons. ask records that the agent asked. It does not undo or forbid anything the turn did before asking. A turn meant only to propose should select ask and tools that cannot write, and select the writing tools on the turn that carries the answer. The id ask is reserved: defineTool({ name: "ask" }) is refused, both by defineTool and by the CLI’s build. ask runs on the default runtime for new deployments; sessions with executionMode: "microvm" cannot select it. ask is different from waiting for a person: ask ends a turn with an open question the agent reasons about when it is answered, while an approval tool records one exact write that runs, unchanged, once someone approves it.

Wait for a person before writing

Some writes should not happen because a model decided they should. Such a tool proposes instead: the model calls it, nothing is written, and the person the agent is talking to sees a card with the exact change and decides. There is no separate primitive for this. Give defineTool a preview and an apply instead of a run, and the model’s call becomes a proposal. run is written for you. useTool selects the tool exactly as it selects any other, and the model cannot tell the difference — waiting for approval is a property of a tool, not a different kind of thing.
The model calls attach like any other tool and is told the change was recorded for approval, so it stops rather than reporting the work as done. apply runs later, with the arguments that were on the card — no second pass through the model, so what happens is what was agreed to. The proposing session is usually gone by then, and that is fine: apply needs its arguments, not the conversation.

Always pass decision.id

decision.id identifies this approval, and it is the same value on every attempt to carry it out. Give it to whatever you call as an idempotency key. If a write never reports back — the runtime died mid-flight, the answer was lost — it is recorded as unconfirmed rather than failed, because it may well have happened. That is only recoverable if running it again is safe, and this is what makes it safe.

What this does and does not guarantee

Approval is a convention your code cooperates with. Nothing stops a tool from writing inside preview. What the platform guarantees is narrower and still worth having: an approved write runs once, from the arguments the person agreed to, whether or not the session that proposed it still exists. A tool that waits needs a conversation to ask in. Called from the playground, a schedule or a webhook, there is nobody to ask, and the call fails saying so.

Computer commands

A session gets an isolated computer when it needs one; conversation alone does not require a computer. The model runs commands through the built-in shell tool. A command runs in the session’s workspace and takes command and an optional timeoutSeconds: 120 by default, at most 1800, and never more than the computer’s remaining lifetime allows; a value out of range fails the call before anything runs. A command is not started on a computer that cannot host it for its full timeout; a fresh computer is used instead. When the timeout passes, the command and every process it started are stopped, and the call fails as a tool error the model sees: it names the command and the timeout and carries the output so far. The turn continues, and files the command wrote before it was stopped remain. A command counts as stopped only once that is confirmed: every process it started, detached ones included, is gone, or the computer itself was terminated. When the computer stops answering while a command runs, its outcome is unknown, and the call fails with that as the reason; the model is never told a command was stopped when that was not confirmed. That computer runs no further command until the unknown one is confirmed stopped or the computer is replaced by a fresh one with the session’s workspace, which the next command does on its own. See End and interrupt for cancellation guarantees and the compatibility limit for sessions using the older runtime.

Tools versus skills and MCP

  • A tool executes TypeScript you own.
  • The same tool with preview and apply instead of run waits for a person before it does it.
  • A skill teaches the agent a reusable procedure.
  • An MCP server supplies a remote collection of tools.