fix(delegation): run the schema-retry turn in the delegated-child context

_validate_child_output_schema issues a second run_conversation on the child when
the first answer fails the declared output_schema. The main child turn is wrapped
in delegated_child_context; this one was not. It runs on the parent worker's
thread, where HERMES_KANBAN_TASK is set and nothing marks the execution as a
child, so every identity gate keyed on is_delegated_child_context() fails open.

The visible effect is the kanban stop guard: it nudges the child to call
kanban_complete or kanban_block. A child owns no board task and carries no kanban
toolset, so it cannot, and the nudge text ("do not narrate intent", "finish any
remaining deliverable") displaces the structured answer the retry exists to
produce. The retry then fails the same schema and delegate_task reports an error
for a child whose work was already complete.

Observed with four children, each nudged during its retry:

  [subagent-0] Kanban worker tried to exit without kanban_complete/kanban_block
  [subagent-2] Kanban worker tried to exit without kanban_complete/kanban_block
  [subagent-3] Kanban worker tried to exit without kanban_complete/kanban_block
  [subagent-1] Kanban worker tried to exit without kanban_complete/kanban_block
  4/4 - Final answer does not satisfy the declared output_schema (after 1 retry)

Wrap the retry the same way the main turn is wrapped. The context is entered and
exited around the single call, so nothing outside the retry sees it.

Signed-off-by: moep90 <volleyballlive@googlemail.com>
This commit is contained in:
moep90
2026-09-12 19:19:18 +02:00
committed by Teknium
parent 2bd0f1c59e
commit e002cdb92d
2 changed files with 63 additions and 3 deletions
+10 -3
View File
@@ -424,9 +424,16 @@ def _validate_child_output_schema(
# schema re-paste — the child already holds the contract in its context).
_retry_result = None
try:
_retry_result = child.run_conversation(
user_message=build_retry_message(_schema_errors), task_id=child_task_id, stream_callback=relay_child_text,
)
# 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.
from agent.delegation_context import delegated_child_context
with delegated_child_context(str(getattr(child, "session_id", "") or "")):
_retry_result = child.run_conversation(
user_message=build_retry_message(_schema_errors), task_id=child_task_id,
stream_callback=relay_child_text,
)
except Exception as _retry_exc:
logger.warning("Subagent %d schema-retry turn failed: %s", task_index, _retry_exc)
if isinstance(_retry_result, dict):