Skip to main content
Agent Development Kit (ADK) is Google’s framework for building agents with tools, sessions, and streaming events. Back the agent’s tools with sandbox.commands.run and sandbox.files, and keep each conversation’s sandbox ID in the ADK session state. Every command the model runs, and every file it writes, then stays in an isolated VM instead of on your server. The model API key stays in your app; the sandbox never sees it.

Setup

ADK needs Python 3.11 or later. The examples on this page use Claude through ADK’s LiteLLM connector, which reads ANTHROPIC_API_KEY from the environment. The LiteLLM version range is the one that ADK declares for its extensions extra. Install the mcp extra: without it, LiteLLM 1.106 fails every request that includes tools with litellm.APIConnectionError: No module named 'mcp'. The examples were tested with google-adk 2.13.0, litellm 1.106.2, and superserve 0.11.5. See API keys to create a Superserve API key.
ADK uses Gemini models by default. To use Gemini, pass a model name such as model="gemini-3.5-flash" instead of the LiteLlm object, and set GOOGLE_API_KEY instead of ANTHROPIC_API_KEY. You do not need LiteLLM for Gemini. The examples on this page were not tested with Gemini. See Models in the ADK docs.

Wire the sandbox as ADK tools

An ADK tool is a Python function. The highlighted lines define a bash tool backed by the sandbox, and store the sandbox ID in the session state. Everything else is standard ADK usage.
ADK builds the tool declaration from the function signature and docstring. It passes tool_context to every tool that declares it, and does not show that parameter to the model. See Context injection in the ADK docs. The session state holds the sandbox ID, not the sandbox object, so one agent defined at module level can work with a different sandbox in every session. The sandboxes dictionary maps each ID to its sandbox handle in this process. Do not put the handle in the session state: session services copy the state, and DatabaseSessionService stores it as JSON. See State in the ADK docs. ADK prefers tools that return a dictionary. result.model_dump() gives the model stdout, stderr, exit_code, and truncated, so the model sees failed commands as well as their output.

File tools

Add file tools next to bash to let the model write a script, run it, and read the result.
  • read_file returns a missing file as an error result with status and error_message, and the model can correct the path and try again. An exception that a tool does not catch stops the run, and run_async raises it.
  • RunConfig(max_llm_calls=10) caps the model calls in the run (the default is 500). Past the limit, the run raises LlmCallsLimitExceededError. See Runtime Config in the ADK docs.
Output from a run (trimmed; the model’s wording varies):
A run that asked the model to read /home/user/config.json first got the error result, then created the file and read it back:
sandbox.files paths must be absolute. See Read & write files for binary content and directory downloads.

Streaming events and tool calls

runner.run_async() yields ADK events. With StreamingMode.SSE, the model’s text arrives in partial events as it is generated. Tool calls and tool results arrive in events that are not partial.
The event that ends a streamed text response is not partial and repeats the full text, so the loop prints text only from partial events. Output from a run (trimmed; the model’s wording varies):
See Events in the ADK docs, and Streaming to stream a single command’s output.

One sandbox per session

To serve many conversations, create one sandbox for each ADK session, and keep its ID in the session state. On each turn, resume the sandbox, run the agent, and pause the sandbox afterwards. Files and installed packages stay available, and you do not pay for an idle VM between turns.
The session keeps the conversation history and the sandbox ID. The second turn understands “that file” because of the session history, and finds the file because it lives in the sandbox. start_chat keeps the handle from create() in sandboxes, and every turn of the chat uses that one handle. A handle does not resume the sandbox by itself after pause(), so resume_sandbox calls resume() before each turn. If the process has no handle for the chat, for example after a restart, AsyncSandbox.connect() makes one and resumes the sandbox. end_chat calls kill() on the handle, which deletes the sandbox and closes the HTTP connection of the handle. See Lifecycle and Connect. If create_session() or pause() fails, start_chat deletes the sandbox, so no sandbox runs without a session. Run one turn at a time for each session. If two turns overlap, the first turn to finish pauses the sandbox while the other turn still uses it.
InMemoryRunner keeps sessions in memory, so they are lost when the process stops. To keep sessions across restarts, install google-adk[db] and pass a DatabaseSessionService, for example DatabaseSessionService(db_url="sqlite+aiosqlite:///sessions.db"), as session_service to Runner. The sandbox ID is stored with the rest of the session state. See Session in the ADK docs.

Good to know

  • Each AsyncSandbox handle opens its own HTTP connection, which stays open until kill() or until Python frees the handle. pause() does not close it. Keep one handle for each sandbox, share it with the tools, and do not call connect() in each tool call or each turn.
  • The model can call only the tools you define. Keep kill(), pause(), and update() in your application code, not in a tool.
  • To limit what model-written code can reach, create the sandbox with network rules.
  • Pass timeout_seconds to commands.run. Without it, the SDK request times out after 30 seconds, so the tool call fails on longer commands such as package installs.
  • The default sandbox has python3 but no python. Tell the agent in its instruction, or use a template with the tools you need.
  • As a backstop for crashes, set timeout_seconds and auto_delete_seconds when you create the sandbox. See Auto-delete.

Resources

ADK docs

Agents, tools, sessions, and events in Google’s Agent Development Kit.

Run commands

Options for commands.run, including environment variables and working directory.