Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,14 @@ async def run_non_interactive(*args, **kwargs):
"""Enforce the managed headless boundary at the final Python call site."""
settings.shell_allow_list = None
kwargs["startup_cmd"] = None
kwargs["model_params"] = None
from deepagents_code.config import CLI_MAX_RETRIES_KEY

model_params = kwargs.get("model_params")
kwargs["model_params"] = (
{CLI_MAX_RETRIES_KEY: model_params[CLI_MAX_RETRIES_KEY]}
if isinstance(model_params, dict) and CLI_MAX_RETRIES_KEY in model_params
else None
)
kwargs["profile_override"] = None
kwargs["sandbox_type"] = "none"
from deepagents_code._nemoclaw_managed import managed_mcp_config_path
Expand Down
2 changes: 2 additions & 0 deletions docs/get-started/quickstart-langchain-deepagents-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ They disable Deep Agents Code package update checks and the LangGraph server ver
The managed model constructor accepts only Deep Agents Code's `openai` provider path and reads its endpoint from a root-owned image file.
It supplies the non-secret gateway placeholder key and ignores mutable provider classes, credentials, endpoints, and constructor parameters in Deep Agents Code config.
CLI and TUI model parameter overrides and custom rubric models are blocked.
For headless runs, `--max-retries` remains available after Deep Agents Code validates the retry count.
The managed boundary discards every other model parameter, including credentials, endpoints, and provider settings.
Project and user-defined subagents remain available, but they inherit the managed chat model instead of accepting their own model override.
MCP servers registered through `nemoclaw <sandbox-name> mcp add` remain available through NemoClaw's dedicated `/sandbox/.deepagents/.nemoclaw-mcp.json` projection and OpenShell egress policy.
Project and user MCP files are never auto-loaded.
Expand Down
1 change: 1 addition & 0 deletions test/helpers/langchain-deepagents-code-patch-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ from typing import Any
from urllib.parse import urlparse

_dotenv_loaded_values = {}
CLI_MAX_RETRIES_KEY = "__deepagents_cli_max_retries__"


def _preview_dotenv_environ(*, start_path=None):
Expand Down
59 changes: 59 additions & 0 deletions test/langchain-deepagents-code-retry-boundary.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { execFileSync } from "node:child_process";
import { afterEach, describe, expect, it } from "vitest";
import {
cleanupPackageFixtures,
createPackageFixture,
patchFixture,
} from "./helpers/langchain-deepagents-code-patch-fixture";

afterEach(cleanupPackageFixtures);

describe("LangChain Deep Agents Code managed retry boundary", () => {
it("preserves only the parsed retry count in headless runs (#7414)", () => {
const tempDir = createPackageFixture();
patchFixture(tempDir);
const validation = `
import asyncio

from deepagents_code.client import non_interactive
from deepagents_code.config import CLI_MAX_RETRIES_KEY


async def validate():
retry_params = await non_interactive.run_non_interactive(
"message",
"assistant",
model_params={
CLI_MAX_RETRIES_KEY: 4,
"api_key": "secret",
"base_url": "https://attacker.example",
"model_provider": "attacker",
},
)
assert retry_params["model_params"] == {CLI_MAX_RETRIES_KEY: 4}

blocked_params = await non_interactive.run_non_interactive(
"message",
"assistant",
model_params={
"api_key": "secret",
"base_url": "https://attacker.example",
"model_provider": "attacker",
},
)
assert blocked_params["model_params"] is None


asyncio.run(validate())
print("managed-retry-boundary-ok")
`;
const output = execFileSync("python3", ["-c", validation], {
env: { PATH: process.env.PATH, PYTHONPATH: tempDir },
encoding: "utf8",
});
expect(output).toContain("managed-retry-boundary-ok");
});
});
Loading