RepoNovita AINovita AIpublished Sep 11, 2026seen 16h

novitalabs/openai-agents-api-executor

TypeScript

Open original ↗

Captured source

source ↗

novitalabs/openai-agents-api-executor

Description: Novita Sandbox as a self-hosted execution environment for the OpenAI Agents API

Language: TypeScript

Stars: 2

Forks: 0

Open issues: 0

Created: 2026-09-11T04:01:09Z

Pushed: 2026-09-11T09:59:21Z

Default branch: main

Fork: no

Archived: no

README:

Novita Sandbox

Run sandbox tools in Novita while OpenAI runs the agent and maintains session state. OpenAI hosts the Codex harness; the sandbox runs codex exec-server, which dials out to OpenAI to receive commands and return results. Nothing listens on an inbound port.

application ──create session──▶ Agents API ◀──outbound ws── codex exec-server
│ (Novita sandbox)
└──start sandbox + executor───────────────────────────────────┘

Choose a provisioning mode:

  • [Application-managed](#application-managed): your application creates the

sandbox and connects its executor directly.

  • [Webhook-managed](#webhook-managed): a controller starts or reconnects

sandboxes from OpenAI webhooks, so your application never touches the sandbox SDK.

Both are implemented here. Sandbox lifecycle compares them.

Attaching an agent to a sandbox you already own is the common case in either mode; npm run example is that path as running code.

Before you begin

cp .env.example .env
npm install

| Variable | Where it comes from | Scope | | --- | --- | --- | | NOVITA_API_KEY | Novita console | template build, sandbox lifecycle | | OPENAI_API_KEY | platform.openai.com/api-keys | your application; never enters the sandbox | | OPENAI_EXECUTOR_API_KEY | platform Agents tab → environment keys | the only credential that enters the sandbox |

The application key needs api.agents.read, api.agents.write, and api.responses.write. The executor key needs every other permission set to None — it can connect environments and nothing else, which is what makes it safe for agent-generated code to read. Both keys must belong to the same organization, project, and user or service account, or the session and its executor will not match up.

See executor authentication for OpenAI's own description of the split.

Application-managed

Your application owns the sandbox for the whole session: create it, start the executor, send input, then release both when finished. Two steps to a working session — the first needs only NOVITA_API_KEY.

1. Create a sandbox

The executor template is published, so there is nothing to build:

npm install -g novita-sandbox-cli
export NOVITA_API_KEY=...

novita sbx create openai-agents-api-executor --long-running --timeout 720h

Keep the sandbox id it prints — the agent attaches to this sandbox, and its files outlive any single turn.

2. Run one real session

npm run e2e

It creates a self-hosted session, starts a sandbox and the executor, waits for the environment to connect, asks the agent to write a file, and then checks the sandbox filesystem for that file rather than trusting the agent's summary. It deletes the session and kills the sandbox on the way out, including on failure.

A pass looks like this:

1. Creating self-hosted session
session=sess_... environment=env_...
2. Starting sandbox and executor
sandbox=i...
3. Waiting for the environment to connect
agent.session.environment.pending
agent.session.environment.connected
4. Sending a task
5. Checking the workspace for the written file
file contents: novita executor ok

Timing breakdown:
...ms session created
...ms executor started
...ms environment connected
...ms input accepted
...ms file observed

PASS

The timing breakdown is the point of the exercise: the API allows five minutes for an input-time connection, and this shows which phase actually consumes it.

If it fails

The failing step number tells you where to look.

| Fails at | Likely cause | | --- | --- | | 1, 401/403 | Application key missing api.agents.* or api.responses.write. | | 1, 400 invalid_beta | The OpenAI-Beta: agents=v1 header is missing. openai@7.15.0 sends it; an older SDK does not. | | 2 | Template missing or not built — run npm run verify first. | | 3, stuck then timing out | Executor never registered. Read /tmp/codex-executor.log in the sandbox. Usually the executor key is wrong, or belongs to a different project than the application key. | | 3, environment.failed | Same log. Registration failures appear there, not in any HTTP status. | | 5, file never appears | The environment connected but commands are failing. Read the executor log; this is the case that would expose an exec-server filesystem-sandbox problem inside a microVM. |

The sandbox is killed on failure, so to inspect one, re-run with the teardown commented out or reproduce the step by hand against a sandbox you keep alive.

What each step does

The three commands above wrap the sequence below. Read it when adapting the flow to your own application rather than running the examples.

Build the executor template

The published template bakes in codex exec-server at a pinned version, so an executor boots with the CLI already present and every sandbox runs the same image. Rebuild only to change what is in it — a newer Codex, extra tooling:

npm run build:template # optional; about 1m15s cold, 30s warm

Two details matter if you do build your own:

  • Pin the Codex version. @openai/codex@alpha floats. Two templates built a

week apart would ship different executors and diverge with no visible cause.

  • Do not set a start command. A template start command runs once at build

time and is snapshotted, so it cannot see the environment id, which only exists once a session is created. Start the executor at runtime instead.

Verify the image before using it in a session. This needs only NOVITA_API_KEY:

npm run verify

Create a self-hosted session

import OpenAI from 'openai'

const client = new OpenAI()
const session = await client.beta.agents.sessions.create({
agent: { model: 'gpt-6-astra', instructions: 'You are a coding assistant.' },
environment: { type:...

Excerpt shown — open the source for the full document.