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
@@ -0,0 +1,281 @@
1
+ """Bundle inspection: kind detection, manifest parsing, handler loading.
2
+
3
+ Two structurally-detected environment kinds (spec §3.1 + addendum):
4
+
5
+ - **mock-tools** — ``gateway-env.toml`` at the bundle root, ``tools/*.py``
6
+ handlers, ``db/schema.sql`` (+ ``db/seed.sql``), ``verifiers/*.sql``.
7
+ - **harbor** — a tree of Harbor task dirs (``tasks/*/task.toml`` or
8
+ ``*/task.toml``), mirroring ``benchmark_hub.harbor_adapter.detect_env_kind``.
9
+
10
+ Handler loading executes each ``tools/*.py`` with this SDK's
11
+ :mod:`gatewaysdk.environment.runtime` injected into ``sys.modules`` as
12
+ ``gateway_env`` for the duration of the load — the module the generated
13
+ handlers import — and restores ``sys.modules`` afterwards.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import functools
19
+ import importlib.util
20
+ import inspect
21
+ import sys
22
+ import tomllib
23
+ import uuid
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any, Callable, Dict, List, Optional
27
+
28
+ from . import runtime
29
+ from .errors import EnvironmentNotReady, WorldStartError
30
+
31
+ KIND_MOCK_TOOLS = "mock-tools"
32
+ KIND_API = "api"
33
+ KIND_HARBOR = "harbor"
34
+
35
+
36
+ def detect_kind(bundle_path: Path) -> str:
37
+ """"api" iff ``gateway-api-world.toml`` sits at the root; else "mock-tools"
38
+ iff ``gateway-env.toml`` does; else "harbor" iff the tree holds Harbor task
39
+ dirs (same glob heuristics as the runner's ``detect_env_kind``); else the
40
+ bundle is not runnable here."""
41
+ from .schema import bundle_mode
42
+
43
+ bundle_mode(bundle_path) # Orphan/invalid schema declarations never downgrade.
44
+ if (bundle_path / "gateway-api-world.toml").is_file():
45
+ return KIND_API
46
+ if (bundle_path / "gateway-env.toml").is_file():
47
+ return KIND_MOCK_TOOLS
48
+ if list(bundle_path.glob("tasks/*/task.toml")) or list(bundle_path.glob("*/task.toml")):
49
+ return KIND_HARBOR
50
+ raise EnvironmentNotReady(
51
+ f"{bundle_path} is neither a mock-tools bundle (no gateway-env.toml) nor "
52
+ "a harbor task tree (no */task.toml) — nothing gwenv can run locally."
53
+ )
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # mock-tools: manifest + handlers
58
+ # ---------------------------------------------------------------------------
59
+
60
+
61
+ @dataclass
62
+ class EnvManifest:
63
+ """Parsed ``gateway-env.toml``."""
64
+
65
+ name: str
66
+ serve: List[str]
67
+ db_engine: str = "sqlite"
68
+ raw: Dict[str, Any] = field(default_factory=dict)
69
+
70
+
71
+ def parse_manifest(bundle_path: Path) -> EnvManifest:
72
+ manifest_path = bundle_path / "gateway-env.toml"
73
+ try:
74
+ data = tomllib.loads(manifest_path.read_text(encoding="utf-8"))
75
+ except (OSError, tomllib.TOMLDecodeError) as error:
76
+ raise EnvironmentNotReady(f"cannot parse {manifest_path}: {error}") from error
77
+
78
+ # Builder-generated manifests use an [environment] table; hand-written
79
+ # ones may use [env] or a top-level name.
80
+ env_table: Dict[str, Any] = {}
81
+ for key in ("environment", "env"):
82
+ if isinstance(data.get(key), dict):
83
+ env_table = data[key]
84
+ break
85
+ name = env_table.get("name") or data.get("name") or bundle_path.name
86
+
87
+ tools_table = data.get("tools") if isinstance(data.get("tools"), dict) else {}
88
+ serve = tools_table.get("serve") or []
89
+ if not isinstance(serve, list):
90
+ raise EnvironmentNotReady(f"{manifest_path}: [tools] serve must be a list of tool names")
91
+
92
+ db_value = data.get("db")
93
+ if isinstance(db_value, dict):
94
+ engine = db_value.get("engine") or env_table.get("db") or "sqlite"
95
+ elif isinstance(db_value, str):
96
+ engine = db_value
97
+ else:
98
+ engine = env_table.get("db") or "sqlite"
99
+ if engine != "sqlite":
100
+ raise WorldStartError(
101
+ f"db engine {engine!r} is not supported in local mode — P1 runs "
102
+ "sqlite in-process (Postgres arrives with hosted worlds, spec §4.9)."
103
+ )
104
+ return EnvManifest(name=str(name), serve=[str(s) for s in serve], db_engine=engine, raw=data)
105
+
106
+
107
+ def _wrap_run_handler(fn: Callable[..., Any], tool_name: str) -> Callable[..., Any]:
108
+ """Adapt the generated-bundle handler contract to the SDK's call shapes.
109
+
110
+ Builder-generated bundles expose ``run(input: dict, conn=None)`` in
111
+ ``tools/<tool_name>.py`` — the whole tool input arrives as one dict, and
112
+ the runtime injects an open sqlite connection so writes land in the
113
+ active world (not the bundle's on-disk default db).
114
+ """
115
+ takes_conn = "conn" in inspect.signature(fn).parameters
116
+
117
+ @functools.wraps(fn)
118
+ def handler(*args: Any, **kwargs: Any) -> Any:
119
+ tool_input = args[0] if args else (kwargs if runtime._active_store.get() is not None else (kwargs or None))
120
+ if takes_conn:
121
+ return fn(tool_input, conn=runtime._active_connection.get())
122
+ return fn(tool_input)
123
+
124
+ handler.__name__ = tool_name
125
+ return handler
126
+
127
+
128
+ def load_handlers(bundle_path: Path, manifest: EnvManifest) -> Dict[str, Callable[..., Any]]:
129
+ """Execute ``tools/*.py`` and return the tool-name → handler map.
130
+
131
+ Registration paths, in priority order:
132
+ 1. ``@tool(name=...)`` / bare ``@tool`` decorations (captured via the
133
+ runtime's load-registry contextvar during module exec);
134
+ 2. fallback discovery — a plain module-level ``def <tool_name>`` whose
135
+ name matches a served tool that no decorator claimed;
136
+ 3. the generated-bundle contract — ``run(input, conn=None)`` in the
137
+ module whose FILE STEM is the served tool name (wrapped so the
138
+ active world's connection is injected).
139
+
140
+ ``gateway_env`` is importable only inside this function's dynamic extent.
141
+ """
142
+ from .schema import bundle_mode, check_bundle
143
+
144
+ contract = check_bundle(bundle_path) if bundle_mode(bundle_path) is not None else None
145
+ tools_dir = bundle_path / "tools"
146
+ registry: Dict[str, Callable[..., Any]] = {}
147
+ modules = []
148
+ modules_by_stem: Dict[str, Any] = {}
149
+
150
+ handler_files = sorted(tools_dir.glob("*.py")) if tools_dir.is_dir() else []
151
+ load_token = runtime._load_registry.set(registry)
152
+ strict_token = runtime._strict_registration.set(contract is not None)
153
+ saved = sys.modules.get("gateway_env")
154
+ sys.modules["gateway_env"] = runtime
155
+ # Generated bundles use intra-bundle imports ("from tools._db import …"),
156
+ # so the bundle root must be importable for the load's dynamic extent.
157
+ bundle_root = str(bundle_path)
158
+ path_added = bundle_root not in sys.path
159
+ if path_added:
160
+ sys.path.insert(0, bundle_root)
161
+ saved_pkgs = {
162
+ name: sys.modules.get(name)
163
+ for name in list(sys.modules)
164
+ if name == "tools" or name.startswith("tools.")
165
+ }
166
+ for name in saved_pkgs:
167
+ sys.modules.pop(name, None)
168
+ try:
169
+ for handler_file in handler_files:
170
+ if handler_file.name.startswith("_"):
171
+ continue
172
+ module_name = f"_gateway_env_bundle_{uuid.uuid4().hex[:8]}_{handler_file.stem}"
173
+ spec = importlib.util.spec_from_file_location(module_name, handler_file)
174
+ if spec is None or spec.loader is None: # pragma: no cover — importlib edge
175
+ raise WorldStartError(f"cannot load handler module {handler_file}")
176
+ module = importlib.util.module_from_spec(spec)
177
+ sys.modules[module_name] = module
178
+ try:
179
+ spec.loader.exec_module(module)
180
+ except Exception as error:
181
+ raise WorldStartError(
182
+ f"handler module {handler_file.name} failed to import: {error}"
183
+ ) from error
184
+ modules.append(module)
185
+ modules_by_stem[handler_file.stem] = module
186
+ finally:
187
+ if saved is not None:
188
+ sys.modules["gateway_env"] = saved
189
+ else:
190
+ sys.modules.pop("gateway_env", None)
191
+ if path_added:
192
+ try:
193
+ sys.path.remove(bundle_root)
194
+ except ValueError:
195
+ pass
196
+ # Drop bundle-local "tools" modules and restore anything shadowed.
197
+ for name in [
198
+ n for n in sys.modules if n == "tools" or n.startswith("tools.")
199
+ ]:
200
+ sys.modules.pop(name, None)
201
+ for name, module in saved_pkgs.items():
202
+ if module is not None:
203
+ sys.modules[name] = module
204
+ runtime._load_registry.reset(load_token)
205
+ runtime._strict_registration.reset(strict_token)
206
+
207
+ # Fallbacks for undecorated bundles.
208
+ for tool_name in manifest.serve:
209
+ if tool_name in registry:
210
+ continue
211
+ # (2) a plain module-level function named exactly like the tool.
212
+ for module in modules:
213
+ candidate = getattr(module, tool_name, None)
214
+ if callable(candidate) and getattr(candidate, "__module__", None) == module.__name__:
215
+ registry[tool_name] = candidate
216
+ break
217
+ else:
218
+ # (3) run(input, conn=None) in tools/<tool_name>.py.
219
+ stem_module = modules_by_stem.get(tool_name)
220
+ run_fn = getattr(stem_module, "run", None) if stem_module else None
221
+ if callable(run_fn):
222
+ registry[tool_name] = _wrap_run_handler(run_fn, tool_name)
223
+ if contract is not None and set(registry) != set(contract.tools):
224
+ from .schema import ContractError
225
+
226
+ raise ContractError("tool", "Authored handlers must exactly cover the declared tools")
227
+ return registry
228
+
229
+
230
+ # ---------------------------------------------------------------------------
231
+ # harbor: task enumeration
232
+ # ---------------------------------------------------------------------------
233
+
234
+
235
+ @dataclass
236
+ class HarborTask:
237
+ """One Harbor task dir (``task.toml`` + ``instruction.md`` + ``tests/``)."""
238
+
239
+ name: str
240
+ path: Path
241
+ instruction: Optional[str]
242
+ docker_image: Optional[str]
243
+ has_tests: bool
244
+ config: Dict[str, Any] = field(default_factory=dict)
245
+
246
+ @property
247
+ def prompt(self) -> Optional[str]:
248
+ """Alias — the task's instruction is its prompt."""
249
+ return self.instruction
250
+
251
+
252
+ def harbor_tasks_root(bundle_path: Path) -> Path:
253
+ """The dir whose CHILDREN are task dirs: ``<src>/tasks`` if present, else
254
+ ``<src>`` (mirrors ``harbor_adapter.tasks_root``)."""
255
+ tasks = bundle_path / "tasks"
256
+ return tasks if list(tasks.glob("*/task.toml")) else bundle_path
257
+
258
+
259
+ def load_harbor_tasks(bundle_path: Path) -> List[HarborTask]:
260
+ tasks: List[HarborTask] = []
261
+ for task_toml in sorted(harbor_tasks_root(bundle_path).glob("*/task.toml")):
262
+ task_dir = task_toml.parent
263
+ try:
264
+ config = tomllib.loads(task_toml.read_text(encoding="utf-8"))
265
+ except (OSError, tomllib.TOMLDecodeError) as error:
266
+ raise EnvironmentNotReady(f"cannot parse {task_toml}: {error}") from error
267
+ environment = config.get("environment") if isinstance(config.get("environment"), dict) else {}
268
+ instruction_path = task_dir / "instruction.md"
269
+ tasks.append(
270
+ HarborTask(
271
+ name=task_dir.name,
272
+ path=task_dir,
273
+ instruction=(
274
+ instruction_path.read_text(encoding="utf-8") if instruction_path.is_file() else None
275
+ ),
276
+ docker_image=environment.get("docker_image"),
277
+ has_tests=(task_dir / "tests").is_dir(),
278
+ config=config,
279
+ )
280
+ )
281
+ return tasks
@@ -0,0 +1,276 @@
1
+ """Harbor worlds — per-task docker sandboxes for harbor-kind environments.
2
+
3
+ A harbor env is a tree of task dirs (``task.toml`` + ``instruction.md`` +
4
+ ``tests/``). ``env.task(name)`` boots one task's declared ``docker_image`` as
5
+ a local container, copies the task dir in, and hands back a
6
+ :class:`HarborWorld`: ``exec`` commands inside the sandbox, then ``verify()``
7
+ runs the task's own grader (``tests/test.sh``) and parses the harbor reward
8
+ contract (``/logs/verifier/reward.json`` scalar ``reward`` + named numeric
9
+ sub-rewards; ``reward.txt`` fallback; missing/unparseable → score ``None``,
10
+ NEVER 0 — mirrors ``benchmark_hub.harbor_adapter`` / the runner's harvest).
11
+
12
+ The docker layer is an injectable interface (:class:`DockerRuntime`) so tests
13
+ run offline against a fake; the real implementation shells out to the docker
14
+ CLI with timeouts on every call (never hangs).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import re
21
+ import shutil
22
+ import subprocess
23
+ import time
24
+ import uuid
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+ from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Tuple, Union
28
+
29
+ from ._bundle import HarborTask
30
+ from ._world import VerifierResult
31
+ from .errors import VerifierError, WorldStartError
32
+
33
+ if TYPE_CHECKING: # pragma: no cover
34
+ from .core import Environment
35
+
36
+ Command = Union[str, Sequence[str]]
37
+
38
+ REWARD_JSON_PATH = "/logs/verifier/reward.json"
39
+ REWARD_TXT_PATH = "/logs/verifier/reward.txt"
40
+
41
+
42
+ @dataclass
43
+ class ExecResult:
44
+ """Outcome of one command inside the sandbox."""
45
+
46
+ exit_code: int
47
+ stdout: str
48
+ stderr: str
49
+
50
+ @property
51
+ def ok(self) -> bool:
52
+ return self.exit_code == 0
53
+
54
+
55
+ class DockerRuntime:
56
+ """The container operations a harbor world needs. Subclass (or duck-type)
57
+ to inject a fake for offline tests; :class:`CliDockerRuntime` is the real
58
+ thing."""
59
+
60
+ def available(self) -> Tuple[bool, str]:
61
+ """(usable, reason-if-not)."""
62
+ raise NotImplementedError
63
+
64
+ def run_container(self, image: str, *, name: str) -> str:
65
+ """Start a long-lived container from ``image``; return its id."""
66
+ raise NotImplementedError
67
+
68
+ def copy_in(self, container: str, source: Path, dest: str) -> None:
69
+ """Copy a local file/dir into the container at ``dest``."""
70
+ raise NotImplementedError
71
+
72
+ def exec(self, container: str, cmd: Command, *, timeout: float) -> ExecResult:
73
+ """Run a command in the container (str → via ``sh -c``)."""
74
+ raise NotImplementedError
75
+
76
+ def read_file(self, container: str, path: str) -> Optional[bytes]:
77
+ """The file's bytes, or ``None`` when it does not exist."""
78
+ raise NotImplementedError
79
+
80
+ def remove(self, container: str) -> None:
81
+ """Force-remove the container (best-effort)."""
82
+ raise NotImplementedError
83
+
84
+
85
+ class CliDockerRuntime(DockerRuntime):
86
+ """Shells out to the ``docker`` CLI. Every call has a timeout."""
87
+
88
+ def __init__(self, binary: str = "docker", *, pull_timeout: float = 900.0) -> None:
89
+ self._binary = binary
90
+ self._pull_timeout = pull_timeout
91
+
92
+ def _run(self, args: List[str], *, timeout: float, binary_output: bool = False) -> subprocess.CompletedProcess:
93
+ return subprocess.run(
94
+ [self._binary, *args],
95
+ capture_output=True,
96
+ text=not binary_output,
97
+ timeout=timeout,
98
+ )
99
+
100
+ def available(self) -> Tuple[bool, str]:
101
+ if shutil.which(self._binary) is None:
102
+ return False, f"'{self._binary}' not found on PATH"
103
+ try:
104
+ proc = self._run(["version", "--format", "{{.Server.Version}}"], timeout=15)
105
+ except subprocess.TimeoutExpired:
106
+ return False, "docker daemon did not answer within 15s"
107
+ if proc.returncode != 0:
108
+ return False, (proc.stderr or proc.stdout or "docker daemon unreachable").strip()[:300]
109
+ return True, ""
110
+
111
+ def run_container(self, image: str, *, name: str) -> str:
112
+ try:
113
+ proc = self._run(
114
+ ["run", "-d", "--name", name, "--entrypoint", "sleep", image, "infinity"],
115
+ timeout=self._pull_timeout,
116
+ )
117
+ except subprocess.TimeoutExpired as error:
118
+ raise WorldStartError(
119
+ f"docker run {image} timed out after {self._pull_timeout}s (pull too slow?)"
120
+ ) from error
121
+ if proc.returncode != 0:
122
+ raise WorldStartError(
123
+ f"docker run {image} failed: {(proc.stderr or proc.stdout).strip()[:500]}"
124
+ )
125
+ return proc.stdout.strip()
126
+
127
+ def copy_in(self, container: str, source: Path, dest: str) -> None:
128
+ proc = self._run(["cp", str(source), f"{container}:{dest}"], timeout=300)
129
+ if proc.returncode != 0:
130
+ raise WorldStartError(f"docker cp {source} failed: {proc.stderr.strip()[:500]}")
131
+
132
+ def exec(self, container: str, cmd: Command, *, timeout: float) -> ExecResult:
133
+ args = ["exec", container]
134
+ args += ["sh", "-c", cmd] if isinstance(cmd, str) else list(cmd)
135
+ try:
136
+ proc = self._run(args, timeout=timeout)
137
+ except subprocess.TimeoutExpired:
138
+ return ExecResult(exit_code=124, stdout="", stderr=f"timed out after {timeout}s")
139
+ return ExecResult(exit_code=proc.returncode, stdout=proc.stdout, stderr=proc.stderr)
140
+
141
+ def read_file(self, container: str, path: str) -> Optional[bytes]:
142
+ try:
143
+ proc = self._run(["exec", container, "cat", path], timeout=60, binary_output=True)
144
+ except subprocess.TimeoutExpired:
145
+ return None
146
+ return proc.stdout if proc.returncode == 0 else None
147
+
148
+ def remove(self, container: str) -> None:
149
+ try:
150
+ self._run(["rm", "-f", container], timeout=60)
151
+ except subprocess.TimeoutExpired: # pragma: no cover — best-effort cleanup
152
+ pass
153
+
154
+
155
+ def parse_rewards(
156
+ reward_json: Optional[bytes], reward_txt: Optional[bytes]
157
+ ) -> Tuple[Optional[float], Dict[str, float], str]:
158
+ """(scalar score, named sub-rewards, note) from the harbor reward files.
159
+
160
+ ``reward.json``: scalar = the ``reward`` key (or the first numeric value);
161
+ every OTHER numeric key is a named sub-reward. ``reward.txt``: bare float
162
+ fallback. Missing/unparseable → ``(None, {}, why)`` — never 0.
163
+ """
164
+ if reward_json is not None:
165
+ try:
166
+ data = json.loads(reward_json.decode("utf-8"))
167
+ except (ValueError, UnicodeDecodeError):
168
+ data = None
169
+ if isinstance(data, (int, float)) and not isinstance(data, bool):
170
+ return float(data), {}, "scalar reward.json"
171
+ if isinstance(data, dict):
172
+ numeric = {
173
+ key: float(value)
174
+ for key, value in data.items()
175
+ if isinstance(value, (int, float)) and not isinstance(value, bool)
176
+ }
177
+ if "reward" in numeric:
178
+ scalar = numeric.pop("reward")
179
+ return scalar, numeric, "reward.json"
180
+ if numeric:
181
+ first_key = next(iter(numeric))
182
+ scalar = numeric.pop(first_key)
183
+ return scalar, numeric, f"reward.json (no 'reward' key — used '{first_key}')"
184
+ return None, {}, "reward.json held no numeric metrics"
185
+ note = "reward.json unparseable"
186
+ else:
187
+ note = "reward.json missing"
188
+
189
+ if reward_txt is not None:
190
+ try:
191
+ return float(reward_txt.decode("utf-8").strip()), {}, f"{note}; reward.txt fallback"
192
+ except (ValueError, UnicodeDecodeError):
193
+ return None, {}, f"{note}; reward.txt unparseable"
194
+ return None, {}, f"{note}; no reward.txt"
195
+
196
+
197
+ class HarborWorld:
198
+ """One task's live docker sandbox."""
199
+
200
+ def __init__(self, env: "Environment", task: HarborTask, docker: DockerRuntime) -> None:
201
+ self.env = env
202
+ self.task = task
203
+ self._docker = docker
204
+ self.id = uuid.uuid4().hex[:8]
205
+
206
+ usable, reason = docker.available()
207
+ if not usable:
208
+ raise WorldStartError(
209
+ f"docker unavailable: {reason} — install/start Docker, or inject "
210
+ "a DockerRuntime (gwenv.init_from_path(..., docker=...))."
211
+ )
212
+ if not task.docker_image:
213
+ raise WorldStartError(
214
+ f"harbor task '{task.name}' declares no [environment].docker_image "
215
+ "(Dockerfile-only tasks have nothing pullable) — build and tag the "
216
+ "image, set docker_image in task.toml, or run it via the harbor "
217
+ "engine / platform Batch instead."
218
+ )
219
+ container_name = re.sub(r"[^a-zA-Z0-9_.-]", "-", f"gwenv-{env.slug}-{task.name}")[:52] + f"-{self.id}"
220
+ try:
221
+ self.container = docker.run_container(task.docker_image, name=container_name)
222
+ except WorldStartError:
223
+ raise
224
+ except Exception as error: # pragma: no cover — runtime-impl specific
225
+ raise WorldStartError(f"failed to start {task.docker_image}: {error}") from error
226
+ try:
227
+ docker.copy_in(self.container, task.path, "/task")
228
+ except Exception:
229
+ self.close()
230
+ raise
231
+
232
+ def exec(self, cmd: Command, *, timeout: float = 120.0) -> ExecResult:
233
+ """Run a command inside the task sandbox."""
234
+ started = time.perf_counter()
235
+ result = self._docker.exec(self.container, cmd, timeout=timeout)
236
+ summary = cmd if isinstance(cmd, str) else " ".join(cmd)
237
+ self.env._record_route(
238
+ f"{self.task.name}:exec[{summary[:60]}]",
239
+ "env",
240
+ (time.perf_counter() - started) * 1000.0,
241
+ self.id,
242
+ )
243
+ return result
244
+
245
+ def verify(self, name: Optional[str] = None) -> VerifierResult:
246
+ """Run the task's own grader (``tests/test.sh``) and parse the reward
247
+ files. A missing/failed grader yields score ``None`` (never 0), with
248
+ the failure surfaced in ``detail``."""
249
+ if not self.task.has_tests:
250
+ raise VerifierError(f"harbor task '{self.task.name}' ships no tests/ dir — nothing to verify")
251
+
252
+ verifier_cfg = self.task.config.get("verifier") if isinstance(self.task.config.get("verifier"), dict) else {}
253
+ timeout = float(verifier_cfg.get("timeout_sec") or 600.0)
254
+
255
+ self._docker.copy_in(self.container, self.task.path / "tests", "/tests")
256
+ self._docker.exec(self.container, ["mkdir", "-p", "/logs/verifier"], timeout=30)
257
+ run = self._docker.exec(self.container, ["bash", "/tests/test.sh"], timeout=timeout)
258
+
259
+ score, rewards, note = parse_rewards(
260
+ self._docker.read_file(self.container, REWARD_JSON_PATH),
261
+ self._docker.read_file(self.container, REWARD_TXT_PATH),
262
+ )
263
+ detail = note
264
+ if not run.ok:
265
+ detail = f"test.sh exited {run.exit_code} ({(run.stderr or run.stdout).strip()[:300]}); {note}"
266
+ passed = None if score is None else score >= 1.0
267
+ return VerifierResult(
268
+ name=name or self.task.name, passed=passed, score=score, detail=detail, rewards=rewards
269
+ )
270
+
271
+ def state(self, path: str) -> Optional[bytes]:
272
+ """Read a file out of the sandbox (harbor worlds have no SQL surface)."""
273
+ return self._docker.read_file(self.container, path)
274
+
275
+ def close(self) -> None:
276
+ self._docker.remove(self.container)
@@ -0,0 +1,97 @@
1
+ """Shared SDK/hosted template materialization; schema mode never falls back."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import sqlite3
7
+ import sys
8
+ from pathlib import Path
9
+ from tempfile import TemporaryDirectory
10
+
11
+ from ._bundle import load_handlers, parse_manifest
12
+ from .errors import WorldStartError
13
+ from .schema import SQLiteStore, bundle_mode, check_bundle
14
+ from .schema.validation import read_json
15
+
16
+
17
+ def materialize_template(bundle: Path, destination: Path, *, require_sql: bool = False) -> None:
18
+ bundle, destination = Path(bundle), Path(destination)
19
+ if bundle_mode(bundle) is not None:
20
+ contract = check_bundle(bundle)
21
+ if destination.exists():
22
+ from .schema import ContractError
23
+
24
+ raise ContractError("state_exists", "Refusing to overwrite an existing template")
25
+ from .schema.snapshot import current_default, default_snapshot, pin_path
26
+
27
+ if current_default(bundle, contract) is not None:
28
+ # The compiled snapshot is this exact data through this exact contract,
29
+ # already audited: a copy is the materialized template.
30
+ destination.parent.mkdir(parents=True, exist_ok=True)
31
+ shutil.copyfile(default_snapshot(bundle), destination)
32
+ return
33
+ if pin_path(bundle).is_file():
34
+ # A pinned tree's data is the hosted snapshot; its data files cannot
35
+ # stand in for it, so a missing or stale snapshot is refused, not rebuilt.
36
+ from .schema import ContractError
37
+
38
+ raise ContractError(
39
+ "snapshot_pinned",
40
+ "db/snapshot.json pins a hosted snapshot that is not in the tree: fetch the world "
41
+ "with its snapshot (load_world_to_sandbox, gateway worlds pull) instead of "
42
+ "materializing from its data files",
43
+ schema_path="/db/state.sqlite",
44
+ )
45
+ initial = read_json(bundle / "data/initial.json")
46
+ try:
47
+ with SQLiteStore.create(destination, contract) as store:
48
+ store.apply_batch(initial)
49
+ # Immutable refresh journals retain each mapping/source contribution.
50
+ # Coverage never prunes: every change is an explicit store mutation.
51
+ batches = bundle / "data/batches"
52
+ if batches.is_symlink():
53
+ from .schema import ContractError
54
+
55
+ raise ContractError("path", "Refresh batches cannot be symlinks")
56
+ if batches.exists():
57
+ for batch in sorted(batches.iterdir()):
58
+ if batch.is_symlink() or not batch.is_file() or batch.suffix != ".json":
59
+ from .schema import ContractError
60
+
61
+ raise ContractError("path", "Refresh journals must be regular JSON files")
62
+ store.apply_batch(read_json(batch))
63
+ except BaseException:
64
+ destination.unlink(missing_ok=True)
65
+ raise
66
+ return
67
+ schema, seed = bundle / "db/schema.sql", bundle / "db/seed.sql"
68
+ if require_sql and not schema.is_file():
69
+ raise WorldStartError("mock-tools bundle is missing db/schema.sql")
70
+ conn = sqlite3.connect(destination)
71
+ try:
72
+ if schema.is_file():
73
+ conn.executescript(schema.read_text(encoding="utf-8"))
74
+ if seed.is_file():
75
+ conn.executescript(seed.read_text(encoding="utf-8"))
76
+ conn.commit()
77
+ except sqlite3.Error as error:
78
+ raise WorldStartError("Failed to build seed database; check db/schema.sql and db/seed.sql") from error
79
+ finally:
80
+ conn.close()
81
+
82
+
83
+ def validate_schema_materialization(bundle: Path) -> None:
84
+ """Validate compiled artifacts and initial state without retaining a database."""
85
+ bundle = Path(bundle)
86
+ write_bytecode = sys.dont_write_bytecode
87
+ sys.dont_write_bytecode = True
88
+ try:
89
+ load_handlers(bundle, parse_manifest(bundle))
90
+ finally:
91
+ sys.dont_write_bytecode = write_bytecode
92
+ with TemporaryDirectory(prefix="gateway-schema-validation-") as directory:
93
+ materialize_template(
94
+ bundle,
95
+ Path(directory) / "template.sqlite",
96
+ require_sql=True,
97
+ )
@@ -0,0 +1,33 @@
1
+ """Guarded tar extraction.
2
+
3
+ Shared by the benchmark-container ``pull`` path (untrusted-by-provenance), the
4
+ verifiers runner's bundle extraction and the World Host's version cache. Rejects any
5
+ member that would write outside the destination directory — via ``..`` traversal, an absolute path, or a
6
+ sym/hard link — *before* extracting anything, so a malicious or malformed tarball
7
+ cannot escape ``dest_path``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import tarfile
13
+ from pathlib import Path
14
+
15
+
16
+ def safe_tar_extract(tar: tarfile.TarFile, dest_path: str | Path) -> None:
17
+ """Extract ``tar`` into ``dest_path``, refusing any member that escapes it.
18
+
19
+ Raises ``ValueError`` on the first unsafe member (absolute path, ``..``
20
+ component, or a symlink/hardlink). All members are validated up front, so
21
+ nothing is written when any member is unsafe.
22
+ """
23
+ dest = Path(dest_path).resolve()
24
+ for member in tar.getmembers():
25
+ if member.issym() or member.islnk():
26
+ raise ValueError(f"refusing link member in archive: {member.name}")
27
+ member_path = Path(member.name)
28
+ if member_path.is_absolute() or ".." in member_path.parts:
29
+ raise ValueError(f"unsafe path in archive: {member.name}")
30
+ resolved = (dest / member_path).resolve()
31
+ if not resolved.is_relative_to(dest):
32
+ raise ValueError(f"archive member escapes destination: {member.name}")
33
+ tar.extractall(dest)