Summary
soul_writer.py:20-26 stores asyncio.Lock objects in a plain dict[int, asyncio.Lock]. run_soul_writer is called from:
- Pre-turn check (main event loop)
- Post-compaction (main event loop)
on_thread_close via eager_consolidation (background task, potentially different loop)
inactivity_sweep (background task)
An asyncio.Lock created on one event loop cannot be acquired on another — raises RuntimeError on Python 3.10+.
Secondary Issue
_user_locks dict grows without bound (no eviction).
Fix
Replace asyncio.Lock with threading.Lock since the inner pipeline (_run_soul_writer_inner) is synchronous and runs via asyncio.to_thread. Alternatively, ensure all callers share the same event loop and add a size cap.
Files
apps/server/src/anima_server/services/agent/soul_writer.py:20-26, 56-64
Severity
High — crash on cross-loop lock acquisition.
Summary
soul_writer.py:20-26storesasyncio.Lockobjects in a plaindict[int, asyncio.Lock].run_soul_writeris called from:on_thread_closeviaeager_consolidation(background task, potentially different loop)inactivity_sweep(background task)An
asyncio.Lockcreated on one event loop cannot be acquired on another — raisesRuntimeErroron Python 3.10+.Secondary Issue
_user_locksdict grows without bound (no eviction).Fix
Replace
asyncio.Lockwiththreading.Locksince the inner pipeline (_run_soul_writer_inner) is synchronous and runs viaasyncio.to_thread. Alternatively, ensure all callers share the same event loop and add a size cap.Files
apps/server/src/anima_server/services/agent/soul_writer.py:20-26, 56-64Severity
High — crash on cross-loop lock acquisition.