> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.scrapybara.com/conversations/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.scrapybara.com/_mcp/server. # Conversations ## Understanding multi-turn conversations Multi-turn conversations in Scrapybara enable your agents to maintain context and state across multiple interactions. The Act SDK provides a structured way to manage these conversations through its message architecture. ## Message architecture The Act SDK uses a structured message system with three primary message types and five different part types. Understanding these components is crucial for building sophisticated multi-turn agents. ### Message types ```python # Message types class UserMessage: role: str = "user" # Always "user" content: List[Union[TextPart, ImagePart]] # What the user sends class AssistantMessage: role: str = "assistant" # Always "assistant" content: List[Union[TextPart, ToolCallPart, ReasoningPart]] # The agent's response response_id: Optional[str] = None # Unique identifier for the response class ToolMessage: role: str = "tool" # Always "tool" content: List[ToolResultPart] # Results from tool operations Message = Union[UserMessage, AssistantMessage, ToolMessage] ``` ### Message part types Each message type contains various "parts" that serve different purposes: ```python # Message part types class TextPart: type: str = "text" # Always "text" text: str # Plain text content class ImagePart: type: str = "image" # Always "image" image: str # Base64 encoded image or URL mime_type: Optional[str] = None # e.g., "image/png", "image/jpeg" class ToolCallPart: type: str = "tool-call" # Always "tool-call" id: Optional[str] = None # Unique identifier for the tool call tool_call_id: str # ID matching the tool result tool_name: str # Name of the tool being called args: dict[str, Any] # Arguments passed to the tool class ToolResultPart: type: str = "tool-result" # Always "tool-result" tool_call_id: str # ID matching the original tool call tool_name: str # Name of the tool that was called result: Any # Result returned by the tool is_error: Optional[bool] = False # Whether the tool execution resulted in an error class ReasoningPart: type: str = "reasoning" # Always "reasoning" id: Optional[str] = None # Unique identifier for the reasoning part reasoning: str # The agent's internal reasoning signature: Optional[str] = None # Cryptographic signature for verification instructions: Optional[str] = None # Additional context about the reasoning ``` ## Building multi-turn conversations Instead of providing a single `prompt`, you can pass a complete message history using the `messages` parameter. This allows you to maintain the full conversation context. The Act SDK returns a `messages` field in the response that contains the complete conversation history. You can reuse this directly in your next `act` call. #### Python ```python from scrapybara import Scrapybara from scrapybara.anthropic import Anthropic from scrapybara.prompts import UBUNTU_SYSTEM_PROMPT from scrapybara.tools import BashTool, ComputerTool, EditTool client = Scrapybara() instance = client.start_ubuntu() # Initial conversation response = client.act( model=Anthropic(), tools=[ BashTool(instance), ComputerTool(instance), EditTool(instance), ], on_step=lambda step: print(step.text), system=UBUNTU_SYSTEM_PROMPT, prompt="Create a file called hello.py that prints 'Hello, World!'", ) print('--------------------------------') # Continue the conversation with the previous messages follow_up_response = client.act( model=Anthropic(), tools=[ BashTool(instance), ComputerTool(instance), EditTool(instance), ], on_step=lambda step: print(step.text), system=UBUNTU_SYSTEM_PROMPT, messages=response.messages + [ { "role": "user", "content": [ { "type": "text", "text": "Now modify the file to accept a name as a command line argument and print 'Hello, {name}!'" } ] } ] ) instance.stop() ``` #### TypeScript ```typescript import { ScrapybaraClient } from "scrapybara"; import { anthropic } from "scrapybara/anthropic"; import { UBUNTU_SYSTEM_PROMPT } from "scrapybara/prompts"; import { bashTool, computerTool, editTool } from "scrapybara/tools"; const client = new ScrapybaraClient(); const instance = await client.startUbuntu(); // Initial conversation const response = await client.act({ model: anthropic(), tools: [ bashTool(instance), computerTool(instance), editTool(instance), ], onStep: (step) => console.log(step.text), system: UBUNTU_SYSTEM_PROMPT, prompt: "Create a file called hello.py that prints 'Hello, World!'", }); console.log('--------------------------------') // Continue the conversation with the previous messages const followUpResponse = await client.act({ model: anthropic(), tools: [ bashTool(instance), computerTool(instance), editTool(instance), ], onStep: (step) => console.log(step.text), system: UBUNTU_SYSTEM_PROMPT, messages: [ ...response.messages, { role: "user", content: [ { type: "text", text: "Now modify the file to accept a name as a command line argument and print 'Hello, {name}!'" } ] } ] }); await instance.stop(); ``` ## Including screenshots in messages Screenshots are a powerful way to provide visual context to your agent. You can include them in user messages using the `ImagePart` type. #### Python ```python from scrapybara import Scrapybara from scrapybara.anthropic import Anthropic from scrapybara.prompts import UBUNTU_SYSTEM_PROMPT client = Scrapybara() instance = client.start_ubuntu() # Take a screenshot screenshot = instance.screenshot().base_64_image # Send the screenshot to the agent messages = [ { "role": "user", "content": [ { "type": "text", "text": "What do you see in this screenshot? Describe the desktop environment." }, { "type": "image", "image": 'data:image/png;base64,' + screenshot, "mime_type": "image/png" } ] } ] response = client.act( model=Anthropic(), system=UBUNTU_SYSTEM_PROMPT, messages=messages, ) print(response.text) instance.stop() ``` #### TypeScript ```typescript import { ScrapybaraClient } from "scrapybara"; import { anthropic } from "scrapybara/anthropic"; import { UBUNTU_SYSTEM_PROMPT } from "scrapybara/prompts"; const client = new ScrapybaraClient(); const instance = await client.startUbuntu(); // Take a screenshot const screenshotResult = await instance.screenshot(); const screenshot = screenshotResult.base64Image; // Send the screenshot to the agent const messages = [ { role: "user", content: [ { type: "text", text: "What do you see in this screenshot? Describe the desktop environment." }, { type: "image", image: 'data:image/png;base64,' + screenshot, mime_type: "image/png" } ] } ]; const response = await client.act({ model: anthropic(), system: UBUNTU_SYSTEM_PROMPT, messages: messages, }); console.log(response.text); await instance.stop(); ``` ## Working with tools and reasoning The Act SDK captures both tool calls and agent reasoning in its message architecture. Here's how you can access and work with this information: ### Examining tool calls and results #### Python ```python from scrapybara import Scrapybara from scrapybara.anthropic import Anthropic from scrapybara.prompts import UBUNTU_SYSTEM_PROMPT from scrapybara.tools import BashTool client = Scrapybara() instance = client.start_ubuntu() response = client.act( model=Anthropic(), tools=[BashTool(instance)], system=UBUNTU_SYSTEM_PROMPT, prompt="Show me the current directory structure", ) # Analyze the conversation steps for message in response.messages: if message.role == "assistant": for part in message.content: if part.type == "tool-call": print(f"Tool called: {part.tool_name}") print(f"Arguments: {part.args}") elif message.role == "tool": for part in message.content: print(f"Tool result from {part.tool_name}: {part.result}") instance.stop() ``` #### TypeScript ```typescript import { ScrapybaraClient } from "scrapybara"; import { anthropic } from "scrapybara/anthropic"; import { UBUNTU_SYSTEM_PROMPT } from "scrapybara/prompts"; import { bashTool } from "scrapybara/tools"; const client = new ScrapybaraClient(); const instance = await client.startUbuntu(); const response = await client.act({ model: anthropic(), tools: [bashTool(instance)], system: UBUNTU_SYSTEM_PROMPT, prompt: "Show me the current directory structure", }); // Analyze the conversation steps for (const message of response.messages) { if (message.role === "assistant") { for (const part of message.content) { if (part.type === "tool-call") { console.log(`Tool called: ${part.tool_name}`); console.log(`Arguments: ${JSON.stringify(part.args)}`); } } } else if (message.role === "tool") { for (const part of message.content) { console.log(`Tool result from ${part.tool_name}: ${JSON.stringify(part.result)}`); } } } await instance.stop(); ``` ### Accessing agent reasoning #### Python ```python from scrapybara import Scrapybara from scrapybara.anthropic import Anthropic from scrapybara.prompts import UBUNTU_SYSTEM_PROMPT from scrapybara.tools import BashTool, ComputerTool client = Scrapybara() instance = client.start_ubuntu() response = client.act( model=Anthropic(name="claude-3-7-sonnet-20250219-thinking"), tools=[ BashTool(instance), ComputerTool(instance), ], system=UBUNTU_SYSTEM_PROMPT, prompt="Open Firefox and navigate to scrapybara.com", ) # Extract reasoning parts from assistant messages for message in response.messages: if message.role == "assistant": for part in message.content: if part.type == "reasoning": print("Agent reasoning:") print(part.reasoning) # Or access reasoning directly from steps for step in response.steps: if step.reasoning_parts: print(f"Step reasoning: {step.reasoning_parts}") instance.stop() ``` #### TypeScript ```typescript import { ScrapybaraClient } from "scrapybara"; import { anthropic } from "scrapybara/anthropic"; import { UBUNTU_SYSTEM_PROMPT } from "scrapybara/prompts"; import { bashTool, computerTool } from "scrapybara/tools"; const client = new ScrapybaraClient(); const instance = await client.startUbuntu(); const response = await client.act({ model: anthropic({ name: "claude-3-7-sonnet-20250219-thinking" }), tools: [ bashTool(instance), computerTool(instance), ], system: UBUNTU_SYSTEM_PROMPT, prompt: "Open Firefox and navigate to scrapybara.com", }); // Extract reasoning parts from assistant messages for (const message of response.messages) { if (message.role === "assistant") { for (const part of message.content) { if (part.type === "reasoning") { console.log("Agent reasoning:"); console.log(part.reasoning); } } } } // Or access reasoning directly from steps for (const step of response.steps) { if (step.reasoning_parts) { console.log(`Step reasoning: ${step.reasoning_parts}`); } } await instance.stop(); ``` ## Best practices for multi-turn conversations 1. **Maintain message history**: Always use the returned `messages` from each call to maintain conversation context. 2. **Clear instructions**: Provide clear, specific instructions in each new user message. 3. **Handle context length**: For very long conversations, consider summarizing or truncating older messages to avoid exceeding model context limits. 4. **Include visual context**: Use screenshots when appropriate to provide additional context to the agent. 5. **Monitor token usage**: Track token usage through the `usage` field to prevent exceeding quotas or limits. 6. **Process message parts**: Parse and handle different message parts appropriately based on their type. ## Simple multi-turn example Here's an interactive Read-Eval-Print Loop (REPL) implementation that allows you to have ongoing conversations with your agent: #### Python ```python from scrapybara import Scrapybara from scrapybara.anthropic import Anthropic from scrapybara.prompts import UBUNTU_SYSTEM_PROMPT from scrapybara.tools import BashTool, ComputerTool, EditTool def agent_repl(): client = Scrapybara() instance = client.start_ubuntu() tools = [BashTool(instance), ComputerTool(instance), EditTool(instance)] messages = [] print("Scrapybara REPL started. Type 'exit' to quit") try: while True: # Get user input user_input = input("\n> ") # Exit command if user_input.lower() == 'exit': break # Regular text command messages.append({ "role": "user", "content": [{"type": "text", "text": user_input}] }) # Process with agent print("Processing...") response = client.act( model=Anthropic(), tools=tools, system=UBUNTU_SYSTEM_PROMPT, on_step=lambda step: print(step.text), messages=messages ) # Update conversation history messages = response.messages finally: instance.stop() print("Session ended.") if __name__ == "__main__": agent_repl() ``` #### TypeScript ```typescript import * as readline from 'readline'; import { ScrapybaraClient } from "scrapybara"; import { anthropic } from "scrapybara/anthropic"; import { UBUNTU_SYSTEM_PROMPT } from "scrapybara/prompts"; import { bashTool, computerTool, editTool } from "scrapybara/tools"; async function agent_repl() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const client = new ScrapybaraClient(); const instance = await client.startUbuntu(); const tools = [bashTool(instance), computerTool(instance), editTool(instance)]; let messages: any[] = []; console.log("Scrapybara REPL started. Type 'exit' to quit"); const getInput = (): Promise => new Promise(resolve => rl.question("\n> ", resolve)); try { while (true) { // Get user input const userInput = await getInput(); // Exit command if (userInput.toLowerCase() === 'exit') { break; } // Regular text command messages.push({ role: "user", content: [{type: "text", text: userInput}] }); // Process with agent console.log("Processing..."); const response = await client.act({ model: anthropic(), tools: tools, system: UBUNTU_SYSTEM_PROMPT, onStep: (step) => console.log(step.text), messages: messages }); // Update conversation history messages = response.messages; } } finally { await instance.stop(); rl.close(); console.log("Session ended."); } } agent_repl().catch(console.error); ``` > Build stateful agents with persistent conversations