Run modes

Coming soon

Write your agent and harness once, then choose where it runs.

  • in-process while you build in development
  • promote to a durable worker you host yourself in production, and register against Workflows API, so that Mistral handles orchestration and durability for you
  • or run it as a durable managed worker in production : Mistral hosts both the orchestration layer and the agent - you provide your agent definition as a python package.
Localself-hosted Workermanaged Worker
APIagent.run(), agent.create_session()agents serveagents deploy
Infrastructurenone (in-process)Mistral-managed workflow + a worker process you runMistral-managed workflow and worker
Multi-turn in one session✓✓✓
Callbacks, steer, interrupt✓✓✓
Durable and distributed✗✓✓
Session resume after a crash✗✓✓
Fork, manual compaction, cancel✗✓✓
Best fordev, tests, fast iterationproduction on infrastructure you controlproduction with nothing to operate

All three run modes are driven from the SDK with agent.create_session(), and the session API is the same in each one. Only the rows above change.

Local - in-process

Local - in-process

The default. agent.run(...) runs in your process with no infrastructure. Best for development, tests, and fast iteration.

agent = agents.Agent(model="mistral-medium-3-5", instructions="Be concise.")
result = await agent.run("Say hello in three words.")

agent.run(...) is a single question with no memory; a session is a conversation.

Caution

agent.run(...) is a single question with no memory. agent.run() has no callback channel. With permission_mode="ask" the turn stops at the approval and does not terminate. Use create_session().

A session does not need a worker. create_session() opens one in this process, and it remembers the turns you run through it:

async with await agent.create_session() as session:
    await ask(session, "My name is Ada.")
    answer = await ask(session, "What is my name?")   # the session remembers

ask() is the small event-reading helper from Sessions.

An in-process session also answers callbacks, and steer_turn and interrupt_turn both work on a running turn. What it does not have is durability: the session lives and dies with your process, and fork_session, compact_session and cancel_session raise UnsupportedSessionProcedureError.

What the CLI loads

What the CLI loads

agents info, agents serve and agents deploy all need the same thing: the Agent object you built in Python. By default they look for agent in main.py, so a project that is a single main.py runs as it is.

# main.py
from mistralai.agents import agents

agent = agents.Agent(
    model="mistral-medium-3-5",
    name="hello-agent",
    instructions="You are a friendly agent. Greet the user in one sentence.",
    harness=agents.Harness(tools=[get_weather]),
)

get_weather is an @agents.tool function defined in the same module — see Tools.

To keep the agent somewhere else, declare it in pyproject.toml as module:attr — the module that defines it, then the variable that holds it:

[tool.mistralai.agents]
entrypoint = "src.service:support_agent"

The module is imported with the project root on the import path, so a module sitting at the root works without the project being an installable package. Every command takes a project root as its argument, so agents serve ./hello-agent runs one you are not standing in.

The agent must be named

Agent(...) has to set name= explicitly. It is the name the worker registers and the name agents deploy routes on, so the SDK refuses to guess it:

main:agent must set an explicit name: `agents.Agent(model=..., name=...)`. The name is the deployed workflow name.

This name is what the control plane keys on, and it is unique per workspace, not globally: deploying hello-agent again from the same workspace updates that same deployment, but another workspace's hello-agent is a separate one.

The platform keeps some names for its own agents: vibe-code, vibe-code-web and vibe-code-agent, written with - or _. Agent(...) raises SdkError for a reserved name, and agents serve and agents deploy refuse one passed with --name:

Agent name 'vibe-code' is reserved by the platform. Choose another name with `agents.Agent(model=..., name=...)` or `--name`.
Check what was loaded

Check what was loaded

agents info prints the surface the CLI resolved, and names the first thing it could not load:

agent: hello-agent
  model: mistral-medium-3-5
  description: ...
  instructions: You are a friendly agent. Greet the user in one sentence.
  sandbox: none
  tools:
    - get_weather
  connectors: (none)
  skills: (none)
  hooks: (none)
  betas: (none)
  subagents: (none)

