by-framework 0.2.2.dev13__tar.gz → 0.2.2.dev14__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.dev14/CHANGELOG.md +30 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/PKG-INFO +1 -1
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/KEY_FILES.md +68 -4
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/pyproject.toml +1 -1
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/client/client.py +6 -0
- by_framework-0.2.2.dev14/src/by_framework/worker/_resume_metadata.py +65 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/processor.py +89 -22
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/runner.py +29 -1
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/worker.py +43 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/client/test_client.py +29 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_nested_chain.py +148 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_gateway_worker.py +182 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_processor.py +220 -0
- by_framework-0.2.2.dev14/tests/worker/test_resume_metadata.py +69 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_runner.py +52 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/uv.lock +1 -1
- by_framework-0.2.2.dev13/CHANGELOG.md +0 -16
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.codex/hooks.json +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.dockerignore +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/RELEASING.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/workflows/ci.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/workflows/deploy-smoke-test.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/workflows/publish.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/workflows/stale.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.github/workflows/verify.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.gitignore +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/.pre-commit-config.yaml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/AGENTS.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/CLAUDE.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/CODE_OF_CONDUCT.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/CONTRIBUTING.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/LICENSE +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/Makefile +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/README_zh.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/SECURITY.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/assets/img/architecture_en.png +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/assets/img/architecture_zh.png +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/autoformat.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/deploy/Dockerfile +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/deploy/docker-compose.yml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/deploy/entrypoint.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/deploy/kubernetes/worker-deployment.yaml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/GUARD_AUTHORING.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/production-deployment.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/redis-cluster-mode.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/suspend-resume-liveness.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/architecture/worker-readiness-endpoint.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/by-framework-trace-change-usage.html +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/by-framework-trace-complete-guide.html +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/docs/worker_management.html +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/examples/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/examples/echo_worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/examples/send_and_verify.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/tests/test_adapter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-adk/tests/test_worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/01_basic_chat.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/02_tools_and_ask_user.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/03_agent_delegation.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/04_task_group.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/05_real_model_react.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/examples/_infra.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/litellm_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/loop.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/model_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/testing.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/tool_spec.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/src/by_framework_agent/worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/conftest.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_agent_as_tool.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_ask_user_suspend_resume.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_cancel_cleanup.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_native_agent_worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_task_group_batching.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-agent/tests/test_tool_calling_loop.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/Dockerfile +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/index.html +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/package.json +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-byclaw/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-postgres/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-postgres/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-langfuse/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/README.md +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/pyproject.toml +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/pylintrc +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/.map.conf +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/check-doc-discipline.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/check-entry-freshness.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/check-map-territory.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/map-stop-hook.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/python_quality.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/test-growth-ladder.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/scripts/verify.sh +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/__main__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/admin/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/admin/cli.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/admin/worker_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/client/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/client/byai_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/config.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/constants.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/emitter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/exceptions.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/logger.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/common/redis_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/availability.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/delivery_gate.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/discovery.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/agent_config.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/plugin.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/registry.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/extensions/trace_provider.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/action_type.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/agent_state.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/byai_codec.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/byai_command.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/byai_types.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/commands.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/content_codec.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/content_type.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/data_message.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/data_shapes.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/event_type.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/events.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/message.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/message_header.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/responses.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/protocol/results.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/registry.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/file_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/file_paths.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/file_permissions.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/filestore/base.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/filestore/local.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/history/base.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/history/history_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/history/in_memory.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/runtime/session_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/wait_gate.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/wait_index.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/wait_reply.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/wait_sweeper.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/wakeup_controller.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/core/workspace.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/base.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/common.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/execution.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/http.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/protocol.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/errors/registry.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/metrics/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/metrics/catalog.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/metrics/collector.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/metrics/read_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/metrics/snapshot.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/trace/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/trace/external_trace.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/trace/span_recorder.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/trace/trace_schema.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/trace/trace_writer.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/util/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/util/discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/util/generate_message_id.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/util/http_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/_control_handling.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/_execution_tracking.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/_message_processing.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/app.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/byai_context.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/byai_worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/context.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/health_server.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/heartbeat.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/sandbox/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/admin/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/admin/test_cli.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/client/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/test_config.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/test_constants.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/test_exceptions.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/test_logger.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/common/test_redis_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/conftest.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/protocol/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/protocol/test_byai_codec.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/protocol/test_command_wire.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/protocol/test_protocol.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/protocol/test_results.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/history/test_history_persistence.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_file_access_context.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_file_paths.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_file_permissions.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_filestore_local_collection.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_filestore_local_read.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/runtime/test_filestore_local_search.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_availability.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_discovery.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_registry.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_wait_gate.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_wait_index.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_wait_sweeper.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/core/test_worker_manager.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_ask_user_flow.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_callback_flow.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_logger_integration.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_orphan_recovery.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/integration/test_scatter_gather.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/metrics/test_catalog.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/metrics/test_collector.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/metrics/test_metrics.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/metrics/test_read_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/metrics/test_snapshot.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/test_langfuse_plugin.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/test_plugin_discovery.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/test_plugin_improvements.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/test_plugin_registry.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/plugin/test_plugin_system.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/test_main.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/trace/test_external_trace.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/trace/test_logger_correlation.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/trace/test_otel_exporter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/trace/test_span_recorder.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/trace/test_trace_writer.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/util/test_discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/util/test_discovery_http_client_download.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/util/test_discovery_http_client_upload.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/util/test_http_client.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_admin_control_handling.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_app.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_byai_worker.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_context.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_control_handling.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_emitter.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_fetch_messages.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_health_server.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_heartbeat.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_message_processing.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_sandbox.py +0 -0
- {by_framework-0.2.2.dev13 → by_framework-0.2.2.dev14}/tests/worker/test_workspace.py +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- A resumed agent can read its own request metadata again. Previously,
|
|
12
|
+
`command.header.metadata` after a suspend (`ask_user`, or a `call_agent`
|
|
13
|
+
hop) held only the metadata of the message that woke the agent up, and
|
|
14
|
+
everything it had originally been dispatched with was gone. It is now the
|
|
15
|
+
original dispatch metadata with the waking message's merged on top (the
|
|
16
|
+
waking message wins on key collisions). Handlers that treated the resumed
|
|
17
|
+
header's metadata as "only what this hop just sent" will now see additional
|
|
18
|
+
keys. Per-hop trace fields (`trace_parent_span_id`,
|
|
19
|
+
`framework_parent_span_id`, `langfuse_parent_observation_id`) still
|
|
20
|
+
describe the current hop and are not restored from the record. Executions
|
|
21
|
+
recorded before this change are unaffected — they degrade to the previous
|
|
22
|
+
behaviour rather than erroring.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Standardized Open Source governance files (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY).
|
|
26
|
+
- GitHub Issue and Pull Request templates.
|
|
27
|
+
- Dependabot configuration for dependency monitoring.
|
|
28
|
+
|
|
29
|
+
## [0.2.0] - 2026-05-13
|
|
30
|
+
### Initial release
|
|
@@ -260,7 +260,13 @@ Entry anatomy:
|
|
|
260
260
|
implementations fall back to minting a fresh execution_id. Root-dispatch
|
|
261
261
|
trace writes (`_write_trace_root_start/_end`) must only fire when
|
|
262
262
|
`not parent_message_id` — firing them on every `call_agent` hop would
|
|
263
|
-
duplicate trace roots.
|
|
263
|
+
duplicate trace roots. `initialize_execution()`'s payload carries the
|
|
264
|
+
dispatch `metadata`: a root agent suspends (an `ask_user` round is the
|
|
265
|
+
common case) and comes back on a `ResumeCommand` carrying the *callee's*
|
|
266
|
+
metadata, so the record is the only place its own request metadata still
|
|
267
|
+
exists for `worker.py`'s inbound restore to find. `runner.py` records the
|
|
268
|
+
same field independently on first pickup, which is what makes this work
|
|
269
|
+
for dispatchers that don't write it (TS/Java callers, a bare `xadd`).
|
|
264
270
|
|
|
265
271
|
- `src/by_framework/worker/runner.py` — `WorkerRunner`, the consume loop:
|
|
266
272
|
`XREADGROUP` fetch, command dispatch, resume/suspend bookkeeping, denylist
|
|
@@ -290,7 +296,16 @@ Entry anatomy:
|
|
|
290
296
|
suspended caller persists as `WAITING_AGENT`/`WAITING_USER` precisely to
|
|
291
297
|
keep that inference true; reusing QUEUED for any post-pickup state silently
|
|
292
298
|
makes a resume re-derive its identity from the message header instead of
|
|
293
|
-
the record.
|
|
299
|
+
the record. On a **non-resume** pickup it also writes `header.metadata`
|
|
300
|
+
onto the record (as an `update_execution_status` kwarg, or in the
|
|
301
|
+
`save_execution` fallback payload): `header.metadata` *is* the dispatch
|
|
302
|
+
metadata by definition, so recording it here — rather than trusting
|
|
303
|
+
whoever sent the message to have done it — is what makes
|
|
304
|
+
`worker.py`'s inbound restore work for a client root dispatch and for a
|
|
305
|
+
TS/Java caller, neither of which writes the field. The `ResumeCommand`
|
|
306
|
+
guard is load-bearing: a resume writing its own waking metadata over the
|
|
307
|
+
record would destroy the stored original that the restore exists to
|
|
308
|
+
recover, and nothing else keeps a copy. Every `ResumeCommand` passes the `core/wait_gate.py`
|
|
294
309
|
idempotency gate *here*, before the execution lookup — and being upstream
|
|
295
310
|
of `GatewayWorker` is what also puts it before Task Group join, whose
|
|
296
311
|
`HINCRBY completed` a duplicate would push past `total` and aggregate a
|
|
@@ -353,6 +368,22 @@ Entry anatomy:
|
|
|
353
368
|
transient hop overwrite the caller's own data instead of being layered
|
|
354
369
|
under `task_result.metadata` like `_enqueue_agent_return()`'s merge
|
|
355
370
|
already does correctly.
|
|
371
|
+
That is the OUTBOUND direction. The INBOUND one — what this execution's own
|
|
372
|
+
handler reads — is `_restore_inbound_metadata()`, and it is deliberately a
|
|
373
|
+
different rule: the original dispatch metadata from the same snapshot is
|
|
374
|
+
the base and the waking message's metadata is **merged on top** (waking
|
|
375
|
+
message wins collisions), because this agent *is* the addressee of that
|
|
376
|
+
message. Without it, everything a handler was dispatched with disappears
|
|
377
|
+
the moment it first suspends. Two constraints keep the directions from
|
|
378
|
+
contaminating each other: the restore must build a NEW command
|
|
379
|
+
(`prepare_command_for_processing()` returns the *same* object for the base
|
|
380
|
+
worker, so an in-place header mutation would rewrite `raw_command`'s and
|
|
381
|
+
leak the merge into the reply), and `_resolve_reply_command()` must keep
|
|
382
|
+
taking `raw_command`. The shared merge rule, including which framework
|
|
383
|
+
trace keys are excluded from the restored base, lives in
|
|
384
|
+
`_resume_metadata.py` — one copy, because the last time these two files
|
|
385
|
+
each carried their own version of a resume rule the processor was left
|
|
386
|
+
behind for a whole commit.
|
|
356
387
|
The success path must NOT reply while `context._is_suspended` — a suspended
|
|
357
388
|
execution has no result, and forwarding the value the handler returned in
|
|
358
389
|
order to unwind both wakes the caller early and consumes the single reply it
|
|
@@ -431,8 +462,22 @@ Entry anatomy:
|
|
|
431
462
|
to no execution. `_resolve_reply_header()` must read a resumed execution's
|
|
432
463
|
caller from the execution record, not from the resume header (which names
|
|
433
464
|
the sub-agent), while excluding `CLIENT_SOURCE_AGENT_TYPE` for the reason
|
|
434
|
-
spelled out under `worker.py`;
|
|
435
|
-
`
|
|
465
|
+
spelled out under `worker.py`; it restores **four** fields from that record
|
|
466
|
+
— `source_agent_type`, `parent_message_id`, `task_group_id` and
|
|
467
|
+
`metadata` — the last one as a full replacement, never merged with the
|
|
468
|
+
waking message's own metadata, exactly as `worker.py`'s
|
|
469
|
+
`_resolve_reply_command()` does (restoring only the three routing fields is
|
|
470
|
+
the drift this entry warns about: it shipped that way and leaked the waking
|
|
471
|
+
hop's metadata to the caller); `_restore_inbound_metadata()` mirrors
|
|
472
|
+
`worker.py`'s inbound merge for what the *handler* reads, and must run even
|
|
473
|
+
when `_resolve_reply_header()` returned `None` — a client-dispatched root
|
|
474
|
+
is owed no reply but still has its own metadata to get back, so the two
|
|
475
|
+
must not share a short-circuit. `_load_execution_snapshot()` is the single
|
|
476
|
+
registry read feeding both directions; being fail-soft there is what keeps
|
|
477
|
+
a registry error a degrade rather than a lost reply. The reply is built
|
|
478
|
+
from `raw_command`, never the inbound-restored one. And no callback may be
|
|
479
|
+
sent while `context._is_suspended` (with the same terminal-status
|
|
480
|
+
exception).
|
|
436
481
|
Being a *second* entry point for replies, it carries the same
|
|
437
482
|
`core/wait_gate.py` gate as `runner.py` — a gate on one of two doors is not
|
|
438
483
|
a gate, and an ungated reply here would both wake a resolved caller and
|
|
@@ -441,6 +486,25 @@ Entry anatomy:
|
|
|
441
486
|
`call_agents`' queued stand-ins on the same terms as `worker.py` — after
|
|
442
487
|
the handler returns, never when it raised.
|
|
443
488
|
|
|
489
|
+
- `src/by_framework/worker/_resume_metadata.py` — the one copy of the
|
|
490
|
+
**inbound** resume-metadata rule, shared by `worker.py` and `processor.py`
|
|
491
|
+
so the two cannot drift again. `merge_resume_metadata()` puts the
|
|
492
|
+
execution's original dispatch metadata underneath the waking message's
|
|
493
|
+
(waking message wins collisions) — the opposite of the outbound
|
|
494
|
+
replacement, because a resumed handler *is* the addressee of the message
|
|
495
|
+
that woke it. `FRAMEWORK_HOP_METADATA_KEYS` (`trace_parent_span_id`,
|
|
496
|
+
`framework_parent_span_id`, `langfuse_parent_observation_id`) are dropped
|
|
497
|
+
from the restored base: the framework injects them per dispatch, so a
|
|
498
|
+
stored copy describes the hop that *created* the execution, and
|
|
499
|
+
`context.py`'s `_resolve_call_langfuse_parent_id()` reads that key straight
|
|
500
|
+
off the current command — restoring it would parent a post-resume call to
|
|
501
|
+
an observation from before the suspend. The filter belongs here, at read
|
|
502
|
+
time, and not at write time: the outbound path deliberately restores the
|
|
503
|
+
stored dict verbatim, trace keys included, to stay identical to the
|
|
504
|
+
non-suspended reply. A missing/absent stored value degrades to the waking
|
|
505
|
+
message's metadata unchanged, which is the pre-restore behaviour, so old
|
|
506
|
+
records and cross-SDK callers need no migration or version gate.
|
|
507
|
+
|
|
444
508
|
- `src/by_framework/core/registry.py` — `WorkerRegistry`: Redis-backed worker
|
|
445
509
|
membership/heartbeat/execution-state, admin lifecycle, locking primitives.
|
|
446
510
|
`mark_execution_finished()` must stamp `finished_at` only
|
|
@@ -971,6 +971,12 @@ class GatewayClient:
|
|
|
971
971
|
"parent_message_id": params["parent_message_id"] or "",
|
|
972
972
|
"source_agent_type": CLIENT_SOURCE_AGENT_TYPE,
|
|
973
973
|
"target_agent_type": params["target_agent_type"],
|
|
974
|
+
# What this execution was dispatched with, so a worker
|
|
975
|
+
# can restore it after the execution suspends and
|
|
976
|
+
# resumes (see worker/_resume_metadata.py). Only the
|
|
977
|
+
# non-RESUME branch reaches here, so this never
|
|
978
|
+
# overwrites a suspended execution's original.
|
|
979
|
+
"metadata": dict(metadata or {}),
|
|
974
980
|
"stream_name": route.stream_name,
|
|
975
981
|
"status": "QUEUED",
|
|
976
982
|
"route_policy": route_policy,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""The inbound half of a resumed execution's metadata.
|
|
2
|
+
|
|
3
|
+
Two different directions restore metadata on a resume, and they are NOT the
|
|
4
|
+
same rule. Keep them apart:
|
|
5
|
+
|
|
6
|
+
- **Outbound** (``GatewayWorker._resolve_reply_command`` /
|
|
7
|
+
``GatewayProcessor._resolve_reply_header``): the header a resumed execution
|
|
8
|
+
*sends* to its caller. The stored dispatch metadata **replaces**
|
|
9
|
+
``header.metadata`` wholesale — the waking hop's data is plumbing the
|
|
10
|
+
original caller never asked for, and ``_enqueue_agent_return``'s
|
|
11
|
+
``{**header.metadata, **task_result.metadata}`` is the one sanctioned
|
|
12
|
+
channel for a handler to forward part of it.
|
|
13
|
+
- **Inbound** (this module): the header a resumed execution's own handler
|
|
14
|
+
*reads*. Here the waking message's metadata is legitimate payload — an
|
|
15
|
+
``ask_user`` answer's metadata was sent BY a client TO this agent — so it is
|
|
16
|
+
merged on top of the original dispatch metadata rather than discarded.
|
|
17
|
+
|
|
18
|
+
Without this, everything a handler was originally dispatched with disappears
|
|
19
|
+
the first time it suspends: it comes back seeing only whatever woke it up.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any, Mapping, Optional
|
|
23
|
+
|
|
24
|
+
# Injected per dispatch by AgentContext._dispatch_single_task, not supplied by
|
|
25
|
+
# business code. A stored copy of them is stale by definition — it describes
|
|
26
|
+
# the hop that dispatched the execution, not the hop resuming it now — so they
|
|
27
|
+
# are dropped from the restored base and always come from the current message.
|
|
28
|
+
# Letting them through would surface a dispatch-time span id where the waking
|
|
29
|
+
# message has none, and AgentContext._resolve_call_langfuse_parent_id() reads
|
|
30
|
+
# exactly that as its last-resort fallback: a call made after a resume would
|
|
31
|
+
# parent to the original dispatch's observation.
|
|
32
|
+
FRAMEWORK_HOP_METADATA_KEYS = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"trace_parent_span_id",
|
|
35
|
+
"framework_parent_span_id",
|
|
36
|
+
"langfuse_parent_observation_id",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def merge_resume_metadata(
|
|
42
|
+
stored: Optional[Mapping[str, Any]],
|
|
43
|
+
incoming: Optional[Mapping[str, Any]],
|
|
44
|
+
) -> dict[str, Any]:
|
|
45
|
+
"""Merge an execution's original dispatch metadata under this hop's.
|
|
46
|
+
|
|
47
|
+
``stored`` is the ``metadata`` field of the execution record (what the
|
|
48
|
+
execution was originally dispatched with); ``incoming`` is the waking
|
|
49
|
+
message's own metadata. The waking message wins on key collisions: it is
|
|
50
|
+
the newer, more specific hop, and this keeps the property that every key a
|
|
51
|
+
handler can read today stays readable — the restore only ever adds keys.
|
|
52
|
+
|
|
53
|
+
A ``stored`` that is missing (an execution recorded before this field
|
|
54
|
+
existed, or one dispatched by an SDK that doesn't write it) degrades to
|
|
55
|
+
``incoming`` unchanged, i.e. exactly the pre-restore behaviour.
|
|
56
|
+
|
|
57
|
+
Neither argument is mutated.
|
|
58
|
+
"""
|
|
59
|
+
merged = {
|
|
60
|
+
key: value
|
|
61
|
+
for key, value in (stored or {}).items()
|
|
62
|
+
if key not in FRAMEWORK_HOP_METADATA_KEYS
|
|
63
|
+
}
|
|
64
|
+
merged.update(incoming or {})
|
|
65
|
+
return merged
|
|
@@ -24,6 +24,7 @@ from by_framework.core.protocol.results import (
|
|
|
24
24
|
from by_framework.core.runtime.file_permissions import FilePermissionPolicy
|
|
25
25
|
from by_framework.core.wait_gate import consume_wait_entry, emit_orphaned_reply
|
|
26
26
|
from by_framework.core.wait_reply import flush_pending_group_replies
|
|
27
|
+
from by_framework.worker._resume_metadata import merge_resume_metadata
|
|
27
28
|
from by_framework.worker.context import AgentContext
|
|
28
29
|
|
|
29
30
|
ContextHandler = Callable[
|
|
@@ -99,8 +100,18 @@ class GatewayProcessor:
|
|
|
99
100
|
)
|
|
100
101
|
return None
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
# One lookup feeds both directions: the header this execution replies
|
|
104
|
+
# with, and the header its own handler reads. They are different
|
|
105
|
+
# rules over the same record — see _resolve_reply_header and
|
|
106
|
+
# _restore_inbound_metadata.
|
|
107
|
+
snapshot = (
|
|
108
|
+
await self._load_execution_snapshot(command) if is_agent_return else None
|
|
109
|
+
)
|
|
110
|
+
raw_command = command
|
|
111
|
+
reply_header = self._resolve_reply_header(command, snapshot)
|
|
103
112
|
has_source_agent = reply_header is not None
|
|
113
|
+
command = self._restore_inbound_metadata(command, is_agent_return, snapshot)
|
|
114
|
+
header = command.header
|
|
104
115
|
|
|
105
116
|
context = AgentContext(
|
|
106
117
|
session_id=header.session_id,
|
|
@@ -163,8 +174,10 @@ class GatewayProcessor:
|
|
|
163
174
|
getattr(context, "_is_suspended", False)
|
|
164
175
|
) and not is_terminal_state(task_result.status)
|
|
165
176
|
if has_source_agent and not is_suspended:
|
|
177
|
+
# raw_command, not the inbound-restored one: the reply is the
|
|
178
|
+
# outbound direction and must not inherit the inbound merge.
|
|
166
179
|
await self._enqueue_callback(
|
|
167
|
-
|
|
180
|
+
raw_command,
|
|
168
181
|
task_result.status,
|
|
169
182
|
task_result.reply_data,
|
|
170
183
|
content=task_result.content,
|
|
@@ -210,7 +223,7 @@ class GatewayProcessor:
|
|
|
210
223
|
|
|
211
224
|
if has_source_agent:
|
|
212
225
|
await self._enqueue_callback(
|
|
213
|
-
|
|
226
|
+
raw_command,
|
|
214
227
|
AgentState.FAILED.value,
|
|
215
228
|
{"error": str(e)},
|
|
216
229
|
reply_header=reply_header,
|
|
@@ -221,47 +234,101 @@ class GatewayProcessor:
|
|
|
221
234
|
)
|
|
222
235
|
raise
|
|
223
236
|
|
|
224
|
-
async def
|
|
237
|
+
async def _load_execution_snapshot(
|
|
225
238
|
self, command: GatewayCommand
|
|
226
|
-
) -> Optional[
|
|
227
|
-
"""
|
|
228
|
-
to, or None when nobody is waiting.
|
|
239
|
+
) -> Optional[dict[str, Any]]:
|
|
240
|
+
"""Read the execution record the original dispatch wrote.
|
|
229
241
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for the same reason as there, ``CLIENT_SOURCE_AGENT_TYPE`` is not a
|
|
234
|
-
caller. It is what a client writes on a root execution's record, and
|
|
235
|
-
nothing consumes its control stream.
|
|
242
|
+
Fetched once per resume and shared by both restore directions.
|
|
243
|
+
Fail-soft: a registry error degrades to "no record", which each caller
|
|
244
|
+
then handles as its own no-op.
|
|
236
245
|
"""
|
|
237
246
|
header = command.header
|
|
238
|
-
if not isinstance(command, ResumeCommand):
|
|
239
|
-
return header if header.source_agent_type else None
|
|
240
|
-
|
|
241
|
-
execution: Optional[dict[str, Any]] = None
|
|
242
247
|
try:
|
|
243
248
|
from by_framework.core.registry import WorkerRegistry
|
|
244
249
|
|
|
245
|
-
|
|
250
|
+
return await WorkerRegistry(self.redis).get_execution_by_message_id(
|
|
246
251
|
header.message_id, session_id=header.session_id
|
|
247
252
|
)
|
|
248
253
|
except Exception as error: # pylint: disable=broad-exception-caught
|
|
249
254
|
self.logger.warning(
|
|
250
|
-
"[%s] Could not
|
|
255
|
+
"[%s] Could not load the execution record of resumed execution "
|
|
256
|
+
"%s: %s",
|
|
251
257
|
self.worker_id,
|
|
252
258
|
header.message_id,
|
|
253
259
|
error,
|
|
254
260
|
)
|
|
255
261
|
return None
|
|
256
262
|
|
|
257
|
-
|
|
263
|
+
def _resolve_reply_header(
|
|
264
|
+
self,
|
|
265
|
+
command: GatewayCommand,
|
|
266
|
+
snapshot: Optional[dict[str, Any]],
|
|
267
|
+
) -> Optional[MessageHeader]:
|
|
268
|
+
"""Return the header describing the caller this execution owes a reply
|
|
269
|
+
to, or None when nobody is waiting.
|
|
270
|
+
|
|
271
|
+
Mirrors ``GatewayWorker._resolve_reply_command``: a resume's header
|
|
272
|
+
describes the sub-agent that just finished, so the caller has to be
|
|
273
|
+
read back from the execution record the original dispatch wrote — and,
|
|
274
|
+
for the same reason as there, ``CLIENT_SOURCE_AGENT_TYPE`` is not a
|
|
275
|
+
caller. It is what a client writes on a root execution's record, and
|
|
276
|
+
nothing consumes its control stream.
|
|
277
|
+
|
|
278
|
+
Four fields come from that snapshot, not from the waking message:
|
|
279
|
+
``source_agent_type``, ``parent_message_id``, ``task_group_id`` and
|
|
280
|
+
``metadata``. The last one is restored as a full replacement rather
|
|
281
|
+
than a merge, exactly as ``GatewayWorker._resolve_reply_command``
|
|
282
|
+
does it: the waking message (an ``ask_user`` answer, or a sub-call's
|
|
283
|
+
reply) is transient plumbing for that one hop, not something the
|
|
284
|
+
caller ever sent. A snapshot missing the field (an execution recorded
|
|
285
|
+
before it existed) degrades to an empty dict rather than leaking the
|
|
286
|
+
waking message's metadata to the caller.
|
|
287
|
+
|
|
288
|
+
This is the OUTBOUND direction only. What the handler itself reads is
|
|
289
|
+
``_restore_inbound_metadata``, which merges rather than replaces —
|
|
290
|
+
and which must run even when this returns None, since a
|
|
291
|
+
client-dispatched root has no caller but still has its own metadata
|
|
292
|
+
to get back.
|
|
293
|
+
"""
|
|
294
|
+
header = command.header
|
|
295
|
+
if not isinstance(command, ResumeCommand):
|
|
296
|
+
return header if header.source_agent_type else None
|
|
297
|
+
|
|
298
|
+
caller_agent_type = str((snapshot or {}).get("source_agent_type", "") or "")
|
|
258
299
|
if not caller_agent_type or caller_agent_type == CLIENT_SOURCE_AGENT_TYPE:
|
|
259
300
|
return None
|
|
260
301
|
return replace(
|
|
261
302
|
header,
|
|
262
303
|
source_agent_type=caller_agent_type,
|
|
263
|
-
parent_message_id=str((
|
|
264
|
-
task_group_id=str((
|
|
304
|
+
parent_message_id=str((snapshot or {}).get("parent_message_id", "") or ""),
|
|
305
|
+
task_group_id=str((snapshot or {}).get("task_group_id", "") or ""),
|
|
306
|
+
metadata=dict((snapshot or {}).get("metadata") or {}),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def _restore_inbound_metadata(
|
|
311
|
+
command: GatewayCommand,
|
|
312
|
+
is_agent_return: bool,
|
|
313
|
+
snapshot: Optional[dict[str, Any]],
|
|
314
|
+
) -> GatewayCommand:
|
|
315
|
+
"""Give a resumed handler its own dispatch metadata back.
|
|
316
|
+
|
|
317
|
+
Mirrors ``GatewayWorker._restore_inbound_metadata``, including its
|
|
318
|
+
merge-don't-replace rule and its no-mutation rule; see that docstring
|
|
319
|
+
for why the inbound direction differs from the outbound one.
|
|
320
|
+
"""
|
|
321
|
+
if not is_agent_return:
|
|
322
|
+
return command
|
|
323
|
+
return replace(
|
|
324
|
+
command,
|
|
325
|
+
header=replace(
|
|
326
|
+
command.header,
|
|
327
|
+
metadata=merge_resume_metadata(
|
|
328
|
+
(snapshot or {}).get("metadata"),
|
|
329
|
+
command.header.metadata,
|
|
330
|
+
),
|
|
331
|
+
),
|
|
265
332
|
)
|
|
266
333
|
|
|
267
334
|
async def _enqueue_callback(
|
|
@@ -625,11 +625,33 @@ class WorkerRunner:
|
|
|
625
625
|
if registry:
|
|
626
626
|
if existing_execution:
|
|
627
627
|
if hasattr(registry, "update_execution_status"):
|
|
628
|
+
status_fields: dict[str, Any] = {
|
|
629
|
+
"worker_id": self.worker.worker_id
|
|
630
|
+
}
|
|
631
|
+
if not isinstance(command, ResumeCommand):
|
|
632
|
+
# The dispatch metadata, recorded by whoever is
|
|
633
|
+
# executing the message rather than by whoever
|
|
634
|
+
# sent it: header.metadata IS the dispatch
|
|
635
|
+
# metadata by definition, so this is correct for
|
|
636
|
+
# every dispatcher — including a client root
|
|
637
|
+
# dispatch and a TS/Java caller, neither of which
|
|
638
|
+
# writes the field (see context.py's
|
|
639
|
+
# initialize_execution, which does, and is kept
|
|
640
|
+
# because it is the only record that exists
|
|
641
|
+
# before a callee is ever picked up).
|
|
642
|
+
# _handle_message reads it back to restore what
|
|
643
|
+
# this execution was originally asked for once it
|
|
644
|
+
# resumes.
|
|
645
|
+
#
|
|
646
|
+
# A ResumeCommand must NOT write it: the waking
|
|
647
|
+
# message's metadata would overwrite the very
|
|
648
|
+
# original this exists to preserve.
|
|
649
|
+
status_fields["metadata"] = dict(header.metadata)
|
|
628
650
|
await registry.update_execution_status(
|
|
629
651
|
execution_id,
|
|
630
652
|
header.session_id,
|
|
631
653
|
"RUNNING",
|
|
632
|
-
|
|
654
|
+
**status_fields,
|
|
633
655
|
)
|
|
634
656
|
elif hasattr(registry, "save_execution"):
|
|
635
657
|
await registry.save_execution(
|
|
@@ -641,6 +663,12 @@ class WorkerRunner:
|
|
|
641
663
|
"parent_message_id": header.parent_message_id or "",
|
|
642
664
|
"worker_id": self.worker.worker_id,
|
|
643
665
|
"target_agent_type": header.target_agent_type,
|
|
666
|
+
# Same reason as the update branch above. This one
|
|
667
|
+
# is the no-existing-record fallback, so there is
|
|
668
|
+
# no stored original to protect and no ResumeCommand
|
|
669
|
+
# guard to make: whatever woke this is all this
|
|
670
|
+
# execution has ever been told.
|
|
671
|
+
"metadata": dict(header.metadata),
|
|
644
672
|
"stream_name": stream_name,
|
|
645
673
|
"redis_message_id": msg_id,
|
|
646
674
|
"status": "RUNNING",
|
|
@@ -59,6 +59,7 @@ from by_framework.trace.span_recorder import TraceSpan, str_to_uint64
|
|
|
59
59
|
from by_framework.worker.context import AgentContext, current_agent_context_var
|
|
60
60
|
from by_framework.worker.heartbeat import WorkerHeartbeat
|
|
61
61
|
|
|
62
|
+
from ._resume_metadata import merge_resume_metadata
|
|
62
63
|
from .sandbox.hook_sandbox import active_workspace
|
|
63
64
|
|
|
64
65
|
|
|
@@ -377,6 +378,47 @@ class GatewayWorker(ABC):
|
|
|
377
378
|
),
|
|
378
379
|
)
|
|
379
380
|
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _restore_inbound_metadata(
|
|
383
|
+
command: GatewayCommand,
|
|
384
|
+
is_agent_return: bool,
|
|
385
|
+
execution: Optional["RunningExecution"],
|
|
386
|
+
) -> GatewayCommand:
|
|
387
|
+
"""Give a resumed handler its own dispatch metadata back.
|
|
388
|
+
|
|
389
|
+
The mirror image of ``_resolve_reply_command``, and deliberately not
|
|
390
|
+
the same rule. That one rebuilds the header this execution *sends*;
|
|
391
|
+
this one rebuilds the header it *reads*. A resumed handler otherwise
|
|
392
|
+
sees only the metadata of whatever woke it up — an ``ask_user``
|
|
393
|
+
answer's, or a sub-call's reply — and everything the execution was
|
|
394
|
+
originally dispatched with is gone from the moment it first suspends.
|
|
395
|
+
|
|
396
|
+
Merged, not replaced (the opposite of the outbound direction): this
|
|
397
|
+
agent IS the addressee of the waking message, so its metadata is real
|
|
398
|
+
payload here rather than someone else's plumbing. Original dispatch
|
|
399
|
+
metadata is the base, the waking message wins on collisions. See
|
|
400
|
+
``_resume_metadata.py`` for why the framework's per-hop trace keys are
|
|
401
|
+
excluded from the base.
|
|
402
|
+
|
|
403
|
+
Returns a NEW command — never mutates. ``prepare_command_for_processing``
|
|
404
|
+
returns the *same* object for the base worker (only ``ByaiWorker``
|
|
405
|
+
rebuilds it), so mutating the header here would also rewrite
|
|
406
|
+
``raw_command``'s and leak this merge into the reply that goes out.
|
|
407
|
+
"""
|
|
408
|
+
if not is_agent_return:
|
|
409
|
+
return command
|
|
410
|
+
snapshot = getattr(execution, "existing_data", None) or {}
|
|
411
|
+
return replace(
|
|
412
|
+
command,
|
|
413
|
+
header=replace(
|
|
414
|
+
command.header,
|
|
415
|
+
metadata=merge_resume_metadata(
|
|
416
|
+
snapshot.get("metadata"),
|
|
417
|
+
command.header.metadata,
|
|
418
|
+
),
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
|
|
380
422
|
@staticmethod
|
|
381
423
|
def _apply_suspended_status(
|
|
382
424
|
task_result: AgentTaskResult,
|
|
@@ -682,6 +724,7 @@ class GatewayWorker(ABC):
|
|
|
682
724
|
is_agent_return = isinstance(raw_command, ResumeCommand)
|
|
683
725
|
reply_command = self._resolve_reply_command(raw_command, execution)
|
|
684
726
|
has_source_agent = reply_command is not None
|
|
727
|
+
command = self._restore_inbound_metadata(command, is_agent_return, execution)
|
|
685
728
|
|
|
686
729
|
# Get workspace dir from workspace_manager if available
|
|
687
730
|
# Note: We don't use hasattr check because it doesn't work well with mocks
|
|
@@ -1340,6 +1340,34 @@ async def test_client_send_message_ask_agent_does_not_query_registry_for_resume_
|
|
|
1340
1340
|
mock_registry.initialize_execution.assert_awaited_once()
|
|
1341
1341
|
|
|
1342
1342
|
|
|
1343
|
+
@pytest.mark.asyncio
|
|
1344
|
+
async def test_client_root_dispatch_records_its_metadata_on_the_execution():
|
|
1345
|
+
"""A root execution's own dispatch metadata has to be on its record.
|
|
1346
|
+
|
|
1347
|
+
The agent will suspend (an `ask_user` round is the common case) and come
|
|
1348
|
+
back on a ResumeCommand carrying the *callee's* metadata; the only way it
|
|
1349
|
+
can still read what the client originally sent is for the client to have
|
|
1350
|
+
recorded it here.
|
|
1351
|
+
"""
|
|
1352
|
+
mock_redis = AsyncMock()
|
|
1353
|
+
mock_registry = AsyncMock()
|
|
1354
|
+
|
|
1355
|
+
client = GatewayClient(redis_client=mock_redis, registry=mock_registry)
|
|
1356
|
+
await client.send_message(
|
|
1357
|
+
target_agent_type="langgraph_agent",
|
|
1358
|
+
session_id="s1",
|
|
1359
|
+
content="hello",
|
|
1360
|
+
action_type="ASK_AGENT",
|
|
1361
|
+
message_id="msg-new",
|
|
1362
|
+
target_worker_id="worker-42",
|
|
1363
|
+
metadata={"tenant": "acme", "req": "r-1"},
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
initialized = mock_registry.initialize_execution.await_args.args[0]
|
|
1367
|
+
assert initialized["metadata"]["tenant"] == "acme"
|
|
1368
|
+
assert initialized["metadata"]["req"] == "r-1"
|
|
1369
|
+
|
|
1370
|
+
|
|
1343
1371
|
@pytest.mark.asyncio
|
|
1344
1372
|
async def test_client_send_message_resume_falls_back_when_registry_lacks_lookup():
|
|
1345
1373
|
"""A registry double/implementation without get_execution_by_message_id
|
|
@@ -1349,6 +1377,7 @@ async def test_client_send_message_resume_falls_back_when_registry_lacks_lookup(
|
|
|
1349
1377
|
mock_redis = AsyncMock()
|
|
1350
1378
|
|
|
1351
1379
|
class RegistryWithoutLookup:
|
|
1380
|
+
|
|
1352
1381
|
async def initialize_execution(self, execution):
|
|
1353
1382
|
return None
|
|
1354
1383
|
|