Skip to content

Conversation

pira998
Copy link

@pira998 pira998 commented Sep 20, 2025

Closes #760

Implement cross-workflow observability with context propagation and span processing

Fixes

This PR introduces a comprehensive cross-workflow
observability feature that enables tracking and monitoring
execution across multiple interconnected NAT workflows.

Key Features

  • ObservabilityContext: New context management system
    for trace propagation across workflow boundaries
  • Cross-workflow processors: Automatic span enhancement
    with workflow hierarchy and relationship data
  • Context serialization: Support for distributed
    processing with context preservation
  • Workflow utilities: Helper functions for seamless
    observability integration
  • Integration points: Updates to core NAT components
    for observability support

Implementation Details

Core Components

  • ObservabilityContext class for trace ID management and
    workflow hierarchy tracking
  • CrossWorkflowProcessor and
    WorkflowRelationshipProcessor for span enhancement
  • Workflow utility functions for context propagation
    patterns
  • Integration with existing NAT context and runtime systems

Observability Data Added

  • Trace IDs for cross-workflow correlation
  • Workflow hierarchy and depth information
  • Parent-child relationship tracking
  • Custom attributes propagation
  • Timing and status information

Files Changed

Core Implementation

  • src/nat/observability/context.py - New
    ObservabilityContext implementation
  • src/nat/observability/processor/cross_workflow_processor .py - Span processors
  • src/nat/observability/workflow_utils.py - Utility
    functions
  • src/nat/builder/context.py - Context integration
  • src/nat/builder/workflow.py - Workflow integration
  • src/nat/runtime/runner.py - Runtime integration
  • src/nat/observability/__init__.py - Module exports

Examples and Documentation

  • examples/observability/cross_workflow_tracking/ -
    Complete example suite
  • tests/nat/observability/test_cross_workflow_observabilit y.py - Test coverage

Testing

Comprehensive test suite covers:

  • Context creation and propagation
  • Span processing and enhancement
  • Serialization/deserialization
  • Error handling scenarios
  • Integration with NAT workflows

Usage

from nat.observability.context import ObservabilityContext

# Create root context
context = ObservabilityContext.create_root_context("main_workflow")

# Create child context for sub-workflow
child_context =  context.create_child_context("sub_workflow")

This feature enables end-to-end visibility into complex workflow chains, performance analysis, and debugging across workflow boundaries.

Summary by CodeRabbit

  • New Features

    • Adds cross-workflow observability: context propagation (trace/span IDs, parent/child relationships, workflow depth, timings), processors to annotate spans, and utilities to invoke workflows with observability for single and streaming runs. Observability types are now part of the public API and workflows/runners accept observability contexts.
  • Documentation

    • Adds a comprehensive README with overview, usage patterns, configuration examples, benefits, and best practices.
  • Examples

    • Adds multiple end-to-end and integrated examples plus a sample observability config demonstrating real and mock workflows.
  • Tests

    • Adds extensive tests covering context lifecycle, processors, and invocation behaviors.

@pira998 pira998 requested a review from a team as a code owner September 20, 2025 20:55
Copy link

copy-pr-bot bot commented Sep 20, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copy link

coderabbitai bot commented Oct 2, 2025

Walkthrough

Adds a cross-workflow observability subsystem: context model and manager, span processors, an observability-aware workflow invoker, Context/Runner plumbing to propagate ObservabilityContext, example scripts and config, and tests. No breaking API removals reported.

Changes

Cohort / File(s) Summary
Observability Core & API
src/nat/observability/__init__.py, src/nat/observability/context.py, src/nat/observability/processor/cross_workflow_processor.py, src/nat/observability/workflow_utils.py
Add ObservabilityContext, WorkflowMetadata, ObservabilityContextManager; implement CrossWorkflowProcessor and WorkflowRelationshipProcessor to enrich spans; add ObservabilityWorkflowInvoker for invoking workflows with propagated context, timing, status, streaming support, and serialization; expose new API in package init.
Builder / Context Integration
src/nat/builder/context.py, src/nat/builder/workflow.py
Add ContextVar storage and accessors for observability_context in ContextState/Context; add Context.set_observability_context; thread optional observability_context parameter through Workflow.run and Workflow.result_with_steps.
Runtime / Runner
src/nat/runtime/runner.py
Runner init gains optional observability_context parameter and sets ContextState.observability_context on entry when provided.
Examples & Config
examples/observability/cross_workflow_tracking/README.md, examples/observability/cross_workflow_tracking/observability_config.yml, examples/observability/cross_workflow_tracking/example.py, examples/observability/cross_workflow_tracking/integrated_example.py, examples/observability/cross_workflow_tracking/workflow_integration_example.py, examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
Add README, YAML config, and multiple example scripts demonstrating root/child context creation, propagation across sequential/parallel/nested workflows, invoker usage, processors, (de)serialization, and mock fallbacks.
Tests
tests/nat/observability/test_cross_workflow_observability.py
Add tests covering ObservabilityContext, ObservabilityContextManager, CrossWorkflowProcessor, WorkflowRelationshipProcessor, and ObservabilityWorkflowInvoker (single and streaming).
Subproject pointer
external/nat-ui
Update subproject commit reference (hash only); no code changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Invoker as ObservabilityWorkflowInvoker
  participant Workflow
  participant Runner
  participant Ctx as ContextState

  Caller->>Invoker: invoke_workflow_with_context(workflow, message, workflow_name, parent_ctx?)
  Note over Invoker: create/derive ObservabilityContext\nmark workflow start_time
  Invoker->>Workflow: run(message, observability_context)
  Workflow->>Runner: Runner(..., observability_context)
  activate Runner
  Runner->>Ctx: set observability_context (ContextVar)
  Runner-->>Workflow: result
  deactivate Runner
  Note over Invoker: update end_time, status (success/failure)
  Invoker-->>Caller: result
