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

# Monitoring (tai42_contract.monitoring)

> The MonitoringReader and MonitoringWriter protocols.

The read/query half of the monitoring contract.

Implement only if the backend has a read API. A read-incapable backend still
exposes a `reader` object, but its methods raise `MonitoringReadNotSupportedError`
on call (never a silent no-op).

OpenTelemetry has no read/query standard — each backend invents its own read
surface — so this face is the abstraction's real value.

## MonitoringReader

`tai42_contract.monitoring.reader.MonitoringReader`

```python theme={null}
class MonitoringReader(Protocol)
```

Query totals/analytics and the runtime span window.

### Members

#### query\_metrics

`tai42_contract.monitoring.reader.MonitoringReader.query_metrics`

```python theme={null}
async query_metrics(self, filter: MetricsFilter) -> MetricsResult
```

Totals / analytics screen — server-side aggregation.

Counts (runs, tags), total cost, total tokens, latency, grouped by the
requested dimensions.

**Parameters**

| Parameter | Type            | Default | Description |
| --------- | --------------- | ------- | ----------- |
| `filter`  | `MetricsFilter` | —       | —           |

#### list\_spans\_in\_window

`tai42_contract.monitoring.reader.MonitoringReader.list_spans_in_window`

```python theme={null}
async list_spans_in_window(
    self,
    t0: datetime,
    t1: datetime,
    *,
    run: str | None = None,
    kind: SpanKind | None = None,
    filter: MonitoringFilter | None = None,
    order_by: OrderBy | None = None,
) -> list[SpanWindowItem]
```

The smallest spans that ran in the half-open window `[t0, t1)`.

CONTRACT GUARANTEE: exactly one item per tool/node execution in the
window. Every reader performs this tool-granularity selection — it is
not an optional filter.

`run` scopes to a single run (one trace); `None` = all runs in the
window. `kind` narrows WITHIN the tool-granularity set (never widens
or overrides the guarantee). `filter` applies the neutral
`MonitoringFilter` clauses (tags live here, not as a discrete param).
`order_by` sorts the result; omitted ⇒ newest-first (`start` desc).
An unsupported `order_by.field` or filter clause raises
`MonitoringReadNotSupportedError`.

**Parameters**

| Parameter  | Type                       | Default | Description |
| ---------- | -------------------------- | ------- | ----------- |
| `t0`       | `datetime`                 | —       | —           |
| `t1`       | `datetime`                 | —       | —           |
| `run`      | `str \| None`              | `None`  | —           |
| `kind`     | `SpanKind \| None`         | `None`  | —           |
| `filter`   | `MonitoringFilter \| None` | `None`  | —           |
| `order_by` | `OrderBy \| None`          | `None`  | —           |

#### get\_trace

`tai42_contract.monitoring.reader.MonitoringReader.get_trace`

```python theme={null}
async get_trace(self, trace_id: str) -> MonitoringTrace
```

Fetch one COMPLETE trace (top-level attrs + every observation, with
full input/output) for replay / evaluation / normalization.

Always returns a trace or raises — never `None`. An absent trace
raises `TraceNotFoundError`; a transient/backend failure (e.g. a timeout)
propagates its error so the caller sees the failure and may retry.
Distinct from `list_spans_in_window` (trimmed dashboard units).

**Parameters**

| Parameter  | Type  | Default | Description |
| ---------- | ----- | ------- | ----------- |
| `trace_id` | `str` | —       | —           |

#### list\_traces

`tai42_contract.monitoring.reader.MonitoringReader.list_traces`

```python theme={null}
async list_traces(
    self,
    *,
    from_timestamp: datetime | None = None,
    to_timestamp: datetime | None = None,
    limit: int | None = None,
    page: int | None = None,
    filter: MonitoringFilter | None = None,
    order_by: OrderBy | None = None,
) -> list[MonitoringTrace]
```

List complete traces (each with its full observation set) matching
the filters. An individual trace that cannot be fetched is kept in place
with `fetch_error` set (not skipped), so one bad trace neither fails
the batch nor silently vanishes.

`from_timestamp` / `to_timestamp` bound the trace timestamp to the
half-open window `[from, to)`; either may be omitted for an open end.
`filter` applies the neutral `MonitoringFilter` clauses (tags live
here, not as a discrete param). `order_by` sorts the result; omitted ⇒
newest-first (`timestamp` desc). Sortable fields: `timestamp`,
`total_cost`, `name`, `id`, `latency`, `total_tokens` — of
these `total_cost` / `latency` / `total_tokens` rank globally (see
`OrderBy`); the rest sort natively. An unsupported `order_by.field`
or filter clause raises `MonitoringReadNotSupportedError`.

