by-framework 0.2.2.dev9__tar.gz → 0.2.2.dev11__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.
- by_framework-0.2.2.dev11/.codex/hooks.json +14 -0
- by_framework-0.2.2.dev11/.github/workflows/deploy-smoke-test.yml +66 -0
- by_framework-0.2.2.dev11/.github/workflows/verify.yml +16 -0
- by_framework-0.2.2.dev11/AGENTS.md +1 -0
- by_framework-0.2.2.dev11/CLAUDE.md +205 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/PKG-INFO +26 -3
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/README.md +25 -2
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/README_zh.md +22 -1
- by_framework-0.2.2.dev11/deploy/Dockerfile +61 -0
- by_framework-0.2.2.dev11/deploy/docker-compose.yml +70 -0
- by_framework-0.2.2.dev11/deploy/entrypoint.sh +13 -0
- by_framework-0.2.2.dev11/deploy/kubernetes/worker-deployment.yaml +62 -0
- by_framework-0.2.2.dev11/docs/architecture/GUARD_AUTHORING.md +62 -0
- by_framework-0.2.2.dev11/docs/architecture/KEY_FILES.md +186 -0
- by_framework-0.2.2.dev11/docs/architecture/production-deployment.md +142 -0
- by_framework-0.2.2.dev11/docs/architecture/redis-cluster-mode.md +85 -0
- by_framework-0.2.2.dev11/docs/architecture/worker-readiness-endpoint.md +240 -0
- by_framework-0.2.2.dev11/examples/echo_worker.py +24 -0
- by_framework-0.2.2.dev11/examples/send_and_verify.py +98 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/pyproject.toml +1 -1
- by_framework-0.2.2.dev11/scripts/.map.conf +9 -0
- by_framework-0.2.2.dev11/scripts/check-doc-discipline.sh +75 -0
- by_framework-0.2.2.dev11/scripts/check-entry-freshness.sh +113 -0
- by_framework-0.2.2.dev11/scripts/check-map-territory.sh +34 -0
- by_framework-0.2.2.dev11/scripts/map-stop-hook.sh +29 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/scripts/python_quality.sh +2 -1
- by_framework-0.2.2.dev11/scripts/test-growth-ladder.sh +103 -0
- by_framework-0.2.2.dev11/scripts/verify.sh +76 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/__init__.py +2 -0
- by_framework-0.2.2.dev11/src/by_framework/__main__.py +165 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/admin/cli.py +6 -5
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/client/client.py +124 -3
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/config.py +4 -2
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/emitter.py +16 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/__init__.py +2 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/__init__.py +2 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/content_type.py +1 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/responses.py +11 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/registry.py +2 -1
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/app.py +68 -24
- by_framework-0.2.2.dev11/src/by_framework/worker/health_server.py +169 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/runner.py +43 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/admin/test_cli.py +30 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/client/test_client.py +345 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/common/test_config.py +56 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/common/test_constants.py +6 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/test_registry.py +32 -0
- by_framework-0.2.2.dev11/tests/test_main.py +180 -0
- by_framework-0.2.2.dev11/tests/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_app.py +179 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_emitter.py +41 -1
- by_framework-0.2.2.dev11/tests/worker/test_health_server.py +256 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_runner.py +279 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/uv.lock +1 -1
- by_framework-0.2.2.dev9/AGENTS.md +0 -96
- by_framework-0.2.2.dev9/CLAUDE.md +0 -98
- by_framework-0.2.2.dev9/src/by_framework/__main__.py +0 -86
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.dockerignore +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/RELEASING.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/workflows/ci.yml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/workflows/publish.yml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.github/workflows/stale.yml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.gitignore +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/.pre-commit-config.yaml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/CHANGELOG.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/CODE_OF_CONDUCT.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/CONTRIBUTING.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/LICENSE +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/Makefile +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/SECURITY.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/assets/img/architecture_en.png +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/assets/img/architecture_zh.png +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/autoformat.sh +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/by-framework-trace-change-usage.html +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/by-framework-trace-complete-guide.html +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/docs/worker_management.html +0 -0
- {by_framework-0.2.2.dev9/src/by_framework/util → by_framework-0.2.2.dev11/examples}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-adk/tests/test_worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/Dockerfile +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/index.html +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/package.json +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-byclaw/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-postgres/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-postgres/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-langfuse/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/README.md +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/pyproject.toml +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/pylintrc +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/admin/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/admin/worker_manager.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/client/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/client/byai_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/constants.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/exceptions.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/logger.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/common/redis_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/availability.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/delivery_gate.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/discovery.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/agent_config.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/plugin.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/registry.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/extensions/trace_provider.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/action_type.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/agent_state.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/byai_codec.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/byai_command.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/byai_types.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/commands.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/content_codec.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/data_message.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/data_shapes.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/event_type.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/events.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/message.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/message_header.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/protocol/results.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/file_manager.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/file_paths.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/file_permissions.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/filestore/base.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/filestore/local.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/history/base.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/history/history_manager.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/history/in_memory.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/runtime/session_manager.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/wakeup_controller.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/core/workspace.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/base.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/common.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/execution.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/http.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/protocol.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/errors/registry.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/metrics/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/metrics/catalog.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/metrics/collector.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/metrics/read_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/metrics/snapshot.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/trace/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/trace/external_trace.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/trace/span_recorder.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/trace/trace_schema.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/trace/trace_writer.py +0 -0
- {by_framework-0.2.2.dev9/src/by_framework/worker/sandbox → by_framework-0.2.2.dev11/src/by_framework/util}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/util/discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/util/generate_message_id.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/util/http_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/_control_handling.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/_execution_tracking.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/_message_processing.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/byai_context.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/byai_worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/context.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/heartbeat.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/processor.py +0 -0
- {by_framework-0.2.2.dev9/tests/admin → by_framework-0.2.2.dev11/src/by_framework/worker/sandbox}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/src/by_framework/worker/worker.py +0 -0
- {by_framework-0.2.2.dev9/tests/client → by_framework-0.2.2.dev11/tests/admin}/__init__.py +0 -0
- {by_framework-0.2.2.dev9/tests/common → by_framework-0.2.2.dev11/tests/client}/__init__.py +0 -0
- {by_framework-0.2.2.dev9/tests/core → by_framework-0.2.2.dev11/tests/common}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/common/test_exceptions.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/common/test_logger.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/common/test_redis_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/conftest.py +0 -0
- {by_framework-0.2.2.dev9/tests/core/protocol → by_framework-0.2.2.dev11/tests/core}/__init__.py +0 -0
- {by_framework-0.2.2.dev9/tests/core/runtime/history → by_framework-0.2.2.dev11/tests/core/protocol}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/protocol/test_byai_codec.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/protocol/test_command_wire.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/protocol/test_protocol.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/protocol/test_results.py +0 -0
- {by_framework-0.2.2.dev9/tests/integration → by_framework-0.2.2.dev11/tests/core/runtime/history}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/history/test_history_persistence.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_file_access_context.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_file_paths.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_file_permissions.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_filestore_local_collection.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_filestore_local_read.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/runtime/test_filestore_local_search.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/test_agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/test_availability.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/test_discovery.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/core/test_worker_manager.py +0 -0
- {by_framework-0.2.2.dev9/tests/plugin → by_framework-0.2.2.dev11/tests/integration}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/integration/test_ask_user_flow.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/integration/test_callback_flow.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/integration/test_logger_integration.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/integration/test_scatter_gather.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/metrics/test_catalog.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/metrics/test_collector.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/metrics/test_metrics.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/metrics/test_read_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/metrics/test_snapshot.py +0 -0
- {by_framework-0.2.2.dev9/tests/worker → by_framework-0.2.2.dev11/tests/plugin}/__init__.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/plugin/test_langfuse_plugin.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/plugin/test_plugin_discovery.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/plugin/test_plugin_improvements.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/plugin/test_plugin_registry.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/plugin/test_plugin_system.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/trace/test_external_trace.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/trace/test_logger_correlation.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/trace/test_otel_exporter.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/trace/test_span_recorder.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/trace/test_trace_writer.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/util/test_discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/util/test_discovery_http_client_download.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/util/test_discovery_http_client_upload.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/util/test_http_client.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_admin_control_handling.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_byai_worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_context.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_control_handling.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_fetch_messages.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_gateway_worker.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_heartbeat.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_message_processing.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_processor.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_sandbox.py +0 -0
- {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev11}/tests/worker/test_workspace.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
name: Deploy Smoke Test
|
|
2
|
+
|
|
3
|
+
# Validates deploy/'s Dockerfile + docker-compose.yml actually deliver a
|
|
4
|
+
# message through the full pipeline - client sends -> Redis control stream
|
|
5
|
+
# -> Worker consumes -> Worker replies -> Redis data stream -> client reads
|
|
6
|
+
# - using examples/echo_worker.py, built from THIS commit's source (not the
|
|
7
|
+
# last PyPI release; see deploy/Dockerfile's BY_FRAMEWORK_SOURCE=local mode).
|
|
8
|
+
# See docs/architecture/production-deployment.md.
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
pull_request:
|
|
12
|
+
paths:
|
|
13
|
+
- "deploy/**"
|
|
14
|
+
- "examples/**"
|
|
15
|
+
- "src/by_framework/**"
|
|
16
|
+
- "pyproject.toml"
|
|
17
|
+
- "uv.lock"
|
|
18
|
+
- ".github/workflows/deploy-smoke-test.yml"
|
|
19
|
+
push:
|
|
20
|
+
branches:
|
|
21
|
+
- main
|
|
22
|
+
- master
|
|
23
|
+
paths:
|
|
24
|
+
- "deploy/**"
|
|
25
|
+
- "examples/**"
|
|
26
|
+
- "src/by_framework/**"
|
|
27
|
+
workflow_dispatch:
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
smoke-test:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
env:
|
|
33
|
+
BY_FRAMEWORK_SOURCE: local
|
|
34
|
+
WORKER_CLASS: examples.echo_worker.EchoWorker
|
|
35
|
+
REDIS_HOST: localhost
|
|
36
|
+
REDIS_PORT: "6379"
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
- uses: astral-sh/setup-uv@v5
|
|
43
|
+
|
|
44
|
+
- name: Install by-framework (for the verify client script below)
|
|
45
|
+
run: uv sync
|
|
46
|
+
|
|
47
|
+
# --wait blocks until every service with a healthcheck (redis, and now
|
|
48
|
+
# worker's own /readyz-backed HEALTHCHECK - see
|
|
49
|
+
# docs/architecture/worker-readiness-endpoint.md) reports healthy, and
|
|
50
|
+
# fails the step if that doesn't happen in time. This is the actual
|
|
51
|
+
# verification that the readiness endpoint works end to end in a real
|
|
52
|
+
# container - not just that the Python contract tests pass.
|
|
53
|
+
- name: Build and start Redis + Worker via deploy/docker-compose.yml
|
|
54
|
+
run: |
|
|
55
|
+
docker compose -f deploy/docker-compose.yml up --build -d --wait --wait-timeout 60
|
|
56
|
+
|
|
57
|
+
- name: Send a message and verify the Worker echoes it back
|
|
58
|
+
run: uv run python examples/send_and_verify.py
|
|
59
|
+
|
|
60
|
+
- name: Worker logs
|
|
61
|
+
if: always()
|
|
62
|
+
run: docker compose -f deploy/docker-compose.yml logs worker
|
|
63
|
+
|
|
64
|
+
- name: Tear down
|
|
65
|
+
if: always()
|
|
66
|
+
run: docker compose -f deploy/docker-compose.yml down -v
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: verify
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
verify:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v4
|
|
8
|
+
with: { fetch-depth: 0 }
|
|
9
|
+
- name: resolve map base ref
|
|
10
|
+
run: |
|
|
11
|
+
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
12
|
+
echo "MAP_BASE_REF=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_ENV"
|
|
13
|
+
elif git cat-file -e "${{ github.event.before }}" 2>/dev/null; then
|
|
14
|
+
echo "MAP_BASE_REF=${{ github.event.before }}" >> "$GITHUB_ENV"
|
|
15
|
+
fi
|
|
16
|
+
- run: bash scripts/verify.sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CLAUDE.md
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) and Codex when working with code in this repository. (`AGENTS.md` is a symlink to this file — one copy, no drift between agent ecosystems.)
|
|
4
|
+
|
|
5
|
+
## North Star
|
|
6
|
+
|
|
7
|
+
`by-framework-python` lets agent workloads scale horizontally across Redis
|
|
8
|
+
Streams without losing execution identity or silently corrupting shared
|
|
9
|
+
state under partial failure — a worker restart, Cluster-mode operation, a
|
|
10
|
+
misconfigured plugin, or a delayed `ask_user` reply. When a trade-off isn't
|
|
11
|
+
covered by an explicit rule, prefer a change that fails loudly (raise,
|
|
12
|
+
warn-log) or degrades safely (no-op, skip) over one that could silently
|
|
13
|
+
drop, duplicate, orphan, or misroute an execution, a message, or a
|
|
14
|
+
heartbeat — this repo's fix-commit history is dominated by exactly that
|
|
15
|
+
failure shape.
|
|
16
|
+
|
|
17
|
+
## Core mental model
|
|
18
|
+
|
|
19
|
+
1. **Control plane vs. data plane are separate Redis Streams, never
|
|
20
|
+
conflated.** Commands travel on per-agent-type control streams
|
|
21
|
+
(competitive consume via consumer groups); output travels on
|
|
22
|
+
session-scoped data streams. Misrouting one onto the other silently
|
|
23
|
+
breaks delivery.
|
|
24
|
+
2. **An execution's identity must survive suspend/resume, not just
|
|
25
|
+
request/response.** A task can suspend (`ask_user`, or a `call_agent`
|
|
26
|
+
hop) and later resume via a `RESUME` dispatch that must reattach to the
|
|
27
|
+
*original* `execution_id` via the registry lookup — treating every
|
|
28
|
+
dispatch as "mint a new execution" silently orphans the suspended one.
|
|
29
|
+
This was the single most-recurring bug class in this repo's history (fix
|
|
30
|
+
90764e1, closing #75/#76/#77).
|
|
31
|
+
|
|
32
|
+
## Project Overview
|
|
33
|
+
|
|
34
|
+
`by-framework` is a distributed, high-performance Agent scheduling engine built on Redis Streams. It provides a framework for building AI agents with self-driven orchestration and sandbox isolation capabilities.
|
|
35
|
+
|
|
36
|
+
## Build Commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Install dependencies
|
|
40
|
+
make install
|
|
41
|
+
|
|
42
|
+
# Format code (isort + ruff + pyink)
|
|
43
|
+
make format
|
|
44
|
+
|
|
45
|
+
# Lint code (pylint + ruff)
|
|
46
|
+
make lint
|
|
47
|
+
|
|
48
|
+
# Run all tests
|
|
49
|
+
make test
|
|
50
|
+
|
|
51
|
+
# Run a single test file
|
|
52
|
+
uv run pytest tests/worker/test_gateway_worker.py
|
|
53
|
+
|
|
54
|
+
# Run tests matching a pattern
|
|
55
|
+
uv run pytest -k "test_name_pattern"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Architecture
|
|
59
|
+
|
|
60
|
+
### Core Data Flow
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Client → Redis Input MQ (queue:ctrl:{agent_type}) → GatewayWorker
|
|
64
|
+
↓
|
|
65
|
+
Redis Data MQ (queue:data:stream)
|
|
66
|
+
↓
|
|
67
|
+
WebSocket Backend
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Key Components
|
|
71
|
+
|
|
72
|
+
| Component | Location | Purpose |
|
|
73
|
+
|-----------|----------|---------|
|
|
74
|
+
| `GatewayWorker` | `src/by_framework/worker/worker.py` | Abstract base class for workers; implement `get_capabilities()` and `process_command()` |
|
|
75
|
+
| `AgentContext` | `src/by_framework/worker/context.py` | Runtime context for task execution; emits chunks, states, artifacts; calls other agents |
|
|
76
|
+
| `run_worker()` | `src/by_framework/worker/app.py` | Main entry point for starting a worker |
|
|
77
|
+
| `GatewayClient` | `src/by_framework/client/client.py` | Sends commands to Redis Streams |
|
|
78
|
+
| `ByaiGatewayClient` | `src/by_framework/client/byai_client.py` | GatewayClient with ByaiMessageInterceptor |
|
|
79
|
+
| `Plugin` | `src/by_framework/core/extensions/plugin.py` | Abstract base for extensible plugins with lifecycle hooks |
|
|
80
|
+
| `PluginRegistry` | `src/by_framework/core/extensions/registry.py` | Manages plugin registration and discovery |
|
|
81
|
+
|
|
82
|
+
### Protocol System
|
|
83
|
+
|
|
84
|
+
Commands and events are defined in `src/by_framework/core/protocol/`:
|
|
85
|
+
- `commands.py` - `AskAgentCommand`, `CancelTaskCommand`, `ResumeCommand`
|
|
86
|
+
- `events.py` - `StreamChunkEvent`, `StateChangeEvent`, `ArtifactEvent`
|
|
87
|
+
- `message_header.py` - `MessageHeader` with session_id, trace_id, message_id
|
|
88
|
+
|
|
89
|
+
### Plugin Lifecycle Hooks
|
|
90
|
+
|
|
91
|
+
Plugins can implement: `on_worker_startup`, `on_worker_shutdown`, `on_task_start`, `on_task_complete`, `on_task_error`, `on_task_cancel`
|
|
92
|
+
|
|
93
|
+
### Redis Key Patterns
|
|
94
|
+
|
|
95
|
+
- `byai_gateway:ctrl:agent_type:{agent_type}` — Control stream; competitive consume per agent type
|
|
96
|
+
- `byai_gateway:ctrl:worker:{worker_id}` — Direct per-worker routing
|
|
97
|
+
- `byai_gateway:session:{session_id}:data_stream` — Session-scoped output events
|
|
98
|
+
- `byai_gateway:registry:worker:online:{worker_id}` — Heartbeat TTL key
|
|
99
|
+
- `byai_gateway:task_group:{group_id}` — Scatter-gather group tracker
|
|
100
|
+
|
|
101
|
+
Connection precedence and Cluster-mode/key-schema-versioning details: see
|
|
102
|
+
the Reference map below, not repeated here.
|
|
103
|
+
|
|
104
|
+
## Test Structure
|
|
105
|
+
|
|
106
|
+
Tests are organized by module in `tests/`:
|
|
107
|
+
- `tests/common/` - Logger, redis client, config, exceptions
|
|
108
|
+
- `tests/core/` - Registry, protocol, history
|
|
109
|
+
- `tests/worker/` - Worker, context, processor, sandbox
|
|
110
|
+
- `tests/client/` - Client functionality
|
|
111
|
+
- `tests/plugin/` - Plugin system and discovery
|
|
112
|
+
- `tests/integration/` - Cross-component flows (scatter-gather, callbacks, ask_user)
|
|
113
|
+
|
|
114
|
+
## Code Style
|
|
115
|
+
|
|
116
|
+
- **Import sorting**: isort
|
|
117
|
+
- **Formatting**: ruff-format + pyink
|
|
118
|
+
- **Linting**: pylint (with `pylintrc`) + ruff
|
|
119
|
+
- **Testing**: pytest with pytest-asyncio
|
|
120
|
+
|
|
121
|
+
Pre-commit hooks are configured in `.pre-commit-config.yaml` and run isort, ruff, pylint, pyink, and general checks.
|
|
122
|
+
|
|
123
|
+
## Development Notes
|
|
124
|
+
|
|
125
|
+
- Package is at `src/by_framework/` (configured in `pyproject.toml`)
|
|
126
|
+
- `pythonpath = ["src"]` is set in pytest config
|
|
127
|
+
- Redis 7.0+ is required for Streams functionality
|
|
128
|
+
- Worker capabilities are declared via `get_capabilities()` and used for task routing
|
|
129
|
+
|
|
130
|
+
## Cross-cutting invariants
|
|
131
|
+
|
|
132
|
+
- **An explicitly-passed kwarg must never be silently discarded because a
|
|
133
|
+
config object's corresponding field defaults to `None`/"unset."** Incident:
|
|
134
|
+
fix 6ec070c — `redis_client.init_redis()` let `config.max_connections`
|
|
135
|
+
(default `None`, meaning "not specified") unconditionally override an
|
|
136
|
+
explicitly-passed `max_connections` kwarg, discarding
|
|
137
|
+
`worker/app.py`'s `max_concurrency`-derived pool size. Correct form:
|
|
138
|
+
`if config.<field> is not None: value = config.<field>` — only let the
|
|
139
|
+
config object win when it was actually set. No dedicated guard yet;
|
|
140
|
+
applies to any new `Optional[...] = None` config field.
|
|
141
|
+
|
|
142
|
+
- **Plugin/trace hook code must fail soft (log + return/`None`), never raise
|
|
143
|
+
into the primary task-execution or worker-startup path.** Incident: fix
|
|
144
|
+
8b5bf93 — `LangfusePlugin._build_default_tracer()` used to `raise
|
|
145
|
+
RuntimeError` when Langfuse env vars were unset, which would have broken
|
|
146
|
+
worker startup for anyone with the plugin auto-discovered but
|
|
147
|
+
unconfigured; changed to log + return `None`, with every call site guarded.
|
|
148
|
+
Reinforced by `core/extensions/registry.py`'s `_execute_hook()`, which
|
|
149
|
+
wraps every plugin lifecycle hook in try/except + timeout handling so no
|
|
150
|
+
hook exception ever propagates to the caller. Correct form: any new
|
|
151
|
+
plugin/trace integration point must degrade to a no-op (logged) rather
|
|
152
|
+
than raising.
|
|
153
|
+
|
|
154
|
+
## Iron rules
|
|
155
|
+
|
|
156
|
+
<!-- No process rules (test invocation, release steps) have caused an
|
|
157
|
+
incident yet — entries land here only via a real trigger (see
|
|
158
|
+
"Maintaining this map" below), never invented up front. -->
|
|
159
|
+
|
|
160
|
+
## Reference map
|
|
161
|
+
|
|
162
|
+
| When you're working on... | Read first |
|
|
163
|
+
|---|---|
|
|
164
|
+
| any source file | `docs/architecture/KEY_FILES.md` — find the file's entry |
|
|
165
|
+
| Redis connection setup, cluster-mode, or key-schema versioning (`RedisConfig`, `RedisKeys`, `_get_redis()`, admin-index writes) | `docs/architecture/redis-cluster-mode.md` |
|
|
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
|
+
| 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
|
+
|
|
169
|
+
## Maintaining this map
|
|
170
|
+
|
|
171
|
+
The map grows only on triggers — never speculatively:
|
|
172
|
+
|
|
173
|
+
1. **After an incident** — write the postmortem in `docs/incidents/`
|
|
174
|
+
(append-only is legal there), then ask "what grep would have prevented
|
|
175
|
+
this" → a new guard, or an append to an existing one.
|
|
176
|
+
2. **After editing a load-bearing file** — rewrite its KEY_FILES entry in
|
|
177
|
+
place. Behaviour changed means the entry changes; never append history.
|
|
178
|
+
3. **Same mistake twice** — triage into exactly one layer:
|
|
179
|
+
machine-checkable → guard in `scripts/verify.sh`'s CHECKS;
|
|
180
|
+
cross-cutting → quad bullet above;
|
|
181
|
+
domain detail → docs/ file + a Reference-map row.
|
|
182
|
+
4. **Explored a subsystem to do a task and no doc held it** — diarize it into
|
|
183
|
+
`docs/architecture/<x>.md` + a Reference-map row while the understanding is
|
|
184
|
+
hot. Don't make the next agent re-read the same files to learn the same
|
|
185
|
+
thing.
|
|
186
|
+
5. **A rule is already known — no incident needed** — someone states a
|
|
187
|
+
requirement up front (deployment steps, a naming convention, whatever).
|
|
188
|
+
Don't wait for it to bite twice: triage it right now, same three-way
|
|
189
|
+
split as every other find. Machine can check it → write a guard per
|
|
190
|
+
`docs/architecture/GUARD_AUTHORING.md`, register it in `verify.sh`'s
|
|
191
|
+
`CHECKS`. Only judgement can check it → a quad-bullet above — **unless
|
|
192
|
+
two or more existing bullets already cover the same concern** (e.g.
|
|
193
|
+
several deployment steps), in which case lift the whole cluster into a
|
|
194
|
+
`docs/` file + Reference-map row instead of adding a third bullet. Same
|
|
195
|
+
move as "Diarize a subsystem" for file entries: one doc per concern
|
|
196
|
+
beats a pile of bullets that only gets noticed at the next size-cap
|
|
197
|
+
surgery. Domain detail from the start → a `docs/` file + a Reference-map
|
|
198
|
+
row, no waiting to accumulate.
|
|
199
|
+
|
|
200
|
+
Guards: `bash scripts/verify.sh` before every push (kept under 30s).
|
|
201
|
+
Growth enforcement: `check-entry-freshness.sh` warns in verify on stale
|
|
202
|
+
entries, unindexed new source files, and untriaged fix commits; the Stop
|
|
203
|
+
hook (`scripts/map-stop-hook.sh`, wired into `.claude/settings.json` and
|
|
204
|
+
`.codex/hooks.json`) blocks session end once until stale entries are
|
|
205
|
+
rewritten or declared unchanged.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: by-framework
|
|
3
|
-
Version: 0.2.2.
|
|
3
|
+
Version: 0.2.2.dev11
|
|
4
4
|
Summary: 分布式 Agent 调度框架
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -250,6 +250,13 @@ async def main():
|
|
|
250
250
|
asyncio.run(main())
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
+
A runnable version of this pair lives at
|
|
254
|
+
[`examples/echo_worker.py`](examples/echo_worker.py) (Worker) and
|
|
255
|
+
[`examples/send_and_verify.py`](examples/send_and_verify.py) (client — blocks
|
|
256
|
+
until it sees the echoed reply) — the same pair `deploy/`'s CI smoke test
|
|
257
|
+
drives end to end, see
|
|
258
|
+
[`docs/architecture/production-deployment.md`](docs/architecture/production-deployment.md).
|
|
259
|
+
|
|
253
260
|
---
|
|
254
261
|
|
|
255
262
|
## Core Concepts
|
|
@@ -602,7 +609,7 @@ Tests are organized by module under `tests/`:
|
|
|
602
609
|
|
|
603
610
|
```bash
|
|
604
611
|
# 1. Start Redis
|
|
605
|
-
docker run -d --name by-redis -p 6379:6379
|
|
612
|
+
docker run -d --name by-redis -p 6379:6379 redis:7-alpine
|
|
606
613
|
|
|
607
614
|
# 2. Start a Worker
|
|
608
615
|
python -m by_framework \
|
|
@@ -621,11 +628,27 @@ python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-02 &
|
|
|
621
628
|
python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-03 &
|
|
622
629
|
```
|
|
623
630
|
|
|
631
|
+
`&` is fine for a quick local check but isn't real process supervision — for
|
|
632
|
+
an actual deployment, use the reference Dockerfile, Compose file, and
|
|
633
|
+
Kubernetes Deployment under [`deploy/`](deploy/):
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# Docker Compose — N supervised replicas, distinct worker ids, restart policy
|
|
637
|
+
docker compose -f deploy/docker-compose.yml up --build --scale worker=3
|
|
638
|
+
|
|
639
|
+
# Kubernetes — same idea as a Deployment
|
|
640
|
+
kubectl apply -f deploy/kubernetes/worker-deployment.yaml
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
The CLI also exposes `--redis-password`, `--redis-username`, `--redis-mode`,
|
|
644
|
+
and `--redis-cluster-nodes` (all mirrored by the `REDIS_*` env vars used in
|
|
645
|
+
the Compose/Kubernetes examples above) — see `python -m by_framework --help`.
|
|
646
|
+
|
|
624
647
|
### Reliability
|
|
625
648
|
|
|
626
649
|
- **Message persistence:** Messages are stored in Redis Streams until explicitly acknowledged (`XACK`). Unacknowledged messages are redelivered on Worker restart.
|
|
627
650
|
- **Durable config:** Agent config snapshots are persisted to Redis, so a restarted Worker recovers the last-known plugin configuration.
|
|
628
|
-
- **
|
|
651
|
+
- **Graceful shutdown:** `WorkerRunner` drains in-flight tasks before shutting down, acknowledging completed work. This is wired to both `SIGINT` (Ctrl+C) and `SIGTERM` — the signal `docker stop`/Kubernetes pod termination actually send — so it fires under real container orchestration, not just an interactive terminal. Give it enough `stop_grace_period`/`terminationGracePeriodSeconds` to cover your longest task (see the `deploy/` examples), since a hard `SIGKILL` after the grace period skips the drain entirely.
|
|
629
652
|
- **Separate data path:** Data output goes to session-scoped streams independently of control, so backend consumers are decoupled from Worker scaling.
|
|
630
653
|
|
|
631
654
|
### Logging
|
|
@@ -221,6 +221,13 @@ async def main():
|
|
|
221
221
|
asyncio.run(main())
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
A runnable version of this pair lives at
|
|
225
|
+
[`examples/echo_worker.py`](examples/echo_worker.py) (Worker) and
|
|
226
|
+
[`examples/send_and_verify.py`](examples/send_and_verify.py) (client — blocks
|
|
227
|
+
until it sees the echoed reply) — the same pair `deploy/`'s CI smoke test
|
|
228
|
+
drives end to end, see
|
|
229
|
+
[`docs/architecture/production-deployment.md`](docs/architecture/production-deployment.md).
|
|
230
|
+
|
|
224
231
|
---
|
|
225
232
|
|
|
226
233
|
## Core Concepts
|
|
@@ -573,7 +580,7 @@ Tests are organized by module under `tests/`:
|
|
|
573
580
|
|
|
574
581
|
```bash
|
|
575
582
|
# 1. Start Redis
|
|
576
|
-
docker run -d --name by-redis -p 6379:6379
|
|
583
|
+
docker run -d --name by-redis -p 6379:6379 redis:7-alpine
|
|
577
584
|
|
|
578
585
|
# 2. Start a Worker
|
|
579
586
|
python -m by_framework \
|
|
@@ -592,11 +599,27 @@ python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-02 &
|
|
|
592
599
|
python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-03 &
|
|
593
600
|
```
|
|
594
601
|
|
|
602
|
+
`&` is fine for a quick local check but isn't real process supervision — for
|
|
603
|
+
an actual deployment, use the reference Dockerfile, Compose file, and
|
|
604
|
+
Kubernetes Deployment under [`deploy/`](deploy/):
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
# Docker Compose — N supervised replicas, distinct worker ids, restart policy
|
|
608
|
+
docker compose -f deploy/docker-compose.yml up --build --scale worker=3
|
|
609
|
+
|
|
610
|
+
# Kubernetes — same idea as a Deployment
|
|
611
|
+
kubectl apply -f deploy/kubernetes/worker-deployment.yaml
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
The CLI also exposes `--redis-password`, `--redis-username`, `--redis-mode`,
|
|
615
|
+
and `--redis-cluster-nodes` (all mirrored by the `REDIS_*` env vars used in
|
|
616
|
+
the Compose/Kubernetes examples above) — see `python -m by_framework --help`.
|
|
617
|
+
|
|
595
618
|
### Reliability
|
|
596
619
|
|
|
597
620
|
- **Message persistence:** Messages are stored in Redis Streams until explicitly acknowledged (`XACK`). Unacknowledged messages are redelivered on Worker restart.
|
|
598
621
|
- **Durable config:** Agent config snapshots are persisted to Redis, so a restarted Worker recovers the last-known plugin configuration.
|
|
599
|
-
- **
|
|
622
|
+
- **Graceful shutdown:** `WorkerRunner` drains in-flight tasks before shutting down, acknowledging completed work. This is wired to both `SIGINT` (Ctrl+C) and `SIGTERM` — the signal `docker stop`/Kubernetes pod termination actually send — so it fires under real container orchestration, not just an interactive terminal. Give it enough `stop_grace_period`/`terminationGracePeriodSeconds` to cover your longest task (see the `deploy/` examples), since a hard `SIGKILL` after the grace period skips the drain entirely.
|
|
600
623
|
- **Separate data path:** Data output goes to session-scoped streams independently of control, so backend consumers are decoupled from Worker scaling.
|
|
601
624
|
|
|
602
625
|
### Logging
|
|
@@ -221,6 +221,11 @@ async def main():
|
|
|
221
221
|
asyncio.run(main())
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
可运行的完整版本见 [`examples/echo_worker.py`](examples/echo_worker.py)(Worker)和
|
|
225
|
+
[`examples/send_and_verify.py`](examples/send_and_verify.py)(客户端,会一直等到收到回显再退出)——
|
|
226
|
+
`deploy/` 下的 CI 冒烟测试用的就是这一对,见
|
|
227
|
+
[`docs/architecture/production-deployment.md`](docs/architecture/production-deployment.md)。
|
|
228
|
+
|
|
224
229
|
---
|
|
225
230
|
|
|
226
231
|
## 核心概念
|
|
@@ -592,11 +597,27 @@ python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-02 &
|
|
|
592
597
|
python -m by_framework --worker-class my_agent.MyAgent --worker-id worker-03 &
|
|
593
598
|
```
|
|
594
599
|
|
|
600
|
+
`&` 适合本地快速验证,但不是真正的进程编排——实际部署请使用
|
|
601
|
+
[`deploy/`](deploy/) 目录下的参考 Dockerfile、Compose 文件和 Kubernetes
|
|
602
|
+
Deployment:
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
# Docker Compose —— N 个受进程守护的副本,各自独立的 worker id、重启策略
|
|
606
|
+
docker compose -f deploy/docker-compose.yml up --build --scale worker=3
|
|
607
|
+
|
|
608
|
+
# Kubernetes —— 同样的思路,写成 Deployment
|
|
609
|
+
kubectl apply -f deploy/kubernetes/worker-deployment.yaml
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
CLI 现在也支持 `--redis-password`、`--redis-username`、`--redis-mode`、
|
|
613
|
+
`--redis-cluster-nodes`(与上面 Compose/Kubernetes 示例里用的 `REDIS_*`
|
|
614
|
+
环境变量一一对应)——见 `python -m by_framework --help`。
|
|
615
|
+
|
|
595
616
|
### 可靠性
|
|
596
617
|
|
|
597
618
|
- **消息持久化:** 消息存储在 Redis Streams 中,直到显式确认(`XACK`)。未确认的消息在 Worker 重启后重新投递。
|
|
598
619
|
- **配置持久化:** Agent 配置快照持久化到 Redis,Worker 重启后恢复上次已知配置。
|
|
599
|
-
- **优雅退出:** `WorkerRunner`
|
|
620
|
+
- **优雅退出:** `WorkerRunner` 在退出前排空进行中的任务,确认已完成的工作。这一逻辑同时挂在 `SIGINT`(Ctrl+C)和 `SIGTERM`——也就是 `docker stop`/Kubernetes Pod 终止实际发送的信号——上,因此在真实容器编排下也能触发,而不仅是交互式终端里的 Ctrl+C。请给 `stop_grace_period`/`terminationGracePeriodSeconds` 留出覆盖最长任务耗时的余量(见 `deploy/` 示例),因为宽限期后的强制 `SIGKILL` 会完全跳过排空过程。
|
|
600
621
|
- **数据与控制分离:** 数据输出到会话级 Stream,与 Worker 扩缩容解耦。
|
|
601
622
|
|
|
602
623
|
### 日志
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
#
|
|
3
|
+
# Template Dockerfile for a by-framework Worker. This installs the published
|
|
4
|
+
# `by-framework` package from PyPI and expects YOUR worker code (the module
|
|
5
|
+
# containing your GatewayWorker subclass) to sit next to this Dockerfile in
|
|
6
|
+
# the build context - copy this file into your own project and adjust the
|
|
7
|
+
# COPY line and the default --worker-class below.
|
|
8
|
+
#
|
|
9
|
+
# Build: docker build -f deploy/Dockerfile -t my-agent-worker .
|
|
10
|
+
# Run: docker run --rm -e REDIS_HOST=redis my-agent-worker \
|
|
11
|
+
# --worker-class my_agent.MyAgent
|
|
12
|
+
|
|
13
|
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
14
|
+
|
|
15
|
+
ENV PYTHONUNBUFFERED=1 \
|
|
16
|
+
PATH="/opt/venv/bin:${PATH}"
|
|
17
|
+
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
# Pin this in production (e.g. --build-arg BY_FRAMEWORK_VERSION=0.2.2).
|
|
21
|
+
ARG BY_FRAMEWORK_VERSION=""
|
|
22
|
+
# "pypi" (default - for downstream projects) or "local" (used only by this
|
|
23
|
+
# repo's own .github/workflows/deploy-smoke-test.yml, to validate this
|
|
24
|
+
# Dockerfile against a PR's code before it's ever released). Leave this
|
|
25
|
+
# alone unless you're working on by-framework-python itself.
|
|
26
|
+
ARG BY_FRAMEWORK_SOURCE="pypi"
|
|
27
|
+
|
|
28
|
+
# Replace this with your own worker package/module.
|
|
29
|
+
COPY . /app/worker
|
|
30
|
+
ENV PYTHONPATH=/app/worker
|
|
31
|
+
|
|
32
|
+
# In "local" mode, /app/worker above IS the by-framework-python checkout, so
|
|
33
|
+
# install it editable from there instead of from PyPI.
|
|
34
|
+
RUN uv venv /opt/venv \
|
|
35
|
+
&& if [ "$BY_FRAMEWORK_SOURCE" = "local" ]; then \
|
|
36
|
+
uv pip install --python /opt/venv/bin/python -e /app/worker; \
|
|
37
|
+
else \
|
|
38
|
+
uv pip install --python /opt/venv/bin/python \
|
|
39
|
+
"by-framework${BY_FRAMEWORK_VERSION:+==${BY_FRAMEWORK_VERSION}}"; \
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
COPY deploy/entrypoint.sh /entrypoint.sh
|
|
43
|
+
|
|
44
|
+
# Opt-in readiness endpoint (see
|
|
45
|
+
# docs/architecture/worker-readiness-endpoint.md) - BYAI_WORKER_HEALTH_PORT
|
|
46
|
+
# is run_worker()'s own env-var fallback for --health-port, so this alone
|
|
47
|
+
# is enough to turn it on; no CMD change needed even if you override
|
|
48
|
+
# --worker-class below.
|
|
49
|
+
ENV BYAI_WORKER_HEALTH_PORT=8080
|
|
50
|
+
EXPOSE 8080
|
|
51
|
+
|
|
52
|
+
# Readiness only - see the hard rule in
|
|
53
|
+
# docs/architecture/worker-readiness-endpoint.md: never turn this into a
|
|
54
|
+
# liveness/restart-on-unhealthy check. urlopen() raises (non-zero exit) on
|
|
55
|
+
# the endpoint's real 503, so this actually fails on "not ready" - unlike
|
|
56
|
+
# a healthcheck that only checks "did the server respond at all".
|
|
57
|
+
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \
|
|
58
|
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/readyz', timeout=3)"
|
|
59
|
+
|
|
60
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
61
|
+
CMD ["--worker-class", "my_agent.MyAgent"]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Illustrates the README's "水平扩展"/"Horizontal Scaling" section as a real
|
|
2
|
+
# orchestration form instead of backgrounded shell processes: N Worker
|
|
3
|
+
# replicas, same agent type, distinct worker ids (see entrypoint.sh),
|
|
4
|
+
# competing for the same Redis control stream via a consumer group.
|
|
5
|
+
#
|
|
6
|
+
# Usage (downstream project, default my_agent.MyAgent + published PyPI package):
|
|
7
|
+
# docker compose -f deploy/docker-compose.yml up --build --scale worker=3
|
|
8
|
+
#
|
|
9
|
+
# Usage (this repo's own CI smoke test - see
|
|
10
|
+
# .github/workflows/deploy-smoke-test.yml - builds from local source and
|
|
11
|
+
# runs examples/echo_worker.py instead):
|
|
12
|
+
# BY_FRAMEWORK_SOURCE=local WORKER_CLASS=examples.echo_worker.EchoWorker \
|
|
13
|
+
# docker compose -f deploy/docker-compose.yml up --build
|
|
14
|
+
|
|
15
|
+
services:
|
|
16
|
+
redis:
|
|
17
|
+
image: redis:7-alpine
|
|
18
|
+
ports:
|
|
19
|
+
- "6379:6379"
|
|
20
|
+
healthcheck:
|
|
21
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
22
|
+
interval: 5s
|
|
23
|
+
timeout: 3s
|
|
24
|
+
retries: 5
|
|
25
|
+
|
|
26
|
+
worker:
|
|
27
|
+
build:
|
|
28
|
+
context: ..
|
|
29
|
+
dockerfile: deploy/Dockerfile
|
|
30
|
+
args:
|
|
31
|
+
BY_FRAMEWORK_SOURCE: ${BY_FRAMEWORK_SOURCE:-pypi}
|
|
32
|
+
depends_on:
|
|
33
|
+
redis:
|
|
34
|
+
condition: service_healthy
|
|
35
|
+
# Mirrors deploy/Dockerfile's HEALTHCHECK - redeclared here (Compose
|
|
36
|
+
# would otherwise just inherit the image's own) so this file is a
|
|
37
|
+
# self-contained example of the readiness endpoint's wiring, and so any
|
|
38
|
+
# future service added here can depend on
|
|
39
|
+
# `worker: condition: service_healthy` without having to go trace
|
|
40
|
+
# through the Dockerfile to confirm one exists. Readiness only - never
|
|
41
|
+
# liveness, see docs/architecture/worker-readiness-endpoint.md.
|
|
42
|
+
#
|
|
43
|
+
# No host port published for 8080/readyz on purpose: this file's
|
|
44
|
+
# documented `--scale worker=N` usage above would collide trying to
|
|
45
|
+
# publish the same fixed host port for every replica. The healthcheck
|
|
46
|
+
# itself needs no host port - Compose evaluates it inside the
|
|
47
|
+
# container's own network. deploy-smoke-test.yml verifies it via
|
|
48
|
+
# `up --wait`.
|
|
49
|
+
healthcheck:
|
|
50
|
+
test:
|
|
51
|
+
[
|
|
52
|
+
"CMD",
|
|
53
|
+
"python",
|
|
54
|
+
"-c",
|
|
55
|
+
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/readyz', timeout=3)",
|
|
56
|
+
]
|
|
57
|
+
interval: 10s
|
|
58
|
+
timeout: 3s
|
|
59
|
+
start_period: 10s
|
|
60
|
+
retries: 3
|
|
61
|
+
environment:
|
|
62
|
+
REDIS_HOST: redis
|
|
63
|
+
REDIS_PORT: "6379"
|
|
64
|
+
# REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE, REDIS_CLUSTER_HOST, etc.
|
|
65
|
+
# all follow RedisConfig.from_env() - see docs/architecture/redis-cluster-mode.md.
|
|
66
|
+
BYAI_WORKER_CONCURRENCY: "50"
|
|
67
|
+
command: ["--worker-class", "${WORKER_CLASS:-my_agent.MyAgent}"]
|
|
68
|
+
# Give WorkerRunner._shutdown() room to drain in-flight tasks before
|
|
69
|
+
# Compose escalates to SIGKILL on `docker compose down`/stop.
|
|
70
|
+
stop_grace_period: 30s
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Uses `exec` so the Python process replaces this shell as PID 1, receiving
|
|
3
|
+
# SIGTERM directly - a shell left as PID 1 does not forward signals to its
|
|
4
|
+
# child by default, which would silently defeat the graceful-shutdown fix in
|
|
5
|
+
# by_framework.worker.app (see docs/architecture/production-deployment.md).
|
|
6
|
+
#
|
|
7
|
+
# `$(hostname)` gives every replica a distinct worker id with no extra
|
|
8
|
+
# wiring: Docker Compose assigns each scaled replica a unique container
|
|
9
|
+
# hostname, and Kubernetes sets a Pod's hostname to its Pod name by default -
|
|
10
|
+
# so the same entrypoint works unchanged under `docker compose up --scale`
|
|
11
|
+
# and a Kubernetes Deployment.
|
|
12
|
+
set -e
|
|
13
|
+
exec python -m by_framework --worker-id "worker-$(hostname)" "$@"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Minimal reference Deployment for a by-framework Worker. Replaces the
|
|
2
|
+
# README's shell-`&` "水平扩展" example with a real, supervised, restart-on-
|
|
3
|
+
# failure orchestration form. Adjust image/replicas/resources for your setup.
|
|
4
|
+
#
|
|
5
|
+
# readinessProbe ONLY below - see docs/architecture/worker-readiness-endpoint.md.
|
|
6
|
+
# Do NOT add a livenessProbe pointed at /readyz (or at anything else,
|
|
7
|
+
# absent a separate deliberate design for one): a Worker's health depends
|
|
8
|
+
# on Redis being reachable, and wiring this to liveness would mean a
|
|
9
|
+
# transient Redis outage kills and restarts every Worker replica
|
|
10
|
+
# simultaneously - a self-inflicted restart storm strictly worse than the
|
|
11
|
+
# outage itself. A failing readinessProbe never kills anything; it only
|
|
12
|
+
# stops this replica from being counted, which is exactly what "not
|
|
13
|
+
# ready" should do.
|
|
14
|
+
apiVersion: apps/v1
|
|
15
|
+
kind: Deployment
|
|
16
|
+
metadata:
|
|
17
|
+
name: my-agent-worker
|
|
18
|
+
spec:
|
|
19
|
+
replicas: 3
|
|
20
|
+
selector:
|
|
21
|
+
matchLabels:
|
|
22
|
+
app: my-agent-worker
|
|
23
|
+
template:
|
|
24
|
+
metadata:
|
|
25
|
+
labels:
|
|
26
|
+
app: my-agent-worker
|
|
27
|
+
spec:
|
|
28
|
+
# WorkerRunner._shutdown() drains in-flight tasks on SIGTERM before
|
|
29
|
+
# exiting; give it real room before Kubernetes escalates to SIGKILL.
|
|
30
|
+
# Must be >= the longest task this Worker is expected to run.
|
|
31
|
+
terminationGracePeriodSeconds: 30
|
|
32
|
+
containers:
|
|
33
|
+
- name: worker
|
|
34
|
+
image: my-agent-worker:latest
|
|
35
|
+
args: ["--worker-class", "my_agent.MyAgent"]
|
|
36
|
+
env:
|
|
37
|
+
- name: REDIS_HOST
|
|
38
|
+
value: redis
|
|
39
|
+
- name: REDIS_PORT
|
|
40
|
+
value: "6379"
|
|
41
|
+
# REDIS_PASSWORD/REDIS_USERNAME: prefer a Secret + secretKeyRef.
|
|
42
|
+
# REDIS_MODE/REDIS_CLUSTER_HOST: see docs/architecture/redis-cluster-mode.md.
|
|
43
|
+
- name: BYAI_WORKER_CONCURRENCY
|
|
44
|
+
value: "50"
|
|
45
|
+
- name: BYAI_WORKER_HEALTH_PORT
|
|
46
|
+
value: "8080"
|
|
47
|
+
ports:
|
|
48
|
+
- name: readiness
|
|
49
|
+
containerPort: 8080
|
|
50
|
+
readinessProbe:
|
|
51
|
+
httpGet:
|
|
52
|
+
path: /readyz
|
|
53
|
+
port: readiness
|
|
54
|
+
periodSeconds: 5
|
|
55
|
+
timeoutSeconds: 3
|
|
56
|
+
failureThreshold: 3
|
|
57
|
+
resources:
|
|
58
|
+
requests:
|
|
59
|
+
cpu: "250m"
|
|
60
|
+
memory: "256Mi"
|
|
61
|
+
limits:
|
|
62
|
+
memory: "512Mi"
|