Skip to content

[RFC]: Support Serverless Code Interpreter #308

Description

@haoshan98

Problem statement / motivation

Summary

Add an optional, gateway-executed built-in tool that runs Python source in a fresh embedded sandbox and returns bounded text output to the model. Use Eryx initially, subject to resource containment and packaging validation. Here, serverless means no separately deployed execution service: the gateway calls a Rust library directly.

Motivation

Calculations and small data transformations can benefit from executable Python before we introduce file storage, artifact delivery, or session management. The existing tool framework already provides normalization, scheduling, and the model tool loop. Its code-interpreter declaration is currently a placeholder without an executor.

Goals and non-goals

Goals: execute Python source, return stdout/stderr or typed errors, isolate consecutive calls, and support existing Responses transports and item history. Operators control the runtime and package set.

Non-goals: file uploads/downloads, artifacts, persistent Python variables, reusable containers, shell/network access, host callbacks, model-installed packages, a standalone execution endpoint, Messages API parity, or multiple runtime implementations.

A turn may contain several executions, including corrections after errors; each starts fresh.

Proposed solution

Proposal

Runtime

Use Eryx's Rust embedded feature to run CPython compiled to WASI through Wasmtime. Approved pure-Python packages can be bundled; native extensions require WASI-compatible builds and experimental native-extensions support, rather than native Linux wheels. Start with the standard library and validate additional packages individually.

Keep the integration behind an optional Cargo feature and operator configuration, disabled by default. Share immutable runtime assets, but create fresh guest state for each call. Allow read-only access to approved runtime and package assets and required runtime configuration; expose no other host files, inherited host environment, network, or callbacks. Bound and discard any scratch storage.

API contract

Use execution: "gateway" to select gateway execution; the embedded runtime remains an implementation detail:

{"type":"code_interpreter","execution":"gateway"}

Gateway requests use the project-specific execution selector in place of OpenAI's container setting; SDKs may require extra_body or extended request types. Normalize the tool to a model-visible code_interpreter function accepting {"code":"print(25)"}. Instruct the model to print its answer; implicit last-expression display and structured return values are outside this contract. Reject unsupported declarations, disabled configurations, and function-name collisions before inference; validate source size again before execution.

Use OpenAI's code_interpreter_call output-item fields and status values:

{
  "type": "code_interpreter_call",
  "id": "ci_...",
  "container_id": "cntr_gw_...",
  "code": "print(25)",
  "status": "completed",
  "outputs": [{"type": "logs", "logs": "25\n"}]
}

Retain call_id, stdout/stderr, and typed errors internally. Project bounded output and error messages into standard logs entries, with completed for success and failed for failure; add no custom public fields. Cover invalid code, Python errors, timeout, cancellation, memory/output limits, unavailable capacity, and runtime failures, preserving partial output only when supplied. Model-facing results and public items derive from the same typed outcome, including the scheduler's outer timeout, while preserving other tools' failure behavior.

OpenAI requires a string container_id. Allocate one unique opaque gateway execution-context ID with each call plan, before resource admission or sandbox initialization, and retain it across lifecycle events, failures, and retrieval. This identifies a single-execution context, not a reusable container; it does not imply initialization succeeded. Reject client-supplied container IDs and configuration in phase one. Output shape alignment does not provide OpenAI container semantics: /v1/containers, files, and reuse remain deferred. Validate request routing and output decoding with supported client SDKs before release.

Integration

Extend the existing typed tool framework and reuse the output-item lifecycle across Responses transports, preserving streaming ownership boundaries. Buffer bounded output until completion, then append each canonical function call and output once before the next inference round. The public execution item must not replay as an input call; continuation retains item history while each execution starts with fresh Python state.

Alternatives

Option Advantages Limitations
Eryx embedded CPython compatibility; approved package support Runtime artifact/toolchain requirements; containment work
Monty embedded Direct Rust API; structured expression results Python subset; no third-party packages; process-level memory concerns

Both Eryx 0.7.2 and Monty 0.0.23 passed direct Rust execution proofs. Eryx also loaded a custom pure-Python module and demonstrated fresh namespaces. These establish basic capability, not complete containment or gateway integration.

Monty's official guide recommends subprocess-based monty-pool for untrusted code. Its direct memory limit requires allocator integration, whose hard-limit failure can terminate the process.

Risks and mitigations

Eryx inherits the hosting process's stdout/stderr. In the verified test, raw os.write(1, ...) remained sandbox-mediated but bypassed normal output capture and reached the hosting process's stdout. This observation does not demonstrate a sandbox escape. Redirect or deny WASI stdout/stderr, enforce output limits while bytes are produced, and verify capture with replaced Python streams. Disable unbounded tracing/stream handlers and disable or bound optional result capture. Truncating returned output is insufficient.

On timeout, cancellation, or shutdown, interrupt execution and await task completion before releasing capacity. Preserve existing foreground/background request policy; dropping a future does not prove execution stopped.

Build and distribution: Eryx 0.7.2 requires Rust 1.98.1 and a matching trusted precompiled runtime. The repository currently pins 1.98.0, while its documented baseline is 1.85. Resolve this explicitly. Verify artifacts and packages before deployment, initialize before readiness, and account for Eryx's host temporary-directory extraction. Validate supported release targets and offline startup.

Open questions

  • Is Eryx's embedded sandbox an acceptable choice for the initial gateway-executed code interpreter, given its Python package support and integration requirements?

References

Alternatives considered

No response

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestrfctag those issue that are related to rfc

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions