Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e1abda3
Add deepagents extra
dagardner-nv May 12, 2026
92ca845
Import improvement
dagardner-nv May 12, 2026
d8912c9
WIP: First pass at deep agents integration
dagardner-nv May 12, 2026
3ce8bc7
Use a nim model, remove unnecessary helper method
dagardner-nv May 12, 2026
649e9f8
Rename helper method
dagardner-nv May 12, 2026
2e2b9f0
Document constants, remove unneeded truncation
dagardner-nv May 12, 2026
561eb93
Add protocol type hints
dagardner-nv May 12, 2026
4d4435c
Cleanups to callbacks class, distinguish between hitl and other graph…
dagardner-nv May 12, 2026
6a67371
Nitpicky formatting
dagardner-nv May 12, 2026
bc51450
Update attribs
dagardner-nv May 12, 2026
601e771
Move the subscribed_events fixture to be shared
dagardner-nv May 12, 2026
822bab7
WIP: [skip ci]
dagardner-nv May 12, 2026
445bdff
Formatting [skip ci]
dagardner-nv May 12, 2026
aa3b66e
Fix linting errors
dagardner-nv May 12, 2026
a1eecc3
Add deepagents in ci
dagardner-nv May 12, 2026
e609300
Add return type hint per code rabbit suggestion
dagardner-nv May 12, 2026
bdbe385
Install dependencies in the CI check stage needed for ty tool
dagardner-nv May 12, 2026
92e3908
Install OpenSSL via vcpkg for Windows-arm64
dagardner-nv May 12, 2026
59e01c8
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv May 12, 2026
c6f0eff
Add more tests
dagardner-nv May 12, 2026
9948131
Formatting
dagardner-nv May 12, 2026
47b225e
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv May 12, 2026
9b7e117
Apply suggestions from coderabbit
dagardner-nv May 12, 2026
b3aba0d
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv May 13, 2026
dd2e39c
Merge branch 'release/0.2' of github.com:NVIDIA/NeMo-Flow into david-…
dagardner-nv May 14, 2026
05ac673
Record skills as tool scopes not marks
dagardner-nv May 14, 2026
e5faedb
Replace tool marks with scopes
dagardner-nv May 14, 2026
5fe997f
Remove redundant backend.py
dagardner-nv May 14, 2026
cb3ef49
Remove overloads of wrap_tool_call and awrap_tool_call and therefore …
dagardner-nv May 14, 2026
0606d75
WIP
dagardner-nv May 14, 2026
e175f04
Set to warning not debug, failing to pop due to a failure in _prepare…
dagardner-nv May 14, 2026
9c49a65
Handle pydantic classes in _serialize_value
dagardner-nv May 14, 2026
95bf464
Handle langgraph command and send types in prepare_outputs
dagardner-nv May 14, 2026
ab8a14b
Update lock
dagardner-nv May 14, 2026
cd2ec54
Remove unused methods
dagardner-nv May 14, 2026
7016a3c
Formatting
dagardner-nv May 14, 2026
869edaf
Expand e2e test to include a subagent
dagardner-nv May 14, 2026
08870f7
Improve event validation
dagardner-nv May 14, 2026
85577cc
Merge branch 'release/0.2' of github.com:NVIDIA/NeMo-Flow into david-…
dagardner-nv May 14, 2026
342b367
Move documentation
dagardner-nv May 14, 2026
c478f91
Forgot to check in moved file
dagardner-nv May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ jobs:
run: |
set -e
uv tool install pre-commit==${{ steps.ci-config.outputs.pre_commit_version }}
uv sync --inexact --no-install-project --no-install-package nemo-flow --extra langchain --extra langgraph --extra deepagents
if [[ "$FULL_CI" == "true" || -z "$PRE_COMMIT_BASE" ]]; then
pre-commit run --all-files --show-diff-on-failure
else
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ jobs:
set -e
UV_PYTHON_DOWNLOADS=manual uv python install --managed-python ${{ steps.ci-config.outputs.default_python_version }}

