DEV Community

Emery Li
Emery Li

Posted on

Fail Closed on Path Delay: A Repo Deadline for Local-First Agents

On a Tuesday commute, a backend engineer opened a failing helper that parsed inbound webhook timestamps. The local agent had already listed the test file, sketched a patch, and paused for one confirmation. The laptop stayed on battery while the office VPN dropped twice during a tunnel renewal. A default in the agent config then spilled the next generation call to a remote endpoint. The editor caret froze for almost two seconds because handshake and queue wait had already spent the interactive budget.

That stall is a placement failure rather than a model-quality failure, and it shows up whenever leftover tokens become the score. Interactive agent work dies on wall clock, not on unused context that a remote runtime still happens to offer. This article records a repo deadline file, a path-delay probe, and a closed default that keep secrets and tight turns on the laptop. A free server still participates when batch work actually wins on heat, battery, and a relaxed deadline.

Token surplus is the wrong score

Teams often treat unused context as waste, and that habit pushes generation off the laptop as soon as any remote option appears. The user-facing constraint is different, because an interactive turn has a deadline the caret will not forgive. A three-line patch and an overnight refactor do not share that deadline, even when both consume tokens from the same pool. Scoring the work on surplus capacity hides the freeze that the developer actually felt.

Recent public threads have argued about casual generation versus engineering discipline, and the useful part of that argument is measurement. Placement is engineering when the repo publishes a deadline, a secret glob, and a residency decision that a later incident review can read. Casual generation skips those files and then blames the model for a freeze the network already caused. Wall clock, secret residency, and offline behavior have to be written down before any remote default is flipped.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option belong in the batch class after secrets are stripped, not in the inline path that must fail closed on path delay.

Four clocks in one turn

An agent turn is the interval from the last developer input to the first trustworthy token or tool result. Four clocks consume that interval, and generation is only the third. Local prework covers file reads, grep, secret redaction, and prompt assembly on the laptop. Path delay covers DNS, TLS, queue wait, and the return path once the call leaves the machine.

Generation covers sampled tokens on whatever runtime accepted the prompt. Local postwork covers patch application, tests, and the next tool call in the same loop. Interactive placement should fail closed when path delay plus generation exceeds the remaining deadline after prework. Batch placement may ignore the caret deadline and optimize for throughput, thermal headroom, or remaining battery. Offline placement ignores the remote clock entirely and queues compacted work on disk until the network returns.

A deadline file in the repository

Label the following schema as a proposal, not a vendor SLA. Commit turn_deadline.json next to the agent config so every surface reads the same numbers. Reviewers can treat that file like linter config, because a silent remote default is harder to audit than a published deadline.

{
  "surfaces": {
    "inline": { "class": "interactive", "deadline_ms": 400 },
    "sidebar": { "class": "tool-loop", "deadline_ms": 1500 },
    "nightly": { "class": "batch", "deadline_ms": 60000 }
  },
  "secret_globs": [".env", "**/*.pem", "fixtures/customers/**"],
  "offline_policy": "local_queue",
  "fail_closed_on_path_delay": true
}
Enter fullscreen mode Exit fullscreen mode

Let D be the surface deadline in milliseconds, P the measured prework, R the measured path delay, and G a conservative generation estimate. Keep the job local when the turn is interactive and R is not zero. Keep it local when P + R + G exceeds D for tool-loop work. Consider a free server only when the class is batch, the secret globs match nothing in the payload, and R is finite.

The numeric examples are placeholders for the probe, not product benchmarks and not promises of latency. Operators should replace D and G with numbers taken from their own editor surface and their own tree.

Class Example D (ms) Path delay allowed Default residency
interactive 400 no local
tool-loop 1500 only if P + R + G <= D local first
batch 60000+ yes if secrets allow free-server candidate
offline n/a never local queue

Numbered workflow

The workflow below runs on the developer laptop before any agent default is flipped toward a remote runtime. Each step produces a number or a glob match that the later probe can log.

1. Publish the deadline per surface

Pick one deadline for inline completions and a looser one for sidebar chat. Write both numbers into turn_deadline.json so the runtime does not borrow chatbot patience for an editor caret. Review the file in pull requests the same way reviewers already treat formatter config. Surfaces that share a runtime still need separate classes, because a sidebar pause is not a frozen caret.