Loading
sequenceDiagram
  autonumber
  participant Runtime as Runtime/Exporter
  participant Processor1 as CrossWorkflowProcessor
  participant Processor2 as WorkflowRelationshipProcessor
  participant Span as Span Item
  participant Ctx as Context (with observability_context)

  Runtime->>Processor1: process(span)
  Processor1->>Ctx: read observability_context
  alt context available
    Processor1->>Span: enrich with trace_id, span_ids, workflow_chain, timing, tags
  else no context
    Processor1-->>Runtime: span (unchanged)
  end
  Processor1-->>Runtime: span

  Runtime->>Processor2: process(span)
  Processor2->>Ctx: read observability_context
  alt context available
    Processor2->>Span: add parent/child relationship, nesting, hierarchy path
  else
    Processor2-->>Runtime: span (unchanged)
  end
  Processor2-->>Runtime: span (enriched)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title is imperative and accurately describes the feature but exceeds the recommended maximum of around 72 characters, making it less concise than desired. A concise title improves clarity in pull request listings and commit history. Please shorten it under the character limit by trimming redundant phrasing while preserving its descriptive nature. Please shorten the title to under 72 characters by removing redundant words. For example, consider “Enable cross-workflow observability with context and span processing,” which captures the intent in fewer characters while maintaining clarity.
Out of Scope Changes Check ⚠️ Warning The PR contains a commit updating the external/nat-ui subproject reference, which does not relate to the observability objectives defined in the linked issue and falls outside the scope of cross-workflow observability. Leaving unrelated updates in this PR could complicate review and obscure the feature changes. All other changes correctly implement the intended context propagation and span processing enhancements. Please remove the external/nat-ui reference update or move it to a separate PR so that this PR remains focused solely on observability-related enhancements.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues Check ✅ Passed The PR implements all primary coding objectives from issue #760 by introducing ObservabilityContext for trace and workflow hierarchy management, span processors to annotate spans with hierarchical and relational data, utilities for context propagation in workflow invocation, and core modifications to builder, workflow, and runtime components. Serialization and deserialization support alongside comprehensive tests ensure cross-process context propagation capabilities. The examples demonstrate usage patterns consistent with the linked issue’s requirements for end-to-end trace correlation across multiple workflows. Therefore, the changes satisfy the mechanism for propagating observability across workflows and integrating detailed spans into a global trace.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added feature request New feature or request non-breaking Non-breaking change labels Oct 2, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/nat/runtime/runner.py (1)

108-168: Reset the observability ContextVar after the run.

We call ContextVar.set(...) in __aenter__ but never reset it. That leaves the last workflow’s observability context stuck in the global state and contaminates subsequent runs. Capture the token from set() and reset it in __aexit__ (or finally block).

Proposed fix:

-        if self._observability_context:
-            self._context_state.observability_context.set(self._observability_context)
+        self._observability_context_token = None
+        if self._observability_context:
+            self._observability_context_token = (
+                self._context_state.observability_context.set(self._observability_context)
+            )
@@
         self._context_state.input_message.reset(self._input_message_token)
+        if self._observability_context_token is not None:
+            self._context_state.observability_context.reset(self._observability_context_token)
+            self._observability_context_token = None
🧹 Nitpick comments (3)
src/nat/observability/__init__.py (1)

22-28: Sort __all__ to satisfy Ruff.

ruff (RUF022) asks for the exported names to be sorted; please reorder the list accordingly so linting passes.

Updated ordering:

-__all__ = [
-    "ObservabilityContext",
-    "ObservabilityContextManager",
-    "ObservabilityWorkflowInvoker",
-    "CrossWorkflowProcessor",
-    "WorkflowRelationshipProcessor"
-]
+__all__ = [
+    "CrossWorkflowProcessor",
+    "ObservabilityContext",
+    "ObservabilityContextManager",
+    "ObservabilityWorkflowInvoker",
+    "WorkflowRelationshipProcessor",
+]
examples/observability/cross_workflow_tracking/integrated_example.py (1)

320-331: Avoid blanket except Exception in the parallel helper.

Catching every exception trips ruff (BLE001) and hides unexpected failures. Consider letting the task raise and using asyncio.gather(..., return_exceptions=True) (or catching a specific workflow error type) so you can still surface per-query failures without suppressing unrelated bugs.

src/nat/observability/workflow_utils.py (1)

181-187: Don’t blanket-catch every exception when reading the current context.

Context.get() shouldn’t be raising arbitrary exceptions here; catching Exception masks real bugs and triggers BLE001. Either let it propagate or catch only the specific failure you expect (e.g., LookupError). Given the current implementation, simply drop the try/except and return the property.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c80936 and 52c8da5.

📒 Files selected for processing (14)
  • examples/observability/cross_workflow_tracking/README.md (1 hunks)
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/integrated_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/observability_config.yml (1 hunks)
  • examples/observability/cross_workflow_tracking/workflow_integration_example.py (1 hunks)
  • src/nat/builder/context.py (4 hunks)
  • src/nat/builder/workflow.py (2 hunks)
  • src/nat/observability/__init__.py (1 hunks)
  • src/nat/observability/context.py (1 hunks)
  • src/nat/observability/processor/cross_workflow_processor.py (1 hunks)
  • src/nat/observability/workflow_utils.py (1 hunks)
  • src/nat/runtime/runner.py (5 hunks)
  • tests/nat/observability/test_cross_workflow_observability.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/README.@(md|ipynb)

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Ensure READMEs follow the naming convention; avoid deprecated names; use “NeMo Agent Toolkit” (capital T) in headings

Files:

  • examples/observability/cross_workflow_tracking/README.md
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • examples/observability/cross_workflow_tracking/README.md
  • examples/observability/cross_workflow_tracking/observability_config.yml
  • tests/nat/observability/test_cross_workflow_observability.py
  • src/nat/observability/__init__.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/builder/context.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/workflow_integration_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/observability/cross_workflow_tracking/README.md
  • examples/observability/cross_workflow_tracking/observability_config.yml
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/workflow_integration_example.py
**/*.{yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

In workflow/config YAML, set llms.._type: nat_test_llm to stub responses.

Files:

  • examples/observability/cross_workflow_tracking/observability_config.yml
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • examples/observability/cross_workflow_tracking/observability_config.yml
  • tests/nat/observability/test_cross_workflow_observability.py
  • src/nat/observability/__init__.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/builder/context.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/workflow_integration_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

**/*.py: In code comments/identifiers use NAT abbreviations as specified: nat for API namespace/CLI, nvidia-nat for package name, NAT for env var prefixes; do not use these abbreviations in documentation
Follow PEP 20 and PEP 8; run yapf with column_limit=120; use 4-space indentation; end files with a single trailing newline
Run ruff check --fix as linter (not formatter) using pyproject.toml config; fix warnings unless explicitly ignored
Respect naming: snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: use bare raise to re-raise; log with logger.error() when re-raising to avoid duplicate stack traces; use logger.exception() when catching without re-raising
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise and ending with a period; surround code entities with backticks
Validate and sanitize all user input, especially in web or CLI interfaces
Prefer httpx with SSL verification enabled by default and follow OWASP Top-10 recommendations
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile or mprof before optimizing; cache expensive computations with functools.lru_cache or external cache; leverage NumPy vectorized operations when beneficial

