Skip to content

feat: add cosine similarity crowding alerts - #1497

Merged
stranske merged 6 commits into
mainfrom
codex/issue-1469-peer-similarity-cosine-crowding
Jul 31, 2026
Merged

feat: add cosine similarity crowding alerts#1497
stranske merged 6 commits into
mainfrom
codex/issue-1469-peer-similarity-cosine-crowding

Conversation

@stranske

@stranske stranske commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #1469

Summary

  • store embedding-cosine scores alongside Jaccard manager similarity
  • expose selectable basis=jaccard|cosine peer ranking
  • qualify crowded-trade alerts by a configurable similar-manager threshold

Validation

  • .venv/bin/python -m pytest -q tests/test_manager_similarity_flow.py tests/test_alert_engine.py tests/test_conviction_flow.py tests/test_schema.py
  • .venv/bin/python -m ruff check ...
  • .venv/bin/python -m black --target-version py313 --fast --check ...
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added cosine similarity alongside Jaccard similarity for comparing managers.
    • Similar-manager results can now be ranked by either metric, with the selected basis and scores displayed.
    • Added crowded-trade alerts based on the number of similar managers.
    • Crowded-trade events now include related managers, counts, and the similarity threshold.
  • Bug Fixes

    • Invalid, missing, or non-finite similarity scores are excluded from results.
    • Added validation for unsupported similarity ranking options.
  • Tests

    • Expanded coverage for similarity calculations, ranking, filtering, and crowded-trade alerts.

@stranske stranske added agent:codex autofix Triggers autofix on PR agents:keepalive Enables keepalive automation for PR agent:retry Add to trigger agent retry after rate limit or pause labels Jul 31, 2026
@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@stranske
stranske temporarily deployed to agent-standard July 31, 2026 13:14 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds cosine similarity storage and ranking, extends the similar-managers API, and adds similarity-based crowded-trade alert metadata and condition evaluation.

Changes

Similarity and crowding alert flow

Layer / File(s) Summary
Similarity storage and computation
schema.sql, alembic/versions/017_manager_similarity_cosine.py, etl/manager_similarity_flow.py, tests/test_manager_similarity_flow.py
The similarity table stores nullable cosine scores. The ETL computes cosine similarity from manager security vectors and maintains compatibility with existing SQLite schemas. Tests cover valid, zero-vector, non-finite, and endpoint cases.
Similarity basis API
api/managers.py, tests/test_manager_similarity_flow.py
The endpoint accepts jaccard or cosine, filters invalid scores, applies the limit after filtering, and returns both metrics with the selected basis and score.
Crowding alert evaluation
.env.example, alerts/models.py, alerts/engine.py, etl/conviction_flow.py, tests/test_alert_engine.py, tests/test_conviction_flow.py, tests/test_crowded_contrarian.py
Crowded-trade payloads now include similar manager IDs, counts, and the configured score floor. The alert engine evaluates similar_manager_count_gte, with tests for thresholds and dispatched payloads.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SimilarManagersAPI
  participant manager_similarity
  Client->>SimilarManagersAPI: Request similar managers with basis
  SimilarManagersAPI->>manager_similarity: Query selected similarity score
  manager_similarity-->>SimilarManagersAPI: Return ranked similarity rows
  SimilarManagersAPI-->>Client: Return basis, score, metrics, and counts
Loading
sequenceDiagram
  participant conviction_flow
  participant manager_similarity
  participant alert_engine
  conviction_flow->>manager_similarity: Find managers above similarity threshold
  manager_similarity-->>conviction_flow: Return connected manager IDs
  conviction_flow->>alert_engine: Submit crowded-trade payload
  alert_engine-->>conviction_flow: Evaluate similar_manager_count_gte
Loading

Possibly related PRs

Suggested labels: verify:compare

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding cosine-similarity crowding alerts.
Linked Issues check ✅ Passed The changes implement cosine similarity, selectable ranking, crowded-trade alert thresholds, persistence, migrations, and required tests for issue #1469.
Out of Scope Changes check ✅ Passed The changes remain within issue #1469 scope and support cosine similarity, crowding alerts, persistence, migration, configuration, and tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-1469-peer-similarity-cosine-crowding

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

@stranske
stranske force-pushed the codex/issue-1469-peer-similarity-cosine-crowding branch from 93c953f to 466a893 Compare July 31, 2026 13:27

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_alert_engine.py (1)

