{"record":{"id":"53dd3b1cd727805f","repo":"langchain-ai/deepagents","slug":"tool-call-id-is-required-for-subagent-invocation","errorCode":null,"errorMessage":"Tool call ID is required for subagent invocation","messagePattern":"Tool call ID is required for subagent invocation","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/deepagents/deepagents/middleware/subagents.py","lineNumber":554,"sourceCode":"        \"\"\"Prepare state for invocation.\"\"\"\n        subagent = _select_subagent(subagent_type, runtime)\n        # Create a new state dict to avoid mutating the original\n        subagent_state = {k: v for k, v in runtime.state.items() if k not in _EXCLUDED_STATE_KEYS}\n        subagent_state = {k: v for k, v in subagent_state.items() if k not in private_state_keys}\n        subagent_state[\"messages\"] = [HumanMessage(content=description)]\n        return subagent, subagent_state\n\n    def task(\n        description: str,\n        subagent_type: str,\n        runtime: ToolRuntime,\n    ) -> str | Command:\n        if subagent_type not in subagent_graphs:\n            allowed_types = \", \".join([f\"`{k}`\" for k in subagent_graphs])\n            return f\"We cannot invoke subagent {subagent_type} because it does not exist, the only allowed types are {allowed_types}\"\n        if not runtime.tool_call_id:\n            value_error_msg = \"Tool call ID is required for subagent invocation\"\n            raise ValueError(value_error_msg)\n        subagent, subagent_state = _validate_and_prepare_state(\n            subagent_type,\n            description,\n            runtime,\n        )\n        # The parent's callbacks, tags and configurable reach the subagent\n        # automatically: langgraph's `ensure_config` seeds each run from the\n        # ambient parent config and (as of langgraph#7926) merges it per-key, so\n        # the subagent's bound config still wins collisions (e.g. `lc_agent_name`,\n        # `recursion_limit`) and parent metadata propagates (deepagents#3634).\n        # Forwarding those keys explicitly would double-count under the merge\n        # (e.g. duplicate `tags`), so we only stamp the subagent tracing tag.\n        subagent_config: RunnableConfig = {\"configurable\": {\"ls_agent_type\": \"subagent\"}}\n        with _subagent_tracing_context():\n            result = subagent.invoke(subagent_state, subagent_config)\n        return _return_command_with_state_update(result, runtime.tool_call_id)\n\n    async def atask(","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/deepagents/deepagents/middleware/subagents.py#L536-L572","documentation":"The synchronous `task` tool must attribute its result to the invoking tool call. `runtime.tool_call_id` is required to construct the returning `Command`; if it is empty/None the method raises `ValueError` since the result could not be routed back to the model.","triggerScenarios":"Calling the sync `task` tool implementation outside a real tool-call context (e.g. invoking the tool's `func` directly with a fabricated `Runtime` whose `tool_call_id` is None or empty).","commonSituations":"Unit tests invoking the tool function manually; custom runners executing tool bodies without a tool-call id; reusing a runtime object across calls.","solutions":["Invoke `task` through the agent so the framework supplies `tool_call_id`","If calling directly, set a non-empty `tool_call_id` on the runtime/tool-call object","Update tests to simulate a proper ToolCall/InstrumentedRuntime"],"exampleFix":"// before\nruntime = SimpleNamespace(tool_call_id=None)\ntask(runtime, description=\"do it\", subagent_type=\"researcher\")\n// after\nruntime = SimpleNamespace(tool_call_id=\"call_123\")\ntask(runtime, description=\"do it\", subagent_type=\"researcher\")","handlingStrategy":"try-catch","validationCode":"if not getattr(runtime, \"tool_call_id\", None):\n    raise RuntimeError(\"task() must be invoked within a tool call context\")","typeGuard":"def in_tool_call_context(runtime) -> bool:\n    return bool(getattr(runtime, \"tool_call_id\", None))","tryCatchPattern":"try:\n    result = task(runtime, description=desc, subagent_type=kind)\nexcept ValueError as e:\n    if \"Tool call ID\" in str(e):\n        logger.error(\"task() called outside a tool-call context\")\n    raise","preventionTips":["Always invoke `task` through the agent loop, never call its body directly with a hand-built runtime","In direct-call tests, populate `tool_call_id` with a realistic value","Wrap raw runtimes in a typed harness that enforces the field"],"tags":["python","subagents","tool-call","valueerror"],"backgroundTag":"missing-tool-call-id","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}