Skip to content

Add OpenAI-compatible shell tool support for hosted and local execution #170

Description

@maralbahari

Problem statement / motivation

The Server-Side Tool Execution roadmap lists Shell tools as an expected tool area, but agentic-api does not currently support the OpenAI Responses API shell tool.

The Rust request model has no typed shell declaration or shell_call / shell_call_output items, and the tool loop has no configurable local shell executor. This prevents a self-hosted agentic-api deployment from acting as the caller-controlled local runtime described by the OpenAI API.

This enhancement is intentionally limited to the current local execution shape:

{
  "type": "shell",
  "environment": {
    "type": "local"
  }
}

It does not require a Containers API, hosted execution environments, or container lifecycle management.

The older tool named local_shell, with local_shell_call items, is documented by OpenAI as outdated. New implementation work should target the current shell tool with environment.type: "local".

Proposed solution

Add first-class support for the current OpenAI-compatible local shell tool and allow deployments to register an explicit local shell executor.

Wire types

  • Accept and preserve {"type": "shell", "environment": {"type": "local"}}.
  • Add typed input/output items for:
    • shell_call
    • shell_call_output
  • Preserve:
    • call_id
    • action.commands[]
    • timeout_ms
    • max_output_length
    • per-command stdout and stderr
    • exit outcomes with exit_code
    • timeout outcomes
    • call status
  • Keep the wire representation non-exhaustive so future fields can be preserved safely.
  • Do not treat unsupported shell environments as permission to execute. Preserve/pass through or return a clear unsupported-capability error according to the existing tool ownership rules.

Tool routing and execution

  • Register the local shell tool in the request-scoped tool registry.
  • If upstream inference requires function-shaped tools, normalize the declaration internally and restore model calls to the public typed shell_call shape.
  • Execute shell calls only when an explicit gateway shell handler and execution policy are configured.
  • Treat the configured agentic-api execution environment as the local runtime: execute the requested commands, capture stdout/stderr and outcomes, append shell_call_output, and continue the inference loop.
  • If no gateway shell handler is configured, preserve/return the shell_call so a client-controlled runtime can execute it and submit the matching shell_call_output.
  • Never execute arbitrary commands merely because a request declares the shell tool.
  • Support blocking and streaming Responses flows, including continuation through previous_response_id and stored response state.

Execution policy and safety

Local command execution must be disabled by default and enabled only through explicit deployment configuration.

The executor contract should support:

  • An allowed working root and validated working directory.
  • A minimal, explicitly configured environment instead of inheriting all gateway environment variables.
  • Command or executable allowlists/denylists where appropriate.
  • Time, CPU, memory, process, file-size, and output limits.
  • Configurable filesystem and network restrictions.
  • Cancellation that terminates child processes when the request is cancelled or times out.
  • Maximum tool-loop rounds.
  • Audit logging without leaking secrets or sensitive command output.
  • An approval hook for commands outside the deployment's pre-approved policy.

The initial implementation may use a deployment-provided sandbox, jailed user, restricted process runner, or another local isolation mechanism. It does not need to create or expose managed containers.

Acceptance criteria

  • {"type": "shell", "environment": {"type": "local"}} round-trips through request parsing, serialization, storage, and rehydration.
  • shell_call and shell_call_output preserve commands, timeouts, maximum output length, stdout/stderr, and exit/timeout outcomes.
  • A configured test executor completes a multi-round local shell tool loop and produces a final assistant response.
  • If no gateway executor is configured, the shell call is never executed implicitly and remains available for client execution.
  • Blocking and streaming Responses flows expose OpenAI-compatible shell item lifecycles.
  • previous_response_id continuation preserves shell calls and their outputs.
  • Timeouts, non-zero exits, output truncation, malformed outputs, cancellation, executor failure, and loop exhaustion are test-covered.
  • Unsupported shell environment shapes are never executed as local shell commands.
  • No Containers API or managed-container lifecycle is required by this enhancement.
  • Documentation explains executor registration, disabled-by-default behavior, local execution policy, approvals, and the difference between current shell local mode and legacy local_shell.
  • Compatibility tests cover the OpenAI request and output shapes below.

Alternatives considered

No response

Additional context

OpenAI API examples

The model name below follows the current OpenAI documentation; a vLLM deployment would use a model capable of emitting the compatible tool-call shape.

Request local shell access

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1")

response = client.responses.create(
    model="gpt-5.6",
    instructions="The local bash shell environment is on Linux.",
    input="Find the five largest PDF files in ~/Documents.",
    tools=[
        {"type": "shell", "environment": {"type": "local"}}
    ],
)

The model can return a shell call:

{
  "type": "shell_call",
  "call_id": "call_001",
  "action": {
    "commands": ["find ~/Documents -name '*.pdf' -printf '%s %p\\n' | sort -nr | head -5"],
    "timeout_ms": 120000,
    "max_output_length": 4096
  },
  "status": "in_progress"
}

After the configured local runtime executes the command, it returns:

{
  "type": "shell_call_output",
  "call_id": "call_001",
  "max_output_length": 4096,
  "output": [
    {
      "stdout": "...",
      "stderr": "",
      "outcome": {
        "type": "exit",
        "exit_code": 0
      }
    }
  ]
}

The output is appended to the next inference round until the model stops emitting shell_call items.

Out of scope

  • A public Containers API.
  • API-managed hosted shell environments.
  • Reusable managed execution environments and their lifecycle.
  • Uploading files into or downloading artifacts from a managed environment.
  • Network configuration for API-managed environments.
  • Automatic execution on the unrestricted gateway host.

These capabilities can be proposed separately if reusable hosted shell environments become a project requirement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions