Conversation
d3ce92c to
5969da0
Compare
|
This was generated by AI during triage. Summary: Problems:
Solution: Evidenceno deterministic fact backs this claim — model belief, not executed or read evidence Checked against |
|
@spfcraze Thanks — agreed the map must not grow unbounded. Popping immediately after lock.release() is unsafe when a waiter has already checked out that same Lock object: the next turn would insert a new lock and run in parallel with the waiter (the original bug this PR is fixing). Addressed in 5b98fd3 with a checkout refcount (holder + waiters). The map entry is removed only when the last ref drops, including if acquire() is cancelled. |
fix(api_server): serialize concurrent agent turns per session
|
Overlapping chat/wake POSTs on the same session_id could run two conversation loops against one SessionDB transcript (stale snapshots / duplicate side effects). Queue turns on a per-session asyncio lock. Related to NousResearch#84235. Complementary to NousResearch#77800 (wake idempotency); does not change wake timeout retry policy.
_run_agent locking alone left /v1/runs free to race wake self-posts on the same SessionDB transcript. Hold the same per-session lock inside the runs background task (after 202) so run and chat/completions queue instead of interleaving. Related to NousResearch#84235.
Drop a session's asyncio.Lock when the last holder or waiter exits so ephemeral fingerprint ids cannot accumulate unbounded map entries. Refcount checkouts instead of popping on release, which would let a new lock race a waiter still queued on the old one.
Follow-up to review on NousResearch#84876: remove inspect.getsource test (AGENTS.md ban) and note that same-session re-entry must arrive as a new request.
5b98fd3 to
3de6431
Compare
|
@Enough1122 Thanks for the review — addressed in latest push:
Also rebased onto latest |
|
Thanks @meiqinsi — dropping the |
What does this PR do?
Serialize concurrent agent turns on the same
session_idinsideAPIServerAdapter, so overlapping/v1/chat/completions(including wake self-posts) and/v1/runscannot run two conversation loops against one SessionDB transcript.Without this, co-turns load stale history snapshots and can re-execute the same work / fight over persistence (duplicate side effects,
lock_contended/session_persistence_failedclass failures). A per-session asyncio lock queues the second turn behind the first. Emptysession_idstill skips the lock; different sessions stay parallel./v1/runsstill returns 202 immediately — the background task waits on the lock after admit.This is an in-process api_server serialize fix only. It does not reload history after acquiring the lock, and does not add a cross-process / DB-level turn queue.
Related Issue
Related to #84235 (partial: in-process serialize for api_server chat + runs; does not close post-wait history reload or cross-process queue).
Complementary to #77800 (wake idempotency / coalesce); this PR does not change wake timeout retry policy.
Type of Change
Changes Made
gateway/platforms/api_server.py: add_session_turn_locks/_hold_session_turn_lock; acquire around_run_agentand inside/v1/runs_run_and_close(after 202).tests/gateway/test_wake_delivery.py: same-session serialize, different-session parallel, wiring into_run_agent+_handle_runs.tests/gateway/test_api_server_runs.py: while the shared lock is held,/v1/runsstaysqueuedand does not_create_agent; completes after release.How to Test
scripts/run_tests.sh tests/gateway/test_wake_delivery.pyscripts/run_tests.sh tests/gateway/test_api_server_runs.py -k session_turn_lock/v1/runsthat spawns async delegation + wake self-post → parent session shows one continuous turn (no interleaved short turns; nolock_contended/session_persistence_failed). Expect wake retries to queue behind an in-flight parent turn rather than starting parallel loops.Checklist
Code
fix(scope):,feat(scope):, etc.)scripts/run_tests.sh tests/gateway/test_wake_delivery.py tests/gateway/test_api_server_runs.py -k session_turn_lock(project CI-parity wrapper; not barepytest) and targeted tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Targeted tests via scripts/run_tests.sh: 8 passed (wake_delivery + session_turn_lock).
E2E after this change (same session, async delegation + wake): wake timeout retries queue behind one long parent turn; four
/v1/chat/completionscomplete together when the lock is released; nolock_contended/session_persistence_failed. Parent wall-clock looks longer because wakes are serialized — expected and desirable vs concurrent stale-snapshot turns.