Run it after changing the harness. A tool that does not appear here will not appear to the model either.

:::note [tool.mistralai.agents].runtime is rejected Older projects pinned a backend with runtime = "remote" or runtime = "nuage". There is one runtime now, so the key selects nothing and the CLI errors on it rather than ignoring it. Delete the line if a project still carries it. :::

Self-hosted worker - agents serve

Self-hosted worker - agents serve

You run the worker; Mistral runs the orchestration. State survives crashes and scales across workers - with the same agent and harness code.

Tip

agents serve is a way to test what a deployed agent would do, or to host and operate this worker in production in your own cluster.

agents serve loads the agent you built in Python:

export MISTRAL_API_KEY="<your-key>"
agents serve                              # the project in the current directory
agents serve ./hello-agent --name hello-agent

It runs a long-lived worker registered against the Workflows API and prints a link to trigger the agent.

--name overrides the agent's defined name, which is also what the run is routed on.

✓ Agent hello-agent is serving on agents-sdk-hello-agent

  Try the agent:
  • easily in Vibe's UI:      https://chat.mistral.ai/code?agent=hello-agent
  • or programmatically, using the Session API: https://docs.mistral.ai/studio/agents/core/sessions

  Debug:  https://console.mistral.ai/build/workflows?workflowSearch=hello-agent

Press Ctrl+C to stop.

Registration is what points the agent name at this deployment. Until it succeeds no session can reach the worker, so serve treats it as fatal: the banner appears only once the agent is registered, and otherwise it exits non-zero naming the control plane it tried.

Cannot serve agent: registration failed at https://api.mistral.ai/v2/agents: <detail>
The worker cannot receive sessions until it is registered.

A managed worker skips this step: agents deploy registers the agent when it creates the deployment.

The usual causes are a control-plane URL that does not end in /v2/agents, an agents API that is not enabled for the account, and a name already bound to another workflow or deployment - most often one you created earlier with agents deploy. Agent names are unique per workspace, so serve under a free name (--name hello-agent-dev) or shut down the agent holding it — see Agent lifecycle.

The worker is where your agent actually runs. Stop it and the agent stops answering, but ongoing sessions are safe : durability and workflow state is managed by Workflows API. So the session will resume from its last checkpoint when a worker comes back.

Warning

Note that the harness executing actions from anyone opening session against this agent - whether it's from Vibe UI or from the Session API - runs on the machine hosting the worker. Be careful to provide isolation through a Sandbox.

:::note Worker environment

Against production a worker needs only MISTRAL_API_KEY (workflows-enabled). To point one at another deployment, staging here, move both URLs together:

export MISTRAL_API_KEY="<staging key>"
export MISTRAL_API_URL="https://api.globalaegis.net"
export AGENTS_SDK_DEPLOY_CONTROL_PLANE_URL="${MISTRAL_API_URL}/v2/agents"

MISTRAL_API_URL selects the Workflows and model APIs. It is the API root, without /v1. SERVER_URL and MISTRAL_SERVER_URL still work as deprecated aliases.

AGENTS_SDK_DEPLOY_CONTROL_PLANE_URL is where the worker registers itself. It has to end in /v2/agents: the SDK appends its own /agents segment, and that doubled path is the real route. Pointing it at the bare host is a 404 and serve will refuse to start. :::

:::note Model rate limit

Agents running in parallel share the workspace's model rate limit, and a burst of completions can hit 429. Set AGENT__LLM_RATE_LIMIT on the worker to cap model calls across every worker of the deployment:

export AGENT__LLM_RATE_LIMIT='{\"time_window_in_sec\": 60, \"max_execution\": 30}'

Completions over the budget wait in the queue instead of failing. The cap counts calls, not tokens, and it is off when unset. See Workflows rate limiting for how it is enforced. :::

:::tip When serve fails to start agents serve prints only warnings and the banner. Set AGENTS_SDK_DEBUG=1 for the full worker log, which names the Workflows API it resolved and the Temporal endpoint it connected to. LOG_LEVEL does not work here.

