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: 2 additions & 0 deletions .github/workflows/local-backends.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
uv run --no-project --with tomlkit python scripts/bump_version.py
uv lock --check
git checkout -- pyproject.toml packages/agentenv-protocol/pyproject.toml uv.lock
- name: Lint
run: .venv/bin/ruff check
- name: Unit tier
run: .venv/bin/python -m pytest tst/unit packages/agentenv-protocol/tests -n auto -q --timeout=300 --junitxml=junit-unit.xml

Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ or one of them only where it is used.

| Command | What it does |
|---|---|
| `make lint` | `ruff check` over the repository, with the rules in `pyproject.toml` (pyflakes) |
| `make unit-test` | `tst/unit` and `packages/agentenv-protocol/tests`, in parallel, no network |
| `make int-test-fast` | `tst/integration` minus `int_test_slow`, in parallel; needs Docker and a local OCI registry (`docker run -d -p 5000:5000 public.ecr.aws/docker/library/registry:2`) |
| `make int-test-slow` | the `int_test_slow` tests, serially (they build the same Docker tags from module fixtures and race under xdist) |
Expand Down Expand Up @@ -195,7 +196,8 @@ The required checks on pull requests and `main` are `unit`, `integration-local`,
`integration-local-slow`, `installer`, `plugin-api` and `clean-install`. `.github/workflows/local-backends.yml` runs
the first four, all installed from `uv.lock` (public PyPI only) with no secrets and no external services (a `registry:2` service container for the integration jobs). The
`unit` job also fails if `uv.lock` resolves anything from a registry other than PyPI or if
`agentenv-framework-protocol` is not the editable workspace member. The integration jobs run
`agentenv-framework-protocol` is not the editable workspace member, or if `ruff check` reports a
problem. The integration jobs run
`.github/scripts/check_skip_policy.py` over the JUnit report: only `agentenv-capability-missing`
skips for the allowed capabilities pass, and the fast job allows no skips under
`tst/integration/store/`. The `installer` job runs `tst/installer` when a pull request or push
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ reinstalling.

| Command | What it runs | Needs |
|---|---|---|
| `make lint` | `ruff check`, with the rules in `pyproject.toml`; the `unit` job runs it too | nothing external |
| `make unit-test` | unit suite for `agentenv-framework` and `agentenv-framework-protocol` | nothing external |
| `make int-test-fast` | integration tests against the local backends | Docker |
| `make int-test-slow` | tests that build images or deploy sandboxes, minutes each | Docker, and a sandbox backend for some |
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install unit-test int-test int-test-fast int-test-slow installer-test clean-install-test test clean
.PHONY: help install lint unit-test int-test int-test-fast int-test-slow installer-test clean-install-test test clean

VENV := .venv
VENV_BIN := $(abspath $(VENV))/bin
Expand All @@ -22,6 +22,9 @@ install: ## Create .venv and install the package with dev dependencies
$(PIP) install -e ./packages/agentenv-protocol
$(PIP) install -e ".[dev]"

lint: ## Lint with ruff, the check the unit job runs
$(VENV_BIN)/ruff check

unit-test: ## Run the unit test suite in parallel (no external services)
$(PYTHON) -m pytest tst/unit/ packages/agentenv-protocol/tests/ -n auto

Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ dev = [
"pytest-socket>=0.7.0",
"pytest-timeout>=2.3.0",
"pytest-xdist>=3.0.0",
"ruff==0.16.10", # the lint gate; exact, since what it reports changes between releases
]

