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.
Files changed (312) hide show
  1. agent_env/__init__.py +0 -0
  2. agent_env/a2a_agent/__init__.py +23 -0
  3. agent_env/a2a_agent/a2a_agent.py +600 -0
  4. agent_env/a2a_agent/conversation_store.py +356 -0
  5. agent_env/a2a_agent/object_transfer.py +609 -0
  6. agent_env/a2a_agent/protocol.py +238 -0
  7. agent_env/a2a_agent/store.py +201 -0
  8. agent_env/a2a_agent/validator.py +901 -0
  9. agent_env/artifact/__init__.py +48 -0
  10. agent_env/artifact/artifact.py +74 -0
  11. agent_env/artifact/artifacts/__init__.py +0 -0
  12. agent_env/artifact/artifacts/cli.py +83 -0
  13. agent_env/artifact/artifacts/docker_image.py +457 -0
  14. agent_env/artifact/artifacts/environment.py +89 -0
  15. agent_env/artifact/artifacts/environment_universe.py +156 -0
  16. agent_env/artifact/artifacts/file.py +214 -0
  17. agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
  18. agent_env/artifact/artifacts/skill.py +206 -0
  19. agent_env/artifact/artifacts/vm_image.py +95 -0
  20. agent_env/artifact/ref.py +15 -0
  21. agent_env/artifact/registry.py +251 -0
  22. agent_env/artifact/store.py +295 -0
  23. agent_env/artifact/universe.py +38 -0
  24. agent_env/attribution.py +41 -0
  25. agent_env/bundle/__init__.py +4 -0
  26. agent_env/bundle/_fs.py +128 -0
  27. agent_env/bundle/authoring.py +239 -0
  28. agent_env/bundle/installed.py +175 -0
  29. agent_env/bundle/ledger.py +269 -0
  30. agent_env/bundle/materialize.py +204 -0
  31. agent_env/bundle/parse.py +594 -0
  32. agent_env/bundle/plan.py +414 -0
  33. agent_env/bundle/resolve.py +404 -0
  34. agent_env/bundle/run.py +361 -0
  35. agent_env/cli/__init__.py +69 -0
  36. agent_env/cli/__main__.py +10 -0
  37. agent_env/cli/_installers.py +576 -0
  38. agent_env/cli/_plugin_changes.py +947 -0
  39. agent_env/cli/a2a_agent/__init__.py +22 -0
  40. agent_env/cli/a2a_agent/add_skill.py +97 -0
  41. agent_env/cli/a2a_agent/deploy.py +60 -0
  42. agent_env/cli/a2a_agent/get.py +15 -0
  43. agent_env/cli/a2a_agent/get_instance.py +26 -0
  44. agent_env/cli/a2a_agent/put.py +78 -0
  45. agent_env/cli/a2a_agent/validate.py +56 -0
  46. agent_env/cli/artifact/__init__.py +20 -0
  47. agent_env/cli/artifact/cli.py +83 -0
  48. agent_env/cli/artifact/environment.py +44 -0
  49. agent_env/cli/artifact/environment_universe.py +163 -0
  50. agent_env/cli/artifact/file_artifact_universe.py +281 -0
  51. agent_env/cli/artifact/skill.py +128 -0
  52. agent_env/cli/banner.py +25 -0
  53. agent_env/cli/config.py +263 -0
  54. agent_env/cli/env/__init__.py +31 -0
  55. agent_env/cli/env/deploy.py +112 -0
  56. agent_env/cli/env/gateway.py +57 -0
  57. agent_env/cli/env/get_instance.py +40 -0
  58. agent_env/cli/env/mcp_server.py +410 -0
  59. agent_env/cli/env/multi.py +282 -0
  60. agent_env/cli/env/service_db.py +99 -0
  61. agent_env/cli/env/snapshot.py +35 -0
  62. agent_env/cli/env/state/__init__.py +6 -0
  63. agent_env/cli/env/state/init.py +108 -0
  64. agent_env/cli/env/state/teardown.py +44 -0
  65. agent_env/cli/env/website.py +216 -0
  66. agent_env/cli/env/website_browser.py +67 -0
  67. agent_env/cli/eval/__init__.py +16 -0
  68. agent_env/cli/eval/add_tasks.py +63 -0
  69. agent_env/cli/eval/create.py +49 -0
  70. agent_env/cli/eval/run.py +194 -0
  71. agent_env/cli/identity/__init__.py +5 -0
  72. agent_env/cli/identity/client_identity.py +37 -0
  73. agent_env/cli/plugin.py +438 -0
  74. agent_env/cli/run.py +285 -0
  75. agent_env/cli/task/__init__.py +21 -0
  76. agent_env/cli/task/create.py +109 -0
  77. agent_env/cli/task/get.py +17 -0
  78. agent_env/cli/task/get_instance.py +34 -0
  79. agent_env/cli/task/run.py +615 -0
  80. agent_env/cli/task/validate.py +31 -0
  81. agent_env/cli/up.py +123 -0
  82. agent_env/cli/utils.py +183 -0
  83. agent_env/config/__init__.py +50 -0
  84. agent_env/config/describe.py +1067 -0
  85. agent_env/config/errors.py +13 -0
  86. agent_env/config/loader.py +245 -0
  87. agent_env/config/model.py +154 -0
  88. agent_env/config/paths.py +25 -0
  89. agent_env/config/plugin_tables.py +101 -0
  90. agent_env/config/provenance.py +134 -0
  91. agent_env/config/runtime.py +1064 -0
  92. agent_env/config/snapshot.py +81 -0
  93. agent_env/entity_refs.py +186 -0
  94. agent_env/env/__init__.py +49 -0
  95. agent_env/env/env.py +433 -0
  96. agent_env/env/env_artifact_store.py +87 -0
  97. agent_env/env/envs/__init__.py +7 -0
  98. agent_env/env/envs/_deployment.py +281 -0
  99. agent_env/env/envs/gateway_server.py +32 -0
  100. agent_env/env/envs/mcp_server.py +387 -0
  101. agent_env/env/envs/multi_env.py +780 -0
  102. agent_env/env/envs/service_db/Dockerfile +20 -0
  103. agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
  104. agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
  105. agent_env/env/envs/service_db.py +92 -0
  106. agent_env/env/envs/website.py +294 -0
  107. agent_env/env/envs/website_browser/Dockerfile +40 -0
  108. agent_env/env/envs/website_browser/__init__.py +17 -0
  109. agent_env/env/envs/website_browser/entrypoint.sh +13 -0
  110. agent_env/env/gateway/Dockerfile +14 -0
  111. agent_env/env/gateway/__init__.py +37 -0
  112. agent_env/env/gateway/clock.py +184 -0
  113. agent_env/env/gateway/constants.py +190 -0
  114. agent_env/env/gateway/entrypoint.py +156 -0
  115. agent_env/env/gateway/gateway.py +1095 -0
  116. agent_env/env/gateway/get_time.py +60 -0
  117. agent_env/env/gateway/requirements.txt +4 -0
  118. agent_env/env/gateway/triggers.py +1193 -0
  119. agent_env/env/legacy_protocol.py +146 -0
  120. agent_env/env/registry.py +93 -0
  121. agent_env/env/snapshot_store.py +372 -0
  122. agent_env/env/store.py +411 -0
  123. agent_env/eval/__init__.py +14 -0
  124. agent_env/eval/eval.py +68 -0
  125. agent_env/eval/store.py +116 -0
  126. agent_env/examples/hello/README.md +9 -0
  127. agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
  128. agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
  129. agent_env/examples/hello/tasks/hello.json +10 -0
  130. agent_env/explorer/__init__.py +12 -0
  131. agent_env/explorer/app.py +277 -0
  132. agent_env/explorer/openapi_docs.py +242 -0
  133. agent_env/explorer/plugin.py +95 -0
  134. agent_env/explorer/routers/__init__.py +1 -0
  135. agent_env/explorer/routers/common.py +174 -0
  136. agent_env/explorer/routers/conversations.py +25 -0
  137. agent_env/explorer/routers/objects.py +112 -0
  138. agent_env/explorer/routers/runs.py +475 -0
  139. agent_env/plugins/__init__.py +59 -0
  140. agent_env/plugins/_cli.py +244 -0
  141. agent_env/plugins/_discovery.py +97 -0
  142. agent_env/plugins/_inventory.py +262 -0
  143. agent_env/plugins/_registration.py +268 -0
  144. agent_env/plugins/_report.py +47 -0
  145. agent_env/plugins/_requirements.py +55 -0
  146. agent_env/providers/__init__.py +37 -0
  147. agent_env/providers/env_providers/__init__.py +12 -0
  148. agent_env/providers/env_providers/constants.py +13 -0
  149. agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
  150. agent_env/providers/env_providers/env_provider.py +237 -0
  151. agent_env/providers/env_providers/env_server_provider.py +133 -0
  152. agent_env/providers/env_state/__init__.py +49 -0
  153. agent_env/providers/env_state/env_state_provider.py +554 -0
  154. agent_env/providers/env_state/local_postgres.py +390 -0
  155. agent_env/providers/env_state/store.py +112 -0
  156. agent_env/providers/sandbox_providers/__init__.py +29 -0
  157. agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
  158. agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
  159. agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
  160. agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
  161. agent_env/providers/sandbox_providers/e2b/template.py +158 -0
  162. agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
  163. agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
  164. agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
  165. agent_env/providers/sandbox_providers/sandbox.py +387 -0
  166. agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
  167. agent_env/py.typed +0 -0
  168. agent_env/runner/__init__.py +16 -0
  169. agent_env/runner/local_runner.py +190 -0
  170. agent_env/runner/runner.py +127 -0
  171. agent_env/runner/store.py +94 -0
  172. agent_env/store/__init__.py +126 -0
  173. agent_env/store/_google.py +68 -0
  174. agent_env/store/base.py +33 -0
  175. agent_env/store/document_store/__init__.py +60 -0
  176. agent_env/store/document_store/document_store.py +519 -0
  177. agent_env/store/document_store/dynamodb_document_store.py +306 -0
  178. agent_env/store/document_store/evaluation.py +199 -0
  179. agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
  180. agent_env/store/document_store/mongo_document_store.py +259 -0
  181. agent_env/store/document_store/sqlite_document_store.py +314 -0
  182. agent_env/store/ids.py +123 -0
  183. agent_env/store/image_store/__init__.py +34 -0
  184. agent_env/store/image_store/ecr_image_store.py +131 -0
  185. agent_env/store/image_store/google_credentials.py +113 -0
  186. agent_env/store/image_store/image_store.py +113 -0
  187. agent_env/store/image_store/local_registry_image_store.py +106 -0
  188. agent_env/store/image_store/oci_registry_credentials.py +196 -0
  189. agent_env/store/local_state.py +27 -0
  190. agent_env/store/object_store/__init__.py +24 -0
  191. agent_env/store/object_store/gcs_object_store.py +415 -0
  192. agent_env/store/object_store/local_object_store.py +143 -0
  193. agent_env/store/object_store/object_store.py +198 -0
  194. agent_env/store/object_store/s3_object_store.py +354 -0
  195. agent_env/store/query.py +112 -0
  196. agent_env/store/routing.py +609 -0
  197. agent_env/store/secret_store/__init__.py +19 -0
  198. agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
  199. agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
  200. agent_env/store/secret_store/local_secret_store.py +47 -0
  201. agent_env/store/secret_store/secret_store.py +19 -0
  202. agent_env/task/__init__.py +27 -0
  203. agent_env/task/interrupts.py +109 -0
  204. agent_env/task/registry.py +27 -0
  205. agent_env/task/step_journal.py +99 -0
  206. agent_env/task/store.py +940 -0
  207. agent_env/task/task.py +815 -0
  208. agent_env/task/teardown.py +142 -0
  209. agent_env/task_step/__init__.py +47 -0
  210. agent_env/task_step/context.py +195 -0
  211. agent_env/task_step/context_ops.py +224 -0
  212. agent_env/task_step/registry.py +182 -0
  213. agent_env/task_step/review_store.py +118 -0
  214. agent_env/task_step/snapshot_utils/__init__.py +2 -0
  215. agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
  216. agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
  217. agent_env/task_step/store.py +129 -0
  218. agent_env/task_step/task_step.py +173 -0
  219. agent_env/task_step/task_steps/__init__.py +27 -0
  220. agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
  221. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
  222. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
  223. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
  224. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
  225. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
  226. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
  227. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
  228. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
  229. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
  230. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
  231. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
  232. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
  233. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
  234. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
  235. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
  236. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
  237. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
  238. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
  239. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
  240. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
  241. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
  242. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
  243. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
  244. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
  245. agent_env/task_step/task_steps/add_skills.py +240 -0
  246. agent_env/task_step/task_steps/apply_server_config.py +288 -0
  247. agent_env/task_step/task_steps/collect_artifacts.py +940 -0
  248. agent_env/task_step/task_steps/deploy_agent.py +626 -0
  249. agent_env/task_step/task_steps/deploy_env.py +197 -0
  250. agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
  251. agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
  252. agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
  253. agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
  254. agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
  255. agent_env/task_step/task_steps/install_agent.py +376 -0
  256. agent_env/task_step/task_steps/load_artifact.py +775 -0
  257. agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
  258. agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
  259. agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
  260. agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
  261. agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
  262. agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
  263. agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
  264. agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
  265. agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
  266. agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
  267. agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
  268. agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
  269. agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
  270. agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
  271. agent_env/task_step/task_steps/peer_agents.py +92 -0
  272. agent_env/task_step/task_steps/prompt_agent.py +900 -0
  273. agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
  274. agent_env/task_step/task_steps/register_env_triggers.py +97 -0
  275. agent_env/task_step/task_steps/reset_env.py +71 -0
  276. agent_env/task_step/task_steps/review.py +152 -0
  277. agent_env/task_step/task_steps/run_code.py +461 -0
  278. agent_env/task_step/task_steps/run_code_runner.py +52 -0
  279. agent_env/task_step/task_steps/run_docker_container.py +380 -0
  280. agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
  281. agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
  282. agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
  283. agent_env/task_step/task_steps/snapshot_env.py +637 -0
  284. agent_env/task_step/task_steps/sync_env_clock.py +132 -0
  285. agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
  286. agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
  287. agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
  288. agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
  289. agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
  290. agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
  291. agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
  292. agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
  293. agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
  294. agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
  295. agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
  296. agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
  297. agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
  298. agent_env/task_step/thread_work.py +61 -0
  299. agent_env/utils/__init__.py +0 -0
  300. agent_env/utils/card_naming.py +108 -0
  301. agent_env/utils/deprecation.py +109 -0
  302. agent_env/utils/docker_build.py +29 -0
  303. agent_env/utils/exec_retry.py +77 -0
  304. agent_env/utils/litellm_attribution.py +59 -0
  305. agent_env/utils/paths.py +20 -0
  306. agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
  307. agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
  308. agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
  309. agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
  310. agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
  311. agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
  312. agentenv_framework-0.9.1254.dist-info/licenses/THIRD_PARTY_NOTICES.md +7665 -0