134-150: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a below-threshold assertion.

Keep the equality case, then evaluate the same rule with similar_manager_count: 2 and assert that it does not fire. This verifies the lower boundary for similar_manager_count_gte.

As per path instructions, flag new behavior without sufficient test coverage.

🤖 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 `@tests/test_alert_engine.py` around lines 134 - 150, Add a second evaluation
alongside the existing crowded-trade equality case, reusing the same rule and
event context but setting payload similar_manager_count to 2; assert that this
below-threshold event does not fire, while preserving the current equality
assertion for value 3.

Source: Path instructions

🤖 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 `@alerts/engine.py`:
- Around line 86-90: Update the similar_manager_count_gte handling in the alert
evaluation flow to convert expected to a finite float instead of int(expected),
then compare the integer count against that fractional threshold so a count of 3
does not satisfy 3.9. Preserve the existing false result for missing or
below-threshold counts and the continue behavior for valid matches.

In `@api/managers.py`:
- Around line 1355-1357: Add an HTTP-level test for the endpoint exposing the
basis query parameter, alongside the existing valid jaccard and cosine cases.
Send a request with an invalid basis value and assert that the response status
is 422, preserving coverage of the new validation contract.
- Around line 1373-1401: The SQL query in the manager similarity fetch does not
filter out non-finite values (NaN, Infinity) that PostgreSQL can store, causing
these values to rank above finite results and eventually fail during JSON
serialization. Update the WHERE clause condition in the query to exclude
non-finite values for both the score_column (cosine or jaccard) and the cosine
column using PostgreSQL's IS FINITE function, ensuring only valid numeric values
are returned before the serialization step in the return statement.

In `@etl/manager_similarity_flow.py`:
- Around line 14-23: Update cosine_similarity to reject non-finite values in
both input vectors before calculating the denominator, and validate the
denominator and computed score with math.isfinite before returning. Preserve the
existing None behavior for mismatched, empty, zero-magnitude, or non-finite
inputs and results.

In `@tests/test_conviction_flow.py`:
- Around line 59-60: Add a test in the existing _similar_manager_ids coverage
using a pair whose Jaccard similarity is exactly the supplied minimum_score, and
assert that the pair is included. Preserve the current below-threshold assertion
while explicitly verifying the inclusive jaccard >= minimum_score boundary.

In `@tests/test_crowded_contrarian.py`:
- Around line 320-323: Extend the post-dispatch assertions in the crowded-trade
test to validate the event payload’s similar_manager_ids, similar_manager_count,
and similarity_floor fields against the seeded similarity data. Use the existing
dispatched crowded-trade event assertion path and preserve the current
assertions.

---

Outside diff comments:
In `@tests/test_alert_engine.py`:
- Around line 134-150: Add a second evaluation alongside the existing
crowded-trade equality case, reusing the same rule and event context but setting
payload similar_manager_count to 2; assert that this below-threshold event does
not fire, while preserving the current equality assertion for value 3.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d5b44145-a223-493e-86b1-574c7f49f745

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef858c and 466a893.

📒 Files selected for processing (12)
  • .env.example
  • alembic/versions/016_manager_similarity_cosine.py
  • alerts/engine.py
  • alerts/models.py
  • api/managers.py
  • etl/conviction_flow.py
  • etl/manager_similarity_flow.py
  • schema.sql
  • tests/test_alert_engine.py
  • tests/test_conviction_flow.py
  • tests/test_crowded_contrarian.py
  • tests/test_manager_similarity_flow.py

Comment thread alerts/engine.py
Comment thread api/managers.py
Comment thread api/managers.py
Comment thread etl/manager_similarity_flow.py Outdated
Comment thread tests/test_conviction_flow.py Outdated
Comment thread tests/test_crowded_contrarian.py
The activism campaign migration merged to main as revision 016, so this
branch's cosine migration collided on the same identifier and Alembic
reported two heads named 016.
@stranske

Copy link
Copy Markdown
Owner Author

Closer lane: fixed the Gate failure introduced by the rebase onto merged #1496.

Failure: Python CI / python 3.12 and 3.13 failed 5 tests in tests/test_schema.py with alembic.util.exc.CommandError: Multiple head revisions are present for given argument 'head'; ... 016, 016 (run 30634495422). Merged #1496 landed the activism migration as revision 016, and this branch's cosine migration also declared revision = "016" / down_revision = "015", so the two collided as duplicate heads.

