Skip to main content
Lesson 14 of 14 · Mistral Workflows: Durable Orchestration

Observability and OpenTelemetry

Lesson 14 of 14 Updated November 2, 2026 Advanced
On this page
  1. Why observe your workflows
  2. Name activities for readability
  3. Retrieve traces programmatically
  4. Master sampling
  5. Monitor in production
  6. Debug a failed workflow
  7. Course recap
  8. Key takeaways
  9. Test your knowledge

Updated November 2, 2026 based on mistralai SDK 3.7.0: trace methods are located under client.workflows.executions; their asynchronous versions carry the _async suffix (awaiting the synchronous version fails), execution_id is passed as a keyword argument, and responses expose otel_trace_data, span_tree, and events (the spans, activity_count, or error_count fields read by the older version no longer exist); traceparent is passed as an execution parameter, as the HTTP header is deprecated. Sources: Workflow observability · client-python.

Why observe your workflows

A workflow in production can chain dozens of activities, call multiple external services, and run from a few seconds to several days. Without observability, diagnosing a problem is like finding a needle in a haystack: you know the execution failed, but not at which step, after how many attempts, or whether the culprit is your code or a third-party API. Mistral’s Workflows platform integrates OpenTelemetry natively to answer this question: every activity, every error, and every duration is recorded as a span in a distributed trace, with no instrumentation required on your part.

Two concepts structure this model. The trace represents the entire journey of an execution, from the initial trigger to the final result. The span, on the other hand, is an individual operation within that trace: each activity automatically generates one, carrying its name, start and end times, execution duration, status (success or error), and any custom attributes. These spans form a hierarchy—the workflow span is the parent, and the activity spans are its children—which reproduces the complete structure of the execution.

Name activities for readability

The name parameter of the activity decorator determines the label that appears in traces. It’s a ten-second investment while writing that will save you a lot of time during an incident.

import mistralai.workflows as workflows

@workflows.activity(name="Extract source text")
async def extraire_texte(url: str) -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
    return {"texte": response.text}

@workflows.activity(name="Mistral sentiment analysis")
async def analyser_sentiment(texte: str) -> dict:
    client = Mistral()
    response = await client.chat.complete_async(
        model="mistral-large-latest",
        messages=[
            {"role": "system", "content": "Analyze the sentiment."},
            {"role": "user", "content": texte}
        ]
    )
    return {"sentiment": response.choices[0].message.content}

@workflows.activity(name="Send email notification")
async def envoyer_notification(destinataire: str, contenu: str) -> dict:
    # ...
    return {"status": "sent"}

Put yourself in the shoes of the on-call engineer opening a trace view at 3 a.m. Between fetch_user_data and “Retrieve user profile,” the latter is immediately understandable, even to someone who has never read this code.

Retrieve traces programmatically

The SDK exposes three access methods under client.workflows.executions; their asynchronous versions carry the _async suffix. The first returns raw OpenTelemetry data—spans, durations, attributes, parent-child relationships—allowing you to re-ingest it into your own observability tool (Jaeger, Grafana Tempo, etc.).

from mistralai.client import Mistral

client = Mistral()

# Raw OpenTelemetry data for the execution
trace = await client.workflows.executions.get_workflow_execution_trace_otel_async(
    execution_id="exec_abc123def456"
)

print(f"OTel Trace: {trace.otel_trace_id}")
otel_data = trace.otel_trace_data  # Full trace, to re-export to your backend

When you mainly want to know if the execution succeeded and how long it took, the summary is enough. It includes the status, total duration, and the span tree, which you can traverse recursively:

summary = await client.workflows.executions.get_workflow_execution_trace_summary_async(
    execution_id="exec_abc123def456"
)

print(f"Workflow: {summary.workflow_name} — status {summary.status}")
print(f"Total duration: {summary.total_duration_ms} ms")


def display(span, depth=0):
    end = span.end_time_unix_nano or span.start_time_unix_nano
    duration_ms = (end - span.start_time_unix_nano) / 1e6
    print(f"{'  ' * depth}{span.name}: {duration_ms:.0f} ms, {len(span.events)} event(s)")
    for child in span.children or []:
        display(child, depth + 1)


if summary.span_tree:
    display(summary.span_tree)

The third method returns a chronological list of execution events:

response = await client.workflows.executions.get_workflow_execution_trace_events_async(
    execution_id="exec_abc123def456",
    include_internal_events=False,  # Exclude internal platform events
)

for event in response.events or []:
    print(event)

The include_internal_events=False parameter filters out technical platform events, keeping only those from your workflow and activities. On a slightly long workflow, the difference in readability is considerable. For the logs themselves—those displayed in Studio—get_workflow_execution_logs returns them page by page.