Files:

  • tests/nat/observability/test_cross_workflow_observability.py
  • src/nat/observability/__init__.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/builder/context.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/workflow_integration_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests reside under tests/ and should use markers defined in pyproject.toml (e.g., integration)

Files:

  • tests/nat/observability/test_cross_workflow_observability.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/observability/test_cross_workflow_observability.py
{tests/**/*.py,examples/*/tests/**/*.py}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

{tests/**/*.py,examples/*/tests/**/*.py}: Use pytest (with pytest-asyncio for async); name test files test_*.py; test functions start with test_; extract repeated code into fixtures; fixtures must set name in decorator and be named with fixture_ prefix
Mock external services with pytest_httpserver or unittest.mock; do not hit live endpoints
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration

Files:

  • tests/nat/observability/test_cross_workflow_observability.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All importable Python code must live under src/ (or packages//src/)

Files:

  • src/nat/observability/__init__.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • src/nat/builder/context.py
  • src/nat/observability/processor/cross_workflow_processor.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Changes in src/nat should prioritize backward compatibility

Files:

  • src/nat/observability/__init__.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • src/nat/builder/context.py
  • src/nat/observability/processor/cross_workflow_processor.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/observability/__init__.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • src/nat/builder/context.py
  • src/nat/observability/processor/cross_workflow_processor.py
{src/**/*.py,packages/*/src/**/*.py}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All public APIs must have Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful

Files:

  • src/nat/observability/__init__.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/context.py
  • src/nat/builder/workflow.py
  • src/nat/runtime/runner.py
  • src/nat/builder/context.py
  • src/nat/observability/processor/cross_workflow_processor.py
🧬 Code graph analysis (12)
tests/nat/observability/test_cross_workflow_observability.py (6)
src/nat/runtime/runner.py (5)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
  • result_stream (170-200)
src/nat/observability/context.py (18)
  • ObservabilityContext (39-141)
  • ObservabilityContextManager (144-210)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_span_context (86-92)
  • add_attribute (94-96)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • to_dict (106-122)
  • from_dict (125-141)
  • get_current_context (156-158)
  • clear_context (189-191)
  • propagate_context (184-186)
  • get_trace_id (194-197)
  • get_current_span_id (200-203)
  • add_workflow_attribute (206-210)
src/nat/observability/workflow_utils.py (5)
  • ObservabilityWorkflowInvoker (33-281)
  • invoke_workflow_with_context (42-105)
  • invoke_workflow_stream_with_context (108-171)
  • create_observability_context (190-212)
  • get_current_observability_context (174-187)
src/nat/observability/processor/cross_workflow_processor.py (4)
  • CrossWorkflowProcessor (21-105)
  • WorkflowRelationshipProcessor (108-165)
  • process (31-105)
  • process (117-165)
src/nat/builder/context.py (4)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • set_observability_context (309-316)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/__init__.py (3)
src/nat/observability/context.py (2)
  • ObservabilityContext (39-141)
  • ObservabilityContextManager (144-210)
src/nat/observability/processor/cross_workflow_processor.py (2)
  • CrossWorkflowProcessor (21-105)
  • WorkflowRelationshipProcessor (108-165)
src/nat/observability/workflow_utils.py (1)
  • ObservabilityWorkflowInvoker (33-281)
examples/observability/cross_workflow_tracking/integrated_example.py (5)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/observability/context.py (10)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/builder/context.py (6)
  • Context (122-329)
  • ContextState (66-119)
  • observability_context (114-115)
  • observability_context (296-307)
  • get (118-119)
  • get (319-329)
src/nat/observability/processor/cross_workflow_processor.py (4)
  • CrossWorkflowProcessor (21-105)
  • WorkflowRelationshipProcessor (108-165)
  • process (31-105)
  • process (117-165)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/workflow_utils.py (4)
src/nat/runtime/runner.py (5)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
  • result_stream (170-200)
src/nat/observability/context.py (6)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
src/nat/builder/workflow.py (3)
  • Workflow (42-169)
  • run (98-115)
  • result_with_steps (117-134)
src/nat/builder/context.py (6)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
  • set_observability_context (309-316)
src/nat/observability/context.py (2)
src/nat/builder/context.py (2)
  • get (118-119)
  • get (319-329)
src/nat/runtime/runner.py (1)
  • context (90-91)
src/nat/builder/workflow.py (3)
src/nat/runtime/runner.py (2)
  • context (90-91)
  • Runner (46-200)
src/nat/observability/context.py (1)
  • ObservabilityContext (39-141)
src/nat/builder/context.py (3)
  • observability_context (114-115)
  • observability_context (296-307)
  • input_message (128-139)
src/nat/runtime/runner.py (3)
src/nat/observability/context.py (1)
  • ObservabilityContext (39-141)
src/nat/observability/exporter_manager.py (1)
  • ExporterManager (26-335)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
examples/observability/cross_workflow_tracking/example.py (1)
src/nat/observability/context.py (8)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
src/nat/builder/context.py (1)
src/nat/observability/context.py (1)
  • ObservabilityContext (39-141)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (5)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (9)
  • ObservabilityContext (39-141)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • add_attribute (94-96)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • get_workflow_depth (102-104)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
src/nat/builder/workflow.py (1)
  • run (98-115)
examples/observability/cross_workflow_tracking/workflow_integration_example.py (5)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (8)
  • ObservabilityContext (39-141)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • add_attribute (94-96)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/processor/cross_workflow_processor.py (3)
src/nat/observability/processor/processor.py (1)
  • Processor (27-74)
src/nat/utils/type_utils.py (1)
  • override (56-57)
src/nat/builder/context.py (5)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
🪛 Ruff (0.13.2)
tests/nat/observability/test_cross_workflow_observability.py

349-349: Unused function argument: to_type

(ARG001)

src/nat/observability/__init__.py

22-28: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

examples/observability/cross_workflow_tracking/integrated_example.py

1-1: Shebang is present but file is not executable

(EXE001)


98-98: Consider moving this statement to an else block

(TRY300)


177-189: f-string without any placeholders

Remove extraneous f prefix

(F541)


196-208: f-string without any placeholders

Remove extraneous f prefix

(F541)


215-227: f-string without any placeholders

Remove extraneous f prefix

(F541)


302-302: Do not catch blind exception: Exception

(BLE001)


329-329: Consider moving this statement to an else block

(TRY300)


330-330: Do not catch blind exception: Exception

(BLE001)

src/nat/observability/workflow_utils.py

75-75: Do not catch blind exception: Exception

(BLE001)


142-142: Do not catch blind exception: Exception

(BLE001)


185-185: Consider moving this statement to an else block

(TRY300)


186-186: Do not catch blind exception: Exception

(BLE001)


209-210: try-except-pass detected, consider logging the exception

(S110)


209-209: Do not catch blind exception: Exception

(BLE001)


248-248: Do not catch blind exception: Exception

(BLE001)


271-271: Consider moving this statement to an else block

(TRY300)

src/nat/observability/context.py

78-78: Consider [*self.workflow_chain, child_workflow] instead of concatenation

Replace with [*self.workflow_chain, child_workflow]

(RUF005)

examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py

1-1: Shebang is present but file is not executable

(EXE001)

examples/observability/cross_workflow_tracking/workflow_integration_example.py

1-1: Shebang is present but file is not executable

(EXE001)


79-79: Do not catch blind exception: Exception

(BLE001)

src/nat/observability/processor/cross_workflow_processor.py

101-101: Do not catch blind exception: Exception

(BLE001)


161-161: Do not catch blind exception: Exception

(BLE001)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (5)
examples/observability/cross_workflow_tracking/integrated_example.py (1)

80-87: Use the public observability API (repeat).

We still poke workflow._context_state and then call workflow.run(...) without passing the context, so the runner wipes the setting and spans miss the cross-workflow data. Please drop the private mutation and call workflow.run(input_data, observability_context=obs_context) instead.

-        async with load_workflow(config_file) as workflow:
-            # Set observability context in the workflow's context state
-            context_state = workflow._context_state
-            context_state.observability_context.set(obs_context)
-
-            # Run the workflow without the observability_context parameter
-            async with workflow.run(input_data) as runner:
+        async with load_workflow(config_file) as workflow:
+            # Run the workflow with the propagated observability context
+            async with workflow.run(input_data, observability_context=obs_context) as runner:
                 result = await runner.result(to_type=str)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1)

