perf(cli): cut hermes startup 63% — flip head-to-head vs codex (#31968)
* perf(bitwarden): persist secret-fetch cache across CLI invocations
Every `hermes` invocation paid a ~380ms tax for `bws secret list` to
Bitwarden Secrets Manager because the existing cache was in-process only.
Back-to-back `hermes chat -q`, gateway-spawned agents, and cron-launched
runs all re-fetched.
Adds a disk-persisted L2 cache at `<hermes_home>/cache/bws_cache.json`
(mode 0600, never contains the access token — only the SHA-256
fingerprint prefix). Same TTL as the in-process cache. Read on miss,
write on bws success, ignored on key mismatch / corruption / expiry.
Measured on a startup profile:
load_hermes_dotenv() cold: 372ms → warm (disk cache hit): 20ms
End-to-end `hermes --version` cold→warm: 666ms → ~295ms.
In a hermes-vs-codex benchmark across 11 single- and multi-turn tasks
(framework overhead = wall − llm − tool_exec, median over 3 trials):
cohort before after saved
single-turn (median) 2.96s 2.31s -0.65s
multi-turn (5-turn) 9.40s 8.95s -0.45s (≈0.3s/turn)
Hermes now wins head-to-head on 6/11 tasks vs codex (was 4/11 before).
The remaining ~0.6s single-turn delta is mostly Python's own import
cost in hermes_cli.main, which is a separate optimization.
* perf(cli): lazy-load model catalog + dedupe config.yaml reads at startup
Two import-time wins on top of the bws disk-cache fix:
1. Lazy-load `hermes_cli.models._PROVIDER_MODELS` via PEP 562
module-level `__getattr__`. The catalog is ~55ms of work that was
eagerly imported on every CLI invocation (line 4557 `if not
_is_termux_startup_environment(): from hermes_cli.models import
_PROVIDER_MODELS`). Audit showed every internal call site already
does its own function-local import; only test code reads
`hermes_cli.main._PROVIDER_MODELS` as a module attribute, and
__getattr__ keeps that working transparently. First access triggers
the import once and caches the result on the module via
`globals()[name] = ...`, so subsequent reads are dict lookups.
2. Dedupe the double config.yaml read in the top-of-module bootstrap.
Previously: one raw yaml.safe_load for the `security.redact_secrets`
bridge, then a separate full `load_config()` (with deep-merge) for
`network.force_ipv4`. Both keys come from the same file. Merged
into one raw yaml load.
Combined with the bws cache fix in the previous commit:
hermes --version wall time:
original (cold): 666 ms
after bws fix (warm): 295 ms
after lazy-load + dedupe: 228 ms (-67 ms additional, -66% from original)
Tests:
- tests/hermes_cli/test_api_key_providers.py: 173/173 pass
(lazy __getattr__ correctly handles
`from hermes_cli.main import _PROVIDER_MODELS`)
- tests/test_ipv4_preference.py + tests/hermes_cli/test_redact_config_bridge.py +
tests/agent/test_redact.py: 93/93 pass (dedupe preserves both bridges)
- tests/test_bitwarden_secrets.py + env_loader tests: 49/49 pass
This commit is contained in:
@@ -572,3 +572,224 @@ def test_env_loader_calls_bsm_when_enabled(tmp_path, monkeypatch):
|
||||
|
||||
assert called["n"] == 1
|
||||
assert os.environ.get("MY_BSM_KEY") == "from-bsm"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Disk-persisted cache (cross-process — speeds up back-to-back CLI invocations)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_disk_cache_written_after_first_fetch(monkeypatch, tmp_path):
|
||||
"""First fetch hits bws AND writes a 0600 file under hermes_home/cache/."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
call_count = {"n": 0}
|
||||
def fake_run(*a, **kw):
|
||||
call_count["n"] += 1
|
||||
return mock.Mock(returncode=0, stdout=payload, stderr="")
|
||||
monkeypatch.setattr(bw.subprocess, "run", fake_run)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
secrets, _ = bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
assert secrets == {"K1": "v1"}
|
||||
assert call_count["n"] == 1
|
||||
|
||||
cache_path = bw._disk_cache_path(home)
|
||||
assert cache_path.exists()
|
||||
# Mode must be 0600 — disk cache contains plaintext secret values
|
||||
mode = os.stat(cache_path).st_mode & 0o777
|
||||
assert mode == 0o600, f"expected 0o600, got 0o{mode:o}"
|
||||
|
||||
# File contents: key (fingerprint not raw token), secrets dict, fetched_at
|
||||
payload_disk = json.loads(cache_path.read_text())
|
||||
assert set(payload_disk.keys()) == {"key", "secrets", "fetched_at"}
|
||||
assert payload_disk["secrets"] == {"K1": "v1"}
|
||||
# Critically, the raw access token must NOT appear anywhere in the file
|
||||
assert "0.t" not in cache_path.read_text()
|
||||
|
||||
|
||||
def test_disk_cache_short_circuits_bws_when_fresh(monkeypatch, tmp_path):
|
||||
"""Second fetch (different process simulation) skips bws entirely."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
call_count = {"n": 0}
|
||||
def fake_run(*a, **kw):
|
||||
call_count["n"] += 1
|
||||
return mock.Mock(returncode=0, stdout=payload, stderr="")
|
||||
monkeypatch.setattr(bw.subprocess, "run", fake_run)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
# First call: hits bws, populates disk cache
|
||||
bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
assert call_count["n"] == 1
|
||||
|
||||
# Clear ONLY the in-process cache to simulate a fresh subprocess.
|
||||
bw._CACHE.clear()
|
||||
|
||||
secrets2, _ = bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
assert secrets2 == {"K1": "v1"}
|
||||
# Critical: bws was NOT invoked the second time
|
||||
assert call_count["n"] == 1
|
||||
|
||||
|
||||
def test_disk_cache_expires_with_ttl(monkeypatch, tmp_path):
|
||||
"""Stale disk cache (older than ttl) triggers a refetch."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
call_count = {"n": 0}
|
||||
def fake_run(*a, **kw):
|
||||
call_count["n"] += 1
|
||||
return mock.Mock(returncode=0, stdout=payload, stderr="")
|
||||
monkeypatch.setattr(bw.subprocess, "run", fake_run)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
# First call
|
||||
bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
assert call_count["n"] == 1
|
||||
|
||||
# Backdate the disk cache so the TTL window has passed
|
||||
cache_path = bw._disk_cache_path(home)
|
||||
payload_disk = json.loads(cache_path.read_text())
|
||||
payload_disk["fetched_at"] = time.time() - 10_000
|
||||
cache_path.write_text(json.dumps(payload_disk))
|
||||
bw._CACHE.clear()
|
||||
|
||||
# Second call: stale disk → refetch
|
||||
bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
assert call_count["n"] == 2
|
||||
|
||||
|
||||
def test_disk_cache_key_mismatch_triggers_refetch(monkeypatch, tmp_path):
|
||||
"""Disk cache entry written by a different token/project is ignored."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
call_count = {"n": 0}
|
||||
def fake_run(*a, **kw):
|
||||
call_count["n"] += 1
|
||||
return mock.Mock(returncode=0, stdout=payload, stderr="")
|
||||
monkeypatch.setattr(bw.subprocess, "run", fake_run)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
# Write a cache entry for a DIFFERENT token/project pair
|
||||
cache_path = bw._disk_cache_path(home)
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.write_text(json.dumps({
|
||||
"key": "deadbeef00000000|other-project|",
|
||||
"secrets": {"OTHER": "should-not-leak"},
|
||||
"fetched_at": time.time(),
|
||||
}))
|
||||
|
||||
secrets, _ = bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
# We must NOT have used the foreign cache entry
|
||||
assert secrets == {"K1": "v1"}
|
||||
assert "OTHER" not in secrets
|
||||
assert call_count["n"] == 1
|
||||
|
||||
|
||||
def test_disk_cache_use_cache_false_skips_disk(monkeypatch, tmp_path):
|
||||
"""use_cache=False must skip BOTH in-process and disk caches."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
call_count = {"n": 0}
|
||||
def fake_run(*a, **kw):
|
||||
call_count["n"] += 1
|
||||
return mock.Mock(returncode=0, stdout=payload, stderr="")
|
||||
monkeypatch.setattr(bw.subprocess, "run", fake_run)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
# First call WITH cache populates disk
|
||||
bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, use_cache=True, home_path=home,
|
||||
)
|
||||
assert call_count["n"] == 1
|
||||
bw._CACHE.clear()
|
||||
|
||||
# Second call with use_cache=False MUST hit bws again even though disk is fresh
|
||||
bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, use_cache=False, home_path=home,
|
||||
)
|
||||
assert call_count["n"] == 2
|
||||
|
||||
|
||||
def test_disk_cache_corrupt_file_falls_through(monkeypatch, tmp_path):
|
||||
"""A garbage cache file must NOT crash startup — we refetch."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
fake_binary = tmp_path / "bws"
|
||||
fake_binary.write_text("")
|
||||
payload = _fake_bws_payload([{"key": "K1", "value": "v1"}])
|
||||
|
||||
monkeypatch.setattr(
|
||||
bw.subprocess, "run",
|
||||
lambda *a, **kw: mock.Mock(returncode=0, stdout=payload, stderr=""),
|
||||
)
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
# Write a corrupt cache file
|
||||
cache_path = bw._disk_cache_path(home)
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.write_text("not json {{{")
|
||||
|
||||
secrets, _ = bw.fetch_bitwarden_secrets(
|
||||
access_token="0.t", project_id="proj-1", binary=fake_binary,
|
||||
cache_ttl_seconds=300, home_path=home,
|
||||
)
|
||||
# Refetched cleanly
|
||||
assert secrets == {"K1": "v1"}
|
||||
# And the corrupt file was replaced with a valid one
|
||||
assert json.loads(cache_path.read_text())["secrets"] == {"K1": "v1"}
|
||||
|
||||
|
||||
def test_reset_cache_for_tests_deletes_disk_file(tmp_path):
|
||||
"""_reset_cache_for_tests(home_path) must also clean disk."""
|
||||
home = tmp_path / ".hermes"
|
||||
home.mkdir()
|
||||
cache_path = bw._disk_cache_path(home)
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.write_text("{}")
|
||||
assert cache_path.exists()
|
||||
|
||||
bw._reset_cache_for_tests(home)
|
||||
assert not cache_path.exists()
|
||||
# Idempotent
|
||||
bw._reset_cache_for_tests(home)
|
||||
|
||||
Reference in New Issue
Block a user