dojozero 0.2.1__tar.gz → 0.2.2__tar.gz

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 (172) hide show
  1. {dojozero-0.2.1 → dojozero-0.2.2}/.gitignore +7 -2
  2. {dojozero-0.2.1 → dojozero-0.2.2}/PKG-INFO +1 -1
  3. {dojozero-0.2.1 → dojozero-0.2.2}/pyproject.toml +1 -1
  4. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_endpoints.py +7 -6
  5. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_utils.py +5 -5
  6. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_models.py +4 -0
  7. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/cli.py +69 -3
  8. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_filesystem_orchestrator_store.py +12 -2
  9. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_trial_orchestrator.py +13 -3
  10. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/__init__.py +9 -0
  11. dojozero-0.2.2/src/dojozero/dashboard_server/_cluster.py +433 -0
  12. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_gateway_routing.py +108 -1
  13. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_scheduler.py +336 -33
  14. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_server.py +501 -84
  15. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_trial_manager.py +33 -2
  16. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_adapter.py +48 -0
  17. dojozero-0.2.2/tests/test_cluster.py +465 -0
  18. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_scheduler.py +420 -98
  19. {dojozero-0.2.1 → dojozero-0.2.2}/README.md +0 -0
  20. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/__init__.py +0 -0
  21. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/_optional_alicloud.py +0 -0
  22. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/agents/__init__.py +0 -0
  23. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/agents/_config.py +0 -0
  24. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/agents/_social_board.py +0 -0
  25. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/agents/_toolkit.py +0 -0
  26. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/agents/_trial_utils.py +0 -0
  27. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/__init__.py +0 -0
  28. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_cache.py +0 -0
  29. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_config.py +0 -0
  30. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_constants.py +0 -0
  31. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_models.py +0 -0
  32. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_redis_reader.py +0 -0
  33. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/_server.py +0 -0
  34. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/arena_server/get_snapshot.sh +0 -0
  35. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/__init__.py +0 -0
  36. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_agent.py +0 -0
  37. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_broker.py +0 -0
  38. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_config.py +0 -0
  39. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_formatters.py +0 -0
  40. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/betting/_metadata.py +0 -0
  41. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/__init__.py +0 -0
  42. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_actors.py +0 -0
  43. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_base.py +0 -0
  44. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_credentials.py +0 -0
  45. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_metadata.py +0 -0
  46. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_models.py +0 -0
  47. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_registry.py +0 -0
  48. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_runtime.py +0 -0
  49. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_tracing.py +0 -0
  50. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/core/_types.py +0 -0
  51. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_game_discovery.py +0 -0
  52. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_jsonl_utils.py +0 -0
  53. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/dashboard_server/_types.py +0 -0
  54. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/__init__.py +0 -0
  55. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_backtest.py +0 -0
  56. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_config.py +0 -0
  57. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_context.py +0 -0
  58. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_factory.py +0 -0
  59. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_game_info.py +0 -0
  60. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_hub.py +0 -0
  61. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_models.py +0 -0
  62. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_processors.py +0 -0
  63. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_stores.py +0 -0
  64. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_streams.py +0 -0
  65. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_subscriptions.py +0 -0
  66. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/_utils.py +0 -0
  67. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/__init__.py +0 -0
  68. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/_api.py +0 -0
  69. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/_state_tracker.py +0 -0
  70. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/_stats_events.py +0 -0
  71. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/_stats_fetcher.py +0 -0
  72. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/espn/_utils.py +0 -0
  73. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/__init__.py +0 -0
  74. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_api.py +0 -0
  75. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_events.py +0 -0
  76. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_factory.py +0 -0
  77. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_state_tracker.py +0 -0
  78. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_store.py +0 -0
  79. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nba/_utils.py +0 -0
  80. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/__init__.py +0 -0
  81. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_api.py +0 -0
  82. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_events.py +0 -0
  83. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_factory.py +0 -0
  84. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_state_tracker.py +0 -0
  85. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_store.py +0 -0
  86. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/ncaa/_utils.py +0 -0
  87. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/__init__.py +0 -0
  88. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_api.py +0 -0
  89. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_events.py +0 -0
  90. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_factory.py +0 -0
  91. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_state_tracker.py +0 -0
  92. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_store.py +0 -0
  93. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/nfl/_utils.py +0 -0
  94. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/__init__.py +0 -0
  95. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/_api.py +0 -0
  96. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/_events.py +0 -0
  97. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/_factory.py +0 -0
  98. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/_models.py +0 -0
  99. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/polymarket/_store.py +0 -0
  100. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/__init__.py +0 -0
  101. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_api.py +0 -0
  102. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_events.py +0 -0
  103. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_factory.py +0 -0
  104. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_formatters.py +0 -0
  105. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_store.py +0 -0
  106. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_watchlist.py +0 -0
  107. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/__init__.py +0 -0
  108. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/_api.py +0 -0
  109. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/_events.py +0 -0
  110. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/_factory.py +0 -0
  111. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/_formatters.py +0 -0
  112. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/data/websearch/_store.py +0 -0
  113. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/__init__.py +0 -0
  114. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_auth.py +0 -0
  115. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_models.py +0 -0
  116. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_rate_limit.py +0 -0
  117. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_server.py +0 -0
  118. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/gateway/_sse.py +0 -0
  119. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nba/__init__.py +0 -0
  120. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nba/_agent.py +0 -0
  121. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nba/_datastream.py +0 -0
  122. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nba/_formatters.py +0 -0
  123. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nba/_trial.py +0 -0
  124. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ncaa/__init__.py +0 -0
  125. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ncaa/_agent.py +0 -0
  126. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ncaa/_datastream.py +0 -0
  127. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ncaa/_formatters.py +0 -0
  128. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ncaa/_trial.py +0 -0
  129. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nfl/__init__.py +0 -0
  130. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nfl/_agent.py +0 -0
  131. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nfl/_datastream.py +0 -0
  132. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nfl/_formatters.py +0 -0
  133. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/nfl/_trial.py +0 -0
  134. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ray_runtime/__init__.py +0 -0
  135. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/ray_runtime/_impl.py +0 -0
  136. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/sync_service/__init__.py +0 -0
  137. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/sync_service/_redis_client.py +0 -0
  138. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/sync_service/_sync.py +0 -0
  139. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/sync_service/main.py +0 -0
  140. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/utils/__init__.py +0 -0
  141. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/utils/oss.py +0 -0
  142. {dojozero-0.2.1 → dojozero-0.2.2}/src/dojozero/utils/time.py +0 -0
  143. {dojozero-0.2.1 → dojozero-0.2.2}/tests/conftest.py +0 -0
  144. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_agent_configs.py +0 -0
  145. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_agent_event_throttle.py +0 -0
  146. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_arena_event_deserialization.py +0 -0
  147. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_arena_replay_seek.py +0 -0
  148. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_arena_span_grouping.py +0 -0
  149. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_betting_formatters.py +0 -0
  150. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_broker.py +0 -0
  151. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_cli_agents.py +0 -0
  152. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_dashboard.py +0 -0
  153. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_data_hub.py +0 -0
  154. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_data_nba.py +0 -0
  155. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_data_nfl.py +0 -0
  156. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_data_polymarket.py +0 -0
  157. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_game_state_tracker.py +0 -0
  158. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_gateway.py +0 -0
  159. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_metadata.py +0 -0
  160. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_nba_formatters.py +0 -0
  161. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_nba_moneyline_agent.py +0 -0
  162. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_nfl_formatters.py +0 -0
  163. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_nfl_moneyline_agent.py +0 -0
  164. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_oss.py +0 -0
  165. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_registry.py +0 -0
  166. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_social_board.py +0 -0
  167. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_socialmedia_events.py +0 -0
  168. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_span_models.py +0 -0
  169. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_stats_insight_events.py +0 -0
  170. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_subscriptions.py +0 -0
  171. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_websearch_events.py +0 -0
  172. {dojozero-0.2.1 → dojozero-0.2.2}/tests/test_websearch_formatters.py +0 -0