**Parameters**

| Parameter        | Type                       | Default | Description |
| ---------------- | -------------------------- | ------- | ----------- |
| `from_timestamp` | `datetime \| None`         | `None`  | —           |
| `to_timestamp`   | `datetime \| None`         | `None`  | —           |
| `limit`          | `int \| None`              | `None`  | —           |
| `page`           | `int \| None`              | `None`  | —           |
| `filter`         | `MonitoringFilter \| None` | `None`  | —           |
| `order_by`       | `OrderBy \| None`          | `None`  | —           |

## MonitoringWriter

`tai42_contract.monitoring.writer.MonitoringWriter`

```python theme={null}
class MonitoringWriter(Protocol)
```

Emit traces/spans/events, supply callbacks, and manage lifecycle.

Every backend implements this face.

### Members

#### start\_span

`tai42_contract.monitoring.writer.MonitoringWriter.start_span`

```python theme={null}
start_span(
    self,
    *,
    name: str,
    kind: SpanKind,
    trace_context: TraceContext | None = None,
    input: Any = None,
    model: str | None = None,
    model_parameters: dict[str, Any] | None = None,
    metadata: dict[str, Any] | None = None,
) -> AbstractContextManager[Span]
```

Open a span for the duration of the block, yielding a `Span` handle.

`model` / `model_parameters` carry GENERATION-span detail.
`model_parameters` is open-time only (known before the call);
`model` and `usage_details` can be amended later via the handle's
`Span.update`. A no-op under an active `disable()` block.

**Parameters**

| Parameter          | Type                     | Default | Description |
| ------------------ | ------------------------ | ------- | ----------- |
| `name`             | `str`                    | —       | —           |
| `kind`             | `SpanKind`               | —       | —           |
| `trace_context`    | `TraceContext \| None`   | `None`  | —           |
| `input`            | `Any`                    | `None`  | —           |
| `model`            | `str \| None`            | `None`  | —           |
| `model_parameters` | `dict[str, Any] \| None` | `None`  | —           |
| `metadata`         | `dict[str, Any] \| None` | `None`  | —           |

#### record\_span

`tai42_contract.monitoring.writer.MonitoringWriter.record_span`

```python theme={null}
record_span(
    self,
    *,
    name: str,
    kind: SpanKind,
    start: datetime,
    end: datetime,
    trace_context: TraceContext,
    input: Any = None,
    output: Any = None,
    level: MonitoringLevel | None = None,
    status_message: str | None = None,
    model: str | None = None,
    usage_details: dict[str, Any] | None = None,
    metadata: dict[str, Any] | None = None,
) -> None
```

Record an already-closed span with EXPLICIT `start` / `end` times.

For a span whose execution window cannot be timed live in-process —
work that ran across a pause and is reported afterwards. `start` /
`end` are wall-clock `datetime`; the backend converts as needed.
`trace_context.trace_id` is REQUIRED — the explicit-time path has no
ambient context to fall back to, so a missing `trace_id` RAISES
(a caller bug); `parent_span_id` nests the span. Backend emission
failures are caught + logged like the other emit methods.

**Parameters**

| Parameter        | Type                      | Default | Description |
| ---------------- | ------------------------- | ------- | ----------- |
| `name`           | `str`                     | —       | —           |
| `kind`           | `SpanKind`                | —       | —           |
| `start`          | `datetime`                | —       | —           |
| `end`            | `datetime`                | —       | —           |
| `trace_context`  | `TraceContext`            | —       | —           |
| `input`          | `Any`                     | `None`  | —           |
| `output`         | `Any`                     | `None`  | —           |
| `level`          | `MonitoringLevel \| None` | `None`  | —           |
| `status_message` | `str \| None`             | `None`  | —           |
| `model`          | `str \| None`             | `None`  | —           |
| `usage_details`  | `dict[str, Any] \| None`  | `None`  | —           |
| `metadata`       | `dict[str, Any] \| None`  | `None`  | —           |

#### create\_event

`tai42_contract.monitoring.writer.MonitoringWriter.create_event`

```python theme={null}
create_event(
    self,
    *,
    name: str,
    level: MonitoringLevel = DEFAULT_LEVEL,
    trace_context: TraceContext | None = None,
    input: Any = None,
    output: Any = None,
    status_message: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> None
```

Record a point-in-time event, carrying `input` / `output`.

**Parameters**

| Parameter        | Type                     | Default         | Description |
| ---------------- | ------------------------ | --------------- | ----------- |
| `name`           | `str`                    | —               | —           |
| `level`          | `MonitoringLevel`        | `DEFAULT_LEVEL` | —           |
| `trace_context`  | `TraceContext \| None`   | `None`          | —           |
| `input`          | `Any`                    | `None`          | —           |
| `output`         | `Any`                    | `None`          | —           |
| `status_message` | `str \| None`            | `None`          | —           |
| `metadata`       | `dict[str, Any] \| None` | `None`          | —           |

