Free shared compute is the right first lane when your agent loop can move tomorrow without rewriting tools, traces, or secrets. Paid APIs and self-hosted boxes win when that loop is welded to private data, mutating tools, or evals you cannot replay. I treat the reverse order as the expensive mistake, because you pay in migrations rather than invoices. Why start with the price tag when the part you cannot unwind is the coupling?
Coupling is not latency, and it is not your monthly bill
I use coupling to mean how much of the agent definition lives outside your repository. Prompts, tool schemas, trace format, secret handling, and retry policy should stay yours even if the GPU does not. If swapping the endpoint forces a rewrite of those five pieces, you did not rent compute. You rented an architecture, and that is a much stickier invoice.
This is a proposed checklist, not a production study, and I am not attaching fabricated timings to it. Treat the scores as a conversation you can have in a pull request, then replace the weights with your constraints. The point is to decide the lane before the loop grows a dozen irreversible side effects. Can your standup even name those side effects today?
If the agent is still a trench coat, stay on free
A lot of so-called agents are still a classifier plus a switch statement wearing a confident system prompt. Those loops barely couple to anything, so a shared free lane is usually enough and I would not apologize for using one. The coupling checklist will green-light them quickly, and that result is correct rather than naive. Are you sure your loop is more than a trench coat, or have the tools never left the slide deck?
The moment you add tools that send mail, open pull requests, or touch billing, the score should flop toward a private lane. I want that flop to happen on paper, before a weekend prototype writes to production because someone pasted a key. List every tool out loud and mark each one read-only or mutating. If you cannot finish that list, you are not choosing compute yet. You are still designing the agent.
Six questions that actually change the lane
Ask these in order, and stop at the first hard no whenever the data is truly sensitive. I keep dollars out of this pass on purpose so the architecture argument stays visible.
- Can I export every trace as JSON that another runner can ingest without a vendor SDK?
- Do any tools mutate production systems, or do they only read fixtures and stubs?
- Do prompts or retrieval chunks contain customer data I cannot put on a shared tenant?
- Must evals pin behavior across weeks, or is directional quality enough for this loop?
- Can the loop tolerate noisy-neighbor latency, or does a human wait on every tool call?
- If the free lane vanished tonight, could I rerun the same fixtures on another endpoint this week?
Notice that none of those questions mention a unit price for tokens. Cost still matters later, after you know whether the loop is portable at all. If question two or three is a hard no, skip the free shared lane and go to a paid API or a box you control. Would you like a prettier matrix than that? Not until those two answers are honest.
A five-step workflow before anyone pastes a key
Here is the sequence I want written down before CI learns a new endpoint. The code is a proposed harness. It checks the scorer and the loop contract, not a live vendor and not a benchmark I did not run.
Step 1: Freeze the loop in your repo
Write the agent as a plain function that takes a task record and returns a trace. Keep system prompts, tool JSON, and retry limits in files you can grep without clicking a hosted UI. If the product console is the only place those live, you are already coupled and the rest of this article will not save you.
# loop_contract.py — proposed interface, not a vendor SDK
from dataclasses import dataclass, field
from typing import Any, Callable
@dataclass
class Task:
id: str
input: str
fixtures: dict[str, Any]
@dataclass
class TraceEvent:
role: str
name: str
payload: dict[str, Any]
@dataclass
class Trace:
task_id: str
events: list[TraceEvent] = field(default_factory=list)
ok: bool = False
def run_loop(task: Task, complete: Callable[[str, list[dict]], str]) -> Trace:
"""One turn-taking loop. `complete` is the only endpoint-shaped hole."""
trace = Trace(task_id=task.id)
system = open("prompts/system.md", encoding="utf-8").read()
messages = [
{"role": "system", "content": system},
{"role": "user", "content": task.input},
]
# Dispatch tools against task.fixtures only. Never against live prod.
_ = messages
return trace
Step 2: Score coupling with explicit weights
I keep the weights boring so a reviewer can argue with them in the diff. Hide them in a private spreadsheet and the decision turns back into vibes. Change the numbers in review if your world is harsher than mine.
# coupling_score.py
from dataclasses import dataclass
@dataclass
class CouplingAnswers:
traces_exportable: bool
tools_are_read_only: bool
no_customer_data_in_prompts: bool
directional_evals_ok: bool
latency_slack_ok: bool
can_rerun_elsewhere_this_week: bool
WEIGHTS = {
"traces_exportable": 2,
"tools_are_read_only": 3,
"no_customer_data_in_prompts": 3,
"directional_evals_ok": 1,
"latency_slack_ok": 1,
"can_rerun_elsewhere_this_week": 2,
}
def coupling_score(a: CouplingAnswers) -> tuple[int, str]:
flags = a.__dict__
score = sum(WEIGHTS[k] for k, ok in flags.items() if ok)
hard_block = (not a.tools_are_read_only) or (not a.no_customer_data_in_prompts)
if hard_block:
return score, "self_hosted_or_paid_private"
if score >= 10 and a.can_rerun_elsewhere_this_week:
return score, "free_shared_ok"
if score >= 7:
return score, "paid_api_ok"
return score, "self_hosted_or_paid_private"
I also keep the answers in YAML so a human can read the gate without opening Python. That file belongs next to the prompts, not in a wiki that nobody updates.
# coupling.yml — proposed answers for a read-only sketch loop
traces_exportable: true
tools_are_read_only: true
no_customer_data_in_prompts: true
directional_evals_ok: true
latency_slack_ok: true
can_rerun_elsewhere_this_week: true
# load_coupling.py — proposed
import yaml
from coupling_score import CouplingAnswers, coupling_score
def recommend(path: str = "coupling.yml") -> tuple[int, str]:
with open(path, encoding="utf-8") as fh:
raw = yaml.safe_load(fh)
return coupling_score(CouplingAnswers(**raw))
Step 3: Prove the score with a tiny test, not a slide
Slides do not fail in CI. These tests do, and they only prove the scorer, which is the entire point of this artifact.
# test_coupling_score.py
from coupling_score import CouplingAnswers, coupling_score
def test_mutating_tools_never_land_on_shared_free():
a = CouplingAnswers(
traces_exportable=True,
tools_are_read_only=False,
no_customer_data_in_prompts=True,
directional_evals_ok=True,
latency_slack_ok=True,
can_rerun_elsewhere_this_week=True,
)
_, lane = coupling_score(a)
assert lane == "self_hosted_or_paid_private"
def test_portable_read_only_loop_may_use_free_shared():
a = CouplingAnswers(
traces_exportable=True,
tools_are_read_only=True,
no_customer_data_in_prompts=True,
directional_evals_ok=True,
latency_slack_ok=True,
can_rerun_elsewhere_this_week=True,
)
score, lane = coupling_score(a)
assert score == 12
assert lane == "free_shared_ok"
def test_private_prompts_are_a_hard_block():
a = CouplingAnswers(
traces_exportable=True,
tools_are_read_only=True,
no_customer_data_in_prompts=False,
directional_evals_ok=True,
latency_slack_ok=True,
can_rerun_elsewhere_this_week=True,
)
_, lane = coupling_score(a)
assert lane == "self_hosted_or_paid_private"
python -m pytest test_coupling_score.py -q
python -c "from load_coupling import recommend; print(recommend())"
Step 4: Map the lane to an actual substrate
Use the table, then write the choice into the pull request, not into Slack memory. I care less about the brand on the GPU than about which refusals you are signing up for.
| Lane | Fit | You accept | You refuse |
|---|---|---|---|
| Free shared models plus a free server | Read-only tools, no customer prompts, exportable traces | Noisy neighbors, weaker pinning, possible queueing | Secrets, production mutators, compliance boundaries |
| Paid API | You need a clearer endpoint contract and less queueing | An invoice, still someone else's tenancy | Full control of weights and disk |
| Self-hosted | Mutating tools, private corpora, pinned evals | Ops load, patching, capacity planning | Treating GPUs as a weekend experiment |
Step 5: Keep a kill switch in the runner
If the abort feels harsh, good. The cheap lane should be hard to enter once tools start writing. I would rather fail a job than discover coupling during an incident review.
# runner.py — proposed
import os
from coupling_score import CouplingAnswers, coupling_score
LANE = os.environ.get("AGENT_LANE", "free_shared_ok")
def assert_lane_matches(answers: CouplingAnswers) -> None:
_, recommended = coupling_score(answers)
if LANE == "free_shared_ok" and recommended != "free_shared_ok":
raise SystemExit(
f"refusing to start: coupling recommends {recommended}, not {LANE}"
)
export AGENT_LANE=free_shared_ok
python runner.py # should refuse when coupling.yml is a hard block
Where a free model lane still wins
Plenty of loops are sketches: classifying fixtures, drafting refactors against a public repo, or teaching the team how tool calling actually looks. Those loops should not wait for a purchase order, and they should not wait for a cluster either. I want them cheap, portable, and easy to throw away when the prompt is wrong.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. I mention MonkeyCode here only because its free model access and free server option are a concrete shared lane you can point this checklist at, not because a coupling test requires that product. If you try that lane, keep the complete() hole tiny and keep traces in your repository. The free server is useful when you want a process that is not your laptop, not when you want a new source of truth.
Would I put a customer corpus there? No. Would I put a read-only loop that I can replay on another endpoint? Yes, and I would keep the pytest from Step 3 in CI so the answer cannot drift quietly. That is the whole rehearsal: prove portability on a lane that is allowed to be imperfect.
Limitations, and who should skip this
This scoring does not measure model quality, and it does not promise that any free lane stays free. I am not publishing quotas, hardware lists, or model names I cannot verify, and you should not treat my weights as research. Teams under HIPAA, PCI, or similar rules should not use a shared free tenant for prompts or traces, period.
Skip this approach if your agent is already a production mutator with no stubbed tools. Skip it if you cannot export traces into JSON you own. Skip it if leadership wants a vendor demo more than a portable loop. A checklist will not save a design that stored secrets inside a web UI and called that an architecture.
The honest close is simple. Score coupling first, pick the cheapest lane that survives the hard blocks, and keep the loop in git. If a free shared option helps you rehearse that discipline on a non-sensitive task, use it as a rehearsal stage rather than a forever home.
Top comments (0)