Fail to connect to Temporal Service Client is the error for any failed connection, including one the server refused. An expired or revoked MISTRAL_API_KEY reports as a connection failure rather than an authentication one, so check the key before looking at the network. :::

Managed worker - agents deploy

Managed worker - agents deploy

Mistral runs the worker too. Nothing to host, nothing to keep alive. A deploy builds your project's Dockerfile, so add one at the project root and commit it:

# Dockerfile
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

ENV UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never
WORKDIR /app

# Pinned: a deploy rebuilds this image, so an unpinned version changes the runtime under you.
RUN --mount=type=cache,target=/root/.cache/uv \
    uv pip install --system --prerelease=allow mistralai-agents==1.1.0rc4

COPY . /app

CMD ["agents", "serve"]

COPY . /app takes everything in the build context, so add a .dockerignore beside the Dockerfile before your first deploy:

# .dockerignore
.env
.venv/
.git/
__pycache__/
*.py[oc]
build/
dist/

The first line is the most critical one. An image layer keeps whatever it copied, so a .env that lands in the image is readable by anyone who can pull it.

The project is built from your git checkout, so commit and push first, and then deploy:

agents deploy --name hello-agent

--dockerfile points at another path. If neither that nor ./Dockerfile exists, the deploy stops before creating anything.

--name overrides the name= on your Agent and is what the deployment is keyed on. Without it, the agent keeps the name= from your code, the same name that agents serve uses.

An agent that acts on behalf of the user who starts a session — it uses all_connectors(), its named connectors all keep the default run_as="auto", or it uses a Mistral sandbox behind the sandbox proxy — is deployed hardened by default, the same way agents serve hardens on its own. An unhardened deployment could be taken over by anyone, and its worker could not register anyway. --no-hardened opts out; --hardened forces it on for agents without such needs.

If the worker fails to start, agents deploy exits with the rollout error and prints the worker's last output, such as a traceback or a missing dependency:

Deploy failed. Rollout failed: Service is unhealthy.

Last output from the worker:
  Traceback (most recent call last):
    ...
  ModuleNotFoundError: No module named 'my_tools'

To read the full logs, pass the managed deployment id that agents deploy prints after Created agent:

curl -s "https://api.mistral.ai/v1/runtime/logs/<managed-deployment-id>?log_type=runtime&order=asc&limit=1000" \
  -H "Authorization: Bearer $MISTRAL_API_KEY"

The container command

The container must run only the worker, because agents deploy already registered the agent. agents run-worker does this. It needs a mistralai-agents release newer than 1.1.0rc6:

CMD ["agents", "run-worker"]

The platform sets DEPLOYMENT_NAME, and run-worker polls that exact task queue.

To start the worker from your own Python code instead, for example in a Helm chart, call run_worker() on the agent:

import asyncio

from main import agent

asyncio.run(agent.run_worker())

Outside a managed deployment, set DEPLOYMENT_NAME to the task queue to poll. If you do not set it, the worker reads the queue of the agent with the same name from the control plane, so register the agent first with agents serve or agents deploy.

If your platform routes sessions to other names, pass them as run_worker(workflow_name=..., deployment_name=...).

An image with CMD ["agents", "serve"] keeps working. serve detects the managed deployment, skips the registration, and prints a warning that asks you to change the CMD.

Secrets

A managed worker can get a secret from the Secret API as an environment variable, bound at deploy time.

Create the secret first. --secret-binding only points at one reference; it cannot create it. The secret has to already exist in the deployment's workspace. Create it in the Secrets Manager, at console.mistral.ai → Secrets, and note the name you give it — that name is what the reference below points at.

Then bind it:

agents deploy . --name hello-agent \
  --secret-binding COMPANY_LLM_API_KEY=secret:workspace:company-llm-key \
  --secret-binding ANOTHER_API_KEY=secret:workspace:another-api-key

The option takes a reference, not a value. secret:workspace:<NAME> names the secret you created above; the platform reads it at boot, inside the pod. No secret material is typed on the command line, sent to the control plane, or stored with the deployment. A value passed here instead of a reference is rejected before the deploy starts.

