From 9c37c1ebf21ab25e3118b1bd2b5854f82ae79024 Mon Sep 17 00:00:00 2001 From: Lucifer Date: Fri, 5 Jun 2026 09:11:55 +0800 Subject: [PATCH] feat(gateway): hot-reload memory_char_limit and user_char_limit Previously, edits to memory.memory_char_limit and memory.user_char_limit in config.yaml only took effect after a full gateway restart, even though sister keys (model.context_length, compression.*, memory.provider) are already picked up transparently on the next message via the agent-config signature. Add the two keys to _CACHE_BUSTING_CONFIG_KEYS so the cached AIAgent is rebuilt when either cap changes. Tests pin the behavior: - test_reads_memory_char_limit_subkeys - test_memory_char_limit_change_busts_signature User-facing docs note the new behavior in the hot-reload tip. --- gateway/run.py | 5 +++ tests/gateway/test_agent_cache.py | 44 ++++++++++++++++++++++++ website/docs/user-guide/configuration.md | 2 +- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/gateway/run.py b/gateway/run.py index 7887ec23c3a2..fc50b3f36e59 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -16083,6 +16083,11 @@ async def _run_process_watcher(self, watcher: dict) -> None: ("compression", "protect_last_n"), ("agent", "disabled_toolsets"), ("memory", "provider"), + # memory_char_limit / user_char_limit are passed to MemoryStore at + # agent construction; changing them mid-session must rebuild the + # cached agent so the new limits take effect on the next message. + ("memory", "memory_char_limit"), + ("memory", "user_char_limit"), ) _HONCHO_CACHE_BUSTING_KEYS = ( diff --git a/tests/gateway/test_agent_cache.py b/tests/gateway/test_agent_cache.py index 37f8b51a458d..1bd3e1ea57c5 100644 --- a/tests/gateway/test_agent_cache.py +++ b/tests/gateway/test_agent_cache.py @@ -237,6 +237,50 @@ def test_reads_compression_subkeys(self): assert out["compression.target_ratio"] == 0.3 assert out["compression.protect_last_n"] == 25 + def test_reads_memory_char_limit_subkeys(self): + """memory_char_limit / user_char_limit must be picked up so editing + the cap in config.yaml invalidates the cached agent on the next + message (no gateway restart).""" + from gateway.run import GatewayRunner + + out = GatewayRunner._extract_cache_busting_config( + { + "memory": { + "memory_char_limit": 5000, + "user_char_limit": 2500, + "provider": "mem0", # unrelated — should not affect these + } + } + ) + assert out["memory.memory_char_limit"] == 5000 + assert out["memory.user_char_limit"] == 2500 + + def test_memory_char_limit_change_busts_signature(self): + """Changing memory_char_limit between messages must produce a + different agent-config signature, so the gateway rebuilds the + cached AIAgent and the new cap takes effect immediately.""" + from gateway.run import GatewayRunner + + sig_default = GatewayRunner._agent_config_signature( + model="minimax/MiniMax-M2.7", + runtime={"api_key": "k", "base_url": "u", "provider": "p", "api_mode": ""}, + enabled_toolsets=["memory"], + ephemeral_prompt="", + cache_keys=GatewayRunner._extract_cache_busting_config( + {"memory": {"memory_char_limit": 2200, "user_char_limit": 1375}} + ), + ) + sig_bumped = GatewayRunner._agent_config_signature( + model="minimax/MiniMax-M2.7", + runtime={"api_key": "k", "base_url": "u", "provider": "p", "api_mode": ""}, + enabled_toolsets=["memory"], + ephemeral_prompt="", + cache_keys=GatewayRunner._extract_cache_busting_config( + {"memory": {"memory_char_limit": 5000, "user_char_limit": 2500}} + ), + ) + assert sig_default != sig_bumped + def test_missing_keys_yield_none(self): """Absent config keys must produce None values (still contribute to signature).""" from gateway.run import GatewayRunner diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index d74587432d5b..a35b3590f03e 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -651,7 +651,7 @@ Older configs with `compression.summary_model`, `compression.summary_provider`, `protect_first_n` controls how many **non-system** head messages are pinned across every compaction. Default `3` — the opening user/assistant exchange survives every summarizer pass so the original goal stays visible. On long-running rolling-compaction sessions where the opening turn is no longer relevant, set `protect_first_n: 0` to pin nothing but the system prompt + summary + tail. The system prompt itself is always preserved regardless of this setting. :::tip Gateway hot-reload of compression and context length -As of recent releases, editing `model.context_length` or any `compression.*` key in `config.yaml` on a running gateway takes effect on the next message — no gateway restart, no `/reset`, no session rotation required. The cached-agent signature includes these keys, so the gateway transparently rebuilds the agent when it sees a change. API keys and tool/skill config still require the usual reload paths. +As of recent releases, editing `model.context_length`, any `compression.*` key, or `memory.memory_char_limit` / `memory.user_char_limit` in `config.yaml` on a running gateway takes effect on the next message — no gateway restart, no `/reset`, no session rotation required. The cached-agent signature includes these keys, so the gateway transparently rebuilds the agent when it sees a change. API keys and tool/skill config still require the usual reload paths. ::: ### Common setups