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

# Metadata & identity

> Attach the agent, user, session, tags, and custom attributes that Surface Area displays and filters on.

Metadata makes a trace findable. Stamp the agent, user, session, tags, and your own attributes on spans, and Surface Area displays them in the dashboard and filters on them.

## Semantic conventions hold the attribute keys

The `tracing.semconv` module holds attribute-key constants in the `gateway.*` namespace. Set them on a span with `set_attribute()`.

```python theme={null}
import gatewaysdk.tracing as tracing

with tracing.trace("checkout", input=cart) as span:
    span.set_attribute(tracing.semconv.USER_ID, "user-123")
    span.set_attribute(tracing.semconv.SESSION_ID, "session-456")
    result = checkout(cart)
    span.set_attribute(tracing.semconv.TRACE_OUTPUT, result)
```

The constants used most often:

| Constant | Attribute key | Use |
| - | - | - |
| `semconv.TRACE_INPUT` | `gateway.trace.input` | Trace-level input |
| `semconv.TRACE_OUTPUT` | `gateway.trace.output` | Trace-level output |
| `semconv.OBSERVATION_INPUT` | `gateway.observation.input` | Span-level input |
| `semconv.OBSERVATION_OUTPUT` | `gateway.observation.output` | Span-level output |
| `semconv.USER_ID` | `gateway.user.id` | Link the trace to an end user |
| `semconv.SESSION_ID` | `gateway.session.id` | Group traces into a session |
| `semconv.TRACE_TAGS` | `gateway.trace.tags` | Tags for filtering (JSON array) |

<Info>
  Setting `input=` on a `tracing.trace(...)` block sets `TRACE_INPUT` for you, and `input=` on `tracing.span(...)` sets `OBSERVATION_INPUT`. Set the output constant explicitly inside the block.
</Info>

## Declare the agent once

One process runs one agent. Pass it to `tracing.init(agent=...)` and the SDK stamps every trace with the agent. You never hand-stamp the agent per span.

```python theme={null}
import gatewaysdk.tracing as tracing

tracing.init(agent="checkout-agent")
```

Pass a dict (or an `AgentIdentity`) to add a name and version alongside the id.

```python theme={null}
tracing.init(agent={"id": "checkout-agent", "name": "Checkout Agent", "version": "1.4.0"})
```

The agent also resolves from the environment when you do not pass it: `GATEWAY_AGENT_ID`, `GATEWAY_AGENT_NAME`, and `GATEWAY_AGENT_VERSION`. Declaring an agent also registers it with the Surface Area control plane in the background, so it appears on the Agents page before its first trace.

| Constant | Attribute key | Set by |
| - | - | - |
| `semconv.AGENT_ID` | `gateway.agent.id` | `tracing.init(agent=...)` or `GATEWAY_AGENT_ID` |
| `semconv.AGENT_NAME` | `gateway.agent.name` | The agent dict/identity or `GATEWAY_AGENT_NAME` |
| `semconv.AGENT_VERSION` | `gateway.agent.version` | The agent dict/identity or `GATEWAY_AGENT_VERSION` |

## Attach the user

Call `gatewaysdk.identify()` to attribute the current trace, and every span that runs after it in the same context, to an end user. One call from your request handler tags the whole request.

```python theme={null}
import gatewaysdk
import gatewaysdk.tracing as tracing

tracing.init(service_name="support-bot")

with tracing.trace("turn", input=question) as span:
    gatewaysdk.identify("user-123")  # Tags this trace and the spans after it
    answer = run_agent(question)
    span.set_attribute(tracing.semconv.TRACE_OUTPUT, answer)
```

Pass profile fields alongside the id to enrich the user record. Surface Area merges the attributes into the user's profile in the background, which suits slow-changing facts like plan or account value.

```python theme={null}
import gatewaysdk

gatewaysdk.identify(
    "user-123",
    display_name="Ada Lovelace",
    email="ada@example.com",
    attributes={"plan": "enterprise", "account_value": "48000"},
)
```

<Info>
  The profile update needs `GATEWAY_HOST`, `GATEWAY_PUBLIC_KEY`, and `GATEWAY_SECRET_KEY` to reach the platform. Trace attribution (tagging spans with the user id) works without them.
</Info>

### Set the user without identify()

The user is also ambient: it resolves from the `GATEWAY_USER_ID` environment variable and is stamped on every span. Set the `USER_ID` attribute on a span to override the ambient default for that span.

```python theme={null}
import gatewaysdk.tracing as tracing

with tracing.trace("turn", input=question) as span:
    span.set_attribute(tracing.semconv.USER_ID, "user-123")  # Overrides ambient
    ...
```

<Info>
  Set `GATEWAY_USER_ID` for single-user processes: a CLI or a per-user worker. For a multi-tenant service, leave it unset and call `identify()` (or set the `USER_ID` attribute) per request from your auth context.
</Info>

## Tag traces and set environment

Tags label traces for filtering. Set them across a block with `tracing.tracing_context(tags=[...])`, which applies to every span created inside, including auto-instrumented LLM calls.

```python theme={null}
import gatewaysdk.tracing as tracing

with tracing.tracing_context(tags=["beta", "high-priority"]):
    run_agent(query)  # Every span in here carries both tags
```

Set the environment label at init so it applies to the whole process, or per block with `tracing_context(environment=...)`.

```python theme={null}
tracing.init(service_name="my-agent", environment="production")
```

The environment label lands on the trace's resource attributes and powers the Environment filter in the sessions and traces tables.

## Add custom metadata

Set any key/value attribute directly on a span for data the built-in constants do not cover.

```python theme={null}
import gatewaysdk.tracing as tracing

with tracing.span("retrieve") as s:
    s.set_attribute("retriever.index", "products-v3")
    s.set_attribute("retriever.top_k", 5)
```

You can also pass extra attributes as keyword arguments when you open a `span` or `tool`.

```python theme={null}
with tracing.span("retrieve", index="products-v3", top_k=5) as s:
    docs = retrieve(query)
```

<Info>
  To make custom metadata filterable in the sessions and traces tables, prefix the key with `gateway.trace.metadata.`, since Surface Area hoists prefixed attributes into trace metadata that the tables query. For example, `s.set_attribute("gateway.trace.metadata.tenant", "acme")` adds a filterable `tenant` field.
</Info>

## Screenshots on spans

A span can carry an image: upload the bytes and put the returned reference into the span's input
or output. The trace view shows it inline.

```python theme={null}
import gatewaysdk.tracing as tracing
from gatewaysdk.tracing.media import MediaUploader

media = MediaUploader.from_env()   # GATEWAY_HOST / GATEWAY_PUBLIC_KEY / GATEWAY_SECRET_KEY

with tracing.tool("render-chart", input={"series": "weekly"}) as span:
    png = render_chart()
    context = span.get_span_context()
    reference = media.upload(
        png,
        content_type="image/png",
        trace_id=format(context.trace_id, "032x"),
        observation_id=format(context.span_id, "016x"),
        field="output",
    )
    span.set_attribute(tracing.semconv.OBSERVATION_OUTPUT, str(reference))
```

The reference looks like `@@@langfuseMedia:type=image/png|id=…|source=bytes@@@`. TypeScript has the
same `MediaUploader` in `@withgateway/sdk/tracing`.

[Native computer use](/worlds/computer-use) does this for you: each computer action is a
`computer.<action>` span whose output holds the screenshot after it.

## Where to go next

* [Sessions & traces](./sessions): how the session id groups traces.
* [Auto-instrumentation](./auto-instrumentation): tag the LLM calls Surface Area captures for you.
* [Export to Surface Area](./export): where these spans go and how they are batched.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.