> This page is for version 0.3.0.
> For other versions, use one of these documentation indexes:
> - Main (default): https://docs.nvidia.com/nemo/gym/main/llms.txt
> - 0.7.0: https://docs.nvidia.com/nemo/gym/v0.7.0/llms.txt
> - 0.6.0: https://docs.nvidia.com/nemo/gym/v0.6.0/llms.txt
> - 0.5.1: https://docs.nvidia.com/nemo/gym/v0.5.1/llms.txt
> - 0.5.0: https://docs.nvidia.com/nemo/gym/v0.5.0/llms.txt
> - 0.4.0: https://docs.nvidia.com/nemo/gym/v0.4.0/llms.txt
> - 0.3.0: https://docs.nvidia.com/nemo/gym/v0.3.0/llms.txt
> - 0.2.1: https://docs.nvidia.com/nemo/gym/v0.2.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/gym/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/gym/_mcp/server.

# nemo_gym.trajectory.tool_timing

Normalize adapter evidence into attempt-owned tool measurements and exact capture joins.

## Module Contents

### Functions

| Name                                                                                        | Description                                                                               |
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [`_import_tool_observations`](#nemo_gym-trajectory-tool_timing-_import_tool_observations)   | Import the complete raw span namespace once, before generating any diagnostics.           |
| [`_matches_reference`](#nemo_gym-trajectory-tool_timing-_matches_reference)                 | -                                                                                         |
| [`_unresolved_gap`](#nemo_gym-trajectory-tool_timing-_unresolved_gap)                       | Retain an unattributable diagnostic without asserting a canonical span reference.         |
| [`normalize_operation_timing`](#nemo_gym-trajectory-tool_timing-normalize_operation_timing) | Salvage usable native measurements without clamping invalid endpoints or inventing time.  |
| [`normalize_tool_calls`](#nemo_gym-trajectory-tool_timing-normalize_tool_calls)             | Resolve one record per attempt after capture merge, retaining uncertainty as scoped gaps. |
| [`tool_attempt_id`](#nemo_gym-trajectory-tool_timing-tool_attempt_id)                       | Derive a replay-stable ID from persisted operation identity, never timestamp or content.  |
| [`tool_snapshot_advances`](#nemo_gym-trajectory-tool_timing-tool_snapshot_advances)         | A more definite outcome can replace a partial snapshot, never the reverse.                |
| [`tool_snapshot_output`](#nemo_gym-trajectory-tool_timing-tool_snapshot_output)             | Read one explicitly linked output from the same source snapshot as the tool.              |

### API

```python
nemo_gym.trajectory.tool_timing._import_tool_observations(
    observations: collections.abc.Iterable[nemo_gym.trajectory.trajectory_record.ToolCallObservation],
    observation_gaps: collections.abc.Iterable[nemo_gym.trajectory.trajectory_record.ObservationGap],
    capture_key: str,
    producer_id: str,
    invocations: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.AgentInvocation],
    observation_invocations: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.AgentInvocation]
) -> tuple[list[nemo_gym.trajectory.trajectory_record.TrajectoryToolCall], list[nemo_gym.trajectory.trajectory_record.ObservationGap], set[str]]
```

Import the complete raw span namespace once, before generating any diagnostics.

Canonical tools already own qualified IDs. Only raw observations participate in
raw-gap lookup; model/failure IDs and conversation call references are untouched.
Gaps carry no producer ID, so an ambiguous local reference stays explicitly unresolved.

```python
nemo_gym.trajectory.tool_timing._matches_reference(
    call: nemo_gym.trajectory.trajectory_record.TrajectoryModelCall,
    reference: nemo_gym.trajectory.trajectory_record.ModelCallRef
) -> bool
```

```python
nemo_gym.trajectory.tool_timing._unresolved_gap(
    gap: nemo_gym.trajectory.trajectory_record.ObservationGap,
    reason: str
) -> nemo_gym.trajectory.trajectory_record.ObservationGap
```

Retain an unattributable diagnostic without asserting a canonical span reference.

```python
nemo_gym.trajectory.tool_timing.normalize_operation_timing(
    boundary: nemo_gym.core.operation_timing.TimingBoundary,
    timing_source: str,
    started_at: object = None,
    completed_at: object = None,
    duration_ms: object = None,
    duration_source: str | None = None,
    clock_domain: str | None = None
) -> tuple[nemo_gym.core.operation_timing.OperationTiming | None, list[str]]
```

Salvage usable native measurements without clamping invalid endpoints or inventing time.

```python
nemo_gym.trajectory.tool_timing.normalize_tool_calls(
    tools: collections.abc.Iterable[nemo_gym.trajectory.trajectory_record.ToolCallObservation],
    capture_key: str,
    producer_id: str,
    model_calls: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.TrajectoryModelCall],
    invocations: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.AgentInvocation],
    failure_events: collections.abc.Mapping[str, object] | None = None,
    observation_gaps: collections.abc.Iterable[nemo_gym.trajectory.trajectory_record.ObservationGap] = (),
    observation_invocations: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.AgentInvocation] | None = None
) -> tuple[list[nemo_gym.trajectory.trajectory_record.TrajectoryToolCall], list[nemo_gym.trajectory.trajectory_record.ObservationGap]]
```

Resolve one record per attempt after capture merge, retaining uncertainty as scoped gaps.

Input-only producer identity/message/model references stay in bundles for replay. Native
call IDs verify an exact model reference; by themselves they never select a model attempt.
Pass raw bundle diagnostics via observation_gaps so they cross the same import boundary.
Returned span-scoped gaps resolve to retained tools; unresolved references keep their raw
span and reason in detail without claiming a canonical span. Canonical input gaps stay
with their trajectory and do not cross this raw import boundary again.

```python
nemo_gym.trajectory.tool_timing.tool_attempt_id(
    producer_id: str,
    invocation_id: str,
    operation_key: str
) -> str
```

Derive a replay-stable ID from persisted operation identity, never timestamp or content.

```python
nemo_gym.trajectory.tool_timing.tool_snapshot_advances(
    previous: str,
    incoming: str
) -> bool
```

A more definite outcome can replace a partial snapshot, never the reverse.

```python
nemo_gym.trajectory.tool_timing.tool_snapshot_output(
    tool: nemo_gym.trajectory.trajectory_record.ToolCallObservation,
    invocations: collections.abc.Sequence[nemo_gym.trajectory.trajectory_record.AgentInvocation]
) -> typing.Any | None
```

Read one explicitly linked output from the same source snapshot as the tool.