A backend engineer boarded a late train with a half-finished agent run still sitting in a terminal. The laptop held repository tokens inside a local vault, and the next tool call needed a long compile that would wake every fan. Cellular data flickered between two bars and airplane mode, so a naive always-remote setup would stall without warning. An always-local setup would cook the chassis and drain the battery before the next station arrived.
That commute is the whole placement problem in miniature for teams that still ship local-first agents. Some work must never leave the disk, because secrets and customer fragments live beside the checkout. Some work cannot finish without extra cycles, because the host is already warm, battery constrained, or thermally throttled. The network is not a promise, and latency is a measured round trip that either beats local load time or does not.
Placement belongs in the control plane, not in a prompt preface or a vendor checkbox. The rest of this write-up treats four signals as executable policy, then ships a decision table, a small Python gate, and tests that lock the lanes. Readers can delete every product name and still keep a working local-versus-remote split.
Always-local and always-remote both fail
Always-local feels virtuous until cold weights delay the first useful token past the user's attention span. Always-remote feels scalable until an API key, a private patch, or a dead tunnel turns the agent into a brick. Teams that wrap every tool call around a single endpoint learn this during incidents, travel days, and laptop sleep. The useful split is which lane may carry which payload under a declared latency budget, while secrets stay on the machine.
A placement gate should be boring on purpose. It reads four signals, returns one lane, and refuses to improvise when those signals conflict. Folklore routers that mix job type, queue depth, and model brand in one if chain are how secrets leak into retry buffers.
Four signals that actually change the lane
The first signal is latency, measured as a short probe rather than a remembered anecdote from last week. Local load time includes disk, memory pressure, and model spin-up on a warm chassis; remote time includes DNS, TLS, and one cheap health request. If the probe fails, the remote lane is absent, which is a different state from being merely slow. Absence must fail closed instead of failing into a queued upload.
The second signal is secret class, and it is stricter than a checkbox that says the user typed a password once. Job envelopes that still contain tokens, private keys, customer records, or unpublished patches must stay resident on disk. Envelopes that have been stripped to a public issue number, a file hash, or a redacted stack trace may travel. Unknown classes are treated as vault material, because a guessed label is how private source rides a burst lane.
The third signal is offline capability, meaning the job has a local fallback that still makes measurable progress. A lint pass, a unit test, or retrieval over an on-disk index can continue when the WAN dies between stations. A long synthesis the laptop cannot finish does not have a local fallback, and the gate should say so without pretending. Progress that only exists as a remote promise is not offline capability.
The fourth signal is burst need, which is host capacity rather than a preference for someone else's GPUs. Burst means the battery is below a local policy, the chassis is thermally limited, or the work exceeds a declared local minute cap. Burst is the only moment a free remote server is allowed to win, and only for envelopes that already passed the secret check. Everywhere else, local-first remains the default because the bytes never move.
When those four disagree, the policy is conservative on purpose. Secrets pin the job local even if burst is screaming and the probe looks perfect. Offline work without a local fallback fails closed instead of copying a payload into a crash log. Latency that cannot be measured is treated as remote-unavailable, not as remote-fast.
A decision table the runtime can execute
The table is the artifact the code must obey. Rows are evaluated in order, and the first match wins, which keeps the implementation free of nested folklore.
| Order | Envelope may leave disk | Offline fallback exists | Remote probe OK | Burst needed | Lane |
|---|---|---|---|---|---|
| 1 | no (vault or private source) | any | any | any | local_only |
| 2 | yes (sanitized) | yes | no | any | local_fallback |
| 3 | yes | no | no | any | fail_closed |
| 4 | yes | any | yes | no | local_preferred |
| 5 | yes | any | yes | yes | remote_burst |
local_preferred still runs on the laptop because the host can finish the work inside its own budget. remote_burst is the only lane that may spend a remote runtime, and it never receives the original vault. fail_closed is a feature: it beats a silent retry that serializes a secret into a debug dump. Teams should log the lane name and the probe reason, never the payload body.
Implementation: a small placement gate
The following module is meant to be saved beside the agent wrapper and executed locally. It does not call a vendor, and remote execution stays a stub so readers can swap their own transport later. Treat the probe as a health check, not as a benchmark study.
# placement_gate.py
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Optional
import json
import time
import urllib.error
import urllib.request
class Lane(str, Enum):
LOCAL_ONLY = "local_only"
LOCAL_FALLBACK = "local_fallback"
LOCAL_PREFERRED = "local_preferred"
REMOTE_BURST = "remote_burst"
FAIL_CLOSED = "fail_closed"
@dataclass(frozen=True)
class JobEnvelope:
job_id: str
secret_class: str # vault | private_source | sanitized
has_local_fallback: bool
burst_needed: bool
payload: str
@dataclass(frozen=True)
class ProbeResult:
ok: bool
rtt_ms: Optional[float]
reason: str
BANNED_FRAGMENTS = (
"begin private",
"api_key",
"authorization:",
"x-api-key",
"secret=",
)
def classify_secret(envelope: JobEnvelope) -> bool:
"""Return True only when the envelope is allowed to leave the disk."""
if envelope.secret_class in {"vault", "private_source"}:
return False
if envelope.secret_class != "sanitized":
return False
lowered = envelope.payload.lower()
return not any(fragment in lowered for fragment in BANNED_FRAGMENTS)
def redact_for_wire(envelope: JobEnvelope) -> JobEnvelope:
if not classify_secret(envelope):
raise ValueError("refusing to copy a non-sanitized envelope onto the wire")
# Keep public identifiers; drop anything that looks like assignment of secrets.
kept_lines = []
for line in envelope.payload.splitlines():
lower = line.lower()
if any(fragment in lower for fragment in BANNED_FRAGMENTS):
continue
kept_lines.append(line)
return JobEnvelope(
job_id=envelope.job_id,
secret_class="sanitized",
has_local_fallback=envelope.has_local_fallback,
burst_needed=envelope.burst_needed,
payload="\n".join(kept_lines).strip(),
)
def probe_remote(url: str, timeout_s: float = 1.5) -> ProbeResult:
started = time.perf_counter()
try:
req = urllib.request.Request(url, method="HEAD")
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
_ = resp.status
rtt = (time.perf_counter() - started) * 1000.0
return ProbeResult(ok=True, rtt_ms=rtt, reason="probe_ok")
except (urllib.error.URLError, TimeoutError, ValueError) as exc:
return ProbeResult(ok=False, rtt_ms=None, reason=type(exc).__name__)
def place(envelope: JobEnvelope, probe: ProbeResult) -> Lane:
if not classify_secret(envelope):
return Lane.LOCAL_ONLY
if not probe.ok and envelope.has_local_fallback:
return Lane.LOCAL_FALLBACK
if not probe.ok:
return Lane.FAIL_CLOSED
if envelope.burst_needed:
return Lane.REMOTE_BURST
return Lane.LOCAL_PREFERRED
def decide(envelope: JobEnvelope, probe_url: str) -> dict:
probe = probe_remote(probe_url)
lane = place(envelope, probe)
wire = None
if lane is Lane.REMOTE_BURST:
wire = redact_for_wire(envelope).payload
return {
"job_id": envelope.job_id,
"lane": lane.value,
"probe_ok": probe.ok,
"probe_reason": probe.reason,
"wire_payload": wire,
}
if __name__ == "__main__":
demo = JobEnvelope(
job_id="job-4412",
secret_class="sanitized",
has_local_fallback=True,
burst_needed=True,
payload="public issue 4412: flaky timer in worker",
)
print(json.dumps(decide(demo, "https://example.com"), indent=2))
A single HEAD request on a commute network is enough to distinguish remote-exists from remote-is-a-fantasy. It is not enough to claim that one runtime is faster than another, and this article does not publish winners, quotas, or hardware comparisons. Point probe_url at a health endpoint the team actually operates, not at a random third-party page.
Numbered workflow to adopt the gate
- Inventory secret classes in the agent store, and map each tool to
vault,private_source, orsanitizedbefore any model call. - Strip remote envelopes in a pure function that drops headers, tokens, and unpublished hunks before DNS is even touched.
- Add a probe URL that belongs to the team, and fail closed when DNS, TLS, or timeouts fire on the train network.
- Declare a local burst policy from battery percentage, thermal state, or a simple elapsed-minute cap the host already knows.
- Route only sanitized burst jobs to a remote runtime, and keep the original envelope on disk for a later local resume.
- Log lane decisions without logging payload bodies, then add tests for the five table rows before enabling traffic.
Step five is the only place a free remote option belongs in this design. When the laptop cannot absorb burst work, a remote runtime with free model access and a free server option can take the already-redacted job. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The gate does not depend on that runtime; it only needs a transport that accepts a redacted envelope and ignores everything in local_only.
Tests that lock the policy
# test_placement_gate.py
from placement_gate import (
JobEnvelope,
Lane,
ProbeResult,
classify_secret,
place,
redact_for_wire,
)
DOWN = ProbeResult(ok=False, rtt_ms=None, reason="timeout")
UP = ProbeResult(ok=True, rtt_ms=84.0, reason="probe_ok") # fixture, not a measurement claim
def env(**kwargs) -> JobEnvelope:
base = dict(
job_id="job-1",
secret_class="sanitized",
has_local_fallback=True,
burst_needed=False,
payload="public issue 4412: flaky timer in worker",
)
base.update(kwargs)
return JobEnvelope(**base)
def test_vault_never_leaves_even_when_burst_and_probe_are_ready():
job = env(secret_class="vault", burst_needed=True, payload="token=not-for-wire")
assert place(job, UP) is Lane.LOCAL_ONLY
def test_private_source_stays_local_when_offline():
job = env(secret_class="private_source", has_local_fallback=False)
assert place(job, DOWN) is Lane.LOCAL_ONLY
def test_offline_sanitized_job_uses_local_fallback():
job = env(has_local_fallback=True)
assert place(job, DOWN) is Lane.LOCAL_FALLBACK
def test_offline_without_fallback_fails_closed():
job = env(has_local_fallback=False)
assert place(job, DOWN) is Lane.FAIL_CLOSED
def test_no_burst_stays_local_even_if_remote_is_healthy():
job = env(burst_needed=False)
assert place(job, UP) is Lane.LOCAL_PREFERRED
def test_sanitized_burst_may_use_remote():
job = env(burst_needed=True)
assert place(job, UP) is Lane.REMOTE_BURST
def test_payload_with_key_material_is_not_sanitized():
job = env(payload="Authorization: Bearer demo")
assert classify_secret(job) is False
assert place(job, UP) is Lane.LOCAL_ONLY
def test_redact_refuses_vault_envelopes():
job = env(secret_class="vault", payload="not-for-wire")
try:
redact_for_wire(job)
except ValueError:
return
raise AssertionError("vault envelopes must not be copied for the wire")
Run the contract with a boring toolchain so the policy stays executable after the next refactor:
python -m pip install pytest
python -m pytest test_placement_gate.py -q
python placement_gate.py
The tests are the contract. If a future wrapper grows extra states for model brands or queue depth, these rows should still pass before anyone debates where tokens are billed. The 84.0 millisecond figure is a fixture for the healthy probe, not a recorded commute measurement and not a product claim.
Limitations and who should skip this
The gate does not make a remote vendor safe for regulated data. If a contract forbids even redacted stack traces from leaving the building, remote_burst must be compiled out, not merely skipped at runtime. The probe is not a performance study, and this article does not publish latency winners, token allowances, hardware sizes, or promises that a free server will remain free.
Teams without a local secret store should not enable remote_burst at all, because they cannot prove the envelope is sanitized. Teams whose entire workload is a short lint loop on a cool machine should stay on local_preferred and skip the extra moving parts. People who need guaranteed offline completion must build the fallback first; the gate will not invent a local compiler, a local index, or a local model spin-up path.
A free server wins only in the remaining slice: sanitized payload, healthy probe, and a host that honestly cannot finish the work. Latency still matters in that slice, but it is checked as presence, not as a leaderboard. Secrets still win every conflict, which is the entire point of keeping residencies local while spilling only cycles.
Closing
Placement is a control-plane decision, not a personality trait of the model sitting under the agent loop. The train story ends the same way most incidents end: the engineer either kept the vault on disk and finished a local fallback, or waited for a probe and spilled a redacted burst job. Drop the module into an agent wrapper, run the pytest file, and refuse any lane that the table does not name.
Developers who already pin secrets to local disks can attach only the sanitized remote_burst lane to that free server option and compare outcomes against local_preferred on the same public jobs.
Top comments (1)
The "absence is a different state from slow" point is the one most routers get wrong. We run a fleet that splits between a VPS and a local box, and our early versions degraded to a queue upload whenever the probe timed out — exactly the retry-buffer secret leak you warn about.
How often do you re-probe latency? Our problem is that a route can be 40ms at boot and 900ms twenty minutes later under load, so a cached lane decision goes stale fast. Are you probing per job dispatch, or on a timer with hysteresis?