Fix (a11170b): renamed alembic/versions/016_manager_similarity_cosine.py017_manager_similarity_cosine.py and set revision = "017" / down_revision = "016" so the cosine column stacks on top of the merged activism tables. No schema semantics changed.

Validation

  • Alembic head resolution is single again: ScriptDirectory.get_heads() = ['017'], chain 017 <- 016 <- 015 <- ... <- 001.
  • python3 -m pytest -o addopts= tests/test_schema.py tests/test_crowded_contrarian.py tests/test_manager_similarity_flow.py tests/test_env_example_coverage.py34 passed (includes all 5 previously failing test_schema.py cases).
  • ruff check clean, black --check clean, scripts/check_dialect_portability.py exit 0.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Runner dispatch state for codex on PR #1497. Do not edit.

@agents-workflows-bot

agents-workflows-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🤖 Keepalive Loop Status

PR #1497 | Agent: Codex | Iteration 0/12

Current State

Metric Value
Iteration progress [----------] 0/12
Action run (agent-run-failed)
Agent status ❌ AGENT FAILED
Gate unknown
Tasks 0/7 complete
Timeout 45 min (default)
Timeout usage 8m elapsed (18%, 37m remaining)
Keepalive ✅ enabled
Autofix ❌ disabled

Last Codex Run

Result Value
Status ❌ AGENT FAILED
Reason agent-run-failed
Exit code unknown
Failures 2/3 before pause

To retry immediately:

  • Add the agent:retry label to this PR

Or wait for the next successful Gate run to automatically retry.

🔍 Failure Classification

| Error type | infrastructure |
| Error category | transient |
| Suggested recovery | Capture logs and context; retry once and escalate if the issue persists. |

⚠️ Failure Tracking

| Consecutive failures | 2/3 |
| Reason | agent-run-failed |

@stranske-keepalive

stranske-keepalive Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
Keepalive Work Log (click to expand)
# Time (UTC) Agent Action Result Files Tasks Progress Commit Gate
0 2026-07-31 14:06:44 Codex run (agent-run-failed) failure 31 file(s) 0 0/7 4e574c4 success
0 2026-07-31 14:11:34 Codex run (agent-run-failed) failure 31 file(s) 0 0/7 5303045

@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: 1

🤖 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 `@langsmith-fleet-worker-attempt.json`:
- Line 4: Remove the langsmith-fleet-worker-attempt.json file from the
repository, as it declares the langsmith-fleet/v1 schema but lacks the required
contract fields (recorded_at, schema_version, run_id, and status). If any tests
depend on this artifact with its current shape, relocate it to a test fixtures
directory instead of keeping it at the repository root.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 80c4ea28-f773-4b57-88d3-a122bb9f30f8

📥 Commits

Reviewing files that changed from the base of the PR and between 4e574c4 and 5303045.

📒 Files selected for processing (1)
  • langsmith-fleet-worker-attempt.json

Comment thread langsmith-fleet-worker-attempt.json Outdated
@stranske

Copy link
Copy Markdown
Owner Author

Addressed the seven CodeRabbit findings in f6a0b97. Similar-manager thresholds now preserve fractional values; cosine generation and API responses reject non-finite scores; the endpoint, inclusive-boundary, and alert-payload contracts are covered; and the malformed generated worker-attempt artifact was removed. The invalid-basis regression asserts this APIs established validation mapping (400). Validation: 37 focused tests passed; Ruff, Black, and git diff --check passed.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
etl/manager_similarity_flow.py (1)

89-99: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a flow-level test for persisted cosine scores.

The current cosine test calls cosine_similarity directly. The API test seeds cosine values manually. Neither test verifies that compute_manager_similarity stores the computed cosine value in manager_similarity.

Add a deterministic flow test that runs the rebuild and asserts the stored cosine score for a known manager pair.

As per path instructions, flag “new or changed behavior with no accompanying test.”

🤖 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 `@etl/manager_similarity_flow.py` around lines 89 - 99, Update the similarity
flow tests around compute_manager_similarity to add a deterministic rebuild test
using known manager vectors, then query manager_similarity for the expected
manager pair and assert the persisted cosine score matches the computed value.
Keep existing direct cosine and API tests intact while covering the end-to-end
database persistence path.

