Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions agent_with_runloop_backend.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"metadata": {},
"source": [
"# Deep Agent with Runloop Backend\n",
"\n",
"This notebook demonstrates how to create a deep agent that operates on files in a Runloop devbox."
]
},
{
"cell_type": "markdown",
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"metadata": {},
"source": [
"## Setup: Import dependencies"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from runloop_api_client import Runloop\n",
"from deepagents.graph import create_deep_agent\n",
"from deepagents.integrations.runloop import RunloopBackend"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "22f0b297-52c9-45b0-a96d-95063f08ae4a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"dbx_31Wy37EKz7tur7WyBxD0L\n"
]
}
],
"source": [
"import os\n",
"\n",
"from runloop_api_client import Runloop\n",
"\n",
"client = Runloop(\n",
" bearer_token=os.environ.get(\"RUNLOOP_API_KEY\"), # This is the default and can be omitted\n",
")\n",
"\n",
"devbox_view = client.devboxes.create()\n",
"print(devbox_view.id)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "40135f20-a1ad-45f9-9475-79669d4e05c5",
"metadata": {},
"outputs": [],
"source": [
"backend = RunloopBackend(devbox_id=devbox_view.id, client=client)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "e5f6a7b8-c9d0-1234-ef12-345678901234",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Agent created with Runloop backend!\n"
]
}
],
"source": [
"# Create the deep agent with the Runloop backend\n",
"agent = create_deep_agent(\n",
" backend=backend,\n",
" system_prompt=\"You are a helpful coding assistant that operates on files in a remote devbox.\",\n",
")\n",
"\n",
"print(\"Agent created with Runloop backend!\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d799bce6-4a67-40ce-858e-7ab32ddcb688",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'messages': [HumanMessage(content='could you list the files you have access to (not using the shell)', additional_kwargs={}, response_metadata={}, id='af9024cc-2f43-4189-adb4-df4002ba16e8'),\n",
" AIMessage(content=[{'text': \"I'll list the files in the root directory for you using the `ls` tool.\", 'type': 'text'}, {'id': 'toolu_01F5JG2vEa3MfUkkVJDECfar', 'input': {'path': '/'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01AJgwWnMfcuFRsowUgKymfd', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 6021}, 'cache_creation_input_tokens': 6021, 'cache_read_input_tokens': 0, 'input_tokens': 3, 'output_tokens': 70, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--40e0b169-0f0e-4454-8d27-54ad0af8202b-0', tool_calls=[{'name': 'ls', 'args': {'path': '/'}, 'id': 'toolu_01F5JG2vEa3MfUkkVJDECfar', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6024, 'output_tokens': 70, 'total_tokens': 6094, 'input_token_details': {'cache_read': 0, 'cache_creation': 6021, 'ephemeral_5m_input_tokens': 6021, 'ephemeral_1h_input_tokens': 0}}),\n",
" ToolMessage(content='[\"/bin\", \"/boot/\", \"/dev/\", \"/etc/\", \"/home/\", \"/lib\", \"/lost+found/\", \"/media/\", \"/mnt/\", \"/opt/\", \"/proc/\", \"/root/\", \"/run/\", \"/runloop/\", \"/sbin\", \"/srv/\", \"/sys/\", \"/tmp/\", \"/usr/\", \"/var/\"]', name='ls', id='0e7977b3-29be-4c7f-9732-99df7717a52a', tool_call_id='toolu_01F5JG2vEa3MfUkkVJDECfar'),\n",
" AIMessage(content=[{'text': \"These are the top-level directories. Would you like me to explore a specific directory? Common places where user files might be located include:\\n- `/home/` - typically contains user home directories\\n- `/root/` - root user's home directory\\n- `/tmp/` - temporary files\\n\\nLet me check the home directory to see if there are any user files:\", 'type': 'text'}, {'id': 'toolu_01VbsrkDwfDCqwDAx8wxcvwv', 'input': {'path': '/home'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014z33y8Tjf3JUTDygCPZ1ow', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 161}, 'cache_creation_input_tokens': 161, 'cache_read_input_tokens': 6021, 'input_tokens': 6, 'output_tokens': 130, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--37884afe-18dd-49c4-8b42-4c1b3352f354-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home'}, 'id': 'toolu_01VbsrkDwfDCqwDAx8wxcvwv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6188, 'output_tokens': 130, 'total_tokens': 6318, 'input_token_details': {'cache_read': 6021, 'cache_creation': 161, 'ephemeral_5m_input_tokens': 161, 'ephemeral_1h_input_tokens': 0}}),\n",
" ToolMessage(content='[\"/home/user/\"]', name='ls', id='9c9ce343-4d71-43c1-81e1-4e6ffb713bb9', tool_call_id='toolu_01VbsrkDwfDCqwDAx8wxcvwv'),\n",
" AIMessage(content=[{'id': 'toolu_01YYB5BrUsHCERQENFEchwDH', 'input': {'path': '/home/user'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01MEYsD2CkCVZnfMAchsoWyG', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 148}, 'cache_creation_input_tokens': 148, 'cache_read_input_tokens': 6182, 'input_tokens': 6, 'output_tokens': 54, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--31594999-5a24-4bc8-92bd-7710ff24833e-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home/user'}, 'id': 'toolu_01YYB5BrUsHCERQENFEchwDH', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6336, 'output_tokens': 54, 'total_tokens': 6390, 'input_token_details': {'cache_read': 6182, 'cache_creation': 148, 'ephemeral_5m_input_tokens': 148, 'ephemeral_1h_input_tokens': 0}}),\n",
" ToolMessage(content='[\"/home/user/.bash_logout\", \"/home/user/.bashrc\", \"/home/user/.profile\", \"/home/user/.ssh/\", \"/home/user/.sudo_as_admin_successful\"]', name='ls', id='989c232e-847d-4bf5-b83a-d5f6170453dd', tool_call_id='toolu_01YYB5BrUsHCERQENFEchwDH'),\n",
" AIMessage(content='The `/home/user` directory contains mostly configuration files. Would you like me to explore any other directories or search for specific types of files?', additional_kwargs={}, response_metadata={'id': 'msg_01GBSvugbTUcF13j4s7veT3n', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 111}, 'cache_creation_input_tokens': 111, 'cache_read_input_tokens': 6330, 'input_tokens': 6, 'output_tokens': 32, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--f8ac1432-1219-4afc-869b-63b3b9df7093-0', usage_metadata={'input_tokens': 6447, 'output_tokens': 32, 'total_tokens': 6479, 'input_token_details': {'cache_read': 6330, 'cache_creation': 111, 'ephemeral_5m_input_tokens': 111, 'ephemeral_1h_input_tokens': 0}})]}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent.invoke({\n",
" \"messages\": [\n",
" { \n",
" \"role\": \"user\",\n",
" \"content\": \"could you list the files you have access to (not using the shell)\"\n",
" }\n",
" ]}\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "77c3cee4-fb80-4488-adfc-2044c33afbaa",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[{'type': 'text', 'text': \"Perfect! I've created the file `foo.md` in `/home/user/` with a short 3 sentence poem about nature. The poem describes the morning sun, a gentle breeze, and nature's peaceful symphony.\"}]\n"
]
}
],
"source": [
"import uuid\n",
"\n",
"config = {\n",
" \"configurable\": {\n",
" \"thread_id\": str(uuid.uuid4())\n",
" }\n",
"}\n",
"\n",
"# Example: Ask the agent to create a file\n",
"result = agent.invoke({\n",
" \"messages\": [{\n",
" \"role\": \"user\",\n",
" \"content\": \"create a file called foo.md with a short 3 sentence poem'\"\n",
" }]\n",
"}, config=config)\n",
"\n",
"# Print the agent's response\n",
"print(result[\"messages\"][-1].content_blocks)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "c6d439fe-1aa8-45a4-a534-b62e8a2e6498",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"total 4.0K\n",
"4.0K -rw-r--r-- 1 user user 152 Nov 6 17:58 foo.md\n",
"\n",
"The morning sun breaks through the trees.\n",
"A gentle breeze whispers secrets to the leaves.\n",
"Nature's symphony plays on, bringing peace to all who listen.\n",
"\n"
]
}
],
"source": [
"print(backend.execute('ls -lsh').output)\n",
"print(backend.execute('cat foo.md').output)"
]
},
{
"cell_type": "markdown",
"id": "c9d0e1f2-a3b4-5678-3456-789012345678",
"metadata": {},
"source": [
"## With GitHub code mounts\n",
"\n",
"Create an agent that operates on a cloned GitHub repository."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d0e1f2a3-b4c5-6789-4567-890123456789",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Agent created with GitHub repository mounted!\n"
]
}
],
"source": [
"# Create provider with GitHub code mount\n",
"provider_with_repo = RunloopProvider(\n",
" api_key=os.environ.get(\"RUNLOOP_API_KEY\"),\n",
" create_params={\n",
" \"code_mounts\": [\n",
" {\n",
" \"repo_name\": \"personal-site-generator\", # Replace with your repo\n",
" \"repo_owner\": \"eyurtsev\", # Replace with your username\n",
" \"token\": os.environ.get(\"GITHUB_TOKEN\"),\n",
" }\n",
" ],\n",
" }\n",
")\n",
"\n",
"# The None Here doesn't make much sense\n",
"backend = provider.backend_factory(None)\n",
"\n",
"# Create agent\n",
"agent = create_deep_agent(\n",
" backend=backend,\n",
" system_prompt=\"You are a helpful coding assistant. The repository is mounted in the devbox.\",\n",
")\n",
"\n",
"print(\"Agent created with GitHub repository mounted!\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
41 changes: 40 additions & 1 deletion libs/deepagents/backends/composite.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
"""CompositeBackend: Route operations to different backends based on path prefix."""

from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult
from deepagents.backends.protocol import (
BackendProtocol,
EditResult,
ExecuteResponse,
FileInfo,
GrepMatch,
SandboxBackendProtocol,
WriteResult,
)
from deepagents.backends.state import StateBackend


Expand Down Expand Up @@ -211,3 +219,34 @@ def edit(
except Exception:
pass
return res

def execute(
self,
command: str,
*,
timeout: int = 30 * 60,
) -> ExecuteResponse:
"""Execute a command via the default backend.

Execution is not path-specific, so it always delegates to the default backend.
The default backend must implement SandboxBackendProtocol for this to work.

Args:
command: Full shell command string to execute.
timeout: Maximum execution time in seconds (default: 30 minutes).

Returns:
ExecuteResponse with combined output, exit code, and truncation flag.

Raises:
NotImplementedError: If default backend doesn't support execution.
"""
if isinstance(self.default, SandboxBackendProtocol):
return self.default.execute(command, timeout=timeout)

# This shouldn't be reached if the runtime check in the execute tool works correctly,
# but we include it as a safety fallback.
raise NotImplementedError(
"Default backend doesn't support command execution (SandboxBackendProtocol). "
"To enable execution, provide a default backend that implements SandboxBackendProtocol."
)
26 changes: 26 additions & 0 deletions libs/deepagents/backends/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Generic, TypedDict, TypeVar

T = TypeVar("T")


class PaginationCursor(TypedDict):
"""Pagination cursor for listing sandboxes."""

next_cursor: str | None
"""Cursor for the next page of results.

None OR empty string if there are no more results.

string to be interpreted as an opaque token.
"""
has_more: bool
"""Whether there are more results to fetch."""


class PageResults(TypedDict, Generic[T]):
"""Page results for listing sandboxes."""

items: list[T]
"""List of sandbox IDs."""
cursor: PaginationCursor
"""Pagination cursor."""
Loading
Loading