dojozero 0.3.0__tar.gz → 0.4.0__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 (215) hide show
  1. {dojozero-0.3.0 → dojozero-0.4.0}/PKG-INFO +2 -2
  2. {dojozero-0.3.0 → dojozero-0.4.0}/pyproject.toml +2 -2
  3. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_config.py +16 -4
  4. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_cache.py +2 -2
  5. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_server.py +10 -3
  6. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_utils.py +159 -21
  7. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/__init__.py +32 -17
  8. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_agent.py +105 -62
  9. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_broker.py +130 -27
  10. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_config.py +53 -13
  11. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_metadata.py +6 -1
  12. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_models.py +203 -8
  13. dojozero-0.4.0/src/dojozero/betting/_prediction_broker.py +1225 -0
  14. dojozero-0.4.0/src/dojozero/betting/_protocol.py +90 -0
  15. dojozero-0.4.0/src/dojozero/betting/_scoring.py +94 -0
  16. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/cli.py +93 -25
  17. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_models.py +4 -0
  18. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_tracing.py +142 -0
  19. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_game_discovery.py +188 -5
  20. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_scheduler.py +74 -6
  21. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_server.py +209 -15
  22. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_trial_manager.py +106 -53
  23. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/__init__.py +13 -0
  24. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_models.py +1 -1
  25. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_sls_source.py +11 -1
  26. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_api.py +214 -8
  27. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_store.py +28 -29
  28. dojozero-0.4.0/src/dojozero/data/world_cup/__init__.py +37 -0
  29. dojozero-0.4.0/src/dojozero/data/world_cup/_api.py +100 -0
  30. dojozero-0.4.0/src/dojozero/data/world_cup/_constants.py +45 -0
  31. dojozero-0.4.0/src/dojozero/data/world_cup/_events.py +162 -0
  32. dojozero-0.4.0/src/dojozero/data/world_cup/_factory.py +52 -0
  33. dojozero-0.4.0/src/dojozero/data/world_cup/_state_tracker.py +164 -0
  34. dojozero-0.4.0/src/dojozero/data/world_cup/_store.py +776 -0
  35. dojozero-0.4.0/src/dojozero/data/world_cup/_utils.py +209 -0
  36. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/__init__.py +9 -0
  37. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_adapter.py +274 -69
  38. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_auth.py +14 -7
  39. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_models.py +88 -1
  40. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_server.py +247 -125
  41. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_trial.py +14 -2
  42. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_trial.py +14 -1
  43. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_trial.py +14 -2
  44. dojozero-0.4.0/src/dojozero/world_cup/__init__.py +8 -0
  45. dojozero-0.4.0/src/dojozero/world_cup/_agent.py +60 -0
  46. dojozero-0.4.0/src/dojozero/world_cup/_datastream.py +114 -0
  47. dojozero-0.4.0/src/dojozero/world_cup/_formatters.py +190 -0
  48. dojozero-0.4.0/src/dojozero/world_cup/_trial.py +475 -0
  49. dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/plays_735949_aet.json +1 -0
  50. dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/plays_735958_final.json +1 -0
  51. dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/scoreboard_20250713.json +1 -0
  52. dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/summary_735949_aet.json +1 -0
  53. dojozero-0.4.0/tests/fixtures/world_cup/cwc_2025/summary_735958_final.json +1 -0
  54. dojozero-0.4.0/tests/fixtures/world_cup/plays_684665.json +1 -0
  55. dojozero-0.4.0/tests/fixtures/world_cup/scoreboard_conmebol_20250909.json +1 -0
  56. dojozero-0.4.0/tests/fixtures/world_cup/scoreboard_fifa_world.json +1 -0
  57. dojozero-0.4.0/tests/fixtures/world_cup/summary_684665.json +1 -0
  58. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_agent_configs.py +14 -0
  59. dojozero-0.4.0/tests/test_arena_cache.py +26 -0
  60. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_broker.py +254 -2
  61. dojozero-0.4.0/tests/test_cli_sls_polling.py +323 -0
  62. dojozero-0.4.0/tests/test_dashboard_games_endpoint.py +79 -0
  63. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_polymarket.py +208 -1
  64. dojozero-0.4.0/tests/test_data_world_cup.py +689 -0
  65. dojozero-0.4.0/tests/test_dual_mode_trial.py +574 -0
  66. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_gateway.py +104 -9
  67. dojozero-0.4.0/tests/test_genai_tracing.py +196 -0
  68. dojozero-0.4.0/tests/test_materialization_tracker.py +306 -0
  69. dojozero-0.4.0/tests/test_prediction_broker.py +780 -0
  70. dojozero-0.4.0/tests/test_scoring.py +227 -0
  71. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_sls_source.py +91 -1
  72. dojozero-0.4.0/tests/test_sls_trace_reader_progress.py +302 -0
  73. dojozero-0.4.0/tests/test_world_cup_agent.py +38 -0
  74. dojozero-0.4.0/tests/test_world_cup_formatters.py +255 -0
  75. dojozero-0.4.0/tests/test_world_cup_scheduling.py +535 -0
  76. dojozero-0.4.0/tests/test_world_cup_trial.py +381 -0
  77. {dojozero-0.3.0 → dojozero-0.4.0}/.gitignore +0 -0
  78. {dojozero-0.3.0 → dojozero-0.4.0}/README.md +0 -0
  79. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/__init__.py +0 -0
  80. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/_optional_alicloud.py +0 -0
  81. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/__init__.py +0 -0
  82. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_social_board.py +0 -0
  83. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_toolkit.py +0 -0
  84. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/agents/_trial_utils.py +0 -0
  85. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/__init__.py +0 -0
  86. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_config.py +0 -0
  87. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_constants.py +0 -0
  88. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_endpoints.py +0 -0
  89. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_models.py +0 -0
  90. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/_redis_reader.py +0 -0
  91. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/arena_server/get_snapshot.sh +0 -0
  92. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/betting/_formatters.py +0 -0
  93. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/__init__.py +0 -0
  94. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_actors.py +0 -0
  95. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_base.py +0 -0
  96. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_credentials.py +0 -0
  97. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_filesystem_orchestrator_store.py +0 -0
  98. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_metadata.py +0 -0
  99. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_registry.py +0 -0
  100. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_runtime.py +0 -0
  101. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_trial_orchestrator.py +0 -0
  102. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/core/_types.py +0 -0
  103. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/__init__.py +0 -0
  104. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_cluster.py +0 -0
  105. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_gateway_routing.py +0 -0
  106. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_jsonl_utils.py +0 -0
  107. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/dashboard_server/_types.py +0 -0
  108. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_backtest.py +0 -0
  109. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_config.py +0 -0
  110. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_context.py +0 -0
  111. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_factory.py +0 -0
  112. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_game_info.py +0 -0
  113. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_hub.py +0 -0
  114. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_processors.py +0 -0
  115. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_stores.py +0 -0
  116. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_streams.py +0 -0
  117. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_subscriptions.py +0 -0
  118. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/_utils.py +0 -0
  119. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/__init__.py +0 -0
  120. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_api.py +0 -0
  121. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_state_tracker.py +0 -0
  122. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_stats_events.py +0 -0
  123. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_stats_fetcher.py +0 -0
  124. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/espn/_utils.py +0 -0
  125. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/__init__.py +0 -0
  126. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_api.py +0 -0
  127. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_events.py +0 -0
  128. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_factory.py +0 -0
  129. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_state_tracker.py +0 -0
  130. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_store.py +0 -0
  131. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nba/_utils.py +0 -0
  132. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/__init__.py +0 -0
  133. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_api.py +0 -0
  134. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_events.py +0 -0
  135. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_factory.py +0 -0
  136. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_state_tracker.py +0 -0
  137. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_store.py +0 -0
  138. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/ncaa/_utils.py +0 -0
  139. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/__init__.py +0 -0
  140. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_api.py +0 -0
  141. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_events.py +0 -0
  142. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_factory.py +0 -0
  143. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_state_tracker.py +0 -0
  144. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_store.py +0 -0
  145. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/nfl/_utils.py +0 -0
  146. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/__init__.py +0 -0
  147. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_events.py +0 -0
  148. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_factory.py +0 -0
  149. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/polymarket/_models.py +0 -0
  150. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/__init__.py +0 -0
  151. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_api.py +0 -0
  152. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_events.py +0 -0
  153. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_factory.py +0 -0
  154. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_formatters.py +0 -0
  155. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_store.py +0 -0
  156. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/socialmedia/_watchlist.py +0 -0
  157. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/__init__.py +0 -0
  158. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_api.py +0 -0
  159. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_events.py +0 -0
  160. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_factory.py +0 -0
  161. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_formatters.py +0 -0
  162. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/data/websearch/_store.py +0 -0
  163. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_rate_limit.py +0 -0
  164. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/gateway/_sse.py +0 -0
  165. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/__init__.py +0 -0
  166. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_agent.py +0 -0
  167. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_datastream.py +0 -0
  168. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nba/_formatters.py +0 -0
  169. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/__init__.py +0 -0
  170. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_agent.py +0 -0
  171. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_datastream.py +0 -0
  172. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ncaa/_formatters.py +0 -0
  173. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/__init__.py +0 -0
  174. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_agent.py +0 -0
  175. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_datastream.py +0 -0
  176. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/nfl/_formatters.py +0 -0
  177. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ray_runtime/__init__.py +0 -0
  178. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/ray_runtime/_impl.py +0 -0
  179. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/__init__.py +0 -0
  180. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/_redis_client.py +0 -0
  181. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/_sync.py +0 -0
  182. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/sync_service/main.py +0 -0
  183. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/__init__.py +0 -0
  184. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/oss.py +0 -0
  185. {dojozero-0.3.0 → dojozero-0.4.0}/src/dojozero/utils/time.py +0 -0
  186. {dojozero-0.3.0 → dojozero-0.4.0}/tests/conftest.py +0 -0
  187. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_agent_event_throttle.py +0 -0
  188. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_event_deserialization.py +0 -0
  189. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_replay_seek.py +0 -0
  190. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_arena_span_grouping.py +0 -0
  191. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_backtest_cache_naming.py +0 -0
  192. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_betting_formatters.py +0 -0
  193. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cli_agents.py +0 -0
  194. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cli_dev_span_start.py +0 -0
  195. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_cluster.py +0 -0
  196. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_dashboard.py +0 -0
  197. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_hub.py +0 -0
  198. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_nba.py +0 -0
  199. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_data_nfl.py +0 -0
  200. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_game_state_tracker.py +0 -0
  201. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_metadata.py +0 -0
  202. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nba_formatters.py +0 -0
  203. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nba_moneyline_agent.py +0 -0
  204. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nfl_formatters.py +0 -0
  205. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_nfl_moneyline_agent.py +0 -0
  206. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_oss.py +0 -0
  207. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_registry.py +0 -0
  208. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_scheduler.py +0 -0
  209. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_social_board.py +0 -0
  210. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_socialmedia_events.py +0 -0
  211. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_span_models.py +0 -0
  212. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_stats_insight_events.py +0 -0
  213. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_subscriptions.py +0 -0
  214. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_websearch_events.py +0 -0
  215. {dojozero-0.3.0 → dojozero-0.4.0}/tests/test_websearch_formatters.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dojozero