Source: Path instructions

api/managers.py (1)

1375-1410: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Do not load every peer row before applying limit.

This query loads all similarity rows for the manager, then filters and limits them in Python. A manager with many peers can cause large database reads and allocations for a request that returns at most 100 items.

Filter finite scores in dialect-specific SQL, or retrieve bounded pages until the endpoint collects limit finite rows. Add coverage where invalid rows precede valid rows.

🤖 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 `@api/managers.py` around lines 1375 - 1410, Update the peer-similarity
retrieval around the query and finite_rows construction so the database does not
load every matching row before enforcing limit. Apply finite-score filtering in
dialect-specific SQL, or fetch bounded pages until limit valid rows are
collected, while preserving score and overlap ordering; add coverage for invalid
rows preceding valid results.
🤖 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.

Outside diff comments:
In `@api/managers.py`:
- Around line 1375-1410: Update the peer-similarity retrieval around the query
and finite_rows construction so the database does not load every matching row
before enforcing limit. Apply finite-score filtering in dialect-specific SQL, or
fetch bounded pages until limit valid rows are collected, while preserving score
and overlap ordering; add coverage for invalid rows preceding valid results.

In `@etl/manager_similarity_flow.py`:
- Around line 89-99: Update the similarity flow tests around
compute_manager_similarity to add a deterministic rebuild test using known
manager vectors, then query manager_similarity for the expected manager pair and
assert the persisted cosine score matches the computed value. Keep existing
direct cosine and API tests intact while covering the end-to-end database
persistence path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 56707971-4725-42c2-aa48-8262bbff9b02

📥 Commits

Reviewing files that changed from the base of the PR and between 5303045 and f6a0b97.

📒 Files selected for processing (7)
  • alerts/engine.py
  • api/managers.py
  • etl/manager_similarity_flow.py
  • tests/test_alert_engine.py
  • tests/test_conviction_flow.py
  • tests/test_crowded_contrarian.py
  • tests/test_manager_similarity_flow.py

@stranske
stranske merged commit 872569b into main Jul 31, 2026
33 checks passed
@stranske
stranske deleted the codex/issue-1469-peer-similarity-cosine-crowding branch July 31, 2026 14:39
@stranske stranske added the verify:compare Runs verifier comparison mode after merge label Jul 31, 2026
@stranske
stranske temporarily deployed to agent-standard July 31, 2026 14:39 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

Provider Comparison Report

Provider Summary

Provider Model Verdict Confidence Summary
openai gpt-5.4 CONCERNS 86% This PR substantially advances the requested feature set: it adds schema support for cosine similarity, extends manager similarity handling, introduces selectable basis at the API layer, and adds a...
anthropic claude-opus-4-6 PASS 82% The PR implements the three core tasks from the issue: (1) a cosine column added to manager_similarity via Alembic migration and schema.sql, with the similarity flow computing cosine scores alo...
📋 Full Provider Details (click to expand)

openai

  • Model: gpt-5.4
  • Verdict: CONCERNS
  • Confidence: 86%
  • Scores:
    • Correctness: 7.0/10
    • Completeness: 6.0/10
    • Quality: 7.0/10
    • Testing: 7.0/10
    • Risks: 6.0/10
  • Summary: This PR substantially advances the requested feature set: it adds schema support for cosine similarity, extends manager similarity handling, introduces selectable basis at the API layer, and adds alert-condition logic for similar-manager crowding. Tests were added in the relevant areas. However, the merged code only partially matches the documented acceptance criteria as written. The strongest gaps are completeness/end-to-end clarity: the requirement called for config-selectable basis and a crowding alert rule type/event_type, while the visible implementation mainly exposes an API query selector and a condition evaluator keyed on similar_manager_count. Because those pieces may not fully satisfy the specified design intent, this is best rated CONCERNS rather than PASS.
  • Concerns:
    • The implementation stores cosine as a new nullable column on manager_similarity, but the documented requirement said to expose selectable basis via config with Jaccard remaining default. The API adds a basis query parameter, but from the visible diff there is no corresponding config-driven basis selection in the ETL or broader application behavior.
    • The PR appears to implement crowding qualification as an alert condition key (similar_manager_count_gte) plus a new environment threshold, but the requirement was to add a crowding alert rule type/event_type that fires when more than N similar managers hold the same security. From the visible changes, alerts/engine.py only evaluates a payload field if present; it is unclear that a new crowded-trade event_type was actually added or emitted end-to-end.
    • The acceptance criteria called for a deliberate off-by-one crowding-threshold test. The modified tests likely cover positive/negative threshold behavior, but from the diff summary there is no clear evidence of an explicit regression test proving a spuriously firing off-by-one case fails.
    • The migration adds only a cosine column, while the issue allowed either a basis column or score pair. This is acceptable structurally, but it means the API and ETL must consistently understand both scores on the same row; without seeing stronger safeguards, compatibility with existing rows and backfill behavior remains somewhat uncertain.
    • API ordering logic was changed to fetch all candidate rows, filter finite values in Python, and then presumably limit afterward. That is functionally reasonable, but for managers with many peer rows it is less efficient than limiting in SQL and may slightly change tie/ordering semantics.

