Back to Articles

OXP-Python: A Type-Safe Client for the Open eXecution Protocol Nobody's Talking About

[ View on GitHub ]

OXP-Python: A Type-Safe Client for the Open eXecution Protocol Nobody's Talking About

Hook

With 17 GitHub stars and zero documentation about what it actually does, oxp-python might be the most polished client for a protocol you've never heard of. Let's figure out if that matters.

Context

The Open eXecution Protocol (OXP) sits in the emerging space of standardized tool execution for AI systems. As LLMs gained the ability to call functions and use tools, the industry fragmented into vendor-specific implementations—OpenAI's function calling, Anthropic's tool use, LangChain's agent frameworks. Each uses different schemas, error handling patterns, and execution models.

OXP appears to be an attempt at creating a vendor-neutral protocol for AI tool execution, similar to how OpenAPI standardized REST API descriptions. The oxp-python library is the Python client for this protocol, generated using Stainless—a toolchain that produces SDK code from API specifications. The promise is compelling: write your tool execution logic once, expose it through OXP, and any compliant client can consume it. But with adoption numbers this low, we need to ask whether the protocol itself has gained traction, or if this is beautifully-engineered infrastructure for a standard that never materialized.

Technical Insight

The library's architecture reveals professional engineering choices, even if its adoption doesn't reflect it. Built on httpx rather than the ubiquitous requests library, it gains HTTP/2 support, connection pooling, and native async capabilities. The dual-client design means you get identical APIs whether you're working in synchronous or asynchronous contexts.

Here's what instantiation looks like:

from oxp import Oxp, AsyncOxp

# Synchronous client
client = Oxp(
    api_key="oxp_....",
    base_url="https://api.openexecprotocol.org",
    timeout=30.0,
    max_retries=2
)

# Asynchronous client with identical interface
async_client = AsyncOxp(
    api_key="oxp_....",
    timeout=30.0
)

The Stainless generation pipeline produces Pydantic models for all responses, which means you get runtime validation and convenient serialization helpers. When you make a request, you're working with strongly-typed objects:

# TypedDict for request parameters (nested)
response = client.executions.create(
    tool="data_analyzer",
    parameters={
        "query": "SELECT * FROM users",
        "format": "json"
    },
    timeout=60
)

# Pydantic model for response
print(response.execution_id)  # Type-safe attribute access
print(response.status)  # IDE autocomplete works
print(response.to_dict())  # Easy serialization

The error handling taxonomy is notably comprehensive. Rather than catching generic exceptions, you can handle specific failure modes:

from oxp import (
    APIConnectionError,
    APITimeoutError,
    RateLimitError,
    AuthenticationError,
    BadRequestError,
    ConflictError,
    InternalServerError
)

try:
    result = client.executions.create(...)
except RateLimitError as e:
    # Specific handling for rate limits
    retry_after = e.response.headers.get('Retry-After')
    logger.warning(f"Rate limited, retry after {retry_after}s")
except AuthenticationError:
    # Token refresh logic
    refresh_credentials()
except APITimeoutError:
    # Fallback to cached results
    return get_cached_execution()

The library also provides raw response access through wrapper interfaces, useful when you need to inspect headers, status codes, or implement custom caching:

# Access raw httpx.Response object
with_raw = client.with_raw_response
response = with_raw.executions.create(...)

print(response.headers['X-Request-ID'])
print(response.status_code)
parsed = response.parse()  # Get the Pydantic model

The streaming response wrapper handles long-running executions that emit progress updates:

with_streaming = client.with_streaming_response
with with_streaming.executions.create_stream(...) as stream:
    for chunk in stream.iter_lines():
        progress = json.loads(chunk)
        print(f"Progress: {progress['percent']}%")

Under the hood, the retry logic uses exponential backoff with jitter, and it's smart about which status codes warrant retries (429, 503, 504) versus which should fail immediately (401, 403). The default configuration retries twice with a base delay of 0.5 seconds, maxing out at 8 seconds. This is configurable per-client and even per-request.

What's particularly interesting is that all of this code is generated. The Stainless tool reads an OpenAPI spec for OXP and produces this entire library. That means updates to the protocol automatically flow into the client without manual maintenance. It also means customization is limited—you can't easily patch in custom behavior without modifying the generation pipeline or monkey-patching generated code, both of which are maintenance nightmares.

Gotcha

The elephant in the room is adoption. Seventeen GitHub stars isn't just low—it's a signal that either OXP hasn't gained traction, or this client library isn't the primary way people interact with the protocol. The repository has no examples directory, no documentation beyond the README, and critically, no explanation of what OXP actually does beyond "tool execution protocol." If you visit openexecprotocol.org, you'll need to assess whether the protocol itself is mature enough for your use case.

The generated nature cuts both ways. Yes, you get automatic updates and API consistency. But if you need to implement custom authentication flows, add middleware for logging, or integrate with existing monitoring systems, you're fighting against the generation pipeline. Want to add a custom header to all requests? You'll need to subclass the client and override methods, which may break on the next generation run. Need to swap out httpx for a different HTTP library? You're essentially forking the entire codebase. The abstraction is elegant but rigid.

Dependency weight is another consideration. Installing oxp-python pulls in httpx, Pydantic, typing-extensions, and their transitive dependencies. For a simple REST API client, that's considerable overhead. If you're building a Lambda function or a Docker container where size matters, you might get better results with a thin wrapper around requests or urllib3, sacrificing type safety for deployment efficiency.

Verdict

Use if: You're already committed to OXP as your tool execution protocol and need Python integration with strong typing. The generated client will save you from manually tracking API changes, and the async support is valuable if you're building event-driven systems or concurrent execution engines. The error handling granularity is genuinely useful if you're building production services that need to differentiate between transient failures and permanent errors. Use it if you value type safety enough to accept the dependency weight and generation constraints.

Skip if: You're evaluating tool execution protocols and haven't committed to OXP yet—the low adoption numbers suggest you should thoroughly vet the protocol's maturity and community support first. Skip it if you need extensive customization, as the generated nature makes deep modifications painful. Also skip if you're building latency-sensitive microservices where dependency bloat matters, or if you need battle-tested libraries with years of production hardening. Finally, skip if you can't find clear documentation about OXP's guarantees, security model, and protocol stability—a beautiful client for an undefined protocol is still undefined.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/ai-agents/openexecprotocol-oxp-python.svg)](https://starlog.is/api/badge-click/ai-agents/openexecprotocol-oxp-python)