fix(subagents): propagate user context across threaded execution (#2676)

This commit is contained in:
JerryLee
2026-05-01 18:27:18 +10:00
committed by GitHub
parent 78633c69ac
commit 83938cf35a
2 changed files with 88 additions and 10 deletions
@@ -5,8 +5,10 @@ import atexit
import logging
import threading
import uuid
from collections.abc import Callable, Coroutine
from concurrent.futures import Future, ThreadPoolExecutor
from concurrent.futures import TimeoutError as FuturesTimeoutError
from contextvars import Context, copy_context
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
@@ -168,6 +170,19 @@ def _get_isolated_subagent_loop() -> asyncio.AbstractEventLoop:
return _isolated_subagent_loop
def _submit_to_isolated_loop_in_context(
context: Context,
coro_factory: Callable[[], Coroutine[Any, Any, SubagentResult]],
) -> Future[SubagentResult]:
"""Submit a coroutine to the isolated loop while preserving ContextVar state."""
return context.run(
lambda: asyncio.run_coroutine_threadsafe(
coro_factory(),
_get_isolated_subagent_loop(),
)
)
def _filter_tools(
all_tools: list[BaseTool],
allowed: list[str] | None,
@@ -549,10 +564,11 @@ class SubagentExecutor:
from being tied to a short-lived loop that gets closed per execution.
"""
future: Future[SubagentResult] | None = None
parent_context = copy_context()
try:
future = asyncio.run_coroutine_threadsafe(
self._aexecute(task, result_holder),
_get_isolated_subagent_loop(),
future = _submit_to_isolated_loop_in_context(
parent_context,
lambda: self._aexecute(task, result_holder),
)
return future.result(timeout=self.config.timeout_seconds)
except FuturesTimeoutError:
@@ -646,6 +662,8 @@ class SubagentExecutor:
with _background_tasks_lock:
_background_tasks[task_id] = result
parent_context = copy_context()
# Submit to scheduler pool
def run_task():
with _background_tasks_lock:
@@ -656,9 +674,9 @@ class SubagentExecutor:
try:
# Submit execution directly to the persistent isolated loop so the
# background path does not create a temporary loop via execute().
execution_future = asyncio.run_coroutine_threadsafe(
self._aexecute(task, result_holder),
_get_isolated_subagent_loop(),
execution_future = _submit_to_isolated_loop_in_context(
parent_context,
lambda: self._aexecute(task, result_holder),
)
try:
# Wait for execution with timeout