22-34: Stub the example with nat_test_llm instead of NIM.

Example configs must never call real services. Replace the nvidia_llm block with a nat_test_llm stub (include response_seq and delay_ms) and point workflow.llm_name at it so the sample stays offline. Based on learnings

-llms:
-  nvidia_llm:
-    _type: nim
-    model_name: meta/llama-3.1-8b-instruct
-    temperature: 0.7
-    max_tokens: 1024
+llms:
+  demo_llm:
+    _type: nat_test_llm
+    response_seq:
+      - "Stubbed support reply."
+    delay_ms: 0

 workflow:
   _type: chat_completion
-  llm_name: nvidia_llm
+  llm_name: demo_llm
src/nat/observability/workflow_utils.py (3)

66-111: Clean up context propagation error handling.

Catching Exception and doing per-branch bookkeeping breaks our lint config (BLE001) and reintroduces the pattern the previous review asked to refactor. Switch to try/finally, capture exc = sys.exc_info()[1], update status/tags in the finally block, and log with logger.error(...) when exc exists before re-raising. This also lets you drop the blanket catch entirely.

-        try:
-            async with workflow.run(message, observability_context=obs_context) as runner:
-                result = await runner.result(to_type=to_type)
-
-                # Update workflow metadata on completion
-                if obs_context:
-                    current_workflow = obs_context.get_current_workflow()
-                    if current_workflow:
-                        current_workflow.end_time = time.time()
-                        current_workflow.status = "completed"
-
-                return result
-
-        except Exception as e:
-            # Update workflow metadata on failure and log error
-            if obs_context:
-                current_workflow = obs_context.get_current_workflow()
-                if current_workflow:
-                    current_workflow.end_time = time.time()
-                    current_workflow.status = "failed"
-                    current_workflow.tags["error"] = str(e)
-
-            logger.error(f"Workflow '{workflow_name}' failed with error: {e}", exc_info=True)
-            raise
+        result = None
+        try:
+            async with workflow.run(message, observability_context=obs_context) as runner:
+                result = await runner.result(to_type=to_type)
+            return result
+        finally:
+            exc = sys.exc_info()[1]
+            if obs_context:
+                current_workflow = obs_context.get_current_workflow()
+                if current_workflow:
+                    current_workflow.end_time = time.time()
+                    if exc is None:
+                        current_workflow.status = "completed"
+                    else:
+                        current_workflow.status = "failed"
+                        current_workflow.tags["error"] = str(exc)
+                        logger.error("Workflow '%s' failed: %s", workflow_name, exc)

148-179: Apply the same finally pattern to streaming runs.

The streaming helper still swallows BLE001 with a broad except. Mirror the try/finally + sys.exc_info() structure so metadata updates and logging run exactly once and lint passes.

-        try:
-            async with workflow.run(message, observability_context=obs_context) as runner:
-                async for item in runner.result_stream(to_type=to_type):
-                    yield item
-
-                # Update workflow metadata on completion
-                if obs_context:
-                    current_workflow = obs_context.get_current_workflow()
-                    if current_workflow:
-                        current_workflow.end_time = time.time()
-                        current_workflow.status = "completed"
-
-        except Exception as e:
-            # Update workflow metadata on failure and log error
-            if obs_context:
-                current_workflow = obs_context.get_current_workflow()
-                if current_workflow:
-                    current_workflow.end_time = time.time()
-                    current_workflow.status = "failed"
-                    current_workflow.tags["error"] = str(e)
-
-            logger.error(f"Streaming workflow '{workflow_name}' failed with error: {e}", exc_info=True)
-            raise
+        try:
+            async with workflow.run(message, observability_context=obs_context) as runner:
+                async for item in runner.result_stream(to_type=to_type):
+                    yield item
+        finally:
+            exc = sys.exc_info()[1]
+            if obs_context:
+                current_workflow = obs_context.get_current_workflow()
+                if current_workflow:
+                    current_workflow.end_time = time.time()
+                    if exc is None:
+                        current_workflow.status = "completed"
+                    else:
+                        current_workflow.status = "failed"
+                        current_workflow.tags["error"] = str(exc)
+                        logger.error("Streaming workflow '%s' failed: %s", workflow_name, exc)