anthropic

  • Model: claude-opus-4-6
  • Verdict: PASS
  • Confidence: 82%
  • Scores:
    • Correctness: 8.0/10
    • Completeness: 8.0/10
    • Quality: 7.0/10
    • Testing: 7.0/10
    • Risks: 7.0/10
  • Summary: The PR implements the three core tasks from the issue: (1) a cosine column added to manager_similarity via Alembic migration and schema.sql, with the similarity flow computing cosine scores alongside Jaccard; (2) a basis=jaccard|cosine query parameter on the peer-ranking endpoint; (3) a similar_manager_count_gte condition in the alert engine for crowding alerts. Tests are added/modified across 4 test files covering similarity flow, alert engine, conviction flow, and crowded/contrarian scenarios. The pr-00-gate.yml CI passed successfully with all jobs green. Code quality is reasonable though the SQL string interpolation pattern in api/managers.py is worth noting. The acceptance criteria test commands (pytest on the specified test files, ruff, black, git diff --check) are covered by the gate workflow results. Overall the implementation addresses the stated requirements.
  • Concerns:
    • The alert engine uses _as_float for the similar_manager_count_gte threshold comparison but compares an integer count against a float threshold — this works but is semantically inconsistent with the _gte naming suggesting an integer comparison.
    • The SQL query in api/managers.py constructs column names via string interpolation (score_column). While the value is constrained by the regex pattern on the basis query parameter, this pattern of injecting identifiers into SQL is fragile and could become a risk if the validation is ever relaxed.
    • The PR diff was truncated, so the full implementation of api/managers.py (finite row filtering, limit application) and several other files (etl/manager_similarity_flow.py, etl/conviction_flow.py, test files) could not be fully reviewed. Assessment is based on visible code plus file change summaries.
    • The ci.yml workflow is still in_progress — however per instructions this is not a blocking concern post-merge.
    • The deliberate-break acceptance criterion ('off-by-one in the crowding threshold fires spuriously → test fails') is not clearly verifiable from the visible diff, though test_crowded_contrarian.py and test_alert_engine.py additions likely cover this.

Agreement

  • Correctness: scores within 1 point (avg 7.5/10, range 7.0-8.0)
  • Quality: scores within 1 point (avg 7.0/10, range 7.0-7.0)
  • Testing: scores within 1 point (avg 7.0/10, range 7.0-7.0)
  • Risks: scores within 1 point (avg 6.5/10, range 6.0-7.0)

Disagreement

Dimension openai anthropic
Verdict CONCERNS PASS
Completeness 6.0/10 8.0/10

