diff --git a/tests/tools/test_delegate_capacity_interrupt.py b/tests/tools/test_delegate_capacity_interrupt.py index 92f6adc753..8f847bc573 100644 --- a/tests/tools/test_delegate_capacity_interrupt.py +++ b/tests/tools/test_delegate_capacity_interrupt.py @@ -101,7 +101,7 @@ def _batch(parent, *children): creds={"model": children[0].model}, context=None, top_role="leaf", max_children=len(children), live_deleg_id=None, live_writers=[], live_paths=[], origin_wake_sid="", origin_ui_session_id="", origin_owner_transport=None, - origin_owner_session_record=None, overall_start=time.monotonic(), + origin_owner_session_record=None, origin_session_history_delivery=False, overall_start=time.monotonic(), ) diff --git a/tools/delegate_tool_child_run.py b/tools/delegate_tool_child_run.py index 4870e9ad1e..036e83f7bb 100644 --- a/tools/delegate_tool_child_run.py +++ b/tools/delegate_tool_child_run.py @@ -69,8 +69,16 @@ def _attach_child(parent_agent: Any, child: Any) -> None: so the whole spawn tree dies with its parent.""" if hasattr(parent_agent, "_active_children"): _with_children_lock(parent_agent, "append", child) - if getattr(parent_agent, "_interrupt_requested", False) is True: - _signal_child_stop(child, getattr(parent_agent, "_interrupt_message", None) or "parent agent interrupted") + if getattr(parent_agent, "_interrupt_requested", False) is not True: + return + # Same soft/hard split as ``interrupt()``'s own fan-out: a hard stop cancels, a soft one redirects. + message = getattr(parent_agent, "_interrupt_message", None) + hard = getattr(parent_agent, "_hard_interrupt_requested", None) + if hard is None or hard.is_set(): + _signal_child_stop(child, message or "parent agent interrupted") + else: + with _quiet("Failed to propagate interrupt to late child: %s"): + child.interrupt(message) def _detach_child(parent_agent: Any, child: Any) -> None: """Remove the child from parent interrupt propagation (no-op if absent).""" diff --git a/tools/delegate_tool_dispatch.py b/tools/delegate_tool_dispatch.py index 7f768416b8..7d52532698 100644 --- a/tools/delegate_tool_dispatch.py +++ b/tools/delegate_tool_dispatch.py @@ -375,19 +375,10 @@ def _dispatch_unit(unit: _Batch, unit_id: Optional[str], slot_key: Optional[str] ) def _restore_parent_cancellation(unit: _Batch) -> None: - # Rejected children remain owned by the parent. Attach before replaying a - # cancellation that may have arrived while async admission had them detached. - parent = unit.parent_agent + """Rejected children stay owned by the parent: re-attach them (``_attach_child`` replays a stop that + arrived while async admission had them detached).""" for _, _, child in unit.children: - _attach_child(parent, child) - if getattr(parent, "_interrupt_requested", False) is True: - hard_stop = getattr(parent, "_hard_interrupt_requested", None) - for _, _, child in unit.children: - if hard_stop is not None and hard_stop.is_set(): - _signal_child_stop(child, getattr(parent, "_interrupt_message", None)) - else: - with _quiet("Failed to propagate interrupt to fallback child: %s"): - child.interrupt(getattr(parent, "_interrupt_message", None)) + _attach_child(unit.parent_agent, child) def _dispatch_background(batch: _Batch) -> str: """Dispatch the call as independent async units (see ``_units_of``) and return the tool result JSON. Every unit