agentenv-framework 0.9.1254__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_env/__init__.py +0 -0
- agent_env/a2a_agent/__init__.py +23 -0
- agent_env/a2a_agent/a2a_agent.py +600 -0
- agent_env/a2a_agent/conversation_store.py +356 -0
- agent_env/a2a_agent/object_transfer.py +609 -0
- agent_env/a2a_agent/protocol.py +238 -0
- agent_env/a2a_agent/store.py +201 -0
- agent_env/a2a_agent/validator.py +901 -0
- agent_env/artifact/__init__.py +48 -0
- agent_env/artifact/artifact.py +74 -0
- agent_env/artifact/artifacts/__init__.py +0 -0
- agent_env/artifact/artifacts/cli.py +83 -0
- agent_env/artifact/artifacts/docker_image.py +457 -0
- agent_env/artifact/artifacts/environment.py +89 -0
- agent_env/artifact/artifacts/environment_universe.py +156 -0
- agent_env/artifact/artifacts/file.py +214 -0
- agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
- agent_env/artifact/artifacts/skill.py +206 -0
- agent_env/artifact/artifacts/vm_image.py +95 -0
- agent_env/artifact/ref.py +15 -0
- agent_env/artifact/registry.py +251 -0
- agent_env/artifact/store.py +295 -0
- agent_env/artifact/universe.py +38 -0
- agent_env/attribution.py +41 -0
- agent_env/bundle/__init__.py +4 -0
- agent_env/bundle/_fs.py +128 -0
- agent_env/bundle/authoring.py +239 -0
- agent_env/bundle/installed.py +175 -0
- agent_env/bundle/ledger.py +269 -0
- agent_env/bundle/materialize.py +204 -0
- agent_env/bundle/parse.py +594 -0
- agent_env/bundle/plan.py +414 -0
- agent_env/bundle/resolve.py +404 -0
- agent_env/bundle/run.py +361 -0
- agent_env/cli/__init__.py +69 -0
- agent_env/cli/__main__.py +10 -0
- agent_env/cli/_installers.py +576 -0
- agent_env/cli/_plugin_changes.py +947 -0
- agent_env/cli/a2a_agent/__init__.py +22 -0
- agent_env/cli/a2a_agent/add_skill.py +97 -0
- agent_env/cli/a2a_agent/deploy.py +60 -0
- agent_env/cli/a2a_agent/get.py +15 -0
- agent_env/cli/a2a_agent/get_instance.py +26 -0
- agent_env/cli/a2a_agent/put.py +78 -0
- agent_env/cli/a2a_agent/validate.py +56 -0
- agent_env/cli/artifact/__init__.py +20 -0
- agent_env/cli/artifact/cli.py +83 -0
- agent_env/cli/artifact/environment.py +44 -0
- agent_env/cli/artifact/environment_universe.py +163 -0
- agent_env/cli/artifact/file_artifact_universe.py +281 -0
- agent_env/cli/artifact/skill.py +128 -0
- agent_env/cli/banner.py +25 -0
- agent_env/cli/config.py +263 -0
- agent_env/cli/env/__init__.py +31 -0
- agent_env/cli/env/deploy.py +112 -0
- agent_env/cli/env/gateway.py +57 -0
- agent_env/cli/env/get_instance.py +40 -0
- agent_env/cli/env/mcp_server.py +410 -0
- agent_env/cli/env/multi.py +282 -0
- agent_env/cli/env/service_db.py +99 -0
- agent_env/cli/env/snapshot.py +35 -0
- agent_env/cli/env/state/__init__.py +6 -0
- agent_env/cli/env/state/init.py +108 -0
- agent_env/cli/env/state/teardown.py +44 -0
- agent_env/cli/env/website.py +216 -0
- agent_env/cli/env/website_browser.py +67 -0
- agent_env/cli/eval/__init__.py +16 -0
- agent_env/cli/eval/add_tasks.py +63 -0
- agent_env/cli/eval/create.py +49 -0
- agent_env/cli/eval/run.py +194 -0
- agent_env/cli/identity/__init__.py +5 -0
- agent_env/cli/identity/client_identity.py +37 -0
- agent_env/cli/plugin.py +438 -0
- agent_env/cli/run.py +285 -0
- agent_env/cli/task/__init__.py +21 -0
- agent_env/cli/task/create.py +109 -0
- agent_env/cli/task/get.py +17 -0
- agent_env/cli/task/get_instance.py +34 -0
- agent_env/cli/task/run.py +615 -0
- agent_env/cli/task/validate.py +31 -0
- agent_env/cli/up.py +123 -0
- agent_env/cli/utils.py +183 -0
- agent_env/config/__init__.py +50 -0
- agent_env/config/describe.py +1067 -0
- agent_env/config/errors.py +13 -0
- agent_env/config/loader.py +245 -0
- agent_env/config/model.py +154 -0
- agent_env/config/paths.py +25 -0
- agent_env/config/plugin_tables.py +101 -0
- agent_env/config/provenance.py +134 -0
- agent_env/config/runtime.py +1064 -0
- agent_env/config/snapshot.py +81 -0
- agent_env/entity_refs.py +186 -0
- agent_env/env/__init__.py +49 -0
- agent_env/env/env.py +433 -0
- agent_env/env/env_artifact_store.py +87 -0
- agent_env/env/envs/__init__.py +7 -0
- agent_env/env/envs/_deployment.py +281 -0
- agent_env/env/envs/gateway_server.py +32 -0
- agent_env/env/envs/mcp_server.py +387 -0
- agent_env/env/envs/multi_env.py +780 -0
- agent_env/env/envs/service_db/Dockerfile +20 -0
- agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
- agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
- agent_env/env/envs/service_db.py +92 -0
- agent_env/env/envs/website.py +294 -0
- agent_env/env/envs/website_browser/Dockerfile +40 -0
- agent_env/env/envs/website_browser/__init__.py +17 -0
- agent_env/env/envs/website_browser/entrypoint.sh +13 -0
- agent_env/env/gateway/Dockerfile +14 -0
- agent_env/env/gateway/__init__.py +37 -0
- agent_env/env/gateway/clock.py +184 -0
- agent_env/env/gateway/constants.py +190 -0
- agent_env/env/gateway/entrypoint.py +156 -0
- agent_env/env/gateway/gateway.py +1095 -0
- agent_env/env/gateway/get_time.py +60 -0
- agent_env/env/gateway/requirements.txt +4 -0
- agent_env/env/gateway/triggers.py +1193 -0
- agent_env/env/legacy_protocol.py +146 -0
- agent_env/env/registry.py +93 -0
- agent_env/env/snapshot_store.py +372 -0
- agent_env/env/store.py +411 -0
- agent_env/eval/__init__.py +14 -0
- agent_env/eval/eval.py +68 -0
- agent_env/eval/store.py +116 -0
- agent_env/examples/hello/README.md +9 -0
- agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
- agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
- agent_env/examples/hello/tasks/hello.json +10 -0
- agent_env/explorer/__init__.py +12 -0
- agent_env/explorer/app.py +277 -0
- agent_env/explorer/openapi_docs.py +242 -0
- agent_env/explorer/plugin.py +95 -0
- agent_env/explorer/routers/__init__.py +1 -0
- agent_env/explorer/routers/common.py +174 -0
- agent_env/explorer/routers/conversations.py +25 -0
- agent_env/explorer/routers/objects.py +112 -0
- agent_env/explorer/routers/runs.py +475 -0
- agent_env/plugins/__init__.py +59 -0
- agent_env/plugins/_cli.py +244 -0
- agent_env/plugins/_discovery.py +97 -0
- agent_env/plugins/_inventory.py +262 -0
- agent_env/plugins/_registration.py +268 -0
- agent_env/plugins/_report.py +47 -0
- agent_env/plugins/_requirements.py +55 -0
- agent_env/providers/__init__.py +37 -0
- agent_env/providers/env_providers/__init__.py +12 -0
- agent_env/providers/env_providers/constants.py +13 -0
- agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
- agent_env/providers/env_providers/env_provider.py +237 -0
- agent_env/providers/env_providers/env_server_provider.py +133 -0
- agent_env/providers/env_state/__init__.py +49 -0
- agent_env/providers/env_state/env_state_provider.py +554 -0
- agent_env/providers/env_state/local_postgres.py +390 -0
- agent_env/providers/env_state/store.py +112 -0
- agent_env/providers/sandbox_providers/__init__.py +29 -0
- agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
- agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
- agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
- agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
- agent_env/providers/sandbox_providers/e2b/template.py +158 -0
- agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
- agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
- agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
- agent_env/providers/sandbox_providers/sandbox.py +387 -0
- agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
- agent_env/py.typed +0 -0
- agent_env/runner/__init__.py +16 -0
- agent_env/runner/local_runner.py +190 -0
- agent_env/runner/runner.py +127 -0
- agent_env/runner/store.py +94 -0
- agent_env/store/__init__.py +126 -0
- agent_env/store/_google.py +68 -0
- agent_env/store/base.py +33 -0
- agent_env/store/document_store/__init__.py +60 -0
- agent_env/store/document_store/document_store.py +519 -0
- agent_env/store/document_store/dynamodb_document_store.py +306 -0
- agent_env/store/document_store/evaluation.py +199 -0
- agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
- agent_env/store/document_store/mongo_document_store.py +259 -0
- agent_env/store/document_store/sqlite_document_store.py +314 -0
- agent_env/store/ids.py +123 -0
- agent_env/store/image_store/__init__.py +34 -0
- agent_env/store/image_store/ecr_image_store.py +131 -0
- agent_env/store/image_store/google_credentials.py +113 -0
- agent_env/store/image_store/image_store.py +113 -0
- agent_env/store/image_store/local_registry_image_store.py +106 -0
- agent_env/store/image_store/oci_registry_credentials.py +196 -0
- agent_env/store/local_state.py +27 -0
- agent_env/store/object_store/__init__.py +24 -0
- agent_env/store/object_store/gcs_object_store.py +415 -0
- agent_env/store/object_store/local_object_store.py +143 -0
- agent_env/store/object_store/object_store.py +198 -0
- agent_env/store/object_store/s3_object_store.py +354 -0
- agent_env/store/query.py +112 -0
- agent_env/store/routing.py +609 -0
- agent_env/store/secret_store/__init__.py +19 -0
- agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
- agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
- agent_env/store/secret_store/local_secret_store.py +47 -0
- agent_env/store/secret_store/secret_store.py +19 -0
- agent_env/task/__init__.py +27 -0
- agent_env/task/interrupts.py +109 -0
- agent_env/task/registry.py +27 -0
- agent_env/task/step_journal.py +99 -0
- agent_env/task/store.py +940 -0
- agent_env/task/task.py +815 -0
- agent_env/task/teardown.py +142 -0
- agent_env/task_step/__init__.py +47 -0
- agent_env/task_step/context.py +195 -0
- agent_env/task_step/context_ops.py +224 -0
- agent_env/task_step/registry.py +182 -0
- agent_env/task_step/review_store.py +118 -0
- agent_env/task_step/snapshot_utils/__init__.py +2 -0
- agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
- agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
- agent_env/task_step/store.py +129 -0
- agent_env/task_step/task_step.py +173 -0
- agent_env/task_step/task_steps/__init__.py +27 -0
- agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
- agent_env/task_step/task_steps/add_skills.py +240 -0
- agent_env/task_step/task_steps/apply_server_config.py +288 -0
- agent_env/task_step/task_steps/collect_artifacts.py +940 -0
- agent_env/task_step/task_steps/deploy_agent.py +626 -0
- agent_env/task_step/task_steps/deploy_env.py +197 -0
- agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
- agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
- agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
- agent_env/task_step/task_steps/install_agent.py +376 -0
- agent_env/task_step/task_steps/load_artifact.py +775 -0
- agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
- agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
- agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
- agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
- agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
- agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
- agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
- agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
- agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
- agent_env/task_step/task_steps/peer_agents.py +92 -0
- agent_env/task_step/task_steps/prompt_agent.py +900 -0
- agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
- agent_env/task_step/task_steps/register_env_triggers.py +97 -0
- agent_env/task_step/task_steps/reset_env.py +71 -0
- agent_env/task_step/task_steps/review.py +152 -0
- agent_env/task_step/task_steps/run_code.py +461 -0
- agent_env/task_step/task_steps/run_code_runner.py +52 -0
- agent_env/task_step/task_steps/run_docker_container.py +380 -0
- agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
- agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
- agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
- agent_env/task_step/task_steps/snapshot_env.py +637 -0
- agent_env/task_step/task_steps/sync_env_clock.py +132 -0
- agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
- agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
- agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
- agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
- agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
- agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
- agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
- agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
- agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
- agent_env/task_step/thread_work.py +61 -0
- agent_env/utils/__init__.py +0 -0
- agent_env/utils/card_naming.py +108 -0
- agent_env/utils/deprecation.py +109 -0
- agent_env/utils/docker_build.py +29 -0
- agent_env/utils/exec_retry.py +77 -0
- agent_env/utils/litellm_attribution.py +59 -0
- agent_env/utils/paths.py +20 -0
- agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
- agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
- agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
- agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
- agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
- agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
- agentenv_framework-0.9.1254.dist-info/licenses/THIRD_PARTY_NOTICES.md +7665 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""TaskStep registry for type-based deserialization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from agent_env.plugins import _registration
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from agent_env.task_step.task_step import TaskStep
|
|
11
|
+
from agent_env.config.runtime import Config
|
|
12
|
+
|
|
13
|
+
# TaskStep defines this only to raise, so a registered class must override it.
|
|
14
|
+
_MUST_IMPLEMENT = ("from_dict",)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_type(cls: type["TaskStep"]) -> str:
|
|
18
|
+
return cls.type
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_task_step_registry() -> dict[str, type["TaskStep"]]:
|
|
24
|
+
from agent_env.config import runtime
|
|
25
|
+
|
|
26
|
+
return runtime.get_config().task_step_registry()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _builtin_registry() -> dict[str, type["TaskStep"]]:
|
|
30
|
+
from agent_env.task_step.task_steps.collect_artifacts import CollectArtifactsTaskStep
|
|
31
|
+
from agent_env.task_step.task_steps.mcp_cli_builder.build_mcp_cli import BuildMcpCliTaskStep
|
|
32
|
+
from agent_env.task_step.task_steps.deploy_agent import DeployAgentTaskStep
|
|
33
|
+
from agent_env.task_step.task_steps.deploy_env import DeployEnvTaskStep
|
|
34
|
+
from agent_env.task_step.task_steps.deploy_human_agent import DeployHumanAgentTaskStep
|
|
35
|
+
from agent_env.task_step.task_steps.deploy_sandbox import DeploySandboxTaskStep
|
|
36
|
+
from agent_env.task_step.task_steps.reset_env import ResetEnvTaskStep
|
|
37
|
+
from agent_env.task_step.task_steps.teardown_sandboxes import TeardownSandboxesTaskStep
|
|
38
|
+
from agent_env.task_step.task_steps.verifiers.env_outcome_verifier import EnvOutcomeVerifierTaskStep
|
|
39
|
+
from agent_env.task_step.task_steps.load_artifact import LoadArtifactTaskStep
|
|
40
|
+
from agent_env.task_step.task_steps.modify_env_tool_access import ModifyEnvToolAccessStep
|
|
41
|
+
from agent_env.task_step.task_steps.prompt_agent import PromptAgentTaskStep
|
|
42
|
+
from agent_env.task_step.task_steps.register_env_triggers import RegisterEnvTriggersStep
|
|
43
|
+
from agent_env.task_step.task_steps.register_agent_triggers import RegisterAgentTriggersStep
|
|
44
|
+
from agent_env.task_step.task_steps.sync_env_clock import SyncEnvClockTaskStep
|
|
45
|
+
from agent_env.task_step.task_steps.install_agent import InstallAgentTaskStep
|
|
46
|
+
from agent_env.task_step.task_steps.verifiers.rubrics_verifier import RubricsVerifierTaskStep
|
|
47
|
+
from agent_env.task_step.task_steps.verifiers.run_container_unit_tests_verifier import RunContainerUnitTestsVerifierTaskStep
|
|
48
|
+
from agent_env.task_step.task_steps.run_docker_container import RunDockerContainerTaskStep
|
|
49
|
+
from agent_env.task_step.task_steps.mcp_env_validator.verify_mcp_tool_schema import VerifyMCPToolSchemaTaskStep
|
|
50
|
+
from agent_env.task_step.task_steps.mcp_env_validator.verify_spec_conformance import VerifySpecConformanceTaskStep
|
|
51
|
+
from agent_env.task_step.task_steps.mcp_env_validator.verify_mcp_env_assessment import VerifyMCPEnvAssessmentStep
|
|
52
|
+
from agent_env.task_step.task_steps.mcp_env_validator.validation_gate_aggregator import ValidationGateAggregatorStep
|
|
53
|
+
from agent_env.task_step.task_steps.multienv_validator.verify_universe_roundtrip import VerifyUniverseLoadExportRoundtripStep
|
|
54
|
+
from agent_env.task_step.task_steps.multienv_validator.combine_universe_verdicts import CombineUniverseVerdictsStep
|
|
55
|
+
from agent_env.task_step.task_steps.env_card_validator.verify_env_card import VerifyEnvironmentCardStep
|
|
56
|
+
from agent_env.task_step.task_steps.env_card_validator.verify_env_core_protocol import VerifyCoreEnvironmentProtocolStep
|
|
57
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_agent_card import VerifyA2AAgentCardStep
|
|
58
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_agent_config_identity import VerifyA2AAgentConfigIdentityStep
|
|
59
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_agent_mcp import VerifyA2AAgentMCPStep
|
|
60
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_core_protocol import VerifyCoreA2AProtocolStep
|
|
61
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_modalities import VerifyA2AModalitiesStep
|
|
62
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_peer_agents import VerifyA2APeerAgentsStep
|
|
63
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_install import VerifyA2AInstallStep
|
|
64
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_skill_config import VerifyA2ASkillConfigStep
|
|
65
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_snapshot import VerifyA2ASnapshotStep
|
|
66
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_trajectory import VerifyA2ATrajectoryStep
|
|
67
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_role import VerifyA2ARoleStep
|
|
68
|
+
from agent_env.task_step.task_steps.a2a_agent_validator.verify_a2a_system_prompt import VerifyA2ASystemPromptStep
|
|
69
|
+
from agent_env.task_step.task_steps.add_skills import AddSkillsTaskStep
|
|
70
|
+
from agent_env.task_step.task_steps.apply_server_config import ApplyServerConfigStep
|
|
71
|
+
from agent_env.task_step.task_steps.verifiers.agent_prompt_response_verifier import AgentPromptResponseVerifierTaskStep
|
|
72
|
+
from agent_env.task_step.task_steps.snapshot_agent_state import SnapshotAgentStateTaskStep
|
|
73
|
+
from agent_env.task_step.task_steps.snapshot_env import SnapshotEnvTaskStep
|
|
74
|
+
from agent_env.task_step.task_steps.peer_agents import PeerAgentsTaskStep
|
|
75
|
+
from agent_env.task_step.task_steps.verifiers.verify_sandbox import VerifySandboxTaskStep
|
|
76
|
+
from agent_env.task_step.task_steps.verifiers.aggregate_verifiers import AggregateVerifiersTaskStep
|
|
77
|
+
from agent_env.task_step.task_steps.run_code import RunCodeTaskStep
|
|
78
|
+
from agent_env.task_step.task_steps.review import ReviewTaskStep
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
_get_type(ReviewTaskStep): ReviewTaskStep,
|
|
82
|
+
_get_type(BuildMcpCliTaskStep): BuildMcpCliTaskStep,
|
|
83
|
+
_get_type(CollectArtifactsTaskStep): CollectArtifactsTaskStep,
|
|
84
|
+
_get_type(DeployAgentTaskStep): DeployAgentTaskStep,
|
|
85
|
+
_get_type(DeployEnvTaskStep): DeployEnvTaskStep,
|
|
86
|
+
_get_type(DeployHumanAgentTaskStep): DeployHumanAgentTaskStep,
|
|
87
|
+
_get_type(DeploySandboxTaskStep): DeploySandboxTaskStep,
|
|
88
|
+
_get_type(ResetEnvTaskStep): ResetEnvTaskStep,
|
|
89
|
+
_get_type(TeardownSandboxesTaskStep): TeardownSandboxesTaskStep,
|
|
90
|
+
_get_type(EnvOutcomeVerifierTaskStep): EnvOutcomeVerifierTaskStep,
|
|
91
|
+
_get_type(LoadArtifactTaskStep): LoadArtifactTaskStep,
|
|
92
|
+
_get_type(ModifyEnvToolAccessStep): ModifyEnvToolAccessStep,
|
|
93
|
+
_get_type(PromptAgentTaskStep): PromptAgentTaskStep,
|
|
94
|
+
_get_type(RegisterEnvTriggersStep): RegisterEnvTriggersStep,
|
|
95
|
+
_get_type(RegisterAgentTriggersStep): RegisterAgentTriggersStep,
|
|
96
|
+
_get_type(SyncEnvClockTaskStep): SyncEnvClockTaskStep,
|
|
97
|
+
_get_type(InstallAgentTaskStep): InstallAgentTaskStep,
|
|
98
|
+
_get_type(RubricsVerifierTaskStep): RubricsVerifierTaskStep,
|
|
99
|
+
_get_type(RunContainerUnitTestsVerifierTaskStep): RunContainerUnitTestsVerifierTaskStep,
|
|
100
|
+
_get_type(RunDockerContainerTaskStep): RunDockerContainerTaskStep,
|
|
101
|
+
_get_type(VerifyMCPToolSchemaTaskStep): VerifyMCPToolSchemaTaskStep,
|
|
102
|
+
_get_type(VerifySpecConformanceTaskStep): VerifySpecConformanceTaskStep,
|
|
103
|
+
_get_type(VerifyMCPEnvAssessmentStep): VerifyMCPEnvAssessmentStep,
|
|
104
|
+
_get_type(ValidationGateAggregatorStep): ValidationGateAggregatorStep,
|
|
105
|
+
_get_type(VerifyUniverseLoadExportRoundtripStep): VerifyUniverseLoadExportRoundtripStep,
|
|
106
|
+
_get_type(CombineUniverseVerdictsStep): CombineUniverseVerdictsStep,
|
|
107
|
+
_get_type(VerifyEnvironmentCardStep): VerifyEnvironmentCardStep,
|
|
108
|
+
_get_type(VerifyCoreEnvironmentProtocolStep): VerifyCoreEnvironmentProtocolStep,
|
|
109
|
+
_get_type(VerifyA2AAgentCardStep): VerifyA2AAgentCardStep,
|
|
110
|
+
_get_type(VerifyA2AAgentConfigIdentityStep): VerifyA2AAgentConfigIdentityStep,
|
|
111
|
+
_get_type(VerifyA2AAgentMCPStep): VerifyA2AAgentMCPStep,
|
|
112
|
+
_get_type(VerifyA2ATrajectoryStep): VerifyA2ATrajectoryStep,
|
|
113
|
+
_get_type(VerifyA2AInstallStep): VerifyA2AInstallStep,
|
|
114
|
+
_get_type(VerifyA2ASkillConfigStep): VerifyA2ASkillConfigStep,
|
|
115
|
+
_get_type(AddSkillsTaskStep): AddSkillsTaskStep,
|
|
116
|
+
_get_type(ApplyServerConfigStep): ApplyServerConfigStep,
|
|
117
|
+
_get_type(AgentPromptResponseVerifierTaskStep): AgentPromptResponseVerifierTaskStep,
|
|
118
|
+
_get_type(VerifyCoreA2AProtocolStep): VerifyCoreA2AProtocolStep,
|
|
119
|
+
_get_type(SnapshotAgentStateTaskStep): SnapshotAgentStateTaskStep,
|
|
120
|
+
_get_type(SnapshotEnvTaskStep): SnapshotEnvTaskStep,
|
|
121
|
+
_get_type(RunCodeTaskStep): RunCodeTaskStep,
|
|
122
|
+
_get_type(PeerAgentsTaskStep): PeerAgentsTaskStep,
|
|
123
|
+
_get_type(VerifyA2ASnapshotStep): VerifyA2ASnapshotStep,
|
|
124
|
+
_get_type(VerifyA2APeerAgentsStep): VerifyA2APeerAgentsStep,
|
|
125
|
+
_get_type(VerifyA2AModalitiesStep): VerifyA2AModalitiesStep,
|
|
126
|
+
_get_type(VerifySandboxTaskStep): VerifySandboxTaskStep,
|
|
127
|
+
_get_type(AggregateVerifiersTaskStep): AggregateVerifiersTaskStep,
|
|
128
|
+
_get_type(VerifyA2ARoleStep): VerifyA2ARoleStep,
|
|
129
|
+
_get_type(VerifyA2ASystemPromptStep): VerifyA2ASystemPromptStep,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _build_registry(source: Config | None = None) -> dict[str, type["TaskStep"]]:
|
|
134
|
+
"""The built-in task steps, then ``agent_env.task_steps`` plugins, then ``[task_steps] impls``
|
|
135
|
+
in ``source``."""
|
|
136
|
+
from agent_env.task_step.task_step import TaskStep
|
|
137
|
+
|
|
138
|
+
registry = _builtin_registry()
|
|
139
|
+
validate = _registration.typed_validator(TaskStep, builtins=frozenset(registry), required=_MUST_IMPLEMENT)
|
|
140
|
+
from_plugins = _registration.merge(registry, _registration.TASK_STEPS, validate, source=source)
|
|
141
|
+
_merge_config_toml_steps(registry, source=source, from_plugins=from_plugins)
|
|
142
|
+
return registry
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _merge_config_toml_steps(
|
|
146
|
+
registry: dict[str, type["TaskStep"]],
|
|
147
|
+
*,
|
|
148
|
+
source: Config | None = None,
|
|
149
|
+
from_plugins: _registration.Registrations | None = None,
|
|
150
|
+
) -> None:
|
|
151
|
+
from agent_env.config import runtime
|
|
152
|
+
from agent_env.config import ConfigError, load_impl
|
|
153
|
+
from agent_env.task_step.task_step import TaskStep
|
|
154
|
+
|
|
155
|
+
section = (source or runtime.get_config()).section("task_steps")
|
|
156
|
+
impls = section.get("impls", [])
|
|
157
|
+
if not isinstance(impls, list):
|
|
158
|
+
raise ConfigError(
|
|
159
|
+
f"[task_steps] impls must be a list of 'module:Class' strings, got {type(impls).__name__}"
|
|
160
|
+
)
|
|
161
|
+
from_plugins = from_plugins if from_plugins is not None else _registration.Registrations.empty()
|
|
162
|
+
for impl in impls:
|
|
163
|
+
if not isinstance(impl, str):
|
|
164
|
+
raise ConfigError(
|
|
165
|
+
f"[task_steps] impl must be a 'module:Class' string, got {type(impl).__name__}: {impl!r}"
|
|
166
|
+
)
|
|
167
|
+
cls = load_impl(impl, TaskStep)
|
|
168
|
+
if cls.type == TaskStep.type:
|
|
169
|
+
raise ConfigError(
|
|
170
|
+
f"[task_steps] impl {impl!r} does not define its own 'type' "
|
|
171
|
+
f"(inherits the base default {TaskStep.type!r}); set a unique 'type' ClassVar"
|
|
172
|
+
)
|
|
173
|
+
if problem := _registration.unimplemented(cls, TaskStep, _MUST_IMPLEMENT):
|
|
174
|
+
raise ConfigError(f"[task_steps] impl {impl!r}: {problem}")
|
|
175
|
+
if from_plugins.refuse(cls.type, f"[task_steps] impl {impl!r}"):
|
|
176
|
+
continue
|
|
177
|
+
if not from_plugins.release(cls.type, "task_steps", impl, cls) and cls.type in registry:
|
|
178
|
+
raise ConfigError(
|
|
179
|
+
f"[task_steps] impl {impl!r} type {cls.type!r} is already registered "
|
|
180
|
+
f"(conflicts with a built-in or another custom step)"
|
|
181
|
+
)
|
|
182
|
+
registry[cls.type] = cls
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Store for human-in-the-loop review decisions, keyed by ``(instance_id, step_id)``.
|
|
2
|
+
|
|
3
|
+
Both the worker (``ReviewTaskStep``) and the hub write here, so it lives in
|
|
4
|
+
agent-env and the hub imports it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime, timedelta, timezone
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from agent_env.store import DuplicateKeyError, Eq, Filter, UpdateSpec, get_config
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
REVIEWS_COLLECTION = "task_reviews"
|
|
18
|
+
|
|
19
|
+
AWAITING = "awaiting"
|
|
20
|
+
CONTINUE = "continue"
|
|
21
|
+
ABORT = "abort"
|
|
22
|
+
_DECISIONS = (CONTINUE, ABORT)
|
|
23
|
+
|
|
24
|
+
# Reap old coordination docs; window dwarfs any realistic review timeout, so active reviews never expire.
|
|
25
|
+
_TTL = timedelta(days=7)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReviewStore:
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self._indexed = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def _doc_store(self):
|
|
34
|
+
# Resolved per call: a cached store outlives reset_config(), so a process that
|
|
35
|
+
# re-pointed would read the new config and write the old backend.
|
|
36
|
+
store = get_config().get_document_store()
|
|
37
|
+
if self._indexed is not store:
|
|
38
|
+
store.ensure_index(
|
|
39
|
+
REVIEWS_COLLECTION, ["instance_id", "step_id"], unique=True
|
|
40
|
+
)
|
|
41
|
+
store.ensure_index(
|
|
42
|
+
REVIEWS_COLLECTION, ["expires_at"], ttl_seconds=0
|
|
43
|
+
)
|
|
44
|
+
self._indexed = store
|
|
45
|
+
return store
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _now() -> str:
|
|
49
|
+
return datetime.now(timezone.utc).isoformat()
|
|
50
|
+
|
|
51
|
+
def put_awaiting(
|
|
52
|
+
self,
|
|
53
|
+
instance_id: str,
|
|
54
|
+
step_id: str,
|
|
55
|
+
*,
|
|
56
|
+
label: Optional[str] = None,
|
|
57
|
+
upstream: Optional[dict[str, Any]] = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Register a review as awaiting a decision. Idempotent — a retry won't clobber a recorded decision."""
|
|
60
|
+
doc = {
|
|
61
|
+
"instance_id": instance_id,
|
|
62
|
+
"step_id": step_id,
|
|
63
|
+
"state": AWAITING,
|
|
64
|
+
"label": label,
|
|
65
|
+
"upstream": upstream or {},
|
|
66
|
+
"awaiting_at": self._now(),
|
|
67
|
+
"expires_at": datetime.now(timezone.utc) + _TTL,
|
|
68
|
+
}
|
|
69
|
+
try:
|
|
70
|
+
self._doc_store.insert(REVIEWS_COLLECTION, doc)
|
|
71
|
+
except DuplicateKeyError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def get(self, instance_id: str, step_id: str) -> Optional[dict[str, Any]]:
|
|
75
|
+
return self._doc_store.find_one(
|
|
76
|
+
REVIEWS_COLLECTION, Filter.of(instance_id=instance_id, step_id=step_id)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def set_decision(
|
|
80
|
+
self,
|
|
81
|
+
instance_id: str,
|
|
82
|
+
step_id: str,
|
|
83
|
+
decision: str,
|
|
84
|
+
*,
|
|
85
|
+
decided_by: Optional[str] = None,
|
|
86
|
+
) -> bool:
|
|
87
|
+
"""Record ``continue``/``abort`` on an awaiting review; False if none was awaiting."""
|
|
88
|
+
if decision not in _DECISIONS:
|
|
89
|
+
raise ValueError(f"decision must be one of {_DECISIONS}, got {decision!r}")
|
|
90
|
+
matched = self._doc_store.update(
|
|
91
|
+
REVIEWS_COLLECTION,
|
|
92
|
+
Filter.of(instance_id=instance_id, step_id=step_id).where("state", Eq(AWAITING)),
|
|
93
|
+
UpdateSpec(
|
|
94
|
+
set={"state": decision, "decided_by": decided_by, "decided_at": self._now()}
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
return matched > 0
|
|
98
|
+
|
|
99
|
+
def list_awaiting(self, instance_id: Optional[str] = None) -> list[dict[str, Any]]:
|
|
100
|
+
filt = Filter.of(state=AWAITING)
|
|
101
|
+
if instance_id is not None:
|
|
102
|
+
filt = filt.where("instance_id", Eq(instance_id))
|
|
103
|
+
return self._doc_store.query(REVIEWS_COLLECTION, filt)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_store: Optional[ReviewStore] = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_review_store() -> ReviewStore:
|
|
110
|
+
global _store
|
|
111
|
+
if _store is None:
|
|
112
|
+
_store = ReviewStore()
|
|
113
|
+
return _store
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def reset_review_store() -> None:
|
|
117
|
+
global _store
|
|
118
|
+
_store = None
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""The reads a capture makes against a running agent's sidecar.
|
|
2
|
+
|
|
3
|
+
``capture_workspace`` raises on any failure — without the tar there is nothing to
|
|
4
|
+
grade — while ``read_partial_trajectory`` never does, because the bundle alone is
|
|
5
|
+
what makes a point gradable.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import uuid
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
from agentenv_protocol.a2a_agent import ObjectSnapshotSaveResponse
|
|
18
|
+
|
|
19
|
+
from agent_env.a2a_agent import A2AAgent
|
|
20
|
+
from agent_env.a2a_agent.object_transfer import (
|
|
21
|
+
SNAPSHOT_TRAJECTORY_OBJECT_NAME,
|
|
22
|
+
SNAPSHOT_WORKSPACE_OBJECT_NAME,
|
|
23
|
+
FetchedTrajectory,
|
|
24
|
+
TrajectoryUpload,
|
|
25
|
+
bounded_echo,
|
|
26
|
+
fetch_trajectory,
|
|
27
|
+
invoke_transfer,
|
|
28
|
+
snapshot_save_call,
|
|
29
|
+
trajectory_mode,
|
|
30
|
+
)
|
|
31
|
+
from agent_env.config import get_config
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class WorkspaceCapture:
|
|
38
|
+
"""One ``/ext/snapshot`` call plus the ``FileArtifactUniverse`` wrapping it."""
|
|
39
|
+
|
|
40
|
+
universe_id: str
|
|
41
|
+
universe_version: Optional[int]
|
|
42
|
+
# Where the files landed; what restores.
|
|
43
|
+
bundle_object_url: str
|
|
44
|
+
# What was presigned — not always where the agent wrote. Per-service state,
|
|
45
|
+
# captured alongside, still goes here.
|
|
46
|
+
capture_prefix: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class TrajectoryCapture:
|
|
51
|
+
trajectory: Any = None
|
|
52
|
+
object_url: str | None = None
|
|
53
|
+
# Non-None means this read produced no trajectory; the caller records it as a
|
|
54
|
+
# `partial` capture reason.
|
|
55
|
+
reason: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def capture_workspace(
|
|
59
|
+
*,
|
|
60
|
+
a2a_url: str,
|
|
61
|
+
a2a_card: dict,
|
|
62
|
+
agent_name: str,
|
|
63
|
+
a2a_context_id: str,
|
|
64
|
+
artifact_id: str,
|
|
65
|
+
timeout_seconds: float,
|
|
66
|
+
) -> WorkspaceCapture:
|
|
67
|
+
"""Tar the agent's workspace to a fresh S3 prefix and wrap it as a universe.
|
|
68
|
+
|
|
69
|
+
Raises on any failure — without the tar there is nothing to grade."""
|
|
70
|
+
from agent_env.artifact.store import get_artifact_store
|
|
71
|
+
|
|
72
|
+
snapshot_ext = A2AAgent.find_extension(a2a_card or {}, A2AAgent.EXT_SNAPSHOT)
|
|
73
|
+
if not snapshot_ext:
|
|
74
|
+
raise RuntimeError(f"Agent '{agent_name}' does not advertise the snapshot extension")
|
|
75
|
+
save_method, save_path = A2AAgent.operation(snapshot_ext, "save")
|
|
76
|
+
|
|
77
|
+
# The random suffix, not the version, isolates captures: two concurrent ones
|
|
78
|
+
# can peek the same version. to_thread because this loop is shared by every
|
|
79
|
+
# concurrent rollout, so a blocking call here stalls their poll loops.
|
|
80
|
+
snapshot_version = await asyncio.to_thread(get_artifact_store().next_version, artifact_id)
|
|
81
|
+
config = get_config()
|
|
82
|
+
capture_key_prefix = (
|
|
83
|
+
f"{config.get_artifact_key_prefix()}agent_snapshots/{artifact_id}/{snapshot_version}-{uuid.uuid4().hex[:8]}/"
|
|
84
|
+
)
|
|
85
|
+
store = config.get_object_store()
|
|
86
|
+
capture_prefix = store.object_url(capture_key_prefix)
|
|
87
|
+
|
|
88
|
+
call = await asyncio.to_thread(
|
|
89
|
+
snapshot_save_call,
|
|
90
|
+
save_method,
|
|
91
|
+
store,
|
|
92
|
+
agent_name=agent_name,
|
|
93
|
+
context_id=a2a_context_id,
|
|
94
|
+
capture_prefix=capture_prefix,
|
|
95
|
+
)
|
|
96
|
+
save_body = await invoke_transfer(
|
|
97
|
+
a2a_url + save_path,
|
|
98
|
+
call,
|
|
99
|
+
verb="POST",
|
|
100
|
+
operation="snapshot save",
|
|
101
|
+
timeout=timeout_seconds,
|
|
102
|
+
response_model=ObjectSnapshotSaveResponse,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if call.mode == "objects":
|
|
106
|
+
# The agent's answer is only a claim: a snapshot missing either object cannot be restored.
|
|
107
|
+
listed = await asyncio.to_thread(store.list_at, capture_prefix)
|
|
108
|
+
stored = {url.rsplit("/", 1)[-1] for url in listed}
|
|
109
|
+
missing = {SNAPSHOT_TRAJECTORY_OBJECT_NAME, SNAPSHOT_WORKSPACE_OBJECT_NAME} - stored
|
|
110
|
+
if missing:
|
|
111
|
+
raise RuntimeError(
|
|
112
|
+
f"snapshot save from agent '{agent_name}' left {sorted(missing)} out of the object store"
|
|
113
|
+
)
|
|
114
|
+
registered_prefix = capture_prefix
|
|
115
|
+
else:
|
|
116
|
+
echoed = save_body.get("s3_prefix")
|
|
117
|
+
if not echoed:
|
|
118
|
+
raise RuntimeError(f"snapshot save response missing 's3_prefix': {save_body}")
|
|
119
|
+
registered_prefix = bounded_echo(capture_prefix, echoed)
|
|
120
|
+
|
|
121
|
+
from agent_env.artifact.artifacts.file_artifact_universe import FileArtifactUniverse
|
|
122
|
+
|
|
123
|
+
# Unserialized — a concurrent version race on one artifact id is
|
|
124
|
+
# `ArtifactStore.put_document`'s retry to absorb.
|
|
125
|
+
universe = await asyncio.to_thread(
|
|
126
|
+
FileArtifactUniverse.put_existing, id=artifact_id, s3_url=registered_prefix
|
|
127
|
+
)
|
|
128
|
+
# `put_existing` always sets it, but the field is Optional on the artifact, and a
|
|
129
|
+
# row carrying None here would read as an ungradable capture rather than an error.
|
|
130
|
+
if not universe.bundle_object_url:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
f"FileArtifactUniverse {universe.id} v{universe.version} registered no "
|
|
133
|
+
f"bundle url for {registered_prefix}"
|
|
134
|
+
)
|
|
135
|
+
logger.info(
|
|
136
|
+
"Snapshot captured: agent=%s context_id=%s -> FileArtifactUniverse %s v%s at %s",
|
|
137
|
+
agent_name, a2a_context_id, universe.id, universe.version,
|
|
138
|
+
universe.bundle_object_url,
|
|
139
|
+
)
|
|
140
|
+
return WorkspaceCapture(
|
|
141
|
+
universe_id=universe.id,
|
|
142
|
+
universe_version=universe.version,
|
|
143
|
+
bundle_object_url=universe.bundle_object_url,
|
|
144
|
+
capture_prefix=capture_prefix,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def read_partial_trajectory(
|
|
149
|
+
*,
|
|
150
|
+
a2a_url: str,
|
|
151
|
+
a2a_card: dict,
|
|
152
|
+
context_id: str,
|
|
153
|
+
timeout_seconds: float,
|
|
154
|
+
trajectory_output_prefix: str,
|
|
155
|
+
) -> TrajectoryCapture:
|
|
156
|
+
"""Read the trajectory-so-far for an in-progress run.
|
|
157
|
+
|
|
158
|
+
Separate from the workspace bundle because not every harness's snapshot
|
|
159
|
+
carries the transcript, which would leave interior points with no trajectory.
|
|
160
|
+
|
|
161
|
+
Served by ``get`` in its ``context_id`` mode — cumulative, and keyed on the a2a
|
|
162
|
+
context id each image maps to its own session. An agent advertising ``get`` as
|
|
163
|
+
``task_id``-only cannot answer this: a task id names one finished turn, which
|
|
164
|
+
404s while the run it belongs to is still going.
|
|
165
|
+
|
|
166
|
+
Never raises: a failure yields a ``reason`` the caller records as ``partial``,
|
|
167
|
+
so an agent serving neither shape still yields a usable curve minus the
|
|
168
|
+
trajectory column.
|
|
169
|
+
"""
|
|
170
|
+
traj_ext = A2AAgent.find_extension(a2a_card or {}, A2AAgent.EXT_TRAJECTORY)
|
|
171
|
+
if not traj_ext:
|
|
172
|
+
return TrajectoryCapture(reason="trajectory_ext_unavailable")
|
|
173
|
+
# Presence, not truthiness: a card may advertise a method as a bare `{}`,
|
|
174
|
+
# which is falsy. Testing truth would read such a card as not having it.
|
|
175
|
+
get_method, get_path = A2AAgent.operation(traj_ext, "get")
|
|
176
|
+
if get_method is None:
|
|
177
|
+
return TrajectoryCapture(reason="trajectory_get_unadvertised")
|
|
178
|
+
# Only an explicit request contract offers the context mode: a `get` that declares
|
|
179
|
+
# none may be task_id-only, and would 400 on every tick.
|
|
180
|
+
if "request" not in get_method:
|
|
181
|
+
return TrajectoryCapture(reason="trajectory_context_unsupported")
|
|
182
|
+
store = get_config().get_object_store()
|
|
183
|
+
mode = trajectory_mode(get_method, store, by="context_id")
|
|
184
|
+
if mode is None:
|
|
185
|
+
return TrajectoryCapture(reason="trajectory_context_unsupported")
|
|
186
|
+
|
|
187
|
+
upload = None
|
|
188
|
+
if mode == "objects":
|
|
189
|
+
try:
|
|
190
|
+
object_url = trajectory_object_url(trajectory_output_prefix, store=store)
|
|
191
|
+
upload = await asyncio.to_thread(TrajectoryUpload.to, store, object_url)
|
|
192
|
+
except Exception as exc:
|
|
193
|
+
logger.warning("partial trajectory grant failed for %s: %s", context_id, exc)
|
|
194
|
+
return TrajectoryCapture(reason="trajectory_grant_unavailable")
|
|
195
|
+
try:
|
|
196
|
+
fetched = await fetch_trajectory(
|
|
197
|
+
a2a_url + get_path, {"context_id": context_id}, upload=upload, timeout=timeout_seconds
|
|
198
|
+
)
|
|
199
|
+
except httpx.HTTPStatusError as exc:
|
|
200
|
+
if exc.response.status_code == 404:
|
|
201
|
+
# No transcript yet — the agent hasn't written a turn.
|
|
202
|
+
return TrajectoryCapture(reason="trajectory_session_missing")
|
|
203
|
+
return TrajectoryCapture(reason=f"trajectory_http_{exc.response.status_code}")
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
logger.warning("partial trajectory read failed for %s: %s", context_id, exc)
|
|
206
|
+
return TrajectoryCapture(reason="trajectory_read_failed")
|
|
207
|
+
|
|
208
|
+
if fetched.object_url is not None:
|
|
209
|
+
return TrajectoryCapture(object_url=fetched.object_url)
|
|
210
|
+
if not fetched.inline:
|
|
211
|
+
return TrajectoryCapture(reason="trajectory_empty")
|
|
212
|
+
return TrajectoryCapture(trajectory=fetched.inline)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def upload_trajectory(
|
|
216
|
+
trajectory: Any, trajectory_output_prefix: str, *, name: Optional[str] = None
|
|
217
|
+
) -> str:
|
|
218
|
+
"""Serialize a trajectory payload and store it, returning its object URL.
|
|
219
|
+
|
|
220
|
+
Always uploaded from the worker side, never by the agent: sandbox VMs hold
|
|
221
|
+
static STS env vars with a fixed expiry, and long runs exceed it.
|
|
222
|
+
|
|
223
|
+
``name`` makes the object addressable by an id the caller already records
|
|
224
|
+
(#731); omit it to get a random one, which is what writing repeatedly under a
|
|
225
|
+
single prefix needs, since a fixed name would overwrite the last upload.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
store = get_config().get_object_store()
|
|
229
|
+
# Via the store, not urlparse().path: a prefix naming a different bucket
|
|
230
|
+
# must fail loudly, not silently write to the configured one.
|
|
231
|
+
prefix_key = store.get_object_key(trajectory_output_prefix)
|
|
232
|
+
if prefix_key and not prefix_key.endswith("/"):
|
|
233
|
+
prefix_key += "/"
|
|
234
|
+
key = f"{prefix_key}trajectory-{name or uuid.uuid4().hex[:12]}.json"
|
|
235
|
+
body = json.dumps(trajectory, indent=2, default=str).encode()
|
|
236
|
+
return store.put(key, body, content_type="application/json")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def store_trajectory(
|
|
240
|
+
fetched: FetchedTrajectory, trajectory_output_prefix: str, *, name: Optional[str] = None
|
|
241
|
+
) -> str | None:
|
|
242
|
+
"""The URL of a fetched trajectory, storing it first when the agent returned it inline."""
|
|
243
|
+
if fetched.object_url is not None:
|
|
244
|
+
return fetched.object_url
|
|
245
|
+
if fetched.legacy_prefix:
|
|
246
|
+
urls = get_config().get_object_store().list_at(fetched.legacy_prefix)
|
|
247
|
+
return urls[0] if urls else None
|
|
248
|
+
if fetched.inline is not None:
|
|
249
|
+
return upload_trajectory(fetched.inline, trajectory_output_prefix, name=name)
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def trajectory_object_url(
|
|
254
|
+
trajectory_output_prefix: str, *, store, name: Optional[str] = None
|
|
255
|
+
) -> str:
|
|
256
|
+
"""Return the durable URL used for one agent-written trajectory."""
|
|
257
|
+
prefix_key = store.get_object_key(trajectory_output_prefix)
|
|
258
|
+
if prefix_key and not prefix_key.endswith("/"):
|
|
259
|
+
prefix_key += "/"
|
|
260
|
+
key = f"{prefix_key}trajectory-{name or uuid.uuid4().hex[:12]}.json"
|
|
261
|
+
return store.object_url(key)
|