Unique Insights

  • openai: The implementation stores cosine as a new nullable column on manager_similarity, but the documented requirement said to expose selectable basis via config with Jaccard remaining default. The API adds a basis query parameter, but from the visible diff there is no corresponding config-driven basis selection in the ETL or broader application behavior.; The PR appears to implement crowding qualification as an alert condition key (similar_manager_count_gte) plus a new environment threshold, but the requirement was to add a crowding alert rule type/event_type that fires when more than N similar managers hold the same security. From the visible changes, alerts/engine.py only evaluates a payload field if present; it is unclear that a new crowded-trade event_type was actually added or emitted end-to-end.; The acceptance criteria called for a deliberate off-by-one crowding-threshold test. The modified tests likely cover positive/negative threshold behavior, but from the diff summary there is no clear evidence of an explicit regression test proving a spuriously firing off-by-one case fails.; The migration adds only a cosine column, while the issue allowed either a basis column or score pair. This is acceptable structurally, but it means the API and ETL must consistently understand both scores on the same row; without seeing stronger safeguards, compatibility with existing rows and backfill behavior remains somewhat uncertain.; API ordering logic was changed to fetch all candidate rows, filter finite values in Python, and then presumably limit afterward. That is functionally reasonable, but for managers with many peer rows it is less efficient than limiting in SQL and may slightly change tie/ordering semantics.
  • anthropic: The alert engine uses _as_float for the similar_manager_count_gte threshold comparison but compares an integer count against a float threshold — this works but is semantically inconsistent with the _gte naming suggesting an integer comparison.; The SQL query in api/managers.py constructs column names via string interpolation (score_column). While the value is constrained by the regex pattern on the basis query parameter, this pattern of injecting identifiers into SQL is fragile and could become a risk if the validation is ever relaxed.; The PR diff was truncated, so the full implementation of api/managers.py (finite row filtering, limit application) and several other files (etl/manager_similarity_flow.py, etl/conviction_flow.py, test files) could not be fully reviewed. Assessment is based on visible code plus file change summaries.; The ci.yml workflow is still in_progress — however per instructions this is not a blocking concern post-merge.; The deliberate-break acceptance criterion ('off-by-one in the crowding threshold fires spuriously → test fails') is not clearly verifiable from the visible diff, though test_crowded_contrarian.py and test_alert_engine.py additions likely cover this.

🔍 LangSmith Traces

@github-actions

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Agents Verifier. Do not edit.

