The integration of Large Language Models into task queues via the Model Context Protocol (MCP) is often treated as a seamless, event-driven process. However, a recent technical breakdown of OSFoundry’s architecture exposes a harsh reality: when the worker is a human-interacted chat session, push events are impossible, and robust polling logic is mandatory. The article, published on DEV.to by bbt-it, reconstructs the JSON-RPC handshakes between OSFoundry and a simulated Claude host to demonstrate how claim races, lease expirations, and idempotency are handled in the absence of a persistent, listening daemon.
The Push Event Gap in Chat-Based Workers
Unlike traditional background workers, a chat session with Claude or ChatGPT is stateless from the server's perspective until a user actively engages. OSFoundry’s connector exposes four core tools—link_current_chat, read_linked_chat_tasks, claim_linked_chat_task, and submit_linked_chat_result—but lacks a generic event trigger to wake the model. While ChatGPT’s Work mode supports MCP Events for subscriptions, the standard chat interface relies on the user to open the tab. This forces the scheduler to be human, transforming common failures like closed tabs into expected operational states rather than service outages.
Race Conditions and Conditional Writes
The reconstruction highlights a critical vulnerability in naive task claiming. In a test involving 200 tasks and eight simultaneous threads, a simple read-then-write pattern resulted in 7.40 winners per task on average, with 200 out of 200 tasks experiencing duplicate claims. By switching to a conditional SQL update (UPDATE ... WHERE status='queued'), the race condition was eliminated, resulting in exactly one winner per task. OSFoundry mitigates this by returning the existing claim ID if a duplicate claim is attempted from the same chat link, effectively treating double-clicks or repeated tool calls as idempotent successes rather than errors.
Lease Expiry and Fencing Tokens
When a chat session disappears mid-task, the system must reclaim the work. The analysis introduces a lease mechanism, where uncompleted claims revert to queued after a set time. In a test with a one-second lease, a second chat successfully took over the task 64ms after expiry. Crucially, when the original chat returned late and attempted to submit a result, the server rejected it because the claim_id no longer matched the active lease. This claim_id acts as a fencing token, preventing stale workers from overwriting fresh results—a pattern also seen in OSFoundry’s Graph relay, where identical retries are accepted but changed results on completed tasks are rejected.
The Cost of Lazy Sweeps in SQLite
Polling efficiency is paramount when the cost of a request is the model's context window. The reconstruction revealed that performing a 'lazy sweep' of expired leases on every read operation introduced significant overhead, slowing down empty inbox checks by nearly nine times due to write locks. By optimizing the sweep to only execute when expired rows are detected via a partial index, the server response time dropped from ~2ms to ~0.2ms. For a browser panel polling every 15 seconds, this optimization reduces the cumulative latency impact, ensuring that the 87-byte empty inbox response remains cheap enough for frequent checks.
Key Takeaways
- Idempotency is Critical: Claim tools must return existing claim IDs for duplicate requests from the same source to handle user double-clicks or model retries gracefully.
- Fencing Tokens Prevent Stale Writes: Always include the
claim_idin theWHEREclause of result submissions to reject updates from workers whose leases have expired. - Optimize Read Paths: Avoid write locks during read operations; use partial indexes and conditional updates to keep polling latency low for chat-based workers.
- Human Latency is the Bottleneck: With a 24-hour task expiry and manual claiming, the system must be designed for long delays and abrupt worker terminations.
The Bottom Line
OSFoundry’s approach proves that 'agent' workflows are often just distributed systems problems in disguise. Without push infrastructure, we are forced to build robust, lock-free polling mechanisms that treat human unpredictability as a core architectural constraint, not an edge case.