@@ -158,7 +158,8 @@ uv.lock
158
158
  Thumbs.db
159
159
 
160
160
  # Sample scenario artifacts
161
- dojozero-store
161
+ dojozero-store/
162
+ store/
162
163
 
163
164
  # Data
164
165
  /outputs/
@@ -174,4 +175,8 @@ package-lock.json
174
175
  logs/
175
176
 
176
177
  # node modules
177
- node_modules/
178
+ node_modules/
179
+
180
+
181
+ agents/llms/*.yaml
182
+ !agents/llms/default.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dojozero
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A platform for running AI agents on realtime sport data and make predictions about game outcomes.
5
5
  Project-URL: Homepage, https://github.com/agentscope-ai/DojoZero
6
6
  Project-URL: Repository, https://github.com/agentscope-ai/DojoZero
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dojozero"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "A platform for running AI agents on realtime sport data and make predictions about game outcomes."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -357,11 +357,11 @@ def register_rest_endpoints(app: FastAPI) -> None:
357
357
  default=None,
358
358
  description="Filter by league: 'NBA', 'NFL', etc.",
359
359
  ),
360
- limit: int = Query(
361
- default=20,
362
- description="Maximum number of agents to return.",
360
+ limit: int | None = Query(
361
+ default=None,
362
+ description="Maximum number of agents to return. Returns all if not specified.",
363
363
  ge=1,
364
- le=100,
364
+ le=1000,
365
365
  ),
366
366
  ) -> JSONResponse:
367
367
  """Get agent leaderboard ranked by winnings.
@@ -379,8 +379,9 @@ def register_rest_endpoints(app: FastAPI) -> None:
379
379
  if leaderboard is None:
380
380
  leaderboard = await refresher.refresh_leaderboard_on_demand(league=league)
381
381
 
382
- # Apply limit
383
- leaderboard = leaderboard[:limit]
382
+ # Apply limit only if specified
383
+ if limit is not None:
384
+ leaderboard = leaderboard[:limit]
384
385
 
385
386
  response = LeaderboardResponse(leaderboard=leaderboard)
386
387
  return JSONResponse(
@@ -831,7 +831,7 @@ def _compute_leaderboard_from_spans(
831
831
  spans_by_trial: dict[str, list[SpanData]],
832
832
  agent_info_cache: dict[str, AgentInfo],
833
833
  trial_ids: list[str] | None = None,
834
- limit: int = 20,
834
+ limit: int | None = None,
835
835
  ) -> list[LeaderboardEntry]:
836
836
  """Compute agent leaderboard from pre-fetched spans.
837
837
 
@@ -842,7 +842,7 @@ def _compute_leaderboard_from_spans(
842
842
  spans_by_trial: Pre-fetched spans grouped by trial_id
843
843
  agent_info_cache: Pre-populated agent info cache (agent_id -> AgentInfo)
844
844
  trial_ids: Optional list to filter which trials to process (None = all)
845
- limit: Maximum entries to return
845
+ limit: Maximum entries to return (None = all)
846
846
 
847
847
  Returns:
848
848
  List of agents sorted by winnings (highest first)
@@ -937,9 +937,9 @@ def _compute_leaderboard_from_spans(
937
937
 
938
938
  # Sort by winnings (descending) and add rank
939
939
  leaderboard.sort(key=lambda x: x.winnings, reverse=True)
940
+ entries = leaderboard[:limit] if limit is not None else leaderboard
940
941
  ranked = [
941
- entry.model_copy(update={"rank": i + 1})
942
- for i, entry in enumerate(leaderboard[:limit])
942
+ entry.model_copy(update={"rank": i + 1}) for i, entry in enumerate(entries)
943
943
  ]
944
944
 
945
945
  return ranked
@@ -949,7 +949,7 @@ async def _compute_leaderboard(
949
949
  trace_reader: TraceReader,
950
950
  trial_ids: list[str],
951
951
  cache: "LandingPageCache | None" = None,
952
- limit: int = 20,
952
+ limit: int | None = None,
953
953
  ) -> list[LeaderboardEntry]:
954
954
  """Compute agent leaderboard (on-demand version).
955
955
 
@@ -435,6 +435,10 @@ class AgentInfo(BaseModel):
435
435
  """Agent registration payload for tracing (agent.agent_initialize span)."""
436
436
 
437
437
  agent_id: str = Field(default="", description="Unique ID for the agent")
438
+ display_name: str = Field(
439
+ default="",
440
+ description="Human-readable display name (e.g., GitHub username or custom name)",
441
+ )
438
442
  persona: str = Field(
439
443
  default="", description="Agent persona tag (e.g., 'degen', 'whale', 'shark')"
440
444
  )
@@ -34,6 +34,7 @@ from dojozero.data.polymarket import PolymarketAPI
34
34
  from dojozero.utils import utc_iso_to_local
35
35
  from dojozero.core import TrialBuilderNotFoundError as _TrialBuilderNotFoundError
36
36
  from dojozero.dashboard_server import InitialTrialSourceDict
37
+ from dojozero.dashboard_server._scheduler import SchedulerStore
37
38
 
38
39
  try: # Optional Ray dependency
39
40
  from dojozero.ray_runtime import RayActorRuntimeProvider
@@ -389,6 +390,26 @@ def _build_parser() -> argparse.ArgumentParser:
389
390
  action="store_true",
390
391
  help="Disable HTTP gateway for external agents (gateway is enabled by default).",
391
392
  )
393
+ # Cluster arguments
394
+ serve_parser.add_argument(
395
+ "--server-id",
396
+ dest="server_id",
397
+ default=None,
398
+ help="Unique server identifier for cluster mode (default: hostname).",
399
+ )
400
+ serve_parser.add_argument(
401
+ "--server-url",
402
+ dest="server_url",
403
+ default=None,
404
+ help="Externally reachable URL for this server (default: http://{host}:{port}).",
405
+ )
406
+ serve_parser.add_argument(
407
+ "--cluster-redis-url",
408
+ dest="cluster_redis_url",
409
+ default=os.environ.get("DOJOZERO_CLUSTER_REDIS_URL"),
410
+ help="Redis URL for cluster leader election and peer discovery (enables redis mode). "
411
+ "Falls back to DOJOZERO_CLUSTER_REDIS_URL env var.",
412
+ )
392
413
 
393
414
  # Arena Server command
394
415
  arena_parser = subparsers.add_parser(
@@ -2020,10 +2041,17 @@ async def _serve_command(args: argparse.Namespace) -> int:
2020
2041
  args.store_directory if args.store_directory else Path(DEFAULT_STORE_DIRECTORY)
2021
2042
  )
2022
2043
 
2023
- # Create scheduler store (uses store root directory for persistence)
2024
- from dojozero.dashboard_server._scheduler import FileSchedulerStore
2044
+ # Create scheduler store Redis in cluster mode, file otherwise
2045
+ cluster_redis_url: str | None = getattr(args, "cluster_redis_url", None)
2025
2046
 
2026
- scheduler_store = FileSchedulerStore(store_path)
2047
+ if cluster_redis_url:
2048
+ from dojozero.dashboard_server._scheduler import RedisSchedulerStore
2049
+
2050
+ scheduler_store: SchedulerStore = RedisSchedulerStore(cluster_redis_url)
2051
+ else:
2052
+ from dojozero.dashboard_server._scheduler import FileSchedulerStore
2053
+
2054
+ scheduler_store = FileSchedulerStore(store_path)
2027
2055
 
2028
2056
  # Expand glob patterns and load trial source configurations
2029
2057
  import glob as glob_module
@@ -2109,6 +2137,43 @@ async def _serve_command(args: argparse.Namespace) -> int:
2109
2137
  "enabled" if local_auth else "disabled",
2110
2138
  )
2111
2139
 
2140
+ # Build cluster config if Redis cluster URL is provided
2141
+ cluster_config = None
2142
+ server_id_arg: str | None = getattr(args, "server_id", None)
2143
+ server_url_arg: str | None = getattr(args, "server_url", None)
2144
+
2145
+ if cluster_redis_url:
2146
+ from dojozero.dashboard_server._cluster import ClusterConfig
2147
+
2148
+ if server_url_arg:
2149
+ server_url = server_url_arg
2150
+ elif host in ("0.0.0.0", "::"):
2151
+ # Resolve to a reachable IP so peers can forward trials to us.
2152
+ # In Kubernetes, expose status.podIP as the POD_IP env var via
2153
+ # the Downward API. The socket fallback works on plain VMs but
2154
+ # may return 127.0.0.1 inside containers — warn if that happens.
2155
+ import socket
2156
+
2157
+ pod_ip = os.environ.get("POD_IP", "").strip()
2158
+ if not pod_ip:
2159
+ pod_ip = socket.gethostbyname(socket.gethostname())
2160
+ if pod_ip.startswith("127."):
2161
+ LOGGER.warning(
2162
+ "Resolved server IP is %s which is not routable by "
2163
+ "peers. Set --server-url or the POD_IP env var to "
2164
+ "this server's reachable address.",
2165
+ pod_ip,
2166
+ )
2167
+ server_url = f"http://{pod_ip}:{port}"
2168
+ else:
2169
+ server_url = f"http://{host}:{port}"
2170
+ cluster_config = ClusterConfig(
2171
+ server_id=server_id_arg or "",
2172
+ server_url=server_url,
2173
+ redis_url=cluster_redis_url,
2174
+ )
2175
+ LOGGER.info("Cluster mode enabled (redis_url=%s)", cluster_redis_url)
2176
+
2112
2177
  await run_dashboard_server(
2113
2178
  orchestrator=orchestrator,
2114
2179
  scheduler_store=scheduler_store,
@@ -2124,6 +2189,7 @@ async def _serve_command(args: argparse.Namespace) -> int:
2124
2189
  enable_gateway=enable_gateway,
2125
2190
  authenticator=authenticator,
2126
2191
  no_scheduler=no_scheduler,
2192
+ cluster_config=cluster_config,
2127
2193
  )
2128
2194
  return 0
2129
2195
 
@@ -74,12 +74,22 @@ class FileSystemOrchestratorStore(OrchestratorStore):
74
74
  spec = self._read_spec(spec_path)
75
75
  status_path = trial_dir / self.STATUS_FILE
76
76
  status = self._read_status(status_path) if status_path.exists() else None
77
- return TrialRecord(spec=spec, last_status=status)
77
+ # Read owner_server_id from spec file (stored alongside spec)
78
+ spec_payload = self._read_json(spec_path)
79
+ owner_server_id = spec_payload.get("owner_server_id")
80
+ return TrialRecord(
81
+ spec=spec,
82
+ last_status=status,
83
+ owner_server_id=owner_server_id,
84
+ )
78
85
 
79
86
  def upsert_trial_record(self, record: TrialRecord) -> None:
80
87
  trial_dir = self._trial_dir(record.trial_id)
81
88
  trial_dir.mkdir(parents=True, exist_ok=True)
82
- self._write_json(trial_dir / self.SPEC_FILE, self._serialize_spec(record.spec))
89
+ spec_data = self._serialize_spec(record.spec)
90
+ if record.owner_server_id is not None:
91
+ spec_data["owner_server_id"] = record.owner_server_id
92
+ self._write_json(trial_dir / self.SPEC_FILE, spec_data)
83
93
  if record.last_status is not None:
84
94
  self._write_json(
85
95
  trial_dir / self.STATUS_FILE, self._serialize_status(record.last_status)
@@ -297,6 +297,7 @@ class TrialRecord:
297
297
 
298
298
  spec: TrialSpec
299
299
  last_status: TrialStatus | None = None
300
+ owner_server_id: str | None = None
300
301
 
301
302
  @property
302
303
  def trial_id(self) -> str:
@@ -448,15 +449,24 @@ class TrialOrchestrator:
448
449
  """Access the underlying OrchestratorStore."""
449
450
  return self._store
450
451
 
451
- async def launch_trial(self, spec: TrialSpec) -> TrialStatus:
452
- """Instantiate and start every actor defined in *spec*."""
452
+ async def launch_trial(
453
+ self,
454
+ spec: TrialSpec,
455
+ owner_server_id: str | None = None,
456
+ ) -> TrialStatus:
457
+ """Instantiate and start every actor defined in *spec*.
458
+
459
+ Args:
460
+ spec: Trial specification.
461
+ owner_server_id: Server that owns this trial (cluster mode).
462
+ """
453
463
 
454
464
  LOGGER.info("launching trial '%s'", spec.trial_id)
455
465
  spec = self._apply_resume_from_spec(spec)
456
466
  normalized_spec = self._normalize_spec(spec)
457
467
  record = self._catalog.get(spec.trial_id)
458
468
  if record is None:
459
- record = TrialRecord(spec=normalized_spec)
469
+ record = TrialRecord(spec=normalized_spec, owner_server_id=owner_server_id)
460
470
  elif record.spec != normalized_spec:
461
471
  raise OrchestratorError(
462
472
  f"trial '{spec.trial_id}' already registered with a different configuration"
@@ -8,6 +8,10 @@ This package provides the Dashboard Server functionality including:
8
8
  Moved from core module to keep server-related code separate from core abstractions.
9
9
  """
10
10
 
11
+ from ._cluster import (
12
+ ClusterConfig,
13
+ PeerInfo,
14
+ )
11
15
  from ._game_discovery import (
12
16
  GameInfo,
13
17
  NBAGameFetcher,
@@ -17,6 +21,7 @@ from ._game_discovery import (
17
21
  )
18
22
  from ._scheduler import (
19
23
  FileSchedulerStore,
24
+ RedisSchedulerStore,
20
25
  ScheduledTrial,
21
26
  ScheduledTrialPhase,
22
27
  ScheduleManager,
@@ -60,6 +65,9 @@ from ._types import (
60
65
  )
61
66
 
62
67
  __all__ = [
68
+ # Cluster
69
+ "ClusterConfig",
70
+ "PeerInfo",
63
71
  # Server
64
72
  "create_dashboard_app",
65
73
  "run_dashboard_server",
@@ -78,6 +86,7 @@ __all__ = [
78
86
  "ScheduledTrialPhase",
79
87
  "SchedulerStore",
80
88
  "FileSchedulerStore",
89
+ "RedisSchedulerStore",
81
90
  # Trial Sources
82
91
  "TrialSource",
83
92
  "TrialSourceConfig",