3
- Version: 0.3.0
3
+ Version: 0.4.0
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
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
18
  Requires-Python: >=3.11
19
- Requires-Dist: agentscope[gemini]>=1.0.14
19
+ Requires-Dist: agentscope[gemini]<2,>=1.0.14
20
20
  Requires-Dist: aiohttp>=3.9.0
21
21
  Requires-Dist: dashscope>=1.0.0
22
22
  Requires-Dist: fastapi>=0.128.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dojozero"
7
- version = "0.3.0"
7
+ version = "0.4.0"
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"
@@ -26,7 +26,7 @@ dependencies = [
26
26
  "PyYAML>=6.0",
27
27
  "pydantic>=2.12.4",
28
28
  "aiohttp>=3.9.0",
29
- "agentscope[gemini]>=1.0.14",
29
+ "agentscope[gemini]>=1.0.14,<2",
30
30
  "py-clob-client>=0.1.0",
31
31
  "fastapi>=0.128.0",
32
32
  "uvicorn>=0.40.0",
@@ -122,8 +122,14 @@ def load_persona_config(config_path: str | Path) -> PersonaConfig:
122
122
  Parsed PersonaConfig dictionary
123
123
  """
124
124
  path = Path(config_path)
125
- with path.open("r", encoding="utf-8") as f:
126
- data: dict[str, Any] = yaml.safe_load(f)
125
+ try:
126
+ with path.open("r", encoding="utf-8") as f:
127
+ data: dict[str, Any] = yaml.safe_load(f)
128
+ except FileNotFoundError as e:
129
+ raise FileNotFoundError(
130
+ f"Persona config file not found: {path}. Check the persona_config_path "
131
+ "referenced by your agent or trial-source config."
132
+ ) from e
127
133
 
128
134
  return PersonaConfig(sys_prompt=data.get("sys_prompt", ""))
129
135
 
@@ -156,8 +162,14 @@ def load_llm_file_config(config_path: str | Path) -> LLMFileConfig:
156
162
  Parsed LLMFileConfig dictionary with llm as a list of LLMConfig
157
163
  """
158
164
  path = Path(config_path)
159
- with path.open("r", encoding="utf-8") as f:
160
- data: dict[str, Any] = yaml.safe_load(f)
165
+ try:
166
+ with path.open("r", encoding="utf-8") as f:
167
+ data: dict[str, Any] = yaml.safe_load(f)
168
+ except FileNotFoundError as e:
169
+ raise FileNotFoundError(
170
+ f"LLM config file not found: {path}. Check the llm_config_path "
171
+ "referenced by your agent or trial-source config."
172
+ ) from e
161
173
 
162
174
  llm_configs: list[LLMConfig] = []
163
175
 
@@ -62,7 +62,7 @@ class CacheConfig:
62
62
  from dojozero.arena_server._config import ArenaServerConfig # noqa: E402
63
63
 
64
64
  DEFAULT_CACHE_CONFIG = CacheConfig()
65
- CACHEABLE_LEAGUES: frozenset[str] = frozenset({"NBA", "NFL"})
65
+ CACHEABLE_LEAGUES: frozenset[str] = frozenset({"NBA", "NFL", "WORLD_CUP"})
66
66
  LEADERBOARD_PERIODS: tuple[str, ...] = ("7d", "14d", "30d")
67
67
 
68
68
 
@@ -102,7 +102,7 @@ class LandingPageCache:
102
102
  - leaderboard: Agent rankings (global + per-league)
103
103
  - agent_actions: Recent agent actions (global + per-league)
104
104
 
105
- Per-league caches are maintained for CACHEABLE_LEAGUES (NBA, NFL).
105
+ Per-league caches are maintained for CACHEABLE_LEAGUES.
106
106
 
107
107
  Note: No lock is needed because BackgroundRefresher is the single writer
108
108
  and Python's GIL ensures atomic reference assignments.
@@ -29,7 +29,7 @@ Filtering:
29
29
 
30
30
  Supported endpoints: /api/landing, /api/stats, /api/games, /api/leaderboard, /api/agent-actions
31
31
 
32
- Per-league results are cached separately for leagues in CACHEABLE_LEAGUES (NBA, NFL).
32
+ Per-league results are cached separately for leagues in CACHEABLE_LEAGUES.
33
33
  To add a new league, update CACHEABLE_LEAGUES in the code.
34
34
 
35
35
  Configuration:
@@ -90,6 +90,7 @@ from dojozero.arena_server._utils import (
90
90
  _compute_leaderboard,
91
91
  _compute_leaderboard_from_spans,
92
92
  _load_replay_data,
93
+ ARENA_RENDERED_OPERATIONS,
93
94
  TRIAL_INFO_OPERATION_NAMES,
94
95
  trial_id_for_span_grouping,
95
96
  )
@@ -789,7 +790,11 @@ class BackgroundRefresher:
789
790
  tid: str, span_start: datetime | None
790
791
  ) -> tuple[str, list[SpanData]]:
