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,673 @@
1
+ """Harbor → Gateway evals adapter.
2
+
3
+ A harbor-kind container version is a directory of Harbor task dirs
4
+ (``task.toml`` + ``instruction.md`` + ``tests/``). The first-party ``harbor``
5
+ engine executes them (agent in a sandbox, then the task's own verifier script,
6
+ which writes ``/logs/verifier/reward.json`` / ``reward.txt``) and leaves a jobs
7
+ directory of trials. This module maps that jobs directory into the SAME
8
+ version-pinned evaluation shape the verifiers adapter produces — so the
9
+ Environment page, dispatcher back-link, and sub-reward drill-down are identical
10
+ across env kinds.
11
+
12
+ Trial dir layout (harbor >= 0.15, same shape Pier emits):
13
+ trial_dir/
14
+ result.json # TrialResult: task_name, agent_info, verifier_result.rewards, timing
15
+ verifier/reward.json # named numeric metrics (fallback / sub-rewards)
16
+ agent/** # agent logs
17
+
18
+ Reward precedence mirrors deploy/benchmark-runner/harvest.py:_reward — the
19
+ ``result.json`` ``verifier_result.rewards`` dict first, then
20
+ ``verifier/reward.json``. The scalar reward is the dict's ``reward`` key (or
21
+ its first numeric value); ALL other numeric keys become named sub-rewards.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import glob
27
+ import json
28
+ import os
29
+ import re
30
+ import shutil
31
+ import subprocess
32
+ from typing import Any, Dict, List, Optional
33
+
34
+ from .evals import EvalHarvestError, GatewayEvalsClient
35
+
36
+ # --- verifiers v1 detection (kept in sync with the runner, engines/hub_bundle.py).
37
+ # The `verifiers.v1` namespace first shipped in verifiers 0.2.0 (PyPI), so a v1
38
+ # Taskset package declares `verifiers>=0.2` (or `~=0.2` / `==0.2.*` / a future
39
+ # `>=1`) in pyproject and/or imports the `verifiers.v1` namespace / subclasses
40
+ # `Taskset` (with the v1 convention of an `__all__` export) in its package
41
+ # __init__. A bare unversioned `verifiers` dep, a pre-0.2 spec (`==0.1.5`,
42
+ # `>=0.1,<0.2`), or the classic v0 `verifiers<1.0.0` pin never matches.
43
+ # Version alternation: minor >= 2 on the 0.x line (`0.2`, `0.10`, ...) or any
44
+ # major >= 1 — never `0.0`/`0.1[.z]`.
45
+ _V1_DEP_RE = re.compile(
46
+ r"""["']verifiers\s*(?:\[[^\]]*\])?\s*(?:>=|~=|==|>)\s*"""
47
+ r"""(?:0\.(?:[2-9]\d*|[1-9]\d+)|[1-9]\d*)"""
48
+ )
49
+ _V1_IMPORT_RE = re.compile(
50
+ r"^\s*(?:from\s+verifiers\.v1\s+import\b"
51
+ r"|from\s+verifiers\s+import\s+v1\b"
52
+ r"|import\s+verifiers\.v1\b)",
53
+ re.MULTILINE,
54
+ )
55
+ _V1_TASKSET_RE = re.compile(
56
+ r"class\s+\w+\s*\(\s*(?:(?:vf|v1|verifiers(?:\.v1)?)\.)?Taskset\s*[,)]"
57
+ )
58
+
59
+
60
+ def _read_text(path: str) -> str:
61
+ try:
62
+ with open(path, encoding="utf-8", errors="replace") as f:
63
+ return f.read()
64
+ except OSError:
65
+ return ""
66
+
67
+
68
+ def _is_verifiers_v1(src: str) -> bool:
69
+ pyproject = os.path.join(src, "pyproject.toml")
70
+ if os.path.isfile(pyproject) and _V1_DEP_RE.search(_read_text(pyproject)):
71
+ return True
72
+ inits = glob.glob(os.path.join(src, "*", "__init__.py")) + glob.glob(
73
+ os.path.join(src, "src", "*", "__init__.py")
74
+ )
75
+ for init in inits:
76
+ text = _read_text(init)
77
+ if not text:
78
+ continue
79
+ if _V1_IMPORT_RE.search(text):
80
+ return True
81
+ if "__all__" in text and _V1_TASKSET_RE.search(text):
82
+ return True
83
+ return False
84
+
85
+
86
+ def detect_env_kind(src: str) -> str:
87
+ """"mock-tools" iff a ``gateway-env.toml`` sits at the source root (the
88
+ environment builder's declarative bundle: manifest + ``db/`` + ``tools/`` +
89
+ ``verifiers/``); else "harbor" iff the source contains Harbor task dirs
90
+ (``tasks/*/task.toml`` or ``*/task.toml``); else "verifiers-v1" for verifiers
91
+ v1 Taskset packages; else "verifiers" (legacy v0).
92
+
93
+ ``mock-tools`` is checked FIRST because a root ``gateway-env.toml`` is
94
+ unambiguous — such a bundle is neither a Python package (no ``load_environment``)
95
+ nor a Harbor task tree, so classifying it as ``verifiers`` would drive the
96
+ runner into ``pip install -e`` and crash.
97
+
98
+ Kept in sync with the runner (engines/hub_bundle.py), the local runtime's
99
+ ``gatewaysdk.environment._bundle.detect_kind``, and the web read router
100
+ (deriveEnvKind — where ``mock-tools`` maps to the cosmetic ``verifiers``
101
+ badge, since the badge is binary)."""
102
+ if os.path.isfile(os.path.join(src, "gateway-api-world.toml")):
103
+ return "api"
104
+ if os.path.isfile(os.path.join(src, "gateway-env.toml")):
105
+ return "mock-tools"
106
+ if glob.glob(os.path.join(src, "tasks", "*", "task.toml")) or glob.glob(
107
+ os.path.join(src, "*", "task.toml")
108
+ ):
109
+ return "harbor"
110
+ if _is_verifiers_v1(src):
111
+ return "verifiers-v1"
112
+ return "verifiers"
113
+
114
+
115
+ def tasks_root(src: str) -> str:
116
+ """The dir whose CHILDREN are task dirs: <src>/tasks if present, else <src>."""
117
+ tasks = os.path.join(src, "tasks")
118
+ return tasks if glob.glob(os.path.join(tasks, "*", "task.toml")) else src
119
+
120
+
121
+ def _read_json(path: str) -> Optional[Any]:
122
+ try:
123
+ with open(path) as f:
124
+ return json.load(f)
125
+ except (OSError, json.JSONDecodeError):
126
+ return None
127
+
128
+
129
+ def find_trials(job_dir: str) -> List[str]:
130
+ """Trial dirs = dirs holding a per-trial ``result.json`` AND a ``verifier/``
131
+ subdir (excludes the job-level result.json at the run root)."""
132
+ out = []
133
+ for p in glob.glob(os.path.join(job_dir, "**", "result.json"), recursive=True):
134
+ d = os.path.dirname(p)
135
+ if os.path.isdir(os.path.join(d, "verifier")):
136
+ out.append(d)
137
+ return sorted(out)
138
+
139
+
140
+ def trial_rewards(trial_dir: str) -> Dict[str, float]:
141
+ """All numeric reward metrics for a trial (``{}`` when the verifier wrote
142
+ nothing — the trial is then UNGRADED and its reward stays null)."""
143
+ results = _read_json(os.path.join(trial_dir, "result.json")) or {}
144
+ rewards = (results.get("verifier_result") or {}).get("rewards") or _read_json(
145
+ os.path.join(trial_dir, "verifier", "reward.json")
146
+ )
147
+ if isinstance(rewards, (int, float)):
148
+ return {"reward": float(rewards)}
149
+ if isinstance(rewards, dict):
150
+ return {
151
+ k: float(v) for k, v in rewards.items() if isinstance(v, (int, float))
152
+ }
153
+ return {}
154
+
155
+
156
+ def scalar_reward(rewards: Dict[str, float]) -> Optional[float]:
157
+ """The trial's headline reward, or None when NOTHING was graded.
158
+
159
+ An ungraded trial must stay null. Coercing it to 0.0 made "the grader
160
+ never ran" indistinguishable from "the agent scored zero" — the exact
161
+ confusion behind a prod zero that was actually a RewardFileNotFoundError.
162
+ Null is the platform's own contract (null scores are their own category,
163
+ never coerced), and it is what lets an agent act on "nothing was graded —
164
+ a failure to fix, not a pass".
165
+ """
166
+ if "reward" in rewards:
167
+ return rewards["reward"]
168
+ return next(iter(rewards.values()), None)
169
+
170
+
171
+ def samples_from_job_dir(job_dir: str) -> List[Dict[str, Any]]:
172
+ """Map a harbor jobs directory → eval-sample dicts (one per trial).
173
+
174
+ ``exampleId`` = the task name; ``rolloutIndex`` = the occurrence of that
175
+ task within the job (n-attempts > 1 produces one trial per attempt).
176
+ Trajectories stay in the jobs dir — samples carry compact info only.
177
+ """
178
+ seen: Dict[str, int] = {}
179
+ samples: List[Dict[str, Any]] = []
180
+ for trial_dir in find_trials(job_dir):
181
+ results = _read_json(os.path.join(trial_dir, "result.json")) or {}
182
+ task_id = (results.get("task_name") or "").split("/")[-1] or os.path.basename(
183
+ trial_dir.rstrip("/")
184
+ )
185
+ idx = seen.get(task_id, 0)
186
+ seen[task_id] = idx + 1
187
+
188
+ rewards = trial_rewards(trial_dir)
189
+ reward = scalar_reward(rewards)
190
+ agent_info = results.get("agent_info") or {}
191
+ agent_result = results.get("agent_result") or {}
192
+ samples.append(
193
+ {
194
+ "exampleId": task_id,
195
+ # `task` is what run tables and reward breakdowns display —
196
+ # without it every harbor sample rendered as "?".
197
+ "task": task_id,
198
+ "rolloutIndex": idx,
199
+ "reward": reward,
200
+ "score": reward,
201
+ # Ungraded is not incorrect — correctness is unknowable when
202
+ # nothing scored the trial.
203
+ "correct": (reward >= 1.0) if reward is not None else None,
204
+ "rewards": {k: v for k, v in rewards.items() if k != "reward"},
205
+ "info": {
206
+ "trialName": os.path.basename(trial_dir.rstrip("/")),
207
+ "agent": agent_info.get("name"),
208
+ "model": (agent_info.get("model_info") or {}).get("name"),
209
+ "costUsd": agent_result.get("cost_usd"),
210
+ "startedAt": results.get("started_at"),
211
+ "finishedAt": results.get("finished_at"),
212
+ },
213
+ }
214
+ )
215
+ return samples
216
+
217
+
218
+ def metrics_from_samples(samples: List[Dict[str, Any]]) -> Dict[str, Any]:
219
+ """Aggregate metrics: average reward + average of each named sub-reward
220
+ (mirrors verifiers_adapter.metrics_from_generate_outputs)."""
221
+ rewards = [s["reward"] for s in samples if isinstance(s.get("reward"), (int, float))]
222
+ by_metric: Dict[str, List[float]] = {}
223
+ for s in samples:
224
+ for name, value in (s.get("rewards") or {}).items():
225
+ if isinstance(value, (int, float)):
226
+ by_metric.setdefault(name, []).append(float(value))
227
+
228
+ def _mean(xs: List[float]) -> Optional[float]:
229
+ return sum(xs) / len(xs) if xs else None
230
+
231
+ return {
232
+ "n": len(samples),
233
+ "avg_reward": _mean(rewards),
234
+ "metrics": {name: _mean(vals) for name, vals in by_metric.items()},
235
+ }
236
+
237
+
238
+ def emit_trial_traces(job_dir: str, evaluation_id: str) -> int:
239
+ """Emit one rich OTLP trace per trial so platform traces are not shells.
240
+
241
+ Reads the verifier's full-state export (``verifier/state.json``, written by
242
+ task verifiers that support it) and maps it to an AGENT root span with one
243
+ TOOL child span per audited tool call, joined to the platform's harvest
244
+ session (``<evaluationId>:<task>:<rolloutIndex>``). Requires
245
+ ``GATEWAY_OTLP_ENDPOINT`` + key envs and the opentelemetry sdk; silently
246
+ returns 0 when either is absent (tracing is additive, never load-bearing).
247
+ """
248
+ endpoint = os.environ.get("GATEWAY_OTLP_ENDPOINT")
249
+ public_key = os.environ.get("GATEWAY_PUBLIC_KEY")
250
+ secret_key = os.environ.get("GATEWAY_SECRET_KEY")
251
+ if not (endpoint and public_key and secret_key):
252
+ return 0
253
+ try:
254
+ from opentelemetry import trace as otel_trace
255
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
256
+ OTLPSpanExporter,
257
+ )
258
+ from opentelemetry.sdk.resources import Resource
259
+ from opentelemetry.sdk.trace import TracerProvider
260
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
261
+ except ImportError:
262
+ return 0
263
+ import base64
264
+
265
+ auth = base64.b64encode(f"{public_key}:{secret_key}".encode()).decode()
266
+ provider = TracerProvider(
267
+ resource=Resource.create({"service.name": "gateway-bench-harvest"})
268
+ )
269
+ provider.add_span_processor(
270
+ BatchSpanProcessor(
271
+ OTLPSpanExporter(
272
+ endpoint=endpoint, headers={"Authorization": f"Basic {auth}"}
273
+ )
274
+ )
275
+ )
276
+ tracer = provider.get_tracer("bench-harvest")
277
+ emitted = 0
278
+ seen: Dict[str, int] = {}
279
+ for trial_dir in find_trials(job_dir):
280
+ results = _read_json(os.path.join(trial_dir, "result.json")) or {}
281
+ task_id = (results.get("task_name") or "").split("/")[-1] or os.path.basename(
282
+ trial_dir.rstrip("/")
283
+ )
284
+ idx = seen.get(task_id, 0)
285
+ seen[task_id] = idx + 1
286
+ # No full-state export is NOT "no session": every rollout emits at
287
+ # least its graded outline (instruction context, reward, sub-reward
288
+ # gates, agent/model/cost). state.json, when a world exports it,
289
+ # upgrades the same trace with one TOOL span per audited call.
290
+ state = _read_json(os.path.join(trial_dir, "verifier", "state.json")) or {}
291
+ rewards = trial_rewards(trial_dir)
292
+ report = state.get("report") or {}
293
+ case = state.get("case_state") or {}
294
+ with tracer.start_as_current_span(f"episode {task_id}") as root:
295
+ root.set_attribute("openinference.span.kind", "AGENT")
296
+ root.set_attribute("session.id", f"{evaluation_id}:{task_id}:{idx}")
297
+ root.set_attribute("langfuse.session.id", f"{evaluation_id}:{task_id}:{idx}")
298
+ agent_info = results.get("agent_info") or {}
299
+ root.set_attribute(
300
+ "input.value",
301
+ json.dumps(
302
+ {
303
+ "task": task_id,
304
+ **({"case": case.get("title")} if case else {}),
305
+ **(
306
+ {"agent": agent_info.get("name")}
307
+ if agent_info.get("name")
308
+ else {}
309
+ ),
310
+ }
311
+ ),
312
+ )
313
+ root.set_attribute(
314
+ "output.value",
315
+ json.dumps(
316
+ {
317
+ "reward": scalar_reward(rewards),
318
+ "report": report,
319
+ "failing_gates": [
320
+ k for k, v in rewards.items() if v not in (1.0, None)
321
+ ] or "none",
322
+ },
323
+ default=str,
324
+ )[:4000],
325
+ )
326
+ audit_by_seq = {
327
+ entry.get("seq"): entry for entry in state.get("audit_entries") or []
328
+ }
329
+ call_log = state.get("call_log") or []
330
+ per_call = call_log or [
331
+ {
332
+ "seq": entry.get("seq"),
333
+ "tool": entry.get("tool"),
334
+ "arguments": None,
335
+ "result_preview": None,
336
+ "error": None,
337
+ }
338
+ for entry in state.get("audit_entries") or []
339
+ ]
340
+ for call in per_call:
341
+ audit = audit_by_seq.get(call.get("seq"), {})
342
+ with tracer.start_as_current_span(
343
+ f"{call.get('seq', 0):02d} {call.get('tool', '?')}"
344
+ ) as span:
345
+ span.set_attribute("openinference.span.kind", "TOOL")
346
+ span.set_attribute(
347
+ "input.value",
348
+ call.get("arguments")
349
+ or json.dumps({"args_hash": audit.get("args_hash")}),
350
+ )
351
+ span.set_attribute(
352
+ "output.value",
353
+ (
354
+ json.dumps({"error": call["error"]})
355
+ if call.get("error")
356
+ else call.get("result_preview")
357
+ )
358
+ or json.dumps({"result_hash": audit.get("result_hash")}),
359
+ )
360
+ if audit.get("args_hash"):
361
+ span.set_attribute("audit.args_hash", audit["args_hash"])
362
+ span.set_attribute(
363
+ "audit.result_hash", audit.get("result_hash") or ""
364
+ )
365
+ span.set_attribute("audit.at", audit.get("timestamp") or "")
366
+
367
+ # THE AGENT'S OWN DIALOGUE, when the trial recorded one. Installed
368
+ # agents (claude-code and kin) never touch state.json, so without
369
+ # this the rollout page showed a graded outline over an empty
370
+ # conversation — a run that traced "nothing" while the agent had
371
+ # written a sixteen-point redline. Capped, never load-bearing.
372
+ transcript = agent_transcript(trial_dir)
373
+ if transcript and transcript.get("kind") == "claude-code":
374
+ messages = transcript["messages"][:80]
375
+ first_user = next(
376
+ (m["text"] for m in messages if m["role"] == "user" and m["text"]),
377
+ "",
378
+ )
379
+ if first_user:
380
+ root.set_attribute(
381
+ "input.value",
382
+ json.dumps(
383
+ {"task": task_id, "instruction": first_user[:3000]}
384
+ ),
385
+ )
386
+ for step, message in enumerate(messages):
387
+ if message["role"] == "assistant":
388
+ with tracer.start_as_current_span(
389
+ f"{step:02d} assistant"
390
+ ) as span:
391
+ span.set_attribute("openinference.span.kind", "LLM")
392
+ span.set_attribute(
393
+ "output.value",
394
+ json.dumps(
395
+ {
396
+ "text": message["text"],
397
+ **(
398
+ {"tool_calls": message["tool_uses"]}
399
+ if message["tool_uses"]
400
+ else {}
401
+ ),
402
+ },
403
+ default=str,
404
+ )[:4000],
405
+ )
406
+ elif message["tool_results"]:
407
+ with tracer.start_as_current_span(
408
+ f"{step:02d} tool result"
409
+ ) as span:
410
+ span.set_attribute("openinference.span.kind", "TOOL")
411
+ span.set_attribute(
412
+ "output.value",
413
+ "\n".join(message["tool_results"])[:4000],
414
+ )
415
+ elif transcript and transcript.get("kind") == "trajectory":
416
+ for step, entry in enumerate(transcript["steps"][:80]):
417
+ if not isinstance(entry, dict):
418
+ continue
419
+ with tracer.start_as_current_span(
420
+ f"{step:02d} {entry.get('type') or entry.get('role') or 'step'}"
421
+ ) as span:
422
+ span.set_attribute("openinference.span.kind", "LLM")
423
+ span.set_attribute(
424
+ "output.value", json.dumps(entry, default=str)[:4000]
425
+ )
426
+ emitted += 1
427
+ provider.shutdown()
428
+ return emitted
429
+
430
+
431
+ def agent_transcript(trial_dir: str) -> Optional[Dict[str, Any]]:
432
+ """The best agent transcript a trial left behind.
433
+
434
+ Installed agents record their dialogue in agent-specific shapes; the
435
+ state.json convention only covers worlds that export audited tool calls.
436
+ Preference order:
437
+ 1. claude-code's session JSONL (agent/sessions/projects/*/<id>.jsonl) —
438
+ the full message stream, one JSON object per line;
439
+ 2. harbor's normalized agent/trajectory.json, shape-tolerantly.
440
+ Returns None when a trial recorded neither (e.g. mini-swe, whose
441
+ transcript already flows through state.json-style worlds)."""
442
+ import glob as _glob
443
+
444
+ jsonls = sorted(
445
+ _glob.glob(
446
+ os.path.join(trial_dir, "agent", "sessions", "projects", "*", "*.jsonl")
447
+ ),
448
+ key=lambda path: os.path.getmtime(path),
449
+ )
450
+ if jsonls:
451
+ messages: List[Dict[str, Any]] = []
452
+ with open(jsonls[-1], errors="replace") as fh:
453
+ for line in fh:
454
+ try:
455
+ event = json.loads(line)
456
+ except (ValueError, TypeError):
457
+ continue
458
+ message = event.get("message") if isinstance(event, dict) else None
459
+ if not isinstance(message, dict):
460
+ continue
461
+ role = message.get("role")
462
+ if role not in ("user", "assistant"):
463
+ continue
464
+ content = message.get("content")
465
+ text = ""
466
+ tool_uses: List[Dict[str, Any]] = []
467
+ tool_results: List[str] = []
468
+ if isinstance(content, str):
469
+ text = content
470
+ elif isinstance(content, list):
471
+ for block in content:
472
+ if not isinstance(block, dict):
473
+ continue
474
+ if block.get("type") == "text":
475
+ text += str(block.get("text") or "")
476
+ elif block.get("type") == "tool_use":
477
+ tool_uses.append(
478
+ {
479
+ "name": block.get("name"),
480
+ "input": json.dumps(
481
+ block.get("input"), default=str
482
+ )[:600],
483
+ }
484
+ )
485
+ elif block.get("type") == "tool_result":
486
+ raw = block.get("content")
487
+ if isinstance(raw, list):
488
+ raw = "".join(
489
+ str(piece.get("text") or "")
490
+ for piece in raw
491
+ if isinstance(piece, dict)
492
+ )
493
+ tool_results.append(str(raw)[:800])
494
+ if text or tool_uses or tool_results:
495
+ messages.append(
496
+ {
497
+ "role": role,
498
+ "text": text[:2000],
499
+ "tool_uses": tool_uses,
500
+ "tool_results": tool_results,
501
+ }
502
+ )
503
+ if messages:
504
+ return {"kind": "claude-code", "messages": messages}
505
+
506
+ trajectory = _read_json(os.path.join(trial_dir, "agent", "trajectory.json"))
507
+ if trajectory:
508
+ steps = (
509
+ trajectory.get("steps") if isinstance(trajectory, dict) else trajectory
510
+ )
511
+ if isinstance(steps, list) and steps:
512
+ return {"kind": "trajectory", "steps": steps}
513
+ return None
514
+
515
+
516
+ def harvest_job_dir(
517
+ job_dir: str,
518
+ version_id: str,
519
+ evals_client: GatewayEvalsClient,
520
+ *,
521
+ model: str,
522
+ run_id: Optional[str] = None,
523
+ dispatch_shard: Optional[Dict[str, Any]] = None,
524
+ rollouts_per_example: int = 1,
525
+ ) -> Dict[str, Any]:
526
+ """Harvest a completed harbor jobs directory into a version-pinned
527
+ evaluation: create → push samples → finalize with aggregate metrics."""
528
+ samples = samples_from_job_dir(job_dir)
529
+ if not samples:
530
+ raise EvalHarvestError(f"no harbor trials found under {job_dir}")
531
+ metrics = metrics_from_samples(samples)
532
+
533
+ evaluation_id = evals_client.create_evaluation(
534
+ version_id,
535
+ model=model,
536
+ run_id=run_id,
537
+ **({"dispatch_shard": dispatch_shard} if dispatch_shard is not None else {}),
538
+ num_examples=len({s["exampleId"] for s in samples}),
539
+ rollouts_per_example=rollouts_per_example,
540
+ )
541
+ inserted = evals_client.push_samples(evaluation_id, samples)
542
+ evals_client.finalize_evaluation(evaluation_id, metrics=metrics)
543
+ traced = emit_trial_traces(job_dir, evaluation_id)
544
+ return {
545
+ "evaluationId": evaluation_id,
546
+ "samples": inserted,
547
+ "metrics": metrics,
548
+ "richTraces": traced,
549
+ }
550
+
551
+
552
+ def harbor_bin() -> str:
553
+ """The harbor CLI: on PATH, or fall back to the Pier tool venv (Pier depends
554
+ on the same harbor package)."""
555
+ found = shutil.which("harbor")
556
+ if found:
557
+ return found
558
+ pier_sibling = os.path.expanduser("~/.local/share/uv/tools/datacurve-pier/bin/harbor")
559
+ if os.path.exists(pier_sibling):
560
+ return pier_sibling
561
+ raise EvalHarvestError(
562
+ "harbor CLI not found — `uv tool install harbor` (or install datacurve-pier)."
563
+ )
564
+
565
+
566
+ DEFAULT_HARBOR_AGENT = "mini-swe-agent"
567
+
568
+ # Env vars forwarded into the agent sandbox when present — installed agents
569
+ # authenticate their own model calls (mini-swe-agent via OpenRouter/litellm,
570
+ # claude-code via the Anthropic API, goose via provider envs). OPENAI_HOST
571
+ # lets goose's openai provider point at any OpenAI-compatible endpoint
572
+ # (e.g. OPENAI_API_KEY=$OPENROUTER_API_KEY + OPENAI_HOST=https://openrouter.ai/api).
573
+ AGENT_ENV_PASSTHROUGH = (
574
+ "OPENROUTER_API_KEY",
575
+ "ANTHROPIC_API_KEY",
576
+ "OPENAI_API_KEY",
577
+ "OPENAI_HOST",
578
+ )
579
+
580
+
581
+ def find_mcp_config(src: str) -> Optional[str]:
582
+ """An env-bundled Claude-style MCP config: <src>/mcp.json (local source
583
+ dirs) or <src>/tasks/mcp.json (what survives the push archiver)."""
584
+ for candidate in (os.path.join(src, "mcp.json"), os.path.join(src, "tasks", "mcp.json")):
585
+ if os.path.isfile(candidate):
586
+ return candidate
587
+ return None
588
+
589
+
590
+ def resolve_harbor_model(model: str, agent: str) -> str:
591
+ """mini-swe-agent speaks litellm ids — default to the OpenRouter provider
592
+ prefix. Other agents (claude-code, codex, …) take their provider's native
593
+ ids verbatim."""
594
+ if agent == DEFAULT_HARBOR_AGENT and not model.startswith("openrouter/"):
595
+ return f"openrouter/{model}"
596
+ return model
597
+
598
+
599
+ def run_harbor_job(
600
+ tasks_dir: str,
601
+ jobs_dir: str,
602
+ *,
603
+ model: str,
604
+ agent: str = DEFAULT_HARBOR_AGENT,
605
+ sandbox_env: Optional[str] = None,
606
+ num_examples: int = -1,
607
+ rollouts_per_example: int = 1,
608
+ n_concurrent: int = 4,
609
+ extra_env: Optional[Dict[str, str]] = None,
610
+ mcp_config: Optional[str] = None,
611
+ ) -> str:
612
+ """Run the first-party harbor engine over a Harbor task dir. Returns the
613
+ job output dir. Exit-code tolerant: harbor exits non-zero when trials fail;
614
+ the reward files still land and the harvest reflects them honestly."""
615
+ resolved_sandbox = sandbox_env or os.environ.get("HARBOR_SANDBOX_ENV", "docker")
616
+ # Trial concurrency is a HOST-CAPACITY knob, not a task knob: each harbor
617
+ # task composes several docker networks, and a host whose address pools
618
+ # are exhausted fails every subsequent trial in ~1s with reward null
619
+ # (run cmt6a2bzc: 2 shards x -n 4 on one instance -> 42/50 trials never
620
+ # ran). HARBOR_N_CONCURRENT lets the dispatcher size runs to the host
621
+ # without a code change.
622
+ env_concurrency = os.environ.get("HARBOR_N_CONCURRENT", "").strip()
623
+ resolved_concurrency = (
624
+ int(env_concurrency) if env_concurrency.isdigit() else n_concurrent
625
+ )
626
+ if resolved_sandbox == "docker":
627
+ # Leftover networks from crashed prior trials shrink the same address
628
+ # pools; a run should never inherit a previous run's leak. Unused
629
+ # networks only — running trials are untouched.
630
+ subprocess.run(
631
+ ["docker", "network", "prune", "-f"],
632
+ check=False,
633
+ capture_output=True,
634
+ )
635
+ cmd = [
636
+ harbor_bin(),
637
+ "run",
638
+ "-p", tasks_dir,
639
+ "-a", agent,
640
+ "-m", model,
641
+ "-e", resolved_sandbox,
642
+ "-o", jobs_dir,
643
+ "-k", str(max(1, rollouts_per_example)),
644
+ "-n", str(max(1, resolved_concurrency)),
645
+ "-y",
646
+ "-q",
647
+ ]
648
+ if num_examples and num_examples > 0:
649
+ cmd += ["-l", str(num_examples)]
650
+ if mcp_config:
651
+ cmd += ["--mcp-config", mcp_config]
652
+
653
+ env_pairs: Dict[str, str] = {
654
+ key: os.environ[key]
655
+ for key in AGENT_ENV_PASSTHROUGH
656
+ if os.environ.get(key)
657
+ }
658
+ env_pairs.update(extra_env or {})
659
+ for key, value in env_pairs.items():
660
+ if value:
661
+ cmd += ["--ae", f"{key}={value}"]
662
+
663
+ # Redact env values from the printed command line.
664
+ printable = " ".join(
665
+ "--ae " + arg.split("=", 1)[0] + "=***" if i and cmd[i - 1] == "--ae" else arg
666
+ for i, arg in enumerate(cmd)
667
+ if arg != "--ae"
668
+ )
669
+ print(f"[harbor] {printable}", flush=True)
670
+ proc = subprocess.run(cmd)
671
+ if proc.returncode != 0:
672
+ print(f"[harbor] exited {proc.returncode} (harvesting whatever ran)", flush=True)
673
+ return jobs_dir