Master sampling

By default, all traces are exported: the worker samples at 100%. To set the trace ID or control sampling for an execution, pass a W3C-format traceparent in the body of the execution request. The worker uses a parent-based sampler: it follows your decision, carried by the last field (01 to collect, 00 not to collect).

execution = await client.workflows.execute_workflow_async(
    workflow_identifier="my_workflow",
    input={"data": "test"},
    traceparent="00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
)

Mistral SDKs do this automatically: with each execution, they transmit the traceparent of the current span. Passing traceparent in an HTTP header is still accepted but deprecated, because a header can be rewritten in transit; use the parameter instead.

Monitor in production

Five indicators are enough to keep a workflow under control: the success rate, i.e., the percentage of executions that finish without error; the average duration of a complete execution; the P95 and P99 durations, which reveal the slowest executions that the average hides; the retry rate per activity, whose sudden rise betrays an unstable external service well before the success rate degrades; and finally, the identification of the slowest activity, your bottleneck.

Since traces respect the OpenTelemetry standard, nothing forces you to consult them from the SDK: they can be exported to Datadog via its OTel collector, to Grafana and Tempo by the same mechanism, to Jaeger for distributed trace visualization, or to New Relic via its dedicated exporter. Your workflows thus join the dashboard where the rest of your infrastructure already lives.

Debug a failed workflow

Faced with a failure, a methodical procedure avoids guessing.

  1. Retrieve the execution ID from logs or the API response
  2. Consult the trace summary to know the status and spot the faulty activity in the span tree
  3. Examine detailed events to understand the sequence that led to the error
  4. Check retries: was the activity restarted? How many times?
  5. Analyze the error message: timeout, network error, business error?

The first two steps can be scripted in a few lines, using the display function seen above, giving you a reusable diagnostic tool.

async def diagnose_failure(execution_id: str):
    client = Mistral()
    executions = client.workflows.executions

    # Step 1: Summary and span tree
    summary = await executions.get_workflow_execution_trace_summary_async(execution_id=execution_id)
    print(f"Status: {summary.status}")
    if summary.span_tree:
        display(summary.span_tree)

    # Step 2: Events from your code, in order
    response = await executions.get_workflow_execution_trace_events_async(
        execution_id=execution_id,
        include_internal_events=False,
    )
    for event in response.events or []:
        print(event)

The number of attempts deserves special attention: an activity that fails on the first attempt and one that fails after five retries tell different stories and call for different fixes.

Course recap

Congratulations, you have finished this course on Mistral Workflows. The journey took you from durable orchestration and the event sourcing mechanism to installing the SDK (lessons 1 to 3), then to creating activities, workflows, and workers, triggering and testing them (lessons 4 to 7). Lessons 8 to 11 laid out the structuring constraints—determinism, Pydantic inputs, timeouts, limitations—before opening up to advanced patterns of parallelism and continuation. The last three covered retry policies, the requirement for async I/O, and observability with OpenTelemetry.

The platform is in Public Preview: APIs and features may evolve. Check the official documentation regularly at docs.mistral.ai/workflows to follow updates.

Key takeaways

  • Each activity automatically generates OpenTelemetry spans with name, duration, and status
  • Name your activities with the name parameter to facilitate debugging
  • Three methods to access traces, under client.workflows.executions: raw OTel, summary (status, duration, span tree), and detailed events
  • Sampling is controlled by the traceparent parameter of the execution (100% by default; the HTTP header is deprecated)
  • In production, monitor the success rate, duration, and retry rate
  • Standard OTel traces are exportable to Datadog, Grafana, Jaeger, or New Relic

Test your knowledge

Durable workflows: engine rules before production.

1. What does the Mistral Workflows platform guarantee?

Answer: Durability: a workflow survives failures and resumes where it left off—long-duration orchestration becomes reliable by design.

2. Activity, workflow, worker: who does what?

Answer: The activity is the unit of work (external call, computation), the workflow orchestrates activities, the worker executes—the separation that enables resumption and scaling.

3. What does the determinism rule impose?

Answer: Workflow code must be replayable identically: no randomness or direct clock access in orchestration—non-deterministic effects live in activities.

4. How are transient failures handled?

Answer: Via the activity retry policy (attempts, backoff) and timeouts—failure is a normal case, handled declaratively rather than manually.

5. What does OpenTelemetry integration bring?

Answer: Standard observability: execution traces, metrics, and correlation with the rest of your system—essential for operating workflows in production.

Durability, determinism, retry, observability: four rules that hold long orchestrations together—the engine handles the rest.