The question landed at minute forty of a planning call: "why don't we just put everything on the free lane and revisit next quarter?"
It is a fair question, and it is usually answered badly — with a price comparison, or with a vibe about model quality. Both miss the thing that actually decides the outcome.
The scene is a composite, assembled from conversations I keep having with platform leads. No client numbers below, and no measured benchmarks.
There are three lanes most teams are actually choosing between:
Every lane forces refusals. The free lane refuses some data classes, some concurrency, some model pinning, some audit trails — exactly which ones depends on terms you should have in writing. Self-hosting refuses the team that has no named ops owner.
So the real question is not "which is cheapest?" It is: which refusals can this workload absorb for the next 90 days?
Price is a continuous variable. Constraints are binary.
A lane you are not permitted to use has an infinite unit cost, no matter how good the sticker looks. I've written before about computing crossovers; this post is about the step before that math, where you decide which lanes are even admissible.
Get the admissibility set wrong and the spreadsheet becomes a very precise answer to the wrong question. Sound familiar?
Here is the artifact. It is deliberately dumb, stdlib-only, and reproducible in one file.
"""lane_fit.py — exclude AI coding lanes by hard constraints, then break ties.
Python 3.10+. No dependencies.
python lane_fit.py
Every number in LANE_ASSUMPTIONS is a PLACEHOLDER. Replace it with the
term you actually received in writing before trusting a line of output.
"""
from dataclasses import dataclass, replace
LANE_ASSUMPTIONS = {
"hosted_free": dict(
allowed_data={"public", "internal"},
max_parallel=4, # PLACEHOLDER: concurrency the shared lane tolerates
allows_pinning=False, # can you pin an exact model version?
keeps_audit_log=False, # retained, exportable request logs?
commitments=1, # irreversible commitments you accept
),
"hosted_paid": dict(
allowed_data={"public", "internal", "regulated"},
max_parallel=50,
allows_pinning=True,
keeps_audit_log=True,
commitments=3,
),
"self_hosted": dict(
allowed_data={"public", "internal", "regulated"},
max_parallel=None, # no hard cap, but you own p99
allows_pinning=True,
keeps_audit_log=True,
commitments=5,
),
}
OPS_OWNER = False # True only if a named human owns patching + on-call
@dataclass(frozen=True)
class Workload:
name: str
data_class: str # public | internal | regulated
peak_parallel_agents: int
pinned_model: bool
audit_log: bool
reviewer_required: bool
hours_per_week: float
def blockers(w: Workload, lane: str) -> list[str]:
a = LANE_ASSUMPTIONS[lane]
out = []
if w.data_class not in a["allowed_data"]:
out.append(f"data_class={w.data_class} not allowed")
if a["max_parallel"] is not None and w.peak_parallel_agents > a["max_parallel"]:
out.append(f"peak_parallel_agents={w.peak_parallel_agents} > {a['max_parallel']}")
if w.pinned_model and not a["allows_pinning"]:
out.append("needs a pinned model version")
if w.audit_log and not a["keeps_audit_log"]:
out.append("needs retained, exportable logs")
if lane == "self_hosted" and w.data_class == "regulated" and not OPS_OWNER:
out.append("regulated data, no named ops owner for self-hosting")
return out
def rank(w: Workload) -> list[str]:
ok = [lane for lane in LANE_ASSUMPTIONS if not blockers(w, lane)]
return sorted(ok, key=lambda lane: (LANE_ASSUMPTIONS[lane]["commitments"], lane))
def flip_test(w: Workload, chosen: str) -> list[tuple[str, str]]:
"""Which single-variable change would exclude the lane we just picked?"""
mutations = {
"data one level stricter": replace(w, data_class=_stricter(w.data_class)),
"peak parallelism x2": replace(w, peak_parallel_agents=w.peak_parallel_agents * 2),
"pin an exact model": replace(w, pinned_model=True),
"require exportable logs": replace(w, audit_log=True),
}
hits = []
for label, mutated in mutations.items():
if mutated != w and blockers(mutated, chosen):
hits.append((label, blockers(mutated, chosen)[0]))
return hits
def _stricter(data_class: str) -> str:
order = ["public", "internal", "regulated"]
return order[min(order.index(data_class) + 1, len(order) - 1)]
WORKLOADS = [
Workload("docs-and-tests", "public", 2, False, False, True, 6.0),
Workload("internal-refactor", "internal", 3, False, False, True, 12.0),
Workload("billing-migration", "regulated", 2, True, True, True, 4.0),
]
def main() -> None:
for w in WORKLOADS:
admissible = rank(w)
print(f"\n{w.name} (data={w.data_class}, parallel={w.peak_parallel_agents}, "
f"{w.hours_per_week}h/wk)")
for lane in LANE_ASSUMPTIONS:
why = blockers(w, lane)
print(f" {'OK ' if not why else 'OUT '} {lane:<12} {'; '.join(why)}")
if not admissible:
print(" => no admissible lane: split the workload or reduce scope")
continue
chosen = admissible[0]
fallback = admissible[1] if len(admissible) > 1 else "none"
print(f" => pick {chosen}; fallback {fallback}")
flips = flip_test(w, chosen)
if not flips:
print(" flip: none of the four single-variable changes break this lane")
for label, reason in flips:
print(f" flip: {label} -> {reason}")
if __name__ == "__main__":
main()
Run it. Then argue with the output. That argument is the meeting you actually needed to have.
docs-and-tests (data=public, parallel=2, 6.0h/wk)
OK hosted_free
OK hosted_paid
OK self_hosted
=> pick hosted_free; fallback hosted_paid
flip: pin an exact model -> needs a pinned model version
flip: require exportable logs -> needs retained, exportable logs
internal-refactor (data=internal, parallel=3, 12.0h/wk)
OK hosted_free
OK hosted_paid
OK self_hosted
=> pick hosted_free; fallback hosted_paid
flip: peak parallelism x2 -> peak_parallel_agents=6 > 4
flip: pin an exact model -> needs a pinned model version
flip: require exportable logs -> needs retained, exportable logs
billing-migration (data=regulated, parallel=2, 4.0h/wk)
OUT hosted_free data_class=regulated not allowed
OK hosted_paid
OUT self_hosted regulated data, no named ops owner for self-hosting
=> pick hosted_paid; fallback none
flip: none of the four single-variable changes break this lane
These are placeholder assumptions, not measurements. Feed it your own terms.
OUT rows are decisions, not preferences.billing-migration has no fallback because self-hosting without an ops owner is not a lane — it is a future incident with a start date.
The free lane is not the "cheap lane." It is the lane for workloads whose refusals you can absorb: documentation, test scaffolding, mechanical refactors, anything that a human reviewer would catch anyway.
The lane I am running this quarter on the free side is MonkeyCode — free model access plus a free server option, which the operator describes as an open-source project. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
As of 2026-10-26, the operator's pitch is free model access (a ten-million-token allowance, per their own copy) plus a free server option. I have not independently verified the allowance, the model list, the server specifications, how long either offer lasts, or the license terms. Treat every one of those as an unverified input to the worksheet — a row you still have to fill in, not a number you get to budget against.
What makes it a reasonable first lane for docs-and-tests is structural, not promotional: it asks for the fewest commitments. No contract. No hardware. No pager. That is also exactly why it runs out of road for billing-migration.
Anyone can list tradeoffs. The useful move is naming the single variable that would reverse your call, and assigning it an owner.
For each flip line, write down three things:
If nobody owns "peak parallelism x2," then the free lane is not a decision — it is a coin flip with documentation.
OK.
The commitments field is ordinal, not cardinal — it ranks lanes, it does not price them. The model ignores reviewer hours entirely, and reviewer hours are usually the largest real cost of a free lane. The sensitivity analysis mutates one variable at a time; two simultaneous changes can produce outcomes this script will never show you. And the whole thing is a conversation tool, not objective truth — two reasonable leads can fill in the assumptions differently and both be right.
One soft ask: if you run this, bring back the flip list for the workload you were about to move to the free lane. That single list tells me more about your risk posture than any architecture diagram.