Skip to main content

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 uses Cloudflare Durable Objects to maintain isolated, persistent state for each collaboration room. Each room gets its own DuetAgent instance that stores conversation history and coordinates with the room’s dedicated sandbox.

DuetAgent State

Each DuetAgent maintains a simple state structure:
interface DuetAgentState {
  messages: DuetMessage[];
}

interface DuetMessage {
  role: "user" | "agent";
  userId?: string;
  text: string;
  ts: number;
}

export class DuetAgent extends Agent<Env, DuetAgentState> {
  override initialState: DuetAgentState = { messages: [] };
}
Source: ~/workspace/source/cf-worker/index.ts:18-90

State Isolation

Each room is identified by a unique roomId. The worker extracts this from the URL and routes to the appropriate DuetAgent instance:
const agent = await getAgentByName(env.DUET_AGENT, roomId);
Source: ~/workspace/source/cf-worker/index.ts:67 This ensures:
  • Each room has isolated conversation history
  • Multiple rooms can operate simultaneously without interference
  • State persists across requests for the same room

Message History Management

The DuetAgent maintains conversation history with automatic limits:

Recent Context for AI

When generating responses, the agent uses the last 10 messages as context:
const aiMessages: AIMessage[] = [
  {
    role: "system",
    content: "You are Duet, a concise pair-programming assistant..."
  },
  ...this.state.messages.slice(-10).map<AIMessage>((m) => ({
    role: m.role === "agent" ? "assistant" : "user",
    content: m.text,
  })),
  { role: "user", content: userMsg.text },
];
Source: ~/workspace/source/cf-worker/index.ts:154-168

Total History Limit

The agent stores up to 50 messages in total to prevent unbounded growth:
const nextMessages = [...this.state.messages, userMsg, agentMsg].slice(-50);
this.setState({ messages: nextMessages });
Source: ~/workspace/source/cf-worker/index.ts:179-180

State Updates

Every message interaction updates the state:
  1. User message is added to history
  2. AI generates response
  3. Agent message is added to history
  4. State is updated with both messages
const userMsg: DuetMessage = {
  role: "user",
  userId: data.userId?.trim(),
  text: data.text.trim(),
  ts: Date.now(),
};

const agentMsg: DuetMessage = {
  role: "agent",
  text: textWithOutputs,
  ts: Date.now(),
};

const nextMessages = [...this.state.messages, userMsg, agentMsg].slice(-50);
this.setState({ messages: nextMessages });

return Response.json({ reply: agentMsg.text, messages: nextMessages });
Source: ~/workspace/source/cf-worker/index.ts:147-182

Room Cleanup

When a room is deleted, the DuetAgent resets its state and destroys the associated sandbox:
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 instanceof Error ? e.message : String(e)}`);
  }

  if (errors.length > 0) {
    return Response.json({ cleaned: true, errors }, { status: 207 });
  }

  return Response.json({ cleaned: true, roomId });
}
Source: ~/workspace/source/cf-worker/index.ts:244-266 The cleanup is triggered via DELETE request from the client:
func (c *Client) CleanupRoom(ctx context.Context, roomID string) error {
  url := fmt.Sprintf("%s/api/rooms/%s", c.baseURL, roomID)
  req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
  // ...
}
Source: ~/workspace/source/internal/ai/client.go:106-125

Durable Objects Configuration

The worker configuration defines two Durable Object bindings:
[durable_objects]
bindings = [
  { name = "DUET_AGENT", class_name = "DuetAgent" },
  { name = "Sandbox", class_name = "Sandbox" },
]

[[migrations]]
tag = "v1"
new_sqlite_classes = ["DuetAgent"]

[[migrations]]
tag = "v2"
new_sqlite_classes = ["Sandbox"]
Source: ~/workspace/source/cf-worker/wrangler.toml:6-21 Migrations define the SQLite-backed storage for each Durable Object class.

Next Steps

Build docs developers (and LLMs) love