stranske added a commit that referenced this pull request Jul 31, 2026
The runner writes langsmith-fleet-worker-attempt.json to the repo root, so keepalive committed it into this PR (the same artifact was removed from #1497). .gitignore only covered artifacts/langsmith/.
stranske added a commit that referenced this pull request Jul 31, 2026
* feat: add short-interest conviction context

* fix: document short-interest endpoint override

* chore(codex-keepalive): apply updates (PR #1498)

* fix: address short-interest review findings

Rejects rows without a real calendar report_date, preserves zero short-interest values, treats a blank FINRA_SHORT_INTEREST_URL as unset, gives metric_id a SQLite INTEGER variant, keeps DB write errors out of the fetch-failure path, removes DDL from the annotation read path, and prefers CUSIP over a reassigned ticker.

* chore: keep generated langsmith fleet telemetry out of the PR

The runner writes langsmith-fleet-worker-attempt.json to the repo root, so keepalive committed it into this PR (the same artifact was removed from #1497). .gitignore only covered artifacts/langsmith/.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: stranske <stranske@users.noreply.github.com>
stranske added a commit that referenced this pull request Jul 31, 2026
…1500)

Issue #1469 required the cosine basis to be selectable via config with Jaccard remaining the default; merged PR #1497 only added a per-request basis query parameter. MANAGER_SIMILARITY_DEFAULT_BASIS now supplies the default, an explicit query parameter still wins, and an unrecognized value warns and falls back to jaccard.

Co-authored-by: stranske <stranske@users.noreply.github.com>
@stranske

stranske commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Closer verifier disposition — openai CONCERNS (86%) / anthropic PASS (82%): one concern was real and is fixed in #1500; the rest are verified false positives

The Provider Comparison Report landed at 14:46Z, seven minutes after this PR merged. Every openai concern was re-checked against current main rather than accepted or dismissed on the report text. openai's own framing was that the gaps are "completeness/end-to-end clarity" from a truncated diff — that turns out to be right for four of the five, and the fifth was genuinely missing at report time and has since been implemented.

Concern 1 — "no config-driven basis selection; only an API query parameter" → real at the time, and fixed in merged #1500

Correct as of 14:46Z: this PR added basis as a required-to-be-explicit query parameter with no configured default. #1500 (feat(api): config-selectable default peer-similarity basis (#1469 verifier follow-up), merged 16:25Z, PASS/PASS from both providers, closing the same issue #1469) supplied exactly the missing piece:

  • .env.example:92MANAGER_SIMILARITY_DEFAULT_BASIS=jaccard.
  • api/managers.py:1352-1364SIMILARITY_BASES plus _configured_similarity_basis(), which lower-cases and validates the env value and falls back to jaccard with a logged warning on anything unrecognized, so Jaccard remains the default exactly as the issue required.
  • api/managers.py:1371-1378basis is now str | None defaulting to None, resolved by basis = basis or _configured_similarity_basis().

The verifier follow-up chain therefore already closed this gap; no further debt remains.

Concern 2 — "unclear that a crowded-trade event_type was actually added or emitted end-to-end" → false positive; the path is wired and tested end-to-end

No new event_type was needed: crowded_trade_change already existed and is already emitted by the conviction flow. What the requirement asked for — an alert that fires when more than N similar managers hold the same security — is what this PR wired in, and it is complete on main:

  • etl/conviction_flow.py:223-247_resolve_similarity_crowding_min_score() reads SIMILARITY_CROWDING_MIN_SCORE (default 0.5), and _similar_manager_ids() returns the crowd members actually connected at that Jaccard floor.
  • etl/conviction_flow.py:725-749dispatch_conviction_alerts computes that set per crowded trade and emits AlertEvent(event_type="crowded_trade_change", ...) with similar_manager_ids, similar_manager_count, and similarity_floor in the payload.
  • alerts/engine.py:86-91 — the similar_manager_count_gte rule condition consumes it.
  • tests/test_crowded_contrarian.py:357-366 — end-to-end assertion that the dispatched event is crowded_trade_change and its payload carries similar_manager_count == 5.

Concern 3 — "no explicit regression test proving a spuriously firing off-by-one case fails" → false positive; that test exists and is the reason the threshold is a float

tests/test_alert_engine.py:130-169 runs both sides of the boundary against one event with similar_manager_count: 3:

  • Crowding Rule (similar_manager_count_gte: 3) must fire at exactly the threshold (:155).
  • Fractional Crowding Rule (similar_manager_count_gte: 3.9) is then registered on the same event_type, and the assertion at :169 is that the result is still only ["Crowding Rule"] — the 3.9 rule must not fire on a count of 3.

That is precisely the deliberate-break case: had engine.py:88 used int(expected) — the way the neighbouring manager_count_gte does at :833.9 would truncate to 3, the fractional rule would fire spuriously, and this assertion would fail. This also answers anthropic's "_as_float is semantically inconsistent with the _gte naming": the float comparison is deliberate and load-bearing, keeping a fractional threshold strict instead of silently rounding it down.

Concern 4 — "compatibility with existing rows and backfill behavior remains uncertain" → false positive; handled by the query, not by a backfill

api/managers.py:1405-1411 filters <score_column> IS NOT NULL and orders by it, so pre-existing rows that have no cosine score are simply not returned under basis=cosine, and the jaccard path is untouched. Each row returns both scores side by side (:1400, :1424-1426, with cosine emitted as None when absent), so the API and ETL read one consistent row shape. Nullable-column-plus-null-filter is the correct choice here precisely because no backfill exists.

Concern 5 — "fetching all rows then limiting in Python is less efficient and may change tie/ordering semantics" → false positive on ordering; the efficiency note is a deliberate trade

Ordering is done entirely in SQL, with a deterministic tiebreak (ORDER BY <score_column> DESC, overlap_count DESC, :1407-1409); Python only applies the finite filter (:1412-1418) and then slices (:1430). Tie and ordering semantics are therefore unchanged. Limiting in SQL before the finite filter is what would change behavior — it could return fewer than limit peers whenever a non-finite score occupies a top slot. The fetch is bounded by one manager's peer rows, so the cost is acceptable for the correctness it buys.

Worth recording alongside anthropic's note on score_column interpolation: the interpolated value is never user text. :1394 is a two-way ternary ("cosine" if basis == "cosine" else "jaccard"), so any other input resolves to the literal jaccard. There is no identifier-injection surface even if the pattern= validation on the query parameter were relaxed.

Disposition

CONCERNS is cleared. The one real gap (config-selectable default basis) was closed by merged, independently PASS-verified #1500 on the same issue; the other four claims do not hold against current main. Source issue #1469 is closed and carries no remaining completion debt. No follow-up PR or issue is warranted.

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

Labels

agent:codex agent:retry Add to trigger agent retry after rate limit or pause agents:keepalive Enables keepalive automation for PR autofix Triggers autofix on PR verify:compare Runs verifier comparison mode after merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Audit] Peer similarity: embedding-cosine basis + crowding alerts

1 participant