Free agent compute stays the right default only while idle cost hurts more than contention and data gravity. I refuse to pick a runtime because a landing page called the model or the box free. I wait until five written tradeoffs flip, because a sticker never describes the loop I actually run. Would you rather pay for a quiet machine that sits idle, or borrow a busy one you cannot fully explain?
Price is not a comparator
Most teams compare a free shared runtime with a paid or self-hosted box as if both were the same product. They are not, and the mix-up shows up the first time a tool call waits on somebody else's retry storm. One option taxes you with queues, noisy neighbors, and a control plane you do not own. The other taxes you with idle capacity, patching, and a bill that continues when nobody is prompting.
I keep a five-row sheet beside the agent repo instead of a vibe about vendor kindness. Each row is a tradeoff I can probe with a command, not a feeling. Why would a twelve-factor matrix help if nobody updates it after the first demo?
Artifact: the five-row tradeoff sheet
Copy this file into the repo and refuse to choose compute until every row has a number. I treat missing scores as permission only for throwaway prompts, never for anything that touches secrets. The direction is simple: a low weighted leave-free average means the free column still fits this week.
# tradeoff_sheet.yaml
# Proposal / unexecuted template. Fill before picking free shared compute
# or a paid / self-hosted box.
# score_free_better: 1-5, where 5 means free/shared clearly wins this row.
# The scorer inverts that into a "leave free" average.
schema_version: 1
agent: "example-triage-bot"
rows:
- id: idle_vs_contention
prompt: "Does idle spend hurt more than queue delay this week?"
weight: 2
score_free_better: null
notes: ""
- id: credential_locality
prompt: "Must tools and secrets stay on a network you route?"
weight: 3
score_free_better: null
notes: ""
- id: scheduler_explainability
prompt: "Can you explain a slow run without a vendor timeline?"
weight: 2
score_free_better: null
notes: ""
- id: tool_roundtrip
prompt: "Does the tool hop dominate model time on a realistic path?"
weight: 2
score_free_better: null
notes: ""
- id: promotion_cost
prompt: "How expensive is moving this agent later if the free lane changes?"
weight: 1
score_free_better: null
notes: ""
A weighted total near the free side is not a moral victory for the cheaper lane. It is a temporary fit that I re-score when tools, secrets, or concurrency change. Do you re-score when the agent gains a write tool, or only when the invoice arrives?
Fill the sheet with probes, not opinions
I do not type a five because the runtime felt snappy during lunch on a quiet network. I run a small probe that records the parts of the loop I can observe from my laptop. The script below is a proposal, not a benchmark from a production fleet, and it will not prove a vendor. If you cannot freeze the prompt, you are not comparing compute, you are comparing moods.
1. Record idle versus contention on a realistic path
Start with one frozen prompt, one frozen tool stub, and three sequential runs rather than a heroic load test. A realistic agent path is closer to a few slow tool hops than to an HTTP flood against health checks. API-style flood tests still matter for gateways, but they hide the wait your agent actually feels.
# probe_loop.py
# Proposal: local comparison harness, not a published benchmark.
import json, time, urllib.request
PROMPT = {"messages": [{"role": "user", "content": "summarize ticket 1842"}]}
def timed_post(url: str, payload: dict) -> dict:
data = json.dumps(payload).encode()
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("content-type", "application/json")
t0 = time.perf_counter()
with urllib.request.urlopen(req, timeout=60) as resp:
body = resp.read()
ms = (time.perf_counter() - t0) * 1000
return {"ms": round(ms, 1), "bytes": len(body), "url": url}
if __name__ == "__main__":
targets = [
("free_shared", "http://127.0.0.1:8080/v1/chat"),
("self_hosted", "http://127.0.0.1:8081/v1/chat"),
]
rows = []
for name, url in targets:
samples = [timed_post(url, PROMPT) for _ in range(3)]
rows.append({"target": name, "samples": samples})
print(json.dumps(rows, indent=2))
Point those URLs at a local stub first so the harness fails on your desk, not in a shared queue. If the free lane is remote, I still run the same payload so the comparison stays about the loop. Three samples will not settle a capacity argument, and that restraint is the point of the sheet.
2. Ask where credentials actually live
If the agent must mint a short-lived cloud token, I do not send that minting step to a shared box I do not route. Credential locality is a veto, not a score I average away with friendlier rows on the sheet. Can a free server hold the prompt while the tool stays on a host I already own? That split is allowed, and it is often the only honest architecture for a first pass.
# Proposal: prove the tool host, not the marketing diagram.
curl -sS -D - http://127.0.0.1:9090/tool/whoami -o /tmp/whoami.json
python -c "import json; print(json.load(open('/tmp/whoami.json')))"
Read the response as a placement proof, because a missing network identity is already a locality failure. I will not average that failure with a pretty latency number from a toy prompt. If whoami cannot name the network, the free column should not receive the tool.
3. Demand an explainable slow run
I keep the request id, the tool names, and wall-clock per hop in a local file that I control. If a free runtime cannot give me those three fields, the explainability row moves against free immediately. How would you debug a stuck loop if the only timeline lives in someone else's UI?
# Proposal: local hop log. Replace the pipeline with your tracer later.
mkdir -p ./runs
python probe_loop.py | tee "./runs/$(date -u +%Y%m%dT%H%M%SZ).json"
Commit the hop log format even if you change vendors later, because portable traces are part of promotion cost. I want the same JSON keys on both columns of the comparison so a later move stays boring. That habit is cheaper than rewriting dashboards after the free lane stops fitting.
4. Separate model time from tool round-trip
Teams still treat "the model was slow" as one number, and that habit hides the real tax. I split the trace, because a remote model plus a local tool can lose to a smaller model beside the data. Does cheaper access help if every tool hop crosses a public path you cannot pin? If the tool time dominates, I colocate tools with data first and only then reopen the model question.
5. Price the later move, not the first weekend
Promotion cost is the row people skip because the first weekend felt easy on a free endpoint. If swapping the endpoint later means rewriting tool adapters, the free lane is a trap even when it scores well today. I want the agent to take a base URL and a key from the environment, and nothing more clever than that.
# config.py — proposal for a portable agent entrypoint
import os
def runtime():
return {
"base_url": os.environ["AGENT_BASE_URL"],
"api_key": os.environ.get("AGENT_API_KEY", ""),
"timeout_s": float(os.environ.get("AGENT_TIMEOUT_S", "60")),
}
If that function already needs vendor-specific headers for basic chat, the promotion row should not be a one. Portability is a property of the client, not a promise I wait to hear from a pricing page. Would you rebuild the adapter on a deadline, or would you rather have paid that cost in the first commit?
Score it like a decision, not a vibe
I convert the yaml into a weighted total with this tiny script so the argument stays numeric and small. Treat the output as a conversation starter for the team, not as a scientific ranking of vendors. The threshold is a team agreement, and I label it as a proposal rather than a law.
# score_sheet.py
# Proposal: weighted helper for tradeoff_sheet.yaml
import sys
import yaml
FREE_THRESHOLD = 3.2 # below: free/shared still fits; above: prefer paid/isolated
def main(path: str) -> None:
doc = yaml.safe_load(open(path))
weighted = 0.0
weights = 0.0
missing = []
for row in doc["rows"]:
score = row.get("score_free_better")
if score is None:
missing.append(row["id"])
continue
leave_free = 6 - int(score)
weighted += leave_free * row["weight"]
weights += row["weight"]
if missing:
raise SystemExit(f"unscored rows: {missing}")
avg = weighted / weights
decision = "stay_free_shared" if avg < FREE_THRESHOLD else "prefer_paid_or_owned"
print({"leave_free_avg": round(avg, 2), "decision": decision})
if __name__ == "__main__":
main(sys.argv[1])
Run the scorer only after every row is filled, because missing values are how a guess becomes a fake number. A default of three is how teams launder opinions into something that looks like evidence. If the decision says stay free, I still keep the portable entrypoint so a later flip stays boring. If it says leave, I move tools first and the model second, because data gravity rarely follows a DNS change.
python -m pip install pyyaml
python score_sheet.py tradeoff_sheet.yaml
Are you moving the model because it is fashionable this month, or because the five rows actually flipped on paper? I re-run the scorer when a write tool appears, not when a blog post declares a new default. Fashion is not a row on the sheet, and I refuse to add it just to feel current.
Where a free model and free server lane fits
I needed a concrete free-side target while writing the sheet, not a slogan about unlimited capacity. A comparison against an imaginary endpoint teaches nothing about credential locality or hop logs you can keep. The free column has to be a real URL I can put behind the same client config.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode offers free model access and a free server option, which can stand up the free column without a box on day one. I do not treat that lane as a quota, a hardware spec, or a forever plan, because those claims do not belong here. I treat it as one endpoint I can put behind AGENT_BASE_URL while the five rows are still scoring toward free.
That is the whole product role in this workflow, and I will not stretch it into a capacity story. The sheet still decides, and the portable entrypoint still matters more than the brand on the box. If credential locality or scheduler explainability fails, I keep the tools on a host I route.
When the sheet says no
Skip this approach if the agent can write to production data, mint long-lived credentials, or handle regulated records you cannot put on shared compute. Skip it if you cannot freeze a prompt and a tool stub long enough to compare two endpoints honestly. Skip it if your org already owns quiet capacity beside the tools, because then the idle tax is already paid.
The probes above are local and unlabeled as fleet evidence on purpose, not because I forgot to add charts. They will not certify a vendor, win an argument about tokens, or replace an eval you can replay later. They exist so the free-versus-paid choice stops being a screenshot of a pricing table.
Would I run a customer-facing agent on the free column after a single green lunchtime probe? I would not, and the written sheet is how I keep that answer boring on purpose. I re-score the five rows the moment the agent gains a write tool, and I move before the first incident rather than after it.
If you need a free-model and free-server endpoint to drop into AGENT_BASE_URL while you fill the yaml, MonkeyCode can be that free column. Keep the sheet in the repo either way, because the tradeoff sheet should outlive whichever endpoint you try first. I would rather you copy the yaml into git than copy a vendor diagram into a slide.
Top comments (0)