by-framework 0.2.2.dev11__tar.gz → 0.2.2.dev12__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 (325) hide show
  1. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/CLAUDE.md +1 -0
  2. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/PKG-INFO +3 -3
  3. by_framework-0.2.2.dev12/docs/architecture/KEY_FILES.md +465 -0
  4. by_framework-0.2.2.dev12/docs/architecture/suspend-resume-liveness.md +385 -0
  5. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/src/by_framework_adk/adapter.py +28 -3
  6. by_framework-0.2.2.dev12/libs/by-framework-adk/tests/test_adapter.py +129 -0
  7. by_framework-0.2.2.dev12/libs/by-framework-agent/README.md +119 -0
  8. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/01_basic_chat.py +96 -0
  9. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/02_tools_and_ask_user.py +172 -0
  10. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/03_agent_delegation.py +168 -0
  11. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/04_task_group.py +223 -0
  12. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/05_real_model_react.py +284 -0
  13. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/README.md +41 -0
  14. by_framework-0.2.2.dev12/libs/by-framework-agent/examples/_infra.py +253 -0
  15. by_framework-0.2.2.dev12/libs/by-framework-agent/pyproject.toml +35 -0
  16. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/__init__.py +16 -0
  17. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/litellm_client.py +85 -0
  18. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/loop.py +814 -0
  19. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/model_client.py +45 -0
  20. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/testing.py +37 -0
  21. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/tool_spec.py +41 -0
  22. by_framework-0.2.2.dev12/libs/by-framework-agent/src/by_framework_agent/worker.py +81 -0
  23. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/conftest.py +109 -0
  24. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_agent_as_tool.py +243 -0
  25. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_ask_user_suspend_resume.py +182 -0
  26. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_cancel_cleanup.py +212 -0
  27. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_native_agent_worker.py +314 -0
  28. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_task_group_batching.py +339 -0
  29. by_framework-0.2.2.dev12/libs/by-framework-agent/tests/test_tool_calling_loop.py +331 -0
  30. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +10 -1
  31. by_framework-0.2.2.dev12/libs/by-framework-langgraph/README.md +89 -0
  32. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +49 -4
  33. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/tests/test_adapter.py +207 -8
  34. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/pyproject.toml +3 -2
  35. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/client/client.py +4 -3
  36. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/constants.py +243 -0
  37. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/event_type.py +5 -0
  38. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/registry.py +35 -0
  39. by_framework-0.2.2.dev12/src/by_framework/core/wait_gate.py +209 -0
  40. by_framework-0.2.2.dev12/src/by_framework/core/wait_index.py +165 -0
  41. by_framework-0.2.2.dev12/src/by_framework/core/wait_reply.py +167 -0
  42. by_framework-0.2.2.dev12/src/by_framework/core/wait_sweeper.py +1126 -0
  43. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/metrics/snapshot.py +34 -3
  44. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/context.py +395 -108
  45. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/processor.py +113 -9
  46. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/runner.py +73 -0
  47. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/worker.py +230 -13
  48. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/test_registry.py +5 -0
  49. by_framework-0.2.2.dev12/tests/core/test_wait_gate.py +427 -0
  50. by_framework-0.2.2.dev12/tests/core/test_wait_index.py +133 -0
  51. by_framework-0.2.2.dev12/tests/core/test_wait_sweeper.py +689 -0
  52. by_framework-0.2.2.dev12/tests/integration/test_nested_chain.py +667 -0
  53. by_framework-0.2.2.dev12/tests/integration/test_orphan_recovery.py +1263 -0
  54. by_framework-0.2.2.dev12/tests/integration/test_scatter_gather.py +857 -0
  55. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_context.py +694 -1
  56. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_gateway_worker.py +433 -0
  57. by_framework-0.2.2.dev12/tests/worker/test_processor.py +315 -0
  58. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_runner.py +252 -4
  59. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/uv.lock +430 -2
  60. by_framework-0.2.2.dev11/docs/architecture/KEY_FILES.md +0 -186
  61. by_framework-0.2.2.dev11/libs/by-framework-langgraph/README.md +0 -46
  62. by_framework-0.2.2.dev11/tests/integration/test_scatter_gather.py +0 -68
  63. by_framework-0.2.2.dev11/tests/worker/test_processor.py +0 -132
  64. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.codex/hooks.json +0 -0
  65. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.dockerignore +0 -0
  66. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  67. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  68. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  69. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/RELEASING.md +0 -0
  70. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/workflows/ci.yml +0 -0
  71. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/workflows/deploy-smoke-test.yml +0 -0
  72. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/workflows/publish.yml +0 -0
  73. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/workflows/stale.yml +0 -0
  74. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.github/workflows/verify.yml +0 -0
  75. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.gitignore +0 -0
  76. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/.pre-commit-config.yaml +0 -0
  77. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/AGENTS.md +0 -0
  78. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/CHANGELOG.md +0 -0
  79. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/CODE_OF_CONDUCT.md +0 -0
  80. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/CONTRIBUTING.md +0 -0
  81. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/LICENSE +0 -0
  82. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/Makefile +0 -0
  83. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/README.md +0 -0
  84. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/README_zh.md +0 -0
  85. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/SECURITY.md +0 -0
  86. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/assets/img/architecture_en.png +0 -0
  87. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/assets/img/architecture_zh.png +0 -0
  88. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/autoformat.sh +0 -0
  89. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/deploy/Dockerfile +0 -0
  90. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/deploy/docker-compose.yml +0 -0
  91. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/deploy/entrypoint.sh +0 -0
  92. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/deploy/kubernetes/worker-deployment.yaml +0 -0
  93. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/README.md +0 -0
  94. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/architecture/GUARD_AUTHORING.md +0 -0
  95. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/architecture/production-deployment.md +0 -0
  96. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/architecture/redis-cluster-mode.md +0 -0
  97. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/architecture/worker-readiness-endpoint.md +0 -0
  98. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/by-framework-trace-change-usage.html +0 -0
  99. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/by-framework-trace-complete-guide.html +0 -0
  100. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  101. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  102. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/docs/worker_management.html +0 -0
  103. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/examples/__init__.py +0 -0
  104. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/examples/echo_worker.py +0 -0
  105. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/examples/send_and_verify.py +0 -0
  106. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/README.md +0 -0
  107. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/pyproject.toml +0 -0
  108. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  109. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  110. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  111. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-adk/tests/test_worker.py +0 -0
  112. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/Dockerfile +0 -0
  113. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/README.md +0 -0
  114. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/index.html +0 -0
  115. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  116. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/package.json +0 -0
  117. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  118. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  119. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  120. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/pyproject.toml +0 -0
  121. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  122. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  123. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
  124. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
  125. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
  126. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
  127. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  128. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-byclaw/README.md +0 -0
  129. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  130. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  131. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  132. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  133. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-postgres/README.md +0 -0
  134. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  135. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  136. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  137. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  138. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/pyproject.toml +0 -0
  139. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  140. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  141. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  142. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  143. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  144. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  145. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-langfuse/README.md +0 -0
  146. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  147. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
  148. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
  149. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  150. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/README.md +0 -0
  151. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  152. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  153. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  154. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  155. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  156. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/pyproject.toml +0 -0
  157. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
  158. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
  159. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
  160. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
  161. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
  162. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
  163. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/pylintrc +0 -0
  164. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/.map.conf +0 -0
  165. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/check-doc-discipline.sh +0 -0
  166. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/check-entry-freshness.sh +0 -0
  167. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/check-map-territory.sh +0 -0
  168. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/map-stop-hook.sh +0 -0
  169. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/python_quality.sh +0 -0
  170. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/test-growth-ladder.sh +0 -0
  171. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/scripts/verify.sh +0 -0
  172. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/__init__.py +0 -0
  173. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/__main__.py +0 -0
  174. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/admin/__init__.py +0 -0
  175. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/admin/cli.py +0 -0
  176. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/admin/worker_manager.py +0 -0
  177. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/client/__init__.py +0 -0
  178. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/client/byai_client.py +0 -0
  179. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/__init__.py +0 -0
  180. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/config.py +0 -0
  181. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/emitter.py +0 -0
  182. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/exceptions.py +0 -0
  183. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/logger.py +0 -0
  184. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/common/redis_client.py +0 -0
  185. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/__init__.py +0 -0
  186. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/availability.py +0 -0
  187. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/delivery_gate.py +0 -0
  188. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/discovery.py +0 -0
  189. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/__init__.py +0 -0
  190. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/agent_config.py +0 -0
  191. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
  192. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/plugin.py +0 -0
  193. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/registry.py +0 -0
  194. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/extensions/trace_provider.py +0 -0
  195. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/__init__.py +0 -0
  196. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/action_type.py +0 -0
  197. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/agent_state.py +0 -0
  198. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/byai_codec.py +0 -0
  199. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/byai_command.py +0 -0
  200. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/byai_types.py +0 -0
  201. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/commands.py +0 -0
  202. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/content_codec.py +0 -0
  203. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/content_type.py +0 -0
  204. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/data_message.py +0 -0
  205. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/data_shapes.py +0 -0
  206. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/events.py +0 -0
  207. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/message.py +0 -0
  208. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/message_header.py +0 -0
  209. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/responses.py +0 -0
  210. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/protocol/results.py +0 -0
  211. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/__init__.py +0 -0
  212. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  213. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  214. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/file_manager.py +0 -0
  215. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/file_paths.py +0 -0
  216. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/file_permissions.py +0 -0
  217. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  218. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/filestore/base.py +0 -0
  219. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/filestore/local.py +0 -0
  220. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/history/__init__.py +0 -0
  221. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/history/base.py +0 -0
  222. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  223. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  224. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/runtime/session_manager.py +0 -0
  225. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/wakeup_controller.py +0 -0
  226. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/core/workspace.py +0 -0
  227. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/__init__.py +0 -0
  228. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/base.py +0 -0
  229. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/common.py +0 -0
  230. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/execution.py +0 -0
  231. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/http.py +0 -0
  232. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/protocol.py +0 -0
  233. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/errors/registry.py +0 -0
  234. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/metrics/__init__.py +0 -0
  235. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/metrics/catalog.py +0 -0
  236. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/metrics/collector.py +0 -0
  237. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/metrics/read_client.py +0 -0
  238. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/trace/__init__.py +0 -0
  239. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/trace/external_trace.py +0 -0
  240. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/trace/span_recorder.py +0 -0
  241. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/trace/trace_schema.py +0 -0
  242. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/trace/trace_writer.py +0 -0
  243. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/util/__init__.py +0 -0
  244. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/util/discovery_http_client.py +0 -0
  245. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/util/generate_message_id.py +0 -0
  246. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/util/http_client.py +0 -0
  247. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/__init__.py +0 -0
  248. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/_control_handling.py +0 -0
  249. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/_execution_tracking.py +0 -0
  250. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/_message_processing.py +0 -0
  251. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/app.py +0 -0
  252. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/byai_context.py +0 -0
  253. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/byai_worker.py +0 -0
  254. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/health_server.py +0 -0
  255. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/heartbeat.py +0 -0
  256. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/sandbox/__init__.py +0 -0
  257. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  258. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/admin/__init__.py +0 -0
  259. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/admin/test_cli.py +0 -0
  260. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/client/__init__.py +0 -0
  261. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/client/test_client.py +0 -0
  262. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/__init__.py +0 -0
  263. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/test_config.py +0 -0
  264. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/test_constants.py +0 -0
  265. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/test_exceptions.py +0 -0
  266. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/test_logger.py +0 -0
  267. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/common/test_redis_client.py +0 -0
  268. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/conftest.py +0 -0
  269. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/__init__.py +0 -0
  270. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/protocol/__init__.py +0 -0
  271. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/protocol/test_byai_codec.py +0 -0
  272. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/protocol/test_command_wire.py +0 -0
  273. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/protocol/test_protocol.py +0 -0
  274. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/protocol/test_results.py +0 -0
  275. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/history/__init__.py +0 -0
  276. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/history/test_history_persistence.py +0 -0
  277. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_file_access_context.py +0 -0
  278. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  279. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_file_paths.py +0 -0
  280. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_file_permissions.py +0 -0
  281. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  282. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  283. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_filestore_local_read.py +0 -0
  284. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/runtime/test_filestore_local_search.py +0 -0
  285. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/test_agent_config_audit.py +0 -0
  286. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/test_availability.py +0 -0
  287. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/test_discovery.py +0 -0
  288. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/core/test_worker_manager.py +0 -0
  289. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/integration/__init__.py +0 -0
  290. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/integration/test_ask_user_flow.py +0 -0
  291. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/integration/test_callback_flow.py +0 -0
  292. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/integration/test_logger_integration.py +0 -0
  293. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/metrics/test_catalog.py +0 -0
  294. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/metrics/test_collector.py +0 -0
  295. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/metrics/test_metrics.py +0 -0
  296. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/metrics/test_read_client.py +0 -0
  297. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/metrics/test_snapshot.py +0 -0
  298. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/__init__.py +0 -0
  299. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/test_langfuse_plugin.py +0 -0
  300. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/test_plugin_discovery.py +0 -0
  301. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/test_plugin_improvements.py +0 -0
  302. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/test_plugin_registry.py +0 -0
  303. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/plugin/test_plugin_system.py +0 -0
  304. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/test_main.py +0 -0
  305. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/trace/test_external_trace.py +0 -0
  306. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/trace/test_logger_correlation.py +0 -0
  307. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/trace/test_otel_exporter.py +0 -0
  308. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/trace/test_span_recorder.py +0 -0
  309. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/trace/test_trace_writer.py +0 -0
  310. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/util/test_discovery_http_client.py +0 -0
  311. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/util/test_discovery_http_client_download.py +0 -0
  312. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/util/test_discovery_http_client_upload.py +0 -0
  313. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/util/test_http_client.py +0 -0
  314. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/__init__.py +0 -0
  315. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_admin_control_handling.py +0 -0
  316. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_app.py +0 -0
  317. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_byai_worker.py +0 -0
  318. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_control_handling.py +0 -0
  319. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_emitter.py +0 -0
  320. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_fetch_messages.py +0 -0
  321. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_health_server.py +0 -0
  322. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_heartbeat.py +0 -0
  323. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_message_processing.py +0 -0
  324. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_sandbox.py +0 -0
  325. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev12}/tests/worker/test_workspace.py +0 -0
