> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.scrapybara.com/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<string> => 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);
```