2. Measure prework on the real tree

Time the reads the agent actually performs, including git status, a bounded file listing, and secret scanning. Prework that already spends three hundred milliseconds leaves almost nothing for a remote handshake on a four-hundred-millisecond deadline. Re-run the measurement after large generated directories appear, because ignored build output still costs walk time. Record P beside the surface name rather than as a one-off memory.

3. Probe path delay without sending source

ICMP is not TLS, and a green ping does not prove an inference endpoint will answer inside the budget. Measure HTTPS time to an operator-supplied probe URL with a tiny JSON ping that carries no repository bytes. If that probe is already over budget, generation never gets a chance to look fast. Treat any probe exception as infinite path delay so the default stays local.

4. Classify secrets before any spill

API keys, .env files, customer fixtures, and private submodules stay on the laptop under the glob list. A remote job may receive a scaffold, a redacted prompt, or a failing test name, never the raw secret material. When a glob matches, residency stays local even if the batch class would otherwise win. This rule is the difference between a thermal offload and an egress incident.

5. Spill only the batch class

Long refactors, slow test matrices, and summaries of already-public trees can leave the laptop when battery, heat, or wall clock say so. Interactive confirmation turns stay local, including the single confirmation step after a patch is sketched. A free server that is idle still loses when path delay alone exceeds the caret deadline. Offline laptops queue compacted jobs on disk instead of blocking on DNS.

6. Record the decision beside the diff

Log class, deadline, P, R, G, and residency in a small JSON line next to the agent trace. A later incident review needs that ledger more than a screenshot of leftover tokens. If the residency is local, do not burn remote capacity just because unused tokens still look attractive. The log is also how a team notices that prework, not generation, has become the real cost.

Reproducible artifact: turn_budget.py

The script below is a local probe. It does not call a vendor API, does not claim hardware, and treats remote timing as an optional URL the operator supplies. Run it from the repository root after turn_deadline.json exists. Generation time is an operator estimate, labeled as such, because this article does not invent model latency.

#!/usr/bin/env python3
"""Turn-deadline budget probe. Timings are local measurements, not vendor SLAs."""

from __future__ import annotations

import argparse
import json
import statistics
import subprocess
import time
import urllib.request
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Optional


@dataclass
class Probe:
    deadline_ms: float
    prework_ms: float
    path_delay_ms: float
    gen_estimate_ms: float
    job_class: str
    residency: str
    reason: str


def time_prework(root: Path) -> float:
    start = time.perf_counter()
    subprocess.run(
        ["git", "-C", str(root), "status", "--porcelain"],
        check=False,
        capture_output=True,
    )
    counted = 0
    for _path in root.rglob("*.py"):
        counted += 1
        if counted >= 200:
            break
    return (time.perf_counter() - start) * 1000.0


def time_path(url: Optional[str], samples: int) -> float:
    if not url:
        return 0.0
    delays = []
    payload = b'{"ping": true}'
    headers = {"Content-Type": "application/json"}
    for _ in range(samples):
        req = urllib.request.Request(
            url, data=payload, headers=headers, method="POST"
        )
        t0 = time.perf_counter()
        try:
            with urllib.request.urlopen(req, timeout=5) as resp:
                resp.read(64)
        except Exception:
            return float("inf")
        delays.append((time.perf_counter() - t0) * 1000.0)
    return statistics.median(delays)


def classify(
    job_class: str, deadline: float, prework: float, path_delay: float, gen_ms: float
) -> tuple[str, str]:
    if job_class == "offline":
        return "local", "network partition; queue locally"
    if job_class == "interactive" and path_delay > 0:
        return "local", "interactive class forbids nonzero path delay"
    if prework + path_delay + gen_ms > deadline and job_class in {
        "interactive",
        "tool-loop",
    }:
        return "local", "deadline miss if remote path is included"
    if job_class == "batch" and path_delay != float("inf"):
        return "free-server-candidate", "batch class and finite path delay"
    if deadline - prework < gen_ms:
        return "local-or-split", "generation estimate exceeds remaining deadline"
    return "local", "default closed: stay on the laptop"


