Tools

View as Markdown

A Tool is one callable capability. Tools have input and output schemas, so an application can call the same capability directly or make it available to an Agent through a Toolset.

How Tools fit together

ConceptRole
ToolOne capability, such as opening a page or requesting human input
ToolsetA reusable list of Tools
ConversationAn Agent thread that can use a Toolset
Tool callOne execution of a Tool

The relationship is:

Tool → Toolset → Conversation turn → Tool call → Run

Every Tool call can be connected to its runtimeId, runId, turnId, and spanId. Use Runs when you need the complete execution record.

Call a built-in Tool

Built-in Tools are ready-to-use capabilities for browser pages, Stagehand, challenge solving, human input, files, and Vault.

Call a synchronous Tool directly:

1const pages = await bctrl.tools.call("browser.pages.list", {
2}, { runtimeId });
3
4console.log(pages);

The SDK uses the Tool’s generated input and output types. Runtime-bound Tools receive their Runtime selector as the third call-options argument; it is not part of the model-generated Tool input.

For work that may take time or wait for input, start an asynchronous Tool call:

1const call = await bctrl.tools.start("human.request", {
2 prompt: "Approve the purchase",
3 expiresInSeconds: 300,
4}, { runtimeId });
5
6const result = await bctrl.toolCalls.result(call.id, {
7 waitSeconds: 60,
8});

When the call reaches a terminal state before waitSeconds expires, the endpoint returns the tool output. If the call is still pending when the wait ends, it returns the current ToolCall with HTTP 202; call it again to keep polling or use bctrl.toolCalls.get() to inspect the lifecycle state.

Use tools.call for a Tool that can finish within its synchronous limit. Use tools.start when the Tool supports asynchronous execution, may require human input, or should be monitored as a separate resource.

Toolsets

A Toolset is a named collection of Tool references. Tool references can be built-in names or custom Tool IDs. The Space can be omitted to use the caller’s default Space:

1const toolset = await bctrl.toolsets.create({
2 name: "checkout-tools",
3 tools: [
4 "browser.pages.open",
5 "stagehand.observe",
6 "human.request",
7 ],
8});
9
10const conversation = await bctrl.conversations.create({
11 runtimeId,
12 toolsetId: toolset.id,
13});

The Agent can use only the Tools included in the Toolset during its turns.

Toolset fields

ParameterTypeRequiredDescription
namestringYesHuman-readable Toolset name.
spaceIdstringNoSpace that owns the Toolset. Defaults to the caller’s default Space.
descriptionstring | nullNoWhat the Toolset is for.
toolsstring[]NoBuilt-in Tool names or custom Tool IDs. Defaults to an empty list.

Manage a Toolset with bctrl.toolsets.list(), iter(), get(), update(), and delete().

Custom Tools

Create a custom Tool when the capability belongs to your application. The SDK currently supports code, webhook, and workflow implementations:

1const tool = await bctrl.tools.create({
2 name: "crm.lookup",
3 description: "Look up a customer in the CRM",
4 inputSchema: {
5 type: "object",
6 properties: { customerId: { type: "string" } },
7 required: ["customerId"],
8 },
9 outputSchema: {
10 type: "object",
11 properties: { status: { type: "string" } },
12 },
13 implementation: {
14 type: "webhook",
15 url: "https://api.example.com/tools/crm-lookup",
16 authSecretName: "crm-webhook-secret",
17 timeoutMs: 30000,
18 },
19});

For a webhook Tool, use authSecretName to reference a stored secret and timeoutMs to set the execution timeout. Code Tools place their source and timeout inside implementation: { type: "code", ... }. Workflow Tools are created from a published source turn.

Custom Tool fields

ParameterTypeRequiredDescription
namestringYesStable namespaced name, such as crm.lookup.
descriptionstringNoWhat the Tool does.
runtimeTypes("browser" | "desktop" | "spreadsheet")[]NoRuntime categories supported by the immutable current revision.
modes("sync" | "async")[]NoExecution modes supported by the immutable current revision.
inputSchemaJsonObjectYesJSON Schema for the input.
outputSchemaJsonObjectYesJSON Schema for the output.
implementationobjectYesImmutable implementation revision: code, webhook, or workflow. Implementation-specific fields are nested here.
spaceIdstringNoSpace that owns the Tool. Defaults to the caller’s default Space.

Tool responses include currentRevisionId, runtimeTypes, modes, schemas, and the current implementation. A Tool’s machine name is immutable. Updates must include the current revision ID and create a new revision; stale revision IDs are rejected so a ToolCall cannot silently observe a changed definition.

Use bctrl.tools.list(), iter(), get(), update(), and delete() to manage custom Tools. Built-in Tools are read-only catalog definitions with spaceId: null, currentRevisionId: null, and implementation: { type: "builtin", name }.

Tool calls

Every asynchronous call has a ToolCall resource. It records the execution state and links the call to the automation that produced it.

1const current = await bctrl.toolCalls.get(call.id);
2
3if (current.status === "requires_input") {
4 await bctrl.toolCalls.respond(call.id, { approved: true });
5}
6
7if (current.status === "running") {
8 console.log("Still running");
9}
10
11await bctrl.toolCalls.cancel(call.id);

ToolCall status

StatusMeaning
queuedAccepted and waiting to start
runningExecuting
requires_inputWaiting for a response
succeededCompleted successfully
failedCompleted with an error
cancelledCancelled before completion
timed_outExceeded its time limit

Calls that require input can be answered with toolCalls.respond(). A completed, failed, cancelled, or timed-out call cannot be resumed.

ToolCall fields

FieldTypeAlways presentDescription
idstringYesUnique ToolCall identifier.
toolstringYesBuilt-in Tool name or custom Tool name/ID.
statusToolCallStatusYesCurrent execution state.
runtimeIdstring | nullYesRuntime used by the call, or null when no Runtime was involved.
runIdstring | nullYesRun that contains the call, or null when no Run was created.
turnIdstring | nullYesAgent turn that contains the call, or null for non-Agent calls.
parentIdstring | nullYesParent ToolCall, or null when the call is not nested.
spanIdstring | nullYesTrace span for the call, or null when no span is available.
callerType"api" | "agent" | "system" | "test"YesWho started the call.
resultAvailablebooleanYesWhether a result can be read.
promptstring | nullYesInput prompt when the call is waiting for a response, or null otherwise.
responseSchemaJsonObject | nullYesExpected response shape, or null when the call does not expect a response.
responseExpiresAtstring | nullYesInput response deadline, or null when the call is not waiting for input.
createdAtstringYesTime the ToolCall was created.
startedAtstring | nullYesTime execution started, or null before execution begins.
finishedAtstring | nullYesTime execution finished, or null while the call is still active.
errorCodestring | nullYesError code when the call fails, or null otherwise.
errorMessagestring | nullYesError message when the call fails, or null otherwise.
retryableboolean | nullYesWhether the failure can be retried, or null when the call has not failed.

Use bctrl.toolCalls.list() or iter() to filter calls by runtimeId, runId, turnId, tool, or status.

Tool execution metadata

Tool definitions expose descriptive execution metadata so callers can choose a safe execution path. Policy fields such as cancellation support, synchronous limits, and result persistence remain server-owned and are not part of the public Tool resource.

FieldValuesMeaning
runtimeTypesbrowser | desktop | spreadsheetRuntime categories supported by the Tool
modessync | asyncWays the Tool can be called

Use the metadata returned by bctrl.tools.get() to decide whether to call a Tool synchronously or start a ToolCall.