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

# Identity helpers

> Late-bind user_id, session_id, and tags onto open spans, or scope them per-task. Advanced.

These helpers set `user_id`, `session_id`, and `tags` after a span is already open, or scope them to one task. The cleanest place to set identity is the getter callables on [`bento.init()`](/python/configuration#identity-getters), which fire on every span, including ones captured by an [integration](/python/integrations). The helpers here cover the cases those getters don't fit:

* Identity becomes known mid-flow, such as after authentication inside an open trajectory.
* A worker receives identity over a queue and the value should scope to one handler invocation.
* An arbitrary attribute belongs on the currently active span without restructuring the call site.

## update\_current\_trace

Patches the open trajectory's root span. Applies when identity becomes known mid-flow and belongs at the trace level (`traces.user_id`, `traces.session_id`, `traces.tags`).

```python theme={null}
import bentolabs_sdk as bento

with bento.begin(event="user_turn") as interaction:
    user_id, session_id = await authenticate(request)
    bento.update_current_trace(user_id=user_id, session_id=session_id)
    # rest of the work
```

The keys are SDK-defined (`gen_ai.user.id`, `gen_ai.conversation.id`, `langfuse.tags`); the values come from the call.

<ParamField path="user_id" type="str">
  Sets `gen_ai.user.id` on the trajectory root.
</ParamField>

<ParamField path="session_id" type="str">
  Sets `gen_ai.conversation.id` on the trajectory root.
</ParamField>

<ParamField path="tags" type="list[str]">
  Sets `langfuse.tags` on the trajectory root.
</ParamField>

<ParamField path="properties" type="dict">
  Arbitrary custom dimensions, written through `_coerce_property_value` so types are preserved.
</ParamField>

<Info>
  No-op when no `bento.begin(...)` is open in the current task. For bare `track_ai` calls, set the values directly on `track_ai` instead.
</Info>

## update\_current\_span

Sets attributes on the currently active OTel span, the innermost open span, which may or may not be the trajectory root. Applies when a property belongs on the inner span and there's no direct handle to it, such as deep helper functions or callbacks invoked by an integration.

```python theme={null}
with bento.tool_span("web_search") as ts:
    results = run_search(query)
    bento.update_current_span(properties={"result_count": len(results), "cache_hit": False})
```

It reads OTel's current context, so it sees integration-captured spans too. Called inside a callback that ADK invokes, it writes the property onto that span.

<ParamField path="properties" type="dict">
  Keys/values written to the currently active span. Same type-fidelity rules as `track_ai(properties=...)`; see [Properties](/python/properties#type-fidelity).
</ParamField>

## propagate\_attributes

Scopes an identity override to the current task. Per-task overrides take precedence over the global getters registered at `init()`. Applies when the framework-state pattern doesn't fit, such as a worker that received identity over a message queue and scopes it to one handler invocation.

```python theme={null}
with bento.propagate_attributes(user_id="u_42", session_id="conv_99"):
    await agent.run(query)        # every span tagged automatically
```

The block restores the prior values on exit, and identity sources registered at `init()` resume after it.

### Three-value kwargs

Each kwarg accepts three distinct values:

| Pass                            | Effect                                                                                      |
| ------------------------------- | ------------------------------------------------------------------------------------------- |
| *(omitted)*                     | Inherit from any outer `propagate_attributes` scope or the global source.                   |
| A value (e.g. `user_id="u_42"`) | Use this value for the scope.                                                               |
| Explicit `None`                 | Clear the field for the scope, shadowing any outer override and skipping the global source. |

## How they compose

The SDK resolves identity in this order, highest priority first:

1. **Explicit kwarg on the call**: `bento.track_ai(user_id="x", ...)` or `bento.begin(user_id="x", ...)` always wins.
2. **`update_current_trace(...)`** patches set on the trajectory root.
3. **`propagate_attributes(...)`** scope active in the current task.
4. **Init-time getters**: `bento.init(user_id=lambda: ...)` resolved per span.

The first source that returns a non-`None` value at span-emit time is the one that lands in the dashboard.

## See also

<CardGroup cols={2}>
  <Card title="Integrations" icon="plug" href="/python/integrations">
    Identity getters at `init()`, the zero-code path.
  </Card>

  <Card title="Trajectories" icon="diagram-project" href="/python/trajectories">
    Open a `begin()` block to host `update_current_trace` patches.
  </Card>
</CardGroup>
