test: fold the schema-retry context test into the output-schema module

Reuse tests/tools/test_delegate_output_schema.py's _StubChild instead of a
new one-test file with its own double; the invariant (the retry turn sees
is_delegated_child_context() True and the flag is restored afterwards) is
unchanged. Trim the source comment to the WHY.
This commit is contained in:
teknium1
2026-09-15 11:49:38 -07:00
committed by Teknium
parent e002cdb92d
commit 8e16bde491
3 changed files with 26 additions and 57 deletions
@@ -297,6 +297,30 @@ class TestRunSingleChildSchemaValidation:
assert entry["status"] == "completed"
assert "error" not in entry
def test_retry_turn_runs_in_delegated_child_context(self, monkeypatch):
"""The retry is a second run_conversation on the child, issued from the parent's
thread where HERMES_KANBAN_TASK is set. Unwrapped it carries the worker's identity,
so the kanban stop guard nudges the child toward a board tool it does not have
(#109735 / #87671)."""
from agent.delegation_context import is_delegated_child_context
from tools.delegate_tool_child_run import _validate_child_output_schema
monkeypatch.setenv("HERMES_KANBAN_TASK", "t_parent")
seen: list = []
class _Child(_StubChild):
session_id = "sess-child"
def run_conversation(self, user_message, task_id=None, **_kwargs):
seen.append(is_delegated_child_context())
return super().run_conversation(user_message, task_id, **_kwargs)
child = _Child(['{"city": "Berlin"}'])
child._delegate_output_schema = ADDRESS_SCHEMA
_validate_child_output_schema(child, {"final_response": "not json", "api_calls": 1}, 0, "child-0", None)
assert seen == [True]
assert is_delegated_child_context() is False
# ---------------------------------------------------------------------------
# delegate_task dispatch-time schema handling
@@ -1,53 +0,0 @@
"""The schema-retry turn must run inside the delegated-child context.
The main child turn is wrapped in ``delegated_child_context``; the bounded retry in
``_validate_child_output_schema`` is a second ``run_conversation`` on the same child.
Unwrapped, it runs with the parent worker's identity: ``HERMES_KANBAN_TASK`` is set and
nothing marks it as a child, so the kanban stop guard nudges the child to call
``kanban_complete``. A child owns no board task and cannot, so the nudge text displaces
the answer the retry exists to produce and the retry fails the same schema again.
"""
from __future__ import annotations
import pytest
from agent.delegation_context import is_delegated_child_context
from tools.delegate_tool_child_run import _validate_child_output_schema
class _Child:
"""Minimal stand-in recording the context state of each run_conversation turn."""
def __init__(self):
self._delegate_output_schema = {
"type": "object",
"properties": {"ok": {"type": "boolean"}},
"required": ["ok"],
}
self.session_id = "sess-child"
self.seen_in_child_context = []
def run_conversation(self, user_message=None, task_id=None, stream_callback=None):
self.seen_in_child_context.append(is_delegated_child_context())
return {"final_response": '{"ok": true}', "api_calls": 1}
@pytest.fixture
def kanban_env(monkeypatch):
monkeypatch.setenv("HERMES_KANBAN_TASK", "t_parent")
monkeypatch.delenv("HERMES_KANBAN_STOP_NUDGE", raising=False)
return monkeypatch
def test_schema_retry_runs_in_child_context(kanban_env):
child = _Child()
result = {"final_response": "not json at all", "api_calls": 1}
assert is_delegated_child_context() is False
_validate_child_output_schema(child, result, 0, "child-task-0", None)
assert child.seen_in_child_context == [True], (
"the schema-retry turn ran outside delegated_child_context"
)
assert is_delegated_child_context() is False
+2 -4
View File
@@ -424,10 +424,8 @@ def _validate_child_output_schema(
# schema re-paste — the child already holds the contract in its context).
_retry_result = None
try:
# Same identity as the main child turn (delegate_tool_child_run's worker): this runs
# on the parent's thread, where HERMES_KANBAN_TASK is set, so without the marker the
# kanban stop guard nudges the child to call kanban_complete. It owns no board task
# and cannot, and the nudge displaces the answer this retry exists to produce.
# Same identity as the main child turn: this runs on the parent worker's thread, and an
# unmarked turn is misread as the dispatcher-owned worker by every HERMES_KANBAN_* gate.
from agent.delegation_context import delegated_child_context
with delegated_child_context(str(getattr(child, "session_id", "") or "")):
_retry_result = child.run_conversation(