langchain-ai/deepagents · error · ConcurrentEvalError

task bridge called outside active eval

Error message

task bridge called outside active eval

What it means

The async `task()` host bridge raises ConcurrentEvalError when `self._ptc_state` is None, meaning no eval is currently active on this REPL. PTC state is allocated at eval start and cleared in a finally block, so `task()` is only callable from within a running eval. This detects out-of-band or leaked bridge invocations.

Source

Thrown at libs/partners/quickjs/langchain_quickjs/_repl.py:624

        current_loop = asyncio.get_running_loop()
        if current_loop is outer_loop:
            return await _call()
        future = asyncio.run_coroutine_threadsafe(_call(), outer_loop)
        try:
            return await asyncio.wrap_future(future)
        except asyncio.CancelledError:
            future.cancel()
            raise

    def _register_task_bridge(self) -> None:
        """Install the async host function backing top-level `task()`."""
        ctx = self._require_ctx()

        async def _bridge(raw_input: Any = None) -> Any:
            state = self._ptc_state
            if state is None:
                msg = "task bridge called outside active eval"
                raise ConcurrentEvalError(msg)
            task_calls = self._task_calls
            if task_calls is None:
                msg = "task call limiter not initialized"
                raise RuntimeError(msg)

            payload = _normalize_tool_input(raw_input)
            async with task_calls:
                try:
                    result = await self._ainvoke_task_on_outer_loop(
                        payload,
                        state=state,
                    )
                except GraphInterrupt:
                    raise
                except Exception as e:
                    # Subagent dispatches are part of the eval language, not
                    # PTC calls. Surface their validation/runtime failures as
                    # eval errors without changing normal `tools.*` semantics.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Ensure all task() calls complete before the eval's JS finishes (await the promises)
  2. Use one REPL per concurrent eval — never share a REPL across concurrent evaluations
  3. Do not schedule deferred task() calls (setTimeout/setInterval) that outlive the eval

Example fix

// before
setTimeout(() => task({description: 'late', subagentType: 'researcher'}), 1000) // fires after eval
// after
await task({ description: 'inline', subagentType: 'researcher' })
Defensive patterns

Strategy: try-catch

Validate before calling

// JS side: only call task() synchronously awaited within the eval
if (typeof task !== 'function') throw new Error('task() unavailable');

Type guard

function taskAvailable() {
  return typeof task === 'function';
}

Try / catch

try {
  const r = await task(payload);
} catch (e) {
  if (String(e).includes('outside active eval')) {
    // deferred/out-of-band call; do not retry
    console.warn('task() called after eval finished');
  } else { throw e; }
}

Prevention

When it happens

Trigger: The `task` host function is invoked outside an active eval — e.g. a pending JS callback or promise resolved after the eval finished, or the bridge triggered from another thread/concurrent eval context.

Common situations: Fire-and-forget JS scheduling `task()` after the eval returns, concurrent evals sharing one REPL where one eval's bridge call lands after the other's finally-cleaned state, or calling task() from a timer set inside the eval.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/52060aca820f9c27. Report an issue: GitHub.