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

# Contributing

## Development Setup

```bash
git clone <repo-url>
cd lens
pip install --group dev -e .
pre-commit install
```

The `dev` dependency group includes the OTel SDK, pytest, pytest-cov, pre-commit, and Ruff.

## Code Style

* **Line length**: 100 (ruff-enforced)
* **Python**: 3.12+ (use `X | Y`, not `Optional`)
* **Ruff rules**: E, W, F, I (isort), UP, B, SIM, TCH (with TC002/TC003 ignored for runtime imports)
* **Double quotes** for strings

Run the following commands:

```bash
ruff check src tests --fix
ruff format src tests
```

Pre-commit runs both checks automatically on staged files. Continuous integration enforces linting using `pre-commit run --all-files`. Ruff is pinned to version 0.11.5 in `.pre-commit-config.yaml`, so running `pre-commit run --all-files` locally is the authoritative check. Running `ruff` directly is for convenience and might use a different version of Ruff.

## Defer Heavy Imports

`opentelemetry-sdk` and related packages are expensive to import. Every `opentelemetry.sdk.*` import must be deferred inside the function that requires it and not at the module top level. Most SDK imports are located in `providers.py` (distributed across `build_providers()` and either the `_build_*_exporter` or `_setup_log_provider` helpers), but `sampling.py` and `logging_bridge.py` also import the SDK lazily inside their functions.

This keeps `import nemo.lens` cheap; this is important for consumers that might import `nemo.lens` solely to access the fallback implementations.

## Use Type Hints

Public functions must contain type hints on parameters and return types. Use both `typing` and `__future__.annotations` for forward references. You can use `TYPE_CHECKING` guards for imports that are used only in type hints.

## Avoid Redundant Comments

Prefer the following implementation:

```python
span = tracer.start_span(name)
```

Avoid adding comments that repeat the code:

```python
# Start a span on the tracer
span = tracer.start_span(name)
```

Comments should explain **why** (non-obvious invariants, workarounds, design choices). For most code, good names make comments unnecessary.

## Write Tests Before Implementing Changes

For every change that modifies behavior:

1. Write a failing test.
2. Run the test to confirm that it fails.
3. Implement the change.
4. Run the test to confirm that it passes.
5. Run the full test suite to confirm that other functionality remains intact.

Refer to the [Test](/nemo/lens/developer-guide/test) documentation for fixture patterns and state-isolation requirements.

## Changes to the Public API

The `__all__` list in `__init__.py` defines the public API contract. Before adding or modifying any components in that list, address the following questions:

1. Is the change part of the core value proposition or is it a one-off helper? Keep one-off helpers internal.
2. Does the change have a corresponding test?
3. Does the change have a docstring?
4. Is there a corresponding page in the user guide, or does an existing page contain a description of this component?
5. Is an update to `fallbacks.py` required? Signature changes to `managed_span`, `trace_fn`, `span_cm`, `is_span_group_enabled`, and `safe_set_span_attributes` must be mirrored.

## Changes to `fallbacks.py`

Whenever a signature in `helpers.py`, `state.py`, or wherever else is mirrored in `fallbacks.py` changes, update `fallbacks.py` too, and add a test in `test_fallbacks.py` that exercises the new signature.

## Changes to `semconv`

When adding an attribute name constant:

1. Place the constant in the appropriate namespace, such as `dl.*` or `&lt;project&gt;.*` (refer to the [Semantic Conventions](/nemo/lens/design/semantic-conventions) documentation).
2. Update the stability-marker comment block if the namespace is new.
3. Ensure that the constant is used. Unused constants are considered dead code.

When bumping `SEMCONV_VERSION`:

1. Review the upstream changelog for breaking changes.
2. Update constants that were renamed or removed upstream.
3. Describe these changes in the PR description so that downstream consumers are aware.

## Changes that Affect Consumers

Megatron-LM, NeMo RL, and NeMo Gym all depend on NeMo Lens. Changes that break these consumer repositories are blocking:

* Renaming a public function causes a breaking change.
* Changing `managed_span` to require a new positional argument causes a breaking change.
* Removing a span group causes a breaking change.

For such changes, coordinate with the consumer repos (feature branches, paired PRs). Prefer additive changes over breaking ones.

## PR Checklist

* [ ] Tests are added and passing (`pytest -v`).
* [ ] The pull request title follows the conventional commits or semantic format (enforced by the `Validate PR Title` continuous integration check; for example, `feat: ...`, `fix: ...`, or `docs: ...`).
* [ ] Lint checks pass successfully (`pre-commit run --all-files`).
* [ ] Public API changes are mirrored in `fallbacks.py`, if applicable.
* [ ] Docstrings are updated for changed signatures.
* [ ] A user guide page is added or updated if the change is visible to users.
* [ ] The `__all__` list is updated if exports changed.
* [ ] A changelog entry is added, if a changelog exists.

## Build the Documentation

The documentation source is located in `docs/` and is built with Fern. Generate the local Python API reference and validate the site before opening a pull request:

```bash
npm --prefix docs/fern run generate:library:local
npm --prefix docs/fern run check
```

Refer to [Build the Documentation](/nemo/lens/developer-guide/build-the-documentation) for local preview, authoring, versioning, and troubleshooting details.

## Questions

Open an issue on the repository, or contact the NeMo ecosystem team.