Skip to main content

createToolApprovalGate

workflow.createToolApprovalGate() Create a tool to approve the gate. Workflow declares the invokable tool in tools of specific entrypoint,Conversation continues to declare it in tools of executor, and then uses gate to check the running policy before actually calling the tool.

The tool has three results: if it is not registered, the input verification will fail; if it is disabled, the call will be rejected; if it is enabled but not automatically approved, the waiting/recovery protocol of the user input node will be reused to generate an approval request — only a plan-mode agent turn stops to ask. A build turn is running a plan the user already approved, so a tool that is not auto-approved runs there.

Signature​

workflow.createToolApprovalGate(): ToolApprovalGate
interface ToolApprovalGate {
approve(
input: ToolApprovalRequestInput,
context: NodeContext,
): Promise<ToolApprovalDecision>;

wrap<Args extends unknown[], Result>(
name: string,
fn: (...args: Args) => Promise<Result> | Result,
options?: {
label?: string;
description?: string;
reason?: string;
arguments?: (...args: Args) => unknown;
},
): (context: NodeContext, ...args: Args) => Promise<Result>;
}

Tools Registration​

The selected Workflow entrypoint or Conversation executor declares the tool default policy through tools. The runtime only accepts the override policy of the execution configuration declared tool, and the unknown tool is ignored.

export const executor = workflow.defineExecutor<Input, OutputPayload>({
projectType: "workflow",
defaultEntrypoint: "main",
entrypoints: [
workflow.defineEntrypoint<Input, OutputPayload>({
id: "main",
title: "Full workflow",
workflow: toolProject,
tools: [
{
name: "search",
label: "Search",
description: "Search external sources.",
enabled: true,
autoApprove: false,
},
],
createInput({ args }) {
return { query: args.join(" ") };
},
}),
],
});
FieldTypeDescription
namestringStable tool name. Required. Empty strings are ignored.
labelstringShow name.
descriptionstringDescription of the purpose of the tool.
enabledbooleanWhether it is enabled by default. true when not passed.
autoApprovebooleanAutomatically approve by default. false when not passed.

approve​

approve(input, context) only performs approval checks and does not call business functions. { approved: true } is returned when approval is passed; tool_permission_denied is thrown when user rejects.

Input fieldTypeDescription
namestringTool name, must match the tools declaration of the current entrypoint or Conversation executor.
labelstringThe name of this approval is displayed; if it is not passed, use the tool declaration.
descriptionstringThis approval description; use tool declaration if not passed.
reasonstringCall reason.
argumentsunknownThe parameter snapshot of this call is written to the approval request metadata.

wrap​

wrap(name, fn, options) returns a new function. When calling a new function, the first argument must be NodeContext, and gate will approve it before passing the remaining arguments to the original function.

const approval = workflow.createToolApprovalGate();

const searchWithApproval = approval.wrap(
"search",
async (query: string) => {
return fetchSearchResult(query);
},
{
label: "Search",
reason: "External research is needed before answering.",
arguments: (query) => ({ query }),
},
);

const result = await searchWithApproval(context, input.query);

Running behavior​

StatusBehavior
Tool not registeredThrows HarnessError of type input_validation.
Tool is disabledThrows HarnessError of type tool_permission_denied.
autoApprove: trueDirectly return { approved: true }, does not generate wait request.
Requires approval, and the turn is in plan modeThe tool-approval request is created and the running status becomes waiting_for_input.
Requires approval, but the turn is in build modeDirectly return { approved: true }, does not generate wait request.
User ApprovalRestore the same runId, return { approved: true }, and continue calling the tool.
User RejectedThrow tool_permission_denied.

The mode is decided per turn by the agent runtime and written to context.toolPermissions.requiresUserApproval: true for a plan turn, false for a build turn. A run that never enters the agent (a plain Workflow Run, or a caller that declares no mode) keeps the default true and still asks according to autoApprove. This rule only decides whether the run stops to ask — a disabled tool is refused in either mode.

Approval requests use workflow.createUserInputNode() the same set of recovery agreements. The request ID contains workflow, runId, and tool name by default, and params contains a boolean field approved.

CLI Web render tool-approval as a dedicated decision interface, directly showing tool ID and arguments parameter snapshots, and providing "approve and continue" and "reject and end" actions. The interface does not display generic Boolean switches, nor does it implicitly approve tool invocations through Enter.

In Conversation mode, the enablement of the tool is saved with the auto-approval policy by dialog. When you switch back to a conversation, the Tools Permissions panel restores the last state of the conversation, and subsequent runs and approval restores also use the policy snapshot of the conversation. Conversations that are new or do not have a standalone policy use the executor tools default value directly and do not inherit overridden values from project-level or other conversations. Plain Workflow Run still uses project-level tool policies.

CLI Recovery​

Local CLI does not pop up interactive approvals. The first run outputs a waiting_for_input report, followed by a recovery using the same runId and --resolved-user-inputs-json:

steamharness json . -- --message "run tool"
steamharness json . \
--run-id <run-id> \
--resolved-user-inputs-json '[{"requestId":"<request-id>","nodeName":"tool-approval","values":{"approved":true},"submittedAt":"2026-07-08T00:00:00.000Z"}]' \
-- --message "run tool"

CLI Web and local host also pass in the tool policy snapshot for this run through --tool-permissions-json. Normal manual CLI debugging usually does not require this parameter to be passed directly, unless you want to simulate the enable or automatic approval policies passed in by the host.

Error​

SituationError TypeDescription
Tool name is emptyinput_validationname must be a non-empty string.
Tool not registered at executor.toolsinput_validationOnly declared tools can be approved.
Tool disabled by policytool_permission_deniedThe current run does not allow the tool to be called.
User Rejected Approvaltool_permission_deniedOn recovery values.approved !== true.
No user input runtime and approval requireduser_input_requiredThe host does not provide an input handler that pauses resume.