@@ -165,6 +165,7 @@ Pre-commit hooks are configured in `.pre-commit-config.yaml` and run isort, ruff
165
165
  | Redis connection setup, cluster-mode, or key-schema versioning (`RedisConfig`, `RedisKeys`, `_get_redis()`, admin-index writes) | `docs/architecture/redis-cluster-mode.md` |
166
166
  | Worker deployment/production-readiness — README's 部署 section, `__main__.py` CLI flags, `run_worker()`'s signature, or shutdown/signal handling | `docs/architecture/production-deployment.md` |
167
167
  | Worker readiness/health-check endpoint (`WorkerHealthServer`, `/readyz`, `--health-port`) — building it, or touching anything that changes what "ready" means | `docs/architecture/worker-readiness-endpoint.md` |
168
+ | suspend/resume liveness — the wait index, its idempotency gate, or the sweep that resolves callers whose reply never arrives | `docs/architecture/suspend-resume-liveness.md` |
168
169
 
169
170
  ## Maintaining this map
170
171
 
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: by-framework
3
- Version: 0.2.2.dev11
3
+ Version: 0.2.2.dev12
4
4
  Summary: 分布式 Agent 调度框架
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
7
7
  Requires-Dist: dill>=0.4.1
8
8
  Requires-Dist: httpx>=0.28.1
