by-framework 0.2.2.dev11__tar.gz → 0.2.2.dev13__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.dev13}/CLAUDE.md +1 -0
  2. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/PKG-INFO +3 -3
  3. by_framework-0.2.2.dev13/docs/architecture/KEY_FILES.md +483 -0
  4. by_framework-0.2.2.dev13/docs/architecture/suspend-resume-liveness.md +385 -0
  5. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/src/by_framework_adk/adapter.py +28 -3
  6. by_framework-0.2.2.dev13/libs/by-framework-adk/tests/test_adapter.py +129 -0
  7. by_framework-0.2.2.dev13/libs/by-framework-agent/README.md +119 -0
  8. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/01_basic_chat.py +96 -0
  9. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/02_tools_and_ask_user.py +172 -0
  10. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/03_agent_delegation.py +168 -0
  11. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/04_task_group.py +223 -0
  12. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/05_real_model_react.py +284 -0
  13. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/README.md +41 -0
  14. by_framework-0.2.2.dev13/libs/by-framework-agent/examples/_infra.py +253 -0
  15. by_framework-0.2.2.dev13/libs/by-framework-agent/pyproject.toml +35 -0
  16. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/__init__.py +16 -0
  17. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/litellm_client.py +85 -0
  18. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/loop.py +814 -0
  19. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/model_client.py +45 -0
  20. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/testing.py +37 -0
  21. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/tool_spec.py +41 -0
  22. by_framework-0.2.2.dev13/libs/by-framework-agent/src/by_framework_agent/worker.py +81 -0
  23. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/conftest.py +109 -0
  24. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_agent_as_tool.py +243 -0
  25. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_ask_user_suspend_resume.py +182 -0
  26. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_cancel_cleanup.py +212 -0
  27. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_native_agent_worker.py +314 -0
  28. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_task_group_batching.py +339 -0
  29. by_framework-0.2.2.dev13/libs/by-framework-agent/tests/test_tool_calling_loop.py +331 -0
  30. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +10 -1
  31. by_framework-0.2.2.dev13/libs/by-framework-langgraph/README.md +89 -0
  32. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +49 -4
  33. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/tests/test_adapter.py +207 -8
  34. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/pyproject.toml +3 -2
  35. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/client/client.py +4 -3
  36. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/constants.py +243 -0
  37. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/event_type.py +5 -0
  38. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/registry.py +35 -0
  39. by_framework-0.2.2.dev13/src/by_framework/core/wait_gate.py +209 -0
  40. by_framework-0.2.2.dev13/src/by_framework/core/wait_index.py +165 -0
  41. by_framework-0.2.2.dev13/src/by_framework/core/wait_reply.py +167 -0
  42. by_framework-0.2.2.dev13/src/by_framework/core/wait_sweeper.py +1126 -0
  43. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/metrics/snapshot.py +34 -3
  44. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/context.py +400 -108
  45. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/processor.py +113 -9
  46. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/runner.py +73 -0
  47. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/worker.py +239 -13
  48. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/test_registry.py +5 -0
  49. by_framework-0.2.2.dev13/tests/core/test_wait_gate.py +427 -0
  50. by_framework-0.2.2.dev13/tests/core/test_wait_index.py +133 -0
  51. by_framework-0.2.2.dev13/tests/core/test_wait_sweeper.py +700 -0
  52. by_framework-0.2.2.dev13/tests/integration/test_nested_chain.py +719 -0
  53. by_framework-0.2.2.dev13/tests/integration/test_orphan_recovery.py +1263 -0
  54. by_framework-0.2.2.dev13/tests/integration/test_scatter_gather.py +857 -0
  55. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_context.py +725 -1
  56. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_gateway_worker.py +447 -0
  57. by_framework-0.2.2.dev13/tests/worker/test_processor.py +315 -0
  58. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_runner.py +252 -4
  59. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/.codex/hooks.json +0 -0
  65. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.dockerignore +0 -0
  66. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  67. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  68. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  69. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/RELEASING.md +0 -0
  70. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/workflows/ci.yml +0 -0
  71. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/workflows/deploy-smoke-test.yml +0 -0
  72. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/workflows/publish.yml +0 -0
  73. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/workflows/stale.yml +0 -0
  74. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.github/workflows/verify.yml +0 -0
  75. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.gitignore +0 -0
  76. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/.pre-commit-config.yaml +0 -0
  77. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/AGENTS.md +0 -0
  78. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/CHANGELOG.md +0 -0
  79. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/CODE_OF_CONDUCT.md +0 -0
  80. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/CONTRIBUTING.md +0 -0
  81. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/LICENSE +0 -0
  82. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/Makefile +0 -0
  83. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/README.md +0 -0
  84. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/README_zh.md +0 -0
  85. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/SECURITY.md +0 -0
  86. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/assets/img/architecture_en.png +0 -0
  87. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/assets/img/architecture_zh.png +0 -0
  88. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/autoformat.sh +0 -0
  89. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/deploy/Dockerfile +0 -0
  90. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/deploy/docker-compose.yml +0 -0
  91. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/deploy/entrypoint.sh +0 -0
  92. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/deploy/kubernetes/worker-deployment.yaml +0 -0
  93. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/README.md +0 -0
  94. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/architecture/GUARD_AUTHORING.md +0 -0
  95. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/architecture/production-deployment.md +0 -0
  96. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/architecture/redis-cluster-mode.md +0 -0
  97. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/architecture/worker-readiness-endpoint.md +0 -0
  98. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/by-framework-trace-change-usage.html +0 -0
  99. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/by-framework-trace-complete-guide.html +0 -0
  100. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  101. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  102. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/docs/worker_management.html +0 -0
  103. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/examples/__init__.py +0 -0
  104. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/examples/echo_worker.py +0 -0
  105. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/examples/send_and_verify.py +0 -0
  106. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/README.md +0 -0
  107. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/pyproject.toml +0 -0
  108. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  109. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  110. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  111. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-adk/tests/test_worker.py +0 -0
  112. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/Dockerfile +0 -0
  113. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/README.md +0 -0
  114. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/index.html +0 -0
  115. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  116. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/package.json +0 -0
  117. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  118. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  119. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  120. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/pyproject.toml +0 -0
  121. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  122. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  123. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/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.dev13}/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.dev13}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  128. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-history-byclaw/README.md +0 -0
  129. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  130. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  133. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-history-postgres/README.md +0 -0
  134. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  135. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/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.dev13}/libs/by-framework-langgraph/pyproject.toml +0 -0
  139. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  140. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  141. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  142. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  143. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  144. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  145. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-langfuse/README.md +0 -0
  146. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  147. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  150. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-phoenix/README.md +0 -0
  151. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  152. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  155. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  156. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/libs/by-framework-trace-query/pyproject.toml +0 -0
  157. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13}/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.dev13}/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.dev13}/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.dev13}/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.dev13}/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.dev13}/pylintrc +0 -0
  164. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/.map.conf +0 -0
  165. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/check-doc-discipline.sh +0 -0
  166. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/check-entry-freshness.sh +0 -0
  167. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/check-map-territory.sh +0 -0
  168. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/map-stop-hook.sh +0 -0
  169. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/python_quality.sh +0 -0
  170. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/test-growth-ladder.sh +0 -0
  171. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/scripts/verify.sh +0 -0
  172. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/__init__.py +0 -0
  173. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/__main__.py +0 -0
  174. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/admin/__init__.py +0 -0
  175. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/admin/cli.py +0 -0
  176. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/admin/worker_manager.py +0 -0
  177. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/client/__init__.py +0 -0
  178. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/client/byai_client.py +0 -0
  179. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/__init__.py +0 -0
  180. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/config.py +0 -0
  181. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/emitter.py +0 -0
  182. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/exceptions.py +0 -0
  183. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/logger.py +0 -0
  184. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/common/redis_client.py +0 -0
  185. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/__init__.py +0 -0
  186. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/availability.py +0 -0
  187. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/delivery_gate.py +0 -0
  188. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/discovery.py +0 -0
  189. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/__init__.py +0 -0
  190. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/agent_config.py +0 -0
  191. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
  192. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/plugin.py +0 -0
  193. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/registry.py +0 -0
  194. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/extensions/trace_provider.py +0 -0
  195. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/__init__.py +0 -0
  196. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/action_type.py +0 -0
  197. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/agent_state.py +0 -0
  198. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/byai_codec.py +0 -0
  199. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/byai_command.py +0 -0
  200. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/byai_types.py +0 -0
  201. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/commands.py +0 -0
  202. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/content_codec.py +0 -0
  203. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/content_type.py +0 -0
  204. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/data_message.py +0 -0
  205. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/data_shapes.py +0 -0
  206. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/events.py +0 -0
  207. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/message.py +0 -0
  208. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/message_header.py +0 -0
  209. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/responses.py +0 -0
  210. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/protocol/results.py +0 -0
  211. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/__init__.py +0 -0
  212. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  213. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  214. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/file_manager.py +0 -0
  215. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/file_paths.py +0 -0
  216. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/file_permissions.py +0 -0
  217. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  218. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/filestore/base.py +0 -0
  219. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/filestore/local.py +0 -0
  220. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/history/__init__.py +0 -0
  221. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/history/base.py +0 -0
  222. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  223. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  224. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/runtime/session_manager.py +0 -0
  225. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/wakeup_controller.py +0 -0
  226. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/core/workspace.py +0 -0
  227. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/__init__.py +0 -0
  228. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/base.py +0 -0
  229. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/common.py +0 -0
  230. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/execution.py +0 -0
  231. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/http.py +0 -0
  232. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/protocol.py +0 -0
  233. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/errors/registry.py +0 -0
  234. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/metrics/__init__.py +0 -0
  235. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/metrics/catalog.py +0 -0
  236. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/metrics/collector.py +0 -0
  237. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/metrics/read_client.py +0 -0
  238. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/trace/__init__.py +0 -0
  239. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/trace/external_trace.py +0 -0
  240. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/trace/span_recorder.py +0 -0
  241. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/trace/trace_schema.py +0 -0
  242. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/trace/trace_writer.py +0 -0
  243. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/util/__init__.py +0 -0
  244. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/util/discovery_http_client.py +0 -0
  245. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/util/generate_message_id.py +0 -0
  246. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/util/http_client.py +0 -0
  247. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/__init__.py +0 -0
  248. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/_control_handling.py +0 -0
  249. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/_execution_tracking.py +0 -0
  250. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/_message_processing.py +0 -0
  251. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/app.py +0 -0
  252. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/byai_context.py +0 -0
  253. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/byai_worker.py +0 -0
  254. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/health_server.py +0 -0
  255. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/heartbeat.py +0 -0
  256. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/sandbox/__init__.py +0 -0
  257. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  258. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/admin/__init__.py +0 -0
  259. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/admin/test_cli.py +0 -0
  260. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/client/__init__.py +0 -0
  261. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/client/test_client.py +0 -0
  262. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/__init__.py +0 -0
  263. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/test_config.py +0 -0
  264. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/test_constants.py +0 -0
  265. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/test_exceptions.py +0 -0
  266. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/test_logger.py +0 -0
  267. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/common/test_redis_client.py +0 -0
  268. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/conftest.py +0 -0
  269. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/__init__.py +0 -0
  270. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/protocol/__init__.py +0 -0
  271. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/protocol/test_byai_codec.py +0 -0
  272. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/protocol/test_command_wire.py +0 -0
  273. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/protocol/test_protocol.py +0 -0
  274. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/protocol/test_results.py +0 -0
  275. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/history/__init__.py +0 -0
  276. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/history/test_history_persistence.py +0 -0
  277. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_file_access_context.py +0 -0
  278. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  279. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_file_paths.py +0 -0
  280. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_file_permissions.py +0 -0
  281. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  282. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  283. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_filestore_local_read.py +0 -0
  284. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/runtime/test_filestore_local_search.py +0 -0
  285. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/test_agent_config_audit.py +0 -0
  286. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/test_availability.py +0 -0
  287. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/test_discovery.py +0 -0
  288. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/core/test_worker_manager.py +0 -0
  289. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/integration/__init__.py +0 -0
  290. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/integration/test_ask_user_flow.py +0 -0
  291. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/integration/test_callback_flow.py +0 -0
  292. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/integration/test_logger_integration.py +0 -0
  293. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/metrics/test_catalog.py +0 -0
  294. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/metrics/test_collector.py +0 -0
  295. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/metrics/test_metrics.py +0 -0
  296. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/metrics/test_read_client.py +0 -0
  297. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/metrics/test_snapshot.py +0 -0
  298. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/__init__.py +0 -0
  299. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/test_langfuse_plugin.py +0 -0
  300. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/test_plugin_discovery.py +0 -0
  301. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/test_plugin_improvements.py +0 -0
  302. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/test_plugin_registry.py +0 -0
  303. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/plugin/test_plugin_system.py +0 -0
  304. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/test_main.py +0 -0
  305. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/trace/test_external_trace.py +0 -0
  306. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/trace/test_logger_correlation.py +0 -0
  307. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/trace/test_otel_exporter.py +0 -0
  308. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/trace/test_span_recorder.py +0 -0
  309. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/trace/test_trace_writer.py +0 -0
  310. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/util/test_discovery_http_client.py +0 -0
  311. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/util/test_discovery_http_client_download.py +0 -0
  312. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/util/test_discovery_http_client_upload.py +0 -0
  313. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/util/test_http_client.py +0 -0
  314. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/__init__.py +0 -0
  315. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_admin_control_handling.py +0 -0
  316. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_app.py +0 -0
  317. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_byai_worker.py +0 -0
  318. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_control_handling.py +0 -0
  319. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_emitter.py +0 -0
  320. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_fetch_messages.py +0 -0
  321. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_health_server.py +0 -0
  322. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_heartbeat.py +0 -0
  323. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_message_processing.py +0 -0
  324. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/tests/worker/test_sandbox.py +0 -0
  325. {by_framework-0.2.2.dev11 → by_framework-0.2.2.dev13}/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.dev13
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,483 @@
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). `_synthesize_failure()` seeds its
198
+ reply's metadata from `child.get("metadata")` — the callee's execution
199
+ snapshot, where `context.py`'s `initialize_execution()` persists the
200
+ caller's original dispatch metadata — instead of an empty dict, so a
201
+ caller that never gets a real reply at all still gets its own metadata
202
+ back, consistent with every other reply shape to it. Two switches, and
203
+ they must stay separate: *compensation* (the triage, replies, renewals,
204
+ cancellation) is off unless `BY_FRAMEWORK_WAIT_SWEEPER_ENABLED` is set and is the rollback
205
+ switch for the whole liveness feature, while *pruning* is on by default
206
+ (`BY_FRAMEWORK_WAIT_PRUNE_ENABLED`) because nothing else ever removes a
207
+ wait-index entry — with compensation off, every call whose reply never
208
+ arrives would leak one forever into a shard ZSET that is shared across
209
+ sessions and so cannot carry a TTL. Pruning may stay unguarded only because
210
+ it decides nothing: it decodes no member and reads no execution record,
211
+ just one `ZREMRANGEBYSCORE` whose bound is a proof (every writer sets the
212
+ score to its own clock plus a non-negative offset, and only while the
213
+ caller's execution record exists, so an old score means the session
214
+ registry is gone and triage could reach nothing but "caller missing").
215
+ That argument covers renewed entries too — a renewal *raises* the score.
216
+ `CHILD_TIMEOUT` — and only it, since the
217
+ other outcomes have no live process on the other end — also asks the callee
218
+ to stop, by delegating wholesale to `GatewayClient.cancel_task` (which
219
+ targets `worker_ctrl_stream(worker_id)`; a cancel on the agent type's
220
+ competitive stream is claimed by an arbitrary worker whose in-memory table
221
+ has no such execution, so it records a cancellation while cancelling
222
+ nothing). That request must stay strictly *after* the reply is emitted and
223
+ strictly swallowed on failure: cancellation is cooperative, so a callee
224
+ wedged in a blocking call is both why the ceiling fired and the case it
225
+ cannot reach — nothing in the wake-up may depend on it. It also does not
226
+ silence the callee, whose own `CANCELLED` reply is dropped by the gate.
227
+ Opt out with `BY_FRAMEWORK_WAIT_CANCEL_ON_TIMEOUT`; per-call opt-out is
228
+ deliberately not offered (see the file's own note — the member is a wire
229
+ format, so it would cost a side-key write on every dispatch). Fail-soft
230
+ throughout: it runs inside every worker, so a raise here must not take the
231
+ worker down, and one bad entry must not abort a shard.
232
+
233
+ - `src/by_framework/core/wait_reply.py` — The single construction of a reply
234
+ built by someone other than the callee that owed it: the sweeper's
235
+ stand-ins and `call_agents`' compensation for a sub-task whose target agent
236
+ type was never available. Two spellings of that message drift, and the
237
+ drift only shows up as a hung caller, which is why neither site builds it
238
+ itself. `header.message_id` must be the caller's id (what the runner
239
+ reattaches by) and `header.parent_message_id` the sub-task's (what the join
240
+ keys by and what `wait_index.member_from_resume` rebuilds); the failure
241
+ detail must ride in `reply_data`, exactly where a sub-agent that ran and
242
+ raised puts it, so no caller can tell "failed" from "never got to fail".
243
+ Provenance (`synthesized_by`, `liveness_error_code`) goes on
244
+ `header.metadata` and must stay out of `reply_data` for the same reason.
245
+ `flush_pending_group_replies()` is called only after a handler returns
246
+ normally — inline delivery would put a reply on the caller's own control
247
+ stream strictly before it suspends, making a rare race certain, and a
248
+ handler that raised has already aborted its group.
249
+
250
+ - `src/by_framework/client/client.py` — `GatewayClient.send_message()` and
251
+ friends; publishes commands to Redis control streams and drives registry
252
+ execution-tracking as a side effect. On a `RESUME` dispatch, must look up
253
+ the original execution via `registry.get_execution_by_message_id(message_id,
254
+ session_id=...)`, reuse *that* `execution_id`, and skip
255
+ `initialize_execution()` for it — calling `initialize_execution()`
256
+ unconditionally silently detaches the `ResumeCommand` from the suspended
257
+ `WAITING_USER` execution it's meant to continue, orphaning it (fix
258
+ 90764e1, #75/#76/#77). The registry lookup must stay guarded with
259
+ `hasattr(registry, "get_execution_by_message_id")` so registry doubles/older
260
+ implementations fall back to minting a fresh execution_id. Root-dispatch
261
+ trace writes (`_write_trace_root_start/_end`) must only fire when
262
+ `not parent_message_id` — firing them on every `call_agent` hop would
263
+ duplicate trace roots.
264
+
265
+ - `src/by_framework/worker/runner.py` — `WorkerRunner`, the consume loop:
266
+ `XREADGROUP` fetch, command dispatch, resume/suspend bookkeeping, denylist
267
+ enforcement. `_active_agent_type_streams()` must read only the in-memory
268
+ `self._denied_agent_types` frozenset — no Redis `SISMEMBER` call inside the
269
+ hot consume-loop path; refreshed only by the heartbeat thread's
270
+ `denylist_refresh` callback (bounded staleness ~1 heartbeat interval) (fix
271
+ 8f23c78). The frozenset must be swapped by whole-reference reassignment,
272
+ relying on CPython GIL atomicity across the heartbeat thread and the async
273
+ loop — never mutate the set in place without adding a lock. A
274
+ `ResumeCommand` that fails to resolve to an existing execution must log a
275
+ warning — silently starting a disconnected new execution is the exact
276
+ failure mode this log surfaces (fix 90764e1, #77). Terminal-state
277
+ replay-skip logic is coupled to `ResumeCommand` handling: skip replaying an
278
+ execution already in a terminal state *unless* the command is a
279
+ `ResumeCommand`. `_health_server` (see [[worker-readiness-endpoint]]) must
280
+ start before any other step in `start()` (currently first line of the
281
+ `try:` block) so a probe hitting the port during startup gets an honest
282
+ `starting` 503 instead of connection-refused, and must `stop()` as the
283
+ *last* step of `_shutdown()` — after every other teardown step, not
284
+ before — so `/readyz` stays reachable (reporting `draining`) for the
285
+ entire drain. `self._draining = True` must stay the first line of
286
+ `_shutdown()`, ahead of every other teardown step, not just ahead of the
287
+ health-server stop. `is_resumed_execution` infers "already been through a
288
+ worker" from `existing_execution["status"] != QUEUED`, so QUEUED must stay
289
+ the *only* status an execution can hold before its first pickup — a
290
+ suspended caller persists as `WAITING_AGENT`/`WAITING_USER` precisely to
291
+ keep that inference true; reusing QUEUED for any post-pickup state silently
292
+ makes a resume re-derive its identity from the message header instead of
293
+ the record. Every `ResumeCommand` passes the `core/wait_gate.py`
294
+ idempotency gate *here*, before the execution lookup — and being upstream
295
+ of `GatewayWorker` is what also puts it before Task Group join, whose
296
+ `HINCRBY completed` a duplicate would push past `total` and aggregate a
297
+ second time. A dropped reply is acked and reported, never left pending.
298
+ Background tasks (`MetricsCollector`, `WaitIndexSweeper`) are started
299
+ best-effort — a background component that fails to construct must never
300
+ prevent the worker from consuming — and every one of them must be
301
+ cancelled and awaited in `_shutdown()`, or shutdown hangs on it.
302
+
303
+ - `src/by_framework/worker/health_server.py` — `WorkerHealthServer`: the
304
+ `/readyz` readiness HTTP endpoint, on its own daemon thread (mirrors
305
+ `heartbeat.py`'s "don't share the main event loop" pattern — see that
306
+ file's own docstring). Full design record, including why this exists and
307
+ the hard rule against ever wiring it to a liveness check:
308
+ [[worker-readiness-endpoint]]. `_compute_reason()`'s check order is the
309
+ entire contract — `starting > draining > evicted > suspended >
310
+ consumer_stalled > serving`, first match wins; reordering these checks
311
+ silently changes what an operator is told during a real incident. All
312
+ Worker state is read via constructor-injected callables (`has_started`,
313
+ `is_draining`, `admin_lifecycle`, `consumer_healthy`) — this class must
314
+ never reach into `WorkerRunner` directly, which is what keeps it testable
315
+ standalone against fake state (see `tests/worker/test_health_server.py`).
316
+
317
+ - `src/by_framework/worker/worker.py` — `GatewayWorker`: per-message lifecycle
318
+ (`_handle_message`), Task Group join, and the agent-return reply.
319
+ `_enqueue_agent_return()` builds the *only* thing that resumes a suspended
320
+ caller — its header must keep `message_id` = the caller's
321
+ `parent_message_id` and `parent_message_id` = this sub-task's own
322
+ `message_id`; Group Join keys `task_group_results` by the latter because
323
+ it's the only per-sibling-unique value (fix 9d4a0a4). Group Join must stay
324
+ the single accounting path: a result written or `completed` incremented
325
+ anywhere else can be the increment that reaches `total`, leaving no reply
326
+ to wake the caller (fix 55c7e6f; a dispatch-time failure must instead emit
327
+ the `FAILED` reply a sub-agent would have sent).
328
+ `_persist_single_call_result()` covers the non-group path only (early-return
329
+ when `header.task_group_id` is set, or the group's own write is duplicated)
330
+ and must stay fail-soft — a persist error costs recoverability, never the
331
+ reply. Its stored payload must stay field-for-field isomorphic with the
332
+ `result_data` the join path writes; both are read by the same recovery
333
+ code. `should_emit_stream_end` reads `context._is_suspended` /
334
+ `_permission_transferred`, so those flags' accuracy in `context.py` is
335
+ load-bearing here.
336
+ Who gets replied to comes from `_resolve_reply_command()`, never from a
337
+ resume's own header: a `ResumeCommand`'s `source_agent_type` /
338
+ `parent_message_id` / `task_group_id` describe the *sub-agent* that just
339
+ finished, so a resumed execution must rebuild its caller from the execution
340
+ record `initialize_execution()` wrote (`existing_data`). Treating "is a
341
+ resume" as "has no caller" drops the middle link's result in any chain of
342
+ depth >= 3. That record names `CLIENT_SOURCE_AGENT_TYPE` for anything a
343
+ *client* dispatched, which is a marker and not an agent type — it must be
344
+ excluded explicitly, or every root execution that ever resumes (an
345
+ `ask_user` round is the common one) posts its result to a control stream
346
+ nobody consumes and, worse, stops emitting the end-of-stream event the user
347
+ is waiting on, because it now believes it owes an agent a reply.
348
+ `_resolve_reply_command()` restores `header.metadata` from the same
349
+ execution snapshot (`existing_data["metadata"]`) as a full *replacement*,
350
+ never merged with the resuming command's own metadata: that metadata
351
+ belongs to whatever woke this execution up (an `ask_user` answer, or a
352
+ sub-call's reply), not to the original caller, and leaking it would let a
353
+ transient hop overwrite the caller's own data instead of being layered
354
+ under `task_result.metadata` like `_enqueue_agent_return()`'s merge
355
+ already does correctly.
356
+ The success path must NOT reply while `context._is_suspended` — a suspended
357
+ execution has no result, and forwarding the value the handler returned in
358
+ order to unwind both wakes the caller early and consumes the single reply it
359
+ was waiting for. "Suspended" here must carry the same terminal-status
360
+ exception as `_apply_suspended_status()`: a handler that returned a terminal
361
+ status is recorded finished and will never resume to reply later, so it owes
362
+ its caller a reply now. The `CancelledError`/`Exception` paths must reply
363
+ anyway: a dead execution will never resume to produce one.
364
+ `_apply_suspended_status()` overwrites a non-terminal business status with
365
+ `context._suspended_state`, so what lands in the registry is
366
+ `WAITING_AGENT`/`WAITING_USER` rather than the handler's placeholder; a
367
+ terminal status always wins.
368
+ `flush_pending_group_replies()` runs immediately after `process_command`
369
+ returns and nowhere else: the stand-ins it delivers belong to a group whose
370
+ caller must already have finished suspending, and a handler that raised has
371
+ aborted its group, so its queued stand-ins must die with it.
372
+
373
+ - `src/by_framework/worker/context.py` — `AgentContext`: the agent-facing
374
+ runtime surface; `_dispatch_single_task()` is the one dispatch path behind
375
+ both `call_agent` and `call_agents`. `_is_suspended` /
376
+ `_permission_transferred` are flipped *before* the availability check, so
377
+ every early return that means "nothing was dispatched" must restore the
378
+ values captured on entry — restoring to `False` instead is wrong, since an
379
+ earlier `call_agent` on the same context may legitimately have suspended
380
+ it. An availability rejection returns a `FAILED` result dict, it does not
381
+ raise; `call_agents` depends on that to keep fanning out. It must
382
+ compensate such a member with a *reply* (`core/wait_reply.py`, queued on
383
+ `_pending_group_replies` for the worker to flush after `process_command`
384
+ returns) and must never write `task_group_results`/`HINCRBY completed`
385
+ itself: that is a second implementation of the join's accounting, and when
386
+ its increment is the one that reaches `total` no reply is left to run the
387
+ join and the caller hangs forever — reachable both when every target is
388
+ offline and when a sibling's reply is joined mid-fan-out. It also registers
389
+ a wait entry for the member it could not dispatch, so an undelivered
390
+ stand-in is still compensable by a sweep. On a genuine dispatch exception
391
+ mid-batch, `call_agents` must mark the group `aborted` before re-raising,
392
+ or already-sent siblings' replies resume a caller that is already dead;
393
+ queued stand-ins die with the raise for the same reason. `call_agents`
394
+ rejects `message_id` outright for more than one task: a group's per-sibling
395
+ identity *is* its sub-task message_id (it keys both `task_group_results` and
396
+ the wait index), so pinning one across the fan-out overwrites every
397
+ sibling's result and leaves the gate one entry to claim, hanging the caller
398
+ short of `total`. It fails loudly rather than silently minting ids, and the
399
+ single-task use — where the id collides with nothing — is unaffected.
400
+ Every `wait_for_reply=True` dispatch registers one wait-index entry via
401
+ `_register_wait()` (one per sub-task for a group, so each can be resolved
402
+ independently), keyed by `parent_message_id` = the id the awaited reply
403
+ carries as `header.message_id`; getting that direction wrong makes the
404
+ entry unmatchable by the reply that should clear it. `ask_user` registers
405
+ with an empty `child_message_id` (no sub-task) and its own, much larger
406
+ timeout — sharing `call_agent`'s would time out a human. Because that
407
+ member repeats across consecutive ask_user rounds, registering also clears
408
+ the previous round's `wait_consumed` marker, or the gate would read round
409
+ 2's answer as round 1's duplicate. Registration is fail-soft and must stay
410
+ so: it is bookkeeping, not the dispatch.
411
+ `initialize_execution()`'s payload carries `source_agent_type` and
412
+ `task_group_id` because they are the only durable record of who a
413
+ suspended callee owes its reply to (see `worker.py`). It also carries
414
+ `metadata` (a copy of the dispatched `command.header.metadata`) for the
415
+ same reason: it is the only durable record of what the caller's metadata
416
+ *was*, and `worker.py`'s `_resolve_reply_command()` reads it back
417
+ verbatim so a caller's metadata survives however many times the callee
418
+ suspends and resumes before it finally replies.
419
+ `_suspended_state` records *which* state the execution is waiting in and is
420
+ what the framework persists; keep it set/rolled-back in lockstep with
421
+ `_is_suspended`.
422
+
423
+ - `src/by_framework/worker/processor.py` — `GatewayProcessor`: the standalone
424
+ message-lifecycle path for callers that don't subclass `GatewayWorker`. It
425
+ duplicates `worker.py`'s reply logic, so every reply-side invariant there
426
+ applies here too and the two must be changed together — the shape has
427
+ already drifted once and taken the same bug twice. Specifically:
428
+ `_enqueue_callback()`'s `header.message_id` must be the *caller's*
429
+ message_id (this dispatch's `parent_message_id`), since that is what the
430
+ caller's suspended execution is reattached by; a freshly minted id resolves
431
+ to no execution. `_resolve_reply_header()` must read a resumed execution's
432
+ caller from the execution record, not from the resume header (which names
433
+ the sub-agent), while excluding `CLIENT_SOURCE_AGENT_TYPE` for the reason
434
+ spelled out under `worker.py`; and no callback may be sent while
435
+ `context._is_suspended` (with the same terminal-status exception).
436
+ Being a *second* entry point for replies, it carries the same
437
+ `core/wait_gate.py` gate as `runner.py` — a gate on one of two doors is not
438
+ a gate, and an ungated reply here would both wake a resolved caller and
439
+ leave its wait-index entry behind for a sweep to resolve all over again.
440
+ `process()` returns `None` for a reply it drops. It also flushes
441
+ `call_agents`' queued stand-ins on the same terms as `worker.py` — after
442
+ the handler returns, never when it raised.
443
+
444
+ - `src/by_framework/core/registry.py` — `WorkerRegistry`: Redis-backed worker
445
+ membership/heartbeat/execution-state, admin lifecycle, locking primitives.
446
+ `mark_execution_finished()` must stamp `finished_at` only
447
+ `if is_terminal_state(status)` — stamping it unconditionally makes a
448
+ suspended `WAITING_USER` execution look completed to
449
+ `metrics/snapshot.py`'s latency/`completed_count` math (fix 90764e1, #76).
450
+ `heartbeat_worker()` uses an atomic Lua CAS script with token-mode (verify
451
+ stored token before overwrite) and legacy no-token mode — must not be
452
+ replaced by a plain `SET`; return codes `1`=success / `0`=owned-by-another /
453
+ `-1`=unparseable-legacy are relied on by callers. `_RELEASE_LOCK_SCRIPT` /
454
+ `_REFRESH_LOCK_SCRIPT` are Redlock-style token-verified delete/expire — must
455
+ stay atomic, and an empty-string token means "unconditional" (no-token
456
+ legacy mode). Those two scripts are also the shared Redlock primitives
457
+ behind `acquire_scoped_lock()`/`release_scoped_lock()` (used for wait-index
458
+ shard claims), so the value written must stay a cjson-decodable object
459
+ carrying a `token` field — a bare token string parses as unparseable legacy
460
+ data and leaves the holder unable to release its own lock.
461
+ `set_worker_admin_state`/`clear_worker_admin_state`: the
462
+ per-worker `worker_admin(id)` hash write must complete independently of the
463
+ `admin_workers()` global-index update — see [[redis-cluster-mode]].
464
+
465
+ - `src/by_framework/core/protocol/responses.py` — `SendMessageResponse` /
466
+ `CancelTaskResponse` / `CancelSessionResponse` frozen dataclasses +
467
+ `ExecutionStatus` string constants. `ExecutionStatus` string values
468
+ (`"SUCCESS"`, `"NOT_FOUND"`, `"WORKER_NOT_ONLINE"`, etc.) are a wire-level
469
+ contract matched by literal string elsewhere in the client and its tests —
470
+ renaming a value is a cross-file breaking change.
471
+ `ERR_AGENT_TYPE_NOT_FOUND = ERR_AGENT_TYPE_UNAVAILABLE` is a deliberate
472
+ alias; both names must keep resolving to the same string. Response
473
+ dataclasses are `@dataclass(frozen=True)` — don't drop `frozen` or add
474
+ mutable defaults.
475
+
476
+ - `src/by_framework/core/protocol/content_type.py` — `SseMessageType` /
477
+ `SseReasonMessageType` enums: numeric string codes for SSE messages sent to
478
+ the frontend/other-language SDKs. These codes are an external protocol
479
+ contract — once shipped, a code's meaning must never change; only append
480
+ new codes. `SseReasonMessageType.think_text = "1002"` intentionally reuses
481
+ `SseMessageType.text`'s value — looks like a copy-paste bug but is
482
+ deliberate protocol code reuse; verify against frontend/other-language SDKs
483
+ before "fixing".