feat(cache-proposals): runtime config refresh for agent-cache and sem…#148
Conversation
…antic-cache (TS + Python)
Implements the full propose→approve→apply→pickup loop so BetterDB Monitor
cache proposals take effect in running processes without a restart.
- Periodic refresh of `{name}:__tool_policies` (default 30 s, opt-out via
`configRefresh: { enabled: false }`). First refresh fires synchronously on
construction; subsequent ticks run on a `setInterval`.
- `ToolCache.refreshPolicies()` — atomic swap (clear + repopulate), returns
bool. `loadPolicies()` now delegates to it; stale entries are evicted.
- New Prometheus counter `{prefix}_config_refresh_failed_total`.
- New `ConfigRefreshOptions` type exported from the package root.
- Periodic refresh of `{name}:__config` (same interval/opt-out pattern).
Fields: `threshold` → `defaultThreshold`; `threshold:{cat}` →
`categoryThresholds[cat]`. Constructor values are fallbacks when absent.
- `refreshConfig()` public method with per-field range validation (0–2).
- Adds `threshold_adjust` to the discovery capabilities array, unblocking
`cache_propose_threshold_adjust` in Monitor.
- New `{prefix}_config_refresh_failed_total` counter.
- New `ConfigRefreshOptions` type exported from the package root.
- `escapeTag` exported from the package root (both TS and Python).
- Discovery marker protocol (0.5.0): registers `__betterdb:caches` entry
and 30 s heartbeat on construction; `shutdown()` removes the heartbeat.
New `DiscoveryOptions`, `{prefix}_discovery_write_failed_total` counter.
- Config refresh (0.6.0): `asyncio` task loop mirrors TS behaviour —
first refresh before first sleep. `ToolCache.refresh_policies()` atomic
swap. New `ConfigRefreshOptions`. `{prefix}_config_refresh_failed_total`.
- New `examples/monitor_proposals/main.py` demonstrating the full loop.
- Missing test coverage added: `refresh_policies()` (6 tests),
`AgentCache` config refresh (6 tests + counter), `SessionStore.get_all()`,
`destroy_thread()`, `scan_fields_by_prefix()` (13 tests).
- `aiohttp` declared as `[normalizer]` optional extra in `pyproject.toml`.
- Discovery marker protocol: registers on `initialize()`; capabilities
include `['invalidate', 'similarity_distribution', 'threshold_adjust']`.
Cross-type collision raises `SemanticCacheUsageError`. `flush()` stops the
old manager before dropping the index (matches TS concurrency semantics).
New `DiscoveryOptions`, `{prefix}_discovery_write_failed_total` counter.
- Config refresh: `asyncio` task loop, `refresh_config()` with field-level
validation, constructor fallbacks, per-category support.
New `ConfigRefreshOptions`. `{prefix}_config_refresh_failed_total`.
- New `examples/monitor_proposals/main.py` with deterministic content-word
mock embedder (stopwords stripped, DJB2 hash, dim=64). Output is
bit-for-bit identical to the TypeScript equivalent.
- `escape_tag` exported from the package root.
- New `test_config_refresh.py` (14 tests) and `test_discovery.py` (21 tests).
- `CacheApplyDispatcher.applySemanticInvalidate`: corrected FT index name
from `{prefix}:__index` to `{prefix}:idx` (all semantic invalidation
proposals were silently deleting 0 entries against a non-existent index).
- Dispatcher test `FakeClient.call()` now captures arguments so index name
and filter expression can be asserted.
- New dispatcher contract tests: index name, filter forwarding, field format
agreement between dispatcher writes and library reads.
- `cache-proposal.service.spec.ts`: `readCurrentThreshold` and
`readCurrentTtl` tested with a fake registry, verifying the
apply→re-propose cycle reads the dispatcher-written value.
High — ensure_discovery_ready() hung indefinitely agent_cache.py: track the discovery registration in a dedicated _discovery_task field and await only that task in ensure_discovery_ready(), not all _background_tasks. The config-refresh loop is an infinite task that never completes on its own; gathering it blocked the caller permanently. Medium — cache_edit_and_approve_proposal accepted both edit fields at once mcp/src/index.ts: add a mutual-exclusion guard that returns an error when both new_threshold and new_ttl_seconds are provided. The tool description says 'provide exactly one'; now the contract is enforced in code. Low — DiscoveryOptions defined in two places (types.py and discovery.py) discovery.py: remove the duplicate @DataClass definition and import DiscoveryOptions from types.py, the single canonical location already re-exported by __init__.py. Low — dead code in mock_embed() semantic-cache-py examples/monitor_proposals/main.py: the first words = list({...}) set-comprehension was immediately overwritten by the cleaned loop below it. Remove the dead first pass; keep only the strip-then-filter loop that produces the correct deduplicated word list.
efdb8ae to
e03bf49
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e03bf49. Configure here.
| """ | ||
| enabled: bool = True | ||
| heartbeat_interval_ms: int = 30_000 | ||
| include_categories: bool = True |
There was a problem hiding this comment.
flush() leaks config refresh task on re-initialize
Low Severity
DiscoveryOptions is defined identically in both discovery.py and types.py. The semantic_cache.py module imports it from discovery.py for its type annotation on _discovery_opts, but the actual runtime value comes from SemanticCacheOptions.discovery which is typed using the types.py version. The agent-cache-py package avoids this by having discovery.py import from types.py with a # single source of truth comment. The semantic-cache-py package duplicates the class instead.
Reviewed by Cursor Bugbot for commit e03bf49. Configure here.
jamby77
left a comment
There was a problem hiding this comment.
Not very comfortable reviewing large amounts of python code, but for what it's worth, looks good to me.


…antic-cache (TS + Python)
Implements the full propose→approve→apply→pickup loop so BetterDB Monitor cache proposals take effect in running processes without a restart.
@betterdb/agent-cache 0.6.0
{name}:__tool_policies(default 30 s, opt-out viaconfigRefresh: { enabled: false }). First refresh fires synchronously on construction; subsequent ticks run on asetInterval.ToolCache.refreshPolicies()— atomic swap (clear + repopulate), returns bool.loadPolicies()now delegates to it; stale entries are evicted.{prefix}_config_refresh_failed_total.ConfigRefreshOptionstype exported from the package root.@betterdb/semantic-cache 0.4.0
{name}:__config(same interval/opt-out pattern). Fields:threshold→defaultThreshold;threshold:{cat}→categoryThresholds[cat]. Constructor values are fallbacks when absent.refreshConfig()public method with per-field range validation (0–2).threshold_adjustto the discovery capabilities array, unblockingcache_propose_threshold_adjustin Monitor.{prefix}_config_refresh_failed_totalcounter.ConfigRefreshOptionstype exported from the package root.escapeTagexported from the package root (both TS and Python).betterdb-agent-cache 0.6.0 (Python)
__betterdb:cachesentry and 30 s heartbeat on construction;shutdown()removes the heartbeat. NewDiscoveryOptions,{prefix}_discovery_write_failed_totalcounter.asynciotask loop mirrors TS behaviour — first refresh before first sleep.ToolCache.refresh_policies()atomic swap. NewConfigRefreshOptions.{prefix}_config_refresh_failed_total.examples/monitor_proposals/main.pydemonstrating the full loop.refresh_policies()(6 tests),AgentCacheconfig refresh (6 tests + counter),SessionStore.get_all(),destroy_thread(),scan_fields_by_prefix()(13 tests).aiohttpdeclared as[normalizer]optional extra inpyproject.toml.betterdb-semantic-cache 0.2.0 (Python)
initialize(); capabilities include['invalidate', 'similarity_distribution', 'threshold_adjust']. Cross-type collision raisesSemanticCacheUsageError.flush()stops the old manager before dropping the index (matches TS concurrency semantics). NewDiscoveryOptions,{prefix}_discovery_write_failed_totalcounter.asynciotask loop,refresh_config()with field-level validation, constructor fallbacks, per-category support. NewConfigRefreshOptions.{prefix}_config_refresh_failed_total.examples/monitor_proposals/main.pywith deterministic content-word mock embedder (stopwords stripped, DJB2 hash, dim=64). Output is bit-for-bit identical to the TypeScript equivalent.escape_tagexported from the package root.test_config_refresh.py(14 tests) andtest_discovery.py(21 tests).Monitor API fixes
CacheApplyDispatcher.applySemanticInvalidate: corrected FT index name from{prefix}:__indexto{prefix}:idx(all semantic invalidation proposals were silently deleting 0 entries against a non-existent index).FakeClient.call()now captures arguments so index name and filter expression can be asserted.cache-proposal.service.spec.ts:readCurrentThresholdandreadCurrentTtltested with a fake registry, verifying the apply→re-propose cycle reads the dispatcher-written value.Summary
Changes
Checklist
roborev review --branchor/roborev-review-branchin Claude Code (internal)Note
Medium Risk
Introduces new background refresh/heartbeat loops that mutate in-memory policy/threshold state at runtime and add new Valkey writes/metrics, which can impact performance and behavior in long-running processes if misconfigured or if existing
__config/__tool_policieskeys contain unexpected values.Overview
Enables no-restart rollout of BetterDB Monitor proposals by adding periodic runtime config refresh to both
@betterdb/agent-cacheand@betterdb/semantic-cache(and their Python ports).AgentCachenow periodically re-reads{name}:__tool_policies(immediate first tick; interval clamped to >=1s) andToolCachegainsrefreshPolicies()/refresh_policies()with an atomic swap so externally deleted policies are evicted; failures increment the new{prefix}_config_refresh_failed_totalmetric.Python caches gain/extend discovery markers + heartbeats via
__betterdb:caches/__betterdb:heartbeat:{name}(newDiscoveryOptions,{prefix}_discovery_write_failed_total, collision checks) andSemanticCacheadds periodic refresh of{name}:__configwith validation and constructor fallbacks; the PR also bumps package versions, adds monitor-proposal demo examples, expands unit test coverage, and tightens MCP proposal editing to reject providing bothnew_thresholdandnew_ttl_seconds.Reviewed by Cursor Bugbot for commit e03bf49. Bugbot is set up for automated code reviews on this repo. Configure here.