Skip to content

feat(cache-proposals): runtime config refresh for agent-cache and sem…#148

Merged
KIvanow merged 2 commits into
feature/cache-proposal-data-modelfrom
feature/cache-proposal-data-model---libs-support
May 5, 2026
Merged

feat(cache-proposals): runtime config refresh for agent-cache and sem…#148
KIvanow merged 2 commits into
feature/cache-proposal-data-modelfrom
feature/cache-proposal-data-model---libs-support

Conversation

@KIvanow
Copy link
Copy Markdown
Member

@KIvanow KIvanow commented May 4, 2026

…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

  • 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.

@betterdb/semantic-cache 0.4.0

  • Periodic refresh of {name}:__config (same interval/opt-out pattern). Fields: thresholddefaultThreshold; 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).

betterdb-agent-cache 0.6.0 (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.

betterdb-semantic-cache 0.2.0 (Python)

  • 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).

Monitor API fixes

  • 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.

Summary

Changes

Checklist

  • Unit / integration tests added
  • Docs added / updated
  • Roborev review passed — run roborev review --branch or /roborev-review-branch in Claude Code (internal)
  • Competitive analysis done / discussed (internal)
  • Blog post about it discussed (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_policies keys contain unexpected values.

Overview
Enables no-restart rollout of BetterDB Monitor proposals by adding periodic runtime config refresh to both @betterdb/agent-cache and @betterdb/semantic-cache (and their Python ports).

AgentCache now periodically re-reads {name}:__tool_policies (immediate first tick; interval clamped to >=1s) and ToolCache gains refreshPolicies()/refresh_policies() with an atomic swap so externally deleted policies are evicted; failures increment the new {prefix}_config_refresh_failed_total metric.

Python caches gain/extend discovery markers + heartbeats via __betterdb:caches/__betterdb:heartbeat:{name} (new DiscoveryOptions, {prefix}_discovery_write_failed_total, collision checks) and SemanticCache adds periodic refresh of {name}:__config with 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 both new_threshold and new_ttl_seconds.

Reviewed by Cursor Bugbot for commit e03bf49. Bugbot is set up for automated code reviews on this repo. Configure here.

@KIvanow KIvanow requested a review from jamby77 May 4, 2026 19:06
Comment thread packages/semantic-cache-py/betterdb_semantic_cache/discovery.py
Comment thread packages/agent-cache-py/betterdb_agent_cache/discovery.py Outdated
KIvanow added 2 commits May 5, 2026 13:14
…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.
@KIvanow KIvanow force-pushed the feature/cache-proposal-data-model---libs-support branch from efdb8ae to e03bf49 Compare May 5, 2026 10:14
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e03bf49. Configure here.

Copy link
Copy Markdown
Collaborator

@jamby77 jamby77 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very comfortable reviewing large amounts of python code, but for what it's worth, looks good to me.

@KIvanow KIvanow merged commit 54d899a into feature/cache-proposal-data-model May 5, 2026
2 checks passed
@KIvanow KIvanow deleted the feature/cache-proposal-data-model---libs-support branch May 5, 2026 14:40
@github-actions github-actions Bot locked and limited conversation to collaborators May 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants