An AI agent that manages a customer database, where reads are free but writes require human approval. The database lives in a sub-agent with its own isolated SQLite, so the agent structurally cannot bypass the approval queue.
GatekeeperAgent (extends AIChatAgent)
│
│ LLM ──▶ Tools ──▶ Approval Queue (action_queue table, parent SQLite)
│ │
│ ┌──────────────────────▼──────────────────────────────────────┐
│ │ CustomerDatabase (Agent — own isolated SQLite) │
│ │ query() / execute() / getAllCustomers() │
│ │ ┌──────────────────────────────────────────────────────┐ │
│ │ │ customers table (parent CANNOT access directly) │ │
│ │ └──────────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────────────┘
The parent has no path to customer data except through the sub-agent's typed RPC methods. This makes the approval queue structurally enforceable — not just a convention.
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 } {
/* ... */
}
getAllCustomers(): CustomerRecord[] {
/* ... */
}
}
export class GatekeeperAgent extends AIChatAgent<Env, GatekeeperState> {
private _getDb() {
return this.subAgent(CustomerDatabase, "database");
}
// The ONLY path to mutate customer data
async approveAction(id: number) {
const db = await this._getDb();
db.execute(action.sql);
}
}The LLM's mutateDatabase tool queues actions for approval. The queryDatabase tool reads freely via db.query(). Only approveAction() calls db.execute().
npm start- "Show me all customers" — reads via
db.query(), logged as observation - "Upgrade all East customers to Gold" — queued for approval
- Click Approve — executes via
db.execute(), table updates - Click Revert — undone via
db.execute(revertSql) - Click Reject — nothing happens
- gadgets-subagents — fan-out/fan-in with parallel sub-agents
- gadgets-chat — multi-room chat via sub-agents
- gadgets-sandbox — isolated database sub-agent with dynamic Worker isolates
- design/rfc-sub-agents.md — RFC for the sub-agent API