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,983 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """GatewayTracker and GatewayLightningStore - Gateway tracking integration.
4
+
5
+ This module provides Gateway tracking integration:
6
+
7
+ 1. **GatewayLightningStore** (recommended): Creates a real GatewayRun for experiment
8
+ tracking with automatic session management, heartbeats, and crash detection.
9
+ ```python
10
+ from gatewaysdk.store import GatewayLightningStore
11
+
12
+ store = GatewayLightningStore(
13
+ api_key="pk-lf-abc123",
14
+ secret_key="sk-lf-xyz789",
15
+ gateway_url="https://app.gateway.dev",
16
+ project_id="proj-123",
17
+ run_name="gepa-experiment-v1",
18
+ config={"lr": 0.01, "epochs": 100},
19
+ )
20
+ # Store creates a real GatewayRun record in the database
21
+ # Rollouts create real TraceSession records
22
+ # All traces are linked via run_id
23
+ ```
24
+
25
+ 2. **GatewayTracker** (DEPRECATED): Legacy event-based tracking with virtual job IDs.
26
+ Use GatewayLightningStore instead, which uses real GatewayRun records.
27
+
28
+ Features:
29
+ - Real GatewayRun records with lifecycle management (heartbeats, crash detection)
30
+ - Session creation for rollouts (TraceSession records linked to runs)
31
+ - OTLP trace export support
32
+ - Automatic reward-to-score conversion
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import atexit
38
+ import base64
39
+ import logging
40
+ import os
41
+ import queue
42
+ import threading
43
+ import time
44
+ import uuid
45
+ import warnings
46
+ from dataclasses import dataclass
47
+ from datetime import datetime, timezone
48
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
49
+
50
+ from .gateway_listener import GatewayRunListener
51
+ from .memory import InMemoryLightningStore # noqa: F401
52
+
53
+ if TYPE_CHECKING:
54
+ from gatewaysdk.run import Run
55
+ from .client_server import LightningStoreClient # noqa: F401
56
+ from .redis_stream import RedisStreamLightningStore # noqa: F401
57
+
58
+ import httpx
59
+
60
+ from gatewaysdk.emitter.reward import get_rewards_from_span, is_reward_span
61
+ from gatewaysdk.types import (
62
+ Attempt,
63
+ ResourcesUpdate,
64
+ Rollout,
65
+ Span,
66
+ )
67
+
68
+ from .listener import StorageListener
69
+
70
+ logger = logging.getLogger(__name__)
71
+
72
+
73
+ @dataclass
74
+ class GatewayEvent:
75
+ """An event to be sent to the Gateway API."""
76
+
77
+ id: str
78
+ type: str
79
+ timestamp: str # ISO 8601 datetime string
80
+ data: Dict[str, Any]
81
+
82
+
83
+ class GatewayTracker:
84
+ """DEPRECATED: A StorageListener that streams storage events to Gateway.
85
+
86
+ .. deprecated::
87
+ GatewayTracker is deprecated. Use GatewayLightningStore instead, which
88
+ creates real GatewayRun records with proper lifecycle management, heartbeats,
89
+ and crash detection.
90
+
91
+ This tracker implements the StorageListener protocol and can be attached
92
+ to any LightningStore to enable Gateway tracking. However, it uses virtual
93
+ job IDs that are not persisted to the database.
94
+
95
+ For new code, use GatewayLightningStore which:
96
+ - Creates real GatewayRun records in PostgreSQL
97
+ - Has automatic heartbeats and crash detection
98
+ - Creates real TraceSession records for rollouts
99
+ - Links all traces via run_id
100
+
101
+ Args:
102
+ api_key: Gateway public API key for authentication.
103
+ secret_key: Gateway secret key for authentication.
104
+ gateway_url: Gateway server URL (e.g., "http://localhost:3000").
105
+ project_id: Project ID to associate events with.
106
+ flush_interval: Seconds between automatic flushes (default: 1.0).
107
+ max_buffer_size: Maximum events before forcing a flush (default: 1000).
108
+ request_timeout: HTTP request timeout in seconds (default: 10.0).
109
+ max_retries: Maximum retry attempts for failed requests (default: 3).
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ *,
115
+ api_key: str,
116
+ secret_key: str,
117
+ gateway_url: str,
118
+ project_id: str,
119
+ flush_interval: float = 1.0,
120
+ max_buffer_size: int = 1000,
121
+ request_timeout: float = 10.0,
122
+ max_retries: int = 3,
123
+ ) -> None:
124
+ warnings.warn(
125
+ "GatewayTracker is deprecated. Use GatewayLightningStore instead, which "
126
+ "creates real GatewayRun records with proper lifecycle management.",
127
+ DeprecationWarning,
128
+ stacklevel=2,
129
+ )
130
+ # Store configuration
131
+ self._api_key = api_key
132
+ self._secret_key = secret_key
133
+ self._gateway_url = gateway_url.rstrip("/")
134
+ self._project_id = project_id
135
+ self._flush_interval = flush_interval
136
+ self._max_buffer_size = max_buffer_size
137
+ self._request_timeout = request_timeout
138
+ self._max_retries = max_retries
139
+
140
+ # Generate a unique job ID for this tracker instance
141
+ self._job_id = f"job-{uuid.uuid4().hex[:12]}"
142
+
143
+ # Track if job has been completed to prevent double-complete
144
+ self._completed = False
145
+
146
+ # Event buffer (thread-safe queue)
147
+ self._event_buffer: queue.Queue[GatewayEvent] = queue.Queue()
148
+
149
+ # Background sender thread control
150
+ self._stop_event = threading.Event()
151
+ self._sender_thread: Optional[threading.Thread] = None
152
+
153
+ # Start the background sender thread
154
+ self._start_sender_thread()
155
+
156
+ # Emit job.created event immediately
157
+ self._emit_job_created()
158
+
159
+ # Register cleanup on exit
160
+ atexit.register(self._cleanup)
161
+
162
+ logger.info(
163
+ f"GatewayTracker initialized - streaming to {self._gateway_url} "
164
+ f"(project={self._project_id}, job={self._job_id})"
165
+ )
166
+
167
+ # ─────────────────────────────────────────────────────────────
168
+ # StorageListener Protocol Implementation
169
+ # ─────────────────────────────────────────────────────────────
170
+
171
+ @property
172
+ def capabilities(self) -> Dict[str, bool]:
173
+ """Return the capabilities of the listener."""
174
+ return {
175
+ "otlp_traces": True, # Enable OTLP trace export to Gateway
176
+ }
177
+
178
+ @property
179
+ def job_id(self) -> str:
180
+ """Return the job ID for this tracker instance."""
181
+ return self._job_id
182
+
183
+ def otlp_traces_endpoint(self) -> Optional[str]:
184
+ """Return the OTLP/HTTP traces endpoint."""
185
+ endpoint = f"{self._gateway_url}/api/public/otel/v1/traces"
186
+ logger.debug(f"OTLP traces endpoint: {endpoint}")
187
+ return endpoint
188
+
189
+ def get_otlp_headers(self) -> Dict[str, str]:
190
+ """Return the authentication headers for OTLP export.
191
+
192
+ Gateway's OTLP endpoint uses Basic Auth with format: public_key:secret_key
193
+ """
194
+ credentials = f"{self._api_key}:{self._secret_key}"
195
+ encoded = base64.b64encode(credentials.encode()).decode()
196
+ return {
197
+ "Authorization": f"Basic {encoded}",
198
+ }
199
+
200
+ async def on_job_created(self, job_id: str, project_id: Optional[str] = None) -> None:
201
+ """Called when a job is created. (Usually handled internally)"""
202
+ pass # We emit job.created in __init__
203
+
204
+ async def on_rollout_created(self, rollout: Rollout) -> None:
205
+ """Called when a rollout is created."""
206
+ self._emit(
207
+ "rollout.created",
208
+ {
209
+ "id": rollout.rollout_id,
210
+ "input": rollout.input,
211
+ "status": rollout.status,
212
+ "resource_id": rollout.resources_id,
213
+ "mode": rollout.mode,
214
+ "start_time": rollout.start_time,
215
+ "config": {
216
+ "max_attempts": rollout.config.max_attempts,
217
+ "retry_condition": rollout.config.retry_condition,
218
+ "timeout_seconds": rollout.config.timeout_seconds,
219
+ "unresponsive_seconds": rollout.config.unresponsive_seconds,
220
+ },
221
+ "metadata": rollout.metadata,
222
+ },
223
+ )
224
+
225
+ async def on_rollout_updated(self, rollout: Rollout) -> None:
226
+ """Called when a rollout is updated."""
227
+ data: Dict[str, Any] = {
228
+ "id": rollout.rollout_id,
229
+ "status": rollout.status,
230
+ }
231
+ if rollout.end_time is not None:
232
+ data["end_time"] = rollout.end_time
233
+ self._emit("rollout.status_changed", data)
234
+
235
+ async def on_attempt_created(self, attempt: Attempt) -> None:
236
+ """Called when an attempt is created."""
237
+ self._emit(
238
+ "attempt.created",
239
+ {
240
+ "id": attempt.attempt_id,
241
+ "rollout_id": attempt.rollout_id,
242
+ "sequence_id": attempt.sequence_id,
243
+ "status": attempt.status,
244
+ "start_time": attempt.start_time,
245
+ "worker_id": attempt.worker_id,
246
+ },
247
+ )
248
+
249
+ async def on_attempt_updated(self, attempt: Attempt, rollout_id: str) -> None:
250
+ """Called when an attempt is updated."""
251
+ data: Dict[str, Any] = {
252
+ "id": attempt.attempt_id,
253
+ "rollout_id": rollout_id,
254
+ "status": attempt.status,
255
+ }
256
+ if attempt.end_time is not None:
257
+ data["end_time"] = attempt.end_time
258
+ self._emit("attempt.status_changed", data)
259
+
260
+ async def on_span_created(self, span: Span) -> None:
261
+ """Called when a span is added.
262
+
263
+ If the span is a reward span, also sends the reward as an Gateway score.
264
+ """
265
+ # Emit span event to the event buffer
266
+ self._emit(
267
+ "span.emitted",
268
+ {
269
+ "trace_id": span.trace_id,
270
+ "span_id": span.span_id,
271
+ "parent_id": span.parent_id,
272
+ "attempt_id": span.attempt_id,
273
+ "rollout_id": span.rollout_id,
274
+ "sequence_id": span.sequence_id,
275
+ "name": span.name,
276
+ "status": {
277
+ "status_code": span.status.status_code,
278
+ "description": span.status.description,
279
+ },
280
+ "attributes": span.attributes,
281
+ "start_time": span.start_time,
282
+ "end_time": span.end_time,
283
+ },
284
+ )
285
+
286
+ # Check if this is a reward span and send as Gateway score
287
+ if is_reward_span(span):
288
+ rewards = get_rewards_from_span(span)
289
+ for reward in rewards:
290
+ self._send_score(
291
+ name=reward.name,
292
+ value=reward.value,
293
+ trace_id=span.trace_id,
294
+ observation_id=span.span_id,
295
+ rollout_id=span.rollout_id,
296
+ attempt_id=span.attempt_id,
297
+ )
298
+
299
+ async def on_resource_registered(self, resource: ResourcesUpdate) -> None:
300
+ """Called when a resource is registered/updated.
301
+
302
+ Sends FULL resource content for complete experiment tracking.
303
+ """
304
+ # Serialize resources to JSON-compatible dicts
305
+ # Resources are Pydantic models (PromptTemplate, LLM, etc.) which need model_dump()
306
+ serialized_resources: Dict[str, Any] = {}
307
+ for name, res in resource.resources.items():
308
+ if hasattr(res, "model_dump"):
309
+ serialized_resources[name] = res.model_dump()
310
+ elif hasattr(res, "dict"):
311
+ # Fallback for older Pydantic v1 models
312
+ serialized_resources[name] = res.dict()
313
+ else:
314
+ # Already a dict or primitive
315
+ serialized_resources[name] = res
316
+
317
+ self._emit(
318
+ "resource.registered",
319
+ {
320
+ "id": resource.resources_id,
321
+ "version": resource.version,
322
+ "create_time": resource.create_time,
323
+ "update_time": resource.update_time,
324
+ "resources": serialized_resources,
325
+ },
326
+ )
327
+
328
+ # ─────────────────────────────────────────────────────────────
329
+ # Event Emission Helpers
330
+ # ─────────────────────────────────────────────────────────────
331
+
332
+ def _emit(self, event_type: str, data: Dict[str, Any]) -> None:
333
+ """Add an event to the buffer for later sending."""
334
+ # Format: 2026-01-17T19:51:40.123Z (Zod datetime expects 'Z' suffix for UTC)
335
+ now = datetime.now(timezone.utc)
336
+ timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
337
+ event = GatewayEvent(
338
+ id=f"evt-{uuid.uuid4().hex[:8]}",
339
+ type=event_type,
340
+ timestamp=timestamp,
341
+ data=data,
342
+ )
343
+ try:
344
+ self._event_buffer.put_nowait(event)
345
+ except queue.Full:
346
+ logger.warning(f"Event buffer full, dropping event: {event_type}")
347
+ return
348
+
349
+ # Force flush if buffer is at capacity
350
+ if self._event_buffer.qsize() >= self._max_buffer_size:
351
+ self._trigger_flush()
352
+
353
+ def _emit_job_created(self) -> None:
354
+ """Emit the job.created event when the tracker is initialized."""
355
+ self._emit(
356
+ "job.created",
357
+ {
358
+ "project_id": self._project_id,
359
+ "type": "agent",
360
+ },
361
+ )
362
+ # Immediately flush to ensure job is created before other events
363
+ self._flush_events()
364
+
365
+ def _send_score(
366
+ self,
367
+ name: str,
368
+ value: float,
369
+ trace_id: str,
370
+ observation_id: Optional[str] = None,
371
+ rollout_id: Optional[str] = None,
372
+ attempt_id: Optional[str] = None,
373
+ ) -> None:
374
+ """Send a score to Gateway's /api/public/scores endpoint.
375
+
376
+ Args:
377
+ name: Score name (e.g., "primary", "task_completion").
378
+ value: Numeric score value.
379
+ trace_id: OTEL trace ID to link the score to.
380
+ observation_id: Optional span ID to link to specific observation.
381
+ rollout_id: Optional rollout ID for metadata.
382
+ attempt_id: Optional attempt ID for metadata.
383
+ """
384
+ score_payload: Dict[str, Any] = {
385
+ "name": name,
386
+ "value": value,
387
+ "dataType": "NUMERIC",
388
+ "traceId": trace_id,
389
+ }
390
+
391
+ if observation_id:
392
+ score_payload["observationId"] = observation_id
393
+
394
+ # Add rollout/attempt context as metadata
395
+ metadata: Dict[str, Any] = {
396
+ "source": "agent_lightning",
397
+ "job_id": self._job_id,
398
+ }
399
+ if rollout_id:
400
+ metadata["rollout_id"] = rollout_id
401
+ if attempt_id:
402
+ metadata["attempt_id"] = attempt_id
403
+ score_payload["metadata"] = metadata
404
+
405
+ # Create Basic auth header
406
+ auth_string = f"{self._api_key}:{self._secret_key}"
407
+ auth_bytes = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
408
+
409
+ # Send score in a non-blocking way (fire and forget with retries)
410
+ def send_score_async() -> None:
411
+ for attempt in range(self._max_retries):
412
+ try:
413
+ with httpx.Client(timeout=self._request_timeout) as client:
414
+ response = client.post(
415
+ f"{self._gateway_url}/api/public/scores",
416
+ headers={
417
+ "Authorization": f"Basic {auth_bytes}",
418
+ "Content-Type": "application/json",
419
+ },
420
+ json=score_payload,
421
+ )
422
+ response.raise_for_status()
423
+ logger.debug(f"Successfully sent score '{name}={value}' to Gateway")
424
+ return
425
+ except httpx.HTTPStatusError as e:
426
+ if e.response.status_code == 401:
427
+ logger.error("Unauthorized (401) sending score to Gateway. Check API credentials.")
428
+ return
429
+ else:
430
+ try:
431
+ error_body = e.response.json()
432
+ except Exception:
433
+ error_body = e.response.text
434
+ logger.warning(
435
+ f"HTTP error sending score (attempt {attempt + 1}/{self._max_retries}): "
436
+ f"{e.response.status_code} - {error_body}"
437
+ )
438
+ except httpx.HTTPError as e:
439
+ logger.warning(f"Failed to send score (attempt {attempt + 1}/{self._max_retries}): {e}")
440
+
441
+ # Exponential backoff
442
+ if attempt < self._max_retries - 1:
443
+ backoff_time = 2**attempt
444
+ time.sleep(backoff_time)
445
+
446
+ logger.error(f"Failed to send score '{name}' after {self._max_retries} retries")
447
+
448
+ # Run in a thread to avoid blocking
449
+ threading.Thread(target=send_score_async, daemon=True, name="gateway-score-sender").start()
450
+
451
+ # ─────────────────────────────────────────────────────────────
452
+ # Background Sender Thread
453
+ # ─────────────────────────────────────────────────────────────
454
+
455
+ def _start_sender_thread(self) -> None:
456
+ """Start the background sender thread."""
457
+
458
+ def sender_loop() -> None:
459
+ while not self._stop_event.is_set():
460
+ # Wait for the flush interval or until stopped
461
+ self._stop_event.wait(timeout=self._flush_interval)
462
+ if not self._stop_event.is_set():
463
+ self._flush_events()
464
+
465
+ self._sender_thread = threading.Thread(
466
+ target=sender_loop,
467
+ daemon=True,
468
+ name="gateway-sender",
469
+ )
470
+ self._sender_thread.start()
471
+
472
+ def _trigger_flush(self) -> None:
473
+ """Trigger an immediate flush by interrupting the wait."""
474
+ # The flush will happen on the next iteration since we're using a timeout
475
+ pass
476
+
477
+ def _flush_events(self) -> None:
478
+ """Flush all buffered events to the Gateway API."""
479
+ events: List[GatewayEvent] = []
480
+
481
+ # Drain the queue
482
+ while True:
483
+ try:
484
+ event = self._event_buffer.get_nowait()
485
+ events.append(event)
486
+ except queue.Empty:
487
+ break
488
+
489
+ if not events:
490
+ return
491
+
492
+ self._send_events(events)
493
+
494
+ def _send_events(self, events: List[GatewayEvent]) -> None:
495
+ """Send events to the Gateway API with retry logic."""
496
+ if not events:
497
+ return
498
+
499
+ payload = {
500
+ "job_id": self._job_id,
501
+ # Note: project_id is not sent - it's derived from the API key auth
502
+ "events": [
503
+ {
504
+ "id": e.id,
505
+ "type": e.type,
506
+ "timestamp": e.timestamp,
507
+ "data": e.data,
508
+ }
509
+ for e in events
510
+ ],
511
+ }
512
+
513
+ # Create Basic auth header from api_key:secret_key
514
+ auth_string = f"{self._api_key}:{self._secret_key}"
515
+ auth_bytes = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
516
+
517
+ for attempt in range(self._max_retries):
518
+ try:
519
+ with httpx.Client(timeout=self._request_timeout) as client:
520
+ response = client.post(
521
+ f"{self._gateway_url}/api/public/v1/agent/ingest",
522
+ headers={
523
+ "Authorization": f"Basic {auth_bytes}",
524
+ "Content-Type": "application/json",
525
+ },
526
+ json=payload,
527
+ )
528
+ response.raise_for_status()
529
+ logger.debug(f"Successfully sent {len(events)} events to Gateway")
530
+ return
531
+ except httpx.HTTPStatusError as e:
532
+ if e.response.status_code == 401:
533
+ # Unauthorized - bad API key, won't fix itself
534
+ logger.error("Unauthorized (401) sending events to Gateway. Check API credentials.")
535
+ return
536
+ elif e.response.status_code == 429:
537
+ # Rate limited - back off
538
+ logger.warning("Rate limited (429) sending events, retrying with backoff...")
539
+ else:
540
+ # Log the full response for debugging
541
+ try:
542
+ error_body = e.response.json()
543
+ except Exception:
544
+ error_body = e.response.text
545
+ logger.warning(
546
+ f"HTTP error sending events (attempt {attempt + 1}/{self._max_retries}): "
547
+ f"{e.response.status_code} - {error_body}"
548
+ )
549
+ except httpx.HTTPError as e:
550
+ logger.warning(f"Failed to send events (attempt {attempt + 1}/{self._max_retries}): {e}")
551
+
552
+ # Exponential backoff
553
+ if attempt < self._max_retries - 1:
554
+ backoff_time = 2**attempt
555
+ time.sleep(backoff_time)
556
+
557
+ logger.error(f"Failed to send {len(events)} events after {self._max_retries} retries - events dropped")
558
+
559
+ # ─────────────────────────────────────────────────────────────
560
+ # Lifecycle Methods
561
+ # ─────────────────────────────────────────────────────────────
562
+
563
+ def complete(self, summary: Optional[Dict[str, Any]] = None) -> None:
564
+ """Mark the job as complete and flush all remaining events."""
565
+ if self._completed:
566
+ return
567
+ self._completed = True
568
+ self._emit("job.completed", {"summary": summary or {}})
569
+ self._stop_event.set()
570
+ self._flush_events()
571
+ logger.info(f"GatewayTracker job {self._job_id} completed")
572
+
573
+ def fail(self, error: str) -> None:
574
+ """Mark the job as failed and flush all remaining events."""
575
+ self._emit("job.failed", {"error": error})
576
+ self._stop_event.set()
577
+ self._flush_events()
578
+ logger.error(f"GatewayTracker job {self._job_id} failed: {error}")
579
+
580
+ def _cleanup(self) -> None:
581
+ """Cleanup resources on exit."""
582
+ if not self._stop_event.is_set():
583
+ # Flush any remaining events before exiting
584
+ self._flush_events()
585
+ self._stop_event.set()
586
+
587
+ def __enter__(self) -> "GatewayTracker":
588
+ return self
589
+
590
+ def __exit__(
591
+ self,
592
+ exc_type: Optional[type],
593
+ exc_val: Optional[BaseException],
594
+ exc_tb: Optional[Any],
595
+ ) -> None:
596
+ if exc_type:
597
+ self.fail(str(exc_val) if exc_val else "Unknown error")
598
+ elif not self._completed:
599
+ self.complete()
600
+
601
+
602
+ # ─────────────────────────────────────────────────────────────────────────────
603
+ # GatewayLightningStore - Uses GatewayRunListener for consolidated tracking
604
+ # ─────────────────────────────────────────────────────────────────────────────
605
+
606
+
607
+ def GatewayLightningStore(
608
+ *,
609
+ api_key: str,
610
+ secret_key: str,
611
+ gateway_url: str,
612
+ project_id: str,
613
+ # Run configuration
614
+ run_name: Optional[str] = None,
615
+ run_id: Optional[str] = None,
616
+ config: Optional[Dict[str, Any]] = None,
617
+ labels: Optional[Dict[str, Any]] = None,
618
+ tags: Optional[List[str]] = None,
619
+ source: str = "gateway_store",
620
+ heartbeat_interval: int = 30,
621
+ # Store configuration
622
+ thread_safe: bool = False,
623
+ init_tracing: bool = False,
624
+ # Legacy parameters (passed to listener)
625
+ flush_interval: float = 1.0,
626
+ max_buffer_size: int = 1000,
627
+ request_timeout: float = 10.0,
628
+ max_retries: int = 3,
629
+ **kwargs: Any,
630
+ ) -> "InMemoryLightningStore":
631
+ """Create an InMemoryLightningStore with Gateway tracking.
632
+
633
+ This function creates a real GatewayRun record in the database for experiment
634
+ tracking. All Gateway communication is consolidated in GatewayRunListener:
635
+ - Run creation and lifecycle management
636
+ - Heartbeats for crash detection
637
+ - Metrics logging
638
+ - Session creation for rollouts
639
+ - Score submission
640
+
641
+ Args:
642
+ api_key: Gateway public API key for authentication.
643
+ secret_key: Gateway secret key for authentication.
644
+ gateway_url: Gateway server URL (e.g., "http://localhost:3000").
645
+ project_id: Project ID to associate the run with.
646
+ run_name: Optional name for the run (auto-generated if not provided).
647
+ config: Hyperparameters/config dict (logged for reproducibility).
648
+ labels: Arbitrary key-value labels for filtering runs.
649
+ tags: Simple string tags for categorization.
650
+ source: Source identifier (default: "gateway_store").
651
+ heartbeat_interval: Seconds between heartbeats (default: 30).
652
+ thread_safe: Whether the underlying store is thread-safe (default: False).
653
+ init_tracing: If True, automatically initialize gatewaysdk.tracing with:
654
+ - Gateway OTLP exporter configured from the provided credentials
655
+ - TracingContextSpanProcessor for automatic context injection
656
+ - OpenInference instrumentors (litellm, openai, anthropic, etc.)
657
+
658
+ Returns:
659
+ An InMemoryLightningStore with GatewayRunListener attached.
660
+
661
+ Example:
662
+ ```python
663
+ # Basic usage
664
+ store = GatewayLightningStore(
665
+ api_key="pk-lf-abc123",
666
+ secret_key="sk-lf-xyz789",
667
+ gateway_url="https://app.gateway.dev",
668
+ project_id="proj-123",
669
+ run_name="gepa-experiment-v1",
670
+ config={"lr": 0.01, "epochs": 100},
671
+ )
672
+
673
+ # Log metrics directly on the store
674
+ store.log({"loss": 0.5, "accuracy": 0.92}, step=100)
675
+
676
+ # With automatic tracing SDK setup (recommended)
677
+ store = GatewayLightningStore(
678
+ api_key="pk-lf-abc123",
679
+ secret_key="sk-lf-xyz789",
680
+ gateway_url="https://app.gateway.dev",
681
+ project_id="proj-123",
682
+ init_tracing=True,
683
+ )
684
+
685
+ trainer = Trainer(algorithm=GEPA(...), store=store)
686
+ # Run is tracked with heartbeats, metrics, sessions, etc.
687
+
688
+ # When done, call complete() or fail()
689
+ store.complete()
690
+ ```
691
+ """
692
+ import os
693
+
694
+ # Set env vars for tracing SDK and other components
695
+ os.environ["GATEWAY_HOST"] = gateway_url
696
+ os.environ["GATEWAY_PUBLIC_KEY"] = api_key
697
+ os.environ["GATEWAY_SECRET_KEY"] = secret_key
698
+ os.environ["GATEWAY_PROJECT_ID"] = project_id
699
+
700
+ logger.debug(
701
+ f"GatewayLightningStore: Set env vars - "
702
+ f"GATEWAY_HOST={gateway_url}, "
703
+ f"GATEWAY_PUBLIC_KEY={api_key[:20]}..., "
704
+ f"GATEWAY_PROJECT_ID={project_id}"
705
+ )
706
+
707
+ # Initialize tracing SDK if requested
708
+ if init_tracing:
709
+ import gatewaysdk.tracing as tracing
710
+
711
+ tracing.init(instrument_default=True)
712
+
713
+ # Create consolidated listener - handles ALL Gateway communication
714
+ listener = GatewayRunListener(
715
+ api_key=api_key,
716
+ secret_key=secret_key,
717
+ gateway_url=gateway_url,
718
+ project_id=project_id,
719
+ run_name=run_name,
720
+ run_id=run_id, # Reuse existing run if provided (e.g., platform runs)
721
+ config=config,
722
+ labels=labels,
723
+ tags=tags,
724
+ source=source,
725
+ heartbeat_interval=heartbeat_interval,
726
+ request_timeout=request_timeout,
727
+ max_retries=max_retries,
728
+ )
729
+
730
+ # Start the run (creates DB record, starts heartbeat, registers handlers)
731
+ listener.start()
732
+
733
+ # Create store with listener attached
734
+ store = InMemoryLightningStore(thread_safe=thread_safe, listeners=[listener], **kwargs)
735
+
736
+ # Attach listener to store for access
737
+ store._gateway_listener = listener # type: ignore[attr-defined]
738
+
739
+ # Expose clean API methods on store instance
740
+ def log(
741
+ metrics,
742
+ value=None,
743
+ *,
744
+ step=None,
745
+ labels=None,
746
+ unit="",
747
+ metric_type="gauge",
748
+ ):
749
+ """Log metrics for this run."""
750
+ listener.log(metrics, value, step=step, labels=labels, unit=unit, metric_type=metric_type)
751
+
752
+ def complete(summary: Optional[Dict[str, Any]] = None) -> None:
753
+ """Mark the run as completed."""
754
+ listener.complete()
755
+
756
+ def fail(error: Optional[str] = None) -> None:
757
+ """Mark the run as failed."""
758
+ listener.fail(error)
759
+
760
+ def log_state(state: Dict[str, Any]) -> None:
761
+ """Log rich structured state for live visualization."""
762
+ listener.log_state(state)
763
+
764
+ def flush() -> None:
765
+ """Manually flush buffered metrics."""
766
+ listener.flush()
767
+
768
+ # Monkey-patch the API methods onto the store instance
769
+ store.log = log # type: ignore[attr-defined]
770
+ store.complete = complete # type: ignore[attr-defined]
771
+ store.fail = fail # type: ignore[attr-defined]
772
+ store.log_state = log_state # type: ignore[attr-defined]
773
+ store.flush = flush # type: ignore[attr-defined]
774
+
775
+ # Add run_id property for convenience
776
+ store.run_id = listener.run_id # type: ignore[attr-defined]
777
+ store.job_id = listener.run_id # type: ignore[attr-defined] # Backward compat
778
+
779
+ # Deprecated: _run attribute for backward compatibility
780
+ # Creates a simple object that forwards to the listener
781
+ class _RunCompat:
782
+ """Backward compatibility wrapper for store._run access."""
783
+
784
+ def __init__(self, listener):
785
+ self._listener = listener
786
+
787
+ @property
788
+ def id(self):
789
+ return self._listener.run_id
790
+
791
+ def log(self, *args, **kwargs):
792
+ return self._listener.log(*args, **kwargs)
793
+
794
+ def log_state(self, state):
795
+ return self._listener.log_state(state)
796
+
797
+ def flush(self):
798
+ return self._listener.flush()
799
+
800
+ def finish(self, state="completed"):
801
+ return self._listener.finish(state)
802
+
803
+ store._run = _RunCompat(listener) # type: ignore[attr-defined]
804
+
805
+ logger.info(
806
+ f"GatewayLightningStore created with GatewayRun {listener.run_id} "
807
+ f"(project={project_id}, name={listener.run_name})"
808
+ )
809
+
810
+ return store
811
+
812
+
813
+ # ─────────────────────────────────────────────────────────────────────────────
814
+ # RedisStreamGatewayLightningStore - For platform runs with distributed coord
815
+ # ─────────────────────────────────────────────────────────────────────────────
816
+
817
+
818
+ def RedisStreamGatewayLightningStore(
819
+ *,
820
+ redis_url: str,
821
+ run_id: str,
822
+ api_key: str,
823
+ secret_key: str,
824
+ gateway_url: str,
825
+ project_id: str,
826
+ run_name: Optional[str] = None,
827
+ config: Optional[Dict[str, Any]] = None,
828
+ labels: Optional[Dict[str, Any]] = None,
829
+ tags: Optional[List[str]] = None,
830
+ source: str = "platform",
831
+ heartbeat_interval: int = 30,
832
+ consumer_group: str = "gateway-runners",
833
+ thread_safe: bool = False,
834
+ init_tracing: bool = False,
835
+ ) -> "RedisStreamLightningStore":
836
+ """Create a RedisStreamLightningStore with GatewayRunListener attached.
837
+
838
+ Used by the Python BullMQ worker for platform-managed runs. Combines
839
+ Redis Streams for distributed rollout coordination with GatewayRunListener
840
+ for run lifecycle management (heartbeats, metrics, sessions).
841
+
842
+ Args:
843
+ redis_url: Redis connection string.
844
+ run_id: Existing GatewayRun ID (created by the platform start endpoint).
845
+ api_key: Gateway public API key.
846
+ secret_key: Gateway secret key.
847
+ gateway_url: Gateway server URL.
848
+ project_id: Project ID.
849
+ run_name: Optional run name.
850
+ config: Run config dict.
851
+ labels: Run labels.
852
+ tags: Run tags.
853
+ source: Run source (default: "platform").
854
+ heartbeat_interval: Heartbeat interval in seconds.
855
+ consumer_group: Redis consumer group name.
856
+ thread_safe: Whether the store is thread-safe.
857
+ init_tracing: If True, auto-initialize gatewaysdk.tracing.
858
+
859
+ Returns:
860
+ RedisStreamLightningStore with GatewayRunListener attached.
861
+ """
862
+ import os
863
+
864
+ from .redis_stream import RedisStreamLightningStore as RedisStore
865
+
866
+ # Set non-secret env vars for SDK components that read from env
867
+ os.environ["GATEWAY_HOST"] = gateway_url
868
+ os.environ["GATEWAY_PUBLIC_KEY"] = api_key
869
+ os.environ["GATEWAY_PROJECT_ID"] = project_id
870
+
871
+ if init_tracing:
872
+ # Temporarily set secret key for tracing init, then remove (F6)
873
+ os.environ["GATEWAY_SECRET_KEY"] = secret_key
874
+ import gatewaysdk.tracing as tracing
875
+
876
+ tracing.init(instrument_default=True)
877
+ os.environ.pop("GATEWAY_SECRET_KEY", None)
878
+
879
+ listener = GatewayRunListener(
880
+ api_key=api_key,
881
+ secret_key=secret_key,
882
+ gateway_url=gateway_url,
883
+ project_id=project_id,
884
+ run_name=run_name,
885
+ config=config,
886
+ labels=labels,
887
+ tags=tags,
888
+ source=source,
889
+ heartbeat_interval=heartbeat_interval,
890
+ )
891
+ listener.start()
892
+
893
+ store = RedisStore(
894
+ redis_url=redis_url,
895
+ run_id=run_id,
896
+ consumer_group=consumer_group,
897
+ thread_safe=thread_safe,
898
+ listeners=[listener],
899
+ )
900
+
901
+ store.log = listener.log # type: ignore[attr-defined]
902
+ store.complete = listener.complete # type: ignore[attr-defined]
903
+ store.fail = listener.fail # type: ignore[attr-defined]
904
+ store.log_state = listener.log_state # type: ignore[attr-defined]
905
+ store.flush = listener.flush # type: ignore[attr-defined]
906
+
907
+ from gatewaysdk.utils.redact import redact_url
908
+
909
+ logger.info(
910
+ f"RedisStreamGatewayLightningStore created for run {run_id} "
911
+ f"(redis={redact_url(redis_url)}, project={project_id})"
912
+ )
913
+
914
+ return store
915
+
916
+
917
+ def create_platform_store_client_from_env() -> "LightningStoreClient":
918
+ """Create a store client for agent containers running on the Gateway platform.
919
+
920
+ Only requires ``GATEWAY_STORE_URL`` (set by the orchestrator). All Gateway
921
+ integration (OTLP export, sessions, scores, heartbeats) is handled
922
+ server-side by the worker's GatewayRunListener.
923
+
924
+ Returns:
925
+ A :class:`LightningStoreClient` connected to the worker's store server.
926
+
927
+ Raises:
928
+ EnvironmentError: If GATEWAY_STORE_URL is not set.
929
+ """
930
+ import os
931
+
932
+ from .client_server import LightningStoreClient
933
+
934
+ store_url = os.environ.get("GATEWAY_STORE_URL")
935
+ if not store_url:
936
+ raise EnvironmentError(
937
+ "GATEWAY_STORE_URL is not set. This variable is injected automatically "
938
+ "by the Gateway platform orchestrator when starting agent containers."
939
+ )
940
+
941
+ logger.info(f"Creating store client for {store_url}")
942
+ return LightningStoreClient(server_address=store_url)
943
+
944
+
945
+ def create_platform_store_from_env() -> "RedisStreamLightningStore":
946
+ """DEPRECATED: Use create_platform_store_client_from_env() instead.
947
+
948
+ This function creates a RedisStreamLightningStore which has been
949
+ replaced by LightningStoreClient for platform runs.
950
+ """
951
+ import os
952
+ import warnings
953
+
954
+ warnings.warn(
955
+ "create_platform_store_from_env() is deprecated. "
956
+ "Use create_platform_store_client_from_env() instead, which uses "
957
+ "LightningStoreClient (HTTP) rather than Redis Streams.",
958
+ DeprecationWarning,
959
+ stacklevel=2,
960
+ )
961
+
962
+ required = ["GATEWAY_RUN_ID", "GATEWAY_HOST", "GATEWAY_PUBLIC_KEY", "GATEWAY_SECRET_KEY", "GATEWAY_PROJECT_ID"]
963
+ missing = [k for k in required if not os.environ.get(k)]
964
+ if missing:
965
+ raise EnvironmentError(
966
+ f"Missing required environment variables: {', '.join(missing)}. "
967
+ "These are set automatically by the Gateway platform orchestrator."
968
+ )
969
+
970
+ run_id = os.environ["GATEWAY_RUN_ID"]
971
+ host = os.environ.get("REDIS_HOST", "localhost")
972
+ port = os.environ.get("REDIS_PORT", "6379")
973
+ auth = os.environ.get("REDIS_AUTH", "")
974
+ redis_url = f"redis://:{auth}@{host}:{port}" if auth else f"redis://{host}:{port}"
975
+
976
+ return RedisStreamGatewayLightningStore(
977
+ redis_url=redis_url,
978
+ run_id=run_id,
979
+ api_key=os.environ["GATEWAY_PUBLIC_KEY"],
980
+ secret_key=os.environ["GATEWAY_SECRET_KEY"],
981
+ gateway_url=os.environ["GATEWAY_HOST"],
982
+ project_id=os.environ["GATEWAY_PROJECT_ID"],
983
+ )