gatewaysdk 0.3.2__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 (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
gatewaysdk/worlds.py ADDED
@@ -0,0 +1,639 @@
1
+ """World-over-tools: run an agent's real tool surface against a synced world.
2
+
3
+ The dev-loop story in one file:
4
+
5
+ from gatewaysdk import worlds
6
+
7
+ world = worlds.load("google-drive-sim@wip-charles", variant="alice")
8
+
9
+ @world.over(prod_list_files) # same name, same signature
10
+ def list_files(folder=None): ...
11
+
12
+ # wrapped calls now read/write alice's SEEDED database instead of prod;
13
+ # with no world loaded the decorator is inert and prod impls run.
14
+
15
+ result = world.run(my_agent) # grade the end state, file the
16
+ # rollout back as a version-pinned
17
+ # run (branch tag included)
18
+
19
+ A world is a pulled container bundle (branch-aware — `slug@branch`), one task
20
+ VARIANT of it (its own database, materialized fresh from the task's seed), and
21
+ the bundle's own tool handlers bound to that database. Wrapping maps a prod
22
+ tool to the bundle handler of the SAME name and fails loudly on a tool the
23
+ world does not serve — a silent passthrough would let a "world run" quietly
24
+ hit production, which is the exact accident this module exists to prevent.
25
+
26
+ Conventions a bundle opts into (see samples/environments/google-drive-sim):
27
+ * task dirs carry ``[metadata] variant = "<name>"`` in task.toml;
28
+ * ``environment/`` holds ``schema.sql`` + ``seed.sql`` + ``tools/`` with a
29
+ module exposing ``TOOLS = {name: callable}`` and optionally
30
+ ``DB_ENV_VAR`` naming the env var its tools read the db path from
31
+ (default ``GATEWAY_WORLD_DB``);
32
+ * ``environment/grade.py`` reads ``DRIVE_DB``-style env + ``GRADE_OUT`` and
33
+ writes ``reward.json`` — which makes SDK grading identical to the harbor
34
+ container's own verifier.
35
+
36
+ Stdlib only, like the rest of benchmark_hub.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import functools
42
+ import importlib.util
43
+ import json
44
+ import os
45
+ import re
46
+ import sqlite3
47
+ import subprocess
48
+ import sys
49
+ import tempfile
50
+ import time
51
+ from pathlib import Path
52
+ from typing import Any, Callable, Dict, List, Optional
53
+
54
+ from .benchmark_hub.client import (
55
+ BenchmarkPushError,
56
+ GatewayBenchmarkClient,
57
+ local_tree_state,
58
+ )
59
+ from .benchmark_hub.evals import GatewayEvalsClient
60
+
61
+ try: # py311+
62
+ import tomllib
63
+ except ImportError: # pragma: no cover
64
+ tomllib = None # type: ignore[assignment]
65
+
66
+
67
+ class WorldError(RuntimeError):
68
+ pass
69
+
70
+
71
+ def _task_variant(task_dir: Path) -> Optional[str]:
72
+ toml_path = task_dir / "task.toml"
73
+ if not toml_path.is_file():
74
+ return None
75
+ if tomllib is not None:
76
+ try:
77
+ data = tomllib.loads(toml_path.read_text(encoding="utf-8"))
78
+ v = (data.get("metadata") or {}).get("variant")
79
+ return str(v) if v else None
80
+ except Exception:
81
+ pass
82
+ m = re.search(r'^\s*variant\s*=\s*"([^"]+)"', toml_path.read_text(encoding="utf-8"), re.M)
83
+ return m.group(1) if m else None
84
+
85
+
86
+ def _pick_task(bundle: Path, variant: Optional[str]) -> Path:
87
+ tasks = sorted(p for p in (bundle / "tasks").iterdir() if (p / "task.toml").is_file()) if (bundle / "tasks").is_dir() else []
88
+ if not tasks:
89
+ raise WorldError(f"{bundle} has no tasks/*/task.toml — not a harbor world")
90
+ if variant is None:
91
+ return tasks[0]
92
+ for t in tasks:
93
+ if _task_variant(t) == variant:
94
+ return t
95
+ for t in tasks: # dir-name fallback, per the accepted identity rule
96
+ if variant in t.name:
97
+ return t
98
+ known = [f"{t.name} (variant={_task_variant(t) or '—'})" for t in tasks]
99
+ raise WorldError(f"no task matches variant {variant!r}; have: {', '.join(known)}")
100
+
101
+
102
+ def tasks(bundle_dir: str | Path) -> List[Dict[str, str]]:
103
+ """Rich task listing for a bundle: one dict per task with its name,
104
+ variant, and instruction — what a suite runner or a notebook needs to
105
+ work with tasks as data instead of shelling out to the CLI.
106
+
107
+ first = worlds.load("google-drive-sim@main")
108
+ for task in worlds.tasks(first.bundle_dir):
109
+ print(task["variant"], "—", task["instruction"][:60])
110
+ """
111
+ bundle = Path(bundle_dir)
112
+ listing: List[Dict[str, str]] = []
113
+ for name in variants(bundle):
114
+ task_dir = _pick_task(bundle, name)
115
+ instruction_file = task_dir / "instruction.md"
116
+ listing.append(
117
+ {
118
+ "name": task_dir.name,
119
+ "variant": name,
120
+ "instruction": (
121
+ instruction_file.read_text(encoding="utf-8")
122
+ if instruction_file.is_file()
123
+ else ""
124
+ ),
125
+ }
126
+ )
127
+ return listing
128
+
129
+
130
+ def variants(bundle_dir: str | Path) -> List[str]:
131
+ """Variant names a bundle serves, in task order — one entry per
132
+ ``tasks/*/task.toml``, named by the task's own variant when it declares
133
+ one and by the directory otherwise. This is the suite-runner's loop:
134
+
135
+ first = worlds.load("google-drive-sim@main")
136
+ for name in worlds.variants(first.bundle_dir):
137
+ world = worlds.load_path(first.bundle_dir, variant=name)
138
+ """
139
+ bundle = Path(bundle_dir)
140
+ tasks = (
141
+ sorted(p for p in (bundle / "tasks").iterdir() if (p / "task.toml").is_file())
142
+ if (bundle / "tasks").is_dir()
143
+ else []
144
+ )
145
+ if not tasks:
146
+ raise WorldError(f"{bundle} has no tasks/*/task.toml — not a harbor world")
147
+ return [_task_variant(t) or t.name for t in tasks]
148
+
149
+
150
+ def _load_tools_module(task_dir: Path):
151
+ tools_dir = task_dir / "environment" / "tools"
152
+ candidates = sorted(tools_dir.glob("*.py")) if tools_dir.is_dir() else []
153
+ for path in candidates:
154
+ if path.name.startswith("_"):
155
+ continue
156
+ spec = importlib.util.spec_from_file_location(f"gateway_world_tools_{path.stem}_{id(path)}", path)
157
+ if spec is None or spec.loader is None:
158
+ continue
159
+ module = importlib.util.module_from_spec(spec)
160
+ spec.loader.exec_module(module)
161
+ if hasattr(module, "TOOLS") and isinstance(module.TOOLS, dict):
162
+ return module
163
+ raise WorldError(
164
+ f"{task_dir.name} serves no tools module (environment/tools/*.py exposing TOOLS)"
165
+ )
166
+
167
+
168
+ class World:
169
+ def __init__(
170
+ self,
171
+ *,
172
+ bundle_dir: Path,
173
+ task_dir: Path,
174
+ variant: Optional[str],
175
+ slug: Optional[str],
176
+ branch: Optional[str],
177
+ version_id: Optional[str],
178
+ file_block: Optional[str] = None,
179
+ ) -> None:
180
+ from .environment.schema.compiler import require_legacy_bundle
181
+
182
+ require_legacy_bundle(bundle_dir, "Harbor world sessions")
183
+ self.bundle_dir = bundle_dir
184
+ self.task_dir = task_dir
185
+ self.variant = variant
186
+ self.slug = slug
187
+ self.branch = branch
188
+ self.version_id = version_id
189
+ # Why a run of THIS world cannot be filed (dirty checkout, no hub
190
+ # link). None whenever version_id is set.
191
+ self.file_block = file_block
192
+ self.call_log: List[Dict[str, Any]] = []
193
+
194
+ env_dir = task_dir / "environment"
195
+ schema = env_dir / "schema.sql"
196
+ if not schema.is_file():
197
+ raise WorldError(f"{task_dir.name} has no environment/schema.sql to materialize")
198
+ self.db_path = str(Path(tempfile.mkdtemp(prefix="gateway-world-")) / "world.db")
199
+ conn = sqlite3.connect(self.db_path)
200
+ try:
201
+ conn.executescript(schema.read_text(encoding="utf-8"))
202
+ seed = env_dir / "seed.sql"
203
+ if seed.is_file():
204
+ conn.executescript(seed.read_text(encoding="utf-8"))
205
+ conn.commit()
206
+ finally:
207
+ conn.close()
208
+
209
+ self._module = _load_tools_module(task_dir)
210
+ self._db_env_var = getattr(self._module, "DB_ENV_VAR", "GATEWAY_WORLD_DB")
211
+ os.environ[self._db_env_var] = self.db_path
212
+
213
+ # Handlers bound to THIS world, each recording into the call log so an
214
+ # SDK run files a real trajectory, not just a score.
215
+ self.tools: Dict[str, Callable[..., Any]] = {
216
+ name: self._record(name, fn) for name, fn in self._module.TOOLS.items()
217
+ }
218
+
219
+ def _record(self, name: str, fn: Callable[..., Any]) -> Callable[..., Any]:
220
+ @functools.wraps(fn)
221
+ def recorded(*args: Any, **kwargs: Any) -> Any:
222
+ entry: Dict[str, Any] = {
223
+ "tool": name,
224
+ "arguments": json.dumps({"args": args, "kwargs": kwargs}, default=str)[:2000],
225
+ }
226
+ os.environ[self._db_env_var] = self.db_path
227
+ try:
228
+ result = fn(*args, **kwargs)
229
+ entry["result_preview"] = json.dumps(result, default=str)[:2000]
230
+ return result
231
+ except Exception as exc:
232
+ entry["error"] = str(exc)[:500]
233
+ raise
234
+ finally:
235
+ self.call_log.append(entry)
236
+
237
+ return recorded
238
+
239
+ def over(
240
+ self, prod_fn: Callable[..., Any], *, name: Optional[str] = None
241
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
242
+ """Decorator: route calls to the world's handler of the same name.
243
+
244
+ ``@world.over(prod_list_files)`` on a function keeps the prod
245
+ implementation as the fallback ONLY when this world is inert (never
246
+ constructed / explicitly disabled) — a loaded world that does not
247
+ serve the tool raises instead of silently touching production.
248
+ """
249
+ def decorate(fn: Callable[..., Any]) -> Callable[..., Any]:
250
+ # The DECORATED function's name is the agent's tool name; the prod
251
+ # fn is only the implementation being displaced.
252
+ tool_name = name or fn.__name__
253
+ if tool_name not in self.tools:
254
+ raise WorldError(
255
+ f"world {self.slug or self.bundle_dir.name!r} does not serve "
256
+ f"tool {tool_name!r}; it serves: {', '.join(sorted(self.tools))}"
257
+ )
258
+ world_fn = self.tools[tool_name]
259
+
260
+ @functools.wraps(prod_fn)
261
+ def routed(*args: Any, **kwargs: Any) -> Any:
262
+ return world_fn(*args, **kwargs)
263
+
264
+ return routed
265
+
266
+ return decorate
267
+
268
+ def wrap(self, prod_tools: Dict[str, Callable[..., Any]]) -> Dict[str, Callable[..., Any]]:
269
+ """Wholesale mapping: {name: prod_fn} → {name: world_fn}, fail-loud on
270
+ any name the world does not serve."""
271
+ missing = sorted(set(prod_tools) - set(self.tools))
272
+ if missing:
273
+ raise WorldError(
274
+ f"world does not serve: {', '.join(missing)}; it serves: {', '.join(sorted(self.tools))}"
275
+ )
276
+ return {name: self.tools[name] for name in prod_tools}
277
+
278
+ def override(
279
+ self, name: str, fn: Callable[..., Any]
280
+ ) -> Callable[..., Any]:
281
+ """The INVERSE of wrap(): keep the world's seeded database, tasks and
282
+ graders — swap ONE tool's implementation with your LOCAL one. This is
283
+ the schema-stable dev loop for tool logic changes: the local fn reads
284
+ the same database the world materialized (the module convention — the
285
+ env var named by ``DB_ENV_VAR`` already points at it), the task's own
286
+ grader judges the end state, and calls are recorded into the filed
287
+ call log like any world tool. Unknown names fail loudly: overriding a
288
+ tool the world does not serve would silently test nothing."""
289
+ if name not in self.tools:
290
+ raise WorldError(
291
+ f"world does not serve a tool named {name!r}; it serves: "
292
+ f"{', '.join(sorted(self.tools))}"
293
+ )
294
+ recorded = self._record(name, fn)
295
+ self.tools[name] = recorded
296
+ return recorded
297
+
298
+ def grade(self) -> Dict[str, Any]:
299
+ """Run the task's OWN grader against the world's end state — the same
300
+ code the harbor container runs, pointed at the materialized db."""
301
+ grader = self.task_dir / "environment" / "grade.py"
302
+ if not grader.is_file():
303
+ raise WorldError(f"{self.task_dir.name} ships no environment/grade.py")
304
+ out_dir = tempfile.mkdtemp(prefix="gateway-grade-")
305
+ env = dict(os.environ)
306
+ env[self._db_env_var] = self.db_path
307
+ env["DRIVE_DB"] = env.get("DRIVE_DB", self.db_path)
308
+ env["GRADE_OUT"] = out_dir
309
+ proc = subprocess.run(
310
+ [sys.executable, str(grader)], env=env, capture_output=True, text=True, timeout=120
311
+ )
312
+ reward_path = Path(out_dir) / "reward.json"
313
+ if proc.returncode != 0 or not reward_path.is_file():
314
+ raise WorldError(
315
+ f"grader failed (exit {proc.returncode}): {proc.stderr.strip()[:400]}"
316
+ )
317
+ return json.loads(reward_path.read_text(encoding="utf-8"))
318
+
319
+ def run(
320
+ self,
321
+ agent_fn: Callable[["World"], Any],
322
+ *,
323
+ model: str = "sdk-dev-loop",
324
+ file: bool = True,
325
+ host: Optional[str] = None,
326
+ session_id: Optional[str] = None,
327
+ ) -> Dict[str, Any]:
328
+ """Execute ``agent_fn(world)``, grade the end state, and (by default)
329
+ file the result back as a version-pinned run — landing on the same
330
+ Runs/Rollouts surfaces as hosted runs, branch tag included (the
331
+ server stamps metadata.ref from the pinned version).
332
+
333
+ Pass ``session_id`` when the agent traces itself (the id given to
334
+ ``tracing.session(...)``): the platform reads ``info.sessionId`` off
335
+ the sample to link the run to the traced session — that link is the
336
+ trajectory. The filed sample carries the task name, the instruction
337
+ as the prompt, and ``agent_fn``'s return value as the completion, so
338
+ the run page shows the episode, not an empty row."""
339
+ started = time.time()
340
+ agent_error: Optional[str] = None
341
+ final: Any = None
342
+ try:
343
+ final = agent_fn(self)
344
+ except Exception as exc:
345
+ agent_error = str(exc)[:500]
346
+ verdict = self.grade()
347
+ reward = verdict.get("reward")
348
+ subs = {k: v for k, v in verdict.items() if k != "reward"}
349
+ result: Dict[str, Any] = {
350
+ "reward": reward,
351
+ "rewards": subs,
352
+ "agentError": agent_error,
353
+ "calls": len(self.call_log),
354
+ "durationSeconds": round(time.time() - started, 3),
355
+ **({"sessionId": session_id} if session_id else {}),
356
+ }
357
+ if not file:
358
+ return result
359
+ if not self.version_id:
360
+ raise WorldError(
361
+ self.file_block
362
+ or "this world was loaded from a local path — pass file=False, "
363
+ "or load it from the hub so runs can pin a version"
364
+ )
365
+ instruction_file = self.task_dir / "instruction.md"
366
+ instruction = (
367
+ instruction_file.read_text(encoding="utf-8")
368
+ if instruction_file.is_file()
369
+ else ""
370
+ )
371
+ evals = GatewayEvalsClient.from_env(host)
372
+ evaluation_id = evals.create_evaluation(
373
+ self.version_id,
374
+ model=model,
375
+ rollouts_per_example=1,
376
+ metadata={
377
+ "source": "sdk-world-run",
378
+ "variant": self.variant,
379
+ **({"sessionId": session_id} if session_id else {}),
380
+ },
381
+ )
382
+ evals.push_samples(
383
+ evaluation_id,
384
+ [
385
+ {
386
+ "exampleId": self.task_dir.name,
387
+ "task": self.task_dir.name,
388
+ "rolloutIndex": 0,
389
+ "prompt": instruction[:20000] or None,
390
+ "completion": (
391
+ str(final)[:20000] if final is not None else None
392
+ ),
393
+ "reward": reward,
394
+ "score": reward,
395
+ "correct": (reward >= 1.0) if isinstance(reward, (int, float)) else None,
396
+ "rewards": subs,
397
+ "info": {
398
+ "variant": self.variant,
399
+ "source": "sdk-world-run",
400
+ "agentError": agent_error,
401
+ "callLog": self.call_log[-50:],
402
+ # The platform's session link + token-usage join read
403
+ # exactly this key.
404
+ **({"sessionId": session_id} if session_id else {}),
405
+ },
406
+ }
407
+ ],
408
+ )
409
+ evals.finalize_evaluation(
410
+ evaluation_id,
411
+ status="FAILED" if agent_error else "COMPLETED",
412
+ metrics={
413
+ # avg_reward is what the Runs list renders as the score —
414
+ # a finalize without it files a run that LOOKS unscored.
415
+ **({"avg_reward": reward} if isinstance(reward, (int, float)) else {}),
416
+ "reward": reward,
417
+ **{f"rewards.{k}": v for k, v in subs.items()},
418
+ },
419
+ )
420
+ result["evaluationId"] = evaluation_id
421
+ return result
422
+
423
+
424
+ def load_path(bundle_dir: str | Path, *, variant: Optional[str] = None) -> World:
425
+ """Load a world from a LOCAL directory. A dir that is a hub checkout
426
+ (`gateway bench pull`/`checkout`/`push` wrote `.gateway/`) whose tree
427
+ still matches its recorded base loads with that base's slug/branch/
428
+ version — runs file exactly like `load()`. A drifted checkout or an
429
+ ad-hoc dir still runs and grades, but filing is refused with the reason
430
+ (pinning drifted code to a version would fabricate provenance)."""
431
+ bundle = Path(bundle_dir)
432
+ task = _pick_task(bundle, variant)
433
+ state = local_tree_state(bundle)
434
+ meta = state["meta"] or {}
435
+ clean = bool(state["clean"]) and bool(meta.get("versionId"))
436
+ if clean:
437
+ slug, branch, version_id, file_block = (
438
+ meta.get("slug"),
439
+ meta.get("branch") or meta.get("ref"),
440
+ meta.get("versionId"),
441
+ None,
442
+ )
443
+ elif meta.get("versionId"):
444
+ dirty: List[str] = state["dirty"]
445
+ shown = ", ".join(dirty[:5]) + (", …" if len(dirty) > 5 else "")
446
+ slug, branch, version_id = meta.get("slug"), None, None
447
+ file_block = (
448
+ f"this checkout differs from "
449
+ f"{meta.get('branch') or meta.get('ref') or 'its base'}@"
450
+ f"{str(meta.get('contentHash') or '')[:8]} in {len(dirty)} "
451
+ f"file(s) ({shown}) — push first (`gateway bench push`) so the "
452
+ f"run pins the code it actually executed, or pass file=False"
453
+ )
454
+ else:
455
+ slug, branch, version_id = None, None, None
456
+ file_block = (
457
+ "this directory has no hub link (.gateway/.env-metadata.json) — "
458
+ "push it (`gateway bench push`) so runs can pin a version, or "
459
+ "pass file=False"
460
+ )
461
+ return World(
462
+ bundle_dir=bundle,
463
+ task_dir=task,
464
+ variant=variant or _task_variant(task) or task.name,
465
+ slug=slug,
466
+ branch=branch,
467
+ version_id=version_id,
468
+ file_block=file_block,
469
+ )
470
+
471
+
472
+ def load(
473
+ slug_ref: str,
474
+ *,
475
+ variant: Optional[str] = None,
476
+ host: Optional[str] = None,
477
+ ) -> World:
478
+ """Pull ``slug`` / ``slug@branch|tag|semver|hash`` from the hub and load
479
+ one variant of it. The pulled branch travels with the world, so filed
480
+ runs carry it."""
481
+ client = GatewayBenchmarkClient.from_env(host)
482
+ target = tempfile.mkdtemp(prefix="gateway-world-pull-")
483
+ pulled = client.pull(slug_ref, target=target)
484
+ bundle = Path(pulled["target"])
485
+ task = _pick_task(bundle, variant)
486
+ return World(
487
+ bundle_dir=bundle,
488
+ task_dir=task,
489
+ variant=variant or _task_variant(task) or task.name,
490
+ slug=pulled.get("slug"),
491
+ branch=pulled.get("branch"),
492
+ version_id=pulled.get("versionId"),
493
+ )
494
+
495
+ # ── Object model: WorldSet / Task / TaskEnv ──────────────────────────────────
496
+ # `worlds.open()` is the handle FOR THE WORLD (one pull, one version pin);
497
+ # `Task` is the scenario as data; `TaskEnv` (== the classic `World`) is one
498
+ # task's materialized instance. `worlds.load()` remains sugar over this and
499
+ # behaves exactly as before.
500
+
501
+ TaskEnv = World
502
+ """The materialized per-task instance — seeded db, recorded tools, grader.
503
+ The class historically named `World`; both names stay valid."""
504
+
505
+
506
+ class Task:
507
+ """One scenario of a WorldSet, as data first: inspect `variant`,
508
+ `instruction`, and `name` without paying for any seeding. `start()`
509
+ materializes the environment; `run()` is start → agent → grade → file."""
510
+
511
+ def __init__(self, world_set: "WorldSet", variant: str) -> None:
512
+ self.world_set = world_set
513
+ self.variant = variant
514
+ task_dir = _pick_task(world_set.bundle_dir, variant)
515
+ self.name = task_dir.name
516
+ instruction_file = task_dir / "instruction.md"
517
+ self.instruction = (
518
+ instruction_file.read_text(encoding="utf-8")
519
+ if instruction_file.is_file()
520
+ else ""
521
+ )
522
+
523
+ def start(self) -> TaskEnv:
524
+ return load_path(self.world_set.bundle_dir, variant=self.variant)
525
+
526
+ def run(
527
+ self,
528
+ agent_fn: Callable[[TaskEnv], Any],
529
+ *,
530
+ model: Optional[str] = None,
531
+ file: bool = True,
532
+ session_id: Optional[str] = None,
533
+ ) -> Dict[str, Any]:
534
+ env = self.start()
535
+ return env.run(
536
+ agent_fn,
537
+ file=file,
538
+ session_id=session_id,
539
+ # Only override the env's default label when a model was named.
540
+ **({"model": model} if model else {}),
541
+ )
542
+
543
+ def __repr__(self) -> str: # notebook-friendly
544
+ return f"Task({self.variant!r}, name={self.name!r})"
545
+
546
+
547
+ class WorldSet:
548
+ """THE world: one pull, one pinned version, every task. Iterable and
549
+ indexable by variant; `run_all` is the whole suite as one primitive."""
550
+
551
+ def __init__(self, bundle_dir: Path, *, slug: Optional[str],
552
+ branch: Optional[str], version_id: Optional[str]) -> None:
553
+ self.bundle_dir = Path(bundle_dir)
554
+ self.slug = slug
555
+ self.branch = branch
556
+ self.version_id = version_id
557
+ self.tasks: List[Task] = [Task(self, v) for v in variants(self.bundle_dir)]
558
+
559
+ def task(self, variant: str) -> Task:
560
+ for task in self.tasks:
561
+ if task.variant == variant or variant in task.name:
562
+ return task
563
+ known = ", ".join(t.variant for t in self.tasks)
564
+ raise WorldError(f"no task matches {variant!r}; have: {known}")
565
+
566
+ def __iter__(self):
567
+ return iter(self.tasks)
568
+
569
+ def __len__(self) -> int:
570
+ return len(self.tasks)
571
+
572
+ def run_all(
573
+ self,
574
+ agent_fn: Callable[[TaskEnv], Any],
575
+ *,
576
+ model: Optional[str] = None,
577
+ tasks: Optional[List[str]] = None,
578
+ fail_under: Optional[float] = None,
579
+ file: bool = True,
580
+ ) -> Dict[str, Any]:
581
+ """Run the agent across every task (or the named subset), grading and
582
+ filing each. Returns {results: {variant: result}, mean, passed} —
583
+ `passed` is None without a bar, else mean >= fail_under."""
584
+ chosen = (
585
+ [self.task(v) for v in tasks] if tasks is not None else self.tasks
586
+ )
587
+ results: Dict[str, Dict[str, Any]] = {}
588
+ for task in chosen:
589
+ results[task.variant] = task.run(agent_fn, model=model, file=file)
590
+ rewards = [float(r.get("reward") or 0.0) for r in results.values()]
591
+ mean = sum(rewards) / len(rewards) if rewards else 0.0
592
+ return {
593
+ "results": results,
594
+ "mean": mean,
595
+ "passed": None if fail_under is None else mean >= fail_under,
596
+ }
597
+
598
+ def __repr__(self) -> str:
599
+ ref = f"@{self.branch}" if self.branch else ""
600
+ return f"WorldSet({(self.slug or str(self.bundle_dir)) + ref!r}, tasks={len(self.tasks)})"
601
+
602
+
603
+ def open( # noqa: A001 - deliberate, mirrors pathlib-style opens
604
+ slug_ref: str,
605
+ *,
606
+ host: Optional[str] = None,
607
+ ) -> WorldSet:
608
+ """Open a world from the hub: ONE pull shared by every task.
609
+
610
+ world = worlds.open("google-drive-sim@main")
611
+ world.task("alice").run(my_agent, model="openai/gpt-5.6-terra")
612
+ world.run_all(my_agent, fail_under=0.6)
613
+ """
614
+ client = GatewayBenchmarkClient.from_env(host)
615
+ target = tempfile.mkdtemp(prefix="gateway-world-pull-")
616
+ pulled = client.pull(slug_ref, target=target)
617
+ bundle = Path(pulled["target"])
618
+ state = local_tree_state(bundle)
619
+ meta = state["meta"] or {}
620
+ return WorldSet(
621
+ bundle,
622
+ slug=meta.get("slug"),
623
+ branch=meta.get("branch") or meta.get("ref"),
624
+ version_id=meta.get("versionId"),
625
+ )
626
+
627
+
628
+ def open_path(bundle_dir: str | Path) -> WorldSet:
629
+ """Open a LOCAL checkout as a WorldSet (same filing rules as load_path)."""
630
+ bundle = Path(bundle_dir)
631
+ state = local_tree_state(bundle)
632
+ meta = state["meta"] or {}
633
+ clean = bool(state["clean"]) and bool(meta.get("versionId"))
634
+ return WorldSet(
635
+ bundle,
636
+ slug=meta.get("slug"),
637
+ branch=(meta.get("branch") or meta.get("ref")) if clean else None,
638
+ version_id=meta.get("versionId") if clean else None,
639
+ )