198-291: Likewise fix invoke_with_steps_and_context.

We still have blanket except Exception here. Convert to the shared try/finally structure so status bookkeeping and logging stay consistent and lint stays clean.

-        try:
-            result, steps = await workflow.result_with_steps(
-                message,
-                to_type=to_type,
-                observability_context=obs_context
-            )
-
-            # Update workflow metadata on completion
-            if obs_context:
-                current_workflow = obs_context.get_current_workflow()
-                if current_workflow:
-                    current_workflow.end_time = time.time()
-                    current_workflow.status = "completed"
-
-            return result, steps
-
-        except Exception as e:
-            # Update workflow metadata on failure and log error
-            if obs_context:
-                current_workflow = obs_context.get_current_workflow()
-                if current_workflow:
-                    current_workflow.end_time = time.time()
-                    current_workflow.status = "failed"
-                    current_workflow.tags["error"] = str(e)
-
-            logger.error(f"Workflow with steps '{workflow_name}' failed with error: {e}", exc_info=True)
-            raise
+        result: SingleOutputT | None = None
+        steps: Any = None
+        try:
+            result, steps = await workflow.result_with_steps(
+                message,
+                to_type=to_type,
+                observability_context=obs_context
+            )
+            return result, steps
+        finally:
+            exc = sys.exc_info()[1]
+            if obs_context:
+                current_workflow = obs_context.get_current_workflow()
+                if current_workflow:
+                    current_workflow.end_time = time.time()
+                    if exc is None:
+                        current_workflow.status = "completed"
+                    else:
+                        current_workflow.status = "failed"
+                        current_workflow.tags["error"] = str(exc)
+                        logger.error("Workflow with steps '%s' failed: %s", workflow_name, exc)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52c8da5 and 712749f.

📒 Files selected for processing (6)
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/integrated_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/observability_config.yml (1 hunks)
  • external/nat-ui (1 hunks)
  • src/nat/observability/processor/cross_workflow_processor.py (1 hunks)
  • src/nat/observability/workflow_utils.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • external/nat-ui
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/observability/cross_workflow_tracking/observability_config.yml
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • src/nat/observability/workflow_utils.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

**/*.py: In code comments/identifiers use NAT abbreviations as specified: nat for API namespace/CLI, nvidia-nat for package name, NAT for env var prefixes; do not use these abbreviations in documentation
Follow PEP 20 and PEP 8; run yapf with column_limit=120; use 4-space indentation; end files with a single trailing newline
Run ruff check --fix as linter (not formatter) using pyproject.toml config; fix warnings unless explicitly ignored
Respect naming: snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: use bare raise to re-raise; log with logger.error() when re-raising to avoid duplicate stack traces; use logger.exception() when catching without re-raising
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise and ending with a period; surround code entities with backticks
Validate and sanitize all user input, especially in web or CLI interfaces
Prefer httpx with SSL verification enabled by default and follow OWASP Top-10 recommendations
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile or mprof before optimizing; cache expensive computations with functools.lru_cache or external cache; leverage NumPy vectorized operations when beneficial

Files:

  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • src/nat/observability/workflow_utils.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • examples/observability/cross_workflow_tracking/integrated_example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • src/nat/observability/workflow_utils.py
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All importable Python code must live under src/ (or packages//src/)

Files:

  • src/nat/observability/processor/cross_workflow_processor.py
  • src/nat/observability/workflow_utils.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Changes in src/nat should prioritize backward compatibility

Files:

  • src/nat/observability/processor/cross_workflow_processor.py
  • src/nat/observability/workflow_utils.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/observability/processor/cross_workflow_processor.py
  • src/nat/observability/workflow_utils.py
{src/**/*.py,packages/*/src/**/*.py}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All public APIs must have Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful

Files:

  • src/nat/observability/processor/cross_workflow_processor.py
  • src/nat/observability/workflow_utils.py
