hermes - ✅(Solved) Fix Memory char limits ignored by CLI/MCP tool dispatch path [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#11665Fetched 2026-04-18 05:59:28
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×1referenced ×1renamed ×1

Fix Action

Fix / Workaround

However, when the memory tool is called via the registry dispatch path (CLI terminal sessions, subagent tasks via handle_function_call), the hardcoded defaults in MemoryStore.__init__ are used instead:

Result: Two separate memory limit values are active depending on which code path handles the request:

  • Path A (run_agent.py via AIAgent.__init__): Uses config.yaml values ✅
  • Path B (registry dispatch via handle_function_call): Uses hardcoded defaults (1375/2200 chars) ❌

Either:

  1. Load config.yaml before registry dispatch and pass limits explicitly to MemoryStore.__init__
  2. Pass the existing MemoryStore instance through handle_function_call so dispatch reuses it
  3. Document the discrepancy clearly if this is by design

PR fix notes

PR #11693: fix(memory): honor config.yaml limits in registry-dispatched memory calls (#11665)

Description (problem / solution / changelog)

What does this PR do?

Fixes #11665.

The memory tool is reachable through two code paths, and until now only one of them respected config.yaml:

  • Path AAIAgent._invoke_tool / the main agent loop passes store=self._memory_store, which AIAgent.__init__ (run_agent.py:1219) builds with the user's configured memory.memory_char_limit / memory.user_char_limit. ✅
  • Path B — anything that routes through model_tools.handle_function_calltools.registry.registry.dispatch("memory", ...). The registry handler lambda reads store=kw.get("store"), and nothing passes store= through that path. ❌

Code paths affected by Path B:

  • tools/code_execution_tool.py (execute_code sandboxes that call tools back via JSON-RPC)
  • environments/agent_loop.py (RL training loop)
  • environments/tool_context.py (reward / verifier helpers)
  • hermes_cli/plugins.py (plugin dispatch — already plumbs parent_agent but the handler wasn't using it)

On pre-fix code, dispatches via Path B silently errored with "Memory is not available" (the reporter's description of "hardcoded defaults" maps to the same underlying symptom — config.yaml values were invisible to that path). Any memory_char_limit: 3000 or user_char_limit: … override a user set in config.yaml was ignored in every dispatch site that wasn't the main AIAgent loop.

Root cause

tools/memory_tool.py:572 — the registry handler:

handler=lambda args, **kw: memory_tool(
    ...
    store=kw.get("store")),

store is never populated by handle_function_call or registry.dispatch, so the tool always received None and short-circuited at the if store is None guard (tools/memory_tool.py:475).

Smallest safe fix

Extend the registry handler with _resolve_memory_store_from_kwargs — a fallback chain:

  1. Explicit store= kwarg — main agent loop + tests, unchanged.
  2. kw[\"parent_agent\"]._memory_storehermes_cli/plugins.py already plumbs parent_agent into dispatch kwargs, so plugin-initiated memory calls now reuse the live agent's store.
  3. A process-wide config-driven MemoryStore singleton built lazily from hermes_cli.config.load_config(). Cached so repeated dispatches don't re-read config.yaml or the on-disk MEMORY.md / USER.md files.
  4. If both memory_enabled and user_profile_enabled are false in config, returns None — preserves the "Memory is not available" error for users who opted out.

Also exports _reset_default_store_for_tests so tests that mutate HERMES_HOME can force the next dispatch to rebuild the singleton. No public API change.

Precedence / compat truth table

caller / statestore used
explicit store= kwarg passedthat store (unchanged — main agent loop path)
plugin dispatch with parent_agent, parent has storeparent's store (new)
plugin dispatch with parent_agent, parent store is None (subagent)falls through to config default (new)
no store, no parent_agent, memory_enabled: true in configconfig-driven singleton (new)
no store, no parent_agent, memory_enabled: false AND user_profile_enabled: falseNone → "not available" error (unchanged)

The main agent loop still uses self._memory_store directly in all three special-case sites (_invoke_tool, main tool-calling loop, flush_memories), so there is no behavior change for users on the normal CLI path. The fallback chain only activates when the registry handler lambda fires.

Narrow scope — why only the registry handler

I intentionally did not touch handle_function_call, registry.dispatch, or any of the caller sites that invoke them. Adding store= to those signatures would ripple through RL training, sandbox RPC, plugin context, and the public plugin API. The fallback-chain approach scoped inside memory_tool.py is local, is reversible, and keeps the single source of truth on config.yaml.

How to test

  1. Run the focused suite:

    source venv/bin/activate
    python -m pytest tests/tools/test_memory_tool.py -q

    41 passed (33 existing + 8 new).

  2. Confirm the new behavioral test catches the bug on origin/main:

    git stash push tools/memory_tool.py
    python -m pytest tests/tools/test_memory_tool.py::TestRegistryDispatchStoreResolution::test_registry_dispatch_uses_config_memory_limit -v

    → fails with

    AssertionError: dispatch returned: {'error': 'Memory is not available...', 'success': False}

    Restore with git stash pop.

  3. CI-aligned broad suite:

    python -m pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e --tb=short -n auto

    → 12389 passed, 39 skipped, 7 pre-existing baseline failures (tests/gateway/test_matrix.py, tests/gateway/test_approve_deny_commands.py ×2, tests/hermes_cli/test_gateway_wsl.py ×2, tests/tools/test_file_staleness.py ×2). Each reproduces on clean origin/main with identical assertions — none are in the touched code path.

Tested on: macOS 15 (Darwin 25.5.0), Python 3.11.15.

Related Issue

Fixes #11665

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • tools/memory_tool.py: add _resolve_memory_store_from_kwargs + _reset_default_store_for_tests + module-level lazy singleton; swap the registry handler to use the resolver.
  • tests/tools/test_memory_tool.py: add TestRegistryDispatchStoreResolution — 8 tests covering the full truth table (explicit store wins, parent_agent fallback, subagent-with-None falls through, config-driven default, user_profile-only activation, memory-disabled returns None, singleton identity, end-to-end registry dispatch).

Adjacent surfaces checked

  • run_agent.py special-case branches at lines 7411 and 7894 — unchanged; they still pass store=self._memory_store.
  • run_agent.py:1219 AIAgent.__init__ MemoryStore construction — unchanged.
  • tools/delegate_tool.py:366 (subagent skip_memory=True) — the new behavior is safe: subagents still don't get a store (parent_agent._memory_store is None on them); the resolver falls through to the config-driven default, but the subagent's own skip_memory=True keeps it from dispatching memory in the first place.
  • hermes_cli/plugins.py:291 plugin dispatch with parent_agent — now transparently uses parent's store.
  • tools/code_execution_tool.py sandbox dispatch — now works with config-driven defaults.
  • environments/agent_loop.py and environments/tool_context.py — now work with config-driven defaults.

Checklist

Code

  • I've read the Contributing Guide
  • Commit messages follow Conventional Commits (fix(scope):)
  • Searched for existing PRs — none on #11665
  • Only changes related to this fix
  • Ran the CI-aligned test command and classified failures baseline-vs-change
  • Added tests for my changes (8 new; 1 fails behaviorally on origin/main without the fix, the rest pin the fallback chain)
  • Tested on macOS 15 (Darwin 25.5.0), Python 3.11.15

Documentation & Housekeeping

  • No docs changes needed — internal dispatch detail, no public API surface
  • No config-key changes — existing memory.memory_char_limit / memory.user_char_limit keys are unchanged
  • No architecture/workflow changes
  • Cross-platform impact: none — pure Python, no filesystem/process/encoding changes
  • No tool descriptions/schemas touched

Notes for reviewers

  • No standalone lint command is defined; CI runs python -m pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e --tb=short -n auto (matches .github/workflows/tests.yml).
  • No hermes_cli/** changes — the Nix workflow should not trigger.

Changed files

  • tests/tools/test_memory_tool.py (modified, +232/-0)
  • tools/memory_tool.py (modified, +96/-1)

Code Example

def __init__(self, memory_char_limit: int = 2200, user_char_limit: int = 1375, ...)
RAW_BUFFERClick to expand / collapse

Bug Description

The memory_char_limit and user_char_limit values in config.yaml are only read by AIAgent.__init__ in run_agent.py when creating the MemoryStore instance.

However, when the memory tool is called via the registry dispatch path (CLI terminal sessions, subagent tasks via handle_function_call), the hardcoded defaults in MemoryStore.__init__ are used instead:

File: hermes_agent/tools/memory_tool.py, line ~116

def __init__(self, memory_char_limit: int = 2200, user_char_limit: int = 1375, ...)

Result: Two separate memory limit values are active depending on which code path handles the request:

  • Path A (run_agent.py via AIAgent.__init__): Uses config.yaml values ✅
  • Path B (registry dispatch via handle_function_call): Uses hardcoded defaults (1375/2200 chars) ❌

This means a user can set memory_char_limit: 10000 in config.yaml but CLI terminal sessions still enforce 2,200 — a confusing inconsistency.

Expected Behavior

Both code paths should read from config.yaml so users have a single authoritative configuration.

Suggested Fix

Either:

  1. Load config.yaml before registry dispatch and pass limits explicitly to MemoryStore.__init__
  2. Pass the existing MemoryStore instance through handle_function_call so dispatch reuses it
  3. Document the discrepancy clearly if this is by design

Environment

  • Hermes version: v0.10.0+ (2026.04.16)
  • Config: memory_char_limit: 3000 in config.yaml
  • MCP server: MemPalace connected
  • Affects: CLI terminal sessions, subagent task memory calls

extent analysis

TL;DR

Pass the existing MemoryStore instance through handle_function_call to ensure consistent memory limit enforcement from config.yaml.

Guidance

  • Review hermes_agent/tools/memory_tool.py to understand how MemoryStore.__init__ is called via the registry dispatch path.
  • Modify handle_function_call to pass the MemoryStore instance created in AIAgent.__init__ to ensure config.yaml values are used.
  • Verify the fix by setting a custom memory_char_limit in config.yaml and checking its enforcement in both code paths.
  • Consider documenting the change to avoid future inconsistencies.

Example

# In handle_function_call, pass the existing MemoryStore instance
def handle_function_call(...):
    # ...
    memory_tool = MemoryTool(existing_memory_store)  # assuming existing_memory_store is the instance from AIAgent.__init__
    # ...

Notes

This fix assumes that passing the MemoryStore instance through handle_function_call is feasible and does not introduce other issues. Additional testing may be necessary to ensure the change does not affect other parts of the system.

Recommendation

Apply workaround: Pass the existing MemoryStore instance through handle_function_call to ensure consistent memory limit enforcement, as this approach is more straightforward and less prone to introducing new issues compared to loading config.yaml again or documenting the discrepancy.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Memory char limits ignored by CLI/MCP tool dispatch path [1 pull requests, 1 participants]