langchain-ai/deepagents · error · ValueError

The subagent `task` tool cannot be exposed via `ptc`. It is

Error message

The subagent `task` tool cannot be exposed via `ptc`. It is always available as the top-level `task()` global inside the REPL (with `subagentType`, `label`, and `responseSchema` support); exposing it through the `tools.*` namespace would create a second, conflicting dispatch path that drops `responseSchema`. Remove "task" from `ptc`.

What it means

The `task` subagent tool is reserved: it is always injected as the top-level `task()` REPL global with full support for `subagentType`, `label`, and `responseSchema`. Exposing it through the `ptc` tools namespace would create a conflicting second dispatch path that silently drops `responseSchema`, so listing it in a ptc include list raises.

Source

Thrown at libs/partners/quickjs/langchain_quickjs/_ptc.py:87

    Duplicate tool names are deduplicated.

    The subagent `task` tool is reserved and may not appear in `config`
    (by name or instance) — it is always available as the `task()` global,
    so a `tools.task` PTC variant would be a conflicting, degraded duplicate.
    A `"task"` entry raises `ValueError`.

    Warning:
        PTC tool calls execute through the REPL bridge and currently do
        not respect `interrupt_on` / HITL approval hooks for each
        individual tool invocation.
    """
    if isinstance(config, list):
        explicit_tools: list[BaseTool] = []
        allow_names: set[str] = set()
        for entry in config:
            if isinstance(entry, BaseTool):
                if entry.name == _RESERVED_SUBAGENT_TASK_NAME:
                    raise ValueError(_TASK_IN_PTC_MSG)
                if entry.name != self_tool_name:
                    explicit_tools.append(entry)
                continue
            if isinstance(entry, str):
                if entry == _RESERVED_SUBAGENT_TASK_NAME:
                    raise ValueError(_TASK_IN_PTC_MSG)
                allow_names.add(entry)
                continue
            msg = "ptc list entries must be str or BaseTool"
            raise TypeError(msg)
        selected = [
            *explicit_tools,
            *[t for t in tools if t.name != self_tool_name and t.name in allow_names],
        ]
        deduped: list[BaseTool] = []
        seen_names: set[str] = set()
        for tool in selected:
            if tool.name in seen_names:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Remove `"task"` from the ptc list config
  2. Filter reserved tool names before constructing the ptc list
  3. Rely on the built-in `task()` global — no ptc exposure is needed for subagent spawning

Example fix

// before
const ptc = ["websearch", "task"];
// after
const ptc = ["websearch"];
Defensive patterns

Strategy: validation

Validate before calling

if any(getattr(t, "name", None) == "task" for t in tool_objects):
    raise ValueError("remove reserved 'task' tool from ptc list")

Type guard

def is_ptc_safe_tool(t: BaseTool) -> bool:
    return t.name != "task"

Try / catch

try:
    selected = filter_tools_for_ptc(tools, config)
except ValueError as e:
    if "task" in str(e) and "ptc" in str(e):
        config = [t for t in config if (t.name if isinstance(t, BaseTool) else t) != "task"]
        selected = filter_tools_for_ptc(tools, config)
    else:
        raise

Prevention

When it happens

Trigger: Calling `filter_tools_for_ptc` (directly or via `_prepare_for_call`) with a list-form `ptc` config that includes the `task` tool instance or the string `"task"`.

Common situations: Passing through all agent tools including the subagent task tool; building a tool allowlist from a tool registry without filtering reserved names; users explicitly requesting 'task' in their ptc tool list.

Related errors


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