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,27 @@
1
+ """Task module for AgentEnv."""
2
+
3
+ from .task import Task
4
+ from .store import (
5
+ TaskStore, TaskQuery, get_task_store, set_task_store, reset_task_store,
6
+ TaskInstance, TaskInstanceStore, get_task_instance_store, set_task_instance_store, reset_task_instance_store,
7
+ TaskStepResult, TaskStepStatus, record_step_complete, record_task_cancelled, record_task_failure,
8
+ )
9
+
10
+ __all__ = [
11
+ "Task",
12
+ "TaskStore",
13
+ "TaskQuery",
14
+ "get_task_store",
15
+ "set_task_store",
16
+ "reset_task_store",
17
+ "TaskInstance",
18
+ "TaskInstanceStore",
19
+ "TaskStepResult",
20
+ "TaskStepStatus",
21
+ "get_task_instance_store",
22
+ "set_task_instance_store",
23
+ "reset_task_instance_store",
24
+ "record_step_complete",
25
+ "record_task_cancelled",
26
+ "record_task_failure",
27
+ ]
@@ -0,0 +1,109 @@
1
+ """Ctrl-C and SIGTERM for a command that tears down what its runs deployed.
2
+
3
+ ``Interrupts`` counts both signals from when it is entered until it exits: inside its event loops and in the
4
+ code between them, so none is lost at a hand-off. Inside ``gather``, the first signal cancels the runs that
5
+ haven't begun their teardown and a second cancels the rest, stopping the teardown. It takes over the process's
6
+ handlers, so it has an effect only in the main thread, and a signal the process inherited as ignored stays
7
+ ignored.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import signal
14
+ import threading
15
+ import weakref
16
+ from collections.abc import Awaitable, Callable, Coroutine, Iterable, Iterator
17
+ from contextlib import contextmanager
18
+ from typing import Any
19
+
20
+ SIGNALS = (signal.SIGINT, signal.SIGTERM)
21
+
22
+ _tearing_down: weakref.WeakSet[asyncio.Task] = weakref.WeakSet()
23
+
24
+
25
+ def tearing_down() -> None:
26
+ """Spare the current task from the first signal: it is tearing down what it deployed."""
27
+ task = asyncio.current_task()
28
+ if task is not None:
29
+ _tearing_down.add(task)
30
+
31
+
32
+ class Interrupts:
33
+ def __init__(self) -> None:
34
+ self.signum: int | None = None # the first signal
35
+ self.count = 0
36
+ self._previous: dict[int, Any] = {}
37
+ self._loop: asyncio.AbstractEventLoop | None = None
38
+ self._on_signal: Callable[[int], None] | None = None
39
+
40
+ def __enter__(self) -> Interrupts:
41
+ if threading.current_thread() is threading.main_thread():
42
+ for signum in SIGNALS:
43
+ if signal.getsignal(signum) is not signal.SIG_IGN:
44
+ self._previous[signum] = signal.signal(signum, self._record)
45
+ return self
46
+
47
+ def __exit__(self, *exc) -> None:
48
+ for signum, previous in self._previous.items():
49
+ signal.signal(signum, signal.SIG_DFL if previous is None else previous)
50
+
51
+ @property
52
+ def reason(self) -> str:
53
+ return "cancelled by Ctrl-C" if self.signum in (None, signal.SIGINT) else (
54
+ f"cancelled by {signal.Signals(self.signum).name}")
55
+
56
+ def run(self, main: Coroutine) -> Any:
57
+ """``asyncio.run(main)``, taking the handlers back from the loop once it closes."""
58
+ try:
59
+ return asyncio.run(main)
60
+ finally:
61
+ for signum in self._previous:
62
+ signal.signal(signum, self._record)
63
+
64
+ async def gather(self, runs: Iterable[Awaitable], on_signal: Callable[[int], None]) -> list[asyncio.Future]:
65
+ """Run ``runs`` at once until each is done, and return them. Each signal calls ``on_signal`` with the
66
+ count so far; the first cancels the runs not tearing down, and every later one cancels them all."""
67
+ futures = [asyncio.ensure_future(run) for run in runs]
68
+
69
+ def cancel(count: int) -> None:
70
+ on_signal(count)
71
+ for future in futures:
72
+ if count > 1 or future not in _tearing_down:
73
+ future.cancel()
74
+
75
+ with self._in_loop(cancel):
76
+ await asyncio.gather(*futures, return_exceptions=True)
77
+ return futures
78
+
79
+ async def stopping(self, runs: Iterable[Awaitable]) -> list:
80
+ """Await ``runs`` at once; any signal cancels them all."""
81
+ futures = [asyncio.ensure_future(run) for run in runs]
82
+ with self._in_loop(lambda count: [future.cancel() for future in futures]):
83
+ return list(await asyncio.gather(*futures))
84
+
85
+ @contextmanager
86
+ def _in_loop(self, on_signal: Callable[[int], None]) -> Iterator[None]:
87
+ """Pass each signal to ``on_signal`` in the running loop. The loop's own handlers wake it from any
88
+ thread, and they stay until it closes, counting what comes after, rather than dropping a signal that
89
+ is already on its way."""
90
+ loop = asyncio.get_running_loop()
91
+ self._loop, self._on_signal = loop, on_signal
92
+ for signum in self._previous:
93
+ try:
94
+ loop.add_signal_handler(signum, self._record, signum)
95
+ except (NotImplementedError, RuntimeError):
96
+ pass # the process's handler reaches the loop instead
97
+ for count in range(1, self.count + 1):
98
+ loop.call_soon(on_signal, count)
99
+ try:
100
+ yield
101
+ finally:
102
+ self._loop = self._on_signal = None
103
+
104
+ def _record(self, signum: int, frame=None) -> None:
105
+ self.count += 1
106
+ if self.signum is None:
107
+ self.signum = signum
108
+ if self._on_signal is not None:
109
+ self._loop.call_soon_threadsafe(self._on_signal, self.count)
@@ -0,0 +1,27 @@
1
+ """Task registry for type-based deserialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from agent_env.task.task import Task
9
+
10
+
11
+ def _get_type(cls: type["Task"]) -> str:
12
+ return cls.type
13
+
14
+
15
+ _registry: dict[str, type["Task"]] | None = None
16
+
17
+
18
+ def get_task_registry() -> dict[str, type["Task"]]:
19
+ """Get the task registry, lazily building it to avoid circular imports."""
20
+ global _registry
21
+ if _registry is None:
22
+ from agent_env.task.task import Task
23
+
24
+ _registry = {
25
+ _get_type(Task): Task,
26
+ }
27
+ return _registry
@@ -0,0 +1,99 @@
1
+ """The step journal's replay rules: how one step's diff is applied, in what order, and what
2
+ that reconstructs. Pure — the store owns the collection, the sequence, and the CAS."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import dataclasses
7
+ from typing import Iterable
8
+
9
+ from agent_env.task_step.context import TaskStepContext
10
+ from agent_env.task_step.context_ops import ContextUpdateOps
11
+
12
+ _SEED_STEP_ID = "__seed__"
13
+ # The scheduler's own record (retry audit), written by ``undo_steps_sync`` in the same CAS as
14
+ # the undo and replayed right after the seed, so a retry's bookkeeping survives a later undo.
15
+ _SCHEDULER_STEP_ID = "__scheduler__"
16
+ _RESERVED_STEP_IDS = frozenset({_SEED_STEP_ID, _SCHEDULER_STEP_ID})
17
+
18
+ def _get_path(doc: dict, dotted: str):
19
+ """Read a dotted path (e.g. 'context.metadata.x') from a doc, or None."""
20
+ cur: object = doc
21
+ for part in dotted.split("."):
22
+ if not isinstance(cur, dict):
23
+ return None
24
+ cur = cur.get(part)
25
+ return cur
26
+
27
+
28
+ def _union(existing: list, items: list, *, move_to_end: bool = False, path: str = "") -> list:
29
+ """Union by value (deployed envs by instance), order-stable. ``move_to_end`` is the re-record case: that step's
30
+ completion moves to the end of the replay order, so its items must move with it."""
31
+ key = _item_key(path)
32
+ fresh = [x for i, x in enumerate(items) if key(x) not in [key(y) for y in items[:i]]]
33
+ fresh_keys = [key(x) for x in fresh]
34
+ if move_to_end:
35
+ return [x for x in existing if key(x) not in fresh_keys] + fresh
36
+ existing_keys = [key(x) for x in existing]
37
+ return list(existing) + [x for x, k in zip(fresh, fresh_keys) if k not in existing_keys]
38
+
39
+
40
+ def _set_path(doc: dict, dotted: str, value) -> None:
41
+ cur = doc
42
+ parts = dotted.split(".")
43
+ for part in parts[:-1]:
44
+ nxt = cur.get(part)
45
+ if not isinstance(nxt, dict):
46
+ nxt = {}
47
+ cur[part] = nxt
48
+ cur = nxt
49
+ cur[parts[-1]] = value
50
+
51
+
52
+ def _unset_path(doc: dict, dotted: str) -> None:
53
+ cur = doc
54
+ parts = dotted.split(".")
55
+ for part in parts[:-1]:
56
+ cur = cur.get(part)
57
+ if not isinstance(cur, dict):
58
+ return
59
+ cur.pop(parts[-1], None)
60
+
61
+
62
+ def _apply_forward(doc: dict, ops: ContextUpdateOps, *, move_to_end: bool = False) -> None:
63
+ """Apply one journaled diff in place with the completion write's semantics."""
64
+ for path, value in ops.sets.items():
65
+ _set_path(doc, path, value)
66
+ for path in ops.unsets:
67
+ _unset_path(doc, path)
68
+ for path, items in ops.add_to_sets.items():
69
+ _set_path(doc, path, _union(_get_path(doc, path) or [], items, move_to_end=move_to_end, path=path))
70
+
71
+
72
+ def commit_ordered(entries: list[dict], completed_steps: list[dict]) -> list[dict]:
73
+ """Entries in commit order: seed first, then the scheduler's record, then by position in
74
+ ``completed_steps``, which the CAS appends and so outranks ``seq``. A step with no
75
+ completion never landed; drop it."""
76
+ pos = {c.get("step_id"): i for i, c in enumerate(completed_steps)}
77
+ pos[_SCHEDULER_STEP_ID] = -1
78
+ pos[_SEED_STEP_ID] = -2
79
+ kept = [e for e in entries if e["step_id"] in pos]
80
+ return sorted(kept, key=lambda e: pos[e["step_id"]])
81
+
82
+
83
+ def replay_context(entries: list[dict], rerecorded_steps: Iterable[str]) -> dict:
84
+ """The context ``entries`` produce over an empty one; order them with ``commit_ordered``.
85
+ ``rerecorded_steps`` is the instance's own list, the only record of which steps re-recorded:
86
+ a flag on the entry would be a pre-CAS read and can disagree with the write that landed."""
87
+ flagged = set(rerecorded_steps)
88
+ doc = {"context": dataclasses.asdict(TaskStepContext())}
89
+ for e in entries:
90
+ ops = ContextUpdateOps.from_journal_dict(e["ops"])
91
+ _apply_forward(doc, ops, move_to_end=e["step_id"] in flagged)
92
+ return doc["context"]
93
+
94
+
95
+ def _item_key(path: str):
96
+ """How a context list's items are told apart: a deployed env by its instance, so two kernels' copies stay one entry."""
97
+ if path == "context.deployed_envs":
98
+ return lambda item: (item.get("instance_id") if isinstance(item, dict) else None) or item
99
+ return lambda item: item