DEV Community

Quinn Li
Quinn Li

Posted on

Route by Slack, Not by Price

Route by Slack, Not by Price

Free capacity is only cheap when your deadline is longer than its queue. That single comparison decides more of your bill than any per-token price. Everything below is a way to measure the queue and then choose a pool per request instead of per month.

Agent loops make this bite. One user question fans out into a planner call, three tool calls, a summarizer, and a repair pass when JSON comes back malformed. On a token sheet each call looks affordable. What actually arrives at the other end is wall-clock time, and someone is sitting there waiting for it.

Separate the queue from the work

Most dashboards collapse a request into one latency number. That number is useless for routing, because it hides whether you are paying in queue time or in generation time. A free pool with a long queue and fast generation and a committed pool with no queue and slow generation look identical once you average them.

So log four timestamps per call: submitted, accepted by the pool, first token, last token. The gap between submitted and accepted is your queue. The gap after that is the work.

# timestamps.py -- illustrative, not executed
import time

def timed_call(submit, deadline_monotonic):
    t0 = time.monotonic()
    handle = submit()                    # returns once the pool accepts the job
    t1 = time.monotonic()               # accept / dequeue
    stream = handle.stream()
    first = next(stream)
    t2 = time.monotonic()               # first token
    rest = first + "".join(stream)
    t3 = time.monotonic()               # last token
    return rest, {
        "queue_s": t1 - t0,
        "first_token_s": t2 - t1,
        "stream_s": t3 - t2,
        "slack_at_entry_s": deadline_monotonic - t0,
    }
Enter fullscreen mode Exit fullscreen mode

Convert every deadline to the same monotonic clock at request entry, or the slack column will drift with clock adjustments and quietly lie to you.

Turn the numbers into a lane

A lane is a pool plus the numbers you measured for it. You do not need a scheduler. You need a comparison.

# lanes.py -- illustrative, not executed
from dataclasses import dataclass

@dataclass(frozen=True)
class Lane:
    name: str
    queue_p95_s: float
    service_p95_s: float
    retries_per_call: float      # measured, not guessed

    def expected_s(self, safety: float = 0.25) -> float:
        base = self.queue_p95_s + self.service_p95_s
        return base * (1.0 + self.retries_per_call) * (1.0 + safety)


def admission(slack_s, free, committed):
    if slack_s >= free.expected_s():
        return free
    if slack_s >= committed.expected_s():
        return committed
    return None   # shed or degrade: queueing here just spends the caller's deadline
Enter fullscreen mode Exit fullscreen mode

The third branch is the one people skip. If neither lane fits, waiting is not a strategy, it is a slow failure that also costs tokens. Shedding, returning a cheaper partial result, or telling the caller to poll later is honest. Silently adding four seconds is not.

Note the retry term inside the expectation. A retry inherits the remains of the original deadline, not a fresh one, so a call that failed once is a worse candidate for the slower pool than the same call was before it failed. That asymmetry is easy to miss when retries live in a wrapper nobody reads.

Check it before you believe it

The routing rule is only as good as the percentiles under it, so run the same prompt set twice: once sending everything to the slow lane, once routed by slack. Compare deadline miss rate per lane and total calls, not just average latency. Averages hide exactly the tail that breaks user trust.

# harness.py -- illustrative, not executed
import asyncio, time

async def one(prompt, deadline_s, route, lanes, submit):
    t0 = time.monotonic()
    lane = route(deadline_s, lanes["free"], lanes["committed"])
    if lane is None:
        return {"lane": None, "miss": True, "calls": 0}
    text, usage = await submit(lane.name, prompt)
    return {"lane": lane.name,
            "miss": (time.monotonic() - t0) > deadline_s,
            "calls": usage.get("attempts", 1)}

async def sweep(prompts, deadline_s, route, lanes, submit, n=200):
    results = await asyncio.gather(
        *(one(prompts[i % len(prompts)], deadline_s, route, lanes, submit) for i in range(n))
    )
    return results
Enter fullscreen mode Exit fullscreen mode

Run it with two flags and the same seed, and read the miss rate per lane rather than a combined score.

# illustrative invocations; substitute your own submit() implementation
python harness.py --route all-free      --n 200 --deadline 4
python harness.py --route by-slack      --n 200 --deadline 4
Enter fullscreen mode Exit fullscreen mode

If the routed run has the same miss rate as the committed-only run while pushing a real share of calls to the slow lane, you have earned the right to route. If the miss rate climbs, your percentiles were optimistic, and no amount of prompt tuning will fix a queue you did not measure.

The decision, written down

Workload Slack at entry Route Reason
Interactive turn, human typing shorter than the fast lane's p95 committed the slow lane's queue alone eats the budget
Agent tool call, human still waiting roughly twice the slow lane's p95 slow lane slack absorbs one tail spike
Nightly re-summarize of a backlog hours slow lane the deadline is a maintenance window, not a person
Retry after a failed attempt already halved by the first try committed the retry inherited a shorter deadline
Post-incident backfill hours, very high volume slow lane, throttled volume is the risk, not latency

Read the table as a template, not a recommendation. Your numbers belong in the middle column.

Where the free tier fits, and where it does not

This is the point in the workflow where an offer like the one around MonkeyCode becomes testable rather than aspirational. The operator-supplied terms are free model access and a free server option, with 10 million free tokens quoted for the free tier; the project is presented as open source. Treat those as claims to check on the project's official page, including the license, which models are covered, and whether anything expires.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The reason the free server option matters for a cost note is subtler than the token count. A free pool changes what you spend: you trade money for queue position. That trade is excellent for a batch job whose deadline is a maintenance window, and bad for a request a person is watching. Free capacity is a loan against your latency, and something has to repay it.

A one-paragraph version of when free is the wrong bet: whenever the measured slow-lane wait plus retry inflation exceeds the slack you actually have. Not the slack you wish you had. Not the average slack. The slack on the request in front of you.

Who should skip this

If your total spend is small enough that routing logic costs more engineering time than it saves, skip it. If you have no per-request telemetry, skip it until you do, because a router fed guessed percentiles is worse than no router. And if you need hard guarantees about availability, retention, or long-term free tiers, do not build a deadline class on top of something you have not read the terms of. Verify first, then route.

Limitations worth stating plainly: the snippets above are illustrative and were not executed for this article. Percentiles go stale when traffic shape changes, so re-measure on a schedule. A shared free pool can move under you in ways your own logs lag behind. And the strictest deadline class always wins the fast lane, which means the fast lane never gets cheaper no matter how much you route around it.

If you want your own slack numbers instead of mine, the free model access and free server option are a reasonable place to collect real queue-wait data cheaply. Measure first. Migrate second.

Top comments (0)