Skip to content
Open
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
4 changes: 2 additions & 2 deletions envs/echo_env/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ version = "0.1.0"
description = "Echo Environment for OpenEnv - simple test environment that echoes back messages"
requires-python = ">=3.10"
dependencies = [
# Core OpenEnv dependencies (required for server functionality)
"openenv[core]>=0.2.2",
# Core OpenEnv dependencies (required for server functionality).
"openenv[core]>=0.3.1",
"fastapi>=0.115.0",
"pydantic>=2.0.0",
"uvicorn>=0.24.0",
Expand Down
12 changes: 6 additions & 6 deletions envs/echo_env/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions examples/modal_echo_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""Hello-world example running the Echo environment on Modal.

Boots the echo-env server inside a Modal sandbox via ``ModalProvider``, then
talks to it through ``EchoEnv`` over the encrypted Modal tunnel (https/wss).

Usage:
pip install "modal>=1.4"
modal setup (one-time auth)
PYTHONPATH=src:envs uv run python examples/modal_echo_env.py

Requires:
A configured Modal account/token (see https://modal.com/docs/guide).
"""

import asyncio

from echo_env import EchoEnv
from openenv.core.containers.runtime.modal_provider import ModalProvider


async def _interact(base_url: str) -> None:
"""Run the async WebSocket interaction against the running sandbox."""
async with EchoEnv(base_url=base_url) as env:
await env.reset()

tools = await env.list_tools()
print("Available tools:", [t.name for t in tools])

echoed = await env.call_tool("echo_message", message="Hello, World!")
print("echo_message ->", echoed)


def main() -> int:
image = ModalProvider.image_from_dockerfile("envs/echo_env/server/Dockerfile")

# Provision the sandbox synchronously. The Modal SDK's blocking API warns
# when driven from inside a running event loop, so the provider lifecycle is
# kept out of asyncio; only the WebSocket client runs under asyncio.run().
# (The first run builds the image and cold-starts the sandbox, which can
# take a minute with no output - the prints below show progress.)
provider = ModalProvider(app_name="openenv-echo")
print("Starting Modal sandbox (building image on first run)...", flush=True)
base_url = provider.start_container(image)
print(f"Sandbox up at {base_url} - waiting for server...", flush=True)
provider.wait_for_ready(base_url, timeout_s=180)
print("Server ready.", flush=True)
try:
asyncio.run(_interact(base_url))
finally:
print("Stopping sandbox...", flush=True)
provider.stop_container()

return 0


if __name__ == "__main__":
raise SystemExit(main())
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ aca = [
"azure-containerapps-sandbox>=0.1.0b2,<0.2.0",
"azure-identity>=1.23.0",
]
modal = [
"modal>=1.4.0",
"pyyaml>=6.0",
]
inspect = [
"inspect-ai>=0.3.0",
]
Expand Down
Loading