791
792
  async with sem:
792
- spans = await self.trace_reader.get_spans(tid, start_time=span_start)
793
+ spans = await self.trace_reader.get_spans(
794
+ tid,
795
+ start_time=span_start,
796
+ operation_names=ARENA_RENDERED_OPERATIONS,
797
+ )
793
798
  return tid, spans
794
799
 
795
800
  if is_initial or self._last_span_fetch_time is None:
@@ -1417,7 +1422,9 @@ class BackgroundRefresher:
1417
1422
 
1418
1423
  # Fallback to SLS (slow path)
1419
1424
  if not spans:
1420
- spans = await self.trace_reader.get_spans(trial_id)
1425
+ spans = await self.trace_reader.get_spans(
1426
+ trial_id, operation_names=ARENA_RENDERED_OPERATIONS
1427
+ )
1421
1428
 
1422
1429
  existing = self.cache.get_trial_details(trial_id)
1423
1430
 
@@ -58,6 +58,42 @@ TRIAL_INFO_OPERATION_NAMES = [
58
58
  "event.ncaa_game_update",
59
59
  ]
60
60
 
61
+
62
+ def _build_arena_rendered_operations() -> list[str]:
63
+ """Enumerate every operation name that arena actually renders.
64
+
65
+ Used as a server-side whitelist when calling trace readers so that large
66
+ span types the arena does not render (notably GenAI ``chat`` spans) never
67
+ reach the UI's read path.
68
+
69
+ Derived from the dispatch table in ``core/_models.py::deserialize_span``
70
+ plus the registered ``EventTypes`` enum and ``BrokerStateChangeType``
71
+ literals. Must be kept in sync with ``deserialize_span``; the unit test
72
+ ``test_arena_projection`` asserts the invariant.
73
+ """
74
+ from dojozero.data import EventTypes
75
+
76
+ ops: list[str] = [
77
+ "trial.started",
78
+ "trial.stopped",
79
+ "trial.terminated",
80
+ "agent.response",
81
+ "agent.agent_initialize",
82
+ "broker.bet",
83
+ "broker.state_update",
84
+ "broker.bet_executed",
85
+ "broker.final_stats",
86
+ ]
87
+ # All registered event types (event.*).
88
+ for item in EventTypes:
89
+ ops.append(str(item.value))
90
+ return ops
91
+
92
+
93
+ # Arena renders only these operation names. Any span type not listed here (in
94
+ # particular GenAI ``chat`` spans) is filtered out server-side.
95
+ ARENA_RENDERED_OPERATIONS: list[str] = _build_arena_rendered_operations()
96
+
61
97
  # Tag used by DojoZero to correlate all spans for a trial (see TrialOrchestrator, create_span_from_event).
