A Runaway Prompt Is a Tenant: Fair Scheduling on a Shared Free Endpoint Trouble started with an eleven-second timeout on a request I had already given up on. The same timeout then hit a second agent that shared nothing with the first one except an endpoint. I was running three experimental agents on MonkeyCode's free server with its free model endpoint, and I assumed the platform handled isolation. The failure taught me that isolation is the client's job, and the unit of isolation is the tenant, not the request. Disclosure: This article was prepared as part of MonkeyCode's product outreach. 1. The Day Three Agents Became One The three agents shared a single endpoint, one API key, and one context quota, and I treated them as independent because their prompts were unrelated. Agent A summarized logs, Agent B drafted release notes, and Agent C explored a schema, and they all ran in the same Python process with the same HTTP client. The crash began when Agent C entered a retry loop after a schema validation error, and each retry appended the full conversation history. The history grew from twelve messages to forty-seven messages in under two minutes. The other two agents did not fail because of their own logic, they failed because Agent C consumed the shared context budget and the shared connection pool. That is the moment I realized a runaway prompt is not a bug in one agent, it is a tenant that evicts every other tenant on the same endpoint. 2. Why the SDK Cannot Protect You The SDK sees one request at a time, and it has no concept of a tenant, a budget, or a sibling agent's needs. The endpoint sees a stream of requests from one API key, and it cannot tell whether the requests come from one runaway loop or ten healthy agents. The only layer that knows the difference is the orchestration layer, and that layer is exactly where most prototypes have nothing. # What most prototypes look like def run_agent ( agent_id , task ): messages = build_initial_messages ( task ) while not done : response = call_endpoint ( messages ) messages . append ( response ) return summarize ( messages ) There is no budget, no queue, no detection, and no isolation, and the loop will happily grow until it hits the context limit or the rate limit. The fix is to insert a scheduling layer between the agent loops and the endpoint, and that layer must treat every agent as a tenant with a budget. 3. The Scheduler: Three Rules That Contain the Damage I wrote a minimal scheduler that enforces three rules, and the full implementation fits in about eighty lines. Rule one is a context budget per tenant, rule two is a fair queue that round-robins across tenants, and rule three is a stall detector that pauses a tenant after too many low-information responses. import time import threading from collections import defaultdict class TenantScheduler : def init ( self , max_messages_per_tenant = 20 , stall_threshold = 3 ): self . max_messages = max_messages_per_tenant self . stall_threshold = stall_threshold self . queues = defaultdict ( list ) self . msg_counts = defaultdict ( int ) self . stall_counts = defaultdict ( int ) self . lock = threading . Lock () def submit ( self , tenant_id , task ): with self . lock : self . queues [ tenant_id ]. append ( task ) def next_task ( self ): with self . lock : for tenant_id in list ( self . queues . keys ()): if self . msg_counts [ tenant_id ] >= self . max_messages : continue if self . stall_counts [ tenant_id ] >= self . stall_threshold : continue if self . queues [ tenant_id ]: self . msg_counts [ tenant_id ] += 1 return tenant_id , self . queues [ tenant_id ]. pop ( 0 ) return None def record_response ( self , tenant_id , content_length ): with self . lock : if content_length < 5 : self . stall_counts [ tenant_id ] += 1 else : self . stall_counts [ tenant_id ] = 0 Rule one stops a runaway from consuming the shared context window, rule two guarantees that a healthy tenant always gets a turn, and rule three pauses a tenant that is producing empty or near-empty responses. The scheduler does not solve the underlying model quality problem, but it converts a single-point failure into a contained degradation. 4. What the Tradeoff Table Looks Like Decision What you gain What it costs Per-tenant context budget A runaway cannot evict siblings Long-running tasks need checkpointing Round-robin fair queue Every tenant makes progress A bursty tenant waits its turn Stall detection with pause Empty responses stop consuming budget A paused tenant needs a resume policy Shared API key with tenant IDs Simple to implement The endpoint still sees one aggregate stream The pattern here is that you trade raw throughput for bounded blast radius, and for experimental workloads that is almost always the right trade. 5. Who Should Not Use This Approach If you have a production agent with a hard latency SLA, do not put it behind a shared free endpoint with a round-robin scheduler, because fairness guarantees are not latency guarantees. If your task legitimately needs fifty messages of context, a twenty-message budget will break it, so the budget must be tuned per workload, not set once. Free quotas change without notice, so verify the current token allowance and server terms in the project README before you commit to this pattern. 6. How to Validate the Scheduler in One Afternoon Run two agents on the same endpoint, one healthy and one deliberately stuck in a retry loop, and watch the scheduler keep the healthy agent responsive. Then remove the scheduler and run the same pair, and you will see the healthy agent time out within minutes. That contrast is the whole argument, and it takes about an hour to reproduce with any OpenAI-compatible endpoint. MonkeyCode's free server is a reasonable place to run that experiment, and the project is open source if you want to inspect how its gateway handles shared load. The scheduler I described is generic, and it will work against any endpoint that exposes a chat completion API. Which tenant is your next runaway: an agent that retries forever, a summarizer that doubles the context, or a tool loop that never terminates? Will your scheduler pause it, evict it, or let it take the whole endpoint down with it?

A Runaway Prompt Is a Tenant: Fair Scheduling on a Shared Free Endpoint
Robin

