Skip to content

feat(tools): let the executor decide who runs provider-native code execution, and bring the files API along - #1366

Draft
daavoo wants to merge 6 commits into
mainfrom
feat/code-execution-executor
Draft

daavoo wants to merge 6 commits into
mainfrom
feat/code-execution-executor

Conversation

@daavoo

@daavoo daavoo commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Description

Today a request that declares a provider's own code-execution tool (Anthropic's code_execution_20250825, OpenAI's code_interpreter, the bare code_execution) is always forwarded to the provider, and only the Otari-specific otari_code_execution type reaches the gateway's sandbox. A client written against Anthropic's sandbox therefore breaks the moment its model is swapped for one with no native sandbox, and the same is true of the files it attaches.

The executor. This PR introduces who runs the code such a declaration asks for. auto (the new default) keeps the declaration with a provider that runs it natively for the dispatched model and wire format, and brings it to the gateway's sandbox otherwise, answering in the caller's own vocabulary (server_tool_use + code_execution_tool_result blocks on Messages, a code_interpreter_call item on Responses). otari always runs it here; provider always forwards it. The deployment sets the default (code_execution_executor, on the Tools page), a workspace policy may pin a value, and the X-Otari-Code-Execution header chooses per request where the workspace has not pinned.

Files, held to the same standard. This PR absorbs #1364 (the files API parity work, superseding it) and makes its sandbox half follow the executor rather than the otari_code_execution type alone:

  • Both SDKs' Files APIs are served on the same five routes (Anthropic FileMetadata for a caller sending anthropic-version, the OpenAI file object otherwise), listings are cursor-paged, an fsspec storage backend joins local and s3, and a retention sweep reclaims expired and deleted files.
  • Uploads a request references are seeded into the sandbox session whenever the code runs here, whether the request said otari_code_execution or a provider's declaration the executor brought here. When the provider keeps its declaration, an Anthropic container_upload falls back to being read as a document, exactly as it does without a sandbox.
  • Files the code produces are stored as code_execution_output files owned by the same user and workspace, and a caller who spoke Anthropic's vocabulary gets their file_id in the code_execution_output entries of the native result block, which is where its SDK looks.

A deployment without a sandbox is untouched: provider declarations are forwarded exactly as before and no policy is read for them. otari_code_execution keeps working unchanged.

Not in this PR, and tracked as follow-ups: a CodeExecutionPort with an e2b adapter behind the existing HTTP contract, cross-request container ids (a container id from a previous turn still addresses the provider's container), files in hybrid mode (no local database), cfile_ ids on the Responses wire, a platform-side executor pin in hybrid mode (the control plane's resolve is only consulted once the decision already points at the sandbox, so no natively served request turns into a 403), and staging attachments for a request a workspace pin pulls here (the pin is read after normalization; the docs say so).

How to test it locally

  1. docker compose --profile code-exec up, then send an Anthropic Messages request with tools: [{"type": "code_execution_20250825", "name": "code_execution"}] against a non-Anthropic model. The code runs on the sandbox and the response carries server_tool_use and code_execution_tool_result blocks with otari_srvtoolu_ ids.
  2. Send the same request against an Anthropic model: it is forwarded untouched. Add X-Otari-Code-Execution: otari and it runs here instead.
  3. Upload a file with the OpenAI SDK and with the Anthropic SDK, both pointed at the gateway; each gets its own response shape, and GET /api/v1/files?limit=2 pages with has_more and last_id. Reference it from a request that runs on the sandbox: the file is in the session's working directory, and a file the run writes comes back as a file_id in the tool result and, for an Anthropic-native caller, in the result block.
  4. Tools page: the Code execution group has a new "Who runs provider code tools" select; the workspace card below it has the same choice as a pin. GET /api/v1/tools lists the provider keywords under otari_code_execution.accepted_types unless the default is provider.

Automated coverage: tests/unit/test_code_executor.py (the decision, and when attachments are staged), tests/integration/test_code_execution_executor.py (the request path, header, pin, folding), native block minting in tests/unit/test_mcp_loop_messages.py and tests/unit/test_mcp_loop_responses.py, echoed-block folding in tests/unit/test_messages_minted_block_stripping.py, the files work's own tests (tests/integration/test_files_endpoint.py, tests/unit/test_sandbox_backend.py, tests/unit/test_content_normalizer.py, tests/unit/test_fsspec_file_store.py), plus the tool-settings, tools-discovery and dashboard tests. make lint, make typecheck, the unit suite, the full integration suite, the OSS smoke gate and the dashboard suite pass locally.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Supersedes #1364 (and through it #976 and #977). Part of #786. Groundwork for the code-execution provider abstraction discussed alongside #896.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran the Definition of Done checks locally (make lint, make typecheck, make test).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py).
  • If this changes a rule in ARCHITECTURE.md or scripts/check_architecture.py, the description names the rule and says why.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Fable 5.1)

