fix(sdk): drop in-flight tool calls from ask_agent context - #4630
Open
all-hands-bot wants to merge 1 commit into
Open
fix(sdk): drop in-flight tool calls from ask_agent context#4630all-hands-bot wants to merge 1 commit into
all-hands-bot wants to merge 1 commit into
Conversation
ask_agent built its context from the cached state.view, which is maintained incrementally and skips enforce_properties on linear appends. When ask_agent runs while the main agent is mid tool-call, the cached view can contain an ActionEvent whose ObservationEvent has not landed yet. Anthropic rejects a tool_use with no following tool_result, so the ask_agent call failed with a 400 (and the run then collapsed with a shutdown'd executor). Build the context from a freshly property-enforced view of the active branch so orphaned actions are dropped. Co-authored-by: openhands <openhands@all-hands.dev>
Collaborator
Author
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
Contributor
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
Contributor
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
Contributor
Coverage Report •
|
||||||||||||||||||||
Contributor
🔄 Running Examples with
|
| Example | Status | Duration | Cost |
|---|---|---|---|
| 01_standalone_sdk/02_custom_tools.py | ✅ PASS | 24.0s | $0.03 |
| 01_standalone_sdk/03_activate_skill.py | ✅ PASS | 23.8s | $0.03 |
| 01_standalone_sdk/05_use_llm_registry.py | ✅ PASS | 11.7s | $0.01 |
| 01_standalone_sdk/07_mcp_integration.py | ✅ PASS | 46.9s | $0.04 |
| 01_standalone_sdk/09_pause_example.py | ✅ PASS | 13.8s | $0.01 |
| 01_standalone_sdk/10_persistence.py | ✅ PASS | 26.6s | $0.03 |
| 01_standalone_sdk/11_async.py | ✅ PASS | 35.4s | $0.04 |
| 01_standalone_sdk/12_custom_secrets.py | ✅ PASS | 13.4s | $0.01 |
| 01_standalone_sdk/13_get_llm_metrics.py | ✅ PASS | 38.5s | $0.05 |
| 01_standalone_sdk/14_context_condenser.py | ✅ PASS | 2m 48s | $0.18 |
| 01_standalone_sdk/17_image_input.py | ✅ PASS | 22.7s | $0.02 |
| 01_standalone_sdk/18_send_message_while_processing.py | ✅ PASS | 21.3s | $0.02 |
| 01_standalone_sdk/19_llm_routing.py | ✅ PASS | 17.9s | $0.02 |
| 01_standalone_sdk/20_stuck_detector.py | ✅ PASS | 15.8s | $0.02 |
| 01_standalone_sdk/21_generate_extraneous_conversation_costs.py | ✅ PASS | 11.1s | $0.00 |
| 01_standalone_sdk/22_anthropic_thinking.py | ✅ PASS | 14.7s | $0.01 |
| 01_standalone_sdk/23_responses_reasoning.py | ✅ PASS | 59.3s | $0.01 |
| 01_standalone_sdk/24_planning_agent_workflow.py | ✅ PASS | 3m 7s | $0.23 |
| 01_standalone_sdk/25_agent_delegation.py | ✅ PASS | 54.0s | $0.06 |
| 01_standalone_sdk/26_custom_visualizer.py | ✅ PASS | 19.2s | $0.02 |
| 01_standalone_sdk/28_ask_agent_example.py | ✅ PASS | 41.1s | $0.03 |
| 01_standalone_sdk/29_llm_streaming.py | ✅ PASS | 40.5s | $0.02 |
| 01_standalone_sdk/30_tom_agent.py | ✅ PASS | 11.4s | $0.01 |
| 01_standalone_sdk/31_iterative_refinement.py | ✅ PASS | 1m 22s | $0.07 |
| 01_standalone_sdk/32_configurable_security_policy.py | ✅ PASS | 19.6s | $0.02 |
| 01_standalone_sdk/33_hooks/main.py | ✅ PASS | 36.8s | $0.04 |
| 01_standalone_sdk/34_critic_example.py | ✅ PASS | 1m 31s | $0.10 |
| 01_standalone_sdk/36_event_json_to_openai_messages.py | ✅ PASS | 11.6s | $0.00 |
| 01_standalone_sdk/37_llm_profile_store/main.py | ✅ PASS | 9.0s | $0.00 |
| 01_standalone_sdk/38_browser_session_recording.py | ✅ PASS | 38.1s | $0.04 |
| 01_standalone_sdk/39_llm_fallback.py | ✅ PASS | 12.4s | $0.01 |
| 01_standalone_sdk/40_acp_agent_example.py | ✅ PASS | 60.0s | $0.41 |
| 01_standalone_sdk/41_task_tool_set.py | ✅ PASS | 30.3s | $0.03 |
| 01_standalone_sdk/42_file_based_subagents.py | ✅ PASS | 56.2s | $0.06 |
| 01_standalone_sdk/44_model_switching_in_convo.py | ✅ PASS | 11.0s | $0.01 |
| 01_standalone_sdk/45_parallel_tool_execution.py | ✅ PASS | 4m 46s | $0.54 |
| 01_standalone_sdk/46_agent_settings.py | ✅ PASS | 12.1s | $0.01 |
| 01_standalone_sdk/47_defense_in_depth_security.py | ✅ PASS | 4.1s | $0.00 |
| 01_standalone_sdk/48_conversation_fork.py | ✅ PASS | 23.1s | $0.01 |
| 01_standalone_sdk/49_switch_llm_tool.py | ✅ PASS | 8.5s | $0.04 |
| 01_standalone_sdk/50_async_cancellation.py | ✅ PASS | 14.9s | $0.00 |
| 01_standalone_sdk/51_agent_hooks/main.py | ✅ PASS | 37.2s | $0.04 |
| 01_standalone_sdk/52_dynamic_workflow.py | ✅ PASS | 4m 29s | $0.15 |
| 01_standalone_sdk/53_client_defined_tools.py | ✅ PASS | 12.0s | $0.01 |
| 01_standalone_sdk/54_goal_completion_loop.py | ✅ PASS | 33.7s | $0.03 |
| 01_standalone_sdk/55_persistent_memory.py | ✅ PASS | 22.3s | $0.02 |
| 01_standalone_sdk/56_structured_output.py | ✅ PASS | 48.6s | $0.07 |
| 01_standalone_sdk/57_prompt_hooks/main.py | ✅ PASS | 27.1s | $0.00 |
| 02_remote_agent_server/01_convo_with_local_agent_server.py | ✅ PASS | 34.3s | $0.02 |
| 02_remote_agent_server/02_convo_with_docker_sandboxed_server.py | ✅ PASS | 1m 40s | $0.03 |
| 02_remote_agent_server/03_browser_use_with_docker_sandboxed_server.py | ✅ PASS | 1m 48s | $0.12 |
| 02_remote_agent_server/04_convo_with_api_sandboxed_server.py | ✅ PASS | 1m 49s | $0.04 |
| 02_remote_agent_server/06_custom_tool/main.py | ✅ PASS | 6m 42s | $0.07 |
| 02_remote_agent_server/07_convo_with_cloud_workspace.py | ✅ PASS | 45.4s | $0.03 |
| 02_remote_agent_server/08_convo_with_apptainer_sandboxed_server.py | ✅ PASS | 5m 51s | $0.04 |
| 02_remote_agent_server/09_acp_agent_with_remote_runtime.py | ✅ PASS | 1m 4s | $0.37 |
| 02_remote_agent_server/10_cloud_workspace_share_credentials.py | ✅ PASS | 32.8s | $0.04 |
| 02_remote_agent_server/11_conversation_fork.py | ✅ PASS | 40.3s | $0.00 |
| 02_remote_agent_server/12_settings_and_secrets_api.py | ✅ PASS | 2m 22s | $0.01 |
| 02_remote_agent_server/13_workspace_get_llm.py | ✅ PASS | 25.0s | $0.01 |
| 02_remote_agent_server/14_client_defined_tools.py | ✅ PASS | 26.9s | $0.02 |
| 02_remote_agent_server/15_openai_compatible_gateway.py | ✅ PASS | 22.5s | $0.02 |
| 02_remote_agent_server/16_deferred_init.py | ✅ PASS | 46.6s | $0.01 |
| 04_llm_specific_tools/01_gpt5_apply_patch_preset.py | ✅ PASS | 32.8s | $0.03 |
| 04_llm_specific_tools/02_gemini_file_tools.py | ✅ PASS | 54.5s | $0.09 |
| 05_skills_and_plugins/01_loading_agentskills/main.py | ✅ PASS | 15.2s | $0.02 |
| 05_skills_and_plugins/02_loading_plugins/main.py | ✅ PASS | 45.9s | $0.03 |
| 05_skills_and_plugins/04_mixed_marketplace_skills/main.py | ✅ PASS | 17.4s | $0.00 |
✅ All tests passed!
Total: 68 | Passed: 68 | Failed: 0 | Total Cost: $3.62
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
run-examplesworkflow was failing onexamples/01_standalone_sdk/28_ask_agent_example.py. The example callsconversation.ask_agent()from a background thread while the main agent is mid-tool-call.ask_agent()built its context withprepare_llm_messages(self.state.view, ...). The cachedstate.viewis maintained incrementally (issue #3053) and skipsenforce_propertieson linear appends, so it can transiently contain anActionEventwhoseObservationEventhas not landed yet. Anthropic rejects atool_usewith no followingtool_result, soask_agent()fails with a 400:That error then cascaded: the background
run()thread hit the same malformed history and collapsed withRuntimeError: cannot schedule new futures after shutdown, failing the example.Fix
Build the ask-agent context from a freshly property-enforced
View.from_events(self._state.active_branch())instead of the cachedstate.view. Property enforcement drops the orphaned in-flight action (its observation hasn't arrived yet), exactly matching whatagent.step()already relies on viarebuild_view()on cold load / forks.Test
Added
test_ask_agent_drops_in_flight_action_without_observationasserting the orphanedtool_useis dropped from the context sent to the LLM.This PR was created by an AI agent (OpenHands) on behalf of the user.
🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:632d4fb-pythonRun
All tags pushed for this build
About Multi-Architecture Support
632d4fb-python) is a multi-arch manifest supporting both amd64 and arm64632d4fb-python-amd64) are also available if needed