@@ -0,0 +1,197 @@
1
+ """Deploy environment task step."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import logging
7
+ from typing import ClassVar, Optional
8
+
9
+ from agent_env.env.gateway import GatewayMode
10
+ from agent_env.env.store import update_env_instance_metadata
11
+ from agent_env.task_step.context import TaskStepContext
12
+ from agent_env.entity_refs import EntityRef
13
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
14
+ from agent_env.attribution import deploy_attribution
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class DeployEnvTaskStep(TaskStep):
20
+ type: ClassVar[str] = "deploy_env"
21
+ entity_refs = (EntityRef.env("env_id", version_field="env_version"),)
22
+
23
+ def __init__(
24
+ self,
25
+ id: str,
26
+ version: Optional[int],
27
+ env_id: str,
28
+ env_version: Optional[int] = None,
29
+ ttl_seconds: int = TaskStep.DEFAULT_TTL_SECONDS,
30
+ disk_size_gb: float = 10,
31
+ gateway_mode: str = GatewayMode.PERFORMANCE.value,
32
+ cpu: Optional[float] = None,
33
+ memory_mb: Optional[int] = None,
34
+ sandbox_type: Optional[str] = None,
35
+ priority: Optional[int] = None,
36
+ env_state_type: Optional[str] = None,
37
+ env_state_instance_id: Optional[str] = None,
38
+ depends_on: Optional[list[TaskStepDependency]] = None,
39
+ fail_task_on_error: bool = True,
40
+ metadata: Optional[dict] = None,
41
+ ):
42
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
43
+ self.env_id = env_id
44
+ self.env_version = env_version
45
+ self.ttl_seconds = ttl_seconds
46
+ self.disk_size_gb = disk_size_gb
47
+ self.gateway_mode = gateway_mode
48
+ self.cpu = cpu
49
+ self.memory_mb = memory_mb
50
+ self.sandbox_type = sandbox_type
51
+ self.env_state_type = env_state_type
52
+ # Existing env state instance to attach to; the provider interprets it.
53
+ self.env_state_instance_id = env_state_instance_id
54
+ self.priority = priority
55
+ self.metadata = metadata or {}
56
+
57
+ def to_dict(self) -> dict:
58
+ base = super().to_dict()
59
+ base["env_id"] = self.env_id
60
+ base["env_version"] = self.env_version
61
+ base["ttl_seconds"] = self.ttl_seconds
62
+ base["disk_size_gb"] = self.disk_size_gb
63
+ base["gateway_mode"] = self.gateway_mode
64
+ base["cpu"] = self.cpu
65
+ base["memory_mb"] = self.memory_mb
66
+ base["sandbox_type"] = self.sandbox_type
67
+ base["env_state_type"] = self.env_state_type
68
+ base["env_state_instance_id"] = self.env_state_instance_id
69
+ base["priority"] = self.priority
70
+ base["metadata"] = self.metadata
71
+ return base
72
+
73
+ @classmethod
74
+ def from_dict(cls, data: dict) -> DeployEnvTaskStep:
75
+ return cls(
76
+ **cls._base_from_dict(data),
77
+ env_id=data["env_id"],
78
+ env_version=data.get("env_version"),
79
+ ttl_seconds=data.get("ttl_seconds", TaskStep.DEFAULT_TTL_SECONDS),
80
+ disk_size_gb=data.get("disk_size_gb", 10),
81
+ gateway_mode=data.get("gateway_mode", GatewayMode.PERFORMANCE.value),
82
+ cpu=data.get("cpu"),
83
+ memory_mb=data.get("memory_mb"),
84
+ sandbox_type=data.get("sandbox_type"),
85
+ env_state_type=data.get("env_state_type"),
86
+ env_state_instance_id=data.get("env_state_instance_id"),
87
+ priority=data.get("priority"),
88
+ metadata=dict(data.get("metadata") or {}),
89
+ )
90
+
91
+ def preflight(self) -> list[str]:
92
+ """An invalid gateway_mode, an env this step can't load, or an option it would refuse at deploy, reported at save instead."""
93
+ from agent_env.env.env import Env
94
+ from agent_env.env.envs.mcp_server import MCPServerEnv
95
+ from agent_env.env.envs.multi_env import MultiEnv
96
+ from agent_env.env.envs.website import WebsiteEnv
97
+ from agent_env.store.base import NotFoundError
98
+
99
+ try:
100
+ GatewayMode(self.gateway_mode)
101
+ except ValueError as e:
102
+ return [f"deploy_env '{self.id}': {e}"] # the words execute() would raise
103
+ try:
104
+ env = Env.get(self.env_id, self.env_version)
105
+ if not isinstance(env, (MCPServerEnv, WebsiteEnv, MultiEnv)):
106
+ return []
107
+ # As stored: run-time overrides aren't known at save, and deploy() consumes sandbox_type itself. Every run passes an
108
+ # attribution, so the check does too, whatever it will hold.
109
+ refusal = env.deploy_refusal(ttl_seconds=self.ttl_seconds, disk_size_gb=self.disk_size_gb, gateway_mode=self.gateway_mode, cpu=self.cpu,
110
+ memory_mb=self.memory_mb, priority=self.priority, env_state_type=self.env_state_type,
111
+ env_state_instance_id=self.env_state_instance_id, attribution={})
112
+ except (NotFoundError, ValueError) as e: # missing, or a type or env_provider_type this process can't load
113
+ return [f"deploy_env '{self.id}': env '{self.env_id}' can't be loaded: {e}"]
114
+ return [f"deploy_env '{self.id}': {refusal}"] if refusal else []
115
+
116
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
117
+ from agent_env.env.env import Env
118
+
119
+ existing = next((d for d in context.deployed_envs if d.env_id == self.env_id), None)
120
+ if existing is not None:
121
+ raise RuntimeError(f"Env '{self.env_id}' is already deployed")
122
+
123
+ env = Env.get(self.env_id, self.env_version)
124
+ user_overrides = context.metadata.get("user_overrides", {})
125
+ resolved_sandbox_type = user_overrides.get("env_sandbox") or self.sandbox_type
126
+ resolved_env_state_type = user_overrides.get("env_state_type") or self.env_state_type
127
+ resolved_env_state_instance_id = (
128
+ user_overrides.get("env_state_instance_id") or self.env_state_instance_id
129
+ )
130
+ _priority_override = user_overrides.get("priority")
131
+ resolved_priority = _priority_override if _priority_override is not None else self.priority
132
+ _ttl_override = user_overrides.get("ttl_seconds")
133
+ resolved_ttl = _ttl_override if _ttl_override is not None else self.ttl_seconds
134
+ attribution = deploy_attribution(self, context)
135
+ logger.info(
136
+ f"Deploying env '{self.env_id}' (ttl={resolved_ttl}s, "
137
+ f"disk_size_gb={self.disk_size_gb}, gateway_mode={self.gateway_mode}, "
138
+ f"cpu={self.cpu}, memory_mb={self.memory_mb}, "
139
+ f"sandbox_type={resolved_sandbox_type}, env_state_type={resolved_env_state_type}, "
140
+ f"env_state_instance_id={resolved_env_state_instance_id}, "
141
+ f"attribution={attribution}, priority={resolved_priority})"
142
+ )
143
+ deploy_kwargs = {
144
+ "ttl_seconds": resolved_ttl,
145
+ "disk_size_gb": self.disk_size_gb,
146
+ "gateway_mode": GatewayMode(self.gateway_mode),
147
+ "cpu": self.cpu,
148
+ "memory_mb": self.memory_mb,
149
+ "priority": resolved_priority,
150
+ "attribution": attribution,
151
+ }
152
+ if resolved_sandbox_type is not None:
153
+ deploy_kwargs["sandbox_type"] = resolved_sandbox_type
154
+ if resolved_env_state_type is not None:
155
+ deploy_kwargs["env_state_type"] = resolved_env_state_type
156
+ if resolved_env_state_instance_id is not None:
157
+ deploy_kwargs["env_state_instance_id"] = resolved_env_state_instance_id
158
+ # Forward the per-run LiteLLM key so envs that spin up their own LLM-calling
159
+ # sidecars attribute spend to the same key as the agent. Guard on the
160
+ # signature — most envs' deploy() takes a fixed param list and would
161
+ # TypeError on an unexpected kwarg.
162
+ params = inspect.signature(env.deploy).parameters
163
+ accepts_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())
164
+ model_api_key = user_overrides.get("litellm_api_key")
165
+ if model_api_key and ("litellm_api_key" in params or accepts_kwargs):
166
+ deploy_kwargs["litellm_api_key"] = model_api_key
167
+ deployed_env = await env.deploy(**deploy_kwargs)
168
+ # create_instance runs inside env.deploy(), so an annotation set after it
169
+ # returns has to be persisted explicitly to reach the instance record.
170
+ annotations = {"deploy_step_id": self.id}
171
+ deployed_env.metadata = {**(deployed_env.metadata or {}), **annotations}
172
+ if deployed_env.instance_id:
173
+ update_env_instance_metadata(deployed_env.instance_id, annotations)
174
+ context.deployed_envs.append(deployed_env)
175
+
176
+ # Some envs (e.g. harbor coding tasks) build + run the agent as part of
177
+ # deploy() and have no separate DeployAgent step. They advertise the
178
+ # running agent via DeployedEnv.metadata["deployed_agent"]; register it
179
+ # so PromptAgent can resolve it by name from context.deployed_agents.
180
+ agent_info = (deployed_env.metadata or {}).get("deployed_agent")
181
+ if agent_info:
182
+ from agent_env.task_step.context import DeployedAgent
183
+
184
+ if any(a.agent_name == agent_info["agent_name"] for a in context.deployed_agents):
185
+ raise RuntimeError(
186
+ f"Agent '{agent_info['agent_name']}' already in context.deployed_agents"
187
+ )
188
+ context.deployed_agents.append(DeployedAgent(
189
+ agent_name=agent_info["agent_name"],
190
+ api_url=agent_info["api_url"],
191
+ sandbox_id=agent_info.get("sandbox_id"),
192
+ sandbox_type=agent_info.get("sandbox_type"),
193
+ a2a_url=agent_info.get("a2a_url"),
194
+ a2a_card=agent_info.get("a2a_card"),
195
+ instance_id=agent_info.get("instance_id") or deployed_env.instance_id,
196
+ ))
197
+ return context
@@ -0,0 +1,100 @@
1
+ """Register an external human A2A endpoint as a peerable DeployedAgent, so a
2
+ single-turn solver can consult a human via the peer_send_message tool. A peer
3
+ needs only a name + URL, so the card is best-effort.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+ from typing import ClassVar, Optional
10
+
11
+ import httpx
12
+
13
+ from agent_env.task_step.context import DeployedAgent, TaskStepContext
14
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ _CARD_FETCH_TIMEOUT_SECONDS = 5
19
+
20
+
21
+ class DeployHumanAgentTaskStep(TaskStep):
22
+ type: ClassVar[str] = "deploy_human_agent"
23
+ entity_refs = ()
24
+
25
+ def __init__(
26
+ self,
27
+ id: str,
28
+ version: Optional[int],
29
+ agent_name: str = "human_agent",
30
+ a2a_url: Optional[str] = None,
31
+ depends_on: Optional[list[TaskStepDependency]] = None,
32
+ fail_task_on_error: bool = True,
33
+ ):
34
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
35
+ self.agent_name = agent_name
36
+ self.a2a_url = a2a_url
37
+
38
+ def to_dict(self) -> dict:
39
+ base = super().to_dict()
40
+ base["agent_name"] = self.agent_name
41
+ base["a2a_url"] = self.a2a_url
42
+ return base
43
+
44
+ @classmethod
45
+ def from_dict(cls, data: dict) -> DeployHumanAgentTaskStep:
46
+ return cls(
47
+ **cls._base_from_dict(data),
48
+ agent_name=data.get("agent_name", "human_agent"),
49
+ a2a_url=data.get("a2a_url"),
50
+ )
51
+
52
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
53
+ from agent_env.config import get_config
54
+
55
+ if context.metadata.get("human_agents"):
56
+ raise RuntimeError(
57
+ "DeployHumanAgentTaskStep: a human agent is already registered; "
58
+ "only one human agent is allowed per task"
59
+ )
60
+ if any(a.agent_name == self.agent_name for a in context.deployed_agents):
61
+ raise RuntimeError(f"Agent with name '{self.agent_name}' is already deployed")
62
+
63
+ if self.a2a_url:
64
+ url = self.a2a_url
65
+ else:
66
+ base = get_config().get_default_human_a2a_url()
67
+ # Run-scope the URL so the human endpoint can attribute the ask to this
68
+ # task instance from the request path.
69
+ url = f"{base.rstrip('/')}/instance/{context.instance_id}" if context.instance_id else base
70
+ card = await self._resolve_card(url)
71
+
72
+ context.deployed_agents.append(
73
+ DeployedAgent(agent_name=self.agent_name, api_url=url, a2a_url=url, a2a_card=card)
74
+ )
75
+ context.metadata.setdefault("human_agents", []).append(
76
+ {"step_id": self.id, "agent_name": self.agent_name, "a2a_url": url}
77
+ )
78
+ logger.info(f"Registered human peer '{self.agent_name}' at {url} (card name={card.get('name')})")
79
+ return context
80
+
81
+ async def _resolve_card(self, url: str) -> dict:
82
+ """Best-effort card fetch — a human endpoint may not serve one, and a peer needs no card, so fall back to a synthesized card."""
83
+ try:
84
+ async with httpx.AsyncClient() as client:
85
+ resp = await client.get(f"{url}/.well-known/agent.json", timeout=_CARD_FETCH_TIMEOUT_SECONDS)
86
+ if resp.status_code == 200:
87
+ return resp.json()
88
+ logger.warning(f"Human agent card at {url} returned {resp.status_code}; using a synthesized card")
89
+ except Exception as e:
90
+ logger.warning(f"Could not fetch human agent card from {url} ({type(e).__name__}: {e}); using a synthesized card")
91
+ return self._synthesized_card()
92
+
93
+ def _synthesized_card(self) -> dict:
94
+ return {
95
+ "name": self.agent_name,
96
+ "description": "A human operator available to answer questions",
97
+ "capabilities": {},
98
+ "default_input_modes": ["text"],
99
+ "default_output_modes": ["text"],
100
+ }
@@ -0,0 +1,170 @@
1
+ """Deploy bare sandbox task step."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import ClassVar, Optional
7
+
8
+ from agent_env.providers.sandbox_providers.sandbox import NetworkPolicy
9
+ from agent_env.task_step.context import DeployedSandbox, TaskStepContext
10
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
11
+ from agent_env.attribution import deploy_attribution
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _SUPPORTED_MODES = {"vm", "container"}
16
+
17
+
18
+ class DeploySandboxTaskStep(TaskStep):
19
+ type: ClassVar[str] = "deploy_sandbox"
20
+ entity_refs = ()
21
+
22
+ def __init__(
23
+ self,
24
+ id: str,
25
+ version: Optional[int],
26
+ sandbox_name: str,
27
+ sandbox_mode: str,
28
+ image: Optional[str] = None,
29
+ cpu: float = 2.0,
30
+ memory_mb: int = 8192,
31
+ disk_size_gb: float = 10,
32
+ ttl_seconds: int = TaskStep.DEFAULT_TTL_SECONDS,
33
+ exposed_ports: Optional[list[int]] = None,
34
+ boot_mode: Optional[str] = None,
35
+ port: Optional[int] = None,
36
+ env_vars: Optional[dict[str, str]] = None,
37
+ sandbox_type: Optional[str] = None,
38
+ priority: Optional[int] = None,
39
+ network_policy: Optional[dict] = None,
40
+ depends_on: Optional[list[TaskStepDependency]] = None,
41
+ fail_task_on_error: bool = True,
42
+ metadata: Optional[dict] = None,
43
+ ):
44
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
45
+ if sandbox_mode not in _SUPPORTED_MODES:
46
+ raise ValueError(f"sandbox_mode must be one of {sorted(_SUPPORTED_MODES)}, got {sandbox_mode!r}")
47
+ self.sandbox_name = sandbox_name
48
+ self.sandbox_mode = sandbox_mode
49
+ self.image = image
50
+ self.cpu = cpu
51
+ self.memory_mb = memory_mb
52
+ self.disk_size_gb = disk_size_gb
53
+ self.ttl_seconds = ttl_seconds
54
+ self.exposed_ports = exposed_ports
55
+ self.boot_mode = boot_mode
56
+ self.port = port
57
+ self.env_vars = env_vars
58
+ self.sandbox_type = sandbox_type
59
+ self.priority = priority
60
+ self.metadata = metadata or {}
61
+ self.network_policy = NetworkPolicy.from_dict(network_policy).to_dict() if network_policy else None
62
+
63
+ def to_dict(self) -> dict:
64
+ base = super().to_dict()
65
+ base["sandbox_name"] = self.sandbox_name
66
+ base["sandbox_mode"] = self.sandbox_mode
67
+ base["image"] = self.image
68
+ base["cpu"] = self.cpu
69
+ base["memory_mb"] = self.memory_mb
70
+ base["disk_size_gb"] = self.disk_size_gb
71
+ base["ttl_seconds"] = self.ttl_seconds
72
+ base["exposed_ports"] = self.exposed_ports
73
+ base["boot_mode"] = self.boot_mode
74
+ base["port"] = self.port
75
+ base["env_vars"] = self.env_vars
76
+ base["sandbox_type"] = self.sandbox_type
77
+ base["priority"] = self.priority
78
+ base["metadata"] = self.metadata
79
+ base["network_policy"] = self.network_policy
80
+ return base
81
+
82
+ @classmethod
83
+ def from_dict(cls, data: dict) -> DeploySandboxTaskStep:
84
+ return cls(
85
+ **cls._base_from_dict(data),
86
+ sandbox_name=data["sandbox_name"],
87
+ sandbox_mode=data["sandbox_mode"],
88
+ image=data.get("image"),
89
+ cpu=data.get("cpu", 2.0),
90
+ memory_mb=data.get("memory_mb", 8192),
91
+ disk_size_gb=data.get("disk_size_gb", 10),
92
+ ttl_seconds=data.get("ttl_seconds", TaskStep.DEFAULT_TTL_SECONDS),
93
+ exposed_ports=data.get("exposed_ports"),
94
+ boot_mode=data.get("boot_mode"),
95
+ port=data.get("port"),
96
+ env_vars=data.get("env_vars"),
97
+ sandbox_type=data.get("sandbox_type"),
98
+ priority=data.get("priority"),
99
+ metadata=dict(data.get("metadata") or {}),
100
+ network_policy=data.get("network_policy"),
101
+ )
102
+
103
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
104
+ from agent_env.providers.sandbox_providers.sandbox_provider import build_sandbox_provider, get_sandbox_provider
105
+
106
+ if any(s.sandbox_name == self.sandbox_name for s in context.deployed_sandboxes):
107
+ raise RuntimeError(f"Sandbox '{self.sandbox_name}' is already deployed")
108
+
109
+ user_overrides = context.metadata.get("user_overrides") or {}
110
+ # Moves only deploy_sandbox steps: deploy_env and deploy_agent read env_sandbox and agent_sandbox.
111
+ resolved_sandbox_type = user_overrides.get("sandbox") or self.sandbox_type
112
+ provider = (
113
+ build_sandbox_provider(resolved_sandbox_type)
114
+ if resolved_sandbox_type
115
+ else get_sandbox_provider()
116
+ )
117
+
118
+ _ttl_override = user_overrides.get("ttl_seconds")
119
+ resolved_ttl = _ttl_override if _ttl_override is not None else self.ttl_seconds
120
+ attribution = deploy_attribution(self, context)
121
+ logger.info(
122
+ f"Deploying sandbox '{self.sandbox_name}' (mode={self.sandbox_mode}, "
123
+ f"image={self.image}, cpu={self.cpu}, memory_mb={self.memory_mb}, "
124
+ f"disk_size_gb={self.disk_size_gb}, ttl_seconds={resolved_ttl}, "
125
+ f"sandbox_type={resolved_sandbox_type}, attribution={attribution})"
126
+ )
127
+
128
+ _override = user_overrides.get("network_policy")
129
+ _resolved = _override if _override is not None else self.network_policy
130
+ policy = NetworkPolicy.from_dict(_resolved) if _resolved is not None else None
131
+
132
+ if self.sandbox_mode == "vm":
133
+ sandbox = await provider.create_vm(
134
+ image=self.image,
135
+ boot_mode=self.boot_mode,
136
+ cpu=self.cpu,
137
+ memory=self.memory_mb,
138
+ disk_size_gb=self.disk_size_gb,
139
+ timeout=resolved_ttl,
140
+ exposed_ports=self.exposed_ports or [],
141
+ attribution=attribution,
142
+ priority=self.priority,
143
+ network_policy=policy,
144
+ )
145
+ else:
146
+ if self.image is None or self.port is None:
147
+ raise ValueError("container-mode sandbox requires `image` and `port`")
148
+ sandbox = await provider.create_sandbox(
149
+ image_name=self.image,
150
+ port=self.port,
151
+ env=self.env_vars or {},
152
+ cpu=self.cpu,
153
+ memory=self.memory_mb,
154
+ disk_size_gb=self.disk_size_gb,
155
+ timeout=resolved_ttl,
156
+ attribution=attribution,
157
+ priority=self.priority,
158
+ network_policy=policy,
159
+ )
160
+
161
+ context.deployed_sandboxes.append(DeployedSandbox(
162
+ sandbox_name=self.sandbox_name,
163
+ sandbox_id=sandbox.sandbox_id,
164
+ sandbox_mode=sandbox.mode,
165
+ sandbox_type=sandbox.type,
166
+ tunnel_urls={str(p): u for p, u in sandbox.tunnel_urls.items()} if sandbox.tunnel_urls else None,
167
+ vnc_url=sandbox.vnc_url,
168
+ network_policy=sandbox.network_policy.to_dict() if sandbox.network_policy else None,
169
+ ))
170
+ return context
@@ -0,0 +1,4 @@
1
+ from .verify_env_card import ENVIRONMENT_CARD_KEY, VALIDATED_ENVIRONMENT_CARD_KEY, VerifyEnvironmentCardStep
2
+ from .verify_env_core_protocol import VALIDATED_ENVIRONMENT_PROTOCOL_KEY, VerifyCoreEnvironmentProtocolStep
3
+
4
+ __all__ = ["VerifyEnvironmentCardStep", "VerifyCoreEnvironmentProtocolStep", "ENVIRONMENT_CARD_KEY", "VALIDATED_ENVIRONMENT_CARD_KEY", "VALIDATED_ENVIRONMENT_PROTOCOL_KEY"]
@@ -0,0 +1,109 @@
1
+ """Verify the EnvironmentCard a deployed environment serves at its card's address, and persist it to env metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import ClassVar, Optional
7
+
8
+ from agent_env.task_step.context import TaskStepContext
9
+ from agent_env.entity_refs import EntityRef
10
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ REQUIRED_CARD_FIELDS = ["name", "protocolVersion", "url", "preferredTransport", "capabilities"]
15
+
16
+ # Env-doc metadata keys the validator writes (read back by the CLIs / hub consumers).
17
+ ENVIRONMENT_CARD_KEY = "environment_card"
18
+ VALIDATED_ENVIRONMENT_CARD_KEY = "validated_environment_card"
19
+
20
+
21
+ class VerifyEnvironmentCardStep(TaskStep):
22
+ type: ClassVar[str] = "verify_env_card"
23
+ entity_refs = (EntityRef.env("env_id"),)
24
+
25
+ def __init__(
26
+ self,
27
+ id: str,
28
+ version: Optional[int],
29
+ env_id: str,
30
+ depends_on: Optional[list[TaskStepDependency]] = None,
31
+ fail_task_on_error: bool = True,
32
+ ):
33
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
34
+ self.env_id = env_id
35
+
36
+ def to_dict(self) -> dict:
37
+ base = super().to_dict()
38
+ base["env_id"] = self.env_id
39
+ return base
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: dict) -> VerifyEnvironmentCardStep:
43
+ return cls(**cls._base_from_dict(data), env_id=data["env_id"])
44
+
45
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
46
+ from agentenv_protocol import client as protocol_v1
47
+ from agent_env.env.env import Env
48
+
49
+ deployed = next((d for d in context.deployed_envs if d.env_id == self.env_id), None)
50
+ if deployed is None:
51
+ raise RuntimeError(f"Env '{self.env_id}' not found in context.deployed_envs")
52
+
53
+ card = None
54
+ error = None
55
+ try:
56
+ card = await protocol_v1.get_card(deployed.environment_url)
57
+ except Exception as e:
58
+ error = f"{type(e).__name__}: {e}"
59
+ logger.warning(f"env card for '{self.env_id}' inaccessible at {deployed.environment_url}: {error}")
60
+
61
+ env = Env.get(self.env_id, deployed.env_version)
62
+
63
+ if not card:
64
+ validated = {"accessible": False, "error": error or "empty card"}
65
+ env.update_metadata({**env.metadata, VALIDATED_ENVIRONMENT_CARD_KEY: validated})
66
+ context.metadata.setdefault("verifications", {})["environment_card"] = {VALIDATED_ENVIRONMENT_CARD_KEY: validated}
67
+ return context
68
+
69
+ required_fields = {}
70
+ missing_required = []
71
+ for field in REQUIRED_CARD_FIELDS:
72
+ present = field in card and bool(card[field])
73
+ required_fields[field] = {"present": present}
74
+ if not present:
75
+ missing_required.append(field)
76
+ if missing_required:
77
+ logger.warning(f"env card '{self.env_id}' missing required fields: {missing_required}")
78
+
79
+ children = card.get("children_environments") or []
80
+ capabilities = card.get("capabilities") or {}
81
+ extensions = [uri for e in capabilities.get("extensions") or [] if (uri := e.get("uri"))]
82
+ tools = [name for t in capabilities.get("tools") or [] if (name := t.get("name"))]
83
+ logger.info(f"env card '{self.env_id}': name={card.get('name')} children={len(children)} extensions={extensions} tools={tools}")
84
+
85
+ registered_name = getattr(env, "environment_name", None)
86
+ served_names = [c.get("name") for c in children if c.get("name")]
87
+ name_check = None
88
+ if registered_name and served_names:
89
+ matches = registered_name in served_names
90
+ name_check = {"registered_name": registered_name, "served_card_names": served_names, "matches": matches}
91
+ if not matches:
92
+ logger.warning(
93
+ f"env card name drift for '{self.env_id}': registered environment_name={registered_name!r} is not "
94
+ f"among the served card names {served_names!r} — the image's agentenv-protocol likely predates "
95
+ f"ENVIRONMENT_NAME taking precedence over @environment_card(name=...); rebuild it on a current SDK."
96
+ )
97
+
98
+ validated = {
99
+ "accessible": True,
100
+ "required_fields": required_fields,
101
+ "children_count": len(children),
102
+ "extensions": extensions,
103
+ "tools": tools,
104
+ "name_check": name_check,
105
+ }
106
+ env.update_metadata({**env.metadata, ENVIRONMENT_CARD_KEY: card, VALIDATED_ENVIRONMENT_CARD_KEY: validated})
107
+
108
+ context.metadata.setdefault("verifications", {})["environment_card"] = {ENVIRONMENT_CARD_KEY: card, VALIDATED_ENVIRONMENT_CARD_KEY: validated}
109
+ return context