Skip to content

fix(cognitive-load): serialize weight tuning to stop concurrent update loss#81

Open
tongshu2023 wants to merge 1 commit into
zijinz456:mainfrom
tongshu2023:claude/issue-37-tuning-race
Open

fix(cognitive-load): serialize weight tuning to stop concurrent update loss#81
tongshu2023 wants to merge 1 commit into
zijinz456:mainfrom
tongshu2023:claude/issue-37-tuning-race

Conversation

@tongshu2023

Copy link
Copy Markdown

Summary

Closes #37.

The cognitive-load weight auto-tuner is reached from FastAPI threadpool workers by concurrent chat sessions, but its in-memory store had no synchronization, producing exactly the data-loss class described in the issue:

  1. Lost tuning steps — the per-signal read-modify-write (read current multiplier → apply decay/recovery factor → write back) could interleave across threads, so the second writer silently overwrote the first's adjustment.
  2. Eviction race — the cache-cap eviction does a min() scan over the store; with another thread inserting/deleting users mid-scan this raises RuntimeError: dictionary changed size during iteration, and parallel inserts could overshoot the 200-user cap.

Note on the issue text

The issue references an AgentKV read-modify-write in cognitive_load_tuning.py lines 65–176 with SELECT ... FOR UPDATE as a candidate fix. The tuner has since been rewritten as a pure in-memory module (sliding window + multipliers dict), so there is no DB row to lock anymore — but the same race class moved into the process-local store. The fix targets the current implementation.

Fix

A module-level threading.Lock serializes update_and_get_multipliers, get_multipliers, and reset_multipliers. The critical sections are pure in-memory arithmetic over small dicts (microseconds), so contention is negligible; no lock is held across any I/O.

Tests

First test coverage for this module — tests/test_cognitive_load_tuning.py (10 tests):

Sequential behavior: no adjustment below 5 samples; exactly 2 decay steps after 6 persistently-high samples (0.9²); decay floor at 0.40; recovery after the signal drops; reset; non-numeric signals ignored.

Concurrent safety (the issue's acceptance criterion):

  • 2 threads sharing one signal must produce exactly the sequential number of decay steps — a lost update yields 0.9 instead of 0.81 and fails
  • 8 threads updating 8 distinct signals for one user — every signal must survive with the exact sequential multiplier
  • 480 distinct users from 8 threads crossing the 200-user eviction cap — must neither raise nor overflow the cap

Local run: test_cognitive_load_tuning.py + test_cognitive_load.py + test_cognitive_load_calibrator.py42 passed.

Acceptance criteria

  • Concurrent weight updates don't lose data
  • Unit test verifies concurrent safety
  • Existing tests pass

🤖 Generated with Claude Code

…e loss (zijinz456#37)

The auto-tuner is reached from FastAPI threadpool workers by concurrent
chat sessions, but its in-memory store had no synchronization:

- the read-modify-write on each signal multiplier (read current, apply
  decay/recovery factor, write back) could interleave across threads,
  silently dropping tuning steps - the data-loss race from zijinz456#37;
- the eviction scan (min() over the store) could iterate while another
  thread inserted/deleted users, raising RuntimeError dict-changed-
  during-iteration, and parallel inserts could overshoot the 200-user
  cache cap.

A module-level threading.Lock now serializes update_and_get_multipliers,
get_multipliers, and reset_multipliers. Critical sections are pure
in-memory arithmetic (microseconds), so contention is negligible.

Note: the issue text references an AgentKV read-modify-write at lines
65-176; the tuner has since been rewritten as this in-memory module, so
the fix targets the current implementation (same race class).

First test coverage for the module (tests/test_cognitive_load_tuning.py,
10 tests): decay/floor/recovery/reset/non-numeric behavior, plus three
concurrency tests - 2 threads sharing one signal must produce exactly
the sequential number of decay steps, 8 threads with distinct signals
must all survive, and 480 users crossing the eviction cap from 8
threads must neither crash nor overflow the cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix race condition in cognitive load weight tuning

1 participant