Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Sandbox — Dynamic Code Execution with Worker Loader

An AI agent that writes JavaScript code and runs it in a sandboxed dynamic Worker isolate. The isolate has no internet access — its only connection to the outside world is a database binding that proxies through a sub-agent.

How It Works

SandboxAgent (extends AIChatAgent)
  │
  ├── executeCode tool ──▶ env.LOADER.get(id, {
  │     mainModule: "harness.js",
  │     modules: { "harness.js": ..., "user-code.js": agentCode },
  │     env: { db: DatabaseLoopback },    ← only binding
  │     globalOutbound: null,              ← no fetch()
  │     tails: [TailLoopback]              ← capture console.log
  │   })
  │
  ├── DatabaseLoopback (WorkerEntrypoint)
  │     └── proxies env.db calls back to the parent
  │
  ├── TailLoopback (WorkerEntrypoint)
  │     └── captures console output, delivers to parent
  │
  └── CustomerDatabase (Agent — own isolated SQLite)
        └── query() / execute() / getAllCustomers()

Three layers of isolation:

  1. Dynamic isolate — code runs in a Worker with globalOutbound: null. No fetch(), no connect().
  2. Restricted env — the only binding is env.db, a DatabaseLoopback that proxies to the parent.
  3. Sub-agent storage — the database is a child DO whose SQLite the parent can't access directly.

Key Pattern

import { Agent } from "agents";
import { AIChatAgent } from "@cloudflare/ai-chat";

export class CustomerDatabase extends Agent<Env> {
  onStart() {
    this.sql`CREATE TABLE IF NOT EXISTS customers (...)`;
  }

  query(sql: string): Record<string, unknown>[] {
    /* ... */
  }
  execute(sql: string): { success: boolean } {
    /* ... */
  }
}

export class SandboxAgent extends AIChatAgent<Env, SandboxState> {
  private _db() {
    return this.subAgent(CustomerDatabase, "database");
  }

  // DatabaseLoopback calls these — they forward to the sub-agent
  async proxyDbQuery(sql: string) {
    const db = await this._db();
    return db.query(sql);
  }
}

The Loopback pattern: dynamic Worker isolates can't hold sub-agent stubs directly — they can only have ServiceStub bindings. So DatabaseLoopback (a WorkerEntrypoint) proxies calls back to the parent, which delegates to the sub-agent.

Chain: dynamic isolate → DatabaseLoopback → SandboxAgent → CustomerDatabase sub-agent

Quick Start

npm start

Try It

  1. "Count customers by tier" — agent writes code, runs in sandbox, shows output
  2. "Find customers with emails containing 'example'" — agent queries via env.db
  3. "Add a new customer named Zara" — agent calls env.db.execute() from sandbox
  4. Check the Executions tab to see code + captured output
  5. Check the Customers tab to see the database state

Related