🧠 Learnings (2)
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: When stubbing deterministic LLM responses in tests/workflows, use the NAT Test LLM (nat_test_llm).

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: Applies to **/*.{yaml,yml} : In workflow/config YAML, set llms.<name>._type: nat_test_llm to stub responses.

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
🧬 Code graph analysis (4)
examples/observability/cross_workflow_tracking/integrated_example.py (5)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/observability/context.py (10)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/builder/context.py (6)
  • Context (122-329)
  • ContextState (66-119)
  • observability_context (114-115)
  • observability_context (296-307)
  • get (118-119)
  • get (319-329)
src/nat/observability/processor/cross_workflow_processor.py (4)
  • CrossWorkflowProcessor (25-110)
  • WorkflowRelationshipProcessor (113-171)
  • process (35-110)
  • process (122-171)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/processor/cross_workflow_processor.py (3)
src/nat/observability/processor/processor.py (1)
  • Processor (27-74)
src/nat/utils/type_utils.py (1)
  • override (56-57)
src/nat/builder/context.py (5)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (5)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (9)
  • ObservabilityContext (39-141)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • add_attribute (94-96)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • get_workflow_depth (102-104)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/workflow_utils.py (4)
src/nat/runtime/runner.py (5)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
  • result_stream (170-200)
src/nat/observability/context.py (6)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
src/nat/builder/workflow.py (3)
  • Workflow (42-169)
  • run (98-115)
  • result_with_steps (117-134)
src/nat/builder/context.py (6)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
  • set_observability_context (309-316)
🪛 Ruff (0.13.2)
examples/observability/cross_workflow_tracking/integrated_example.py

1-1: Shebang is present but file is not executable

(EXE001)


98-98: Consider moving this statement to an else block

(TRY300)


302-302: Do not catch blind exception: Exception

(BLE001)


329-329: Consider moving this statement to an else block

(TRY300)


330-330: Do not catch blind exception: Exception

(BLE001)

examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py

1-1: Shebang is present but file is not executable

(EXE001)

src/nat/observability/workflow_utils.py

79-79: Do not catch blind exception: Exception

(BLE001)


148-148: Do not catch blind exception: Exception

(BLE001)


193-193: Consider moving this statement to an else block

(TRY300)


194-194: Do not catch blind exception: Exception

(BLE001)


217-218: try-except-pass detected, consider logging the exception

(S110)


217-217: Do not catch blind exception: Exception

(BLE001)


256-256: Do not catch blind exception: Exception

(BLE001)


279-279: Consider moving this statement to an else block

(TRY300)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1)

78-82: Use the public observability_context parameter instead of private attribute mutation.

Setting workflow._context_state.observability_context is overwritten when workflow.run() resets the context var. Pass query_context via the public parameter so the context reaches the runner.

Apply this diff:

-                # Set observability context in the workflow's context state
-                workflow._context_state.observability_context.set(query_context)
-
-                # Execute workflow
-                async with workflow.run(query) as runner:
+                # Execute workflow with propagated observability context
+                async with workflow.run(query, observability_context=query_context) as runner:

</review_comment_end -->

🧹 Nitpick comments (6)
src/nat/observability/processor/cross_workflow_processor.py (2)

107-107: Remove redundant exception object from logger.exception() call.

logger.exception() automatically includes the exception details, so passing e in the format string is redundant.

Apply this diff:

-            logger.exception("Error processing cross-workflow observability data: %s", e)
+            logger.exception("Error processing cross-workflow observability data")

</review_comment_end -->


168-168: Remove redundant exception object from logger.exception() call.

Same issue as above—drop the %s, e since logger.exception() captures it automatically.

Apply this diff:

-            logger.exception("Error processing workflow relationship data: %s", e)
+            logger.exception("Error processing workflow relationship data")

</review_comment_end -->

examples/observability/cross_workflow_tracking/example.py (1)

127-127: Add missing return type hint.

All public async functions should have type hints per Python guidelines. Add -> None to main().

Apply this diff:

-async def main():
+async def main() -> None:

</review_comment_end -->

src/nat/observability/workflow_utils.py (3)

70-80: Log exceptions when falling back to root context.

The blanket Exception catch is acceptable here since observability is optional and shouldn't break workflows. However, log the exception so unexpected failures are visible.

Apply this diff:

             try:
                 # Import here to avoid circular import
                 from nat.builder.context import Context
                 current_context = Context.get()
                 existing_obs_context = current_context.observability_context
                 if existing_obs_context:
                     obs_context = existing_obs_context.create_child_context(workflow_name)
                 else:
                     obs_context = ObservabilityContext.create_root_context(workflow_name)
-            except Exception:
+            except Exception as e:
+                logger.debug("Failed to get existing observability context, creating root: %s", e)
                 obs_context = ObservabilityContext.create_root_context(workflow_name)

Apply the same pattern at lines 131-141 and 232-242.
</review_comment_end -->


174-180: Log when context retrieval fails.

Returning None on failure is appropriate, but log at debug level so developers can diagnose issues.

Apply this diff:

         try:
             # Import here to avoid circular import
             from nat.builder.context import Context
             current_context = Context.get()
             return current_context.observability_context
-        except Exception:
+        except Exception as e:
+            logger.debug("Failed to get current observability context: %s", e)
             return None

</review_comment_end -->


197-203: Log when setting context fails.

The silent pass on exception is flagged by S110. Log at debug level so failures are visible during troubleshooting.

Apply this diff:

         # Set it in the current context if available
         try:
             # Import here to avoid circular import
             from nat.builder.context import Context
             current_context = Context.get()
             current_context.set_observability_context(context)
-        except Exception:
-            pass  # Context might not be available
+        except Exception as e:
+            logger.debug("Failed to set observability context in execution context: %s", e)

</review_comment_end -->

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 712749f and 184ef8a.

📒 Files selected for processing (4)
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/example.py (1 hunks)
  • src/nat/observability/processor/cross_workflow_processor.py (1 hunks)
  • src/nat/observability/workflow_utils.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

**/*.py: In code comments/identifiers use NAT abbreviations as specified: nat for API namespace/CLI, nvidia-nat for package name, NAT for env var prefixes; do not use these abbreviations in documentation
Follow PEP 20 and PEP 8; run yapf with column_limit=120; use 4-space indentation; end files with a single trailing newline
Run ruff check --fix as linter (not formatter) using pyproject.toml config; fix warnings unless explicitly ignored
Respect naming: snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: use bare raise to re-raise; log with logger.error() when re-raising to avoid duplicate stack traces; use logger.exception() when catching without re-raising
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise and ending with a period; surround code entities with backticks
Validate and sanitize all user input, especially in web or CLI interfaces
Prefer httpx with SSL verification enabled by default and follow OWASP Top-10 recommendations
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile or mprof before optimizing; cache expensive computations with functools.lru_cache or external cache; leverage NumPy vectorized operations when beneficial

