mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
Review of the rebased stack found the same "version, or None when the config is absent" shape written out at three call sites (`config_version(p) if config_exists(p) else None`), a private try/except copy of it in profiles_cache, and the gateway importing the TUI's private `_version_token` from tui_gateway.methods_config. - config_backend gains `config_version_or_none()` (one backend call, no exists-then-stat race) and `version_token()`, moved from tui_gateway/methods_config.py unchanged. - tui_gateway (methods_config, server), the classic CLI config watcher, gateway/session_config and the profile roster cache use them; profiles_cache drops its private copy. No behaviour change: same version tuples, same numeric token.
43 lines
1.8 KiB
Python
43 lines
1.8 KiB
Python
"""The per-profile derived-value cache behind ``list_profiles`` (``hermes_cli.profiles``)."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
|
|
# (path, kind) -> (file signature, the small derived value). `list_profiles` re-reads three YAML
|
|
# files PER PROFILE, and it is the shared body of `GET /api/profiles` and `profiles.list`, which the
|
|
# Bots roster polls every 5s per connection — so an installer-seeded config.yaml (the annotated
|
|
# template, ~119KB) was re-parsed for every bot every five seconds to yield the same two strings.
|
|
# Only DERIVED values are cached, never a document a caller could write back: the raw readers
|
|
# (`read_user_config_raw`, `_load_yaml_dict`) keep their uncached contract. See #117378.
|
|
_PROFILE_FILE_CACHE: dict[tuple, tuple] = {}
|
|
_PROFILE_FILE_CACHE_MAX = 512
|
|
|
|
|
|
def _profile_file_signature(path: Path) -> Optional[tuple]:
|
|
"""``(mtime_ns, size, inode)``, or None when the file is absent. The atomic writers rename a
|
|
temp file into place, so a rewrite always lands a new inode even within one mtime tick."""
|
|
try:
|
|
stat = path.stat()
|
|
except OSError:
|
|
return None
|
|
return (stat.st_mtime_ns, stat.st_size, stat.st_ino)
|
|
|
|
|
|
def _cached_profile_read(path: Path, kind: str, compute, signature_of=_profile_file_signature):
|
|
"""``compute()``'s value, reused while ``signature_of(path)`` is unchanged. A missing file is never
|
|
cached: reading it costs nothing, and one created later must be picked up."""
|
|
signature = signature_of(path)
|
|
if signature is None:
|
|
return compute()
|
|
key = (str(path), kind)
|
|
cached = _PROFILE_FILE_CACHE.get(key)
|
|
if cached is not None and cached[0] == signature:
|
|
return cached[1]
|
|
value = compute()
|
|
if len(_PROFILE_FILE_CACHE) >= _PROFILE_FILE_CACHE_MAX:
|
|
_PROFILE_FILE_CACHE.clear()
|
|
_PROFILE_FILE_CACHE[key] = (signature, value)
|
|
return value
|