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
5 changes: 5 additions & 0 deletions .agentenv/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ agent_default = "local" # agents too
# default_a2a_agent_id = "my-agent" # what a deploy_agent step that names no agent deploys; by default the
# # agents bundle's Claude Code, @local/agentenv-framework/agents/claude-code

# [envs]
# default_gateway_env_id = "default" # the gateway every gateway deploy runs
# default_service_db_env_id = "default-db" # the service-db local Postgres state runs from
# default_website_browser_env_id = "playwright-mcp" # the website browser a deploy with websites adds

# [model]
# base_url = "http://localhost:4000" # a model proxy on this machine; local containers reach it as host.docker.internal
# # on Linux, bind it to the Docker bridge address and use that: http://172.17.0.1:4000
Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ updates:
directories:
- "/src/agent_env/env/gateway"
- "/src/agent_env/env/envs/service_db"
- "/src/agent_env/env/envs/website_browser"
- "/src/agent_env/env/envs/playwright_mcp"
- "/src/agent_env/agents/agents/claude-code"
- "/src/agent_env/agents/agents/codex"
- "/src/agent_env/agents/agents/gemini"
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/infra_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"agentenv-service-db": "src/agent_env/env/envs/service_db/Dockerfile",
"agentenv-db-web": "src/agent_env/env/envs/service_db/Dockerfile.db-web",
"agentenv-db-mcp": "src/agent_env/env/envs/service_db/Dockerfile.db-mcp",
"agentenv-website-browser": "src/agent_env/env/envs/website_browser/Dockerfile",
"agentenv-playwright-mcp": "src/agent_env/env/envs/playwright_mcp/Dockerfile",
"agentenv-claude-code": "src/agent_env/agents/agents/claude-code/Dockerfile",
"agentenv-codex": "src/agent_env/agents/agents/codex/Dockerfile",
"agentenv-gemini": "src/agent_env/agents/agents/gemini/Dockerfile",
Expand Down Expand Up @@ -68,9 +68,9 @@
"agentenv-gateway": ["docker", "run", "--rm", "--entrypoint", "python", "{image}", "-c", "import gateway.entrypoint"],
"agentenv-db-web": ["docker", "run", "--rm", "--entrypoint", "/usr/bin/pgweb", "{image}", "--version"],
"agentenv-db-mcp": ["docker", "run", "--rm", "{image}", "--help"],
"agentenv-website-browser": ["docker", "run", "--rm", "--entrypoint", "sh", "{image}", "-c",
'npx --no-install @playwright/mcp@"$PLAYWRIGHT_MCP_VERSION" --help >/dev/null'
" && ls -d /root/.cache/ms-playwright/chromium-*"],
"agentenv-playwright-mcp": ["docker", "run", "--rm", "--entrypoint", "sh", "{image}", "-c",
'npx --no-install @playwright/mcp@"$PLAYWRIGHT_MCP_VERSION" --help >/dev/null'
" && ls -d /root/.cache/ms-playwright/chromium-*"],
"agentenv-claude-code": ["docker", "run", "--rm", "--entrypoint", "sh", "{image}", "-c",
'claude --version && python -c "import a2a_server"'],
"agentenv-codex": ["docker", "run", "--rm", "--entrypoint", "sh", "{image}", "-c",
Expand Down
7 changes: 4 additions & 3 deletions .github/scripts/infra_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
import sys
from pathlib import Path

from agent_env.env.bootstrap import PUBLISHED_IMAGES
from agent_env.env.bootstrap import PLAYWRIGHT_MCP_DOCKERFILE, PUBLISHED_IMAGES
from agent_env.env.published_images import BUNDLE_IMAGES, REGISTRY, PublishedImage, write_release_digests
from agent_env.store.image_store.oci_registry_credentials import RegistryAuth
from agent_env.store.image_store.registry_api import NoSuchImageError, RegistryRefusedError, pin_digest

REPO = Path(__file__).resolve().parents[2]
# "agents" is the images that install the protocol a release publishes: the shipped bundles', the agents' and an
# example env's, and the website browser's, whose recorder installs it.
_AFTER_PROTOCOL = BUNDLE_IMAGES + tuple(image for image in PUBLISHED_IMAGES if image.name == "agentenv-website-browser")
# example env's, and the Playwright MCP browser's, whose recorder installs it, matched by its Dockerfile.
_AFTER_PROTOCOL = BUNDLE_IMAGES + tuple(image for image in PUBLISHED_IMAGES
if image.dockerfile == PLAYWRIGHT_MCP_DOCKERFILE)
_GROUPS = {"infra": tuple(image for image in PUBLISHED_IMAGES if image not in _AFTER_PROTOCOL), "agents": _AFTER_PROTOCOL}


Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ or one of them only where it is used.
endpoint serving Anthropic's Messages API, such as a LiteLLM gateway. It declares `install/v1` too: its
folder's `install.sh` installs it into a task container or onto a VM sandbox's host, with its own Claude
Code, uv and Python, adding bash only to a target with neither bash nor zsh.
- `[envs] default_gateway_env_id`, `default_service_db_env_id`, `default_website_browser_env_id`: the infra envs a
deploy runs on; built-in `default`, `default-db` and `playwright-mcp`, the Playwright MCP website browser a deploy
with websites adds. An id set in code, by `configure(...)` or the config attribute (as `agent-env env deploy
--gateway` does), beats the file. Within a run, its `user_overrides` (`gateway_env_id`, `service_db_env_id`,
`website_browser_env_id`) beat both.
- Secrets never live in the file: use `secret:KEY` or `env:NAME` references, resolved through
`[stores.secret]`.
- agent-env has no stage concept: "dev" versus "prod" is which file `AGENT_ENV_CONFIG` names. A
Expand Down
12 changes: 9 additions & 3 deletions src/agent_env/bundle/preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
env_images,
infra_to_set_up,
needs_docker,
put_command,
put_hint,
ships,
stores_are_local,
)
from agent_env.env.env import Env
Expand Down Expand Up @@ -429,16 +430,21 @@ def _refusal(self, link: SandboxProvider, form: Accepts, image: _Image, *, vms_l
def _remote_infra(self, link: SandboxProvider, kinds: set[str]) -> list[str]:
"""Why ``link``, a provider not on this machine, can't run the infra envs of ``kinds``. One the run sets up, in
local stores, isn't checked: it's written anew, pulled from its registry or built, with its build context."""
set_up = {setup.kind for setup in infra_to_set_up(kinds)} if stores_are_local() else set()
local = stores_are_local()
# A website browser agent-env doesn't ship is never set up; missing from local stores, finish() reports it.
shipped = {kind for kind in kinds if ships(kind, default_env_id(kind))}
set_up = {setup.kind for setup in infra_to_set_up(shipped)} if local else set()
found = []
for kind in sorted(kinds - set_up):
env_id = default_env_id(kind)
try:
env = Env.get(env_id)
except NotFoundError:
if local and kind not in shipped:
continue
found.append(f"deploys on the {_shown(link)} sandbox provider, which needs the {kind} env {env_id!r}, "
f"and the store doesn't hold it; agent-env builds infra envs only into local stores, so put "
f"it with `{put_command(kind)}`")
f"it with {put_hint(kind)}")
continue
accepts = EnvironmentGatewayProvider.accepts(link)
found += [refusal for image in env_images(env)
Expand Down
2 changes: 0 additions & 2 deletions src/agent_env/cli/env/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from .snapshot import snapshot
from .state import init_env_state, teardown_env_state
from .website import website
from .website_browser import website_browser


@click.group()
Expand All @@ -30,4 +29,3 @@ def env():
env.add_command(snapshot)
env.add_command(teardown_env_state)
env.add_command(website)
env.add_command(website_browser)
7 changes: 6 additions & 1 deletion src/agent_env/cli/env/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
help="Override default_service_db_env_id (on Modal its images must be in the configured image store)")
@click.option("--gateway", "gateway_env_id", default=None,
help="Override default_gateway_env_id")
@click.option("--website-browser", "website_browser_env_id", default=None,
help="Override default_website_browser_env_id, the browser a deploy with websites adds")
@click.option("--disk-size-gb", type=float, default=10.0, show_default=True,
help="Sandbox VM disk size in GB. Large universes (e.g. github/gdrive "
"root file trees) need more than the 10GB default.")
Expand All @@ -46,7 +48,7 @@
@click.option("--env-state-instance-id", default=None,
help="Attach against an EXISTING env state instance instead of creating a fresh store "
"(its own type selects the provider).")
def deploy(env_id: str, env_version: int | None, ttl_seconds: int, gateway_mode: str, sandbox: str, service_db_env_id: str | None, gateway_env_id: str | None, disk_size_gb: float, cpu: float | None, memory_mb: int | None, env_state_type: str | None, env_state_instance_id: str | None):
def deploy(env_id: str, env_version: int | None, ttl_seconds: int, gateway_mode: str, sandbox: str, service_db_env_id: str | None, gateway_env_id: str | None, website_browser_env_id: str | None, disk_size_gb: float, cpu: float | None, memory_mb: int | None, env_state_type: str | None, env_state_instance_id: str | None):
"""Deploy an MCP server environment."""
from agent_env.providers import build_sandbox_provider
from agent_env.config import get_config
Expand All @@ -64,6 +66,9 @@ def deploy(env_id: str, env_version: int | None, ttl_seconds: int, gateway_mode:
if gateway_env_id:
get_config().default_gateway_env_id = gateway_env_id
click.echo(f"Override default_gateway_env_id: {gateway_env_id}")
if website_browser_env_id:
get_config().default_website_browser_env_id = website_browser_env_id
click.echo(f"Override default_website_browser_env_id: {website_browser_env_id}")

click.echo(f"Fetching env: id={env_id} version={env_version or 'latest'}...")
env = Env.get(env_id, version=env_version)
Expand Down
31 changes: 0 additions & 31 deletions src/agent_env/cli/env/website_browser.py

This file was deleted.

9 changes: 5 additions & 4 deletions src/agent_env/cli/task/_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from agent_env.bundle.preflight import stored_infra
from agent_env.bundle.requires import UnmetRequirements
from agent_env.entity.refs import EntityKind, RefRole, ref_sites
from agent_env.config.runtime import GATEWAY_OVERRIDE, SERVICE_DB_OVERRIDE
from agent_env.env.bootstrap import GATEWAY, SERVICE_DB, ensure_default_envs, stores_are_local
from agent_env.config.runtime import GATEWAY_OVERRIDE, SERVICE_DB_OVERRIDE, WEBSITE_BROWSER_OVERRIDE
from agent_env.env.bootstrap import GATEWAY, SERVICE_DB, WEBSITE_BROWSER, ensure_default_envs, stores_are_local
from agent_env.env.env import Env
from agent_env.plugins._requirements import unmet_problems
from agent_env.store.base import NotFoundError
Expand All @@ -32,7 +32,7 @@ def runnable_task(task_id: str, version: int | None, metadata: Mapping[str, Any]
"""The stored task ``task_id`` at ``version``, or its latest, once what it requires is installed and every env and
artifact type the steps a run with ``metadata`` runs read loads here. With local stores, the infra envs those steps
deploy on that the store doesn't hold are set up, but for one the run is given its own of (``gateway_env_id``,
``service_db_env_id``); one it holds is left as it is, for ``agent-env up`` or ``run`` to rebuild. Raises
``service_db_env_id``, ``website_browser_env_id``); one it holds is left as it is, for ``agent-env up`` or ``run`` to rebuild. Raises
UnmetRequirements naming the command that installs what it requires, or a ClickException for a type nothing here
registers."""
try:
Expand All @@ -51,7 +51,8 @@ def runnable_task(task_id: str, version: int | None, metadata: Mapping[str, Any]
raise UnmetRequirements(problems, unmet)
if problems:
raise click.ClickException("\n".join(problems))
given = {kind for kind, key in ((GATEWAY, GATEWAY_OVERRIDE), (SERVICE_DB, SERVICE_DB_OVERRIDE)) if overrides.get(key)}
given = {kind for kind, key in ((GATEWAY, GATEWAY_OVERRIDE), (SERVICE_DB, SERVICE_DB_OVERRIDE),
(WEBSITE_BROWSER, WEBSITE_BROWSER_OVERRIDE)) if overrides.get(key)}
if stores_are_local() and (kinds := stored_infra(steps, overrides) - given):
ensure_default_envs(kinds, say=say, missing_only=True)
return task
Expand Down
8 changes: 6 additions & 2 deletions src/agent_env/cli/task/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from agent_env.cli._install_prompt import offer_to_install
from agent_env.cli.banner import print_banner
from agent_env.cli.task._checks import runnable_task
from agent_env.config.runtime import GATEWAY_OVERRIDE, SERVICE_DB_OVERRIDE
from agent_env.config.runtime import GATEWAY_OVERRIDE, SERVICE_DB_OVERRIDE, WEBSITE_BROWSER_OVERRIDE
from agent_env.cli import submitted_runs
from agent_env.cli.identity import get_agent_env_client_id
from agent_env.cli.teardown_output import echo_teardown
Expand Down Expand Up @@ -364,6 +364,8 @@ def _stored(task, context: TaskStepContext) -> str:
help="Override default_gateway_env_id (required when --env-sandbox=modal until the default gateway image is rebuilt)")
@click.option("--service-db-env-id", default=None, type=str,
help="Override default_service_db_env_id (on Modal its images must be in the configured image store)")
@click.option("--website-browser-env-id", default=None, type=str,
help="Override default_website_browser_env_id, the browser a deploy with websites adds")
@click.option("--env-state-type", default=None, type=str,
help="Override the env state type for deploy_env steps")
@click.option("--env-state-instance-id", default=None, type=str,
Expand All @@ -372,7 +374,7 @@ def _stored(task, context: TaskStepContext) -> str:
help="Keep what a run deployed up when it ends, print it, and tear it down on Ctrl-C; while it holds, "
"another terminal can resume the run with --start-step and --context-json. Without it, each run "
"is torn down as it ends, as Ctrl-C mid-run always does.")
def run(task_id: str, task_version: int | None, output_dir: str | None, k: int, agent_model: str | None, user_model: str | None, judge_model: str | None, agent_artifact_id: str | None, start_step: int, context_json: str | None, litellm_api_key: str | None, judge_litellm_api_key: str | None, apply_trajectory_filter: bool | None, a2a_agent_id: str | None, agent_sandbox: str | None, env_sandbox: str | None, gateway_env_id: str | None, service_db_env_id: str | None, env_state_type: str | None, env_state_instance_id: str | None, keep: bool):
def run(task_id: str, task_version: int | None, output_dir: str | None, k: int, agent_model: str | None, user_model: str | None, judge_model: str | None, agent_artifact_id: str | None, start_step: int, context_json: str | None, litellm_api_key: str | None, judge_litellm_api_key: str | None, apply_trajectory_filter: bool | None, a2a_agent_id: str | None, agent_sandbox: str | None, env_sandbox: str | None, gateway_env_id: str | None, service_db_env_id: str | None, website_browser_env_id: str | None, env_state_type: str | None, env_state_instance_id: str | None, keep: bool):
"""Run a task through the configured [runner] and follow it.

Each run's sandboxes are torn down as it ends, passed or failed; its instance and context JSON stay.
Expand Down Expand Up @@ -416,6 +418,8 @@ def run(task_id: str, task_version: int | None, output_dir: str | None, k: int,
initial_context.metadata.setdefault("user_overrides", {})[GATEWAY_OVERRIDE] = gateway_env_id
if service_db_env_id:
initial_context.metadata.setdefault("user_overrides", {})[SERVICE_DB_OVERRIDE] = service_db_env_id
if website_browser_env_id:
initial_context.metadata.setdefault("user_overrides", {})[WEBSITE_BROWSER_OVERRIDE] = website_browser_env_id
initial_context.metadata["start_step"] = start_step
initial_context.metadata[KEEP_KEY] = keep
initial_context.metadata["task_id"] = task_id
Expand Down
2 changes: 2 additions & 0 deletions src/agent_env/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
set_object_store,
set_runner,
set_secret_store,
website_browser_env_id,
)

__all__ = [
Expand All @@ -53,4 +54,5 @@
"set_object_store",
"set_runner",
"set_secret_store",
"website_browser_env_id",
]
2 changes: 1 addition & 1 deletion src/agent_env/config/describe.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class FileSection:
FileSection("sandbox", ("sandbox",), "providers.sandbox_providers.sandbox_provider",
{"default": None, "agent_default": None, "attribution": None, "providers": Named(_SEAM)}),
FileSection("state", ("state",), "providers.env_state.env_state_provider", {"providers": Named(_SEAM)}),
FileSection("envs", ("envs",), "env.registry", {"impls": None}),
FileSection("envs", ("envs",), "env.registry", {"impls": None, **_resolved_keys("envs")}),
FileSection("artifacts", ("artifacts",), "artifact.registry", {"impls": None, "type_aliases": None}),
FileSection("task_steps", ("task_steps",), "task_step.registry", {"impls": None}),
FileSection("explorer", ("explorer",), "explorer.app",
Expand Down
Loading
Loading