> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiddenlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agentic Sessions

Agentic Runtime Security reconstructs the many individual evaluations of an agent run into one ordered, replayable session. This page explains why that matters, the identifiers that carry the correlation, and how reconstruction works.

***

## Why it matters

One agent run is many model calls. The agent loops: it calls the model, the model asks to use a tool, the tool runs, the model is called again with the result, and so on until it produces a final answer. One goal can be many model calls, often across multiple turns and tool steps.

Evaluated in isolation, those calls are disconnected events. Reconstruction stitches them into one ordered, replayable session you can investigate and write policy against: the whole conversation, in order, with each turn's request and response paired.

## The correlation identifiers

Reconstruction is driven by a small set of headers carried on each evaluation call.

| Header                  | Scope                           | Purpose                                                                                                                   |
| ----------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `HL-Runtime-Session-Id` | One per workflow / conversation | The stable key everything is grouped by. The application must generate it once and propagate it through every model call. |
| `HL-Roundtrip-Id`       | One per turn                    | Pairs a single request with its response. Whatever makes the call can mint this automatically at the point of the call.   |
| `HL-Requester-Id`       | The caller                      | Identifies the calling application or component.                                                                          |

These play two distinct roles:

**Grouping a workflow's calls together (the session id).** Set the same `HL-Runtime-Session-Id` on every call that belongs to one piece of work, including calls made inside tools, sub-agents, retries, and parallel branches. Nothing in the system can infer that two separate calls are part of the same workflow, so your application generates the session id once when the workflow begins and passes it on every call. If a tool or sub-agent drops it, those calls split off into a separate, fragmented session.

**Pairing each request with its response (the roundtrip id).** It only needs to match across the two halves of a single call, a request and its response. Whatever makes that call (your code, an AI gateway, or a framework guardrail) has both halves in hand, so it can put the same roundtrip id on each without tracking anything across the wider workflow. HiddenLayer integrations set these headers for you (see [How integrations help](#how-integrations-help) below). What they cannot do is decide which calls belong to the same workflow; that is the session id, which only your application can supply.

## Reconstructing an agentic session

Reconstruction starts with how you instrument your application. To successfully reconstruct a session, there are three things to get right:

1. When the user's request begins, create one session id.
2. Propagate that same session id on every model call for the request, including calls made inside tools, sub-agents, retries, and parallel branches.
3. Pair each turn's request evaluation and response evaluation with the same roundtrip id.

How to set each identifier with the Python SDK:

* **Session id**: pass `hl_runtime_session_id=session_id` to `evaluate_request(...)` and `evaluate_response(...)`. For `evaluate_interaction(...)`, set it as `metadata.external_session_id`.
* **Roundtrip id**: there is no named keyword argument. Pass it via `extra_headers={"HL-Roundtrip-Id": roundtrip_id}` on both the request and response evaluation of a turn. (If you evaluate a whole turn at once with `evaluate_interaction`, you don't need a roundtrip id.)

Once those interactions are stored, HiddenLayer reconstructs the session automatically: it **groups** every interaction by session id (gathering all the turns of one conversation), **orders** them by the timestamp recorded for each message (putting the conversation back in sequence), and **pairs** each request with its response by roundtrip id (forming each turn). You do not configure this; your part is tagging each call with the session id.

```mermaid theme={null}
flowchart TD
    E["Individual request/response evaluations"] --> G["Group by HL-Runtime-Session-Id"]
    G --> P["Pair by HL-Roundtrip-Id"]
    P --> R["Reconstructed session (ordered, replayable, turn by turn)"]
```

The result is a clean, ordered, turn-by-turn replay: the user prompts, the model's tool calls and results, and the final answers, with any blocked or redacted moments marked in place. The application never sends a transcript; the session is rebuilt entirely from the correlation identifiers tagged onto each call. You retrieve and replay a reconstructed session in the Console, filtered by the same session id the application emitted.

### Example: a deep-research assistant

Consider a deep-research assistant, in the style of open-source agents like GPT Researcher: a lead agent breaks a question into subtopics, spawns a research sub-agent for each one (every sub-agent makes its own model calls), then writes a final report from their findings. One user request becomes many model calls spread across the lead and several sub-agents. They all share **one session id**, and each turn gets **its own roundtrip id**. The crucial point: each sub-agent reuses the **same** session id its lead created.

The example below is simplified pseudocode. `call_model` marks where the call to the model provider is made; block and redact handling is omitted, and the lead uses a fixed list of subtopics rather than generating them.

```python theme={null}
import uuid

from hiddenlayer import HiddenLayer

client = HiddenLayer()

def call_model(request_payload):
    """Where the call to the model provider is made.

    Takes a request payload and returns the provider's response payload.
    """
    ...

def evaluate_turn(session_id, request_payload):
    """One turn: evaluate the request, call the model, evaluate the response."""
    roundtrip_id = str(uuid.uuid4())  # one per turn; pairs the request with its response

    # Evaluate the request before the model runs. The call returns the request
    # payload to forward (possibly redacted), or a block message if policy blocks it.
    safe_request = client.runtime.evaluate_request(
        body=request_payload,
        hl_runtime_session_id=session_id,
        extra_headers={"HL-Roundtrip-Id": roundtrip_id},
    )
    # Handle BLOCK / REDACT here; omitted for brevity. Forward `safe_request`,
    # which is the (possibly modified) request payload, not an SDK wrapper object.

    model_response = call_model(safe_request)

    # Evaluate the response, reusing the SAME roundtrip id so the two halves pair up.
    safe_response = client.runtime.evaluate_response(
        body=model_response,
        hl_runtime_session_id=session_id,
        extra_headers={"HL-Roundtrip-Id": roundtrip_id},
    )
    # Handle BLOCK / REDACT here too; omitted for brevity.
    return safe_response

def research_subagent(session_id, subtopic):
    """A sub-agent that makes its own model call; it MUST reuse the same session id."""
    return evaluate_turn(session_id, {
        "model": "gpt-4o",
        "messages": [{"role": "user", "content": f"Research this subtopic and summarize findings: {subtopic}"}],
    })

def handle_research_request(question):
    # One session id for the whole research run, created once.
    session_id = str(uuid.uuid4())

    # Turn 1: the lead agent plans the work (same session id).
    evaluate_turn(session_id, {
        "model": "gpt-4o",
        "messages": [{"role": "user", "content": f"Plan the research for: {question}"}],
    })

    # Each sub-agent makes its own model call, all under the SAME session id.
    subtopics = ["background", "current state", "risks"]
    findings = [research_subagent(session_id, subtopic) for subtopic in subtopics]

    # Final turn: the lead composes the report from the findings, still the same session id.
    return evaluate_turn(session_id, {
        "model": "gpt-4o",
        "messages": [{"role": "user", "content": f"Write a report on {question} from: {findings}"}],
    })
```

The takeaway: the session id is the value you must carry everywhere, including into every sub-agent; the roundtrip id just pairs a single turn.

## How integrations help

HiddenLayer integrations help carry the correlation headers, reducing the custom plumbing you write to get reconstructed agentic sessions. Your application still owns propagating the session id across tools and sub-agents; integrations can't infer which calls belong to the same workflow. See [Integrations](/docs/products/runtime/agentic/integrations) and [Get Started](/docs/products/runtime/agentic/get_started).

## Next

<Columns cols={1}>
  <Card title="Policy" href="/docs/products/runtime/agentic/policy">
    Author CEL detection rules and policies in the Console.
  </Card>
</Columns>