62
98
  _TRIAL_ID_TAG = "dojozero.trial.id"
63
99
 
@@ -923,6 +959,8 @@ def _compute_leaderboard_from_spans(
923
959
 
924
960
  agent_stats: dict[str, _AgentStats] = {}
925
961
  agent_bets: dict[str, list[BetRecord]] = {}
962
+ has_prediction_data = False
963
+ agent_pred_stats: dict[str, dict[str, Any]] = {}
926
964
 
927
965
  # Filter to requested trials or use all
928
966
  trials_to_process = (
@@ -1031,6 +1069,26 @@ def _compute_leaderboard_from_spans(
1031
1069
  # Fill is_external/created_at from accounts (one pass)
1032
1070
  _fill_account_info(typed)
1033
1071
 
1072
+ # Collect prediction stats in the same pass
1073
+ if typed.prediction_statistics:
1074
+ has_prediction_data = True
1075
+ for agent_id, pred_stat in typed.prediction_statistics.items():
1076
+ if agent_id not in agent_pred_stats:
1077
+ agent_pred_stats[agent_id] = {
1078
+ "total_score": 0.0,
1079
+ "total_predictions": 0,
1080
+ "correct_predictions": 0,
1081
+ }
1082
+ agent_pred_stats[agent_id]["total_score"] += float(
1083
+ pred_stat.total_score
1084
+ )
1085
+ agent_pred_stats[agent_id]["total_predictions"] += (
1086
+ pred_stat.total_predictions
1087
+ )
1088
+ agent_pred_stats[agent_id]["correct_predictions"] += (
1089
+ pred_stat.correct_predictions
1090
+ )
1091
+
1034
1092
  if use_date_filter:
1035
1093
  # Date-filtered: recompute from individual bets
1036
1094
  # Track per-agent wagered/profit within this trial
@@ -1098,26 +1156,100 @@ def _compute_leaderboard_from_spans(
1098
1156
 
1099
1157
  # Convert to sorted list
1100
1158
  leaderboard: list[LeaderboardEntry] = []
1101
- for stats in agent_stats.values():
1102
- win_rate = (stats.wins / stats.total_bets * 100) if stats.total_bets > 0 else 0
1103
- roi = (
1104
- (stats.winnings / stats.total_wagered * 100)
1105
- if stats.total_wagered > 0
1106
- else 0
1107
- )
1108
- sharpe = _compute_sharpe(stats.per_trial_rois)
1109
-
1110
- leaderboard.append(
1111
- LeaderboardEntry(
1112
- agent=stats.agent,
1113
- winnings=round(stats.winnings, 2),
1114
- winRate=round(win_rate, 1),
1115
- totalBets=stats.total_bets,
1116
- roi=round(roi, 1),
1117
- sharpe=round(sharpe, 2),
1118
- createdAt=stats.agent.created_at,
1159
+
1160
+ # Handle prediction-based leaderboard if available
1161
+ if has_prediction_data:
1162
+ all_agent_ids = set(list(agent_stats.keys()) + list(agent_pred_stats.keys()))
1163
+
1164
+ for agent_id in all_agent_ids:
1165
+ agent_info = None
1166
+
1167
+ # Get agent info from existing stats or create default
1168
+ if agent_id in agent_stats:
1169
+ agent_info = agent_stats[agent_id].agent
1170
+ winnings = agent_stats[agent_id].winnings
1171
+ wins = agent_stats[agent_id].wins
1172
+ total_bets = agent_stats[agent_id].total_bets
1173
+ total_wagered = agent_stats[agent_id].total_wagered
1174
+ per_trial_rois = agent_stats[agent_id].per_trial_rois
1175
+ else:
1176
+ agent_info = agent_info_cache.get(agent_id)
1177
+ if agent_info is None:
1178
+ agent_info = AgentInfo(agent_id=agent_id, persona=agent_id)
1179
+ winnings = 0.0
1180
+ wins = 0
1181
+ total_bets = 0
1182
+ total_wagered = 0.0
1183
+ per_trial_rois = []
1184
+
1185
+ # Calculate prediction stats
1186
+ pred_score = None
1187
+ accuracy = None
1188
+ if agent_id in agent_pred_stats:
1189
+ stats = agent_pred_stats[agent_id]
1190
+ pred_score = round(stats["total_score"], 2)
1191
+ if stats["total_predictions"] > 0:
1192
+ accuracy = round(
1193
+ (stats["correct_predictions"] / stats["total_predictions"])
1194
+ * 100,
1195
+ 1,
1196
+ )
1197
+
1198
+ # Calculate traditional stats
1199
+ win_rate = (wins / total_bets * 100) if total_bets > 0 else 0
1200
+ roi = (winnings / total_wagered * 100) if total_wagered > 0 else 0
1201
+ sharpe = _compute_sharpe(per_trial_rois)
1202
+
1203
+ leaderboard.append(
1204
+ LeaderboardEntry(
1205
+ agent=agent_info,
1206
+ winnings=round(winnings, 2),
1207
+ winRate=round(win_rate, 1),
1208
+ totalBets=total_bets,
1209
+ roi=round(roi, 1),
1210
+ sharpe=round(sharpe, 2),
1211
+ createdAt=agent_info.created_at,
1212
+ predictionScore=pred_score,
1213
+ accuracy=accuracy,
1214
+ )
1215
+ )
1216
+ else:
1217
+ # Use traditional betting-based leaderboard
1218
+ for stats in agent_stats.values():
1219
+ win_rate = (
1220
+ (stats.wins / stats.total_bets * 100) if stats.total_bets > 0 else 0
1221
+ )
1222
+ roi = (
1223
+ (stats.winnings / stats.total_wagered * 100)
1224
+ if stats.total_wagered > 0
1225
+ else 0
1226
+ )
1227
+ sharpe = _compute_sharpe(stats.per_trial_rois)
1228
+
1229
+ leaderboard.append(
1230
+ LeaderboardEntry(
1231
+ agent=stats.agent,
1232
+ winnings=round(stats.winnings, 2),
1233
+ winRate=round(win_rate, 1),
1234
+ totalBets=stats.total_bets,
1235
+ roi=round(roi, 1),
1236
+ sharpe=round(sharpe, 2),
1237
+ createdAt=stats.agent.created_at,
1238
+ )
1119
1239
  )
1120
- )
1240
+
1241
+ # Determine which field to sort by based on available data.
1242
+ # In prediction mode there is no balance/ROI/win-rate, so all betting sort
1243
+ # keys must remap to prediction_score — otherwise the lambdas return 0 for
1244
+ # every entry and the sort is deterministically wrong.
1245
+ effective_sort_by = sort_by
1246
+ if has_prediction_data and sort_by in (
1247
+ "winnings",
1248
+ "win_rate",
1249
+ "roi",
1250
+ "total_bets",
1251
+ ):
1252
+ effective_sort_by = "prediction_score"
1121
1253
 
1122
1254
  # Sort by requested field
1123
1255
  sort_key_map: dict[str, Any] = {
@@ -1126,8 +1258,12 @@ def _compute_leaderboard_from_spans(
1126
1258
  "roi": lambda x: x.roi,
1127
1259
  "sharpe": lambda x: x.sharpe,
1128
1260
  "total_bets": lambda x: x.total_bets,
1261
+ "prediction_score": lambda x: (
1262
+ x.prediction_score if x.prediction_score is not None else -float("inf")
1263
+ ),
1264
+ "accuracy": lambda x: x.accuracy if x.accuracy is not None else -float("inf"),
1129
1265
  }
1130
- key_fn = sort_key_map.get(sort_by, sort_key_map["winnings"])
1266
+ key_fn = sort_key_map.get(effective_sort_by, sort_key_map["winnings"])
1131
1267
  leaderboard.sort(key=key_fn, reverse=(sort_order != "asc"))
1132
1268
 
1133
1269
  entries = leaderboard[:limit] if limit is not None else leaderboard
@@ -1392,7 +1528,9 @@ async def _load_replay_data(
1392
1528
 
1393
1529
  if not spans:
1394
1530
  try:
1395
- spans = await trace_reader.get_spans(trial_id)
1531
+ spans = await trace_reader.get_spans(
1532
+ trial_id, operation_names=ARENA_RENDERED_OPERATIONS
1533
+ )
1396
1534
  except Exception as e:
1397
1535
  LOGGER.error("Failed to fetch spans for replay: %s", e)
1398
1536
  return None, "trial_not_found"
@@ -2,27 +2,14 @@
2
2
 
3
3
  This module provides reusable components for sports betting scenarios:
4
4
  - BettingAgent: Generic LLM-powered betting agent
5
- - BrokerOperator: Sport-agnostic betting broker managing accounts, bets, and events
5
+ - BrokerOperator: Sport-agnostic classic betting broker (accounts, bets, odds)
6
+ - PredictionBroker: Sport-agnostic prediction-contest broker with five
7
+ fixed windows (pre-game and Q1-Q4) and shared per-window prize pools
6
8
 
7
9
  These components work with any sport (NBA, NFL, etc.) by:
8
10
  1. Handling sport-prefixed event types (e.g., "nfl_game_start" -> "game_start")
9
11
  2. Supporting pluggable event formatters for sport-specific display
10
12
  3. Converting between different odds formats (decimal, American moneyline)
11
-
12
- Example usage:
13
- from dojozero.betting import BettingAgent, BrokerOperator
14
-
15
- # Create broker
16
- broker_config = {"actor_id": "broker", "initial_balance": "1000"}
17
- broker = BrokerOperator(broker_config, trial_id)
18
-
19
- # Create agent with custom formatter
20
- agent = BettingAgent.from_yaml(
21
- config_path="agent.yaml",
22
- actor_id="agent1",
23
- trial_id=trial_id,
24
- event_formatter=my_custom_formatter,
25
- )
26
13
  """
27
14
 
28
15
  from dojozero.betting._agent import (
@@ -50,9 +37,14 @@ from dojozero.betting._models import (
50
37
  BetStatus,
51
38
  BetType,
52
39
  BettingEvent,
40
+ BettingFinalStats,
53
41
  BrokerFinalStats,
54
42
  EventStatus,
55
43
  OrderType,
44
+ Prediction,
45
+ PredictionFinalStats,
46
+ PredictionOutcome,
47
+ PredictionStatistics,
56
48
  Statistics,
57
49
  StatisticsList,
58
50
  # Agent Message Models
@@ -68,6 +60,16 @@ from dojozero.betting._broker import (
68
60
  BrokerOperator,
69
61
  BrokerOperatorConfig,
70
62
  )
63
+ from dojozero.betting._prediction_broker import (
64
+ PredictionBroker,
65
+ PredictionBrokerConfig,
66
+ )
67
+ from dojozero.betting._protocol import ContestOperator
68
+ from dojozero.betting._scoring import (
69
+ DEFAULT_WINDOW_POOLS,
70
+ NUM_WINDOWS,
71
+ settle_window_predictions,
72
+ )
71
73
 
72
74
  __all__ = [
73
75
  # Agent
@@ -79,9 +81,17 @@ __all__ = [
79
81
  # Metadata types
80
82
  "BettingTrialMetadata",
81
83
  "BacktestBettingTrialMetadata",
82
- # Broker
84
+ # Brokers
83
85
  "BrokerOperator",
84
86
  "BrokerOperatorConfig",
87
+ "PredictionBroker",
88
+ "PredictionBrokerConfig",
89
+ "ContestOperator",
90
+ # Scoring
91
+ "DEFAULT_WINDOW_POOLS",
92
+ "NUM_WINDOWS",
93
+ "settle_window_predictions",
94
+ # Domain models
85
95
  "Account",
86
96
  "BettingEvent",
87
97
  "BetRequest",
@@ -93,6 +103,11 @@ __all__ = [
93
103
  "BetSettledPayload",
94
104
  "Statistics",
95
105
  "BrokerFinalStats",
106
+ "BettingFinalStats",
107
+ "PredictionFinalStats",
108
+ "Prediction",
109
+ "PredictionOutcome",
110
+ "PredictionStatistics",
96
111
  # Enums
97
112
  "EventStatus",
98
113
  "OrderType",
@@ -11,6 +11,7 @@ import json
11
11
  import logging
12
12
  import time
13
13
  from collections import deque
14
+ from datetime import datetime, timezone
14
15
  from typing import Any, Callable, Mapping, Sequence, TypedDict, cast
15
16
 
16
17
  from agentscope.agent import ReActAgent
@@ -27,7 +28,12 @@ from dojozero.agents import (
27
28
  create_toolkit,
28
29
  )
29
30
  from dojozero.core import RuntimeContext, Agent, AgentBase, Operator, StreamEvent
30
- from dojozero.core._tracing import create_span_from_event, emit_span
31
+ from dojozero.core._tracing import (
32
+ SpanData,
33
+ create_span_from_event,
34
+ emit_span,
35
+ emit_span_sls_only,
36
+ )
31
37
  from dojozero.betting._config import MEMORY_SUMMARY_PROMPT
32
38
  from dojozero.data._models import (
33
39
  BaseGameUpdateEvent,
@@ -318,7 +324,9 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
318
324
  event_formatter: EventFormatter | None = None,
319
325
  ) -> None:
320
326
  super().__init__(actor_id, trial_id)
321
- # Create internal ReActAgent for LLM reasoning
327
+ # Create internal ReActAgent for LLM reasoning. LLM-level OTel GenAI
328
+ # spans are emitted by AgentScope's built-in ``@trace_llm`` decorator,
329
+ # wired via ``enable_agentscope_tracing()`` at server startup.
322
330
  self._react_agent = ReActAgent(
323
331
  name=name,
324
332
  sys_prompt=sys_prompt,
@@ -603,19 +611,26 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
603
611
  )
604
612
 
605
613
  async def register_operators(self, operators: Sequence[Operator]) -> None:
606
- """Register operators and auto-register broker tools if available."""
614
+ """Register operators and auto-register broker tools if available.
615
+
616
+ The agent's system prompt is *not* modified here. Contest rules and
617
+ operator-specific information are obtained at runtime via tools (e.g.
618
+ ``get_rules`` exposed by :class:`PredictionBroker`) so that internal
619
+ and external (gateway) agents follow the same discovery flow.
620
+ """
607
621
  all_tools = []
622
+
608
623
  for op in operators:
609
624
  self._operator_registry[op.actor_id] = op
610
625
  logger.info(
611
626
  "agent '%s' registered operator '%s'", self.actor_id, op.actor_id
612
627
  )
628
+
613
629
  agent_tools = getattr(op, "agent_tools", None)
614
630
  if callable(agent_tools):
615
631
  tools_result = agent_tools(self.actor_id, operator=op)
616
632
  if inspect.iscoroutine(tools_result):
617
633
  tools_result = await tools_result
618
- # After awaiting, tools_result should be a list
619
634
  if isinstance(tools_result, list):
620
635
  all_tools.extend(tools_result)
621
636
  logger.info(
@@ -774,26 +789,14 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
774
789
  )
775
790
  emit_span(span)
776
791
 
777
- def _emit_response_span(self, message: AgentResponseMessage) -> None:
778
- """Emit a span for agent response to the OTel exporter.
779
-
780
- Args:
781
- message: Structured agent response message (includes bet fields if present)
782
- """
783
- # Build tags from message, excluding None values (e.g., empty bet fields)
792
+ def _response_tags(self, message: AgentResponseMessage) -> dict[str, Any]:
793
+ """Build the tag dict for an ``agent.response`` span."""
784
794
  tags = message.model_dump(exclude_none=True)
785
-
786
- # Serialize cot_steps to JSON string for proper display in tracing UI
787
795
  if "cot_steps" in tags:
788
796
  tags["cot_steps"] = json.dumps(tags["cot_steps"])
789
-
790
- span = create_span_from_event(
791
- trial_id=self.trial_id,
792
- actor_id=self.actor_id,
793
- operation_name="agent.response",
794
- extra_tags=tags,
795
- )
796
- emit_span(span)
797
+ tags["actor.id"] = self.actor_id
798
+ tags["dojozero.trial.id"] = self.trial_id
799
+ return tags
797
800
 
798
801
  def _format_events_for_llm(self, events: list[StreamEvent[Any]]) -> str:
799
802
  """Format multiple events into a consolidated LLM-friendly message.
@@ -939,48 +942,88 @@ class BettingAgent(AgentBase, Agent[BettingAgentConfig]):
939
942
  memory_before = await self.memory.get_memory()
940
943
  memory_before_dicts = [m.to_dict() for m in memory_before]
941
944
 
942
- # Call agent
943
- response = await self._react_agent(msg)
944
-
945
- # Capture memory state AFTER agent call
946
- memory_after = await self.memory.get_memory()
947
- memory_after_dicts = [m.to_dict() for m in memory_after]
948
- self._state = memory_after_dicts
949
-
950
- # Get only this turn's new messages
951
- turn_messages = _extract_memory_diff(memory_before_dicts, memory_after_dicts)
952
-
953
- # Emit span for agent response
954
- if response is not None:
955
- response_content = getattr(response, "content", None)
956
- text_content, _ = _parse_response_content(response_content)
957
-
958
- # Parse CoT steps and bet info from this turn's messages
959
- cot_steps = _parse_cot_steps(turn_messages)
960
- bet = _extract_bet_from_tool_calls(turn_messages)
961
-
962
- # Build trigger safely (empty string if no events)
963
- trigger = ""
964
- if events:
965
- last_payload = events[-1].payload
966
- if isinstance(last_payload, HotTopicsEvent):
967
- trigger = format_hot_topics_for_llm(last_payload)
968
- elif _is_formattable_payload(last_payload):
969
- trigger = self._event_formatter(last_payload)
970
-
971
- # Emit agent response span with structured message (includes bet fields if present)
972
- response_message = AgentResponseMessage(
973
- sequence=self._event_count,
974
- stream_id=primary_stream_id,
975
- agent_id=self.actor_id,
976
- content=text_content,
977
- cot_steps=cot_steps,
978
- trigger=trigger,
979
- game_id=game_id,
980
- # Bet fields - None if no bet, otherwise populated from bet dict
981
- **(bet or {}),
945
+ # Open a real OTel context-managed span for agent.response so that the
946
+ # ``chat`` spans AgentScope's ``@trace_llm`` emits from inside
947
+ # ``_react_agent`` become children via OTel context propagation. After
948
+ # the span closes we also mirror it to SLS (flat-log logstore).
949
+ from opentelemetry import trace as _otel_trace
950
+
951
+ tracer = _otel_trace.get_tracer("dojozero.agent")
952
+ response_start = datetime.now(timezone.utc)
953
+ response_start_us = int(response_start.timestamp() * 1_000_000)
954
+ response_start_mono = time.monotonic()
955
+
956
+ response: Any = None
957
+ response_tags: dict[str, Any] | None = None
958
+ otel_span_id: int = 0
959
+ with tracer.start_as_current_span("agent.response") as otel_span:
960
+ # Identifiers useful for an error span (before response_tags
961
+ # overwrites / adds to them on the success path).
962
+ otel_span.set_attribute("actor.id", self.actor_id)
963
+ otel_span.set_attribute("dojozero.trial.id", self.trial_id)
964
+ otel_span.set_attribute("sequence", self._event_count)
965
+
966
+ # Let the context manager's __exit__ record exceptions / set
967
+ # ERROR status automatically if the model call raises.
968
+ response = await self._react_agent(msg)
969
+
970
+ # Capture memory state AFTER agent call
971
+ memory_after = await self.memory.get_memory()
972
+ memory_after_dicts = [m.to_dict() for m in memory_after]
973
+ self._state = memory_after_dicts
974
+
975
+ # Get only this turn's new messages
976
+ turn_messages = _extract_memory_diff(
977
+ memory_before_dicts, memory_after_dicts
978
+ )
979
+
980
+ if response is not None:
981
+ response_content = getattr(response, "content", None)
982
+ text_content, _ = _parse_response_content(response_content)
983
+
984
+ cot_steps = _parse_cot_steps(turn_messages)
985
+ bet = _extract_bet_from_tool_calls(turn_messages)
986
+
987
+ trigger = ""
988
+ if events:
989
+ last_payload = events[-1].payload
990
+ if isinstance(last_payload, HotTopicsEvent):
991
+ trigger = format_hot_topics_for_llm(last_payload)
992
+ elif _is_formattable_payload(last_payload):
993
+ trigger = self._event_formatter(last_payload)
994
+
995
+ response_message = AgentResponseMessage(
996
+ sequence=self._event_count,
997
+ stream_id=primary_stream_id,
998
+ agent_id=self.actor_id,
999
+ content=text_content,
1000
+ cot_steps=cot_steps,
1001
+ trigger=trigger,
1002
+ game_id=game_id,
1003
+ **(bet or {}),
1004
+ )
1005
+ response_tags = self._response_tags(response_message)
1006
+ for key, value in response_tags.items():
1007
+ if isinstance(value, (str, int, float, bool)):
1008
+ otel_span.set_attribute(key, value)
1009
+ elif value is not None:
1010
+ otel_span.set_attribute(key, str(value))
1011
+
1012
+ otel_span_id = otel_span.get_span_context().span_id
1013
+
1014
+ response_duration_us = int((time.monotonic() - response_start_mono) * 1_000_000)
1015
+
1016
+ if response_tags is not None:
1017
+ sls_span = SpanData(
1018
+ trace_id=self.trial_id,
1019
+ span_id=f"{otel_span_id:016x}",
1020
+ operation_name="agent.response",
1021
+ start_time=response_start_us,
1022
+ duration=response_duration_us,
1023
+ parent_span_id=None,
1024
+ tags=response_tags,
982
1025
  )
983
- self._emit_response_span(response_message)
1026
+ emit_span_sls_only(sls_span)
984
1027
  # Compact memory for next iteration when context becomes too large
985
1028
  if (
986
1029
  self._estimate_memory_tokens(memory_after_dicts)