From 7d08ccd9e2e8684c641d0a83616eb3e8ff9cf014 Mon Sep 17 00:00:00 2001 From: Alfonso Sastre Date: Fri, 4 Sep 2026 14:55:18 +0200 Subject: [PATCH] fix(tools): expose remember to local-model agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit minimal_tools() (and therefore dynamic_tools(), the toolset every litellm/ollama/vllm-backed agent variant actually gets) never included remember_tool, despite build_permission() allowing it unconditionally. refactor_dynamic_tools() had the same gap against its own refactor_permission(), which explicitly lists "remember". A local-model agent had no way to propose a memory candidate at all — confirmed live, the model correctly reported it had no such tool and declined to fabricate a call rather than hallucinate one. Adds remember_tool.tool() to both lists. It's a single [net, io, proc] tool that appends one JSONL line (candidates.lex), cheap enough for the curated core rather than needing load_toolset gating. Fixes #110 --- src/tools/index.lex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tools/index.lex b/src/tools/index.lex index 21bc2c9..a09c8c9 100644 --- a/src/tools/index.lex +++ b/src/tools/index.lex @@ -139,8 +139,17 @@ fn all_tools() -> List[t.Tool] { # lex_check/lex_spec_check/lex_test are (see lex-llm#51's # is_verification_tool), so the run never got credit for actually finishing # and burned its full step budget regardless. +# +# remember belongs here for the same reason: build_permission() allows it +# unconditionally, but it was missing from dynamic_tools() (#110), so a +# local-model build agent had no way to propose a memory candidate at +# all — confirmed live, the model correctly reported it had no such tool +# and declined to fabricate a call. A single [net, io, proc] tool that +# appends one JSONL line (see remember.lex/candidates.lex) is cheap +# enough for the curated core; it doesn't need load_toolset gating the +# way the heavier vcs/spec/store groups do. fn minimal_tools() -> List[t.Tool] { - [read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), todo_tool.tool(), check_tool.tool(), run_tool.tool(), test_tool.tool()] + [read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), todo_tool.tool(), remember_tool.tool(), check_tool.tool(), run_tool.tool(), test_tool.tool()] } # Model name advertised to the LiteLLM proxy (must match a model_name in @@ -250,8 +259,13 @@ fn plan_dynamic_tools() -> List[t.Tool] { # lex_store_merge (they're already in dynamic_tools()'s gated store # group) — a real bug in an earlier version of this function, caught by # inspecting the actual tool list rather than trusting a live run. +# +# remember_tool belongs here too: refactor_permission() already lists +# "remember" in its allowlist, but this hand-built list omitted the +# actual tool (#110) — a local refactor agent was permitted to propose a +# memory candidate and had no way to. fn refactor_dynamic_tools() -> List[t.Tool] { - list.concat([read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), check_tool.tool(), audit_tool.tool(), sigid_tool.tool(), effects_tool.tool(), store_merge_tool.tool(), propagate_tool.tool(), guidelines_tool.tool()], list.concat(vcs_read_tools(), vcs_write_tools())) + list.concat([read_tool.tool(), write_tool.tool(), edit_tool.tool(), grep_tool.tool(), glob_tool.tool(), bash_tool.tool(), remember_tool.tool(), check_tool.tool(), audit_tool.tool(), sigid_tool.tool(), effects_tool.tool(), store_merge_tool.tool(), propagate_tool.tool(), guidelines_tool.tool()], list.concat(vcs_read_tools(), vcs_write_tools())) } fn tools_for_spec(spec :: sp.Spec) -> List[t.Tool] {