From 03dc4aad52cfdc8138895277549e6a9216ffed4e Mon Sep 17 00:00:00 2001 From: Paolo Shamoon Date: Thu, 6 Aug 2026 05:00:01 +0530 Subject: [PATCH] fix: hide memory tool from cron agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cron agents are constructed with skip_memory=True, so the memory backend is not initialised — exposing the memory tool only gives the model an unbacked tool that fails at runtime with 'Memory is not available.' Add 'memory' to _resolve_cron_disabled_toolsets() so the tool is stripped from the schema before the model can call it. Fixes #38129. Co-authored-by: Paolo Shamoon --- cron/scheduler.py | 6 +++-- tests/cron/test_scheduler.py | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/cron/scheduler.py b/cron/scheduler.py index 47f0f26abb201..4fa234e6dde15 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -165,17 +165,19 @@ class CronPromptInjectionBlocked(Exception): def _resolve_cron_disabled_toolsets(cfg: dict) -> list[str]: """Toolsets a cron-spawned agent must never receive. - Three protected toolsets are always disabled in cron context: + Four protected toolsets are always disabled in cron context: - ``cronjob`` — would let a cron-spawned agent schedule more cron jobs - ``messaging`` — interactive, needs a live gateway session - ``clarify`` — interactive, blocks waiting for user input + - ``memory`` — cron agents are constructed with ``skip_memory=True``, so + exposing this tool only gives the model an unbacked tool that fails User-level ``agent.disabled_toolsets`` from config.yaml is layered on top so per-job ``enabled_toolsets`` cannot bypass policy that applies to ordinary agent runs (#25752 — LLM-supplied enabled_toolsets was widening past config.yaml's denylist). """ - disabled = ["cronjob", "messaging", "clarify"] + disabled = ["cronjob", "messaging", "clarify", "memory"] agent_cfg = (cfg or {}).get("agent") or {} user_disabled = agent_cfg.get("disabled_toolsets") or [] for name in user_disabled: diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index a90cba3ae6ee5..76314f8324e51 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -533,6 +533,49 @@ class TestRunJobSessionPersistence: yield fake_db, mock_agent_cls + def test_run_job_memory_toolset_disabled_in_cron(self, tmp_path): + """memory toolset must be disabled in cron sessions — issue #38129. + + Cron agents are constructed with skip_memory=True, so the memory + backend is not initialised. Exposing the memory tool only gives the + model an unbacked tool that fails at runtime with + "Memory is not available." Hiding it from the schema prevents that. + """ + job = { + "id": "memory-hide-job", + "name": "test", + "prompt": "hello", + } + with self._run_job_patches(tmp_path) as (fake_db, mock_agent_cls): + run_job(job) + + kwargs = mock_agent_cls.call_args.kwargs + assert "memory" in (kwargs["disabled_toolsets"] or []), ( + "memory toolset should be disabled in cron to match skip_memory=True" + ) + + def test_run_job_disables_memory_even_when_per_job_enables_it(self, tmp_path): + """Cron runs pass skip_memory=True, so memory must not be exposed. + + A cron job can request the memory tool through enabled_toolsets, but + there is no MemoryStore injected for cron agents. Keep memory in the + disabled set so AIAgent filters the unbacked tool out before the model + can call it and receive "Memory is not available" failures. + """ + job = { + "id": "memory-toolset-job", + "name": "test", + "prompt": "remember what you learn", + "enabled_toolsets": ["memory", "file"], + } + with self._run_job_patches(tmp_path) as (fake_db, mock_agent_cls): + run_job(job) + + kwargs = mock_agent_cls.call_args.kwargs + assert kwargs["skip_memory"] is True + assert kwargs["enabled_toolsets"] == ["memory", "file"] + assert "memory" in kwargs["disabled_toolsets"] + def test_tick_skips_due_jobs_while_dispatch_is_paused(self, tmp_path): """The drain gate runs before advancing a due job's schedule.""" from cron.scheduler import tick