[project.scripts]
Expand Down Expand Up @@ -138,3 +139,6 @@ markers = [
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s %(levelname)s %(name)s: %(message)s"

[tool.ruff.lint]
select = ["F"]
2 changes: 1 addition & 1 deletion src/agent_env/a2a_agent/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from agent_env.store.query import QueryBuilder, to_document_query

if TYPE_CHECKING:
from agent_env.a2a_agent.a2a_agent import A2AAgent
from agent_env.a2a_agent.a2a_agent import A2AAgent, DeployedA2AAgent

logger = logging.getLogger(__name__)

Expand Down
1 change: 0 additions & 1 deletion src/agent_env/a2a_agent/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ async def validate(
from agent_env.task_step.task_steps.peer_agents import AgentPeers, PeerAgentsTaskStep
from agent_env.task_step.task_steps.snapshot_agent_state import SnapshotAgentStateTaskStep
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
from agent_env.a2a_agent.a2a_agent import A2AAgent

task_id = derive_id(agent.id, f"validate-a2a-v{agent.version}")

Expand Down
2 changes: 0 additions & 2 deletions src/agent_env/artifact/artifacts/docker_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
import asyncio
import gzip
import logging
import os
import posixpath
import re
import shlex
import subprocess
import tempfile
import uuid
from dataclasses import dataclass
from importlib.metadata import version as pkg_version
from pathlib import Path
from typing import Any, Callable, ClassVar, Literal
from urllib.parse import urlparse
Expand Down
17 changes: 17 additions & 0 deletions src/agent_env/bundle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,20 @@

from .parse import Bundle, BundleEntry, BundleError, BundleKind, is_ignored, parse_bundle
from .run import BundleRun, DryRun, EvalRun, Outcome, RunInterrupted, TaskRun, dry_run_bundle, run_bundle

__all__ = [
"Bundle",
"BundleEntry",
"BundleError",
"BundleKind",
"BundleRun",
"DryRun",
"EvalRun",
"Outcome",
"RunInterrupted",
"TaskRun",
"dry_run_bundle",
"is_ignored",
"parse_bundle",
"run_bundle",
]
6 changes: 3 additions & 3 deletions src/agent_env/cli/a2a_agent/put.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def put(agent_id: str, dockerfile: str | None, context_path: str | None, image_r
context = Path(context_path) if context_path else dockerfile_path.parent
image_tag = image_repository(image_id)

click.echo(f"Building Docker image...")
click.echo("Building Docker image...")
build_image(dockerfile_path, context, image_tag, platform=build_platform)

click.echo(f"Creating DockerImageArtifact...")
click.echo("Creating DockerImageArtifact...")
artifact = DockerImageArtifact.put(
id=image_id,
description=f"A2A agent image for {agent_id}",
Expand All @@ -83,7 +83,7 @@ def put(agent_id: str, dockerfile: str | None, context_path: str | None, image_r
if default_model is not None:
metadata["default_model"] = default_model

click.echo(f"Registering A2A agent...")
click.echo("Registering A2A agent...")
agent = A2AAgent.put(
id=agent_id,
docker_image_artifact=artifact,
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/cli/env/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def deploy(env_id: str, env_version: int | None, ttl_seconds: int, gateway_mode:
click.echo(f"Error: {e}", err=True)
raise SystemExit(1)

click.echo(f"Deployed!")
click.echo("Deployed!")
if deployed_env.instance_id:
click.echo("Instance ID: " + click.style(deployed_env.instance_id, fg="green"))
click.echo("Env MCP Url: " + click.style(deployed_env.mcp_url, fg="blue"))
Expand Down
6 changes: 3 additions & 3 deletions src/agent_env/cli/env/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,10 @@ def _on_progress(step: str, message: str, percent: int) -> None:
sys.exit(1)
click.echo(f"Derived environment_name={environment_name!r} from the environment card.")
image_tag = image_repository(image_id)
click.echo(f"Building MCP server Docker image...")
click.echo("Building MCP server Docker image...")
build_image(dockerfile_path, context, image_tag, platform=build_platform)

click.echo(f"Creating DockerImageArtifact...")
click.echo("Creating DockerImageArtifact...")
artifact = DockerImageArtifact.put(
id=image_id,
description="Created from agent-env CLI",
Expand All @@ -227,7 +227,7 @@ def _on_progress(step: str, message: str, percent: int) -> None:
report_dirs = [str(context), str(dockerfile_path.parent)]
metadata.update(user_metadata)

click.echo(f"Creating MCPServerEnv...")
click.echo("Creating MCPServerEnv...")
env = MCPServerEnv.put(
id=env_id,
docker_image_artifact=artifact,
Expand Down
3 changes: 1 addition & 2 deletions src/agent_env/cli/env/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def put(env_id: str, mcp_servers: tuple[str, ...], websites: tuple[str, ...], me
if refusal:
click.echo(f"Error: {refusal}", err=True)
raise SystemExit(1)
click.echo(f"Creating MultiEnv...")
click.echo("Creating MultiEnv...")
multi_env = MultiEnv.put(id=env_id, mcp_server_envs=mcp_server_envs, website_envs=website_envs, metadata=metadata if metadata else None, name=name,
env_provider_type=env_provider_type)
click.echo(f"Created MultiEnv: id={multi_env.id} version={multi_env.version} env_provider_type={multi_env.env_provider_type}")
Expand Down Expand Up @@ -243,7 +243,6 @@ def validate_universe_compatibility(env_id: str, env_version: int | None, univer
instance_id = asyncio.run(env.validate_universe_compatibility(universe_artifact_id=universe_artifact_id, on_progress=click.echo))
click.echo(f"Task instance: {instance_id}")

from agent_env.env.env_artifact_store import get_env_artifact_store
from agent_env.env.env_artifact_store import EnvArtifactType, get_env_artifact_store
results = get_env_artifact_store().get_by_env(env_id, env_version=env.version, type=EnvArtifactType.UNIVERSE_COMPATIBILITY)
doc = next((r for r in results if r["artifact_id"] == universe_artifact_id), None)
Expand Down
4 changes: 4 additions & 0 deletions src/agent_env/cli/env/state/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
"""

import asyncio
from typing import TYPE_CHECKING

import click

from agent_env.env import Env, MultiEnv
from agent_env.store.routing import run_scope

if TYPE_CHECKING:
from agent_env.providers.env_state import EnvStateInstance

# A durable store can't be shorter-lived than a normal run, so the floor is the default run TTL
# (agent-env's current default, mirrored from cli/env/deploy.py).
MIN_TTL_SECONDS = 10800 # 3 hours (= default run TTL)
Expand Down
12 changes: 5 additions & 7 deletions src/agent_env/cli/env/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
)
from agent_env.utils.card_naming import card_name_from_github, card_name_from_source
from agent_env.utils.docker_build import DEFAULT_BUILD_PLATFORM, build_image
from agent_env.env import Env
from agent_env.env.envs.website import WebsiteEnv
from agent_env.providers import get_env_sandbox_provider
from agent_env.store.ids import derive_id, image_repository


Expand Down Expand Up @@ -152,10 +150,10 @@ def _frontend_progress(step: str, message: str, percent: int) -> None:
sys.exit(1)
click.echo(f"Derived environment_name={environment_name!r} from the environment card.")

click.echo(f"Building website backend Docker image...")
click.echo("Building website backend Docker image...")
build_image(backend_dockerfile_path, backend_ctx, backend_tag, platform=build_platform)

click.echo(f"Creating backend DockerImageArtifact...")
click.echo("Creating backend DockerImageArtifact...")
backend_artifact = DockerImageArtifact.put(
id=backend_id,
description="Website backend image created from agent-env CLI",
Expand All @@ -167,10 +165,10 @@ def _frontend_progress(step: str, message: str, percent: int) -> None:
frontend_ctx = Path(frontend_docker_context) if frontend_docker_context else frontend_dockerfile_path.parent
frontend_tag = image_repository(frontend_id)

click.echo(f"Building website frontend Docker image...")
click.echo("Building website frontend Docker image...")
build_image(frontend_dockerfile_path, frontend_ctx, frontend_tag, platform=build_platform)

click.echo(f"Creating frontend DockerImageArtifact...")
click.echo("Creating frontend DockerImageArtifact...")
frontend_artifact = DockerImageArtifact.put(
id=frontend_id,
description="Website frontend image created from agent-env CLI",
Expand All @@ -181,7 +179,7 @@ def _frontend_progress(step: str, message: str, percent: int) -> None:
metadata = detect_env_metadata(backend_dockerfile_path, backend_ctx)
metadata.update(user_metadata)

click.echo(f"Creating WebsiteEnv...")
click.echo("Creating WebsiteEnv...")
env = WebsiteEnv.put(
id=env_id,
backend_docker_image_artifact=backend_artifact,
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/cli/eval/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def run(eval_id: str, eval_version: int | None, output_dir: str | None, k: int,
tasks = []
for et in eval_obj.tasks:
task = Task.get(et.task_id, version=et.task_version)
version_note = f" (resolved from latest)" if et.task_version is None else ""
version_note = " (resolved from latest)" if et.task_version is None else ""
click.echo(f" Loaded task: {task.id} v{task.version} ({len(task.steps)} steps){version_note}")
tasks.append(task)

Expand Down
2 changes: 0 additions & 2 deletions src/agent_env/config/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional

import yaml

from agent_env.config import loader as config_loader
from agent_env.config import model as model_config
from agent_env.config.errors import ConfigError, EmptySecretBundleError
Expand Down
6 changes: 3 additions & 3 deletions src/agent_env/env/envs/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import os
import uuid
from importlib.metadata import version as pkg_version
from typing import TYPE_CHECKING, Callable, ClassVar, Optional
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional

from agentenv_protocol import FilePart, client as protocol_v1
from agent_env.artifact import Artifact, DockerImageArtifact
from agent_env.artifact.artifacts.docker_image import GitHubBuildResult, ProgressCallback, refuse_local_github_build
from agent_env.artifact.artifacts.docker_image import ProgressCallback, refuse_local_github_build
from agent_env.config import get_config
from agent_env.entity_refs import EntityRef
from agent_env.env.env import Env, gateway_url_of
Expand All @@ -28,7 +28,7 @@
if TYPE_CHECKING:
from agent_env.artifact import CliArtifact, EnvironmentArtifact, EnvironmentUniverseArtifact
from agent_env.bundle.authoring import AuthoringContext
from agent_env.env.env import DeployedEnv
from agent_env.env.env import DeployedEnv, LoadFileArtifactUniverseResult
from agent_env.providers.env_providers.env_provider import EnvironmentProvider

logger = logging.getLogger(__name__)
Expand Down
3 changes: 2 additions & 1 deletion src/agent_env/env/envs/multi_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import logging
import os
from typing import TYPE_CHECKING, Callable, ClassVar, Optional
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional

logger = logging.getLogger(__name__)

Expand All @@ -23,6 +23,7 @@ def _snapshot_after_load_default() -> bool:
if TYPE_CHECKING:
from agent_env.artifact import EnvironmentArtifact, EnvironmentUniverseArtifact
from agent_env.bundle.authoring import AuthoringContext
from agent_env.env.env import DeployedEnv, LoadEnvironmentUniverseArtifactResult, LoadFileArtifactUniverseResult
from agent_env.providers.env_state import DatabaseStateProvider

from agent_env.entity_refs import EntityRef
Expand Down
8 changes: 4 additions & 4 deletions src/agent_env/env/envs/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import os
import uuid
from importlib.metadata import version as pkg_version
from typing import TYPE_CHECKING, Callable, ClassVar, Optional
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Optional

from agentenv_protocol import FilePart, client as protocol_v1
from agent_env.artifact import Artifact, DockerImageArtifact, EnvironmentArtifact
from agent_env.artifact.artifacts.docker_image import GitHubBuildResult, ProgressCallback, refuse_local_github_build
from agent_env.artifact.artifacts.docker_image import ProgressCallback, refuse_local_github_build
from agent_env.entity_refs import EntityRef
from agent_env.env.env import Env, gateway_url_of
from agent_env.store.ids import derive_id
Expand All @@ -20,11 +20,11 @@
as_builtin, builtin_provider_for, close_deployed, close_replaced, deploy_refusal, deploy_through_provider, host_staging_refusal,
load_by_signed_url, plugin_provider_like_a_builtin, provider_or_class,
)
from agent_env.env.gateway import AGENT_ENV_GATEWAY_MCP_PORT, GatewayMode
from agent_env.env.gateway import GatewayMode
from agent_env.attribution import Attribution
if TYPE_CHECKING:
from agent_env.bundle.authoring import AuthoringContext
from agent_env.env.env import DeployedEnv
from agent_env.env.env import DeployedEnv, LoadFileArtifactUniverseResult
from agent_env.providers.env_providers.env_provider import EnvironmentProvider

logger = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions src/agent_env/env/gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
# Relative import — gateway runs as a standalone container with no agent_env.*
from .constants import (
AGENT_ENV_ROLE_HEADER,
AGENT_ENV_ROLE_META_KEY, # the server module serves the whole vocabulary, used in this file or not
AGENT_ENV_SESSION_META_KEY,
AGENT_ENV_ROLE_META_KEY, # noqa: F401 (the server module serves the whole vocabulary, used in this file or not)
AGENT_ENV_SESSION_META_KEY, # noqa: F401
CARD_FETCH_TIMEOUT_S,
CHILD_DISCOVERY_TIMEOUT_S,
DATA_PLANE_LOAD_TIMEOUT_MAX_S,
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/env/legacy_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async def reset_via_mcp_tool(
reset_done = True
except BaseExceptionGroup as eg:
if reset_done:
logger.info(f"load_environment_artifact: succeeded (ignoring cleanup ExceptionGroup)")
logger.info("load_environment_artifact: succeeded (ignoring cleanup ExceptionGroup)")
break
logger.info(f"load_environment_artifact attempt {attempt + 1}/{max_retries} failed: {eg}")
if attempt < max_retries - 1:
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/explorer/routers/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, ConfigDict

from agent_env.config import get_config, get_runner
from agent_env.config import get_runner
from agent_env.explorer.entity_ids import EntityId
from agent_env.explorer.routers.common import PaginatedResponse, docs
from agent_env.runner.runner import RunStatus
Expand Down
1 change: 0 additions & 1 deletion src/agent_env/task/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,6 @@ def _mutate(doc: dict) -> UpdateSpec:
undone_seqs.clear()
undone_seqs.update({e["step_id"]: e["seq"] for e in entries if e["step_id"] in step_ids})
before = doc.get("completed_steps", [])
completed_ids = {c.get("step_id") for c in before}
if not any(e["step_id"] == _SEED_STEP_ID for e in entries):
raise ValueError(f"instance {instance_id!r} has no journal seed (pre-journal or unseeded); cannot undo")
journaled = {e["step_id"] for e in entries}
Expand Down
5 changes: 4 additions & 1 deletion src/agent_env/task_step/context_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
import dataclasses
import types
from dataclasses import dataclass, field
from typing import Any, Union, get_args, get_origin, get_type_hints
from typing import TYPE_CHECKING, Any, Union, get_args, get_origin, get_type_hints

from agent_env.task_step.context import PromptResponse, TaskStepContext, _REDACTED_KEYS, _strip_redacted_keys

if TYPE_CHECKING:
from agent_env.store.document_store import UpdateSpec


@dataclass
class ContextUpdateOps:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def execute(self, context: TaskStepContext) -> TaskStepContext:
raise RuntimeError(f"VerifyA2APeerAgentsStep: no deployed agent named '{TaskStep.DEFAULT_AGENT_NAME}' in context")
a2a_url = deployed.a2a_url or deployed.api_url
if not a2a_url:
raise RuntimeError(f"VerifyA2APeerAgentsStep: deployed agent has no a2a_url")
raise RuntimeError("VerifyA2APeerAgentsStep: deployed agent has no a2a_url")

extension = A2AAgent.find_extension(deployed.a2a_card or {}, A2AAgent.EXT_PEER_AGENTS)
extension_advertised = extension is not None
Expand Down
Loading
Loading