- name: Install Windows ARM64 OpenSSL
if: ${{ matrix.platform == 'windows-arm64' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$vcpkgRoot = Join-Path $env:RUNNER_TEMP 'vcpkg'
git clone https://github.com/microsoft/vcpkg $vcpkgRoot
git -C $vcpkgRoot checkout --detach 56bb2411609227288b70117ead2c47585ba07713
& (Join-Path $vcpkgRoot 'bootstrap-vcpkg.bat') -disableMetrics
& (Join-Path $vcpkgRoot 'vcpkg.exe') install openssl:arm64-windows-static-md --clean-after-build --disable-metrics

$opensslDir = Join-Path $vcpkgRoot 'installed\arm64-windows-static-md'
Add-Content -Path $env:GITHUB_ENV -Value "OPENSSL_DIR=$opensslDir"
Add-Content -Path $env:GITHUB_ENV -Value "OPENSSL_STATIC=1"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_ROOT=$vcpkgRoot"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKGRS_TRIPLET=arm64-windows-static-md"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

- name: Run Python tests with coverage
working-directory: ${{ env.NEMO_FLOW_CI_WORKSPACE }}
run: just --set ci true --set output_dir "${{ github.workspace }}" test-python
Expand Down
1,215 changes: 1,120 additions & 95 deletions ATTRIBUTIONS-Python.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ build-python:
#!/usr/bin/env bash
{{ bash_helpers }}
cd "$NEMO_FLOW_REPO_ROOT"
uv sync --inexact --no-install-project --no-install-package nemo-flow --extra langchain
uv sync --inexact --no-install-project --no-install-package nemo-flow --extra langchain --extra langgraph --extra deepagents
activate_project_venv
if is_true "{{ ci }}"; then
prepare_llvm_cov_workspace
Expand Down Expand Up @@ -779,7 +779,7 @@ test-python:
fi
cargo test -p nemo-flow-python --lib
fi
uv sync --inexact --no-install-project --no-install-package nemo-flow --extra langchain
uv sync --inexact --no-install-project --no-install-package nemo-flow --extra langchain --extra langgraph --extra deepagents
activate_project_venv
python_executable="$(project_python_executable)"
use_project_python_source "$python_executable"
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ langgraph = [
"langgraph>=1.1.10,<2.0.0",
]

deepagents = [
"nemo-flow[langgraph]",
"deepagents>=0.5.3,<0.6.0",
]

langchain-nvidia = [
"nemo-flow[langchain]",
"langchain-nvidia-ai-endpoints~=1.0",
Expand Down
67 changes: 67 additions & 0 deletions python/nemo_flow/integrations/deepagents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# NeMo Flow Deep Agents Integration

This directory contains the `nemo_flow.integrations.deepagents` package, which
adds Deep Agents-specific observability on top of the LangChain and LangGraph
integrations.

Deep Agents is built on LangChain `AgentMiddleware` and the LangGraph runtime.
The integration therefore composes the existing NeMo Flow LangChain and
LangGraph hooks, then emits stable mark events for Deep Agents concepts:
subagents, async subagents, human-in-the-loop interrupts and resumes, skills,
filesystem operations, and sandbox execution.

## Setup

```bash
uv sync --all-groups --extra deepagents
just build-python
```

## Usage Example

```python
import nemo_flow
from deepagents import create_deep_agent
from nemo_flow.integrations.deepagents import (
NemoFlowDeepAgentsCallbackHandler,
add_nemo_flow_integration,
)

agent = create_deep_agent(
**add_nemo_flow_integration(
model="nvidia:nvidia/nemotron-3-nano-30b-a3b",
tools=[],
skills=["/skills/research/"],
name="main-agent",
)
)

with nemo_flow.scope.scope("deepagents-request", nemo_flow.ScopeType.Agent):
result = agent.invoke(
{"messages": [{"role": "user", "content": "Research recent GPU news"}]},
config={"callbacks": [NemoFlowDeepAgentsCallbackHandler()]},
)
```

## What Is Captured

- LangChain model and tool calls through NeMo Flow managed execution.
- LangGraph run scopes through callbacks.
- Human-in-the-loop interrupt and resume marks.
- Deep Agents `task` delegation marks for synchronous subagents.
- Deep Agents async subagent tool lifecycle marks.
- Filesystem tool marks for `ls`, `read_file`, `write_file`, `edit_file`,
`glob`, and `grep`.
- Sandbox/local shell `execute` marks.
- Configured skills and subagent summaries at agent-run start.
- Direct backend method calls when the backend is wrapped with
`observe_backend()` or via `add_nemo_flow_integration(..., backend=...)`.

Remote async subagents still need NeMo Flow instrumentation in the remote graph
or process to capture their internal model and tool calls. Supervisor-side
observability captures the task lifecycle and returned status.
127 changes: 127 additions & 0 deletions python/nemo_flow/integrations/deepagents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""NeMo Flow integrations for Deep Agents."""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import Any

from nemo_flow.integrations.deepagents.backend import NemoFlowDeepAgentsBackend, observe_backend
from nemo_flow.integrations.deepagents.callbacks import NemoFlowDeepAgentsCallbackHandler
from nemo_flow.integrations.deepagents.middleware import NemoFlowDeepAgentsMiddleware


def add_nemo_flow_integration(
kwargs: Mapping[str, Any] | None = None,
*,
instrument_subagents: bool = True,
instrument_backend: bool = True,
**overrides: Any,
) -> dict[str, Any]:
"""
Receives the keyword arguments for ``create_deep_agent`` and returns them with NeMo Flow observability attached.

Use this helper as ``create_deep_agent(**add_nemo_flow_integration(...))``.
It injects Deep Agents-aware middleware at the top level, adds the same
middleware to dictionary-style custom subagents that do not inherit parent
middleware, and optionally wraps the backend.
"""
observed = dict(kwargs or {})
observed.update(overrides)

skills = _string_sequence(observed.get("skills"))
subagents = list(observed.get("subagents") or ())
subagent_summaries = [_subagent_summary(subagent) for subagent in subagents]
backend = observed.get("backend")

if isinstance(backend, NemoFlowDeepAgentsBackend):
observed["backend"] = backend
backend_name = type(backend.__wrapped__).__name__
elif instrument_backend and backend is not None:
observed["backend"] = observe_backend(backend)
backend_name = type(backend).__name__
elif backend is not None:
backend_name = type(backend).__name__
else:
backend_name = None

middleware = list(observed.get("middleware") or ())
_append_middleware(
middleware,
NemoFlowDeepAgentsMiddleware(
agent_name=observed.get("name"),
skills=skills,
subagents=subagent_summaries,
backend_name=backend_name,
),
)
observed["middleware"] = middleware

if instrument_subagents and subagents:
observed["subagents"] = [_instrument_subagent(subagent) for subagent in subagents]

return observed


def _append_middleware(middleware: list[Any], new_middleware: NemoFlowDeepAgentsMiddleware) -> None:
if any(isinstance(item, NemoFlowDeepAgentsMiddleware) for item in middleware):
return
middleware.append(new_middleware)


def _instrument_subagent(subagent: Any) -> Any:
if not isinstance(subagent, dict):
return subagent

observed = dict(subagent)
middleware = list(observed.get("middleware") or ())
_append_middleware(
middleware,
NemoFlowDeepAgentsMiddleware(
agent_name=observed.get("name"),
skills=_string_sequence(observed.get("skills")),
subagents=None,
),
)
observed["middleware"] = middleware
return observed


def _subagent_summary(subagent: Any) -> Mapping[str, Any]:
if isinstance(subagent, Mapping):
summary: dict[str, Any] = {"type": "subagent"}
for key in ("name", "description", "model", "graph_id", "url"):
value = subagent.get(key)
if value is not None:
summary[key] = value
if "skills" in subagent:
summary["skills"] = _string_sequence(subagent.get("skills"))
return summary

summary = {"type": type(subagent).__name__}
for attr in ("name", "description", "graph_id", "url"):
value = getattr(subagent, attr, None)
if value is not None:
summary[attr] = value
return summary


def _string_sequence(value: Any) -> Sequence[str] | None:
if value is None:
return None
if isinstance(value, str):
return [value]
if isinstance(value, Sequence):
return [str(item) for item in value]
return [str(value)]


__all__ = [
"NemoFlowDeepAgentsBackend",
"NemoFlowDeepAgentsCallbackHandler",
"NemoFlowDeepAgentsMiddleware",
"add_nemo_flow_integration",
"observe_backend",
]
Loading
Loading