dojozero 0.2.0__tar.gz → 0.2.1__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 (171) hide show
  1. {dojozero-0.2.0 → dojozero-0.2.1}/.gitignore +4 -2
  2. {dojozero-0.2.0 → dojozero-0.2.1}/PKG-INFO +5 -71
  3. dojozero-0.2.1/README.md +25 -0
  4. {dojozero-0.2.0 → dojozero-0.2.1}/pyproject.toml +1 -1
  5. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/agents/_config.py +30 -5
  6. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_cache.py +6 -0
  7. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_constants.py +32 -5
  8. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_server.py +23 -8
  9. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_utils.py +44 -8
  10. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_agent.py +1 -6
  11. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_broker.py +5 -0
  12. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_metadata.py +4 -0
  13. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/cli.py +56 -4
  14. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_models.py +2 -0
  15. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_tracing.py +51 -23
  16. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_scheduler.py +84 -4
  17. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_server.py +20 -9
  18. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_trial_manager.py +152 -4
  19. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/__init__.py +6 -0
  20. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_hub.py +26 -0
  21. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/_store.py +1 -0
  22. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nba/_trial.py +2 -0
  23. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ncaa/_trial.py +2 -0
  24. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nfl/_trial.py +2 -0
  25. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/sync_service/_sync.py +27 -7
  26. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_agent_configs.py +105 -0
  27. dojozero-0.2.1/tests/test_arena_replay_seek.py +60 -0
  28. dojozero-0.2.0/README.md +0 -91
  29. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/__init__.py +0 -0
  30. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/_optional_alicloud.py +0 -0
  31. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/agents/__init__.py +0 -0
  32. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/agents/_social_board.py +0 -0
  33. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/agents/_toolkit.py +0 -0
  34. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/agents/_trial_utils.py +0 -0
  35. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/__init__.py +0 -0
  36. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_config.py +0 -0
  37. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_endpoints.py +0 -0
  38. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_models.py +0 -0
  39. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/_redis_reader.py +0 -0
  40. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/arena_server/get_snapshot.sh +0 -0
  41. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/__init__.py +0 -0
  42. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_config.py +0 -0
  43. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_formatters.py +0 -0
  44. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/betting/_models.py +0 -0
  45. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/__init__.py +0 -0
  46. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_actors.py +0 -0
  47. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_base.py +0 -0
  48. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_credentials.py +0 -0
  49. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_filesystem_orchestrator_store.py +0 -0
  50. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_metadata.py +0 -0
  51. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_registry.py +0 -0
  52. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_runtime.py +0 -0
  53. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_trial_orchestrator.py +0 -0
  54. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/core/_types.py +0 -0
  55. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/__init__.py +0 -0
  56. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_game_discovery.py +0 -0
  57. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_gateway_routing.py +0 -0
  58. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_jsonl_utils.py +0 -0
  59. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/dashboard_server/_types.py +0 -0
  60. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_backtest.py +0 -0
  61. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_config.py +0 -0
  62. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_context.py +0 -0
  63. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_factory.py +0 -0
  64. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_game_info.py +0 -0
  65. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_models.py +0 -0
  66. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_processors.py +0 -0
  67. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_stores.py +0 -0
  68. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_streams.py +0 -0
  69. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_subscriptions.py +0 -0
  70. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/_utils.py +0 -0
  71. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/__init__.py +0 -0
  72. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/_api.py +0 -0
  73. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/_state_tracker.py +0 -0
  74. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/_stats_events.py +0 -0
  75. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/_stats_fetcher.py +0 -0
  76. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/espn/_utils.py +0 -0
  77. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/__init__.py +0 -0
  78. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_api.py +0 -0
  79. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_events.py +0 -0
  80. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_factory.py +0 -0
  81. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_state_tracker.py +0 -0
  82. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_store.py +0 -0
  83. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nba/_utils.py +0 -0
  84. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/__init__.py +0 -0
  85. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_api.py +0 -0
  86. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_events.py +0 -0
  87. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_factory.py +0 -0
  88. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_state_tracker.py +0 -0
  89. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_store.py +0 -0
  90. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/ncaa/_utils.py +0 -0
  91. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/__init__.py +0 -0
  92. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_api.py +0 -0
  93. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_events.py +0 -0
  94. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_factory.py +0 -0
  95. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_state_tracker.py +0 -0
  96. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_store.py +0 -0
  97. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/nfl/_utils.py +0 -0
  98. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/__init__.py +0 -0
  99. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/_api.py +0 -0
  100. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/_events.py +0 -0
  101. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/_factory.py +0 -0
  102. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/polymarket/_models.py +0 -0
  103. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/__init__.py +0 -0
  104. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_api.py +0 -0
  105. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_events.py +0 -0
  106. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_factory.py +0 -0
  107. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_formatters.py +0 -0
  108. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_store.py +0 -0
  109. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/socialmedia/_watchlist.py +0 -0
  110. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/__init__.py +0 -0
  111. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/_api.py +0 -0
  112. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/_events.py +0 -0
  113. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/_factory.py +0 -0
  114. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/_formatters.py +0 -0
  115. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/data/websearch/_store.py +0 -0
  116. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/__init__.py +0 -0
  117. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_adapter.py +0 -0
  118. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_auth.py +0 -0
  119. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_models.py +0 -0
  120. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_rate_limit.py +0 -0
  121. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_server.py +0 -0
  122. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/gateway/_sse.py +0 -0
  123. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nba/__init__.py +0 -0
  124. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nba/_agent.py +0 -0
  125. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nba/_datastream.py +0 -0
  126. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nba/_formatters.py +0 -0
  127. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ncaa/__init__.py +0 -0
  128. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ncaa/_agent.py +0 -0
  129. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ncaa/_datastream.py +0 -0
  130. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ncaa/_formatters.py +0 -0
  131. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nfl/__init__.py +0 -0
  132. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nfl/_agent.py +0 -0
  133. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nfl/_datastream.py +0 -0
  134. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/nfl/_formatters.py +0 -0
  135. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ray_runtime/__init__.py +0 -0
  136. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/ray_runtime/_impl.py +0 -0
  137. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/sync_service/__init__.py +0 -0
  138. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/sync_service/_redis_client.py +0 -0
  139. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/sync_service/main.py +0 -0
  140. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/utils/__init__.py +0 -0
  141. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/utils/oss.py +0 -0
  142. {dojozero-0.2.0 → dojozero-0.2.1}/src/dojozero/utils/time.py +0 -0
  143. {dojozero-0.2.0 → dojozero-0.2.1}/tests/conftest.py +0 -0
  144. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_agent_event_throttle.py +0 -0
  145. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_arena_event_deserialization.py +0 -0
  146. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_arena_span_grouping.py +0 -0
  147. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_betting_formatters.py +0 -0
  148. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_broker.py +0 -0
  149. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_cli_agents.py +0 -0
  150. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_dashboard.py +0 -0
  151. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_data_hub.py +0 -0
  152. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_data_nba.py +0 -0
  153. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_data_nfl.py +0 -0
  154. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_data_polymarket.py +0 -0
  155. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_game_state_tracker.py +0 -0
  156. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_gateway.py +0 -0
  157. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_metadata.py +0 -0
  158. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_nba_formatters.py +0 -0
  159. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_nba_moneyline_agent.py +0 -0
  160. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_nfl_formatters.py +0 -0
  161. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_nfl_moneyline_agent.py +0 -0
  162. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_oss.py +0 -0
  163. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_registry.py +0 -0
  164. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_scheduler.py +0 -0
  165. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_social_board.py +0 -0
  166. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_socialmedia_events.py +0 -0
  167. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_span_models.py +0 -0
  168. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_stats_insight_events.py +0 -0
  169. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_subscriptions.py +0 -0
  170. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_websearch_events.py +0 -0
  171. {dojozero-0.2.0 → dojozero-0.2.1}/tests/test_websearch_formatters.py +0 -0
