dojozero 0.2.0__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 (173) hide show
  1. {dojozero-0.2.0 → dojozero-0.2.2}/.gitignore +9 -2
  2. {dojozero-0.2.0 → dojozero-0.2.2}/PKG-INFO +5 -71
  3. dojozero-0.2.2/README.md +25 -0
  4. {dojozero-0.2.0 → dojozero-0.2.2}/pyproject.toml +1 -1
  5. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/agents/_config.py +30 -5
  6. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_cache.py +6 -0
  7. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_constants.py +32 -5
  8. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_endpoints.py +7 -6
  9. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_server.py +23 -8
  10. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_utils.py +49 -13
  11. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_agent.py +1 -6
  12. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_broker.py +5 -0
  13. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_metadata.py +4 -0
  14. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_models.py +4 -0
  15. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/cli.py +125 -7
  16. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_filesystem_orchestrator_store.py +12 -2
  17. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_models.py +2 -0
  18. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_tracing.py +51 -23
  19. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_trial_orchestrator.py +13 -3
  20. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/__init__.py +9 -0
  21. dojozero-0.2.2/src/dojozero/dashboard_server/_cluster.py +433 -0
  22. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_gateway_routing.py +108 -1
  23. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_scheduler.py +418 -35
  24. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_server.py +515 -87
  25. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_trial_manager.py +185 -6
  26. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/__init__.py +6 -0
  27. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_hub.py +26 -0
  28. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/_store.py +1 -0
  29. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_adapter.py +48 -0
  30. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nba/_trial.py +2 -0
  31. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ncaa/_trial.py +2 -0
  32. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nfl/_trial.py +2 -0
  33. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/sync_service/_sync.py +27 -7
  34. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_agent_configs.py +105 -0
  35. dojozero-0.2.2/tests/test_arena_replay_seek.py +60 -0
  36. dojozero-0.2.2/tests/test_cluster.py +465 -0
  37. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_scheduler.py +420 -98
  38. dojozero-0.2.0/README.md +0 -91
  39. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/__init__.py +0 -0
  40. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/_optional_alicloud.py +0 -0
  41. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/agents/__init__.py +0 -0
  42. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/agents/_social_board.py +0 -0
  43. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/agents/_toolkit.py +0 -0
  44. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/agents/_trial_utils.py +0 -0
  45. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/__init__.py +0 -0
  46. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_config.py +0 -0
  47. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_models.py +0 -0
  48. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/_redis_reader.py +0 -0
  49. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/arena_server/get_snapshot.sh +0 -0
  50. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/__init__.py +0 -0
  51. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_config.py +0 -0
  52. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/betting/_formatters.py +0 -0
  53. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/__init__.py +0 -0
  54. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_actors.py +0 -0
  55. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_base.py +0 -0
  56. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_credentials.py +0 -0
  57. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_metadata.py +0 -0
  58. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_registry.py +0 -0
  59. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_runtime.py +0 -0
  60. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/core/_types.py +0 -0
  61. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_game_discovery.py +0 -0
  62. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_jsonl_utils.py +0 -0
  63. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/dashboard_server/_types.py +0 -0
  64. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_backtest.py +0 -0
  65. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_config.py +0 -0
  66. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_context.py +0 -0
  67. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_factory.py +0 -0
  68. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_game_info.py +0 -0
  69. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_models.py +0 -0
  70. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_processors.py +0 -0
  71. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_stores.py +0 -0
  72. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_streams.py +0 -0
  73. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_subscriptions.py +0 -0
  74. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/_utils.py +0 -0
  75. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/__init__.py +0 -0
  76. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/_api.py +0 -0
  77. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/_state_tracker.py +0 -0
  78. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/_stats_events.py +0 -0
  79. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/_stats_fetcher.py +0 -0
  80. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/espn/_utils.py +0 -0
  81. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/__init__.py +0 -0
  82. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_api.py +0 -0
  83. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_events.py +0 -0
  84. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_factory.py +0 -0
  85. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_state_tracker.py +0 -0
  86. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_store.py +0 -0
  87. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nba/_utils.py +0 -0
  88. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/__init__.py +0 -0
  89. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_api.py +0 -0
  90. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_events.py +0 -0
  91. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_factory.py +0 -0
  92. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_state_tracker.py +0 -0
  93. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_store.py +0 -0
  94. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/ncaa/_utils.py +0 -0
  95. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/__init__.py +0 -0
  96. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_api.py +0 -0
  97. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_events.py +0 -0
  98. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_factory.py +0 -0
  99. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_state_tracker.py +0 -0
  100. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_store.py +0 -0
  101. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/nfl/_utils.py +0 -0
  102. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/__init__.py +0 -0
  103. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/_api.py +0 -0
  104. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/_events.py +0 -0
  105. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/_factory.py +0 -0
  106. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/polymarket/_models.py +0 -0
  107. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/__init__.py +0 -0
  108. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_api.py +0 -0
  109. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_events.py +0 -0
  110. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_factory.py +0 -0
  111. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_formatters.py +0 -0
  112. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_store.py +0 -0
  113. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/socialmedia/_watchlist.py +0 -0
  114. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/__init__.py +0 -0
  115. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/_api.py +0 -0
  116. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/_events.py +0 -0
  117. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/_factory.py +0 -0
  118. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/_formatters.py +0 -0
  119. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/data/websearch/_store.py +0 -0
  120. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/__init__.py +0 -0
  121. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_auth.py +0 -0
  122. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_models.py +0 -0
  123. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_rate_limit.py +0 -0
  124. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_server.py +0 -0
  125. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/gateway/_sse.py +0 -0
  126. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nba/__init__.py +0 -0
  127. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nba/_agent.py +0 -0
  128. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nba/_datastream.py +0 -0
  129. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nba/_formatters.py +0 -0
  130. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ncaa/__init__.py +0 -0
  131. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ncaa/_agent.py +0 -0
  132. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ncaa/_datastream.py +0 -0
  133. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ncaa/_formatters.py +0 -0
  134. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nfl/__init__.py +0 -0
  135. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nfl/_agent.py +0 -0
  136. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nfl/_datastream.py +0 -0
  137. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/nfl/_formatters.py +0 -0
  138. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ray_runtime/__init__.py +0 -0
  139. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/ray_runtime/_impl.py +0 -0
  140. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/sync_service/__init__.py +0 -0
  141. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/sync_service/_redis_client.py +0 -0
  142. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/sync_service/main.py +0 -0
  143. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/utils/__init__.py +0 -0
  144. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/utils/oss.py +0 -0
  145. {dojozero-0.2.0 → dojozero-0.2.2}/src/dojozero/utils/time.py +0 -0
  146. {dojozero-0.2.0 → dojozero-0.2.2}/tests/conftest.py +0 -0
  147. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_agent_event_throttle.py +0 -0
  148. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_arena_event_deserialization.py +0 -0
  149. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_arena_span_grouping.py +0 -0
  150. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_betting_formatters.py +0 -0
  151. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_broker.py +0 -0
  152. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_cli_agents.py +0 -0
  153. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_dashboard.py +0 -0
  154. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_data_hub.py +0 -0
  155. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_data_nba.py +0 -0
  156. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_data_nfl.py +0 -0
  157. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_data_polymarket.py +0 -0
  158. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_game_state_tracker.py +0 -0
  159. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_gateway.py +0 -0
  160. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_metadata.py +0 -0
  161. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_nba_formatters.py +0 -0
  162. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_nba_moneyline_agent.py +0 -0
  163. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_nfl_formatters.py +0 -0
  164. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_nfl_moneyline_agent.py +0 -0
  165. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_oss.py +0 -0
  166. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_registry.py +0 -0
  167. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_social_board.py +0 -0
  168. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_socialmedia_events.py +0 -0
  169. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_span_models.py +0 -0
  170. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_stats_insight_events.py +0 -0
  171. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_subscriptions.py +0 -0
  172. {dojozero-0.2.0 → dojozero-0.2.2}/tests/test_websearch_events.py +0 -0
  173. {dojozero-0.2.0 → 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/
@@ -171,5 +172,11 @@ dojozero-store
171
172
  package-lock.json
172
173
 
173
174
  # logs
174
- /logs/node_modules/
175
+ logs/
176
+
177
+ # node modules
175
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.0
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
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
44
44
 
45
45
  # DojoZero
46
46
 
47
- A platform for running AI agents on realtime sport data and make predictions about game outcomes.
47
+ A platform for running AI agents on realtime sport data to reason about game outcomes and make predictions. DojoZero provides an actor-based runtime with live data streams, agent orchestration, trial management, backtesting, and tracing. Currently supports NBA, NFL, and NCAA.
48
48
 
49
49
  ## Installation
50
50
 
@@ -52,83 +52,17 @@ A platform for running AI agents on realtime sport data and make predictions abo
52
52
  pip install dojozero
53
53
  ```
54
54
 
55
- ### Optional extras
55
+ Optional extras:
56
56
 
57
57
  ```bash
58
- pip install dojozero[alicloud] # Alibaba Cloud integration (OSS, credentials, SLS)
58
+ pip install dojozero[alicloud] # Alibaba Cloud integration (OSS, SLS tracing)
59
59
  pip install dojozero[redis] # Redis-backed data stores
60
60
  pip install dojozero[ray] # Distributed execution via Ray
61
61
  ```
62
62
 
63
- ## Environment Setup
64
-
65
- ```bash
66
- cp .env.example .env
67
- ```
68
-
69
- ## Quick Start
70
-
71
- ### Run a single trial locally
72
-
73
- ```bash
74
- dojo0 run --params trial_params/nba-moneyline.yaml --trial-id nba-local-001
75
- ```
76
-
77
- ### Resume an interrupted trial
78
-
79
- ```bash
80
- dojo0 run --trial-id nba-local-001 --resume-latest
81
- ```
82
-
83
- ### Run through dashboard server
84
-
85
- ```bash
86
- # Start server with tracing
87
- dojo0 serve --trace-backend jaeger
88
-
89
- # Submit a trial from another terminal
90
- dojo0 run \
91
- --params trial_params/nba-moneyline.yaml \
92
- --trial-id nba-server-001 \
93
- --server http://localhost:8000
94
- ```
95
-
96
- ### Automatic scheduling with trial sources
97
-
98
- ```bash
99
- dojo0 serve --trace-backend jaeger --trial-source "trial_sources/daily/*.yaml"
100
- ```
101
-
102
- ### Backtest from captured events
103
-
104
- ```bash
105
- dojo0 backtest \
106
- --events outputs/2026-01-12/401772976.jsonl \
107
- --params outputs/2026-01-12/401772976.yaml \
108
- --speed 100 --max-sleep 1
109
- ```
110
-
111
- ### Arena (live visualization)
112
-
113
- ```bash
114
- dojo0 arena --trace-backend jaeger
115
- ```
116
-
117
- ## Command Index
118
-
119
- | Command | Purpose |
120
- |---------|---------|
121
- | `dojo0 run` | Start a local or server-submitted trial |
122
- | `dojo0 serve` | Dashboard / orchestration server |
123
- | `dojo0 arena` | Trace-backed Arena visualization server |
124
- | `dojo0 backtest` | Replay persisted event streams |
125
- | `dojo0 list-sources` | List trial sources |
126
- | `dojo0 list-trials` | List scheduled trials |
127
- | `dojo0 clear-schedules` | Clear scheduled runs |
128
-
129
63
  ## Documentation
130
64
 
131
- - [Documentation](https://github.com/agentscope-ai/DojoZero/docs/README.md)
65
+ See the [full documentation](https://github.com/agentscope-ai/DojoZero/tree/main/docs) for setup, configuration, and usage guides.
132
66
 
133
67
  ## License
134
68
 
@@ -0,0 +1,25 @@
1
+ # DojoZero
2
+
3
+ A platform for running AI agents on realtime sport data to reason about game outcomes and make predictions. DojoZero provides an actor-based runtime with live data streams, agent orchestration, trial management, backtesting, and tracing. Currently supports NBA, NFL, and NCAA.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install dojozero
9
+ ```
10
+
11
+ Optional extras:
12
+
13
+ ```bash
14
+ pip install dojozero[alicloud] # Alibaba Cloud integration (OSS, SLS tracing)
15
+ pip install dojozero[redis] # Redis-backed data stores
16
+ pip install dojozero[ray] # Distributed execution via Ray
17
+ ```
18
+
19
+ ## Documentation
20
+
21
+ See the [full documentation](https://github.com/agentscope-ai/DojoZero/tree/main/docs) for setup, configuration, and usage guides.
22
+
23
+ ## License
24
+
25
+ MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dojozero"
7
- version = "0.2.0"
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"
@@ -278,7 +278,10 @@ def create_model(llm_config: LLMConfig) -> ChatModelBase:
278
278
  if model_type in "grok":
279
279
  grok_client_kwargs: dict[str, Any] = {"base_url": "https://api.x.ai/v1"}
280
280
  return OpenAIChatModel(
281
- model_name=model_name, api_key=api_key, client_kwargs=grok_client_kwargs
281
+ model_name=model_name,
282
+ api_key=api_key,
283
+ stream=False,
284
+ client_kwargs=grok_client_kwargs,
282
285
  )
283
286
  elif model_type == "openai":
284
287
  if base_url:
@@ -286,14 +289,36 @@ def create_model(llm_config: LLMConfig) -> ChatModelBase:
286
289
  else:
287
290
  client_kwargs = {}
288
291
  return OpenAIChatModel(
289
- model_name=model_name, api_key=api_key, client_kwargs=client_kwargs
292
+ model_name=model_name,
293
+ api_key=api_key,
294
+ stream=False,
295
+ client_kwargs=client_kwargs,
290
296
  )
291
297
  elif model_type == "dashscope":
292
- return DashScopeChatModel(model_name=model_name, api_key=api_key)
298
+ # qwen3.5-* series requires the MultiModalConversation endpoint even
299
+ # for text-only usage. The upstream agentscope SDK only auto-detects
300
+ # "-vl" / "qvq" suffixes/name patterns, so we explicitly opt-in here.
301
+ multimodality: bool | None = None
302
+ if model_name.startswith("qwen3.5"):
303
+ multimodality = True
304
+ return DashScopeChatModel(
305
+ model_name=model_name,
306
+ api_key=api_key,
307
+ multimodality=multimodality,
308
+ stream=False,
309
+ )
293
310
  elif model_type == "anthropic":
294
- return AnthropicChatModel(model_name=model_name, api_key=api_key)
311
+ return AnthropicChatModel(
312
+ model_name=model_name,
313
+ api_key=api_key,
314
+ stream=False,
315
+ )
295
316
  elif model_type == "gemini":
296
- return GeminiChatModel(model_name=model_name, api_key=api_key)
317
+ return GeminiChatModel(
318
+ model_name=model_name,
319
+ api_key=api_key,
320
+ stream=False,
321
+ )
297
322
  else:
298
323
  raise ValueError(f"Unknown model_type: {model_type}")
299
324
 
@@ -519,6 +519,12 @@ class ReplayMetaInfo:
519
519
  odds_update_indices: list[int] = field(
520
520
  default_factory=list
521
521
  ) # Indices of odds_update events
522
+ broker_state_update_indices: list[int] = field(
523
+ default_factory=list
524
+ ) # Indices of broker.state_update events
525
+ broker_final_stats_indices: list[int] = field(
526
+ default_factory=list
527
+ ) # Indices of broker.final_stats events
522
528
 
523
529
 
524
530
  @dataclass
@@ -266,18 +266,45 @@ _NFL_TEAMS: dict[str, TeamIdentity] = {
266
266
  _DEFAULT_TEAM_COLOR = "#666666"
267
267
 
268
268
 
269
- def _get_team_identity(tricode: str, league: str = "NBA") -> TeamIdentity:
269
+ def _get_team_identity(
270
+ tricode: str, league: str = "NBA", *, team_id: str = ""
271
+ ) -> TeamIdentity:
270
272
  """Get team identity by tricode.
271
273
 
272
274
  Returns TeamIdentity from static lookup. Falls back to a minimal identity
273
- with the tricode as the name if not found, but still generates a logo URL
274
- using the ESPN CDN pattern.
275
+ with the tricode as the name if not found.
276
+
277
+ For NBA/NFL, unknown tricodes use the ESPN CDN pattern
278
+ ``/teamlogos/{league}/500/{tricode}.png``.
279
+
280
+ NCAA mens basketball logos on ESPN use **numeric** team IDs in
281
+ ``/teamlogos/ncaa/500/{id}.png``; tricode-based URLs usually 404, so pass
282
+ ``team_id`` from trial metadata when available, otherwise ``logo_url`` is
283
+ left empty to avoid broken images.
275
284
  """
276
- teams = _NBA_TEAMS if league == "NBA" else _NFL_TEAMS
285
+ lu = league.upper()
286
+ if lu == "NBA":
287
+ teams = _NBA_TEAMS
288
+ elif lu == "NFL":
289
+ teams = _NFL_TEAMS
290
+ else:
291
+ teams = {}
277
292
  if tricode in teams:
278
293
  return teams[tricode]
279
294
 
280
- # Generate logo URL dynamically for teams not in static lookup
295
+ if lu == "NCAA":
296
+ logo_url = ""
297
+ tid = (team_id or "").strip()
298
+ if tid.isdigit():
299
+ logo_url = f"https://a.espncdn.com/i/teamlogos/ncaa/500/{tid}.png"
300
+ return TeamIdentity(
301
+ name=tricode,
302
+ tricode=tricode,
303
+ team_id=tid,
304
+ color=_DEFAULT_TEAM_COLOR,
305
+ logo_url=logo_url,
306
+ )
307
+
281
308
  league_lower = league.lower()
282
309
  logo_url = (
283
310
  f"https://a.espncdn.com/i/teamlogos/{league_lower}/500/{tricode.lower()}.png"
@@ -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(
@@ -1473,6 +1473,8 @@ class TrialReplayController:
1473
1473
  The snapshot includes:
1474
1474
  - Meta events: agent_initialize, game_initialize, game_start
1475
1475
  - Latest odds_update before the target position
1476
+ - Latest broker snapshot before the target position
1477
+ (prefer final_stats over state_update)
1476
1478
  - Recent play items up to and including the target
1477
1479
 
1478
1480
  Args:
@@ -1497,6 +1499,15 @@ class TrialReplayController:
1497
1499
  # Collect item indices to include in snapshot (use set to avoid duplicates)
1498
1500
  snapshot_indices: set[int] = set()
1499
1501
 
1502
+ def _latest_index_before(indices: list[int]) -> int | None:
1503
+ latest_idx = None
1504
+ for idx in indices:
1505
+ if idx <= target_item_index:
1506
+ latest_idx = idx
1507
+ else:
1508
+ break
1509
+ return latest_idx
1510
+
1500
1511
  # 1. Add meta events (agent_initialize, game_initialize, game_start)
1501
1512
  if self.meta.agent_initialize_item_index is not None:
1502
1513
  snapshot_indices.add(self.meta.agent_initialize_item_index)
@@ -1507,17 +1518,21 @@ class TrialReplayController:
1507
1518
 
1508
1519
  # 2. Find and add the latest odds_update before target_item_index
1509
1520
  if self.meta.odds_update_indices:
1510
- # Find the largest odds_update index that is <= target_item_index
1511
- latest_odds_idx = None
1512
- for idx in self.meta.odds_update_indices:
1513
- if idx <= target_item_index:
1514
- latest_odds_idx = idx
1515
- else:
1516
- break # odds_update_indices are in chronological order
1521
+ latest_odds_idx = _latest_index_before(self.meta.odds_update_indices)
1517
1522
  if latest_odds_idx is not None:
1518
1523
  snapshot_indices.add(latest_odds_idx)
1519
1524
 
1520
- # 3. Add recent items up to and including the target
1525
+ # 3. Add the latest broker snapshot before target_item_index.
1526
+ # final_stats is preferred because it contains the ranking/statistics payload.
1527
+ latest_broker_idx = _latest_index_before(self.meta.broker_final_stats_indices)
1528
+ if latest_broker_idx is None:
1529
+ latest_broker_idx = _latest_index_before(
1530
+ self.meta.broker_state_update_indices
1531
+ )
1532
+ if latest_broker_idx is not None:
1533
+ snapshot_indices.add(latest_broker_idx)
1534
+
1535
+ # 4. Add recent items up to and including the target
1521
1536
  start = max(0, target_item_index + 1 - self.snapshot_size)
1522
1537
  for i in range(start, target_item_index + 1):
1523
1538
  snapshot_indices.add(i)
@@ -46,6 +46,7 @@ TRIAL_INFO_OPERATION_NAMES = [
46
46
  "event.game_result",
47
47
  "event.nba_game_update",
48
48
  "event.nfl_game_update",
49
+ "event.ncaa_game_update",
49
50
  ]
50
51
 
51
52
  # Tag used by DojoZero to correlate all spans for a trial (see TrialOrchestrator, create_span_from_event).
@@ -258,15 +259,25 @@ def _resolve_team_identity(
258
259
  fallback_tricode: str,
259
260
  fallback_name: str,
260
261
  league: str,
262
+ *,
263
+ team_id: str = "",
261
264
  ) -> TeamIdentity:
262
265
  """Resolve a team to a TeamIdentity, applying fallbacks as needed."""
263
266
  if isinstance(team, TeamIdentity) and team:
264
- # Ensure tricode is populated
267
+ resolved = team
265
268
  if not team.tricode and fallback_tricode:
266
- return team.model_copy(update={"tricode": fallback_tricode})
267
- return team
269
+ resolved = resolved.model_copy(update={"tricode": fallback_tricode})
270
+ if league.upper() == "NCAA" and not resolved.logo_url:
271
+ tid = (resolved.team_id or team_id or "").strip()
272
+ if tid.isdigit():
273
+ resolved = resolved.model_copy(
274
+ update={
275
+ "logo_url": f"https://a.espncdn.com/i/teamlogos/ncaa/500/{tid}.png"
276
+ }
277
+ )
278
+ return resolved
268
279
  # Fallback to static lookup, then override name if provided
269
- identity = _get_team_identity(fallback_tricode, league)
280
+ identity = _get_team_identity(fallback_tricode, league, team_id=team_id)
270
281
  if fallback_name and fallback_name != identity.name:
271
282
  return identity.model_copy(update={"name": fallback_name})
272
283
  return identity
@@ -433,17 +444,33 @@ async def _extract_games_from_trials(
433
444
  game_init = trial_info.get("game_init")
434
445
  if isinstance(game_init, GameInitializeEvent):
435
446
  home_team = _resolve_team_identity(
436
- game_init.home_team, home_tricode, "", league
447
+ game_init.home_team,
448
+ home_tricode,
449
+ "",
450
+ league,
451
+ team_id=str(metadata.get("home_team_id", "") or ""),
437
452
  )
438
453
  away_team = _resolve_team_identity(
439
- game_init.away_team, away_tricode, "", league
454
+ game_init.away_team,
455
+ away_tricode,
456
+ "",
457
+ league,
458
+ team_id=str(metadata.get("away_team_id", "") or ""),
440
459
  )
441
460
  else:
442
461
  home_team = _resolve_team_identity(
443
- "", home_tricode, metadata.get("home_team_name", ""), league
462
+ "",
463
+ home_tricode,
464
+ metadata.get("home_team_name", ""),
465
+ league,
466
+ team_id=str(metadata.get("home_team_id", "") or ""),
444
467
  )
445
468
  away_team = _resolve_team_identity(
446
- "", away_tricode, metadata.get("away_team_name", ""), league
469
+ "",
470
+ away_tricode,
471
+ metadata.get("away_team_name", ""),
472
+ league,
473
+ team_id=str(metadata.get("away_team_id", "") or ""),
447
474
  )
448
475
 
449
476
  # Fetch bets for live games only (performance optimization)
@@ -804,7 +831,7 @@ def _compute_leaderboard_from_spans(
804
831
  spans_by_trial: dict[str, list[SpanData]],
805
832
  agent_info_cache: dict[str, AgentInfo],
806
833
  trial_ids: list[str] | None = None,
807
- limit: int = 20,
834
+ limit: int | None = None,
808
835
  ) -> list[LeaderboardEntry]:
809
836
  """Compute agent leaderboard from pre-fetched spans.
810
837
 
@@ -815,7 +842,7 @@ def _compute_leaderboard_from_spans(
815
842
  spans_by_trial: Pre-fetched spans grouped by trial_id
816
843
  agent_info_cache: Pre-populated agent info cache (agent_id -> AgentInfo)
817
844
  trial_ids: Optional list to filter which trials to process (None = all)
818
- limit: Maximum entries to return
845
+ limit: Maximum entries to return (None = all)
819
846
 
820
847
  Returns:
821
848
  List of agents sorted by winnings (highest first)
@@ -910,9 +937,9 @@ def _compute_leaderboard_from_spans(
910
937
 
911
938
  # Sort by winnings (descending) and add rank
912
939
  leaderboard.sort(key=lambda x: x.winnings, reverse=True)
940
+ entries = leaderboard[:limit] if limit is not None else leaderboard
913
941
  ranked = [
914
- entry.model_copy(update={"rank": i + 1})
915
- for i, entry in enumerate(leaderboard[:limit])
942
+ entry.model_copy(update={"rank": i + 1}) for i, entry in enumerate(entries)
916
943
  ]
917
944
 
918
945
  return ranked
@@ -922,7 +949,7 @@ async def _compute_leaderboard(
922
949
  trace_reader: TraceReader,
923
950
  trial_ids: list[str],
924
951
  cache: "LandingPageCache | None" = None,
925
- limit: int = 20,
952
+ limit: int | None = None,
926
953
  ) -> list[LeaderboardEntry]:
927
954
  """Compute agent leaderboard (on-demand version).
928
955
 
@@ -962,6 +989,7 @@ def _compute_replay_meta(
962
989
  - play_item_indices: mapping from play_index to item_index
963
990
  - periods: list of PeriodInfo with play counts per period
964
991
  - meta event indices: agent_initialize, game_initialize, game_start, odds_update
992
+ - broker snapshot indices: state_update and final_stats
965
993
 
966
994
  Args:
967
995
  items: List of serialized span dicts with "category" and "data" keys
@@ -979,6 +1007,8 @@ def _compute_replay_meta(
979
1007
  game_initialize_item_index: int | None = None
980
1008
  game_start_item_index: int | None = None
981
1009
  odds_update_indices: list[int] = []
1010
+ broker_state_update_indices: list[int] = []
1011
+ broker_final_stats_indices: list[int] = []
982
1012
 
983
1013
  current_period: int = 1 # Default period
984
1014
 
@@ -995,6 +1025,10 @@ def _compute_replay_meta(
995
1025
  game_start_item_index = item_index
996
1026
  elif category == "odds_update":
997
1027
  odds_update_indices.append(item_index)
1028
+ elif category == "state_update":
1029
+ broker_state_update_indices.append(item_index)
1030
+ elif category == "final_stats":
1031
+ broker_final_stats_indices.append(item_index)
998
1032
 
999
1033
  # Track core category items (plays)
1000
1034
  if category in core_categories:
@@ -1031,6 +1065,8 @@ def _compute_replay_meta(
1031
1065
  game_initialize_item_index=game_initialize_item_index,
1032
1066
  game_start_item_index=game_start_item_index,
1033
1067
  odds_update_indices=odds_update_indices,
1068
+ broker_state_update_indices=broker_state_update_indices,
1069
+ broker_final_stats_indices=broker_final_stats_indices,
1034
1070
  )
1035
1071
 
1036
1072
 
@@ -516,12 +516,7 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
516
516
  summary_request = self._build_summary_request()
517
517
  if summary_request:
518
518
  model = self._react_agent.model
519
- original_stream = model.stream
520
- model.stream = False
521
- try:
522
- resp = await model(summary_request)
523
- finally:
524
- model.stream = original_stream
519
+ resp = await model(summary_request)
525
520
  resp_content = getattr(resp, "content", None)
526
521
  events_summary, _ = _parse_response_content(resp_content)
527
522
  if not events_summary:
@@ -262,6 +262,11 @@ class BrokerOperator(OperatorBase, Operator[BrokerOperatorConfig]):
262
262
  len(self._bets),
263
263
  )
264
264
 
265
+ @property
266
+ def has_unsettled_bets(self) -> bool:
267
+ """Return True if there are active (unsettled) bets."""
268
+ return any(bet.status == BetStatus.ACTIVE for bet in self._bets.values())
269
+
265
270
  def ensure_event_initialized(
266
271
  self,
267
272
  event_id: str,
@@ -67,6 +67,10 @@ class BettingTrialMetadata(BaseTrialMetadata):
67
67
  away_team_name: str
68
68
  game_date: str
69
69
 
70
+ # ESPN team IDs (for Arena logos, APIs; optional, default from game lookup)
71
+ home_team_id: str = ""
72
+ away_team_id: str = ""
73
+
70
74
  # Polymarket (optional)
71
75
  market_url: str | None = None
72
76
 
@@ -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
  )