Each deploy must state the complete set. A later deploy that omits --secret-binding unbinds every secret an earlier one bound, so keep the options in whatever script or CI job runs the deploy.

The variable is set for the running worker and for the image build, so a Dockerfile that needs one at build time can declare a matching ARG.

:::warning Build args are not a secure channel A value passed to the build persists in the image's history and layers, and anyone who can pull the image can recover it. Only bind credentials you are willing to rotate, and prefer reading the variable at run time, where it is resolved in the pod and never written to a layer. :::

Studio tracing

Studio tracing

A worker sends traces of its agent turns, model calls, and tool calls to Studio, including prompt and completion content. This is on by default for agents serve and agents deploy, and needs no extra package or setting. Content is redacted on the worker before it's sent, with a default policy that masks common secrets and personal data.

Note

Viewing traces in Studio requires an Enterprise organization with Observability enabled. See Observability access.

Set AGENTS_SDK_STUDIO_TRACING_INCLUDE_CONTENT=0 to drop content, or AGENTS_SDK_STUDIO_TRACING_ENABLED=0 to turn tracing off. On a managed worker, set them with ENV in the Dockerfile.

See Send traces for the settings, and Explore traces for how traces map to sessions and turns.

To change what is redacted, see Data redaction.

What durability buys you

What durability buys you

On a worker, the runtime makes the session durable: each turn, tool call, and sandbox operation is checkpointed. If the worker process dies, the run resumes from the last checkpoint rather than starting over.

Human-in-the-loop. Because the session is durable, it can pause for human input - an approval, an answer - and resume when that input arrives, even days later, without keeping a process running. See Permission.

Choosing

Choosing

  • Reach for local while you build, test, and iterate - zero setup, instant feedback.
  • Promote to a self-hosted worker when you need durability, scale-out, or human-in-the-loop and the agent has to run on infrastructure you control - inside your network, next to your data, or under your own deployment process.
  • Reach for a managed worker for everything else in production. It is the same durability without a process to operate, at the cost of deploying from a git repository.

No code rewrite in either direction: same Agent, same Harness, wrapped in a workflow.

Time, sleeping, ids and randomness

Time, sleeping, ids and randomness

On a worker, a workflow is replayed: when a worker picks the run back up, the agent loop re-executes from the top and Temporal feeds it the recorded outcomes. Anything that reads the outside world during that re-execution answers differently the second time round, which breaks the run in one of two ways. If the value only lands in data, nothing raises and the behaviour is quietly wrong - a deadline computed from time.time() moves forward at every replay, so a "stop after 10 minutes" limit never arrives. If it changes which activity runs next, Temporal fails the workflow task and retries it forever, and the run stops making progress.

The SDK gives you four replay-safe replacements. Each one works from all three places agent code runs - the agent loop, a hook, and a tool body - so a shared helper needs no branching:

from datetime import timedelta

from mistralai.agents import agents

deadline = agents.now() + timedelta(minutes=10)   # stable across replay
job_id = agents.uuid4()                            # same id on replay of this run
await agents.sleep(30 + agents.random().uniform(0, 5))  # jittered poll
Instead ofUseWhy
time.time(), datetime.now()agents.now()Workflow time in the agent loop, so a deadline holds across replay. Returns an aware UTC datetime; call .timestamp() for epoch seconds.
asyncio.sleep(...)await agents.sleep(...)A durable server-side timer in the agent loop. In a tool body it is a plain bounded sleep, and raises if it would outlast the activity's start_to_close_timeout.
uuid.uuid4()agents.uuid4()Re-deriving the id when the run is replayed yields the same value. A continue-as-new starts a new seed, so carry ids that must outlive it in your own state.
random.random()agents.random()Returns a seeded random.Random, so .random(), .uniform(), .choice() and .shuffle() all replay identically.
See also

See also

  • Sessions - driving a worker-backed session across turns.
  • The Harness - capabilities that travel local-to-worker unchanged.
  • Agent lifecycle - what a deploy creates, and shutting an agent down.