eggpool 0.2.2__tar.gz → 0.3.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 (233) hide show
  1. eggpool-0.3.0/AGENTS.md +203 -0
  2. {eggpool-0.2.2 → eggpool-0.3.0}/CHANGELOG.md +99 -0
  3. {eggpool-0.2.2 → eggpool-0.3.0}/PKG-INFO +351 -85
  4. eggpool-0.3.0/README.md +783 -0
  5. {eggpool-0.2.2 → eggpool-0.3.0}/architecture/README.md +158 -9
  6. {eggpool-0.2.2 → eggpool-0.3.0}/config.example.toml +64 -16
  7. eggpool-0.3.0/docs/deployment.md +620 -0
  8. eggpool-0.3.0/docs/filesystem-layout.md +91 -0
  9. {eggpool-0.2.2 → eggpool-0.3.0}/docs/providers.md +133 -3
  10. {eggpool-0.2.2 → eggpool-0.3.0}/docs/raspberry-pi.md +21 -0
  11. {eggpool-0.2.2 → eggpool-0.3.0}/pyproject.toml +1 -1
  12. {eggpool-0.2.2 → eggpool-0.3.0}/scripts/check_database.py +1 -1
  13. eggpool-0.3.0/scripts/install.sh +317 -0
  14. {eggpool-0.2.2 → eggpool-0.3.0}/scripts/verify_upstream_auth.py +389 -185
  15. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/__main__.py +3 -1
  16. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/_share/config.example.toml +98 -16
  17. eggpool-0.3.0/src/eggpool/api/backoff.py +117 -0
  18. eggpool-0.3.0/src/eggpool/api/runtime.py +52 -0
  19. eggpool-0.3.0/src/eggpool/api/stats.py +596 -0
  20. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/app.py +342 -38
  21. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/background/__init__.py +72 -1
  22. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/background/cleanup.py +9 -0
  23. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/cache.py +44 -0
  24. eggpool-0.3.0/src/eggpool/catalog/catalog_resolvers.py +352 -0
  25. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/pricing.py +164 -50
  26. eggpool-0.3.0/src/eggpool/catalog/pricing_aliases.py +247 -0
  27. eggpool-0.3.0/src/eggpool/catalog/pricing_resolver.py +326 -0
  28. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/service.py +196 -109
  29. eggpool-0.3.0/src/eggpool/cli.py +74 -0
  30. eggpool-0.2.2/src/eggpool/cli.py → eggpool-0.3.0/src/eggpool/cli_full.py +1723 -265
  31. eggpool-0.3.0/src/eggpool/constants.py +89 -0
  32. eggpool-0.3.0/src/eggpool/cost_recompute.py +155 -0
  33. eggpool-0.3.0/src/eggpool/dashboard/escape.py +283 -0
  34. eggpool-0.3.0/src/eggpool/dashboard/render.py +3419 -0
  35. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/routes.py +265 -9
  36. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/dashboard.css +184 -0
  37. eggpool-0.3.0/src/eggpool/dashboard/static/dashboard.js +476 -0
  38. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/connection.py +48 -0
  39. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/repositories.py +496 -26
  40. eggpool-0.3.0/src/eggpool/db/schema/0024_account_backoffs.sql +31 -0
  41. eggpool-0.3.0/src/eggpool/db/schema/0025_stale_request_index.sql +18 -0
  42. eggpool-0.3.0/src/eggpool/db/schema/0026_attempt_observability.sql +76 -0
  43. eggpool-0.3.0/src/eggpool/db/schema/0027_routing_decisions.sql +52 -0
  44. eggpool-0.3.0/src/eggpool/db/schema/0028_operational_events.sql +32 -0
  45. eggpool-0.3.0/src/eggpool/db/schema/0029_latency_phases.sql +29 -0
  46. eggpool-0.3.0/src/eggpool/db/schema/0030_model_pricing_aliases.sql +32 -0
  47. eggpool-0.3.0/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +21 -0
  48. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/checksums.json +9 -1
  49. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/deploy/__init__.py +154 -4
  50. eggpool-0.3.0/src/eggpool/deploy_user.py +324 -0
  51. eggpool-0.3.0/src/eggpool/fastcli.py +169 -0
  52. eggpool-0.3.0/src/eggpool/health/backoff.py +253 -0
  53. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/health_manager.py +115 -0
  54. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/uninstall.py +183 -42
  55. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/config.py +117 -0
  56. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/onboard.py +15 -15
  57. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/_templates.toml +86 -16
  58. eggpool-0.3.0/src/eggpool/quota/audit.py +182 -0
  59. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/attempt_finalizer.py +13 -0
  60. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/coordinator.py +473 -28
  61. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/finalizer.py +7 -10
  62. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/retry/classification.py +134 -17
  63. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/eligibility.py +22 -9
  64. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/router.py +49 -0
  65. eggpool-0.3.0/src/eggpool/runtime.py +395 -0
  66. eggpool-0.3.0/src/eggpool/runtime_metrics.py +544 -0
  67. eggpool-0.3.0/src/eggpool/runtime_paths.py +118 -0
  68. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/stats/__init__.py +18 -0
  69. eggpool-0.3.0/src/eggpool/stats/queries.py +1869 -0
  70. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/stats/service.py +441 -1
  71. eggpool-0.2.2/AGENTS.md +0 -120
  72. eggpool-0.2.2/README.md +0 -517
  73. eggpool-0.2.2/docs/deployment.md +0 -247
  74. eggpool-0.2.2/docs/filesystem-layout.md +0 -43
  75. eggpool-0.2.2/scripts/install.sh +0 -182
  76. eggpool-0.2.2/src/eggpool/api/stats.py +0 -278
  77. eggpool-0.2.2/src/eggpool/constants.py +0 -40
  78. eggpool-0.2.2/src/eggpool/dashboard/escape.py +0 -104
  79. eggpool-0.2.2/src/eggpool/dashboard/render.py +0 -1448
  80. eggpool-0.2.2/src/eggpool/runtime.py +0 -163
  81. eggpool-0.2.2/src/eggpool/stats/queries.py +0 -616
  82. {eggpool-0.2.2 → eggpool-0.3.0}/.env.example +0 -0
  83. {eggpool-0.2.2 → eggpool-0.3.0}/.github/workflows/ci.yml +0 -0
  84. {eggpool-0.2.2 → eggpool-0.3.0}/.github/workflows/release.yml +0 -0
  85. {eggpool-0.2.2 → eggpool-0.3.0}/.gitignore +0 -0
  86. {eggpool-0.2.2 → eggpool-0.3.0}/LICENSE +0 -0
  87. {eggpool-0.2.2 → eggpool-0.3.0}/config-examples/claude-code.env +0 -0
  88. {eggpool-0.2.2 → eggpool-0.3.0}/config-examples/opencode.jsonc +0 -0
  89. {eggpool-0.2.2 → eggpool-0.3.0}/deploy/eggpool-logrotate.conf +0 -0
  90. {eggpool-0.2.2 → eggpool-0.3.0}/deploy/eggpool.service +0 -0
  91. {eggpool-0.2.2 → eggpool-0.3.0}/deploy/env.example +0 -0
  92. {eggpool-0.2.2 → eggpool-0.3.0}/docs/backup-restore.md +0 -0
  93. {eggpool-0.2.2 → eggpool-0.3.0}/docs/firewall.md +0 -0
  94. {eggpool-0.2.2 → eggpool-0.3.0}/docs/model-limits.md +0 -0
  95. {eggpool-0.2.2 → eggpool-0.3.0}/docs/proxy.md +0 -0
  96. {eggpool-0.2.2 → eggpool-0.3.0}/scripts/__init__.py +0 -0
  97. {eggpool-0.2.2 → eggpool-0.3.0}/scripts/install_prompt.py +0 -0
  98. {eggpool-0.2.2 → eggpool-0.3.0}/scripts/smoke_test.py +0 -0
  99. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/__init__.py +0 -0
  100. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/_share/.env.example +0 -0
  101. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/__init__.py +0 -0
  102. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/registry.py +0 -0
  103. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/state.py +0 -0
  104. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/__init__.py +0 -0
  105. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/chat_completions.py +0 -0
  106. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/errors.py +0 -0
  107. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/messages.py +0 -0
  108. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/models.py +0 -0
  109. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/proxy_request.py +0 -0
  110. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/auth.py +0 -0
  111. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/__init__.py +0 -0
  112. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/fetcher.py +0 -0
  113. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/limits.py +0 -0
  114. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/normalizer.py +0 -0
  115. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/protocols.py +0 -0
  116. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/config.py +0 -0
  117. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/__init__.py +0 -0
  118. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/_resources.py +0 -0
  119. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
  120. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/favicon.svg +0 -0
  121. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/theme.py +0 -0
  122. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
  123. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
  124. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
  125. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
  126. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
  127. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
  128. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
  129. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
  130. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Discord.toml +0 -0
  131. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
  132. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
  133. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
  134. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
  135. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
  136. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
  137. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
  138. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
  139. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
  140. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
  141. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
  142. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Nord.toml +0 -0
  143. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
  144. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
  145. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
  146. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
  147. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
  148. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
  149. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
  150. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
  151. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
  152. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
  153. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
  154. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/acton.toml +0 -0
  155. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/bam.toml +0 -0
  156. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
  157. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/berlin.toml +0 -0
  158. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
  159. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/broc.toml +0 -0
  160. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/cork.toml +0 -0
  161. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/ferra.toml +0 -0
  162. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/forest.toml +0 -0
  163. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
  164. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/midnight.toml +0 -0
  165. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/oslo.toml +0 -0
  166. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/plum.toml +0 -0
  167. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/portland.toml +0 -0
  168. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/sunset.toml +0 -0
  169. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/tofino.toml +0 -0
  170. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
  171. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/vik.toml +0 -0
  172. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/__init__.py +0 -0
  173. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/migrations.py +0 -0
  174. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0001_initial.sql +0 -0
  175. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0002_indexes.sql +0 -0
  176. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
  177. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
  178. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
  179. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
  180. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
  181. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
  182. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
  183. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
  184. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
  185. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
  186. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
  187. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
  188. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
  189. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
  190. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
  191. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
  192. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
  193. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
  194. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
  195. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
  196. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
  197. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/errors.py +0 -0
  198. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/__init__.py +0 -0
  199. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/circuit_breaker.py +0 -0
  200. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/integrations/__init__.py +0 -0
  201. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/integrations/opencode.py +0 -0
  202. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/__init__.py +0 -0
  203. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/backup.py +0 -0
  204. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/logging.py +0 -0
  205. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/__init__.py +0 -0
  206. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/api.py +0 -0
  207. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/database.py +0 -0
  208. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/domain.py +0 -0
  209. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/__init__.py +0 -0
  210. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/auth.py +0 -0
  211. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/client_pool.py +0 -0
  212. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/connect.py +0 -0
  213. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/contract.py +0 -0
  214. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/pproxy_transport.py +0 -0
  215. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/__init__.py +0 -0
  216. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/client.py +0 -0
  217. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/sse_observer.py +0 -0
  218. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/usage.py +0 -0
  219. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/py.typed +0 -0
  220. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/__init__.py +0 -0
  221. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/estimation.py +0 -0
  222. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/reservation.py +0 -0
  223. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/scorer.py +0 -0
  224. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/__init__.py +0 -0
  225. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/body.py +0 -0
  226. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/limits.py +0 -0
  227. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/retry/__init__.py +0 -0
  228. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/__init__.py +0 -0
  229. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/provider.py +0 -0
  230. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/security/__init__.py +0 -0
  231. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/security/redaction.py +0 -0
  232. {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/toml_edit.py +0 -0
  233. {eggpool-0.2.2 → eggpool-0.3.0}/uv.lock +0 -0
@@ -0,0 +1,203 @@
1
+ # AGENTS.md
2
+
3
+ ## Skills
4
+
5
+ Project-specific skills are in `.opencode/skills/`:
6
+
7
+ - `architecture` — design principles, request lifecycle, invariants, error hierarchy
8
+ - `deployment` — production deployment, systemd, operational scripts
9
+ - `development` — linting, testing, pre-commit checks, code style
10
+
11
+ ## Quick Start
12
+
13
+ - Package manager: **uv** (not pip). Install deps: `uv sync --extra dev`
14
+ - Entry point: `src/eggpool/cli.py` → `eggpool` console script
15
+ - Config: `config.toml` + `.env` for API keys
16
+
17
+ ## Pre-commit Checks (run before every commit)
18
+
19
+ ```bash
20
+ uv run ruff format --check src/ tests/ scripts/
21
+ uv run ruff check src/ tests/ scripts/
22
+ uv run pyright src/ scripts/
23
+ uv run pytest
24
+ ```
25
+
26
+ All four must pass with zero errors.
27
+
28
+ ## Code Style
29
+
30
+ - Python 3.11+ with `from __future__ import annotations` in ALL files
31
+ - Type hints on all function signatures and return types
32
+ - Ruff: E, F, W, I, N, UP, B, A, SIM, TCH rules
33
+ - Pyright strict mode — covers `src/` AND `scripts/` (not tests)
34
+ - Line length: 88 chars
35
+ - Use `NoReturn` for functions that never return (e.g., `sys.exit`)
36
+
37
+ ## Testing
38
+
39
+ - pytest with `asyncio_mode = "strict"` (from `pyproject.toml`)
40
+ - respx for HTTPX upstream mocking
41
+ - Tests in `tests/unit/`, `tests/integration/`, `tests/contract/`
42
+ - Provider contract tests: `uv run pytest tests/unit/test_contract.py tests/unit/test_contract_urls.py -v`
43
+
44
+ ## File Organization
45
+
46
+ - Source: `src/eggpool/`
47
+ - Tests: `tests/` (mirrors src structure)
48
+ - Config: `config.example.toml`, `.env.example`
49
+ - DB schema: `src/eggpool/db/schema/`
50
+ - Scripts: `scripts/` (operational, also type-checked by pyright)
51
+ - Deployment: `deploy/`
52
+
53
+ ## Multi-Provider Architecture
54
+
55
+ - Provider-suffixed model IDs: `model-id/provider-id` (e.g., `claude-sonnet-4/opencode-go`)
56
+ - `ProviderClientPool` manages per-provider `httpx.AsyncClient` with independent connection pools
57
+ - Flat `[[accounts]]` configs auto-normalize to a default `opencode-go` provider
58
+ - `parse_model_provider()` and `format_model_provider()` in `routing/provider.py`
59
+
60
+ ## Provider Contracts
61
+
62
+ Key design rules that are easy to get wrong:
63
+
64
+ - `compose_provider_url()` is the single source of truth for upstream URLs — catalog fetch, non-streaming chat, and streaming chat all call it
65
+ - `build_auth_headers()` reads from `ProviderConfig.auth` — never hardcode Bearer
66
+ - `base_url` ending `/v1` + path beginning `/v1/` is rejected (duplicate version prefix)
67
+ - `auth.mode = "none"` sends no upstream auth (Ollama local)
68
+ - All outbound dispatch paths use `compose_provider_url()` so providers cannot list at one host and dispatch to another
69
+ - **`models_endpoint.method = "DISABLED"`** skips live model listing. Providers using this must declare `[[providers.<id>.static_models]]` rows; otherwise `eggpool check-config` warns and the catalog will be empty for the provider
70
+ - **`static_models` lifecycle**: rows are seeded by `CatalogService._seed_static_models` BEFORE live fetch tasks run. Static-source fields (`protocol`, `protocol_source == "static_config"`, `supports_tools`, `supports_vision`) are preserved by `ModelCatalogCache._preserve_static_fields` when live rows arrive without them
71
+ - **`eggpool check-config`** runs `_check_stale_contracts` after a successful load and emits advisory warnings for: `DISABLED` without static seeds, `DISABLED` with `require_models = true`, declared path fields ignored by protocol mismatch, duplicate `/v1` segments, `Authorization` in static headers when `auth.mode != "none"`, legacy `models_method`/`models_path` fields, and Anthropic providers using `auth.header = "Authorization"` instead of `x-api-key`. Warnings do not change the exit code
72
+
73
+ ## Bearer-prefix Guard
74
+
75
+ `AppConfig.validate_account_credentials()` rejects API keys beginning with `Bearer` for `auth.mode = "bearer"` providers. EggPool prepends `Bearer ` automatically; storing `Bearer <token>` produces `Authorization: Bearer Bearer <token>` and causes 401s. Providers using `auth.mode = "raw_authorization"` are unaffected.
76
+
77
+ ## Error Handling
78
+
79
+ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or `raise ... from None`.
80
+
81
+ - `AggregatorError` → `ConfigError`, `DatabaseError`, `ProxyError`
82
+ - `UpstreamError` (has `status_code`) → `TemporaryUpstreamError`, `TransientUpstreamError`, `AuthenticationError`, `QuotaExhaustedError`, `RateLimitError` (has `retry_after`), `ModelUnavailableError`
83
+ - `ModelNotFoundError` (has `model_id`), `NoEligibleAccountError`, `CatalogUnavailableError`, `AuthenticationUnavailableError`, `UpstreamExhaustedError`, `AccountSuspendedError`, `RequestTooLargeError`, `ContextLimitExceededError`
84
+
85
+ ## Gotchas
86
+
87
+ - Configuration changes require a service restart; live reload is intentionally not supported
88
+ - No CI workflows or pre-commit hooks are configured in this repo
89
+ - `Database.vacuum()` is the only sanctioned path for `VACUUM` in production code
90
+ - Every DML write must run inside `async with db.transaction():`
91
+ - SQLite transactions are serialized across concurrent tasks via a single connection lock + ContextVar
92
+ - Requests must be persisted before upstream dispatch; pre-body failures can retry, but no retry after first downstream byte
93
+ - **Upstream-authoritative suppression**: local quota estimates are advisory by default (`local_quota_mode = "score_only"`). Above-capacity accounts stay eligible; only upstream-observed failures (429/402/5xx/auth) and explicit operator disablement suppress routing. Switch to `hard_cap` only as an opt-in escape hatch.
94
+ - **Backoff persistence**: upstream-derived backoffs survive restarts via the `account_backoffs` table (`src/eggpool/db/schema/0024_account_backoffs.sql`). Hydration runs at startup after account sync, best-effort (never blocks boot). Local cost overruns must never be persisted as backoff rows.
95
+ - **Synthetic 503 vs 502**: `ModelUnavailableError` (503) is reserved for genuine pre-dispatch unavailability. `UpstreamExhaustedError` (502) is raised when every candidate account was attempted and exhausted mid-request. Single-account upstream errors pass through to the client rather than becoming synthetic 503s.
96
+ - **Streaming finalizer shielding**: streaming `_build_stream_generator` finalization runs under `asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task cancellation cannot kill the finalizer while it holds the DB lock. Leaks that escape this path are caught by the periodic `stale_request_finalizer` background task (`app._finalize_stale_requests`, runs every 60s) which force-finalizes any request that has been `pending` longer than `upstream.read_timeout_s`.
97
+ - **Startup crash recovery**: `_crash_recovery` runs at every startup and recovers ALL pending requests and ALL active reservations with no time threshold. A process restart is a definitive boundary, so leaked state from the previous process is unconditionally cleaned up. If `Crash recovery: marked N stale requests` appears in logs, the safety net caught leaks from the previous run.
98
+ - **Pricing resolution pipeline**: prices flow TOML override → upstream metadata → external catalog (OpenRouter / OpenCode Zen via the alias registry). `ResolvedPricing` records `source_detail` (`operator_override` / `provider_metadata` / `openrouter` / `opencode_zen`) and `source_confidence` (`exact_external_id` / `curated_alias` / `provider_metadata`). Cost rows are then labelled `derived` (every category trusted), `partial` (some categories filled by per-category fallback), `estimated` (no trusted rates), or `unknown` (no token usage). `partial_count` is a new exactness value exposed via `/api/stats/summary`, `/api/stats/accounts`, and `/api/stats/models`. The dashboard renders a per-row cost-exactness badge and a high-spend estimated warning banner (>$10 estimated) on the Accounts page.
99
+ - **`eggpool stats recompute-costs [--dry-run|--apply] [--limit N]`**: walks the requests table in started_at DESC order, recomputes cost from the current price snapshots, and reports / applies the change. Default is `--dry-run`. Use after upgrading the resolver to fix inflated totals on cached-token-heavy models (e.g. MiMo 2.5). Implemented in `src/eggpool/cost_recompute.py` and reuses the live `CostCalculator` so the new values match what the finalizer would write today.
100
+ - **Migration 0030 (`model_pricing_aliases`) + 0031 (`price_snapshot_provenance`)**: 0030 introduces the alias registry that maps upstream model IDs (e.g. `mimo-v2.5`) onto external catalog IDs (e.g. `xiaomi/mimo-v2.5`) with an `exact`/`curated_alias`/`ambiguous_skip` confidence enum. 0031 adds `source_detail`, `source_confidence`, `catalog_source` columns to `model_price_snapshots` so the dashboard can attribute prices back to the resolver that produced them. Seed data lives in `seed_default_aliases()` (`src/eggpool/catalog/pricing_aliases.py`) and runs idempotently at startup via `CatalogService.attach_pricing_resolvers()`.
101
+
102
+ ## Observability
103
+
104
+ - **Attempt analytics**: per-attempt aggregates including latency percentiles, byte totals, retry rate, and the `retry_category` distribution. Every `request_attempts` row carries `provider_id/model_id/protocol/retry_category/release_reason/bytes_received/latency_ms/streamed/is_retry_outcome`
105
+ - **Routing analytics**: per-`(model, provider)` decision aggregates, account-level selection counts, and per-`(account, reason)` exclusion counts. Every routing decision is persisted as a `routing_decisions` row inside the same transaction as the `request_attempts` INSERT
106
+ - **Latency phases**: decomposes each request into `upstream_connect_ms`, `upstream_read_ms`, and `coordinator_overhead_ms`
107
+ - **Operational health**: `crash_recovery`, `stale_request_finalizer`, and `reservation_reconcile` safety-net events are recorded as `operational_events` rows in the same transaction as the durable state mutation
108
+ - **Pending health**: instantaneous snapshot of pending request count, oldest pending age, stale-pending count (>15 min), active reservations, total reserved microdollars, and oldest reservation age. Auth-gated
109
+ - **Per-request trace**: parent request row, full attempt chain, and per-attempt routing decisions. Returns account name, model, protocol, status, error class (never raw error_detail), and timing. Auth-gated
110
+ - **Recent request metadata**: bounded list of recent request rows with metadata only (no body, no auth headers, no error_detail). Auth-gated
111
+ - **Cost/cache/reasoning exactness**: per-account and per-model `exact_count`, `partial_count`, `derived_count`, `estimated_count`, `cache_read_ratio`, `cache_write_ratio`, `reasoning_output_ratio`
112
+ - Full API surface is documented in the `architecture` skill
113
+
114
+ ## Dashboard
115
+
116
+ - Server-rendered HTML pages in `src/eggpool/dashboard/render.py`
117
+ - Overview page auto-refreshes in place (every `[dashboard].refresh_interval_s`); all other pages are static
118
+ - Charts use bundled Chart.js v4 at `/static/chart.js` with `Cache-Control: public, max-age=86400`
119
+ - New pages opt into Chart.js via `include_chart_js=True` in `_render_layout`
120
+ - Frontend helpers in `src/eggpool/dashboard/static/dashboard.js` under `window.EggPoolDashboard`
121
+ - Full page list and chart lifecycle details are in the `architecture` skill
122
+
123
+ ## Fast-Path CLI
124
+
125
+ - `src/eggpool/cli.py` is a tiny bootstrap (74 lines)
126
+ - `main()` calls `eggpool.fastcli.maybe_run_fast_command()` first; recognized fast commands (`croncheck`, `ensure-running`) are dispatched without importing Click
127
+ - **Do not add transitive imports to `runtime_paths` or `fastcli`** — they are stdlib-only and must stay lightweight for the Raspberry Pi watchdog contract
128
+ - Unrecognized commands fall through to `eggpool.cli_full`, which holds the heavy Click CLI
129
+ - Public symbols (`cli`, helpers used by tests) are lazily forwarded from `cli_full` via PEP 562 `__getattr__` — so `from eggpool.cli import cli` and existing test imports still work without loading the full graph
130
+ - See `plans/lightweight-cli-watchdog.md` for the full design
131
+
132
+ ## Process Model
133
+
134
+ - `eggpool serve` runs as a single supervisor process that invokes `Granian` with `workers=1`; Granian spawns one worker process, so exactly **two** processes appear under the canonical name
135
+ - The Granian worker is launched with `process_name="eggpool"`, so `ps` / `top` / `pgrep` show the canonical name for both supervisor and worker (not a generic `python` entry)
136
+ - `[server].threads` (int, default `1`, min `1`, max `64`) controls Granian `runtime_threads` — the number of worker event-loop threads. Default is `1` for SBC / Raspberry Pi; raise on capable hardware
137
+ - PID path resolution lives in `eggpool.runtime_paths` and is the single source of truth (`default_pid_file()`). Precedence: `$EGGPOOL_PID_FILE` → `$XDG_RUNTIME_DIR/eggpool.pid` → `~/.local/state/eggpool/eggpool.pid` → `/tmp/eggpool-<UID>.pid`. The PID file is owned by the **supervisor**, written before `Granian.serve()` and cleared in a `finally` block. The FastAPI lifespan no longer touches the PID file
138
+ - `eggpool serve` refuses to start a second instance: first checks `runtime.read_pid()` + `runtime.is_process_running()`; if no live PID, probes `GET /v1/healthz` via stdlib `urllib.request` (bind `0.0.0.0` / `::` is rewritten to `127.0.0.1`). A live PID or a 200 from the probe exits `1`. Stale PID files (PID not running) are cleared before starting
139
+ - `eggpool restart` delegates to `runtime.restart_server` which calls `runtime.send_sigterm` and `runtime.start_server` (which `subprocess.Popen`s a new supervisor)
140
+ - `eggpool ensure-running` is the canonical cron watchdog command — it atomically checks-and-starts without ever spawning a duplicate instance. Use it from `@reboot` and `*/5 * * * *` crontab lines, not `croncheck || eggpool serve &`
141
+
142
+ ### Daemon Mode
143
+
144
+ - Foreground `eggpool serve` remains the debugging/operator path and prints Granian logs to the calling terminal
145
+ - `eggpool serve --daemon` validates the config, refuses to start a second instance, then spawns a detached child and returns promptly with a short success message pointing at the log file
146
+ - The detached child runs the normal foreground `serve` command (Granian supervisor + worker). The `--daemon` flag is **never** forwarded to the child; detachment is purely a parent-side concern. The child owns its own PID file lifecycle via `runtime.write_pid_file()` / `runtime.clear_pid_file()`
147
+ - Default log destination is `~/.local/state/eggpool/eggpool.log`, resolvable via `eggpool.runtime_paths.default_log_file()`. Override with `--log-file PATH` or `$EGGPOOL_LOG_FILE`. A log file beats `/dev/null` by default because a silent background failure is hard to diagnose
148
+ - The child is launched with `start_new_session=True`, `stdin=subprocess.DEVNULL`, and `stdout`/`stderr` redirected to the log file (or `/dev/null` when `--quiet` is set without `--log-file`). The child survives shell exit and signals to the parent CLI do not propagate
149
+ - `runtime.start_server()` signature: `start_server(config_path, *, cwd=None, daemon=True, log_path=None, quiet=True, verify=False, verify_timeout_s=3.0)`. `runtime.restart_server()` accepts the same `daemon`, `log_path`, `quiet` options
150
+ - `serve --daemon` refuses to daemonize when the effective UID is 0 unless `--as-root` is passed (prevents accidental root personal deployment)
151
+ - Systemd should **not** use `--daemon`. The systemd unit already owns the process lifecycle; run foreground `serve` and let systemd manage the PID, journal logs, and restart policy
152
+
153
+ ## CLI Commands
154
+
155
+ | Command | Description |
156
+ |---------|-------------|
157
+ | `eggpool help` | Show help message and available commands |
158
+ | `eggpool version` | Print the installed version |
159
+ | `eggpool serve` | Start the aggregation proxy server (default command). Flags: `--daemon`, `--log-file PATH`, `--quiet`, `--as-root` |
160
+ | `eggpool check-config` | Validate the configuration file |
161
+ | `eggpool migrate` | Run database migrations |
162
+ | `eggpool onboard` | Run the interactive onboarding setup (connect providers, start server) |
163
+ | `eggpool connect` | Connect to a new provider interactively |
164
+ | `eggpool connect list` | List available providers |
165
+ | `eggpool logout` | Remove a configured provider account |
166
+ | `eggpool rehash` | Restart to apply config changes |
167
+ | `eggpool restart` | Fully restart the server (stop then start) |
168
+ | `eggpool stop` | Stop the running server |
169
+ | `eggpool set` | Set a server configuration value and restart |
170
+ | `eggpool getkey` | Print the current server API key |
171
+ | `eggpool newkey` | Generate a new server API key |
172
+ | `eggpool edit` | Open the configuration file in the default editor |
173
+ | `eggpool configsetup` | Print configuration snippets for code editors |
174
+ | `eggpool configsetup opencode` | Print OpenCode provider config JSON with model limits |
175
+ | `eggpool configsetup claude-code` | Print Claude Code config snippet |
176
+ | `eggpool update` | Check for updates and reinstall if newer |
177
+ | `eggpool croncheck` | Lightweight check: exit 0 if server is running, exit 1 if not |
178
+ | `eggpool ensure-running` | Repair: start the server if it is not running; no-op when alive. Fast-path. |
179
+ | `eggpool runtime-status` | Print compact runtime health summary from running server |
180
+ | `eggpool models refresh` | Refresh model catalog from upstream |
181
+ | `eggpool accounts status` | Show configured account status |
182
+ | `eggpool accounts list` | List configured provider accounts |
183
+ | `eggpool dashboard public` | Toggle dashboard public access |
184
+ | `eggpool db vacuum` | Reclaim SQLite space |
185
+ | `eggpool init-config` | Write bundled config.example.toml to current directory or TARGET |
186
+ | `eggpool stats recompute-costs [--dry-run\|--apply] [--limit N]` | Recompute historical `cost_microdollars` from current price snapshots. Default `--dry-run`. |
187
+ | `eggpool deploy systemd` | Print systemd unit; `--install` writes it (personal by default; `--production` for the dedicated-system layout; `--as-root` for a root-owned personal unit) |
188
+ | `eggpool deploy cron` | Print / install / uninstall the watchdog crontab (`@reboot` + `*/N * * * *` `eggpool ensure-running`). `--interval N` (1-59, default 5) |
189
+ | `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron (personal user cron or production `/etc/cron.d/`) |
190
+ | `eggpool deploy logrotate` | Print / install / logrotate config (validated via `logrotate -d`) |
191
+ | `eggpool deploy all` | Print / install systemd + logrotate + watchdog cron (backup-cron is separate) |
192
+ | `eggpool backup` | Create a timestamped `.zip` backup of config, `.env`, and database |
193
+ | `eggpool recover [path]` | Restore from a backup archive (interactive menu if no path) |
194
+ | `eggpool uninstall` | Remove binary, config, database, and shell PATH entries; `--deploy-artifacts` also removes the systemd unit, logrotate config, watchdog + backup cron blocks, and backup script |
195
+
196
+ All commands accept `--config /path/to/config.toml` (defaults to `config.toml`; resolution: `--config` > `$EGGPOOL_CONFIG` > `~/.config/eggpool/config.toml` > `./config.toml`).
197
+ Running `eggpool` with no arguments prints the help message.
198
+
199
+ ## Git Workflow
200
+
201
+ - Branch: `main`
202
+ - Commit messages: concise, imperative mood
203
+ - Never commit secrets, API keys, or `.env` files
@@ -7,6 +7,105 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-06-25
11
+
12
+ ### Added
13
+
14
+ - **Daemon mode**: `eggpool serve --daemon` spawns a detached supervisor
15
+ and returns the shell promptly. The child runs the normal foreground
16
+ `serve` command (Granian supervisor + worker); `--daemon` is never
17
+ forwarded. Flags: `--log-file PATH`, `--quiet`, `--as-root`. Default
18
+ log destination is `~/.local/state/eggpool/eggpool.log`.
19
+ - **Fast-path CLI**: `eggpool ensure-running` and `eggpool croncheck`
20
+ are dispatched without importing Click via `eggpool.fastcli`. Both
21
+ modules are stdlib-only, keeping cron watchdog ticks cheap on
22
+ Raspberry Pi-class hardware.
23
+ - **`eggpool runtime-status`**: compact terminal health summary from
24
+ the running server (process topology, memory, background tasks,
25
+ database health, in-flight requests). Supports `--json` for
26
+ scripting.
27
+ - **Grouped timeseries dashboard**: stacked-bar chart on `/timeseries`
28
+ with groupable dimensions (`provider_model`, `provider`, `model`,
29
+ `account`), top-N + Other folding, per-bucket detail table, and
30
+ interactive controls for period, bucket, group_by, metric, and
31
+ limit. Backed by `/api/timeseries/grouped`.
32
+ - **Metrics dashboard**: reliability page (attempt success/retry
33
+ breakdown, `retry_category` distribution, pending health, operational
34
+ events), routing page (per-`(model, provider)` decision aggregates,
35
+ account selection counts, exclusion taxonomy), traces page
36
+ (auth-gated recent request metadata), latency phase decomposition
37
+ (`upstream_connect_ms`, `upstream_read_ms`, `coordinator_overhead_ms`).
38
+ - **CSS tooltip system**: pure-CSS `[data-tooltip]` bubbles on heatmap
39
+ cells, column headers, topbar controls, and status badges. No
40
+ JavaScript; survives overview auto-refresh `innerHTML` swap.
41
+ - **Upstream-authoritative suppression**: local quota estimates are
42
+ advisory by default (`local_quota_mode = "score_only"`). Above-capacity
43
+ accounts stay eligible; only upstream-observed failures (429/402/5xx/auth)
44
+ and explicit operator disablement suppress routing. Opt in to legacy
45
+ behavior via `local_quota_mode = "hard_cap"`.
46
+ - **Runtime/ops metrics**: `/api/stats/runtime` endpoint exposes
47
+ process topology, memory, background task state, database health,
48
+ and in-flight request counts. `/runtime` dashboard page renders
49
+ these metrics.
50
+ - **Attempt analytics**: per-attempt aggregates including latency
51
+ percentiles, byte totals, retry rate, and `retry_category`
52
+ distribution. Every `request_attempts` row carries
53
+ `provider_id/model_id/protocol/retry_category/release_reason/bytes_received/latency_ms/streamed/is_retry_outcome`.
54
+ - **Routing analytics**: per-`(model, provider)` decision aggregates,
55
+ account-level selection counts, and per-`(account, reason)` exclusion
56
+ counts. Every routing decision persisted to `routing_decisions` in
57
+ the same transaction as the `request_attempts` INSERT.
58
+ - **Operational health**: `crash_recovery`, `stale_request_finalizer`,
59
+ and `reservation_reconcile` safety-net events recorded as
60
+ `operational_events` rows.
61
+ - **Pricing provenance**: `source_detail` and `source_confidence`
62
+ columns on `model_price_snapshots` for dashboard attribution.
63
+ Migration `0031_price_snapshot_provenance.sql`.
64
+ - **Pricing alias registry**: maps upstream model IDs to external
65
+ catalog IDs with `exact`/`curated_alias`/`ambiguous_skip` confidence.
66
+ Migration `0030_model_pricing_aliases.sql`. Seeded idempotently at
67
+ startup.
68
+ - **Install/deploy simplification**: `eggpool deploy systemd --install`
69
+ personal mode auto-detects user, binary, and config paths.
70
+ `eggpool deploy backup-cron` and `eggpool deploy all` for complete
71
+ lifecycle management.
72
+ - `eggpool stats recompute-costs [--dry-run|--apply] [--limit N]`
73
+ operator escape hatch for fixing inflated cost totals after resolver
74
+ upgrades.
75
+ - `eggpool init-config` writes bundled `config.example.toml` to
76
+ current directory or target path.
77
+
78
+ ### Fixed
79
+
80
+ - 503 saturation after several minutes of streaming load. Streaming
81
+ request finalization is now wrapped in
82
+ `asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task
83
+ cancellation cannot kill the finalizer while it holds the SQLite
84
+ connection lock. A periodic `stale_request_finalizer` background
85
+ task force-finalizes any request that has been `pending` longer than
86
+ `upstream.read_timeout_s` and reconciles the in-memory active-count
87
+ and quota-reservation caches. Startup `_crash_recovery` no longer
88
+ time-gates its sweep — a process restart is treated as a definitive
89
+ boundary, so every leaked pending request and every active
90
+ reservation from the previous process is recovered on boot.
91
+ - MiMo-style cost inflation via provider-aware pricing resolution.
92
+ The resolver now correctly handles cached-token-heavy models where
93
+ upstream metadata reports different pricing than the external catalog.
94
+ - Stale `format_tokens` assertion after unit-scaling rewrite.
95
+ - MiniMax and GeneralCompute provider contract alignment: auth headers,
96
+ URL composition, and static model seeding now match the documented
97
+ contracts.
98
+
99
+ ### Changed
100
+
101
+ - `eggpool serve` runs as a single supervisor process invoking Granian
102
+ with `workers=1`. The supervisor owns the PID file; the FastAPI
103
+ lifespan no longer touches it.
104
+ - `eggpool restart` delegates to `runtime.restart_server` instead of
105
+ inlining subprocess logic.
106
+ - AGENTS.md trimmed to point to skills for details; CLI commands
107
+ table expanded to cover all 35+ commands.
108
+
10
109
  ## [0.2.2] - 2026-06-25
11
110
 
12
111
  ### Fixed