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,164 @@
1
+ """Verify the advertised A2A trajectory retrieval variants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from typing import ClassVar, Optional
8
+
9
+ from agent_env.task_step.context import TaskStepContext
10
+ from agent_env.a2a_agent.object_transfer import (
11
+ REPLY_TIMEOUT_SECONDS,
12
+ TransferCall,
13
+ TrajectoryUpload,
14
+ choose_transfer,
15
+ fetch_trajectory,
16
+ invoke_transfer,
17
+ trajectory_mode,
18
+ )
19
+ from agent_env.task_step.snapshot_utils.agent_state_capture import trajectory_object_url
20
+ from agent_env.entity_refs import EntityRef
21
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class VerifyA2ATrajectoryStep(TaskStep):
27
+ type: ClassVar[str] = "verify_a2a_trajectory"
28
+ entity_refs = (EntityRef.agent("a2a_agent_id"),)
29
+
30
+ def __init__(
31
+ self,
32
+ id: str,
33
+ version: Optional[int],
34
+ a2a_agent_id: str,
35
+ depends_on: Optional[list[TaskStepDependency]] = None,
36
+ fail_task_on_error: bool = True,
37
+ ):
38
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
39
+ self.a2a_agent_id = a2a_agent_id
40
+
41
+ def to_dict(self) -> dict:
42
+ base = super().to_dict()
43
+ base["a2a_agent_id"] = self.a2a_agent_id
44
+ return base
45
+
46
+ @classmethod
47
+ def from_dict(cls, data: dict) -> VerifyA2ATrajectoryStep:
48
+ return cls(**cls._base_from_dict(data), a2a_agent_id=data["a2a_agent_id"])
49
+
50
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
51
+ from agent_env.a2a_agent import A2AAgent
52
+ from agent_env.config import get_config
53
+
54
+ deployed_agent = next((a for a in context.deployed_agents), None)
55
+ if deployed_agent is None:
56
+ raise RuntimeError("No deployed agent found in context")
57
+
58
+ card = deployed_agent.a2a_card or {}
59
+ traj_ext = A2AAgent.find_extension(card, A2AAgent.EXT_TRAJECTORY)
60
+ if not traj_ext:
61
+ logger.warning("Trajectory extension not advertised in agent card")
62
+ agent = A2AAgent.get(self.a2a_agent_id)
63
+ validated_ext = agent.metadata.get("validated_a2a_extensions", {})
64
+ validated_ext[A2AAgent.EXT_TRAJECTORY] = {"supported": False}
65
+ agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
66
+ context.metadata.setdefault("verifications", {})["a2a_trajectory"] = {"supported": False}
67
+ return context
68
+
69
+ a2a_url = deployed_agent.a2a_url or deployed_agent.api_url
70
+ get_method, get_path = A2AAgent.operation(traj_ext, "get")
71
+ endpoint = a2a_url + get_path
72
+
73
+ # Get task_id from the MCP step
74
+ mcp_result = context.metadata.get("verifications", {}).get("a2a_agent_mcp", {})
75
+ task_id = mcp_result.get("task_id")
76
+ if not task_id:
77
+ raise RuntimeError("No task_id from MCP verification step — trajectory validation requires a completed task")
78
+
79
+ # Test inline trajectory retrieval
80
+ inline_ok = False
81
+ try:
82
+ fetched = await fetch_trajectory(endpoint, {"task_id": task_id})
83
+ if fetched.inline:
84
+ inline_ok = True
85
+ logger.info(f"Inline trajectory: OK ({len(fetched.inline)} events)")
86
+ else:
87
+ logger.warning("Inline trajectory: response missing 'trajectory' key or empty")
88
+ except Exception as e:
89
+ logger.warning(f"Inline trajectory: failed ({e})")
90
+
91
+ # The object variant is probed only on a store that can issue its grant.
92
+ objects_ok = False
93
+ config = get_config()
94
+ store = config.get_object_store()
95
+ probe_prefix = f"{config.get_artifact_key_prefix()}a2a_validator_trajectories/{self.a2a_agent_id}/"
96
+ if trajectory_mode(get_method, store, by="task_id") == "objects":
97
+ try:
98
+ prefix = store.object_url(probe_prefix)
99
+ upload = await asyncio.to_thread(
100
+ TrajectoryUpload.to, store, trajectory_object_url(prefix, name=task_id, store=store)
101
+ )
102
+ await fetch_trajectory(endpoint, {"task_id": task_id}, upload=upload)
103
+ objects_ok = True
104
+ logger.info("Object trajectory: OK")
105
+ except Exception as e:
106
+ logger.warning(f"Object trajectory: failed ({e})")
107
+
108
+ s3_ok = False
109
+ if choose_transfer(
110
+ get_method, legacy=("task_id", "trajectory_s3_prefix"), store=store
111
+ ) == "legacy":
112
+ s3_prefix = store.object_url(probe_prefix)
113
+ try:
114
+ data = await invoke_transfer(
115
+ endpoint,
116
+ TransferCall(
117
+ "legacy", {"task_id": task_id, "trajectory_s3_prefix": s3_prefix}
118
+ ),
119
+ verb="POST",
120
+ operation="legacy trajectory get",
121
+ timeout=REPLY_TIMEOUT_SECONDS,
122
+ )
123
+ if data.get("trajectory_s3_prefix"):
124
+ s3_ok = True
125
+ logger.info(f"S3 trajectory: OK ({data['trajectory_s3_prefix']})")
126
+ else:
127
+ logger.warning(
128
+ "S3 trajectory: response missing 'trajectory_s3_prefix' key or empty"
129
+ )
130
+ except Exception as e:
131
+ logger.warning(f"S3 trajectory: failed ({e})")
132
+
133
+ logger.info(
134
+ f"Trajectory validation: inline={inline_ok} objects={objects_ok} s3={s3_ok}"
135
+ )
136
+
137
+ # Build validated_a2a_extensions entry
138
+ traj_entry = {
139
+ "supported": inline_ok or objects_ok or s3_ok,
140
+ "methods": {
141
+ "get": {
142
+ "supported": inline_ok or objects_ok or s3_ok,
143
+ "options": {
144
+ "task_id": {
145
+ "supported": inline_ok or objects_ok or s3_ok
146
+ },
147
+ "objects": {"supported": objects_ok},
148
+ "trajectory_s3_prefix": {"supported": s3_ok},
149
+ },
150
+ },
151
+ },
152
+ }
153
+
154
+ agent = A2AAgent.get(self.a2a_agent_id)
155
+ validated_ext = agent.metadata.get("validated_a2a_extensions", {})
156
+ validated_ext[A2AAgent.EXT_TRAJECTORY] = traj_entry
157
+ agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
158
+
159
+ context.metadata.setdefault("verifications", {})["a2a_trajectory"] = {
160
+ "inline": inline_ok,
161
+ "objects": objects_ok,
162
+ "s3": s3_ok,
163
+ }
164
+ return context
@@ -0,0 +1,240 @@
1
+ """Add skills to a deployed A2A agent."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ from dataclasses import dataclass
8
+ from typing import ClassVar, Optional
9
+
10
+ from agent_env.task_step.context import TaskStepContext
11
+ from agent_env.entity_refs import EntityRef
12
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class Skill:
19
+ """Represents an Agent Skill following the Agent Skills specification.
20
+
21
+ See https://agentskills.io/specification for the full spec.
22
+
23
+ A skill can come from three mutually-exclusive sources:
24
+ - **inline**: body + frontmatter fields, rendered into SKILL.md at send time
25
+ - **s3_url**: S3 prefix containing a skill directory with its own SKILL.md
26
+ - **skill_artifact_id**: a SkillArtifact in the store; name/description/skill_s3_url
27
+ are resolved from it at send time
28
+ """
29
+ name: Optional[str] = None
30
+ description: Optional[str] = None
31
+ body: Optional[str] = None
32
+ license: Optional[str] = None
33
+ compatibility: Optional[str] = None
34
+ metadata: Optional[dict[str, str]] = None
35
+ allowed_tools: Optional[str] = None
36
+ s3_url: Optional[str] = None
37
+ skill_artifact_id: Optional[str] = None
38
+ skill_artifact_version: Optional[int] = None
39
+
40
+ def __post_init__(self):
41
+ modes = [bool(self.body), bool(self.s3_url), bool(self.skill_artifact_id)]
42
+ if sum(modes) != 1:
43
+ raise ValueError("Skill must have exactly one of: body (inline), s3_url, skill_artifact_id")
44
+ if self.skill_artifact_id is not None:
45
+ if any([self.body, self.s3_url, self.license, self.compatibility, self.metadata, self.allowed_tools]):
46
+ raise ValueError(
47
+ "Skill with skill_artifact_id must not have body/s3_url/license/compatibility/metadata/allowed_tools"
48
+ )
49
+ else:
50
+ if not self.name or not self.description:
51
+ raise ValueError("Inline or s3_url skills require name and description")
52
+ if self.s3_url and any([self.body, self.license, self.compatibility, self.metadata, self.allowed_tools]):
53
+ raise ValueError(
54
+ "Skill with s3_url must not have body/license/compatibility/metadata/allowed_tools — "
55
+ "the S3 skill directory has its own SKILL.md"
56
+ )
57
+
58
+ def validate(self) -> None:
59
+ from agent_env.artifact import SkillArtifact
60
+
61
+ if self.skill_artifact_id is not None:
62
+ return
63
+ if self.s3_url is not None:
64
+ SkillArtifact.validate(s3_url=self.s3_url, expected_name=self.name)
65
+ return
66
+ SkillArtifact.validate(skill_md=self.to_skill_md().encode("utf-8"), expected_name=self.name)
67
+
68
+ def to_skill_md(self) -> str:
69
+ """Render the full SKILL.md content with YAML frontmatter + body."""
70
+ lines = ["---", f"name: {self.name}", f"description: {self.description}"]
71
+ if self.license:
72
+ lines.append(f"license: {self.license}")
73
+ if self.compatibility:
74
+ lines.append(f"compatibility: {self.compatibility}")
75
+ if self.metadata:
76
+ lines.append("metadata:")
77
+ for k, v in self.metadata.items():
78
+ lines.append(f' {k}: "{v}"')
79
+ if self.allowed_tools:
80
+ lines.append(f"allowed-tools: {self.allowed_tools}")
81
+ lines.append("---")
82
+ if self.body:
83
+ lines.append("")
84
+ lines.append(self.body)
85
+ return "\n".join(lines)
86
+
87
+ def to_dict(self) -> dict:
88
+ d: dict = {}
89
+ for field_name in (
90
+ "name", "description", "body", "license", "compatibility",
91
+ "metadata", "allowed_tools", "s3_url",
92
+ "skill_artifact_id", "skill_artifact_version",
93
+ ):
94
+ value = getattr(self, field_name)
95
+ if value is not None:
96
+ d[field_name] = value
97
+ return d
98
+
99
+ @classmethod
100
+ def from_dict(cls, data: dict) -> Skill:
101
+ return cls(
102
+ name=data.get("name"), description=data.get("description"),
103
+ body=data.get("body"), license=data.get("license"),
104
+ compatibility=data.get("compatibility"), metadata=data.get("metadata"),
105
+ allowed_tools=data.get("allowed_tools"), s3_url=data.get("s3_url"),
106
+ skill_artifact_id=data.get("skill_artifact_id"),
107
+ skill_artifact_version=data.get("skill_artifact_version"),
108
+ )
109
+
110
+
111
+
112
+ def _sanitize_skill_name(s: str) -> str:
113
+ """Coerce arbitrary text into the Agent Skills name regex
114
+ (^(?!-)(?!.*--)[a-z0-9-]{1,64}(?<!-)$). Empty inputs raise."""
115
+ s = s.lower()
116
+ s = re.sub(r"[^a-z0-9-]+", "-", s) # replace runs of disallowed chars with a hyphen
117
+ s = re.sub(r"-+", "-", s) # collapse consecutive hyphens
118
+ s = s.strip("-") # drop leading/trailing hyphens
119
+ if not s:
120
+ raise ValueError(
121
+ "Cannot derive a valid Agent Skills name from input: result was empty after sanitization"
122
+ )
123
+ return s[:60].rstrip("-")
124
+
125
+
126
+ def _build_skill_for_installed_cli(cli_artifact_id: str, entry: dict) -> Skill:
127
+ command_name = entry["command_name"]
128
+ install_path = entry["install_path"]
129
+ skill_name = f"{_sanitize_skill_name(command_name)}-cli"
130
+ return Skill(
131
+ name=skill_name,
132
+ description=(
133
+ f"Use when working with the {command_name} environment. "
134
+ f"Run with --help to discover available subcommands at runtime."
135
+ ),
136
+ body=(
137
+ f"# {command_name} CLI\n\n"
138
+ f"A command-line interface for the `{command_name}` MCP environment.\n\n"
139
+ f"## Location\n\nThe binary is installed at `{install_path}`.\n\n"
140
+ f"## Discovery\n\n"
141
+ f"Run `{install_path} --help` to see all currently-available subcommands. "
142
+ f"Run `{install_path} <subcommand> --help` for the flags of a specific subcommand.\n\n"
143
+ f"## Output\n\nSubcommands print pretty-printed JSON on success. Errors go to stderr with non-zero exit code.\n"
144
+ ),
145
+ )
146
+
147
+
148
+ def _build_skill_for_loaded_file_artifact_universe(universe_id: str, entry: dict) -> Skill:
149
+ destination_path = entry["destination_path"]
150
+ return Skill(
151
+ name=f"{universe_id}-files",
152
+ description=f"Files possibly relevant to the current task are available at {destination_path}.",
153
+ body=f"Files possibly relevant to the current task are available at `{destination_path}`.\n",
154
+ )
155
+
156
+
157
+ class AddSkillsTaskStep(TaskStep):
158
+ type: ClassVar[str] = "add_skills"
159
+ entity_refs = (
160
+ EntityRef.artifact("skills[].skill_artifact_id", version_field="skill_artifact_version", artifact_type="skill"),
161
+ EntityRef.artifact("cli_artifact_ids[]", artifact_type="cli"),
162
+ EntityRef.artifact("file_artifact_universe_ids[]"),
163
+ )
164
+
165
+ def __init__(
166
+ self,
167
+ id: str,
168
+ version: Optional[int],
169
+ skills: Optional[list[Skill | dict]] = None,
170
+ cli_artifact_ids: Optional[list[str]] = None,
171
+ file_artifact_universe_ids: Optional[list[str]] = None,
172
+ agent_name: Optional[str] = None,
173
+ depends_on: Optional[list[TaskStepDependency]] = None,
174
+ fail_task_on_error: bool = True,
175
+ ):
176
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
177
+ self.skills = [s if isinstance(s, Skill) else Skill.from_dict(s) for s in (skills or [])]
178
+ self.cli_artifact_ids = list(cli_artifact_ids or [])
179
+ self.file_artifact_universe_ids = list(file_artifact_universe_ids or [])
180
+ self.agent_name = agent_name or TaskStep.DEFAULT_AGENT_NAME
181
+
182
+ def to_dict(self) -> dict:
183
+ base = super().to_dict()
184
+ base["skills"] = [s.to_dict() for s in self.skills]
185
+ base["cli_artifact_ids"] = self.cli_artifact_ids
186
+ base["file_artifact_universe_ids"] = self.file_artifact_universe_ids
187
+ base["agent_name"] = self.agent_name
188
+ return base
189
+
190
+ @classmethod
191
+ def from_dict(cls, data: dict) -> AddSkillsTaskStep:
192
+ return cls(
193
+ **cls._base_from_dict(data),
194
+ skills=data.get("skills"),
195
+ cli_artifact_ids=data.get("cli_artifact_ids"),
196
+ file_artifact_universe_ids=data.get("file_artifact_universe_ids"),
197
+ agent_name=data.get("agent_name"),
198
+ )
199
+
200
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
201
+ from agent_env.a2a_agent import A2AAgent
202
+ from agent_env.a2a_agent.store import get_a2a_agent_instance_store
203
+
204
+ agent = next((a for a in context.deployed_agents if a.agent_name == self.agent_name), None)
205
+ if agent is None:
206
+ raise RuntimeError(f"Agent '{self.agent_name}' not found in context.deployed_agents")
207
+ if agent.instance_id is None:
208
+ raise RuntimeError(f"Agent '{self.agent_name}' is missing instance_id; cannot resolve deployment")
209
+
210
+ skills_to_add = list(self.skills)
211
+ agent_clis = (context.metadata.get("installed_clis") or {}).get(self.agent_name) or {}
212
+ for cli_artifact_id in self.cli_artifact_ids:
213
+ entry = agent_clis.get(cli_artifact_id)
214
+ if entry is None:
215
+ raise RuntimeError(
216
+ f"CliArtifact '{cli_artifact_id}' not in context.installed_clis['{self.agent_name}']; "
217
+ f"ensure LoadArtifactTaskStep ran first with agent_name='{self.agent_name}'"
218
+ )
219
+ skills_to_add.append(_build_skill_for_installed_cli(cli_artifact_id, entry))
220
+
221
+ loaded_universes_by_id: dict[str, dict] = {
222
+ e["id"]: e
223
+ for e in (context.metadata.get("loaded_file_artifact_universes") or [])
224
+ if e.get("agent_name") == self.agent_name
225
+ }
226
+ for fau_id in self.file_artifact_universe_ids:
227
+ entry = loaded_universes_by_id.get(fau_id)
228
+ if entry is None:
229
+ raise RuntimeError(
230
+ f"FileArtifactUniverse '{fau_id}' was not loaded into agent '{self.agent_name}'; "
231
+ f"ensure a LoadArtifactTaskStep with agent_name='{self.agent_name}' for this FAU ran first"
232
+ )
233
+ skills_to_add.append(_build_skill_for_loaded_file_artifact_universe(fau_id, entry))
234
+
235
+ deployed = get_a2a_agent_instance_store().get(agent.instance_id)
236
+ for skill in skills_to_add:
237
+ result = await A2AAgent.add_skill(deployed, skill)
238
+ logger.info(f"Registered skill: {result}")
239
+
240
+ return context