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
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 abash tool backed by the sandbox, and store the sandbox ID in the session state. Everything else is standard ADK usage.
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 tobash to let the model write a script, run it, and read the result.
read_filereturns a missing file as an error result withstatusanderror_message, and the model can correct the path and try again. An exception that a tool does not catch stops the run, andrun_asyncraises it.RunConfig(max_llm_calls=10)caps the model calls in the run (the default is 500). Past the limit, the run raisesLlmCallsLimitExceededError. See Runtime Config in the ADK docs.
/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.
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.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
AsyncSandboxhandle opens its own HTTP connection, which stays open untilkill()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 callconnect()in each tool call or each turn. - The model can call only the tools you define. Keep
kill(),pause(), andupdate()in your application code, not in a tool. - To limit what model-written code can reach, create the sandbox with network rules.
- Pass
timeout_secondstocommands.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
python3but nopython. Tell the agent in itsinstruction, or use a template with the tools you need. - As a backstop for crashes, set
timeout_secondsandauto_delete_secondswhen 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.