Documentation Index Fetch the complete documentation index at: https://mintlify.com/jaypopat/cf_ai_duet/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Duet provides two execution environments: the shared terminal workspace and isolated Cloudflare Sandboxes. Sandboxes allow you to run commands in a secure, ephemeral container without affecting your main workspace.
Why sandboxes?
Sandboxes are useful for:
Testing dangerous commands Try commands like rm -rf without risking your workspace
AI command execution Let the AI run commands in isolation and show you the output
Quick experiments Test shell scripts or one-liners without cluttering your workspace
Parallel execution Run commands concurrently while continuing work in the main terminal
Running commands in a sandbox
Press Ctrl+R to execute a command in the sandbox:
Open sandbox input
Press Ctrl+R in the terminal. You’ll see:
View the result
A toast notification appears with the output: $ ls -la && whoami → total 8 -rw-r--r-- 1 nobody nogroup...
Press Esc to cancel without executing.
Sandbox execution requires a Cloudflare Worker URL, just like the AI assistant: duet --worker https://duet-cf-worker.your-subdomain.workers.dev
Architecture
Sandboxes are powered by Cloudflare’s Browser Rendering service:
import { getSandbox } from "@cloudflare/sandbox" ;
private async handleSandboxExec (
roomId : string ,
rawBody : unknown
): Promise < Response > {
const sandboxName = `sandbox- ${ roomId } ` ;
try {
const sandbox = getSandbox ( this . env . Sandbox , sandboxName );
const result = await sandbox . exec ( data . cmd );
return Response . json ({ result , sandboxName });
} catch (error) {
return Response. json (
{ error : `sandbox execution failed: ${ error . message } ` },
{ status : 500 }
);
}
}
Per-room sandboxes
Each room gets its own persistent sandbox instance:
const sandboxName = `sandbox- ${ roomId } ` ;
const sandbox = getSandbox ( this . env . Sandbox , sandboxName );
This means:
Files created in the sandbox persist across commands (within the same session)
Each room’s sandbox is completely isolated from others
When the room ends, the sandbox is destroyed
Sandboxes are completely isolated:
No access to your shared terminal workspace
No network access to external services
Separate filesystem for each room
Limited CPU and memory resources
Command execution flow
Client sends request
The Go client makes an HTTP POST to the Worker: func ( c * Client ) ExecCommand ( ctx context . Context , roomID , cmd string ) ( * ExecResponse , error ) {
url := fmt . Sprintf ( " %s /api/rooms/ %s /sandbox/exec" , c . baseURL , roomID )
body := ExecRequest {
Cmd : cmd ,
}
jsonBody , err := json . Marshal ( body )
req , err := http . NewRequestWithContext ( ctx , http . MethodPost , url , bytes . NewReader ( jsonBody ))
req . Header . Set ( "Content-Type" , "application/json" )
resp , err := c . http . Do ( req )
// ... handle response
}
Worker validates input
The Worker uses Zod for schema validation: const SandboxExecRequestSchema = z . object ({
cmd: z . string (). min ( 1 , "Command cannot be empty" ),
});
const parseResult = SandboxExecRequestSchema . safeParse ( rawBody );
if ( ! parseResult . success ) {
return Response . json (
{
error: "invalid request" ,
details: z . flattenError ( parseResult . error ). fieldErrors ,
},
{ status: 400 }
);
}
Sandbox executes command
Cloudflare runs the command in an isolated container and captures stdout/stderr.
Result returned to client
{
"result" : {
"stdout" : "total 8 \n drwxr-xr-x 2 nobody nogroup 4096..." ,
"stderr" : ""
},
"sandboxName" : "sandbox-a3f8e9d2-4c1b-4f3a-9e2b-8d7c6b5a4e3f"
}
Sandbox execution returns both stdout and stderr:
type ExecResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
}
type ExecResponse struct {
Result ExecResult `json:"result"`
SandboxName string `json:"sandboxName"`
Error string `json:"error,omitempty"`
}
The client displays whichever is available:
output := resp . Result . Stdout
if output == "" {
output = resp . Result . Stderr
}
return SandboxResultMsg { Output : output , Cmd : cmd }
AI integration
The AI assistant automatically uses sandboxes when it includes <run> tags:
private async executeCommands ( text : string , roomId : string ): Promise < string > {
const matches = Array . from ( text . matchAll ( /<run> ( [ \s\S ] *? ) < \/ run>/ g ));
let result = text ;
for ( const match of matches ) {
const cmd = match [ 1 ]?. trim ();
if ( ! cmd ) continue ;
try {
const sandbox = getSandbox ( this . env . Sandbox , `sandbox- ${ roomId } ` );
const { stderr , stdout } = await sandbox . exec ( cmd );
const summary = stdout . slice ( 0 , 500 ) || stderr . slice ( 0 , 500 ) || "[no output]" ;
result += ` \n\n Output ( ${ cmd } ): \n ${ summary } ` ;
} catch ( e ) {
result += ` \n\n Error ( ${ cmd } ): \n ${ e . message } ` ;
}
}
return result.replace(/<run> [\s\ S ]*?<\/run>/g, "").trim();
}
Example
When you ask the AI:
You: Create a file called hello.txt with "Hello world"
The AI responds:
AI: I'll create the file for you:
<run>echo "Hello world" > hello.txt</run>
Output (echo "Hello world" > hello.txt):
[no output]
The command runs in the sandbox, and you can verify it worked:
You: Show me the contents of hello.txt
AI: <run>cat hello.txt</run>
Output (cat hello.txt):
Hello world
Files created in the sandbox are NOT accessible from your shared terminal. Sandboxes and the terminal workspace are completely separate environments.
API endpoint
POST /api/rooms/:roomId/sandbox/exec
Execute a command in the room’s sandbox Request body: {
"cmd" : "ls -la && whoami"
}
Success response: {
"result" : {
"stdout" : "total 8 \n drwxr-xr-x 2 nobody nogroup 4096... \n nobody" ,
"stderr" : ""
},
"sandboxName" : "sandbox-a3f8e9d2-4c1b-4f3a-9e2b-8d7c6b5a4e3f"
}
Error response: {
"error" : "sandbox execution failed: command not found"
}
Cleanup
When a room ends, the sandbox is automatically destroyed:
private async handleCleanup ( roomId : string ): Promise < Response > {
const errors: string [] = [];
// Reset agent state
this . setState ({ messages: [] });
// Terminate sandbox
try {
const sandbox = getSandbox ( this . env . Sandbox , `sandbox- ${ roomId } ` );
await sandbox.destroy();
} catch ( e ) {
errors.push( `sandbox: ${ e . message } ` );
}
if (errors.length > 0) {
return Response.json({ cleaned: true , errors }, { status : 207 });
}
return Response . json ({ cleaned: true , roomId });
}
This happens when:
The last participant leaves the room
The Go server calls DELETE /api/rooms/:roomId
The Worker destroys the sandbox and clears AI state
Cleanup is best-effort. If the Worker is unreachable, Cloudflare will eventually garbage-collect idle sandboxes.
Limitations
No long-running processes
Sandboxes are designed for short commands. Long-running processes may be terminated: # This will likely fail
sleep 3600
Sandboxes have a restricted filesystem with minimal tools. Advanced utilities may not be available.
Sandboxes cannot make outbound network requests: # This will fail
curl https://example.com
CPU and memory are constrained. Intensive operations may be throttled or killed.
Error handling
Command not found
Execution timeout
Validation error
{
"error" : "sandbox execution failed: command not found: invalid_command"
}
The command doesn’t exist in the sandbox environment. {
"error" : "sandbox execution failed: timeout"
}
The command took too long to execute. {
"error" : "invalid request" ,
"details" : {
"cmd" : [ "Command cannot be empty" ]
}
}
Empty commands are rejected.
Comparison: Sandbox vs Terminal
Feature Shared Terminal Sandbox Persistence Permanent (until room ends) Ephemeral (per-room) Visibility All participants see output Only command initiator Filesystem Shared workspace Isolated per room Tools Full shell with installed packages Minimal environment Network Full access No outbound connections Use case Primary development work Testing, AI experiments
Best practices
Use for experiments Test unfamiliar commands in the sandbox before running in the terminal
Check output length Sandbox output is truncated to 500 characters. For long output, use the terminal.
Don't rely on state Sandboxes are destroyed when the room ends. Use the terminal for persistent work.
Verify AI commands Always review AI-generated commands before manually running them in your terminal.
Next steps
AI assistant Learn how the AI uses sandboxes for command execution
Deploy a Worker Set up Cloudflare Worker and Sandbox bindings