Any additional AI details you'd like to share: Design agreed with the maintainer in conversation. The executor half was implemented, tested and documented by the agent; the files half is the maintainer's #1364, merged in and adapted to the executor by the agent. Everything was verified locally.

  • I am an AI Agent filling out this form (check box if true)

🤖 Generated with Claude Code

daavoo and others added 3 commits September 18, 2026 12:25
…sweep expired files

The /v1/files routes now answer in Anthropic's FileMetadata shape when the
caller sends anthropic-version (its SDK always does) and in the OpenAI shape
otherwise, and the listing is cursor-paged (limit, after/after_id, order,
has_more). Uploads referenced by a request that runs otari_code_execution are
seeded into the sandbox session with PutFile; container_upload blocks are
staged and replaced by a marker for the model. Files a run produces are
fetched with GetFile, stored as code_execution_output files owned by the same
user and workspace, and named with their file_id in the tool result. A bare
input_file or input_image item at the top level of a Responses input is now
normalized too. A background sweep reclaims the bytes and rows of expired
and deleted files.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
files_backend: fsspec plus a files_url (gcs://, abfs://, s3://, sftp://,
file://, ...) and files_storage_options reach whatever filesystem fsspec has an
implementation installed for, through the same FileStore protocol the local
and boto3 S3 backends implement. fsspec was already in the tree through
any-llm and is now a declared dependency.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…execution tool

A request that declares a provider's own code-execution tool (Anthropic's
code_execution_<date>, OpenAI's code_interpreter, the bare code_execution)
was always forwarded to the provider, so a client written against a frontier
model's sandbox broke when the model was swapped for one without a native
sandbox.

The executor decides who runs the code: auto (the default) keeps the
declaration with a provider that runs it natively for the dispatched model
and wire format, and brings it to the gateway's sandbox otherwise, answering
in the caller's own vocabulary (server_tool_use and code_execution_tool_result
on Messages, code_interpreter_call on Responses). The deployment sets the
default (code_execution_executor), a workspace policy may pin a value, and
the X-Otari-Code-Execution header chooses per request where the workspace has
not pinned. A deployment without a sandbox is untouched, and
otari_code_execution keeps working unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:01 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:01 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:01 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:01 — with GitHub Actions Active
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…half follow the executor

Merges origin/feat/files-api-parity (#1364): both SDKs' Files APIs on the same
routes, cursor paging, the fsspec storage backend, the retention sweep, and
uploads seeded into the sandbox with produced files stored back.

Adapted to the executor: attachments are staged whenever the code runs on the
gateway's sandbox, whether the request said otari_code_execution or a
provider's own declaration was brought here, and a container_upload falls
back to a document when the provider keeps its declaration. A gateway-run
execution's native code_execution_output entries carry the stored file_id a
caller can download, never the sandbox's internal id.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@daavoo daavoo changed the title feat(tools): let the executor decide who runs a provider-native code-execution tool feat(tools): let the executor decide who runs provider-native code execution, and bring the files API along Sep 18, 2026
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:31 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:31 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:31 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 11:31 — with GitHub Actions Active
…fely, fold echoed interpreter calls

Four review fixes on the executor and files work.

The sandbox backend streams a produced file under the files size cap instead of
buffering it first: a declared Content-Length over the cap is refused before a
byte is read, and a body with no length is abandoned as soon as it passes the
cap. What a run writes is untrusted, so the gateway must never hold a file it is
about to refuse.

A staged upload is seeded under its last path segment, suffixed when an earlier
attachment took the name, so a name with separators neither nests nor escapes
and two uploads named alike are both there. The marker the model is given
carries the name the file actually has.

A gateway-minted code_interpreter_call echoed on a Responses turn is folded into
an assistant message rather than dropped, as the Messages route already folds
its pair: the logs exist nowhere else in the transcript.

SandboxFileBridge.outputs was written and never read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… only the result block

The contract's result block carries a list of the files a call produced, but
the reference container leaves it empty and reports files only through
ListFiles, so against the only OSS backend no produced file ever reached
/v1/files. The backend now lists the workspace after seeding and again after
each call, and treats every path that appeared or changed as produced, unioned
with whatever the block names. A backend without ListFiles answers 404 and the
diff is simply empty, leaving the block's list as the only source, exactly as
before.

The rendered result and the Anthropic code_execution_output entries now list
every stored file, whichever source found it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@daavoo
daavoo force-pushed the feat/code-execution-executor branch from dbb0d5d to 6eb79b6 Compare September 18, 2026 15:10
@daavoo
daavoo deployed to integration-tests September 18, 2026 15:11 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 15:11 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 15:11 — with GitHub Actions Active
@daavoo
daavoo deployed to integration-tests September 18, 2026 15:11 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant