Repository Public
1# Programmatic Tool Calling23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45Programmatic Tool Calling lets a model write and run JavaScript that coordinates its tools. A program can call tools in parallel, use loops and conditions, and keep intermediate results in the hosted runtime. This is useful when a task needs a sequence of related tool calls or needs to process large tool outputs before returning a result.67In the Responses API, your application decides whether Programmatic Tool Calling is available and which eligible tools the model can call directly, from a program, or either way. It continues to run any client-owned tool calls. The [Agents API](#agents-api) enables Programmatic Tool Calling by default and manages the agent loop for you.89Check the [model page](https://developers.openai.com/api/docs/models) before enabling Programmatic Tool Calling.1011## Understand the runtime environment1213OpenAI runs each generated program in a fresh, isolated V8 runtime. The runtime supports JavaScript with top-level `await`, but it does not provide Node.js, package installation, direct network access, a general-purpose filesystem, subprocess execution, a console, or persistent JavaScript state between program executions. Programs can interact with external systems only through tools enabled in the request and can emit output with `text(...)` or `image(...)`.1415For Responses API requests, Programmatic Tool Calling supports Zero Data Retention (ZDR) workflows without requiring a persistent code-execution container. ZDR must be enabled for the organization or project; setting `store: false` enables stateless continuation but does not enable ZDR by itself. Eligibility and retention depend on the complete request, including its model, tools, and third-party services; see [data controls](https://developers.openai.com/api/docs/guides/your-data).1617## Choose when to use Programmatic Tool Calling1819Use Programmatic Tool Calling when a stage has predictable control flow and code can return a smaller structured result. Use direct tool calling when one call is sufficient, each result requires fresh model judgment, or the work requires approval or preservation of citations or native artifacts.2021| Task shape | Recommended mode |22| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |23| A single lookup or action | Use direct tool calling. |24| Several results that code can filter, join, rank, remove duplicates from, aggregate, or validate | Use Programmatic Tool Calling when the program can return a smaller structured result. |25| Dependent calls with predictable data flow | Use Programmatic Tool Calling when code can derive later arguments and the limits and failure behavior are explicit. |26| Adaptive search or semantic evaluation | Use direct tool calling when each result should influence the model's next decision. |27| Writes or approval-sensitive actions | Use direct tool calling by default to preserve a clear authorization boundary. |28| Final citation or native artifact validation | Use direct tool calling unless the program preserves the native output and validates every required item. |2930## Configure Programmatic Tool Calling3132For the Responses API, add the `programmatic_tool_calling` hosted tool to the request. Then set `allowed_callers` on each eligible tool that the program can invoke.3334Enable Programmatic Tool Calling3536```json37[38 {39 "type": "function",40 "name": "get_inventory",41 "description": "Return an object with sku (string) and available_units (number).",42 "parameters": {43 "type": "object",44 "properties": {45 "sku": { "type": "string" }46 },47 "required": ["sku"],48 "additionalProperties": false49 },50 "output_schema": {51 "type": "object",52 "properties": {53 "sku": { "type": "string" },54 "available_units": { "type": "number" }55 },56 "required": ["sku", "available_units"],57 "additionalProperties": false58 },59 "allowed_callers": ["programmatic"]60 },61 {62 "type": "programmatic_tool_calling"63 }64]65```666768`allowed_callers` controls how the model can invoke a tool:6970| Value | Behavior |71| ---------------------------- | ------------------------------------------------------- |72| Omitted or `["direct"]` | The model can call the tool directly. |73| `["programmatic"]` | Only code in a `program` item can call the tool. |74| `["direct", "programmatic"]` | The model can call the tool directly or from a program. |7576`parameters` describes the function arguments. When a function returns predictable structured data, `output_schema` describes the JSON object encoded in its `function_call_output.output` string. Define both so generated JavaScript can use the returned fields reliably.7778### Supported tools7980The following tool types support `allowed_callers: ["programmatic"]`:8182- `function` and `custom`83- `mcp`84- `apply_patch`85- Local and hosted `shell`86- `code_interpreter`8788For MCP tools, the tool's `require_approval` policy can pause the program until you approve the call.8990For OpenAI-hosted tools, review the tool's data-retention and security guidance before enabling it in a program.9192### Combine with tool search9394[Tool search](https://developers.openai.com/api/docs/guides/tools-tool-search) runs as a top-level Responses API tool, not from inside generated JavaScript. Function, custom, and MCP tools with `defer_loading: true` are not initially available to a program. After the model loads a matching tool, a later program can invoke it through `tools.*` when its `allowed_callers` includes `"programmatic"`. An already-running program cannot invoke tool search, so the model must load deferred tools before starting a program that needs them.9596## Guide routing when both modes are available9798When your application lets the model call a function directly or from a program, assign each route to a specific workflow stage. Generic instructions such as "use Programmatic Tool Calling efficiently" don't identify the intended boundary. For example:99100```text101<tool_orchestration>102Use Programmatic Tool Calling for [bounded stage] using only [eligible tools].103Run independent calls concurrently when safe. Use only documented tool input104and output fields.105106Process and reduce the intermediate results, then emit exactly [program result shape],107including the evidence needed for the final answer.108109Stop when [condition] is met. Retry transient failures at most [R] times.110Do not repeat completed calls or perform side-effecting actions. If a required111result is still missing, return a clear structured failure.112113Use direct tool calls for [semantic judgment, approval, or final validation].114</tool_orchestration>115```116117Here is an example of how to use this template:118119```text120<tool_orchestration>121Use Programmatic Tool Calling to compare inventory with demand for sku_123122using only get_inventory and get_demand. Run both calls concurrently. Use123only documented tool input and output fields.124125Process and reduce the intermediate results, then emit exactly one JSON object126with sku, available_units, requested_units, and shortage_units, where127shortage_units is max(requested_units - available_units, 0). Include128available_units and requested_units as evidence for the calculation.129130Stop when both tool results contain the required fields. Retry transient131failures at most 1 time. Do not repeat completed calls or perform132side-effecting actions. If a required result is still missing, return a clear133structured failure.134135Use direct tool calls only for approval before any inventory-changing action.136</tool_orchestration>137```138139For workflows that need both modes, define one handoff and avoid switching routes or repeating work. If a safe fallback exists, define it once and limit its retries.140141## Understand program response items142143Each API call still returns the standard [Responses API object](https://developers.openai.com/api/reference/resources/responses/methods/create). Programmatic Tool Calling doesn't introduce a separate response envelope. When the model uses Programmatic Tool Calling, the response's `output` array can contain:144145- A `program` item containing the generated JavaScript, a `call_id`, and an opaque `fingerprint` used to resume or replay the program.146- A `function_call` item made by the program. It has its own `call_id`, which your application uses to return the function result. Its `caller.caller_id` matches the program's `call_id`.147- A `program_output` item containing the program's final result and status. Its `call_id` matches the program's `call_id`, and its `status` is `completed` or `incomplete`.148149These are separate top-level items in `response.output`; the `caller` field records their execution relationship.150151For example, a program can pause while your application runs `get_inventory` and `get_demand`:152153Program and nested function calls154155```json156[157 {158 "type": "program",159 "id": "prog_123",160 "call_id": "call_prog_123",161 "code": "const [stock, demand] = await Promise.all([tools.get_inventory({ sku: 'sku_123' }), tools.get_demand({ sku: 'sku_123' })]); text(JSON.stringify({ sku: stock.sku, available_units: stock.available_units, requested_units: demand.requested_units, shortage_units: Math.max(demand.requested_units - stock.available_units, 0) }));",162 "fingerprint": "opaque_replay_state"163 },164 {165 "type": "function_call",166 "id": "fc_123",167 "call_id": "call_inventory_123",168 "name": "get_inventory",169 "arguments": "{\\"sku\\":\\"sku_123\\"}",170 "caller": {171 "type": "program",172 "caller_id": "call_prog_123"173 }174 },175 {176 "type": "function_call",177 "id": "fc_456",178 "call_id": "call_demand_123",179 "name": "get_demand",180 "arguments": "{\\"sku\\":\\"sku_123\\"}",181 "caller": {182 "type": "program",183 "caller_id": "call_prog_123"184 }185 }186]187```188189190These examples show only the relevant items from `response.output`; they omit the surrounding standard Responses object. After your application returns the nested function results, a later response can contain the complete `program_output` item:191192Program output193194```json195{196 "type": "program_output",197 "id": "prog_out_123",198 "call_id": "call_prog_123",199 "result": "{\\"sku\\":\\"sku_123\\",\\"available_units\\":42,\\"requested_units\\":31,\\"shortage_units\\":0}",200 "status": "completed"201}202```203204205The JSON string in `program_output.result` follows the program result shape from your instructions. The surrounding `program_output` item follows the API contract shown above. These are separate contracts. A final `message` can arrive with the program output or in a later response, so continue until you receive that message.206207OpenAI runs the model-generated JavaScript in the hosted runtime. Your application executes returned client-owned function calls; it does not execute the generated JavaScript.208209Return the function result as a `function_call_output`. Copy `caller` from the function call without changing it. The service uses that value to resume the correct program.210211## Continue after client-owned function calls212213A program can pause more than once as it reaches client-owned tools. Continue until the response contains a final assistant message:2142151. Send the request with the hosted tool and functions that allow programmatic calls.2161. Run every returned client-owned function call.2171. Return each function result with the original `call_id` and `caller`.2181. Handle an incomplete response before continuing.2191. If the response contains no pending `function_call` items and no final `message` item, continue from that response. With `store: false`, replay its output items; for a stored response, use `previous_response_id`.2201. Stop when the response contains a final `message` item. Read `response.output_text` or the message's refusal content.221222The following example uses `store: false`, preserves every response item, and returns each function result to the program:223224Run a programmatic tool-calling loop225226```javascript227import OpenAI from "openai";228import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";229230const client = new OpenAI();231232const implementations = {233 get_inventory: async ({ sku }) => ({ sku, available_units: 42 }),234 get_demand: async ({ sku }) => ({ sku, requested_units: 31 }),235};236237const tools = [238 {239 type: "function",240 name: "get_inventory",241 description:242 "Return an object with sku (string) and available_units (number).",243 parameters: {244 type: "object",245 properties: { sku: { type: "string" } },246 required: ["sku"],247 additionalProperties: false,248 },249 output_schema: {250 type: "object",251 properties: {252 sku: { type: "string" },253 available_units: { type: "number" },254 },255 required: ["sku", "available_units"],256 additionalProperties: false,257 },258 allowed_callers: ["programmatic"],259 strict: true,260 },261 {262 type: "function",263 name: "get_demand",264 description:265 "Return an object with sku (string) and requested_units (number).",266 parameters: {267 type: "object",268 properties: { sku: { type: "string" } },269 required: ["sku"],270 additionalProperties: false,271 },272 output_schema: {273 type: "object",274 properties: {275 sku: { type: "string" },276 requested_units: { type: "number" },277 },278 required: ["sku", "requested_units"],279 additionalProperties: false,280 },281 allowed_callers: ["programmatic"],282 strict: true,283 },284 { type: "programmatic_tool_calling" },285];286287const input = [288 {289 role: "user",290 content: "Compare inventory with demand for sku_123.",291 },292];293294while (true) {295 const response = await client.responses.create({296 model: "YOUR_MODEL_ID",297 store: false,298 input,299 tools,300 });301302 if (response.status !== "completed") {303 throw new Error(`Response ended with status ${response.status}`);304 }305306 // Preserve replayable output, including program and reasoning items.307 input.push(...toResponseInputItems(response.output));308309 const calls = response.output.filter((item) => item.type === "function_call");310311 if (calls.length === 0) {312 const message = response.output.find((item) => item.type === "message");313 if (message) {314 const refusal = message.content.find((part) => part.type === "refusal");315 console.log(response.output_text || refusal?.refusal || "");316 break;317 }318 continue;319 }320321 const outputs = await Promise.all(322 calls.map(async (call) => {323 const run = implementations[call.name];324 if (!run) throw new Error(`Unknown tool: ${call.name}`);325326 const result = await run(JSON.parse(call.arguments));327 return {328 type: "function_call_output",329 call_id: call.call_id,330 output: JSON.stringify(result),331 // Preserve caller so the runtime can resume the correct program.332 caller: call.caller,333 };334 })335 );336337 input.push(...outputs);338}339```340341```python342import json343344from openai import OpenAI345346client = OpenAI()347model = "gpt-6-astra"348349350def get_inventory(sku):351 return {"sku": sku, "available_units": 42}352353354def get_demand(sku):355 return {"sku": sku, "requested_units": 31}356357358implementations = {359 "get_inventory": get_inventory,360 "get_demand": get_demand,361}362363tools = [364 {365 "type": "function",366 "name": "get_inventory",367 "description": "Return an object with sku (string) and available_units (number).",368 "parameters": {369 "type": "object",370 "properties": {"sku": {"type": "string"}},371 "required": ["sku"],372 "additionalProperties": False,373 },374 "output_schema": {375 "type": "object",376 "properties": {377 "sku": {"type": "string"},378 "available_units": {"type": "number"},379 },380 "required": ["sku", "available_units"],381 "additionalProperties": False,382 },383 "allowed_callers": ["programmatic"],384 },385 {386 "type": "function",387 "name": "get_demand",388 "description": "Return an object with sku (string) and requested_units (number).",389 "parameters": {390 "type": "object",391 "properties": {"sku": {"type": "string"}},392 "required": ["sku"],393 "additionalProperties": False,394 },395 "output_schema": {396 "type": "object",397 "properties": {398 "sku": {"type": "string"},399 "requested_units": {"type": "number"},400 },401 "required": ["sku", "requested_units"],402 "additionalProperties": False,403 },404 "allowed_callers": ["programmatic"],405 },406 {"type": "programmatic_tool_calling"},407]408409input_items = [410 {411 "role": "user",412 "content": "Compare inventory with demand for sku_123.",413 }414]415416while True:417 response = client.responses.create(418 model=model,419 store=False,420 input=input_items,421 tools=tools,422 )423424 if response.status != "completed":425 raise RuntimeError(f"Response ended with status {response.status}")426427 # Preserve every output item, including program and reasoning items.428 input_items.extend(item.model_dump(exclude_none=True) for item in response.output)429430 calls = [item for item in response.output if item.type == "function_call"]431 if not calls:432 message = next(433 (item for item in response.output if item.type == "message"), None434 )435 if message:436 refusal = next(437 (part.refusal for part in message.content if part.type == "refusal"),438 "",439 )440 print(response.output_text or refusal)441 break442 continue443444 for call in calls:445 run = implementations.get(call.name)446 if run is None:447 raise ValueError(f"Unknown tool: {call.name}")448449 result = run(**json.loads(call.arguments))450 input_items.append(451 {452 "type": "function_call_output",453 "call_id": call.call_id,454 "output": json.dumps(result),455 # Preserve caller so the runtime can resume the correct program.456 "caller": call.caller.model_dump() if call.caller else None,457 }458 )459```460461```go462package main463464import (465 "context"466 "encoding/json"467 "fmt"468469 "github.com/openai/openai-go/v3"470 "github.com/openai/openai-go/v3/responses"471)472473type toolArguments struct {474 SKU string `json:"sku"`475}476477func main() {478 client := openai.NewClient()479 input := responses.ResponseInputParam{480 responses.ResponseInputItemParamOfMessage(481 "Compare inventory with demand for sku_123.",482 responses.EasyInputMessageRoleUser,483 ),484 }485 tools := []responses.ToolUnionParam{486 functionTool(487 "get_inventory",488 "Return an object with sku (string) and available_units (number).",489 "available_units",490 ),491 functionTool(492 "get_demand",493 "Return an object with sku (string) and requested_units (number).",494 "requested_units",495 ),496 programmaticTool(),497 }498499 for {500 response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{501 Model: "gpt-6-astra",502 Store: openai.Bool(false),503 Input: responses.ResponseNewParamsInputUnion{OfInputItemList: input},504 Tools: tools,505 })506 if err != nil {507 panic(err)508 }509 if response.Status != "completed" {510 panic(fmt.Errorf("response ended with status %s", response.Status))511 }512513 // Preserve every output item, including program and reasoning items.514 input = append(input, outputAsInput(response.Output)...)515516 calls := functionCalls(response.Output)517 if len(calls) == 0 {518 if text, ok := finalMessageText(response); ok {519 fmt.Println(text)520 break521 }522 continue523 }524525 for _, call := range calls {526 result, err := runTool(call.Name, call.Arguments)527 if err != nil {528 panic(err)529 }530 output, err := json.Marshal(result)531 if err != nil {532 panic(err)533 }534535 toolOutput := responses.ResponseInputItemParamOfFunctionCallOutput(string(output))536 toolOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)537 caller := call.Caller.AsProgram()538 if caller.CallerID == "" {539 panic("function call is missing its program caller")540 }541 // Preserve caller so the runtime can resume the correct program.542 toolOutput.OfFunctionCallOutput.Caller.OfProgram =543 &responses.ResponseInputItemFunctionCallOutputCallerProgramParam{544 CallerID: caller.CallerID,545 }546 input = append(input, toolOutput)547 }548 }549}550551func functionTool(name, description, resultField string) responses.ToolUnionParam {552 parameters := map[string]any{553 "type": "object",554 "properties": map[string]any{555 "sku": map[string]any{"type": "string"},556 },557 "required": []string{"sku"},558 "additionalProperties": false,559 }560 outputSchema := map[string]any{561 "type": "object",562 "properties": map[string]any{563 "sku": map[string]any{"type": "string"},564 resultField: map[string]any{"type": "number"},565 },566 "required": []string{"sku", resultField},567 "additionalProperties": false,568 }569 tool := responses.ToolParamOfFunction(name, parameters, true)570 tool.OfFunction.Description = openai.String(description)571 tool.OfFunction.AllowedCallers = []string{"programmatic"}572 tool.OfFunction.OutputSchema = outputSchema573 return tool574}575576func programmaticTool() responses.ToolUnionParam {577 tool := responses.NewToolProgrammaticToolCallingParam()578 return responses.ToolUnionParam{OfProgrammaticToolCalling: &tool}579}580581func outputAsInput(582 output []responses.ResponseOutputItemUnion,583) []responses.ResponseInputItemUnionParam {584 input := make([]responses.ResponseInputItemUnionParam, 0, len(output))585 for _, item := range output {586 var converted responses.ResponseInputItemUnion587 if err := json.Unmarshal([]byte(item.RawJSON()), &converted); err != nil {588 panic(err)589 }590 input = append(input, converted.ToParam())591 }592 return input593}594595func functionCalls(596 output []responses.ResponseOutputItemUnion,597) []responses.ResponseFunctionToolCall {598 calls := make([]responses.ResponseFunctionToolCall, 0)599 for _, item := range output {600 if item.Type == "function_call" {601 calls = append(calls, item.AsFunctionCall())602 }603 }604 return calls605}606607func finalMessageText(response *responses.Response) (string, bool) {608 for _, item := range response.Output {609 if item.Type != "message" {610 continue611 }612 text := response.OutputText()613 if text != "" {614 return text, true615 }616 for _, content := range item.AsMessage().Content {617 if content.Type == "refusal" {618 return content.AsRefusal().Refusal, true619 }620 }621 return "", true622 }623 return "", false624}625626func runTool(name, argumentsJSON string) (map[string]any, error) {627 var arguments toolArguments628 if err := json.Unmarshal([]byte(argumentsJSON), &arguments); err != nil {629 return nil, fmt.Errorf("parse %s arguments: %w", name, err)630 }631632 switch name {633 case "get_inventory":634 return map[string]any{"sku": arguments.SKU, "available_units": 42}, nil635 case "get_demand":636 return map[string]any{"sku": arguments.SKU, "requested_units": 31}, nil637 default:638 return nil, fmt.Errorf("unknown tool: %s", name)639 }640}641```642643```ruby644require "json"645require "openai"646647client = OpenAI::Client.new648649def get_inventory(sku:)650 {651 sku: sku,652 available_units: 42653 }654end655656def get_demand(sku:)657 {658 sku: sku,659 requested_units: 31660 }661end662663implementations = {664 "get_inventory" => method(:get_inventory),665 "get_demand" => method(:get_demand)666}667tools = [668 {669 type: :function,670 name: "get_inventory",671 description: "Return an object with sku (string) and available_units (number).",672 parameters: {673 type: :object,674 properties: { sku: { type: :string } },675 required: ["sku"],676 additionalProperties: false677 },678 output_schema: {679 type: :object,680 properties: {681 sku: { type: :string },682 available_units: { type: :number }683 },684 required: %w[sku available_units],685 additionalProperties: false686 },687 allowed_callers: [:programmatic],688 strict: true689 },690 {691 type: :function,692 name: "get_demand",693 description: "Return an object with sku (string) and requested_units (number).",694 parameters: {695 type: :object,696 properties: { sku: { type: :string } },697 required: ["sku"],698 additionalProperties: false699 },700 output_schema: {701 type: :object,702 properties: {703 sku: { type: :string },704 requested_units: { type: :number }705 },706 required: %w[sku requested_units],707 additionalProperties: false708 },709 allowed_callers: [:programmatic],710 strict: true711 },712 { type: :programmatic_tool_calling }713]714input = [715 {716 role: :user,717 content: "Compare inventory with demand for sku_123."718 }719]720721loop do722 response = client.responses.create(723 model: "gpt-6-astra",724 store: false,725 input: input,726 tools: tools727 )728 unless response.status == OpenAI::Responses::ResponseStatus::COMPLETED729 raise "Response ended with status #{response.status}"730 end731732 # Preserve every output item, including program and reasoning items.733 input.concat(response.output)734 calls = response.output.grep(OpenAI::Models::Responses::ResponseFunctionToolCall)735736 if calls.empty?737 message = response.output.find do |item|738 item.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)739 end740 next unless message.is_a?(OpenAI::Models::Responses::ResponseOutputMessage)741742 refusal = message.content.find do |content|743 content.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)744 end745 text = response.output_text746 if text.empty? &&747 refusal.is_a?(OpenAI::Models::Responses::ResponseOutputRefusal)748 text = refusal.refusal749 end750 puts(text)751 break752 end753754 calls.each do |call|755 implementation = implementations.fetch(call.name) do756 raise ArgumentError, "Unknown tool: #{call.name}"757 end758 result = implementation.call(**JSON.parse(call.arguments, symbolize_names: true))759 output = {760 type: :function_call_output,761 call_id: call.call_id,762 output: JSON.generate(result)763 }764 # Preserve caller so the runtime can resume the correct program.765 output[:caller] = call.caller_.to_h if call.caller_766 input << output767 end768end769```770771772When you store responses, you can continue from `previous_response_id` instead of resending all earlier response items. Send the new `function_call_output` items as the next input. With `store: false`, replay the complete sequence in order, including every `program`, reasoning, function-call, function-call-output, and `program_output` item.773774For stateless reasoning-model requests, replay every returned reasoning item. Each item includes `encrypted_content` by default. See [conversation state](https://developers.openai.com/api/docs/guides/conversation-state#manually-manage-conversation-state) for the general stateless pattern.775776## Design tools for programs777778- Return structured, compact data that JavaScript can inspect without parsing prose.779- Use `output_schema` to define each tool's expected return fields and types, and document its error behavior. If the return shape isn't known in advance, keep the tool direct so the model can inspect the result.780- Define the exact program result shape and required evidence. Return a clear structured failure when the program can't produce a valid result.781- Make function calls idempotent when possible. A retry or replay shouldn't repeat an unsafe side effect.782- Check arguments and permissions for each call in your application, even when it comes from a hosted program.783- Give tools specific names and descriptions so the model can compose them correctly.784- Require application-level approval before high-impact actions, regardless of the caller.785786## Evaluate Programmatic Tool Calling787788Programmatic Tool Calling can reduce the amount of intermediate tool output added to model context, but the effect depends on the task and tool responses. Start with direct tool calling as a baseline, then compare both approaches on representative tasks.789790Define the final-answer quality bar and required evidence before measuring efficiency. Evaluate token use and tool calls alongside correctness, completeness, and evidence coverage, and make any accepted quality tradeoff explicit.791792Measure:793794- Final-answer correctness, completeness, and evidence coverage.795- Input and total tokens, end-to-end latency, and cost.796- Model turns, tool calls, retries, and recovery behavior.797- Safety outcomes, especially for side effects and approval requirements.798- Whether the route that ran matched the intended workflow stage.799800## Agents API801802In the [Agents API](https://developers.openai.com/api/docs/guides/agents-api/overview), Programmatic Tool Calling runs in the OpenAI-managed agent harness and is enabled by default. The harness gives the agent an `exec` tool and makes its existing tools available inside generated JavaScript. You don't need to wrap those tools as command-line programs or install them in the sandbox.803804To disable Programmatic Tool Calling, include this entry in `agent.tools`:805806```json807{808 "type": "programmatic_tool_calling",809 "enabled": false810}811```812813Omitting the entry or its `enabled` field leaves Programmatic Tool Calling enabled. A type-only entry, `{ "type": "programmatic_tool_calling" }`, also keeps it enabled. The `allowed_callers` configuration and Responses continuation loop above describe the Responses API integration.814815Programmatic Tool Calling also works in conversation-only sessions with `environment.type` set to `none`. Bash, executor MCPs, and other tools that run in a sandbox still require an [execution environment](https://developers.openai.com/api/docs/guides/agents-api/environments/self-hosted).816817Orchestrating a tool in JavaScript doesn't change where the tool runs. A shell call runs commands in the sandbox; the JavaScript runtime doesn't start system processes itself. Executor MCPs still use the sandbox, and function tools still call your application server. The agent processes their results before deciding what enters model context.818819Use the routing guidance above to define which workflow stages should use code. Follow [Functions](https://developers.openai.com/api/docs/guides/agents-api/tools/functions) and [MCP connections](https://developers.openai.com/api/docs/guides/agents-api/tools/mcp) for Agents API configuration and call handling.820821## Related guides822823- Use [function calling](https://developers.openai.com/api/docs/guides/function-calling) to define client-owned functions.824- Use [tool search](https://developers.openai.com/api/docs/guides/tools-tool-search) to defer large tool definitions until a model needs them.825- Use [conversation state](https://developers.openai.com/api/docs/guides/conversation-state) to continue stored or stateless Responses API requests.826- Review [data controls](https://developers.openai.com/api/docs/guides/your-data) before choosing a storage mode.