Programmatic Tool Calling allows an OpenAI Responses model to generate and execute JavaScript that orchestrates tool calls with loops, branching, and intermediate calculations, returning a final result without pausing after each invocation. The generated program runs isolated in a V8 sandbox with access only to explicitly allowed tools; tool authors use allowed_callers to control whether tools are invocable by the model directly, the program, or both. Programmatic Tool Calling reduces latency and token overhead by completing multi-step tool workflows in a single model turn, eliminating intermediate request-response cycles where tool results would otherwise be sent back to the model to generate a new response.
Programmatic Tool Calling lets a supported OpenAI Responses model generate JavaScript that calls eligible tools, combines their outputs, and returns one result to the model — useful for bounded workflows that benefit from loops, branching, parallel calls, or intermediate calculations without a model round trip after every tool call.[1] The generated JavaScript program runs in a fresh hosted V8 environment with no Node.js APIs, filesystem or network access, or persistent process; it can interact only with tools explicitly allowed.[1]
ProgrammaticToolCallingTool() and tool_choice="programmatic_tool_calling" are available only with supported OpenAI Responses models; Chat Completions models and non-Responses backends reject both.[1] An agent must include at most one ProgrammaticToolCallingTool() instance and must also expose at least one programmatically callable tool, a ToolSearchTool() backed by a namespace, deferred function, or deferred hosted MCP server, or an opaque prompt-managed tool surface; a bare ToolSearchTool() without a searchable surface is rejected.[1]
The allowed_callers field on a tool controls invocation mode: omitting it allows direct model calls only; ["programmatic"] restricts the tool to program-only access; ["direct", "programmatic"] allows both.[1] SDK tool types that support allowed_callers are FunctionTool, CustomTool, ShellTool, ApplyPatchTool, HostedMCPTool, and CodeInterpreterTool; for HostedMCPTool and CodeInterpreterTool, the field is set inside tool_config.[1] For @function_tool(allowed_callers=[...]), a structured return annotation (Pydantic model, TypedDict, or dataclass) automatically becomes a strict object output schema validated before the value is returned to the program; output_type and output_json_schema are mutually exclusive alternatives when no usable annotation exists.[1]
For schema-backed program-owned tool calls, the default failure formatter is disabled because its free-form text does not satisfy the output schema; a handler exception propagates unless a custom failure_error_function returning schema-conforming JSON is provided.[1] Program-owned SDK tools still run through the normal Runner lifecycle — tool input/output guardrails, hooks, timeouts, concurrency limits, approvals, sessions, and RunState pause/resume all apply (see RunState and resume).[1] When ProgrammaticToolCallingTool() is present, the SDK applies a stricter replay-safety boundary and disables provider-managed retries and WebSocket pre-event retries, even before a program executes.[1]
Canonical ProgrammaticToolCallingTool usage with allowed_callers and a Pydantic output type:
@tool(allowed_callers=["programmatic"])
def get_inventory(sku: str) -> InventoryOutput:
return InventoryOutput(sku=sku, available_units=42)
agent = Agent(
name="Inventory planner",
model="gpt-5.6",
model_settings=ModelSettings(tool_choice="programmatic_tool_calling"),
tools=[get_inventory, ProgrammaticToolCallingTool()],
)
result = Runner.run_sync(agent, "Check inventory for desk-lamp and summarize it.")
Sources