From 1000afe559ada6f8b75f460c9dc37e424c6fc9fa Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Mon, 22 Sep 2025 11:37:35 -0700 Subject: [PATCH 1/4] add tools for reporting, fix numerous bugs, bump pydantic to latest version --- connector_builder_agents/pyproject.toml | 2 +- connector_builder_agents/src/agents.py | 46 ++++++++++++++++++- connector_builder_agents/src/guidance.py | 11 +++-- connector_builder_agents/src/run.py | 5 ++ connector_builder_agents/src/tools.py | 50 +++++++++++++++++++- connector_builder_agents/uv.lock | 8 ++-- connector_builder_mcp/validation_testing.py | 51 ++++++++++----------- poe_tasks.toml | 4 +- pyproject.toml | 2 +- uv.lock | 8 ++-- 10 files changed, 143 insertions(+), 44 deletions(-) diff --git a/connector_builder_agents/pyproject.toml b/connector_builder_agents/pyproject.toml index fd53c6a..88d748c 100644 --- a/connector_builder_agents/pyproject.toml +++ b/connector_builder_agents/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "openai-agents-mcp>=0.0.8", "openai-agents>=0.2.11", "mcp-agent>=0.1.15", # Transitive dependency of openai-agents - "pydantic>=2.0.0", # For phase data models + "pydantic>=2.11.9,<3.0", ] [dependency-groups] diff --git a/connector_builder_agents/src/agents.py b/connector_builder_agents/src/agents.py index 46667ed..976af74 100644 --- a/connector_builder_agents/src/agents.py +++ b/connector_builder_agents/src/agents.py @@ -1,10 +1,10 @@ # Copyright (c) 2025 Airbyte, Inc., all rights reserved. """Agent implementations for the Airbyte connector builder.""" - from agents import Agent as OpenAIAgent from agents import ( handoff, ) +from pydantic.main import BaseModel # from agents import OpenAIConversationsSession from .constants import ( @@ -15,10 +15,13 @@ from .tools import ( DEVELOPER_AGENT_TOOLS, MANAGER_AGENT_TOOLS, + get_latest_readiness_report, + get_progress_log_text, log_problem_encountered_by_developer, log_problem_encountered_by_manager, log_progress_milestone_from_developer, log_progress_milestone_from_manager, + log_tool_failure, mark_job_failed, mark_job_success, update_progress_log, @@ -43,6 +46,7 @@ def create_developer_agent( tools=[ log_progress_milestone_from_developer, log_problem_encountered_by_developer, + log_tool_failure, ], ) @@ -101,5 +105,45 @@ async def on_phase3_handoff(ctx, input_data: Phase3Data) -> None: mark_job_failed, log_problem_encountered_by_manager, log_progress_milestone_from_manager, + log_tool_failure, + get_latest_readiness_report, + get_progress_log_text, ], ) + + +class ManagerHandoffInput(BaseModel): + """Input data for handoff from developer back to manager.""" + + short_status: str + detailed_progress_update: str + is_blocked: bool + is_partial_success: bool + is_full_success: bool + + +async def on_manager_handback(ctx, input_data: ManagerHandoffInput) -> None: + update_progress_log( + f"🤝 Handing back control to manager." + "\n Summary of status: {input_data.short_status}" + f"\n Partial success: {input_data.is_partial_success}" + f"\n Full success: {input_data.is_full_success}" + f"\n Blocked: {input_data.is_blocked}" + f"\n Detailed progress update: {input_data.detailed_progress_update}" + ) + + +def add_handback_to_manager( + developer_agent: OpenAIAgent, + manager_agent: OpenAIAgent, +) -> None: + """Add a handoff from the developer back to the manager to report progress.""" + developer_agent.handoffs.append( + handoff( + agent=manager_agent, + tool_name_override="report_back_to_manager", + tool_description_override="Report progress or issues back to the manager agent", + input_type=ManagerHandoffInput, + on_handoff=on_manager_handback, + ) + ) diff --git a/connector_builder_agents/src/guidance.py b/connector_builder_agents/src/guidance.py index d5212e1..022cd61 100644 --- a/connector_builder_agents/src/guidance.py +++ b/connector_builder_agents/src/guidance.py @@ -20,10 +20,13 @@ Monitor progress and ensure each phase completes successfully before moving to the next. -When checking on the progress of your developer, if the build is complete, summarize the results and -evaluate whether they meet the requirements. If not, you can repeat a phase, calling out what they -missed and suggesting next steps. Determine the next phase or next appropriate action based on their -progress. +When checking on the progress of your developer: +- Use the `get_progress_log_text` tool to get the latest progress log. +- Use the `get_latest_readiness_report` tool to get the latest readiness report. + +If the build is complete, summarize the results and evaluate whether they meet the requirements. If +not, you can repeat a phase, calling out what they missed and suggesting next steps. Determine the +next phase or next appropriate action based on their progress. ## Exit Criteria diff --git a/connector_builder_agents/src/run.py b/connector_builder_agents/src/run.py index 983c2e4..8579636 100644 --- a/connector_builder_agents/src/run.py +++ b/connector_builder_agents/src/run.py @@ -16,6 +16,7 @@ # from agents import OpenAIConversationsSession from ._util import open_if_browser_available from .agents import ( + add_handback_to_manager, create_developer_agent, create_manager_agent, ) @@ -161,6 +162,10 @@ async def run_manager_developer_build( api_name=api_name or "(see below)", additional_instructions=instructions or "", ) + add_handback_to_manager( + developer_agent=developer_agent, + manager_agent=manager_agent, + ) for server in [*MANAGER_AGENT_TOOLS, *DEVELOPER_AGENT_TOOLS]: await server.connect() diff --git a/connector_builder_agents/src/tools.py b/connector_builder_agents/src/tools.py index 4518b9f..0a389b1 100644 --- a/connector_builder_agents/src/tools.py +++ b/connector_builder_agents/src/tools.py @@ -1,8 +1,8 @@ # Copyright (c) 2025 Airbyte, Inc., all rights reserved. """Tools and utilities for running MCP-based agents for connector building.""" - from datetime import datetime from enum import Enum +from typing import Annotated from agents.mcp import ( MCPServer, @@ -11,6 +11,7 @@ ) from agents.mcp.util import create_static_tool_filter from agents.tool import function_tool +from pydantic.fields import Field # from agents import OpenAIConversationsSession from .constants import HEADLESS_BROWSER, WORKSPACE_WRITE_DIR @@ -193,6 +194,37 @@ def log_problem_encountered( update_progress_log(f"⚠️ {agent.value} Encountered a Problem: {description}") +@function_tool() +def log_tool_failure( + tool_name: Annotated[ + str, + Field(description="Name of the tool that failed."), + ], + input_args: Annotated[str, Field(description="Input arguments for the tool.")], + # *, + summary_failure_description: Annotated[ + str, Field(description="Summary description of the failure.") + ], + is_unexpected_input_args_error: bool = False, + is_unhelpful_error_message: bool = False, + additional_info: str | None = None, +) -> None: + """Log a tool failure message. + + This is a specialized version of `log_problem_encountered` to report tool failures. + """ + msg = f"🛠️ {tool_name} Tool Failure in '{tool_name}': {summary_failure_description}" + if is_unexpected_input_args_error: + msg += " (🙈 Unexpected input arguments error)" + if is_unhelpful_error_message: + msg += " (🫤 Unhelpful error message)" + msg += f"\n Input args: '{input_args}'" + if additional_info: + msg += f"\n Additional info: '{additional_info}'" + + update_progress_log(msg) + + @function_tool(name_override="log_problem_encountered") def log_problem_encountered_by_manager(description: str) -> None: """Log a problem encountered message from the manager agent.""" @@ -215,3 +247,19 @@ def log_progress_milestone_from_manager(message: str) -> None: def log_progress_milestone_from_developer(message: str) -> None: """Log a milestone message from the developer agent.""" log_progress_milestone(message, AgentEnum.DEVELOPER_AGENT_NAME) + + +@function_tool +def get_progress_log_text() -> str: + """Get the current progress log text.""" + return EXECUTION_LOG_FILE.absolute().read_text(encoding="utf-8") + + +@function_tool +def get_latest_readiness_report() -> str: + """Get the path to the latest connector readiness report, if it exists.""" + report_path = WORKSPACE_WRITE_DIR / "connector-readiness-report.md" + if report_path.exists(): + return report_path.absolute().read_text(encoding="utf-8") + + return "No readiness report found." diff --git a/connector_builder_agents/uv.lock b/connector_builder_agents/uv.lock index 5e15569..b263922 100644 --- a/connector_builder_agents/uv.lock +++ b/connector_builder_agents/uv.lock @@ -114,7 +114,7 @@ requires-dist = [ { name = "mcp-agent", specifier = ">=0.1.15" }, { name = "openai-agents", specifier = ">=0.2.11" }, { name = "openai-agents-mcp", specifier = ">=0.0.8" }, - { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pydantic", specifier = ">=2.11.9,<3.0" }, { name = "python-dotenv", specifier = ">=1.1.1" }, ] @@ -1200,7 +1200,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1208,9 +1208,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, ] [[package]] diff --git a/connector_builder_mcp/validation_testing.py b/connector_builder_mcp/validation_testing.py index dd5d76b..14dc5fd 100644 --- a/connector_builder_mcp/validation_testing.py +++ b/connector_builder_mcp/validation_testing.py @@ -30,7 +30,6 @@ from airbyte_cdk.sources.declarative.parsers.manifest_reference_resolver import ( ManifestReferenceResolver, ) - from connector_builder_mcp._secrets import hydrate_config from connector_builder_mcp._util import ( as_bool, @@ -511,12 +510,6 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too description="Optional paths/URLs to local .env files or Privatebin.net URLs for secret hydration. Can be a single string, comma-separated string, or list of strings. Privatebin secrets may be created at privatebin.net, and must: contain text formatted as a dotenv file, use a password sent via the `PRIVATEBIN_PASSWORD` env var, and not include password text in the URL." ), ] = None, - save_to_project_dir: Annotated[ - str | Path | None, - Field( - description="Optional path to the project directory where the report should be saved." - ), - ] = None, ) -> str: """Execute a connector readiness test and generate a comprehensive markdown report. @@ -537,11 +530,14 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too stream_results: dict[str, StreamSmokeTest] = {} manifest_dict, manifest_path = parse_manifest_input(manifest) - if not save_to_project_dir and manifest_path: - save_to_project_dir = Path(manifest_path).parent + session_artifacts_dir: Path | None = None + if manifest_path: + session_artifacts_dir = Path(manifest_path).parent report_output_path: Path | None = ( - Path(save_to_project_dir) / "connector-readiness-report.md" if save_to_project_dir else None + Path(session_artifacts_dir) / "connector-readiness-report.md" + if session_artifacts_dir + else None ) config = hydrate_config( @@ -654,6 +650,7 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too "# Connector Readiness Test Report", "", "## Summary", + "", f"- **Streams Tested**: {total_streams_tested} out of {total_available_streams} total streams", f"- **Successful Streams**: {total_streams_successful}/{total_streams_tested}", f"- **Total Records Extracted**: {total_records_count:,}", @@ -680,13 +677,12 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too if field_warnings: warnings.append(f"⚠️ Field count issues: {'; '.join(field_warnings[:2])}") - report_lines.extend( - [ - f"### {stream_name} ✅", - f"- **Records Extracted**: {smoke_result.records_read:,}", - f"- **Duration**: {smoke_result.duration_seconds:.2f}s", - ] - ) + report_lines.extend([ + f"### `{stream_name}` ✅", + "", + f"- **Records Extracted**: {smoke_result.records_read:,}", + f"- **Duration**: {smoke_result.duration_seconds:.2f}s", + ]) if warnings: report_lines.append(f"- **Warnings**: {' | '.join(warnings)}") @@ -695,19 +691,22 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too report_lines.append("") else: - error_msg = getattr(smoke_result, "error_message", "Unknown error") - report_lines.extend( - [ - f"### {stream_name} ❌", - "- **Status**: Failed", - f"- **Error**: {error_msg}", - "", - ] + error_msg = getattr( + smoke_result, + "error_message", + "Unknown error", ) + report_lines.extend([ + f"### `{stream_name}` ❌", + "", + "- **Status**: Failed", + f"- **Error**: {error_msg}", + "", + ]) return _as_saved_report( report_text="\n".join(report_lines), - file_path=save_to_project_dir, + file_path=report_output_path, ) diff --git a/poe_tasks.toml b/poe_tasks.toml index 2cdf9cc..add388c 100644 --- a/poe_tasks.toml +++ b/poe_tasks.toml @@ -2,7 +2,7 @@ # Development Tasks build = { cmd = "bin/build_connector_feature_index.py", help = "Build connector feature index from Airbyte manifests" } -install = { cmd = "uv sync --all-extras", help = "Install and sync project dependencies." } +install = { shell = "uv sync --all-extras && uv sync --project=connector_builder_agents --all-extras", help = "Install and sync project dependencies." } sync = { cmd = "uv sync --all-extras", help = "Sync project dependencies. (Alias for install.)" } format = { cmd = "uv run ruff format .", help = "Format code with ruff" } format-check = { cmd = "uv run ruff format --check .", help = "Format check with ruff" } @@ -13,7 +13,7 @@ which actionlint >/dev/null 2>&1 && actionlint || \ (echo "ERROR: actionlint not found. Install with: 'brew install actionlint'" && exit 1) """ check-gh-actions.help = "Lint GitHub Actions workflows" -lock = { cmd = "uv lock", help = "Lock dependencies." } +lock = { shell = "uv lock && uv lock --project=connector_builder_agents", help = "Lock dependencies." } mypy = { cmd = "uv run mypy connector_builder_mcp", help = "Type check code with mypy" } test = { cmd = "uv run pytest tests/ -v", help = "Run tests with verbose output" } test-fast = { cmd = "uv run pytest --exitfirst tests", help = "Run tests, stop on first failure" } diff --git a/pyproject.toml b/pyproject.toml index 8a2c1da..5e7dcb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "fastmcp>=2.12.1,<3.0", "jsonschema>=4.0.0,<5.0", "privatebin>=0.3.0,<1.0", - "pydantic>=2.7.0,<3.0", + "pydantic>=2.11.9,<3.0", "python-dotenv>=1.1.1,<2.0", "pyyaml>=6.0.0,<7.0", "requests>=2.25.0,<3.0", diff --git a/uv.lock b/uv.lock index ea99aff..9662202 100644 --- a/uv.lock +++ b/uv.lock @@ -100,7 +100,7 @@ requires-dist = [ { name = "fastmcp", specifier = ">=2.12.1,<3.0" }, { name = "jsonschema", specifier = ">=4.0.0,<5.0" }, { name = "privatebin", specifier = ">=0.3.0,<1.0" }, - { name = "pydantic", specifier = ">=2.7.0,<3.0" }, + { name = "pydantic", specifier = ">=2.11.9,<3.0" }, { name = "python-dotenv", specifier = ">=1.1.1,<2.0" }, { name = "pyyaml", specifier = ">=6.0.0,<7.0" }, { name = "requests", specifier = ">=2.25.0,<3.0" }, @@ -1594,7 +1594,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.7" +version = "2.11.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1602,9 +1602,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, ] [package.optional-dependencies] From fcd1bd366176c3935eece36c43342451bc772329 Mon Sep 17 00:00:00 2001 From: Aaron Steers Date: Mon, 22 Sep 2025 11:49:17 -0700 Subject: [PATCH 2/4] format fix --- connector_builder_agents/src/agents.py | 1 + connector_builder_agents/src/tools.py | 1 + connector_builder_mcp/validation_testing.py | 31 ++++++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/connector_builder_agents/src/agents.py b/connector_builder_agents/src/agents.py index 976af74..307491c 100644 --- a/connector_builder_agents/src/agents.py +++ b/connector_builder_agents/src/agents.py @@ -1,5 +1,6 @@ # Copyright (c) 2025 Airbyte, Inc., all rights reserved. """Agent implementations for the Airbyte connector builder.""" + from agents import Agent as OpenAIAgent from agents import ( handoff, diff --git a/connector_builder_agents/src/tools.py b/connector_builder_agents/src/tools.py index 0a389b1..94fa083 100644 --- a/connector_builder_agents/src/tools.py +++ b/connector_builder_agents/src/tools.py @@ -1,5 +1,6 @@ # Copyright (c) 2025 Airbyte, Inc., all rights reserved. """Tools and utilities for running MCP-based agents for connector building.""" + from datetime import datetime from enum import Enum from typing import Annotated diff --git a/connector_builder_mcp/validation_testing.py b/connector_builder_mcp/validation_testing.py index 14dc5fd..4ac36e4 100644 --- a/connector_builder_mcp/validation_testing.py +++ b/connector_builder_mcp/validation_testing.py @@ -30,6 +30,7 @@ from airbyte_cdk.sources.declarative.parsers.manifest_reference_resolver import ( ManifestReferenceResolver, ) + from connector_builder_mcp._secrets import hydrate_config from connector_builder_mcp._util import ( as_bool, @@ -677,12 +678,14 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too if field_warnings: warnings.append(f"⚠️ Field count issues: {'; '.join(field_warnings[:2])}") - report_lines.extend([ - f"### `{stream_name}` ✅", - "", - f"- **Records Extracted**: {smoke_result.records_read:,}", - f"- **Duration**: {smoke_result.duration_seconds:.2f}s", - ]) + report_lines.extend( + [ + f"### `{stream_name}` ✅", + "", + f"- **Records Extracted**: {smoke_result.records_read:,}", + f"- **Duration**: {smoke_result.duration_seconds:.2f}s", + ] + ) if warnings: report_lines.append(f"- **Warnings**: {' | '.join(warnings)}") @@ -696,13 +699,15 @@ def run_connector_readiness_test_report( # noqa: PLR0912, PLR0914, PLR0915 (too "error_message", "Unknown error", ) - report_lines.extend([ - f"### `{stream_name}` ❌", - "", - "- **Status**: Failed", - f"- **Error**: {error_msg}", - "", - ]) + report_lines.extend( + [ + f"### `{stream_name}` ❌", + "", + "- **Status**: Failed", + f"- **Error**: {error_msg}", + "", + ] + ) return _as_saved_report( report_text="\n".join(report_lines), From 1d91acb4e44e44d86a6325cc52bf4ab3bf5ef9a9 Mon Sep 17 00:00:00 2001 From: "Aaron (\"AJ\") Steers" Date: Mon, 22 Sep 2025 11:56:08 -0700 Subject: [PATCH 3/4] Update connector_builder_agents/src/tools.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- connector_builder_agents/src/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_builder_agents/src/tools.py b/connector_builder_agents/src/tools.py index 94fa083..0596641 100644 --- a/connector_builder_agents/src/tools.py +++ b/connector_builder_agents/src/tools.py @@ -214,7 +214,7 @@ def log_tool_failure( This is a specialized version of `log_problem_encountered` to report tool failures. """ - msg = f"🛠️ {tool_name} Tool Failure in '{tool_name}': {summary_failure_description}" + msg = f"🛠️ Tool Failure in '{tool_name}': {summary_failure_description}" if is_unexpected_input_args_error: msg += " (🙈 Unexpected input arguments error)" if is_unhelpful_error_message: From a6f2dbbb87b6b7349fdba4b1ad02d9d0a0d69b81 Mon Sep 17 00:00:00 2001 From: "Aaron (\"AJ\") Steers" Date: Mon, 22 Sep 2025 11:56:48 -0700 Subject: [PATCH 4/4] Update connector_builder_agents/src/agents.py --- connector_builder_agents/src/agents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_builder_agents/src/agents.py b/connector_builder_agents/src/agents.py index 307491c..660111b 100644 --- a/connector_builder_agents/src/agents.py +++ b/connector_builder_agents/src/agents.py @@ -126,7 +126,7 @@ class ManagerHandoffInput(BaseModel): async def on_manager_handback(ctx, input_data: ManagerHandoffInput) -> None: update_progress_log( f"🤝 Handing back control to manager." - "\n Summary of status: {input_data.short_status}" + f"\n Summary of status: {input_data.short_status}" f"\n Partial success: {input_data.is_partial_success}" f"\n Full success: {input_data.is_full_success}" f"\n Blocked: {input_data.is_blocked}"