#### update\_current\_span

`tai42_contract.monitoring.writer.MonitoringWriter.update_current_span`

```python theme={null}
update_current_span(
    self,
    *,
    level: MonitoringLevel | None = None,
    status_message: str | None = None,
    metadata: dict[str, Any] | None = None,
    output: Any = None,
) -> None
```

Mutate the CURRENT (ambient) span without opening a new one.

Targets the span opened by the nearest enclosing `start_span` block
(or the callback-handler span if none). Per-generation token/cost is
recorded via the held `Span.update` handle, not here.

**Parameters**

| Parameter        | Type                      | Default | Description |
| ---------------- | ------------------------- | ------- | ----------- |
| `level`          | `MonitoringLevel \| None` | `None`  | —           |
| `status_message` | `str \| None`             | `None`  | —           |
| `metadata`       | `dict[str, Any] \| None`  | `None`  | —           |
| `output`         | `Any`                     | `None`  | —           |

#### trace\_attributes

`tai42_contract.monitoring.writer.MonitoringWriter.trace_attributes`

```python theme={null}
trace_attributes(
    self,
    *,
    name: str | None = None,
    tags: list[str] | None = None,
    metadata: dict[str, Any] | None = None,
) -> AbstractContextManager[None]
```

Set trace-level name/tags/metadata on the ambient trace for the block.

**Parameters**

| Parameter  | Type                     | Default | Description |
| ---------- | ------------------------ | ------- | ----------- |
| `name`     | `str \| None`            | `None`  | —           |
| `tags`     | `list[str] \| None`      | `None`  | —           |
| `metadata` | `dict[str, Any] \| None` | `None`  | —           |

#### current\_trace\_id

`tai42_contract.monitoring.writer.MonitoringWriter.current_trace_id`

```python theme={null}
current_trace_id(self) -> str | None
```

The active ambient trace id, or `None` if no trace is active.

Used to gate conditional emits (`if writer.current_trace_id():`) so
none fire outside a trace.

#### inject\_context

`tai42_contract.monitoring.writer.MonitoringWriter.inject_context`

```python theme={null}
inject_context(self, ctx: TraceContext) -> dict[str, Any]
```

Build the opaque downstream-propagation blob merged into the
langgraph `RunnableConfig`. Carries `ctx.tags` + `ctx.metadata`.

**Parameters**

| Parameter | Type           | Default | Description |
| --------- | -------------- | ------- | ----------- |
| `ctx`     | `TraceContext` | —       | —           |

#### get\_monitoring\_callbacks

`tai42_contract.monitoring.writer.MonitoringWriter.get_monitoring_callbacks`

```python theme={null}
get_monitoring_callbacks(self, ctx: TraceContext) -> list[object]
```

The LangChain/LangGraph callback handlers appended to the langgraph
`config["callbacks"]`. The caller builds `ctx` (keeping the
auto-generated-trace-id fallback); the impl reads its fields to
construct the vendor handler.

**Parameters**

| Parameter | Type           | Default | Description |
| --------- | -------------- | ------- | ----------- |
| `ctx`     | `TraceContext` | —       | —           |

#### scope

`tai42_contract.monitoring.writer.MonitoringWriter.scope`

```python theme={null}
scope(self, public_key: str) -> AbstractContextManager[None]
```

Bind tracing to a project (`public_key`) for the block.

Mirrors a single-backend multi-project switch. OTel-native backends
may no-op it; a real failure must raise (cross-project leak risk).

**Parameters**

| Parameter    | Type  | Default | Description |
| ------------ | ----- | ------- | ----------- |
| `public_key` | `str` | —       | —           |

#### disable

`tai42_contract.monitoring.writer.MonitoringWriter.disable`

```python theme={null}
disable(self) -> AbstractContextManager[None]
```

Suppress emission within the block.

The backend's tracing-producing methods (and the handlers from
`get_monitoring_callbacks`) MUST honor this. A backend that never
emits may no-op it; an emitting backend may not.

#### flush

`tai42_contract.monitoring.writer.MonitoringWriter.flush`

```python theme={null}
flush(self) -> None
```

Flush any buffered telemetry.

#### shutdown

`tai42_contract.monitoring.writer.MonitoringWriter.shutdown`

```python theme={null}
shutdown(self) -> None
```

Tear the client down so the next use rebuilds clean (fork-safety).

Must fully evict the underlying client, not merely flush — a forked
child inherits dead background threads otherwise.
