Sandbox hooks
A sandbox hook runs your code at two points of a sandbox's life. on_create runs once when the
sandbox is ready, before the agent starts. on_destroy runs once when the session closes. Use them
to seed a workspace, or to upload what the agent produced.
The examples use MistralSandbox, the sandbox Mistral hosts. Besides the SDK,
they need the cloud_user role (Requirements) and the sandbox
extra: uv add "mistralai-agents[koyeb]".
Seed the workspace
Subclass agents.SandboxHook, and put an instance in the sandbox's hooks=, not the agent's. Both
points get ctx, an agents.SandboxCallbackContext.
Its ctx.sandbox runs commands with execute() and reads files with read_file(). Commands run in
the workspace, so the files you create there are visible to the agent's tools:
from pathlib import Path
from mistralai import agents
class SeedWorkspace(agents.SandboxHook):
async def on_create(self, ctx: agents.SandboxCallbackContext) -> None:
result = await ctx.sandbox.execute("echo 'Run the tests with pytest.' > NOTES.md")
if result.exit_code != 0:
raise RuntimeError(f"seeding failed: {result.stderr}")
async def on_destroy(self, ctx: agents.SandboxCallbackContext) -> None:
report = await ctx.sandbox.read_file("NOTES.md")
Path("saved-notes.md").write_bytes(report) # in your process; deployed, upload it from the sandbox
agent = agents.Agent(
model="mistral-large-4",
environment=agents.MistralSandbox(hooks=[SeedWorkspace()]),
)Each session of this agent starts with a NOTES.md file in its workspace, and copies it out of the
sandbox when the session closes. A session closes at await session.cancel(), at a
session limit, or, in your process, once
an agent.run() without idempotency_key= ends its turn. Inside an async with agents.local_runtime() block, it closes when the block exits. See
how a session ends.
To try a hook on your machine without the cloud_user role, pass the same hooks= to a
local sandbox, such as agents.LocalDockerSandbox(hooks=[SeedWorkspace()]),
which needs Docker.
When you deploy with Studio (self-hosted or Mistral-managed), the hook runs in code that
replays: do its work through ctx.sandbox, use
agents.now() and agents.sleep() for time, and keep any upload idempotent.
A failing command does not raise, so check exit_code. A slow command, such as a git clone on a sandbox
with network access, can outlast the sandbox's default time limit (default_exec_timeout_seconds on
MistralSandbox and
LocalDockerSandbox). Give it its own:
await ctx.sandbox.execute("git clone https://github.com/acme/api.git", timeout_seconds=240)A sandbox hook keeps no state between its two points: derive what on_destroy needs from the hook's
constructor arguments, or read it back from the workspace.
To set the git identity that commits in the sandbox carry, use the built-in
agents.pattern.sandbox_hook.GitIdentity hook: see Git identity.
NoSandbox, the default, never creates a sandbox, so neither point runs.
Seed it from a session input
Both points declare session inputs as a tool does, with
Annotated[T, agents.Depends()]. Use an input to seed the workspace with what this session works on:
import shlex
from typing import Annotated
from mistralai import agents
class RepositoryContext(agents.SessionInput):
input_id = "acme.repository"
provider: str
repository: str
revision: str
class RecordRepository(agents.SandboxHook):
async def on_create(
self,
ctx: agents.SandboxCallbackContext,
*,
repo: Annotated[RepositoryContext, agents.Depends()],
) -> None:
line = shlex.quote(f"{repo.repository}@{repo.revision}")
result = await ctx.sandbox.execute(f"echo {line} > REPOSITORY")
if result.exit_code != 0:
raise RuntimeError(f"seeding failed: {result.stderr}")
agent = agents.Agent(
model="mistral-large-4",
environment=agents.MistralSandbox(hooks=[RecordRepository()]),
)Pass the input when you start the session, in an async function, continuing the example above:
repo = RepositoryContext(provider="github", repository="acme/api", revision="main")
session = await agent.start_session(session_inputs={RepositoryContext.input_id: repo})Quote input values with shlex.quote() before you put them in a command: the shell runs whatever the
string holds. The input is checked before the sandbox is created.
When a sandbox hook raises
If on_create raises, the sandbox is deleted and the session fails to start. In your process you get
the hook's exception. On a deployed agent, the session ends as failed, and its message names only the hook
and the exception type. The deployment's logs hold the full error, so keep credentials out of exception messages.
agents serve shows that log only with AGENTS_SDK_DEBUG=1. If on_destroy raises, the error is
logged and the other hooks still run.
Next
Network access: choose what the sandbox can reach.
See also:
- Sandbox hooks reference:
SandboxHookand its points.