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
2 changes: 1 addition & 1 deletion python/packages/ag-ui/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
dependencies = [
"agent-framework-core>=1.22.0,<2",
"ag-ui-protocol>=0.1.19,<0.2",
"fastapi>=0.121.0,<0.142.0",
"fastapi>=0.121.0,<0.163.0",
"httpx>=0.28.1,<1",
"sse-starlette>=3.4.5,<4",
"uvicorn[standard]>=0.30.0,<1"
Expand Down
2 changes: 1 addition & 1 deletion python/packages/devui/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
"agent-framework-core>=1.23.0,<2",
"openai>=2.45.0,<4",
"opentelemetry-sdk>=1.39.0,<2",
"fastapi>=0.115.0,<0.142.0",
"fastapi>=0.115.0,<0.163.0",
"uvicorn[standard]>=0.30.0,<1"
]

Expand Down
2 changes: 1 addition & 1 deletion python/packages/foundry/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies = [
"agent-framework-openai>=1.18.0,<2",
"aiohttp>=3.9,<4",
"azure-ai-inference>=1.0.0b9,<1.0.0b10",
"azure-ai-projects>=2.2.0,<2.8.0",
"azure-ai-projects>=2.2.0,<2.10.0",
"httpx>=0.28,<1",
]

Expand Down
2 changes: 1 addition & 1 deletion python/packages/hosting-responses/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies = [

[dependency-groups]
test = [
"fastapi>=0.115.0,<0.142.0",
"fastapi>=0.115.0,<0.163.0",
"httpx>=0.28.1",
]

Expand Down
6 changes: 3 additions & 3 deletions python/packages/lab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ math = [
[dependency-groups]
dev = [
"agent-framework-openai>=1.14.2,<2",
"uv==0.13.5",
"ruff==0.17.1",
"uv==0.13.6",
"ruff==0.17.3",
"pytest==9.1.1",
"pytest-asyncio==1.4.0",
"pytest-asyncio==1.4.1",
"pytest-cov==7.1.0",
"mypy==2.4.0",
"pyright==1.1.414",
Expand Down
187 changes: 94 additions & 93 deletions python/packages/lab/uv.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion python/packages/monty/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ from agent_framework.monty import (
(mirrors `HyperlightCodeActProvider.workspace_root`)
- `file_mounts` — sequence of `FileMountInput` (str shorthand,
`(host_path, mount_path)` tuple, or `FileMount`)
- `resource_limits` — Monty `ResourceLimits` TypedDict
- `resource_limits` — Monty 1.x `ResourceLimits` keys; legacy `max_duration_secs`
maps to `max_feed_duration_secs`, and supplying both is an error
- `include_detailed_errors` — keyword-only Boolean, default `False`. Host failures become generic
`RuntimeError`s and execution errors omit raw details. Trusted opt-in preserves original diagnostics.
The flag is not a code input, is independent of outer-client error/logging settings, and is copied
Expand Down
17 changes: 11 additions & 6 deletions python/packages/monty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ codeact = MontyCodeActProvider(
write_bytes_limit=10 * 1024 * 1024,
),
],
resource_limits={ # Monty ResourceLimits TypedDict
"max_duration_secs": 5.0,
resource_limits={ # Monty 1.x ResourceLimits TypedDict
"max_feed_duration_secs": 5.0,
"max_memory": 64 * 1024 * 1024,
},
)
Expand All @@ -224,10 +224,15 @@ codeact = MontyCodeActProvider(
attachment capture, not filesystem traversal, disk use, or all host memory.
- `overlay` mounts buffer writes in memory (nothing leaks to the host and
nothing is captured). `read-only` mounts reject writes.
- **`resource_limits`** is forwarded straight to Monty's
[`ResourceLimits`](https://github.com/pydantic/monty) TypedDict
(`max_duration_secs`, `max_memory`, `gc_interval`, `max_recursion_depth`,
`max_suspensions`). These interpreter limits do not independently bound
- **`resource_limits`** accepts Monty 1.x
[`ResourceLimits`](https://github.com/pydantic/monty) keys. Monty 1.0 or later
is required. `max_feed_duration_secs` limits total sandbox execution time
across host round trips; `max_turn_duration_secs` resets at each host round
trip. Both exclude time spent waiting on the host. The documented legacy
`max_duration_secs` option remains supported as an alias for
`max_feed_duration_secs`; specifying both is an error. Other keys include
`max_memory`, `gc_interval`, `max_recursion_depth`, `max_suspensions`, and
`max_total_sleep_secs`. These interpreter limits do not independently bound
host-side post-execution attachment capture.

## DSL inside `execute_code`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,12 @@ async def run(self, code: str) -> dict[str, Any]:
"type_check_stubs": self.type_stubs,
}
if self._resource_limits:
checkout_kwargs["limits"] = self._resource_limits
limits = dict(self._resource_limits)
if "max_duration_secs" in limits:
if "max_feed_duration_secs" in limits:
raise ValueError("Specify only one of max_duration_secs and max_feed_duration_secs.")
limits["max_feed_duration_secs"] = limits.pop("max_duration_secs")
checkout_kwargs["limits"] = limits

feed_kwargs: dict[str, Any] = {"print_callback": printer}
if self._mounts:
Expand Down
2 changes: 1 addition & 1 deletion python/packages/monty/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
]
dependencies = [
"agent-framework-core>=1.19.0,<2",
"pydantic-monty>=0.0.23,<0.1",
"pydantic-monty>=1.0,<1.5",
]

[tool.uv]
Expand Down
1 change: 1 addition & 0 deletions python/packages/monty/tests/monty/test_monty_codeact.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from agent_framework import Content, FunctionTool, Message, tool
from agent_framework._sessions import SessionContext
from agent_framework.exceptions import ToolException

from agent_framework_monty import MontyCodeActProvider, MontyExecuteCodeTool
from agent_framework_monty import _execute_code_tool as execute_code_module
from agent_framework_monty import _instructions as instructions_module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import pytest
from agent_framework import Agent, Content, FunctionTool, Message, tool
from agent_framework._sessions import SessionContext

from agent_framework_monty import MontyCodeActProvider, MontyExecuteCodeTool


Expand Down
62 changes: 62 additions & 0 deletions python/packages/monty/tests/monty/test_monty_runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Copyright (c) Microsoft. All rights reserved.

"""Local runtime regressions that do not require external services."""

from __future__ import annotations

from typing import Any

import pytest
from pydantic_monty import MontyRuntimeError

from agent_framework_monty import MontyExecuteCodeTool
from agent_framework_monty._monty_bridge import InlineCodeBridge


@pytest.mark.parametrize(
"resource_limits",
[
{"max_duration_secs": 5.0, "max_memory": 64 * 1024 * 1024},
{"max_feed_duration_secs": 5.0},
{"max_turn_duration_secs": 5.0},
{"max_duration_secs": 5.0, "max_turn_duration_secs": 1.0},
{"max_duration_secs": None},
],
)
async def test_duration_limits_allow_execution_without_mutating_configuration(resource_limits: dict[str, Any]) -> None:
original_limits = dict(resource_limits)
execute_code = MontyExecuteCodeTool(resource_limits=resource_limits)

contents = await execute_code._run_code(code="print('completed')")

assert not any(content.type == "error" for content in contents)
assert any(content.type == "text" and "completed" in (content.text or "") for content in contents)
assert resource_limits == original_limits
assert execute_code.resource_limits == original_limits
assert execute_code.build_serializable_state()["resource_limits"] == original_limits


@pytest.mark.parametrize(
("duration_key", "limit_scope"),
[
("max_duration_secs", "feed"),
("max_feed_duration_secs", "feed"),
("max_turn_duration_secs", "turn"),
],
)
async def test_duration_limits_stop_execution_with_a_timeout(duration_key: str, limit_scope: str) -> None:
bridge = InlineCodeBridge({}, resource_limits={duration_key: 0.001})

with pytest.raises(MontyRuntimeError, match=f"TimeoutError: {limit_scope} time limit exceeded"):
await bridge.run("while True:\n pass")


@pytest.mark.parametrize("legacy_duration", [None, 5.0])
async def test_legacy_and_feed_duration_limits_are_rejected_as_ambiguous(legacy_duration: float | None) -> None:
bridge = InlineCodeBridge(
{},
resource_limits={"max_duration_secs": legacy_duration, "max_feed_duration_secs": 1.0},
)

with pytest.raises(ValueError, match="Specify only one of max_duration_secs and max_feed_duration_secs"):
await bridge.run("print('must not run')")
2 changes: 1 addition & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies = [
dev = [
"uv==0.13.5",
"pytest==9.1.1",
"pytest-asyncio==1.4.0",
"pytest-asyncio==1.4.1",
"pytest-cov==7.1.0",
"pytest-xdist[psutil]==3.8.0",
"pytest-timeout==2.4.0",
Expand Down
2 changes: 1 addition & 1 deletion python/tooling/requirements-quality.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruff==0.17.1
ruff==0.17.3
2 changes: 1 addition & 1 deletion python/tooling/requirements-typing.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mypy==2.4.0
pyrefly==1.4.0
pyright==1.1.414
ty==0.0.90
ty==0.0.92
zuban==0.10.1
Loading
Loading