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’s AI service with the Llama 3 8B Instruct model to provide intelligent pair programming assistance. The LLM generates responses based on conversation history and can output commands for sandbox execution.
Model Configuration
The worker uses Cloudflare AI binding configured in wrangler.toml:
Source: ~/workspace/source/cf-worker/wrangler.toml:12-13
The model is accessed via the @cf/meta/llama-3-8b-instruct identifier:
private async runAI(messages: AIMessage[]): Promise<string> {
const result = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", {
messages,
});
return result.response?.trim() || "";
}
Source: ~/workspace/source/cf-worker/index.ts:122-127
The AI expects messages in a specific format:
interface AIMessage {
role: "system" | "user" | "assistant";
content: string;
}
Source: ~/workspace/source/cf-worker/index.ts:84-87
System Prompt
Duet’s AI behavior is defined by a system prompt that instructs it to be concise and use special tags for command execution:
const aiMessages: AIMessage[] = [
{
role: "system",
content:
"You are Duet, a concise pair-programming assistant. " +
"You can run commands in a sandbox using <run>command</run> tags. " +
"When asked to perform an action, briefly explain what you will do and wrap the exact shell command(s) in <run> tags. " +
"Do NOT include predicted output in your response - just provide the explanation and command.",
},
// ...
];
Source: ~/workspace/source/cf-worker/index.ts:154-162
This prompt establishes:
- Identity: Duet is a pair-programming assistant
- Tone: Concise and action-oriented
- Command syntax: Use
<run>command</run> tags
- Output handling: Don’t predict output, let sandbox provide it
Conversation Context
The AI receives context from recent conversation history:
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
The agent:
- Includes the system prompt
- Adds the last 10 messages from conversation history
- Appends the current user message
- Converts message roles (“agent” → “assistant”, “user” → “user”)
Command Execution Flow
The AI can trigger sandbox commands using special tags:
1. AI Response with Commands
The AI wraps commands in <run> tags:
I'll check the directory contents.
<run>ls -la</run>
2. Command Extraction
The agent extracts commands using regex:
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;
}
// ...
}
}
Source: ~/workspace/source/cf-worker/index.ts:185-193
3. Sandbox Execution
Each extracted command is executed in the room’s sandbox:
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\nOutput (${cmd}):\n${summary}`;
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
result += `\n\nError (${cmd}):\n${msg}`;
}
Source: ~/workspace/source/cf-worker/index.ts:195-205
4. Output Appending
Command outputs are appended to the AI’s response:
- First 500 characters of stdout or stderr
- Error messages if execution fails
- “[no output]” if command produces nothing
5. Tag Removal
The <run> tags are stripped from the final response:
return result.replace(/<run>[\s\S]*?<\/run>/g, "").trim() || "";
Source: ~/workspace/source/cf-worker/index.ts:207
Complete Message Flow
private async handleMessage(roomId: string, rawBody: unknown): Promise<Response> {
// 1. Validate request
const parseResult = MessageRequestSchema.safeParse(rawBody);
// 2. Create user message
const userMsg: DuetMessage = {
role: "user",
userId: data.userId?.trim(),
text: data.text.trim(),
ts: Date.now(),
};
// 3. Build AI context
const aiMessages: AIMessage[] = [
{ role: "system", content: "..." },
...this.state.messages.slice(-10).map(...),
{ role: "user", content: userMsg.text },
];
// 4. Get AI response
const text = await this.runAI(aiMessages);
// 5. Execute any commands in response
const textWithOutputs = await this.executeCommands(text, roomId);
// 6. Create agent message
const agentMsg: DuetMessage = {
role: "agent",
text: textWithOutputs,
ts: Date.now(),
};
// 7. Update state
const nextMessages = [...this.state.messages, userMsg, agentMsg].slice(-50);
this.setState({ messages: nextMessages });
// 8. Return response
return Response.json({ reply: agentMsg.text, messages: nextMessages });
}
Source: ~/workspace/source/cf-worker/index.ts:129-183
API Request/Response
{
"text": "list files in the current directory",
"userId": "user-123"
}
Validated by:
const MessageRequestSchema = z.object({
text: z.string().min(1, "Text cannot be empty"),
userId: z.string().optional(),
});
Source: ~/workspace/source/cf-worker/index.ts:9-12
{
"reply": "I'll check the directory contents.\n\nOutput (ls -la):\ntotal 48\ndrwxr-xr-x 12 user staff 384 Mar 1 10:00 .",
"messages": [
{
"role": "user",
"userId": "user-123",
"text": "list files in the current directory",
"ts": 1709294400000
},
{
"role": "agent",
"text": "I'll check the directory contents.\n\nOutput (ls -la):\n...",
"ts": 1709294401000
}
]
}
Source: ~/workspace/source/internal/ai/client.go:42-47
Next Steps