Files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
  • examples/observability/cross_workflow_tracking/example.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All importable Python code must live under src/ (or packages//src/)

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Changes in src/nat should prioritize backward compatibility

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
{src/**/*.py,packages/*/src/**/*.py}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All public APIs must have Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
🧠 Learnings (2)
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: When stubbing deterministic LLM responses in tests/workflows, use the NAT Test LLM (nat_test_llm).

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: Applies to **/*.{yaml,yml} : In workflow/config YAML, set llms.<name>._type: nat_test_llm to stub responses.

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
🧬 Code graph analysis (4)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (5)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (9)
  • ObservabilityContext (39-141)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • add_attribute (94-96)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • get_workflow_depth (102-104)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
src/nat/builder/workflow.py (1)
  • run (98-115)
examples/observability/cross_workflow_tracking/example.py (2)
src/nat/observability/context.py (8)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/workflow_utils.py (4)
src/nat/runtime/runner.py (5)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
  • result_stream (170-200)
src/nat/observability/context.py (6)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
src/nat/builder/workflow.py (3)
  • Workflow (42-169)
  • run (98-115)
  • result_with_steps (117-134)
src/nat/builder/context.py (6)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
  • set_observability_context (309-316)
src/nat/observability/processor/cross_workflow_processor.py (3)
src/nat/observability/processor/processor.py (1)
  • Processor (27-74)
src/nat/utils/type_utils.py (1)
  • override (56-57)
src/nat/builder/context.py (5)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
🪛 Ruff (0.13.2)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py

1-1: Shebang is present but file is not executable

(EXE001)

src/nat/observability/workflow_utils.py

79-79: Do not catch blind exception: Exception

(BLE001)


140-140: Do not catch blind exception: Exception

(BLE001)


178-178: Consider moving this statement to an else block

(TRY300)


179-179: Do not catch blind exception: Exception

(BLE001)


202-203: try-except-pass detected, consider logging the exception

(S110)


202-202: Do not catch blind exception: Exception

(BLE001)


241-241: Do not catch blind exception: Exception

(BLE001)

src/nat/observability/processor/cross_workflow_processor.py

107-107: Redundant exception object included in logging.exception call

(TRY401)


168-168: Redundant exception object included in logging.exception call

(TRY401)

@pira998 pira998 force-pushed the feature/cross-workflow-observability branch from 184ef8a to 9012d26 Compare October 2, 2025 09:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (2)

78-82: Use the public API to propagate observability context.

The code mutates the private workflow._context_state.observability_context directly, but workflow.run() resets the ContextVar from its observability_context parameter. This means the manually set context is lost and cross-workflow data never reaches the runner.

Apply this diff to use the public API:

-                # Set observability context in the workflow's context state
-                workflow._context_state.observability_context.set(query_context)
-
-                # Execute workflow
-                async with workflow.run(query) as runner:
+                # Execute workflow with propagated observability context
+                async with workflow.run(query, observability_context=query_context) as runner:

1-1: Make the file executable or remove the shebang.

The shebang is present but the file lacks execute permissions.

Either mark it executable (chmod +x) or remove the shebang line if direct execution isn't intended.

examples/observability/cross_workflow_tracking/integrated_example.py (2)

80-86: Use the public API to propagate observability context.

The code directly mutates workflow._context_state.observability_context, but workflow.run() resets the ContextVar based on its observability_context parameter. Passing None (the default) wipes the value you just set, so spans emitted during execution won't carry the cross-workflow data.

Apply this diff:

         async with load_workflow(config_file) as workflow:
-            # Set observability context in the workflow's context state
-            context_state = workflow._context_state
-            context_state.observability_context.set(obs_context)
-
-            # Run the workflow without the observability_context parameter
-            async with workflow.run(input_data) as runner:
+            # Run the workflow with the propagated observability context
+            async with workflow.run(input_data, observability_context=obs_context) as runner:

1-1: Make the file executable or remove the shebang.

The shebang is present but the file lacks execute permissions.

🧹 Nitpick comments (3)
src/nat/observability/workflow_utils.py (1)

177-180: Consider moving the return to an else block.

The return statement at line 178 follows an except block without re-raising. Moving it into an else block would make the control flow clearer and satisfy the TRY300 guideline.

Apply this diff:

     try:
         # Import here to avoid circular import
         from nat.builder.context import Context
         current_context = Context.get()
-        return current_context.observability_context
+        obs = current_context.observability_context
     except Exception:
         return None
+    else:
+        return obs
src/nat/observability/processor/cross_workflow_processor.py (1)

105-108: Remove redundant exception object from logger.exception calls.

logger.exception() automatically captures the exception's stack trace, so including the exception object in the message format string (%s", e) is redundant and triggers TRY401.

Apply this diff:

         except (AttributeError, KeyError, TypeError, ValueError) as e:
             # If there's any error in processing, log it but don't fail the span
-            logger.exception("Error processing cross-workflow observability data: %s", e)
+            logger.exception("Error processing cross-workflow observability data")
             item.set_attribute("observability.processing_error", str(e))

And:

         except (AttributeError, IndexError, TypeError) as e:
             # If there's any error in processing, log it but don't fail the span
-            logger.exception("Error processing workflow relationship data: %s", e)
+            logger.exception("Error processing workflow relationship data")
             item.set_attribute("relationship.processing_error", str(e))

Also applies to: 166-169

examples/observability/cross_workflow_tracking/integrated_example.py (1)

98-98: Consider moving return statements to else blocks.

The return statements at lines 98 and 329 follow except blocks without re-raising. Moving them into else blocks would make the control flow clearer and satisfy the TRY300 guideline.

For line 98, apply this diff:

     try:
         # Load and run the actual NAT workflow
         async with load_workflow(config_file) as workflow:
             # ...
+            result = None
         # Update completion status
         if current_workflow:
             current_workflow.end_time = time.time()
             current_workflow.status = "completed"
             if current_workflow.start_time:
                 duration = current_workflow.end_time - current_workflow.start_time
                 print(f"   - Duration: {duration:.3f}s")
                 print(f"   - Result: {result}")
-
-        return result
-
     except Exception as e:
         # Update error status
         if current_workflow:
             current_workflow.end_time = time.time()
             current_workflow.status = "failed"
             current_workflow.tags["error"] = str(e)
         print(f"   - Error: {e}")
         raise
+    else:
+        return result

Similar pattern for line 329 in the nested function.

Also applies to: 329-329

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 184ef8a and 9012d26.

📒 Files selected for processing (7)
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/integrated_example.py (1 hunks)
  • examples/observability/cross_workflow_tracking/observability_config.yml (1 hunks)
  • external/nat-ui (1 hunks)
  • src/nat/observability/processor/cross_workflow_processor.py (1 hunks)
  • src/nat/observability/workflow_utils.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • external/nat-ui
  • examples/observability/cross_workflow_tracking/observability_config.yml
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/observability/workflow_utils.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

**/*.py: In code comments/identifiers use NAT abbreviations as specified: nat for API namespace/CLI, nvidia-nat for package name, NAT for env var prefixes; do not use these abbreviations in documentation
Follow PEP 20 and PEP 8; run yapf with column_limit=120; use 4-space indentation; end files with a single trailing newline
Run ruff check --fix as linter (not formatter) using pyproject.toml config; fix warnings unless explicitly ignored
Respect naming: snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: use bare raise to re-raise; log with logger.error() when re-raising to avoid duplicate stack traces; use logger.exception() when catching without re-raising
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise and ending with a period; surround code entities with backticks
Validate and sanitize all user input, especially in web or CLI interfaces
Prefer httpx with SSL verification enabled by default and follow OWASP Top-10 recommendations
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile or mprof before optimizing; cache expensive computations with functools.lru_cache or external cache; leverage NumPy vectorized operations when beneficial

Files:

  • src/nat/observability/workflow_utils.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All importable Python code must live under src/ (or packages//src/)

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Changes in src/nat should prioritize backward compatibility

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
{src/**/*.py,packages/*/src/**/*.py}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

All public APIs must have Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful

Files:

  • src/nat/observability/workflow_utils.py
  • src/nat/observability/processor/cross_workflow_processor.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/observability/workflow_utils.py
  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • src/nat/observability/processor/cross_workflow_processor.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/observability/cross_workflow_tracking/integrated_example.py
  • examples/observability/cross_workflow_tracking/example.py
  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
🧠 Learnings (2)
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: When stubbing deterministic LLM responses in tests/workflows, use the NAT Test LLM (nat_test_llm).

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
📚 Learning: 2025-09-09T20:32:39.016Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/nat-test-llm.mdc:0-0
Timestamp: 2025-09-09T20:32:39.016Z
Learning: Applies to **/*.{yaml,yml} : In workflow/config YAML, set llms.<name>._type: nat_test_llm to stub responses.

Applied to files:

  • examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py
🧬 Code graph analysis (5)
src/nat/observability/workflow_utils.py (4)
src/nat/runtime/runner.py (5)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
  • result_stream (170-200)
src/nat/observability/context.py (6)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
src/nat/builder/workflow.py (3)
  • Workflow (42-169)
  • run (98-115)
  • result_with_steps (117-134)
src/nat/builder/context.py (6)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
  • set_observability_context (309-316)
examples/observability/cross_workflow_tracking/integrated_example.py (6)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (10)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/builder/context.py (6)
  • Context (122-329)
  • ContextState (66-119)
  • observability_context (114-115)
  • observability_context (296-307)
  • get (118-119)
  • get (319-329)
src/nat/observability/processor/cross_workflow_processor.py (4)
  • CrossWorkflowProcessor (25-110)
  • WorkflowRelationshipProcessor (113-171)
  • process (35-110)
  • process (122-171)
src/nat/builder/workflow.py (1)
  • run (98-115)
examples/observability/cross_workflow_tracking/example.py (2)
src/nat/observability/context.py (8)
  • ObservabilityContext (39-141)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • get_current_workflow (98-100)
  • get_workflow_depth (102-104)
  • add_attribute (94-96)
src/nat/builder/workflow.py (1)
  • run (98-115)
src/nat/observability/processor/cross_workflow_processor.py (3)
src/nat/observability/processor/processor.py (1)
  • Processor (27-74)
src/nat/utils/type_utils.py (1)
  • override (56-57)
src/nat/builder/context.py (5)
  • Context (122-329)
  • get (118-119)
  • get (319-329)
  • observability_context (114-115)
  • observability_context (296-307)
examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py (5)
src/nat/runtime/runner.py (4)
  • context (90-91)
  • result (130-131)
  • result (134-135)
  • result (137-168)
src/nat/observability/context.py (9)
  • ObservabilityContext (39-141)
  • create_root_context (54-67)
  • create_root_context (166-170)
  • add_attribute (94-96)
  • create_child_context (69-84)
  • create_child_context (173-181)
  • get_workflow_depth (102-104)
  • to_dict (106-122)
  • from_dict (125-141)
src/nat/runtime/loader.py (1)
  • load_workflow (97-117)
src/nat/builder/context.py (2)
  • observability_context (114-115)
  • observability_context (296-307)
src/nat/builder/workflow.py (1)
  • run (98-115)
🪛 Ruff (0.13.2)
src/nat/observability/workflow_utils.py

79-79: Do not catch blind exception: Exception

(BLE001)


140-140: Do not catch blind exception: Exception

(BLE001)


178-178: Consider moving this statement to an else block

(TRY300)


179-179: Do not catch blind exception: Exception

(BLE001)


202-203: try-except-pass detected, consider logging the exception

(S110)


202-202: Do not catch blind exception: Exception

(BLE001)


241-241: Do not catch blind exception: Exception

(BLE001)

examples/observability/cross_workflow_tracking/integrated_example.py

1-1: Shebang is present but file is not executable

(EXE001)


98-98: Consider moving this statement to an else block

(TRY300)


302-302: Do not catch blind exception: Exception

(BLE001)


329-329: Consider moving this statement to an else block

(TRY300)


330-330: Do not catch blind exception: Exception

(BLE001)

src/nat/observability/processor/cross_workflow_processor.py

107-107: Redundant exception object included in logging.exception call

(TRY401)


168-168: Redundant exception object included in logging.exception call

(TRY401)

examples/observability/cross_workflow_tracking/cross_workflow_tracking_example.py

1-1: Shebang is present but file is not executable

(EXE001)

🔇 Additional comments (3)
src/nat/observability/workflow_utils.py (1)

79-80: Blind Exception catches are justified here.

The static analysis tool flags these as BLE001 violations, but all instances correctly implement graceful fallback patterns where catching broad Exception is appropriate:

  • Lines 79-80, 140-141, 241-242: Defensive fallback to create a root context when Context.get() or accessing observability_context fails
  • Lines 179-180: Returns None when the execution context is unavailable
  • Lines 202-203: Silent pass for optional context mutation that may not be available

These patterns enable the observability utilities to work in contexts where the NAT execution environment may not be fully initialized, which is essential for cross-workflow propagation.

Also applies to: 140-141, 179-180, 202-203, 241-242

examples/observability/cross_workflow_tracking/integrated_example.py (1)

302-307: Broad Exception catches are acceptable in example code.

The static analysis tool flags these as BLE001 violations, but for demonstration/example code, catching broad Exception to display error messages and continue the example flow is a reasonable pattern. The examples are designed to be resilient and educational rather than production-grade.

Also applies to: 329-331

examples/observability/cross_workflow_tracking/example.py (1)

1-283: LGTM! Example demonstrates cross-workflow observability well.

The mock workflow orchestration correctly:

  • Creates and propagates ObservabilityContext hierarchically
  • Uses context to track workflow chains, depths, and timing
  • Demonstrates sequential, parallel, and nested workflow patterns
  • Shows practical usage of context serialization

The conditional logic for workflow name matching (lines 102-109) is now correct after the previous review's fix. No new issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request non-breaking Non-breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Propagate observability over several workflows
1 participant