@@ -171,5 +171,7 @@ dojozero-store
171
171
  package-lock.json
172
172
 
173
173
  # logs
174
- /logs/node_modules/
175
- node_modules/
174
+ logs/
175
+
176
+ # node modules
177
+ node_modules/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dojozero
3
- Version: 0.2.0
3
+ Version: 0.2.1
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.1"
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"
@@ -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)
@@ -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
 
@@ -339,7 +339,9 @@ def _build_parser() -> argparse.ArgumentParser:
339
339
  default=[],
340
340
  help="Path or glob pattern for trial source YAML files (repeatable). "
341
341
  "Enables automatic scheduling for the specified sports/scenarios. "
342
- "Requires filesystem store to be configured.",
342
+ "Requires filesystem store to be configured. "
343
+ "Optional env DOJOZERO_MAX_DAILY_GAMES overrides config.max_daily_games "
344
+ "for every loaded source (integer; 0 = unlimited).",
343
345
  )
344
346
  serve_parser.add_argument(
345
347
  "--no-auto-resume",
@@ -347,6 +349,13 @@ def _build_parser() -> argparse.ArgumentParser:
347
349
  action="store_true",
348
350
  help="Disable automatic resuming of interrupted trials from previous shutdown.",
349
351
  )
352
+ serve_parser.add_argument(
353
+ "--no-scheduler",
354
+ dest="no_scheduler",
355
+ action="store_true",
356
+ help="Disable the trial scheduler entirely. No trial sources will be loaded "
357
+ "or auto-resolved, and persisted sources will be ignored.",
358
+ )
350
359
  serve_parser.add_argument(
351
360
  "--stale-threshold-hours",
352
361
  dest="stale_threshold_hours",
@@ -1881,6 +1890,29 @@ def _expand_compact_trial_source(
1881
1890
  config.setdefault(key, value)
1882
1891
 
1883
1892
 
1893
+ def _parse_max_daily_games_env_override() -> int | None:
1894
+ """Parse ``DOJOZERO_MAX_DAILY_GAMES`` for ``serve`` trial source loading.
1895
+
1896
+ Returns:
1897
+ ``None`` if unset or empty (YAML values are used as-is).
1898
+
1899
+ Note:
1900
+ ``0`` means unlimited, consistent with scheduler / trial source YAML.
1901
+ """
1902
+ raw = os.environ.get("DOJOZERO_MAX_DAILY_GAMES", "").strip()
1903
+ if not raw:
1904
+ return None
1905
+ try:
1906
+ value = int(raw, 10)
1907
+ except ValueError as e:
1908
+ raise DojoZeroCLIError(
1909
+ f"DOJOZERO_MAX_DAILY_GAMES must be an integer, got {raw!r}"
1910
+ ) from e
1911
+ if value < 0:
1912
+ raise DojoZeroCLIError(f"DOJOZERO_MAX_DAILY_GAMES must be >= 0, got {value}")
1913
+ return value
1914
+
1915
+
1884
1916
  def _load_trial_source_from_yaml(path: Path) -> InitialTrialSourceDict:
1885
1917
  """Load a trial source configuration from a YAML file.
1886
1918
 
@@ -1956,13 +1988,17 @@ async def _serve_command(args: argparse.Namespace) -> int:
1956
1988
  trace_backend = getattr(args, "trace_backend", None)
1957
1989
  trace_ingest_endpoint = getattr(args, "trace_ingest_endpoint", None)
1958
1990
  service_name = getattr(args, "service_name", "dojozero")
1991
+ no_scheduler = getattr(args, "no_scheduler", False)
1959
1992
  trial_source_files: list[str] = getattr(args, "trial_sources", []) or []
1960
1993
  auto_resume = not getattr(args, "no_auto_resume", False)
1961
1994
  stale_threshold_hours = getattr(args, "stale_threshold_hours", 24.0)
1962
1995
 
1963
- # Auto-resolve trial sources from DOJOZERO_ENV / SIGMA_APP_STAGE if not
1964
- # explicitly provided via --trial-source.
1965
- if not trial_source_files:
1996
+ if no_scheduler:
1997
+ trial_source_files = []
1998
+ LOGGER.info("Scheduler disabled via --no-scheduler")
1999
+ elif not trial_source_files:
2000
+ # Auto-resolve trial sources from DOJOZERO_ENV / SIGMA_APP_STAGE if not
2001
+ # explicitly provided via --trial-source.
1966
2002
  env_tier = _resolve_env_tier()
1967
2003
  trial_sources_dir = Path("trial_sources") / env_tier
1968
2004
  if trial_sources_dir.is_dir():
@@ -2010,6 +2046,21 @@ async def _serve_command(args: argparse.Namespace) -> int:
2010
2046
  source_data.get("source_id"),
2011
2047
  )
2012
2048
 
2049
+ env_max_daily = _parse_max_daily_games_env_override()
2050
+ if env_max_daily is not None:
2051
+ if initial_trial_sources:
2052
+ for source_data in initial_trial_sources:
2053
+ source_data["config"]["max_daily_games"] = env_max_daily
2054
+ LOGGER.info(
2055
+ "DOJOZERO_MAX_DAILY_GAMES=%s overrides max_daily_games for %d trial source(s)",
2056
+ env_max_daily,
2057
+ len(initial_trial_sources),
2058
+ )
2059
+ else:
2060
+ LOGGER.warning(
2061
+ "DOJOZERO_MAX_DAILY_GAMES is set but no trial sources were loaded; ignoring override"
2062
+ )
2063
+
2013
2064
  LOGGER.info("Starting Dashboard Server at http://%s:%d", host, port)
2014
2065
  LOGGER.info("Trial API: http://%s:%d/api/trials", host, port)
2015
2066
  LOGGER.info("Trial Source API: http://%s:%d/api/trial-sources", host, port)
@@ -2072,6 +2123,7 @@ async def _serve_command(args: argparse.Namespace) -> int:
2072
2123
  stale_threshold_hours=stale_threshold_hours,
2073
2124
  enable_gateway=enable_gateway,
2074
2125
  authenticator=authenticator,
2126
+ no_scheduler=no_scheduler,
2075
2127
  )
2076
2128
  return 0
2077
2129
 
@@ -277,8 +277,10 @@ _OPERATION_NAME_MAP: dict[str, str] = {
277
277
  _SPORT_UNIFY_MAP: dict[str, str] = {
278
278
  "nba_play": "play",
279
279
  "nfl_play": "play",
280
+ "ncaa_play": "play",
280
281
  "nba_game_update": "game_update",
281
282
  "nfl_game_update": "game_update",
283
+ "ncaa_game_update": "game_update",
282
284
  }
283
285
 
284
286