def main() -> None:
    parser = argparse.ArgumentParser(description="Probe a turn-deadline budget")
    parser.add_argument("--deadline-ms", type=float, default=400.0)
    parser.add_argument(
        "--job-class",
        choices=["interactive", "tool-loop", "batch", "offline"],
        default="interactive",
    )
    parser.add_argument("--gen-estimate-ms", type=float, default=180.0)
    parser.add_argument(
        "--ping-url",
        default="",
        help="Optional operator-supplied probe URL; send no repo bytes",
    )
    parser.add_argument("--samples", type=int, default=5)
    args = parser.parse_args()

    prework = time_prework(Path.cwd())
    path_delay = time_path(args.ping_url or None, args.samples)
    residency, reason = classify(
        args.job_class, args.deadline_ms, prework, path_delay, args.gen_estimate_ms
    )
    probe = Probe(
        deadline_ms=args.deadline_ms,
        prework_ms=round(prework, 2),
        path_delay_ms=path_delay
        if path_delay == float("inf")
        else round(path_delay, 2),
        gen_estimate_ms=args.gen_estimate_ms,
        job_class=args.job_class,
        residency=residency,
        reason=reason,
    )
    print(json.dumps(asdict(probe), indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Example local-only run for an inline surface:

python3 turn_budget.py --deadline-ms 400 --job-class interactive --gen-estimate-ms 180
Enter fullscreen mode Exit fullscreen mode

Example batch candidate. The operator supplies a probe URL, and the script still sends only {"ping": true}:

python3 turn_budget.py --deadline-ms 60000 --job-class batch --gen-estimate-ms 8000 --ping-url "$PROBE_URL"
Enter fullscreen mode Exit fullscreen mode

Offline class, which must never wait on DNS:

python3 turn_budget.py --job-class offline --deadline-ms 400
Enter fullscreen mode Exit fullscreen mode

Keep the JSON line next to agent config. When residency is local, that turn does not leave the machine even if a remote runtime is idle.

When a free server still wins

A free server wins when the job is batch, the path delay is finite, and the payload has been reduced to a scaffold. Battery-constrained laptops, thermally throttled fans, and overnight test matrices are the usual triggers. Interactive patches, secret-bearing fixtures, and any moment the network is a partition are not. Idle remote capacity does not cancel TLS, queue wait, or a dropped VPN.

That is the only product-shaped pairing this workflow needs: free model access for the batch class, and a free server option when the laptop should yield after the budget says so. The probe does not depend on a particular vendor, and the same ledger works with any remote runtime the operator already trusts. Readers who want to try that pairing can start from current public product materials and apply the deadline file first, rather than flipping every turn to remote.

Secrets, offline, and latency as one policy

Latency is the loud failure that freezes the caret during a commute. Secrets are the quiet failure that turns a convenient spill into an incident. Offline is the failure that makes remote defaults look clever until DNS stops resolving on a train. A local-first budget treats all three as one policy rather than three separate dashboards.

Interactive turns require zero path delay. Any spill requires glob-based redaction before the prompt is assembled. A partition requires a disk queue rather than a hung HTTPS client. Do not ship the working tree to make generation look cheaper on paper. Ship a failing test name, a stack frame, or a redacted schema, and keep the signing key on the laptop.

Limitations and who should skip this

The probe measures this machine, this tree, and an optional URL. It does not measure model quality, vendor quota, or hardware, and it must not be quoted as a benchmark. The generation estimate is an operator input. Infinite path delay is reported on any probe exception, which is conservative and can hide a mistyped URL. The Python file walk stops at two hundred paths so the probe stays bounded, which can understate prework on huge monorepos.

Skip this approach when the organization already forbids any egress, when the editor surface has no local runtime at all, or when the work is hard real-time below operating-system scheduling noise. Skip it for medical, payroll, or similarly regulated corpora unless a security review already defined an allow-list. Teams with a private, low-latency inference box on the same LAN should measure that box as local-equivalent rather than as a free-server spill.

The method also fails if the agent issues hidden tool calls after the first token. Budget the whole loop, not the first completion. Schema checks, tests, and patch application belong in postwork, and they often dominate the wall clock that the caret actually feels.

Closing

Interactive agents are late when the wrong clock is optimized. Publish a deadline in the repo, fail closed on path delay, keep secrets and tight turns on the laptop, and let a free server take the batch class only after the probe says the path still fits. Token surplus is not a reason to miss a caret blink.

Top comments (0)