9
- Requires-Dist: redis>=7.0.0
9
+ Requires-Dist: redis==7.4.0
10
10
  Requires-Dist: typing-extensions>=4.0.0
11
11
  Provides-Extra: cli
12
12
  Requires-Dist: rich>=13.0.0; extra == 'cli'
@@ -0,0 +1,465 @@
1
+ # Key files — per-file index
2
+
3
+ On-demand reference, routed from CLAUDE.md's Reference map.
4
+ **Read a file's entry before editing that file. After editing, rewrite the
5
+ entry in place if behaviour changed.**
6
+
7
+ Entries describe CURRENT behaviour + load-bearing invariants only. Release
8
+ history lives in the changelog + git, never here — enforced by
9
+ `scripts/check-doc-discipline.sh`.
10
+
11
+ **Two doc types share this on-demand layer.** This index holds one entry per
12
+ file (single-file invariants). Knowledge about how *several* files interact —
13
+ or an invariant that would otherwise repeat across many entries — belongs in a
14
+ **subsystem doc** (`docs/architecture/<x>.md`, routed by a Reference-map row),
15
+ not smeared across entries. Lift it up when it spans files; see "Diarize a
16
+ subsystem".
17
+
18
+ ## Diarize a file
19
+
20
+ An entry is a **diarization**: read many sources, write one page of judgement.
21
+ The move — whether you're seeding this index or the growth guard just flagged a
22
+ stale entry — is always the same:
23
+
24
+ 1. **Read three sources**: the file, its tests, and the last ~10 commits that
25
+ touched it (`git log -p -10 -- <file>`). The odd, specific test assertions
26
+ and the fix commits are where invariants hide.
27
+ 2. **Extract only load-bearing invariants** — what must not break when editing
28
+ this file, precise to the expression level. A recorded incident number
29
+ earns its place. Skip anything the code already states plainly.
30
+ 3. **Write one entry**: a one-line role, then the invariants. **No feature
31
+ lists** — features are legible from the code; invariants are not.
32
+
33
+ The discipline is subtractive: if a sentence describes what the file *does*
34
+ rather than what must *hold*, cut it.
35
+
36
+ Entry anatomy:
37
+
38
+ - `src/path/file.ts` — one-line role. Load-bearing invariants, precise to
39
+ the expression level (e.g. "`ctx.remote === false` for trusted-only
40
+ sites"): what must not break when editing this file. No feature lists —
41
+ the code already says what it does.
42
+
43
+ ## Entries
44
+
45
+ - `src/by_framework/worker/app.py` — Worker bootstrap (`run_worker`/`_run_worker_async`):
46
+ resolves Redis config, wires plugins, starts the runner. Redis-connection
47
+ precedence (see [[redis-cluster-mode]]) must be replicated here identically
48
+ to `common/config.py` and `admin/cli.py`. `max_concurrency + 10` is the
49
+ default connection-pool size when neither `redis_max_connections` nor
50
+ `BYAI_REDIS_MAX_CONNECTIONS` is set — don't decouple pool sizing from
51
+ concurrency without updating both. `_build_auto_trace_plugin()` must raise
52
+ if more than one `by_framework_trace_*` provider factory activates from
53
+ env — silently picking one would hide a misconfiguration. `close_redis()`
54
+ must stay in the `finally` block (including on `asyncio.CancelledError`)
55
+ so restarts don't leak the connection pool. `health_port` (readiness
56
+ endpoint, see [[worker-readiness-endpoint]]) is opt-in only — unlike
57
+ `max_concurrency`/`fetch_count`, its `BYAI_WORKER_HEALTH_PORT` env-var
58
+ fallback must never resolve to a default port number; leave it `None`
59
+ when unset so no port opens for deployments that never asked for one.
60
+
61
+ - `src/by_framework/common/config.py` — `RedisConfig`/`WorkerConfig`/`LoggingConfig`
62
+ env-loaded dataclasses. `RedisConfig.from_env()`'s cluster-mode/key-schema
63
+ precedence must stay mirrored across files — see [[redis-cluster-mode]].
64
+ `REDIS_DB` must keep working as a deprecated fallback (with a warning) for
65
+ `REDIS_DATABASE` — don't remove until deprecation is done.
66
+ `RedisConfig.max_connections` must stay `Optional[int] = None` (meaning
67
+ "unset") — `redis_client.init_redis()` distinguishes "unset" from an
68
+ explicit value; defaulting it to a concrete int would silently discard a
69
+ caller's explicit `max_connections` kwarg (fix 6ec070c).
70
+ `WorkerConfig.heartbeat_lease_ttl_seconds` defaults to 30s = 6x the 5s
71
+ heartbeat interval (`RedisKeys.WORKER_DEFAULT_LEASE_TTL_SECONDS`) — this
72
+ margin is a deliberate second line of defense against event-loop stalls;
73
+ don't shrink one without the other.
74
+
75
+ - `src/by_framework/admin/cli.py` — `by-admin` Typer CLI for cluster ops
76
+ (worker list/suspend/evict, type deny/allow, metrics snapshot). `_get_redis()`
77
+ must replicate the SDK-wide Redis resolution order — see
78
+ [[redis-cluster-mode]]. The module-global `_redis_url` must be assigned
79
+ unconditionally in `_global()` (not only `if redis_url:`), so a prior CLI
80
+ invocation's URL doesn't leak into a later one that didn't pass
81
+ `--redis-url`. `--help` text literally contains `REDIS_MODE=cluster` /
82
+ `REDIS_CLUSTER_NODES` / `REDIS_KEY_SCHEMA_VERSION=v2` and is pinned by
83
+ `test_help_mentions_cluster_env_configuration` — keep help text and actual
84
+ precedence logic in sync.
85
+
86
+ - `src/by_framework/common/constants.py` — Central Redis key/naming registry
87
+ (`RedisKeys`), key-schema versioning (v1/v2), core timing constants. Every
88
+ key factory must route through `_versioned()`, and every SCAN-based
89
+ enumeration must use the paired `_worker_scan_pattern()` /
90
+ `_worker_id_from_scanned_key()` helpers — see [[redis-cluster-mode]].
91
+ `get_key_schema_version()`'s precedence deliberately does *not* infer v2
92
+ from `REDIS_MODE=cluster` alone — must stay mirrored with
93
+ `RedisConfig.from_env()`'s mode precedence. Cross-entity index keys
94
+ (`admin_workers()`, `trace_index_session/worker/agent`) are deliberately
95
+ left *untagged* relative to the per-entity keys they index — never share a
96
+ Cluster hash tag with them (fix 8501407); `wait_index()` is one of them.
97
+ `WORKER_DEFAULT_LEASE_TTL_SECONDS = 30` must stay ~6x
98
+ `WORKER_DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 5`. `WAIT_INDEX_SHARDS` is a
99
+ cross-SDK protocol constant, not a tunable — changing it re-maps every
100
+ session to a different shard, so entries written before the change are
101
+ swept by nobody. `DEFAULT_ASK_USER_TIMEOUT_MS` (machine waiting on a
102
+ human, sized off `DEFAULT_SESSION_TTL`, which is in *seconds*) must stay
103
+ decoupled from `DEFAULT_REPLY_TIMEOUT_MS` (machine waiting on machine) —
104
+ one shared value either kills a human's turn or lets a hung callee sit.
105
+ `LivenessErrorCode` values are wire contract: append only, never rename.
106
+ `CLIENT_SOURCE_AGENT_TYPE` is the marker a client stamps on an execution
107
+ record it dispatched (TS writes the same string; Java writes no field at
108
+ all) — it is a sentinel, not an agent type, and every reader that treats a
109
+ record's `source_agent_type` as somewhere to send a reply must exclude it.
110
+ `wait_consumed()` is the idempotency gate's "already resolved" marker;
111
+ `WAIT_CONSUMED_TTL_SECONDS` bounds how far apart two copies of one reply
112
+ may be and still be recognized as duplicates, and is sized off
113
+ `DEFAULT_SESSION_TTL` — the marker must outlive every wait it may have to
114
+ arbitrate, and the longest is `ask_user`'s, whose deadline *equals* the
115
+ session TTL. Sizing it shorter (it was `TASK_GROUP_TTL_SECONDS`) reopens
116
+ two holes: a repeated user answer stops being a duplicate, and — worse — a
117
+ stale duplicate sub-agent reply that loses its own marker falls through to
118
+ the `ask_user` candidate for the same caller and claims a wait that is
119
+ still live, after which the real answer is dropped. `wait_sweep_lock()`
120
+ guards one wait-index shard while it is swept — see
121
+ [[suspend-resume-liveness]] for why its ownership is advisory rather than
122
+ a leader election, and why `WAIT_SWEEP_LOCK_TTL_SECONDS` must outlast one
123
+ shard's pass. `wait_renew_origin()` remembers a wait's *original* deadline
124
+ across the renewals that overwrite it; `WAIT_RENEW_MAX_MULTIPLE` is the
125
+ budget measured from it, and its TTL must stay well above the largest
126
+ `N * timeout` in use or the budget silently restarts mid-wait.
127
+ `WAIT_PRUNE_AFTER_SECONDS` is how far in the past an entry's score must
128
+ lie before the sweep's prune half deletes it unexamined; it must stay
129
+ *strictly* above `DEFAULT_SESSION_TTL`, because `DEFAULT_ASK_USER_TIMEOUT_MS`
130
+ equals that exactly and a threshold trimmed to it would sit on the boundary
131
+ of a live ask_user wait.
132
+
133
+ - `src/by_framework/core/wait_index.py` — Pure codec for the wait-index ZSET
134
+ member (`{session_id}|{parent_message_id}|{child_message_id}|{task_group_id}`)
135
+ plus shard selection. Every field must remain derivable from a single
136
+ `ResumeCommand` — `member_from_resume()` is what lets the idempotency gate
137
+ `ZREM` before any registry lookup, so a field a reply doesn't carry can
138
+ never be added. That function also *reverses* the header ids (a reply's
139
+ `header.message_id` is the caller's, its `header.parent_message_id` is the
140
+ sub-task's); keying by the reply's own `message_id` collapses every Task
141
+ Group sibling onto one member. `session_id`/`message_id` are
142
+ caller-controlled, so `|` and `\` are escaped rather than assumed absent.
143
+ `wait_index_shard()` must stay a fixed, language-portable hash (FNV-1a
144
+ 32-bit) — Python's `hash()` is per-process salted, and TS/Java sweepers
145
+ must land on the same shard; `member_digest()` (SHA-1 hex) is the same deal
146
+ for every key named after a member. Keep this module I/O-free.
147
+
148
+ - `src/by_framework/core/wait_gate.py` — The idempotency gate: `ZREM`s the
149
+ caller's wait-index entry so exactly one copy of a reply wakes it. Its
150
+ whole difficulty is that `ZREM` returning 0 conflates "someone already
151
+ claimed this wait" with "this wait was never registered" (a pre-upgrade
152
+ dispatch, or an expired entry) — hence the `wait_consumed` marker written
153
+ by the winner, which is the *only* thing that separates them. Treating an
154
+ unmarked 0 as a duplicate drops every in-flight reply during a rolling
155
+ upgrade. Both this and the surrounding `except` must keep failing **open**:
156
+ a duplicated wake-up is recoverable, a dropped reply is permanent silence,
157
+ so any doubt (Redis error, missing marker) allows the message. Concurrent
158
+ copies are arbitrated by `ZREM`'s atomicity, not by the marker; the marker
159
+ only matters for copies separated in time, which is why the write-after-
160
+ claim window is not worth closing. `candidate_members()` must resolve each
161
+ candidate fully (claim, then check *its* marker) before trying the next:
162
+ the second candidate exists only because `ask_user` registers with an empty
163
+ `child_message_id` while its reply carries a client-chosen
164
+ `parent_message_id`, and falling through to it early would clear a live
165
+ ask_user wait belonging to the same caller. `emit_orphaned_reply()` is
166
+ observability for work that will now be thrown away — fail-soft, and never
167
+ able to change the drop/allow decision.
168
+
169
+ - `src/by_framework/core/wait_sweeper.py` — `WaitIndexSweeper`: the only
170
+ thing that can move a caller whose reply is never coming, since a
171
+ suspended caller has *ended* and left no timer behind. Subsystem context
172
+ and the full triage table: [[suspend-resume-liveness]]. It must never
173
+ `ZREM` the entry it acts on — the synthesized reply and a late real one
174
+ are meant to compete for the same entry in `wait_gate.py`, and clearing it
175
+ here forces the synthesized copy to bypass the gate, i.e. a second
176
+ ungated wake-up path. A callee that is itself suspended must be *renewed*,
177
+ never failed: the innermost wait has the earliest deadline by
178
+ construction, so failure climbs a chain hop by hop instead of collapsing
179
+ it — and that is also why such a callee is exempt from the renewal
180
+ ceiling, which arrives *earliest* for the outermost wait and would fail a
181
+ chain top-down. A live worker lease buys more time (that is what keeps
182
+ slow work from being killed) but only up to
183
+ `registered_at + WAIT_RENEW_MAX_MULTIPLE * timeout`, after which the callee
184
+ is failed with `CHILD_TIMEOUT`: a lease says the process is up, not that
185
+ the work is moving, so renewing on it alone hangs the caller forever. The
186
+ budget must be re-derived from the saved `wait_renew_origin`, never from
187
+ the score a renewal just wrote, and never from whether the callee's
188
+ `updated_at` advanced — a legitimate long model call stands just as still
189
+ as a deadlock. A Task Group orphan gets the *same* synthesized reply,
190
+ carrying the group id, so the existing join counts it; this file must
191
+ never write `task_group_results`/`completed` itself, or the copy that
192
+ reaches `total` leaves no reply to run the join. A caller that is already
193
+ terminal gets its entry cleaned up
194
+ and *no* reply — registration happens before the dispatch `xadd`, so a
195
+ failed `xadd` leaves exactly that. Synthesized failures must stay shaped
196
+ like a sub-agent's own failure (`error`/`error_code` in `reply_data`,
197
+ same stream, same header id reversal). Two switches, and they must stay
198
+ separate: *compensation* (the triage, replies, renewals, cancellation) is
199
+ off unless `BY_FRAMEWORK_WAIT_SWEEPER_ENABLED` is set and is the rollback
200
+ switch for the whole liveness feature, while *pruning* is on by default
201
+ (`BY_FRAMEWORK_WAIT_PRUNE_ENABLED`) because nothing else ever removes a
202
+ wait-index entry — with compensation off, every call whose reply never
203
+ arrives would leak one forever into a shard ZSET that is shared across
204
+ sessions and so cannot carry a TTL. Pruning may stay unguarded only because
205
+ it decides nothing: it decodes no member and reads no execution record,
206
+ just one `ZREMRANGEBYSCORE` whose bound is a proof (every writer sets the
207
+ score to its own clock plus a non-negative offset, and only while the
208
+ caller's execution record exists, so an old score means the session
209
+ registry is gone and triage could reach nothing but "caller missing").
210
+ That argument covers renewed entries too — a renewal *raises* the score.
211
+ `CHILD_TIMEOUT` — and only it, since the
212
+ other outcomes have no live process on the other end — also asks the callee
213
+ to stop, by delegating wholesale to `GatewayClient.cancel_task` (which
214
+ targets `worker_ctrl_stream(worker_id)`; a cancel on the agent type's
215
+ competitive stream is claimed by an arbitrary worker whose in-memory table
216
+ has no such execution, so it records a cancellation while cancelling
217
+ nothing). That request must stay strictly *after* the reply is emitted and
218
+ strictly swallowed on failure: cancellation is cooperative, so a callee
219
+ wedged in a blocking call is both why the ceiling fired and the case it
220
+ cannot reach — nothing in the wake-up may depend on it. It also does not
221
+ silence the callee, whose own `CANCELLED` reply is dropped by the gate.
222
+ Opt out with `BY_FRAMEWORK_WAIT_CANCEL_ON_TIMEOUT`; per-call opt-out is
223
+ deliberately not offered (see the file's own note — the member is a wire
224
+ format, so it would cost a side-key write on every dispatch). Fail-soft
225
+ throughout: it runs inside every worker, so a raise here must not take the
226
+ worker down, and one bad entry must not abort a shard.
227
+
228
+ - `src/by_framework/core/wait_reply.py` — The single construction of a reply
229
+ built by someone other than the callee that owed it: the sweeper's
230
+ stand-ins and `call_agents`' compensation for a sub-task whose target agent
231
+ type was never available. Two spellings of that message drift, and the
232
+ drift only shows up as a hung caller, which is why neither site builds it
233
+ itself. `header.message_id` must be the caller's id (what the runner
234
+ reattaches by) and `header.parent_message_id` the sub-task's (what the join
235
+ keys by and what `wait_index.member_from_resume` rebuilds); the failure
236
+ detail must ride in `reply_data`, exactly where a sub-agent that ran and
237
+ raised puts it, so no caller can tell "failed" from "never got to fail".
238
+ Provenance (`synthesized_by`, `liveness_error_code`) goes on
239
+ `header.metadata` and must stay out of `reply_data` for the same reason.
240
+ `flush_pending_group_replies()` is called only after a handler returns
241
+ normally — inline delivery would put a reply on the caller's own control
242
+ stream strictly before it suspends, making a rare race certain, and a
243
+ handler that raised has already aborted its group.
244
+
245
+ - `src/by_framework/client/client.py` — `GatewayClient.send_message()` and
246
+ friends; publishes commands to Redis control streams and drives registry
247
+ execution-tracking as a side effect. On a `RESUME` dispatch, must look up
248
+ the original execution via `registry.get_execution_by_message_id(message_id,
249
+ session_id=...)`, reuse *that* `execution_id`, and skip
250
+ `initialize_execution()` for it — calling `initialize_execution()`
251
+ unconditionally silently detaches the `ResumeCommand` from the suspended
252
+ `WAITING_USER` execution it's meant to continue, orphaning it (fix
253
+ 90764e1, #75/#76/#77). The registry lookup must stay guarded with
254
+ `hasattr(registry, "get_execution_by_message_id")` so registry doubles/older
255
+ implementations fall back to minting a fresh execution_id. Root-dispatch
256
+ trace writes (`_write_trace_root_start/_end`) must only fire when
257
+ `not parent_message_id` — firing them on every `call_agent` hop would
258
+ duplicate trace roots.
259
+
260
+ - `src/by_framework/worker/runner.py` — `WorkerRunner`, the consume loop:
261
+ `XREADGROUP` fetch, command dispatch, resume/suspend bookkeeping, denylist
262
+ enforcement. `_active_agent_type_streams()` must read only the in-memory
263
+ `self._denied_agent_types` frozenset — no Redis `SISMEMBER` call inside the
264
+ hot consume-loop path; refreshed only by the heartbeat thread's
265
+ `denylist_refresh` callback (bounded staleness ~1 heartbeat interval) (fix
266
+ 8f23c78). The frozenset must be swapped by whole-reference reassignment,
267
+ relying on CPython GIL atomicity across the heartbeat thread and the async
268
+ loop — never mutate the set in place without adding a lock. A
269
+ `ResumeCommand` that fails to resolve to an existing execution must log a
270
+ warning — silently starting a disconnected new execution is the exact
271
+ failure mode this log surfaces (fix 90764e1, #77). Terminal-state
272
+ replay-skip logic is coupled to `ResumeCommand` handling: skip replaying an
273
+ execution already in a terminal state *unless* the command is a
274
+ `ResumeCommand`. `_health_server` (see [[worker-readiness-endpoint]]) must
275
+ start before any other step in `start()` (currently first line of the
276
+ `try:` block) so a probe hitting the port during startup gets an honest
277
+ `starting` 503 instead of connection-refused, and must `stop()` as the
278
+ *last* step of `_shutdown()` — after every other teardown step, not
279
+ before — so `/readyz` stays reachable (reporting `draining`) for the
280
+ entire drain. `self._draining = True` must stay the first line of
281
+ `_shutdown()`, ahead of every other teardown step, not just ahead of the
282
+ health-server stop. `is_resumed_execution` infers "already been through a
283
+ worker" from `existing_execution["status"] != QUEUED`, so QUEUED must stay
284
+ the *only* status an execution can hold before its first pickup — a
285
+ suspended caller persists as `WAITING_AGENT`/`WAITING_USER` precisely to
286
+ keep that inference true; reusing QUEUED for any post-pickup state silently
287
+ makes a resume re-derive its identity from the message header instead of
288
+ the record. Every `ResumeCommand` passes the `core/wait_gate.py`
289
+ idempotency gate *here*, before the execution lookup — and being upstream
290
+ of `GatewayWorker` is what also puts it before Task Group join, whose
291
+ `HINCRBY completed` a duplicate would push past `total` and aggregate a
292
+ second time. A dropped reply is acked and reported, never left pending.
293
+ Background tasks (`MetricsCollector`, `WaitIndexSweeper`) are started
294
+ best-effort — a background component that fails to construct must never
295
+ prevent the worker from consuming — and every one of them must be
296
+ cancelled and awaited in `_shutdown()`, or shutdown hangs on it.
297
+
298
+ - `src/by_framework/worker/health_server.py` — `WorkerHealthServer`: the
299
+ `/readyz` readiness HTTP endpoint, on its own daemon thread (mirrors
300
+ `heartbeat.py`'s "don't share the main event loop" pattern — see that
301
+ file's own docstring). Full design record, including why this exists and
302
+ the hard rule against ever wiring it to a liveness check:
303
+ [[worker-readiness-endpoint]]. `_compute_reason()`'s check order is the
304
+ entire contract — `starting > draining > evicted > suspended >
305
+ consumer_stalled > serving`, first match wins; reordering these checks
306
+ silently changes what an operator is told during a real incident. All
307
+ Worker state is read via constructor-injected callables (`has_started`,
308
+ `is_draining`, `admin_lifecycle`, `consumer_healthy`) — this class must
309
+ never reach into `WorkerRunner` directly, which is what keeps it testable
310
+ standalone against fake state (see `tests/worker/test_health_server.py`).
311
+
312
+ - `src/by_framework/worker/worker.py` — `GatewayWorker`: per-message lifecycle
313
+ (`_handle_message`), Task Group join, and the agent-return reply.
314
+ `_enqueue_agent_return()` builds the *only* thing that resumes a suspended
315
+ caller — its header must keep `message_id` = the caller's
316
+ `parent_message_id` and `parent_message_id` = this sub-task's own
317
+ `message_id`; Group Join keys `task_group_results` by the latter because
318
+ it's the only per-sibling-unique value (fix 9d4a0a4). Group Join must stay
319
+ the single accounting path: a result written or `completed` incremented
320
+ anywhere else can be the increment that reaches `total`, leaving no reply
321
+ to wake the caller (fix 55c7e6f; a dispatch-time failure must instead emit
322
+ the `FAILED` reply a sub-agent would have sent).
323
+ `_persist_single_call_result()` covers the non-group path only (early-return
324
+ when `header.task_group_id` is set, or the group's own write is duplicated)
325
+ and must stay fail-soft — a persist error costs recoverability, never the
326
+ reply. Its stored payload must stay field-for-field isomorphic with the
327
+ `result_data` the join path writes; both are read by the same recovery
328
+ code. `should_emit_stream_end` reads `context._is_suspended` /
329
+ `_permission_transferred`, so those flags' accuracy in `context.py` is
330
+ load-bearing here.
331
+ Who gets replied to comes from `_resolve_reply_command()`, never from a
332
+ resume's own header: a `ResumeCommand`'s `source_agent_type` /
333
+ `parent_message_id` / `task_group_id` describe the *sub-agent* that just
334
+ finished, so a resumed execution must rebuild its caller from the execution
335
+ record `initialize_execution()` wrote (`existing_data`). Treating "is a
336
+ resume" as "has no caller" drops the middle link's result in any chain of
337
+ depth >= 3. That record names `CLIENT_SOURCE_AGENT_TYPE` for anything a
338
+ *client* dispatched, which is a marker and not an agent type — it must be
339
+ excluded explicitly, or every root execution that ever resumes (an
340
+ `ask_user` round is the common one) posts its result to a control stream
341
+ nobody consumes and, worse, stops emitting the end-of-stream event the user
342
+ is waiting on, because it now believes it owes an agent a reply.
343
+ The success path must NOT reply while `context._is_suspended` — a suspended
344
+ execution has no result, and forwarding the value the handler returned in
345
+ order to unwind both wakes the caller early and consumes the single reply it
346
+ was waiting for. "Suspended" here must carry the same terminal-status
347
+ exception as `_apply_suspended_status()`: a handler that returned a terminal
348
+ status is recorded finished and will never resume to reply later, so it owes
349
+ its caller a reply now. The `CancelledError`/`Exception` paths must reply
350
+ anyway: a dead execution will never resume to produce one.
351
+ `_apply_suspended_status()` overwrites a non-terminal business status with
352
+ `context._suspended_state`, so what lands in the registry is
353
+ `WAITING_AGENT`/`WAITING_USER` rather than the handler's placeholder; a
354
+ terminal status always wins.
355
+ `flush_pending_group_replies()` runs immediately after `process_command`
356
+ returns and nowhere else: the stand-ins it delivers belong to a group whose
357
+ caller must already have finished suspending, and a handler that raised has
358
+ aborted its group, so its queued stand-ins must die with it.
359
+
360
+ - `src/by_framework/worker/context.py` — `AgentContext`: the agent-facing
361
+ runtime surface; `_dispatch_single_task()` is the one dispatch path behind
362
+ both `call_agent` and `call_agents`. `_is_suspended` /
363
+ `_permission_transferred` are flipped *before* the availability check, so
364
+ every early return that means "nothing was dispatched" must restore the
365
+ values captured on entry — restoring to `False` instead is wrong, since an
366
+ earlier `call_agent` on the same context may legitimately have suspended
367
+ it. An availability rejection returns a `FAILED` result dict, it does not
368
+ raise; `call_agents` depends on that to keep fanning out. It must
369
+ compensate such a member with a *reply* (`core/wait_reply.py`, queued on
370
+ `_pending_group_replies` for the worker to flush after `process_command`
371
+ returns) and must never write `task_group_results`/`HINCRBY completed`
372
+ itself: that is a second implementation of the join's accounting, and when
373
+ its increment is the one that reaches `total` no reply is left to run the
374
+ join and the caller hangs forever — reachable both when every target is
375
+ offline and when a sibling's reply is joined mid-fan-out. It also registers
376
+ a wait entry for the member it could not dispatch, so an undelivered
377
+ stand-in is still compensable by a sweep. On a genuine dispatch exception
378
+ mid-batch, `call_agents` must mark the group `aborted` before re-raising,
379
+ or already-sent siblings' replies resume a caller that is already dead;
380
+ queued stand-ins die with the raise for the same reason. `call_agents`
381
+ rejects `message_id` outright for more than one task: a group's per-sibling
382
+ identity *is* its sub-task message_id (it keys both `task_group_results` and
383
+ the wait index), so pinning one across the fan-out overwrites every
384
+ sibling's result and leaves the gate one entry to claim, hanging the caller
385
+ short of `total`. It fails loudly rather than silently minting ids, and the
386
+ single-task use — where the id collides with nothing — is unaffected.
387
+ Every `wait_for_reply=True` dispatch registers one wait-index entry via
388
+ `_register_wait()` (one per sub-task for a group, so each can be resolved
389
+ independently), keyed by `parent_message_id` = the id the awaited reply
390
+ carries as `header.message_id`; getting that direction wrong makes the
391
+ entry unmatchable by the reply that should clear it. `ask_user` registers
392
+ with an empty `child_message_id` (no sub-task) and its own, much larger
393
+ timeout — sharing `call_agent`'s would time out a human. Because that
394
+ member repeats across consecutive ask_user rounds, registering also clears
395
+ the previous round's `wait_consumed` marker, or the gate would read round
396
+ 2's answer as round 1's duplicate. Registration is fail-soft and must stay
397
+ so: it is bookkeeping, not the dispatch.
398
+ `initialize_execution()`'s payload carries `source_agent_type` and
399
+ `task_group_id` because they are the only durable record of who a
400
+ suspended callee owes its reply to (see `worker.py`).
401
+ `_suspended_state` records *which* state the execution is waiting in and is
402
+ what the framework persists; keep it set/rolled-back in lockstep with
403
+ `_is_suspended`.
404
+
405
+ - `src/by_framework/worker/processor.py` — `GatewayProcessor`: the standalone
406
+ message-lifecycle path for callers that don't subclass `GatewayWorker`. It
407
+ duplicates `worker.py`'s reply logic, so every reply-side invariant there
408
+ applies here too and the two must be changed together — the shape has
409
+ already drifted once and taken the same bug twice. Specifically:
410
+ `_enqueue_callback()`'s `header.message_id` must be the *caller's*
411
+ message_id (this dispatch's `parent_message_id`), since that is what the
412
+ caller's suspended execution is reattached by; a freshly minted id resolves
413
+ to no execution. `_resolve_reply_header()` must read a resumed execution's
414
+ caller from the execution record, not from the resume header (which names
415
+ the sub-agent), while excluding `CLIENT_SOURCE_AGENT_TYPE` for the reason
416
+ spelled out under `worker.py`; and no callback may be sent while
417
+ `context._is_suspended` (with the same terminal-status exception).
418
+ Being a *second* entry point for replies, it carries the same
419
+ `core/wait_gate.py` gate as `runner.py` — a gate on one of two doors is not
420
+ a gate, and an ungated reply here would both wake a resolved caller and
421
+ leave its wait-index entry behind for a sweep to resolve all over again.
422
+ `process()` returns `None` for a reply it drops. It also flushes
423
+ `call_agents`' queued stand-ins on the same terms as `worker.py` — after
424
+ the handler returns, never when it raised.
425
+
426
+ - `src/by_framework/core/registry.py` — `WorkerRegistry`: Redis-backed worker
427
+ membership/heartbeat/execution-state, admin lifecycle, locking primitives.
428
+ `mark_execution_finished()` must stamp `finished_at` only
429
+ `if is_terminal_state(status)` — stamping it unconditionally makes a
430
+ suspended `WAITING_USER` execution look completed to
431
+ `metrics/snapshot.py`'s latency/`completed_count` math (fix 90764e1, #76).
432
+ `heartbeat_worker()` uses an atomic Lua CAS script with token-mode (verify
433
+ stored token before overwrite) and legacy no-token mode — must not be
434
+ replaced by a plain `SET`; return codes `1`=success / `0`=owned-by-another /
435
+ `-1`=unparseable-legacy are relied on by callers. `_RELEASE_LOCK_SCRIPT` /
436
+ `_REFRESH_LOCK_SCRIPT` are Redlock-style token-verified delete/expire — must
437
+ stay atomic, and an empty-string token means "unconditional" (no-token
438
+ legacy mode). Those two scripts are also the shared Redlock primitives
439
+ behind `acquire_scoped_lock()`/`release_scoped_lock()` (used for wait-index
440
+ shard claims), so the value written must stay a cjson-decodable object
441
+ carrying a `token` field — a bare token string parses as unparseable legacy
442
+ data and leaves the holder unable to release its own lock.
443
+ `set_worker_admin_state`/`clear_worker_admin_state`: the
444
+ per-worker `worker_admin(id)` hash write must complete independently of the
445
+ `admin_workers()` global-index update — see [[redis-cluster-mode]].
446
+
447
+ - `src/by_framework/core/protocol/responses.py` — `SendMessageResponse` /
448
+ `CancelTaskResponse` / `CancelSessionResponse` frozen dataclasses +
449
+ `ExecutionStatus` string constants. `ExecutionStatus` string values
450
+ (`"SUCCESS"`, `"NOT_FOUND"`, `"WORKER_NOT_ONLINE"`, etc.) are a wire-level
451
+ contract matched by literal string elsewhere in the client and its tests —
452
+ renaming a value is a cross-file breaking change.
453
+ `ERR_AGENT_TYPE_NOT_FOUND = ERR_AGENT_TYPE_UNAVAILABLE` is a deliberate
454
+ alias; both names must keep resolving to the same string. Response
455
+ dataclasses are `@dataclass(frozen=True)` — don't drop `frozen` or add
456
+ mutable defaults.
457
+
458
+ - `src/by_framework/core/protocol/content_type.py` — `SseMessageType` /
459
+ `SseReasonMessageType` enums: numeric string codes for SSE messages sent to
460
+ the frontend/other-language SDKs. These codes are an external protocol
461
+ contract — once shipped, a code's meaning must never change; only append
462
+ new codes. `SseReasonMessageType.think_text = "1002"` intentionally reuses
463
+ `SseMessageType.text`'s value — looks like a copy-paste bug but is
464
+ deliberate protocol code reuse; verify against frontend/other-language SDKs
465
+ before "fixing".