Skip to content

Commit a89228b

Browse files
committed
fix(tools): stop ToolSearch description claiming nonexistent deferral
Root-cause follow-up to the ToolSearch gate: pythinker emits no defer_loading and ToolSearch only searches already-visible, already-callable tools (it unlocks nothing). The old description told the model that "deferred/hidden tool loading" might leave capabilities off the prompt — a false claim that primed weaker models to loop searching for tools they already had. Rewrite it to state plainly that it reveals nothing new and to call tools directly.
1 parent 7c78542 commit a89228b

3 files changed

Lines changed: 18 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ GitHub Releases page; `0.8.0` is the new starting line.
2121
`type="anthropic"` compat proxies (z.ai/GLM, Kimi, MiniMax, opencode) and all
2222
non-Anthropic providers no longer see it, fixing a loop where weaker tool-callers
2323
(e.g. GLM-5.2) repeatedly "searched" for tools instead of calling them. Override
24-
with `ENABLE_TOOL_SEARCH=true|false`.
24+
with `ENABLE_TOOL_SEARCH=true|false`. The tool's description no longer claims that
25+
hidden/deferred tools exist (pythinker loads no tools lazily), removing the prompt
26+
that primed the loop in the first place.
2527
- **Output-token-limit nudge text aligned with reference.** The system-reminder injected when a response is cut off by the output token limit now matches the reference byte-exactly: "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces."
2628
- **`SetTodoList` accepts Cursor-style todo payloads.** Todo items sent with `content` instead of `title` (the shape models learn from Cursor/Claude `TodoWrite`) now validate and persist correctly instead of failing with missing-`title` errors.
2729
- **ToolSearch scrollback suppression.** Consecutive `ToolSearch` probes during deferred tool discovery are now collapsed: only the last probe in each run is shown in the transcript, mirroring the blackbox `isAbsorbedSilently` contract. Intermediate discovery calls no longer produce repeated "Tools(…)" lines.
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
Search the currently visible tool list by name and description.
22

3-
Use this when you are unsure which tool is available for a task or when deferred/hidden tool
4-
loading means the initial prompt may not list every useful capability. The search only returns
5-
tools visible under the current runtime and permission profile.
3+
Use this only when you are unsure which existing tool fits a task. It returns tools that are
4+
already available to you under the current runtime and permission profile — it does NOT load,
5+
unlock, or reveal any hidden tools. Everything it can return is already callable directly, so
6+
prefer calling the tool you need over searching for it; searching does not make any new tool
7+
available.
68

79
Provide concise keywords such as `worktree`, `background task`, `read file`, or `web search`.

tests/tools/test_tool_search.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,13 @@ async def test_tool_search_excludes_hidden_tools() -> None:
7777
assert not result.is_error
7878
assert "ReadProjectFiles" not in result.output
7979
assert result.output == "No visible tools matched `read files`."
80+
81+
82+
def test_description_does_not_claim_nonexistent_deferral() -> None:
83+
"""Guard the trigger of the GLM-5.2 loop: pythinker has no defer_loading, so
84+
the ToolSearch description must not imply hidden/deferred tools exist or that
85+
searching unlocks anything. See test_tool_search_gating + the toolset gate."""
86+
desc = ToolSearch(PythinkerToolset()).description.lower()
87+
assert "deferred" not in desc
88+
assert "hidden" in desc and "does not" in desc # explicitly says it unlocks nothing
89+
assert "already callable" in desc

0 commit comments

Comments
 (0)