---
title: Build a Coding Agent Using Codex SDK and Daytona
description: Step-by-step guide to building an autonomous coding agent using OpenAI Codex and Daytona sandboxes.
guide:
tags: ["Codex"]
sidebar:
label: "Codex"
---
This guide demonstrates how to run an autonomous coding agent based on [OpenAI Codex](https://chatgpt.com/features/codex) inside a Daytona sandbox environment. The agent can develop full-stack web apps, write code in any language, install dependencies, and run scripts. It can also start and manage dev servers, and generate preview links for live apps.
---
### 1. Workflow Overview
When you launch the main module, a Daytona sandbox is created and a Node.js agent is initialized inside it. The agent is based on the [Codex SDK](https://developers.openai.com/codex/sdk/).
You interact with the main program via a command line chat interface. The program sends your prompts to the agent inside the sandbox, which executes them and returns the results:
```
$ npm run start
Creating sandbox...
Installing Codex agent in sandbox...
Press Ctrl+C at any time to exit.
User: create a 3d animated web-based, lunar lander game
Thinking...
🔨 ✓ Run: /bin/sh -lc ls
🔨 ✓ Run: /bin/sh -lc 'ls -a'
🔨 ✓ Run: /bin/sh -lc 'ls .daytona'
🔨 ✓ Run: /bin/sh -lc 'find /home/daytona -maxdepth 4 -name .git'
📝 Add /home/daytona/index.html
📝 Add /home/daytona/style.css
📝 Add /home/daytona/main.js
📝 Update /home/daytona/main.js
- Built a self-contained 3D lunar lander experience with HUD in index.html wired to main.js.
- Styled a glassy mission card, typography, and neon accents in style.css.
- Implemented the Three.js scene in main.js: starfield + noisy terrain with a flattened pad, modeled lander, thrust/fuel/rotation controls, gravity/drag physics, landing/crash checks, exhaust particles, and a chase camera. Controls: Space/↑ thrust, ←/→ yaw, W/S pitch, R restart.
Next steps:
1) Serve locally (e.g., cd /home/daytona && python3 -m http.server 8080) and open https://8080-e7c5deb5-7723-4bb8-93c6-25258d9b7c53.proxy.daytona.works.
2) Tune physics constants or terrain size if you want a harder/easier landing.
🗒️ To-do list:
- [x] Inspect workspace and set up project structure for web-based lunar lander game
- [x] Implement 3D scene, lunar lander controls, physics, and game loop
- [x] Add UI elements, polish, and quick sanity check (open file if feasible)
Usage Summary: Cached: 71936, Input: 103238, Output: 11311
User: start the server
Thinking...
🔨 ✓ Run: /bin/sh -lc 'cd /home/daytona && nohup python3 -m http.server 8080 --bind 0.0.0.0 >/home/daytona/server.log 2>&1 & echo $!'
Server started on port 8080 (pid 274). Open the game at:
https://8080-e7c5deb5-7723-4bb8-93c6-25258d9b7c53.proxy.daytona.works
If you need to stop it later: kill 274.
Usage Summary: Cached: 4096, Input: 22231, Output: 272
User:
Cleaning up...
```
The agent can also host web apps and provide you with a preview link using the [Daytona Preview Links](https://www.daytona.io/docs/en/preview/) feature. When your task involves running or previewing a web application, the agent automatically reasons about this need, hosts the app, and generates a preview link for you to inspect the live result:
You can continue interacting with your agent until you are finished. When you exit the program, the sandbox will be deleted automatically.
### 2. Project Setup
#### Clone the Repository
First, clone the daytona [repository](https://github.com/daytona/guides.git) and navigate to the example directory:
```bash
git clone https://github.com/daytona/guides.git
cd guides/typescript/openai/codex-sdk
```
#### Configure Environment
Get your API keys:
- **Daytona API key:** [Daytona Dashboard](https://app.daytona.io/dashboard/keys)
- **OpenAI API key:** [OpenAI Developer Platform](https://platform.openai.com/api-keys)
Copy `.env.example` to `.env` and add your keys:
```bash
DAYTONA_API_KEY=your_daytona_key
SANDBOX_OPENAI_API_KEY=your_openai_key
```
Note: The `SANDBOX_OPENAI_API_KEY` key is passed into the Daytona sandbox environment and is accessible to any code executed inside the sandbox.
#### Alternative: Inject the Key as a Daytona Secret
The default setup passes the OpenAI key into the sandbox as a plain environment variable, so anything running inside the sandbox - including the agent itself - can read the raw key with `env`. [Daytona Secrets](/docs/en/secrets) keep the raw value out of the sandbox entirely: the environment variable holds only an opaque placeholder (`dtn_secret_`), and Daytona's outbound proxy substitutes the real value into HTTPS request headers at egress - and only for requests to the hosts the Secret allows. An agent that dumps the environment or exfiltrates it never sees a usable key.
The Secret-based flow needs `@daytona/sdk` 0.192.0 or newer and a one-time Secret setup:
1. Create the Secret once for your organization - in the [Daytona Dashboard](https://app.daytona.io/dashboard/secrets) or with a one-off script (save as `create-secret.ts` next to this guide's `.env` and run `npx tsx create-secret.ts`):
```typescript
import { Daytona } from '@daytona/sdk'
import * as dotenv from 'dotenv'
dotenv.config()
async function main() {
const value = process.env.SANDBOX_OPENAI_API_KEY
if (!value) throw new Error('SANDBOX_OPENAI_API_KEY is not set')
const daytona = new Daytona()
await daytona.secret.create({
name: 'openai-api-key',
value,
hosts: ['api.openai.com'], // the only host the real key may be sent to
})
}
main()
```
2. In `src/index.ts`, swap the `OPENAI_API_KEY` env var for a `secrets:` mapping (environment variable name to Secret name):
```diff
sandbox = await daytona.create({
- envVars: {
- OPENAI_API_KEY: process.env.SANDBOX_OPENAI_API_KEY || '',
- },
+ secrets: {
+ OPENAI_API_KEY: 'openai-api-key',
+ },
})
```
Inside the sandbox, `env` now shows `OPENAI_API_KEY=dtn_secret_...`, yet Codex still authenticates: it sends the key as an HTTPS `Authorization` header to `api.openai.com`, where the proxy swaps in the real value. Substitution happens only in HTTPS request headers toward allowed hosts - requests to any other host carry the harmless placeholder. See the [Secrets documentation](/docs/en/secrets#substitution-scope) for the full substitution scope.
#### Local Usage
Node.js 18 or newer is required to run this example. Please ensure your environment meets this requirement before proceeding.
Install dependencies:
```bash
npm install
```
Run the agent:
```bash
npm run start
```
The agent will start and wait for your prompt.
### 3. Understanding the Agent's Architecture
This example consists of two main components:
- **Main Program:** The main program is a Node.js script (`src/index.ts`) that runs on your local machine. It uses the Daytona SDK to create and manage a Daytona sandbox. The main program provides a command line interface for interacting with the agent inside the sandbox.
- **Sandbox Agent:** The sandbox agent is a Node.js script (`agent/index.ts`) that runs inside the Daytona sandbox. It uses the Codex SDK to create a customized coding agent.
#### Initialization
On initialization, the main program:
1. Creates a new [Daytona sandbox](/docs/en/sandboxes) with your OpenAI API key included in the environment variables.
2. Configures the Codex system prompt with Daytona-specific instructions and writes it to a `.codex/config.toml` file in the sandbox.
3. Uploads the agent package to the sandbox with [file uploading](/docs/en/file-system-operations#upload-a-single-file).
4. Installs the agent dependencies by running `npm install` in the uploaded agent directory.
5. Waits for user input and runs the agent asynchronously for each prompt.
#### Main Program Code
Custom system prompts for Codex must be configured via a `.codex/config.toml` file, so the main program creates this file in the sandbox before starting the agent:
```typescript
const systemPrompt = [
'You are running in a Daytona sandbox.',
'Use the /home/daytona directory instead of /workspace for file operations.',
`When running services on localhost, they will be accessible as: ${previewUrlPattern}`,
].join(' ')
const config = `developer_instructions = "${systemPrompt}"`
await sandbox.fs.createFolder('.codex', '755')
await sandbox.fs.uploadFile(Buffer.from(config, 'utf8'), '.codex/config.toml')
```
This prompt instructs the agent to use the correct file paths and preview link format for Daytona sandboxes.
After installing dependencies, the main program enters a loop to read user input and send it to the agent. For each user prompt it receives, it creates a new Daytona process session to run the agent command asynchronously and stream back the output:
```typescript
// Create a session to stream the agent output
const sessionId = `codex-session-${Date.now()}`
await sandbox.process.createSession(sessionId)
// Run the agent asynchronously, passing the prompt and OpenAI API key
const command = await sandbox.process.executeSessionCommand(sessionId, {
command: `${environmentPrefix({ PROMPT: prompt })} npm exec --prefix /tmp/agent tsx -- /tmp/agent/index.ts`,
runAsync: true,
})
// Stream agent output as it arrives
if (!command.cmdId) throw new Error('Failed to start agent command in sandbox')
await sandbox.process.getSessionCommandLogs(
sessionId,
command.cmdId,
onStdout,
onStderr,
)
// Delete the session
await sandbox.process.deleteSession(sessionId)
```
The `onStdout` and `onStderr` callbacks are used to pass the agent's output back to the main program. After the agent finishes responding to the prompt, the main program waits for the next user input.
#### Sandbox Agent Code
The sandbox agent uses the [Codex SDK](https://developers.openai.com/codex/sdk/) to create a customized coding agent.
The agent is initialized with custom options that include the workspace directory:
```typescript
// Configure Codex options
const options: ThreadOptions = {
workingDirectory: '/home/daytona',
skipGitRepoCheck: true,
sandboxMode: 'danger-full-access',
}
```
The agent maintains thread state between requests by writing the thread ID to a file, allowing it to maintain context across multiple interactions:
```typescript
const threadIdPath = '/tmp/codex-thread-id'
const threadId = (await readFileIfExisting(threadIdPath))?.trim()
const thread: Thread = threadId
? codex.resumeThread(threadId, options)
: codex.startThread(options)
```
Additional code to stream agent responses follows the examples in OpenAI's [Codex SDK documentation](https://github.com/openai/codex/blob/main/sdk/typescript/README.md).
#### Clean up
When you exit the main program, the Daytona sandbox and all files are automatically deleted.
**Key advantages:**
- Secure, isolated execution in Daytona sandboxes
- Communicate with the agent directly in your terminal
- Automatic dev server detection and live preview links
- Multi-language and full-stack support
- Thread persistence across multiple requests
- Simple setup and automatic cleanup