feat: add optional distributed response cache - #772
Conversation
|
Warning Review limit reachedNext included review available in 42 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review exact current HEAD The PR is limited to issue #745's optional distributed response cache: injected Redis/Dragonfly-compatible provider, deterministic model/mode/parameter-aware keys, fail-open cache errors, strict Please publish a formal current-head verdict using same-head Checks and evidence. Do not push unrelated changes. |
|
Validated and pushed normal commit at exact current HEAD
Please review and issue a formal verdict for |
|
@opencode-agent Review exact current HEAD |
|
Current-head review at
|
|
Root cause fixed at exact current HEAD |
|
@opencode-agent Review exact current HEAD |
|
Correction: the exact full HEAD is |
|
@opencode-agent Review exact current HEAD |
|
Exact head advanced to |
|
|
|
@devin review exact current HEAD |
|
Exact-head local proof for |
|
@opencode-agent Review exact current HEAD only. Verify distributed response-cache partition isolation, cache-hit cost attribution, fail-open provider behavior, request-parameter key completeness, and absence of raw prompt/token material in keys. Publish a formal verdict from same-head evidence. |
|
@opencode-agent Review exact current HEAD f72ddc8 only. Verify distributed response-cache partition isolation, cache-hit cost attribution, fail-open provider behavior, request-parameter key completeness, and absence of raw prompt/token material in keys. Publish a formal verdict from same-head evidence. |
Exact-head validation — PR #772
@opencode-agent please independently review exact current HEAD |
|
Exact-head validation for PR #772:
Please have |
|
Exact current HEAD f72ddc8 was revalidated for optional distributed response caching.
Please review and run protected Checks for this exact HEAD only. |
|
Exact current HEAD f72ddc8 was revalidated for the optional distributed response-cache boundary. Cache truth/isolation, response/cache behavior, HTTP honesty, tool-shape, and security regression proof: 60 passed; compileall and diff-check passed. The cache remains opt-in, tenant/request-policy isolated, and invalid or non-cacheable tool/stream responses do not become reusable output. Current hosted Checks expose no failures; protected independent approval remains required. |
Exact-head validation — PR #772
@opencode-agent please review only exact current HEAD |
Pull request was closed
f72ddc8 to
a7f8a80
Compare
| cache_bypass = _cache_bypass_header(self.headers.get("x-cache-bypass")) | ||
| cache_partition = self._cache_partition() |
There was a problem hiding this comment.
🟡 Cache bypass ignored on non-chat run endpoints
do_POST computes the bypass flag but the /admin/simulate, /api/v1/workflow_runs, and /api/v1/evaluation_runs handlers call orchestrator.run/run_evaluation without it (simulate, workflow_runs, evaluation_runs). With a distributed cache configured, X-Cache-Bypass: true cannot force a fresh run on these endpoints.
Prompt for agents
In server.py do_POST, cache_bypass and cache_partition are computed once (around lines 4950-4951) but are only forwarded to coordinator.complete on the /v1/completions and /v1/chat/completions paths. The /admin/simulate, /api/v1/workflow_runs, and /api/v1/evaluation_runs handlers call orchestrator.run(...) and orchestrator.run_evaluation(...) directly without passing bypass_cache or cache_partition, so a configured distributed cache cannot be bypassed on those endpoints. TaskOrchestrator.run already accepts bypass_cache and cache_partition keyword arguments (and run_evaluation would need to thread them through to its internal run calls). Decide whether these endpoints should honor caching at all; if so, forward bypass_cache and cache_partition consistently; if not, document that they always bypass the cache and pass bypass_cache=True explicitly.
Was this helpful? React with 👍 or 👎 to provide feedback.
| def build_response_cache_key( | ||
| messages: list[Mapping[str, Any]], | ||
| mode: str, | ||
| *, | ||
| model: str = "contextual-orchestrator", | ||
| parameters: Mapping[str, Any] | None = None, | ||
| partition: str | None = None, | ||
| ) -> str: | ||
| """Build a deterministic key from the semantic request envelope. | ||
|
|
||
| Message order and content are meaningful; mapping key order is not. A digest | ||
| keeps prompts and user data out of Redis keys while avoiding false hits across | ||
| models or orchestration modes. | ||
| """ | ||
| if partition is not None and (not isinstance(partition, str) or not partition.strip()): | ||
| raise ValueError("partition must be a non-empty string when provided") | ||
| payload = json.dumps( | ||
| { | ||
| "model": model, | ||
| "mode": mode, | ||
| "messages": messages, | ||
| "parameters": dict(parameters or {}), | ||
| "partition": partition, | ||
| }, | ||
| sort_keys=True, | ||
| ensure_ascii=False, | ||
| separators=(",", ":"), | ||
| ) | ||
| return hashlib.sha256(payload.encode("utf-8")).hexdigest() |
There was a problem hiding this comment.
📝 Info: Distributed cache key omits agent-pool identity
build_response_cache_key digests model, mode, messages, parameters, and partition, but not agent-pool composition. Two deployments with different agent pools sharing one Redis namespace can serve each other's cached responses for the same model/prompt. Operators must assign distinct namespace values per deployment; the digest alone does not isolate them.
Was this helpful? React with 👍 or 👎 to provide feedback.
| cache_bypass = _cache_bypass_header(self.headers.get("x-cache-bypass")) | ||
| cache_partition = self._cache_partition() |
There was a problem hiding this comment.
🟨 Unpartitioned cache reuse across principals on run endpoints
The bearer-derived partition is forwarded to the chat and completions paths but not to the /admin/simulate, /api/v1/workflow_runs, or /api/v1/evaluation_runs handlers, which call orchestrator.run/run_evaluation with no partition (workflow_runs, evaluation_runs). With a distributed cache and multiple principals authenticating through a bearer_verifier, one principal's cached answer for a prompt can be served to another on these endpoints.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Merge-gate evidence (2026-08-24): Rebased onto main (conflict-resolved with #771 tool-failure stack); full local suite green (1559 tests) on head a7f8a80. Deep diff review verdict: merge-ready (cache-key isolation, hot-partition spread, zero-overhead-disabled verified). Required checks green except strix — org-wide NVIDIA NIM quota exhaustion (external provider-capacity blocker; serialization fix in ContextualWisdomLab/.github#1297). |
…buted-response-cache # Conflicts: # contextual_orchestrator/orchestrator.py
…nse-cache' into feat/issue-745-distributed-response-cache # Conflicts: # contextual_orchestrator/__init__.py # contextual_orchestrator/orchestrator.py
| "policy_mode": mode, | ||
| "prompt_text": prompt, | ||
| "answer": result["answer"], | ||
| "cache_status": result.get("cache_status", "disabled"), |
There was a problem hiding this comment.
🟡 Cache hits still count against the spend budget
On a cache hit, run() persists a workflow run whose trace holds the original output and provider usage. spend_analytics (orchestrator.py) sums tokens over every persisted run, and budget_status/run enforce the budget from that total, so replayed cached answers accrue spend and can raise BudgetExceededError. The cost ledger records these hits at zero, and the persisted cache_status is never read by the spend path.
Prompt for agents
On a cache hit, run() persists a new workflow_run with the cached trace (including the original step output text and any provider-reported usage). spend_analytics() aggregates estimated/reported tokens over self._workflow_runs, and budget_status()/run() enforce the spend budget from that aggregate. As a result, cache hits are counted as real spend and can trip BudgetExceededError, even though the cost ledger correctly records cache hits at zero tokens/cost. The record now carries a cache_status field but spend_analytics ignores it. Consider having spend_analytics()/budget accounting skip (or zero out) runs whose cache_status == 'hit', so replayed cached responses are not re-counted as provider spend consistent with the ledger's cache channel.
Was this helpful? React with 👍 or 👎 to provide feedback.
| def _cache_partition(self) -> str: | ||
| """Return a non-secret cache partition for the authenticated bearer.""" | ||
| raw = self.headers.get("authorization", "") | ||
| token = raw.split(" ", 1)[1].strip() if raw.lower().startswith("bearer ") else "" | ||
| if not token: # pragma: no cover - _authorize rejects this first | ||
| raise RequestError(401, "unauthorized", "bearer token is required") | ||
| return hashlib.sha256(token.encode("utf-8")).hexdigest() |
There was a problem hiding this comment.
🟡 JWT bearer tokens defeat the shared cache
_cache_partition keys the partition on the SHA-256 of the raw bearer token. With a bearer_verifier (OIDC/JWT), the presented token rotates per issuance, so identical requests from one principal land in different partitions and different cache keys. Hits almost never occur and the backend fills with single-use entries.
Prompt for agents
_cache_partition() hashes the raw bearer token to isolate cache entries per principal. This works for static shared tokens (auth_token/inference_token), but when SecurityConfig.bearer_verifier is used with rotating OIDC/JWT bearers, each request may present a different token string, producing a different partition and cache key for otherwise identical requests. That makes the distributed cache almost never hit and accumulate many single-use keys. Consider deriving the partition from a stable principal/subject identity supplied by the verifier (e.g. have bearer_verifier return a stable subject claim) rather than the raw token string, while still keeping raw credentials out of the key.
Was this helpful? React with 👍 or 👎 to provide feedback.
| cache_bypass = _cache_bypass_header(self.headers.get("x-cache-bypass")) | ||
| cache_partition = self._cache_partition() |
There was a problem hiding this comment.
📝 Info: Strict X-Cache-Bypass parsing spans all POST endpoints
cache_bypass/cache_partition are computed for every POST before path dispatch (server.py), so a malformed X-Cache-Bypass now returns 400 on non-cache endpoints (agent-pool create, /admin/simulate, workflow/evaluation runs) that previously ignored it.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Merge-gate evidence (2026-08-25): Rebased onto main integrating #771/#785; full local suite green (1621 tests) on head 054183c. Deep review verdict: merge-ready (cache-key isolation, hot-partition spread, zero-overhead-when-disabled verified). Required checks pending on this head; strix blocked externally by org-wide NVIDIA NIM quota exhaustion (ContextualWisdomLab/.github#1297 carries the systemic fix). Merging on the strength of complete local + hosted evidence minus the external blocker. |
…earer (#844) * fix: session-cookie requests partition the response cache without a bearer Merging #772 (distributed cache) with #788 (opaque admin sessions) left a gap: _cache_partition required a bearer header, so every state-changing admin POST from a cookie-authenticated operator failed with 401 before any handler logic ran. An active opaque session id now derives the partition (random per login, so cross-session reuse stays impossible), and a regression test locks the flow in. * test: join session cache test server thread
Buyer-visible outcome
Adds an optional Redis/Dragonfly-compatible response cache while preserving standalone behavior.
cache_partition.X-Cache-Bypassremains strict.cacheledger channel; they are not rebilled as inference.Exact-head verification
51df15d78853992cb25fdee900381e2d2cb79be5— full suite1451 passed in 545.88s28 passedPlease review exact current HEAD
51df15d78853992cb25fdee900381e2d2cb79be5only. Merge only through the protected normal review/check path; no bypass or self-approval.