Skip to content

feat(api): opt-in per-bank attribution for remote reranker requests - #2

Open
chethanuk wants to merge 8 commits into
mainfrom
fix/issue-2729-featapi-optin-perbank-attribution-for-remote
Open

feat(api): opt-in per-bank attribution for remote reranker requests#2
chethanuk wants to merge 8 commits into
mainfrom
fix/issue-2729-featapi-optin-perbank-attribution-for-remote

Conversation

@chethanuk

@chethanuk chethanuk commented Jul 16, 2026

Copy link
Copy Markdown
Owner

User description

Summary

Opt-in per-bank cost attribution for remote rerank requests. When HINDSIGHT_API_RERANKER_SEND_BANK_AS_HEADER=true, outbound rerank POSTs carry X-Hindsight-Bank-Id: <bank_id>. Default is false — a byte-identical no-op.

Covers: the Cohere-compatible HTTP client (Cohere via base URL, OpenRouter, ZeroEntropy, SiliconFlow, Alibaba) and the LiteLLM proxy reranker (LiteLLMCrossEncoder, same per-request pattern).

Fixes vectorize-io#2729

Root cause

_CohereCompatibleRerankClient.predict posted with no per-request headers. The process-wide client only carries Authorization/Content-Type and is shared across banks, so a client-default header could pin bank A's id onto bank B's requests. The existing apply_bank_attribution() body mutator can't help — the Cohere rerank wire format has no OpenAI-style user field.

sequenceDiagram
    participant R as recall / reflect
    participant CV as _current_bank_id (contextvar)
    participant C as shared rerank client<br/>(one per process, all banks)
    participant P as remote provider
    R->>CV: bind bank_id
    R->>C: predict(query, docs)
    C->>C: bank_attribution_headers()<br/>flag on + bank bound → {X-Hindsight-Bank-Id}
    C->>P: POST /rerank (per-request headers,<br/>never client defaults)
    Note over C,P: bank A and bank B requests on the same client<br/>each carry their own id — no bleed
Loading

Design notes

  • Per-request headers, not client defaults — the client is shared across banks; defaults would misattribute. A dedicated two-bank isolation test proves requests interleaved on the same client carry distinct ids.
  • Flag is static (server-level), not bank-configurable — it's an infrastructure/billing concern.
  • Header and flag names match the contract proposed in the issue exactly. The issue's open question about an X-Bank-Id spelling for MCP surfaces is left to maintainers — trivial rename if preferred.
  • Reranks issued during reflect currently run with no bank bound; upstream fix(reflect): preserve bank attribution in provider calls vectorize-io/hindsight#2764 fixes that binding orthogonally, and this feature picks it up automatically once merged.
  • Non-ASCII bank ids are skipped (no header) rather than percent-encoded: httpx requires ASCII header values, and an encoded id wouldn't match billing dashboards. Covered by a dedicated test — a unicode bank id can never 500 a recall.

How this differs from vectorize-io#2740

Strictly additive and scoped to the issue's stated minimum plus the safe LiteLLM proxy path: no existing attribution/LiteLLM tests are deleted, the LiteLLM legacy response fallback is untouched, and the unrelated reflect-binding change stays in vectorize-io#2764. The LiteLLM SDK path is deliberately left out: passing headers= into litellm.arerank unconditionally is unverified against litellm's rerank signature (it may break SDK reranks even with the flag off). Tests here exercise the real env flag + contextvar end-to-end through httpx.MockTransport — including absent-by-default, absent-when-no-bank, auth-header survival, and two-bank per-request isolation — rather than patching the header helper.

Out of scope (follow-ups)

Native Cohere SDK, LiteLLM SDK (pending kwarg verification), Google, TEI (self-hosted; attribution value low), local/FlashRank/JinaMLX/RRF (no remote spend to attribute).

How to test

cd hindsight-api-slim
uv run pytest tests/test_reranker_bank_attribution.py tests/test_bank_attribution_config.py -v
uv run pytest tests/test_cohere_cross_encoder.py tests/test_bank_attribution.py \
              tests/test_tei_cross_encoder.py tests/test_reranker_timeouts.py -q
uv run ty check hindsight_api/engine/bank_attribution.py hindsight_api/engine/cross_encoder.py
cd ../hindsight-embed && uv run pytest tests/test_env_template.py -q

Test evidence

Run on the de-stacked, squashed branch (single commit on upstream main):

Check Result
attribution + cohere + tei + timeouts suites (6 files) 72 passed, 3 skipped
hindsight-embed/tests/test_env_template.py 7 passed
ruff check / ruff format --check on touched files clean
ty check on bank_attribution.py + cross_encoder.py clean
generate-docs-skill.sh idempotency clean (no drift)

CodeAnt-AI Description

Add opt-in bank attribution for remote rerank calls and fix token and memory reporting

What Changed

  • Remote rerank requests can now include X-Hindsight-Bank-Id when enabled, so shared reranker gateways can track spend per bank
  • The new header applies to Cohere-compatible remote rerank paths and the LiteLLM proxy path; local and native SDK rerankers are unchanged
  • OpenAI-compatible LLM metrics now record cached input tokens and reasoning tokens, so billed tokens are not dropped from usage reporting
  • Memory expansion now keeps each requested memory ID matched to its own result, including when some IDs are invalid
  • Reflect now preserves the active bank while it runs, and observation scopes are read back consistently during memory fetches

Impact

✅ Per-bank rerank billing
✅ Fewer missing token usage counts
✅ Correct memory lookup results

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@codeant-ai

codeant-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add preflight validation for bank operations and configuration updates, separate configuration validation from persistence, and introduce optional per-request bank-id headers for Cohere-compatible remote reranking with accompanying configuration, documentation, and tests.

Changes

Bank configuration validation

Layer / File(s) Summary
Configuration validation contract
hindsight-api-slim/hindsight_api/config_resolver.py
ConfigResolver adds non-persisting validation that normalizes updates, checks permissions and field values, and is reused by persistence.
API and MCP preflight checks
hindsight-api-slim/hindsight_api/api/http.py, hindsight-api-slim/hindsight_api/mcp_tools.py
Bank update, template import, and MCP flows validate operations and configuration before mutations, while the config endpoint rereads stored state for its response.

Reranker bank attribution

Layer / File(s) Summary
Attribution configuration and helper
hindsight-api-slim/hindsight_api/config.py, hindsight-api-slim/hindsight_api/engine/bank_attribution.py
A new opt-in setting controls generation of X-Hindsight-Bank-Id from the current bank context.
Per-request rerank header
hindsight-api-slim/hindsight_api/engine/cross_encoder.py
The Cohere-compatible rerank client adds attribution headers to each predict() request.
Attribution coverage and documentation
hindsight-api-slim/tests/*, .env.example, hindsight-embed/hindsight_embed/env.example, hindsight-docs/docs/developer/configuration.md
Tests cover flag parsing, header presence, required headers, absent bank context, and per-request values; examples and documentation describe the setting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RerankCaller
  participant BankContext
  participant CohereCompatibleRerankClient
  participant RemoteRerankEndpoint
  RerankCaller->>BankContext: bind current bank id
  CohereCompatibleRerankClient->>BankContext: read attribution context
  BankContext-->>CohereCompatibleRerankClient: X-Hindsight-Bank-Id header
  CohereCompatibleRerankClient->>RemoteRerankEndpoint: POST /rerank with headers
Loading
sequenceDiagram
  participant BankUpdateAPI
  participant ConfigResolver
  participant BankStore
  BankUpdateAPI->>ConfigResolver: validate bank operations and updates
  ConfigResolver-->>BankUpdateAPI: normalized updates
  BankUpdateAPI->>BankStore: persist validated updates
  BankStore-->>BankUpdateAPI: return latest stored configuration
Loading

Suggested reviewers: nicoloboschi, sireika

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: opt-in per-bank attribution for remote reranker requests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2729-featapi-optin-perbank-attribution-for-remote

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jul 16, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an opt-in configuration (HINDSIGHT_API_RERANKER_SEND_BANK_AS_HEADER) to tag outbound Cohere-compatible remote rerank requests with X-Hindsight-Bank-Id for per-bank cost attribution. It also refactors bank configuration updates to pre-validate payloads and permissions before executing any side effects across several API endpoints and MCP tools. The review feedback highlights a redundancy where validate_bank_config_updates is called first, but the subsequent call to update_bank_config runs the exact same validation again, and suggests refactoring to avoid this duplicate processing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +438 to +455
async def update_bank_config(
self, bank_id: str, updates: dict[str, Any], context: RequestContext | None = None
) -> None:
"""
Update bank configuration overrides (with permission checking).

Args:
bank_id: Bank identifier
updates: Dict of config field names to new values.
Keys can be in env var format (HINDSIGHT_API_LLM_PROVIDER)
or Python field format (llm_provider).
Only configurable fields are allowed.
context: Request context for permission checking

Raises:
ValueError: If attempting to override invalid/disallowed fields
"""
normalized_updates = await self.validate_bank_config_updates(bank_id, updates, context)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

With the introduction of validate_bank_config_updates, several endpoints (e.g., api_create_or_update_bank, api_update_bank, api_import_bank_template, api_update_bank_config, and _do_update_bank in mcp_tools.py) now call validate_bank_config_updates first to validate the payload before any side effects, and then call update_bank_config which runs the exact same validation again.

To avoid this redundant validation and potential extra database/cache lookups, consider splitting the persistence logic out of update_bank_config into a separate method (e.g., _persist_bank_config_updates), or allowing update_bank_config to accept pre-validated/normalized updates.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@hindsight-api-slim/hindsight_api/api/http.py`:
- Around line 5661-5663: Wrap the validate_bank_config_updates call in both bank
update handlers with ValueError handling and map validation or permission
failures to HTTP 400, matching the existing config and import endpoint behavior.
Apply this at hindsight-api-slim/hindsight_api/api/http.py lines 5661-5663 and
5730-5732, preserving validation before any side effects.
- Around line 5861-5869: Extend the pre-validation block around
_verify_operation to also validate the update operations used by the import flow
for existing mental models and directives. When body.mental_models or
body.directives is present, check the corresponding update permission in
addition to the existing create permission checks, so all possible mutations are
authorized before changes begin.

In `@hindsight-api-slim/hindsight_api/config_resolver.py`:
- Line 455: Update the compound bank-update flow around
validate_bank_config_updates to persist its returned normalized_updates directly
instead of invoking asynchronous validation again. Add or reuse a
persistence-only path that accepts the prevalidated result, and ensure the
bank-name mutation and normalized configuration update commit atomically so a
persistence failure cannot leave a partial name change.
- Around line 466-468: Wrap the bank initialization, row fetch, overlay
computation, and final update block in an explicit transaction using
self._backend.transaction() or the acquired connection’s transaction context.
Ensure ensure_bank_exists() and the UPDATE execute within that same transaction,
and revise the nearby comment to accurately describe the consolidated steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d096bd3f-4f91-43e3-ba42-efe31e458188

📥 Commits

Reviewing files that changed from the base of the PR and between 016b5f0 and d5d761e.

📒 Files selected for processing (11)
  • .env.example
  • hindsight-api-slim/hindsight_api/api/http.py
  • hindsight-api-slim/hindsight_api/config.py
  • hindsight-api-slim/hindsight_api/config_resolver.py
  • hindsight-api-slim/hindsight_api/engine/bank_attribution.py
  • hindsight-api-slim/hindsight_api/engine/cross_encoder.py
  • hindsight-api-slim/hindsight_api/mcp_tools.py
  • hindsight-api-slim/tests/test_bank_attribution_config.py
  • hindsight-api-slim/tests/test_reranker_bank_attribution.py
  • hindsight-docs/docs/developer/configuration.md
  • hindsight-embed/hindsight_embed/env.example

Comment on lines +5661 to +5663
if config_updates:
# Validate config payload and permissions BEFORE any side effects
await app.state.memory._config_resolver.validate_bank_config_updates(bank_id, config_updates, request_context)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Map preflight configuration failures to a client error.

Both handlers now invoke validate_bank_config_updates, but neither catches its ValueError, so invalid or permission-denied config requests return HTTP 500.

  • hindsight-api-slim/hindsight_api/api/http.py#L5661-L5663: catch ValueError and return HTTP 400, consistent with the config and import endpoints.
  • hindsight-api-slim/hindsight_api/api/http.py#L5730-L5732: apply the same mapping to PATCH bank updates.
📍 Affects 1 file
  • hindsight-api-slim/hindsight_api/api/http.py#L5661-L5663 (this comment)
  • hindsight-api-slim/hindsight_api/api/http.py#L5730-L5732
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hindsight-api-slim/hindsight_api/api/http.py` around lines 5661 - 5663, Wrap
the validate_bank_config_updates call in both bank update handlers with
ValueError handling and map validation or permission failures to HTTP 400,
matching the existing config and import endpoint behavior. Apply this at
hindsight-api-slim/hindsight_api/api/http.py lines 5661-5663 and 5730-5732,
preserving validation before any side effects.

Comment on lines +5861 to +5869
if hasattr(app.state.memory, "_verify_operation"):
# Pre-validate all requested operations before making any changes
await app.state.memory._verify_operation(bank_id, "get_bank_profile", request_context)
if body.bank:
await app.state.memory._verify_operation(bank_id, "update_bank_config", request_context)
if body.mental_models:
await app.state.memory._verify_operation(bank_id, "create_mental_model", request_context)
if body.directives:
await app.state.memory._verify_operation(bank_id, "create_directive", request_context)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C5 'def apply_bank_template_manifest|async def apply_bank_template_manifest' .
rg -n -C4 '\b(create|update)_(mental_model|directive)\s*\(' hindsight-api-slim/hindsight_api

Repository: chethanuk/hindsight

Length of output: 23787


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate permission/operation validation logic and the relevant operation names.
rg -n -C3 '_verify_operation|create_mental_model|update_mental_model|create_directive|update_directive|OperationValidationError|permission' hindsight-api-slim/hindsight_api | sed -n '1,260p'

Repository: chethanuk/hindsight

Length of output: 21951


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the route area around the preflight checks and the import helper.
sed -n '2440,2565p' hindsight-api-slim/hindsight_api/api/http.py
printf '\n----\n'
sed -n '5840,5875p' hindsight-api-slim/hindsight_api/api/http.py

Repository: chethanuk/hindsight

Length of output: 7376


Preflight the update paths too. The import flow updates existing mental models and directives, but this guard only checks create_mental_model and create_directive, so a request can partially mutate the bank before an update permission failure aborts the rest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hindsight-api-slim/hindsight_api/api/http.py` around lines 5861 - 5869,
Extend the pre-validation block around _verify_operation to also validate the
update operations used by the import flow for existing mental models and
directives. When body.mental_models or body.directives is present, check the
corresponding update permission in addition to the existing create permission
checks, so all possible mutations are authorized before changes begin.

Raises:
ValueError: If attempting to override invalid/disallowed fields
"""
normalized_updates = await self.validate_bank_config_updates(bank_id, updates, context)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Persist the prevalidated result without validating again.

HTTP and MCP callers validate first, mutate the bank name, then this method repeats asynchronous permission/config validation. If the second pass rejects, the name remains changed and the compound update is only partially applied. Add a persistence path accepting the returned normalized_updates, or make the complete update atomic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hindsight-api-slim/hindsight_api/config_resolver.py` at line 455, Update the
compound bank-update flow around validate_bank_config_updates to persist its
returned normalized_updates directly instead of invoking asynchronous validation
again. Add or reuse a persistence-only path that accepts the prevalidated
result, and ensure the bank-name mutation and normalized configuration update
commit atomically so a persistence failure cannot leave a partial name change.

Comment on lines +466 to +468
# We explicitly execute the initial ensure_bank_exists call in a transaction to safely handle the
# ON CONFLICT DO NOTHING insertion, then fetch the updated row and compute the overlay,
# then finally update in the same transaction to prevent race conditions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'database|backend|fact_storage' hindsight-api-slim/hindsight_api |
  xargs -r rg -n -C4 'def acquire|async def acquire|ensure_bank_exists|conn\.transaction'

Repository: chethanuk/hindsight

Length of output: 1003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and inspect the surrounding implementations.
printf '\n## config_resolver.py around referenced lines\n'
sed -n '430,500p' hindsight-api-slim/hindsight_api/config_resolver.py

printf '\n## fact_storage.py around ensure_bank_exists\n'
sed -n '135,240p' hindsight-api-slim/hindsight_api/engine/retain/fact_storage.py

printf '\n## Search for DatabaseBackend acquire implementation\n'
rg -n -C4 'class DatabaseBackend|def acquire|async def acquire|transaction\(' hindsight-api-slim/hindsight_api

Repository: chethanuk/hindsight

Length of output: 50375


Wrap this block in a transaction
self._backend.acquire() only opens a connection; it does not start a transaction. ensure_bank_exists() and the UPDATE can commit separately, so use async with self._backend.transaction() as conn: (or async with conn.transaction():) here and trim the comment to match the actual steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hindsight-api-slim/hindsight_api/config_resolver.py` around lines 466 - 468,
Wrap the bank initialization, row fetch, overlay computation, and final update
block in an explicit transaction using self._backend.transaction() or the
acquired connection’s transaction context. Ensure ensure_bank_exists() and the
UPDATE execute within that same transaction, and revise the nearby comment to
accurately describe the consolidated steps.

Comment on lines +466 to +468
# We explicitly execute the initial ensure_bank_exists call in a transaction to safely handle the
# ON CONFLICT DO NOTHING insertion, then fetch the updated row and compute the overlay,
# then finally update in the same transaction to prevent race conditions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The new comment states this block runs in a single transaction to prevent race conditions, but no transaction is actually opened here (only a pooled connection is acquired). That mismatch is dangerous because readers will assume atomicity that is not guaranteed; either wrap the sequence in an explicit transaction (conn.transaction()) or correct the comment to avoid documenting behavior that the code does not provide. [comment mismatch]

Severity Level: Major ⚠️
- ⚠️ Bank config PATCH handler not truly transactional as documented.
- ⚠️ Concurrent config updates may interleave despite transaction comment.
- ⚠️ Future maintainers may rely on nonexistent race protection.
Steps of Reproduction ✅
1. Open `hindsight-api-slim/hindsight_api/config_resolver.py` and locate
`update_bank_config()`; see the comment at lines 466-468 stating the sequence runs "in a
transaction" to prevent race conditions.

2. Inspect the context in `update_bank_config()`: the comment sits above `async with
self._backend.acquire() as conn:` and the calls to `ensure_bank_exists(conn, bank_id,
ops=self._backend.ops)` and `conn.execute(...)` (lines 465-470).

3. Check the backend implementation in
`hindsight-api-slim/hindsight_api/engine/db/base.py:11-33`, where `acquire()` is defined
as an async context manager that "Acquire[s] a connection from the pool" and yields a
`DatabaseConnection` wrapper without starting a transaction.

4. Verify the concrete PostgreSQL backend in
`hindsight-api-slim/hindsight_api/engine/db/postgresql.py:4-15`: `acquire()` (lines 4-8)
wraps `pool.acquire()` only, while `transaction()` (lines 10-15) explicitly uses `async
with conn.transaction()` to start a database transaction, demonstrating that
`update_bank_config()` currently uses a non-transactional acquire despite the
transactional comment.

5. Trace a real caller: the bank config update HTTP handler in
`hindsight-api-slim/hindsight_api/api/http.py:6328-21` calls `await
app.state.memory._config_resolver.update_bank_config(bank_id, request.updates,
request_context)` whenever the bank config PATCH endpoint is hit, meaning each live config
update relies on this non-transactional sequence even though the comment claims
transaction-based race protection.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hindsight-api-slim/hindsight_api/config_resolver.py
**Line:** 466:468
**Comment:**
	*Comment Mismatch: The new comment states this block runs in a single transaction to prevent race conditions, but no transaction is actually opened here (only a pooled connection is acquired). That mismatch is dangerous because readers will assume atomicity that is not guaranteed; either wrap the sequence in an explicit transaction (`conn.transaction()`) or correct the comment to avoid documenting behavior that the code does not provide.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +3240 to +3241
if mission is not None and "reflect_mission" not in effective_config:
effective_config["reflect_mission"] = mission

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The precedence check only looks for the Python-style key reflect_mission, so if callers provide the equivalent env-style key (for example HINDSIGHT_API_REFLECT_MISSION) and also pass mission, the alias value is appended and then wins during normalization. This silently overrides the explicit config_updates value with the deprecated alias; normalize keys first (or check both key forms) before applying the mission fallback. [incorrect condition logic]

Severity Level: Major ⚠️
- ⚠️ MCP update_bank tool may ignore env-style reflect_mission.
- ⚠️ Mixed env/Python reflect_mission overrides resolve to deprecated alias.
- ⚠️ Bank reflect behavior may not match explicit config_updates.
Steps of Reproduction ✅
1. Create an MCP server using the helper `_make_mcp_server` defined in
`hindsight-api-slim/tests/test_mcp_tools.py:979-989`, which calls `register_mcp_tools(mcp,
mock_memory, config)` and registers the `update_bank` tool implemented via
`_do_update_bank` in `hindsight-api-slim/hindsight_api/mcp_tools.py:3221-3320`.

2. Invoke the registered MCP tool function for `update_bank` (obtained via
`_tools(mcp)["update_bank"].fn` from `tests/test_mcp_tools.py:500-505`) with both an
env-style key and the deprecated alias, for example:

   `await _tools(mcp)["update_bank"].fn(mission="alias-mission",
   config_updates={"HINDSIGHT_API_REFLECT_MISSION": "explicit-mission"})`.

3. Inside `_do_update_bank` (`hindsight_api/mcp_tools.py:3221-3242`), `effective_config`
is initialized from `config_updates`, so it contains the key
`"HINDSIGHT_API_REFLECT_MISSION"` but not `"reflect_mission"`. Because the condition at
line 3240 only checks for `"reflect_mission"` in `effective_config`, it evaluates true and
line 3241 writes `effective_config["reflect_mission"] = mission`, adding a second key for
the same conceptual setting.

4. `_do_update_bank` then calls
`memory._config_resolver.validate_bank_config_updates(target_bank, effective_config,
request_context)` at lines 3253-3255, which in turn invokes `normalize_config_dict` in
`hindsight-api-slim/hindsight_api/config_resolver.py:15-24` and
`hindsight-api-slim/hindsight_api/config.py:86-107`. Both
`"HINDSIGHT_API_REFLECT_MISSION"` and `"reflect_mission"` normalize to
`"reflect_mission"`, and due to dict overwrite semantics, the later alias-derived value
from `mission` overwrites the earlier env-style `config_updates` value, so the final
update uses `"alias-mission"` instead of the explicitly provided `"explicit-mission"`.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hindsight-api-slim/hindsight_api/mcp_tools.py
**Line:** 3240:3241
**Comment:**
	*Incorrect Condition Logic: The precedence check only looks for the Python-style key `reflect_mission`, so if callers provide the equivalent env-style key (for example `HINDSIGHT_API_REFLECT_MISSION`) and also pass `mission`, the alias value is appended and then wins during normalization. This silently overrides the explicit `config_updates` value with the deprecated alias; normalize keys first (or check both key forms) before applying the `mission` fallback.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +3253 to +3254
if effective_config:
await memory._config_resolver.validate_bank_config_updates(target_bank, effective_config, request_context)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: This introduces a hard dependency on a private async method on memory._config_resolver; implementations/mocks that only provide update_bank_config will now fail with AttributeError or TypeError when this await runs. Use a capability check/fallback (or rely on update_bank_config’s built-in validation) so _do_update_bank does not require an extra private API contract. [api mismatch]

Severity Level: Major ⚠️
- ❌ MCP update_bank tests crash with AttributeError on validation call.
- ⚠️ Custom MemoryEngine mocks without validation method break MCP tools.
- ⚠️ MCP integration tightly couples to private ConfigResolver API.
Steps of Reproduction ✅
1. The `mock_memory` fixture in `hindsight-api-slim/tests/test_mcp_tools.py:124-203`
constructs a `MagicMock` to stand in for the memory engine; it assigns
`memory._config_resolver = MagicMock()` and only defines
`memory._config_resolver.update_bank_config = AsyncMock()`, without providing any
`validate_bank_config_updates` attribute.

2. `_make_mcp_server` in `tests/test_mcp_tools.py:979-989` calls `register_mcp_tools(mcp,
mock_memory, config)`, which wires the `update_bank` MCP tool to the shared implementation
`_do_update_bank` defined in `hindsight-api-slim/hindsight_api/mcp_tools.py:3221-3269`.

3. Tests such as `test_update_bank_with_bank_id_override` in
`tests/test_mcp_tools.py:1720-1729` invoke the tool via
`_tools(mcp)["update_bank"].fn(config_updates={"reflect_mission": "Test"},
bank_id="other-bank")`, causing `_do_update_bank` to execute with a non-empty
`effective_config`.

4. When execution reaches the new validation block at
`hindsight-api-slim/hindsight_api/mcp_tools.py:3252-3255`, the condition `if
effective_config:` is true and Python evaluates `await
memory._config_resolver.validate_bank_config_updates(...)`. Because the mock
`_config_resolver` from step 1 has no `validate_bank_config_updates` attribute, this
raises an `AttributeError` at runtime, breaking all such test paths (and any real caller
whose engine mock or alternative implementation only provides
`_config_resolver.update_bank_config` but not `validate_bank_config_updates`).

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hindsight-api-slim/hindsight_api/mcp_tools.py
**Line:** 3253:3254
**Comment:**
	*Api Mismatch: This introduces a hard dependency on a private async method on `memory._config_resolver`; implementations/mocks that only provide `update_bank_config` will now fail with `AttributeError` or `TypeError` when this `await` runs. Use a capability check/fallback (or rely on `update_bank_config`’s built-in validation) so `_do_update_bank` does not require an extra private API contract.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

ebarkhordar and others added 8 commits July 17, 2026 09:58
…rize-io#2759)

tool_expand zipped memory_ids against valid_uuids, which only collects the
ids that parsed as UUIDs. One invalid id shifts every later pair by one, so
a memory comes back stamped with a different memory's id, and zip truncates
the tail so the last requested id gets no entry at all.

Key each id to its own UUID and iterate memory_ids directly, so an invalid
id can only affect its own entry.
…unters (vectorize-io#2758)

The OpenAI-compatible provider extracts cached_tokens and thoughts_tokens on
both call paths and hands them to TokenUsage, but never passes them to
metrics.record_llm_call, which accepts and buckets both. Two separate effects:

- Reasoning tokens reach no counter at all. vectorize-io#2378 made output_tokens
  visible-only by subtracting thoughts_tokens directly above the
  record_llm_call, so the reasoning half of the billed output was removed
  from the metrics path rather than moved onto llm_tokens_thoughts. Before
  vectorize-io#2378 those tokens were still counted inside output_tokens.
- cached_input_tokens has read 0 for every OpenAI-compatible provider since
  the counter was added; only gemini_llm passes it.

Pass both kwargs at the two call sites that parse a usage object. The
fallback path (no usage) and the Ollama native path (no reasoning or cached
fields) are unchanged.

Invariant: recorded output_tokens + recorded thoughts_tokens equals the
provider's completion_tokens, so every billed token lands on exactly one
counter. The new tests assert on the collector itself; the existing ones
patch it without asserting, which is why this went unnoticed.
…io#2764)

Bind Reflect to the existing per-bank ContextVar so its tool loop and final synthesis preserve provider cost attribution. Replace two direct ContextVar implementation tests with one integrated Reflect binding/reset regression.
…t MagicMock crash (vectorize-io#2776)

test_null_content_recovers_on_retry failed intermittently on the test-api
shard with:

  hindsight_api/metrics.py:591: TypeError: '>' not supported between
  instances of 'MagicMock' and 'int'   (if cached_input_tokens > 0)

The mock in _make_chat_response set completion_tokens_details but not the
cached-token fields, so the cached-token extraction
(openai_compatible_llm.py:948 `response_usage.cached_tokens`, and the
prompt_tokens_details path) read an auto-MagicMock and passed it to the
metrics recorder. It only surfaced when the metrics path actually ran —
which depends on telemetry state that leaks across pytest-xdist workers —
so it presented as an intermittent, co-scheduling-dependent failure rather
than a deterministic one.

Set usage.cached_tokens = 0 and usage.prompt_tokens_details = None so both
extraction paths yield int 0. Verified: both tests pass and both paths
return int 0 (no MagicMock reaches the `> 0` comparison).
…y) (vectorize-io#2727)

Clears the pydantic-settings Dependabot alert across all affected
manifests plus the three high-severity alerts in the root lock.

  pydantic-settings 2.12.0/2.14.0/2.14.1 -> 2.14.2  GHSA-4xgf-cpjx-pc3j
  transformers      5.3.0  -> 5.12.1               GHSA-fgcw-684q-jj6r
  soupsieve         2.8    -> 2.8.4                GHSA-2wc2-fm75-p42x
                                                   GHSA-836r-79rf-4m37

pydantic-settings is transitive everywhere (no direct declaration), so
the locks are the only lever. crewai is deliberately left at 2.10.1: the
advisory's range is >=2.12.0,<2.14.2 and NestedSecretsSettingsSource did
not exist in 2.10.x, so it is unaffected.

transformers is a direct dep, and hindsight-api is published, so the
declared floor -- not our lock -- is what protects installers of the
local-ml/local-onnx extras. The old >=4.53.0 floor resolved to 4.57.6
(vulnerable) under any downstream cap of transformers<5, so raise it to
the advisory's first patched version. Note this now fails resolution for
consumers pinned below transformers 5 rather than silently installing a
vulnerable build. The >=4.53.0 floor was already unreachable in practice:
4.53.0 requires tokenizers<0.22, which our own cap excludes.

The tokenizers<=0.23.0 cap is kept. vectorize-io#2055 was caused by transformers
declaring a wider tokenizers range in metadata than its import-time check
enforces, and the cap is what blocks that; the comment now records this
so it does not read as removable.

Root uv.lock is reformatted from lock revision 1 to 3 because uv rewrites
in its current format whenever it writes. The other 32 locks in the repo
are already revision 3 and CI's setup-uv is unpinned, so this aligns root
rather than drifting it. Only 3 versions actually change.

Verified: local-ml sync resolves tokenizers 0.22.2 under transformers
5.12.1; LocalSTEmbeddings and LocalSTCrossEncoder both initialize and run
(the vectorize-io#2055 import path). Lint passes.
@chethanuk
chethanuk force-pushed the fix/issue-2729-featapi-optin-perbank-attribution-for-remote branch from d5d761e to 83e4c46 Compare July 17, 2026 15:37
@codeant-ai

codeant-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

CodeAnt AI is running Incremental review

@codeant-ai

codeant-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Jul 17, 2026
@codeant-ai

codeant-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

CodeAnt AI Incremental review completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(api): opt-in per-bank attribution for remote reranker requests

5 participants