Scene: a staging bot opens a pull request at 09:12. The plan JSON names a working directory and a retry ceiling. None of those keys live in the repository.
The model wrote them because the prompt stayed quiet. The job still looked cheap to the reviewer. The reviewer almost scheduled it on a shared free host.
Cheap inference hides expensive assumptions inside ordinary plan files. Silent fields become invented runtime on the next host. A free host then executes those guesses next to other tenants.
This field guide lists when that path should fail. It also lists better homes for the same work.
Uncited runtime, in plain terms
Uncited runtime is a concrete value the agent emitted. No repo file supplied that value. No secret store and no checklist approved it.
The value often looks boring in review. Boring values still steer process, disk, and network. Shared hosts then amplify a quiet guess.
Common inventions that pass a weak schema
-
/tmp/agent-workas a working directory -
UTCas a timezone nobody pinned -
retries: 30with no budget owner -
localhost:6379as a cache the app lacks -
chmod 777on a scratch path -
curlto an undocumented health URL
Each line can satisfy a loose JSON schema. Each line can still be false. False runtime on a free host travels farther than a laptop scratch folder.
Where free model access fits this gate
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode offers free model access and a free server option. Those two availability facts matter for scheduling. They do not weaken the allowlist.
Cheap planning invites another pass over silent fields. A free server invites another retry of the same guess. Silence starts to look like consent.
The checks below still stand if that product is absent. Any team with a plan file can run them. The product is only one place the brake can sit.
Red flags before the job is scheduled
Refuse the free host when any flag below is true.
- The plan fills paths the repository never named.
- The plan sets locale, timezone, or umask without a manifest.
- The plan invents hostnames, ports, or health URLs.
- The plan sets retries above the checked-in budget file.
- The plan requests egress the network allowlist omits.
- The plan writes credentials into argv, env, or log format.
- The plan claims a container user the image never defined.
- The job class is incident, prod-adjacent, or secret-adjacent.
One flag is enough to stop the schedule. Two flags mean the plan is fiction.
Decision table
| Job class | Uncited runtime present | Allowed on free host | Better home |
|---|---|---|---|
| Docs lint | No | Yes | Free host or laptop |
| Unit tests with fixtures | No | Yes | CI runner with cache |
| Agent plan with filled paths | Yes | No | Local fixture VM |
| Secret rotation dry run | Either | No | Isolated paid runner |
| Incident replay | Either | No | Locked debug box |
| Load probe with egress | Either | No | Named staging cluster |
| Eval of recorded traces | No, traces redacted | Maybe | Ephemeral runner |
Maybe still needs a human owner. The table is a brake, not a waiver.
Artifact: an assumption gate for plan files
The gate reads a plan file and an allowlist. It fails CI when the plan cites nothing. Treat the code as an unexecuted example. Teams must adapt paths and policy.
Allowlist file
# agent-runtime.allow.yaml
working_directory:
- "."
- "./tmp/ci"
timezone:
- "UTC"
max_retries: 3
allowed_hosts:
- "api.internal.example"
forbidden_keys:
- "password"
- "api_key"
- "token"
- "secret"
job_classes_blocked_on_free_host:
- "incident"
- "secret-adjacent"
- "prod-replay"
Plan file the agent must emit
{
"job_class": "docs-lint",
"working_directory": "./tmp/ci",
"timezone": "UTC",
"retries": 2,
"egress_hosts": [],
"env": {},
"argv": ["python", "-m", "ruff", "check", "."]
}
Gate script
#!/usr/bin/env python3
"""Fail CI when an agent plan emits uncited runtime."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import yaml # install pyyaml in CI; do not assume a global copy
FALLBACK_NEEDLES = ("password", "api_key", "token", "secret")
def load_yaml(path: Path) -> dict:
with path.open("r", encoding="utf-8") as handle:
data = yaml.safe_load(handle)
if not isinstance(data, dict):
raise ValueError(f"allowlist must be a mapping: {path}")
return data
def flatten_strings(value: object) -> list[str]:
if value is None:
return []
if isinstance(value, str):
return [value]
if isinstance(value, (int, float, bool)):
return [str(value)]
if isinstance(value, list):
out: list[str] = []
for item in value:
out.extend(flatten_strings(item))
return out
if isinstance(value, dict):
out: list[str] = []
for key, item in value.items():
out.append(str(key))
out.extend(flatten_strings(item))
return out
return [str(value)]
def main(plan_path: str, allow_path: str) -> int:
plan = json.loads(Path(plan_path).read_text(encoding="utf-8"))
allow = load_yaml(Path(allow_path))
failures: list[str] = []
job_class = plan.get("job_class")
blocked = set(allow.get("job_classes_blocked_on_free_host", []))
if job_class in blocked:
failures.append(f"job_class {job_class} is banned on the free host")
cwd = plan.get("working_directory")
if cwd not in allow.get("working_directory", []):
failures.append(f"working_directory uncited or denied: {cwd}")
tz = plan.get("timezone")
if tz not in allow.get("timezone", []):
failures.append(f"timezone uncited or denied: {tz}")
retries = plan.get("retries", 0)
max_retries = allow.get("max_retries", 0)
if retries > max_retries:
failures.append(f"retries {retries} exceed budget {max_retries}")
allowed_hosts = set(allow.get("allowed_hosts", []))
for host in plan.get("egress_hosts", []):
if host not in allowed_hosts:
failures.append(f"egress host uncited or denied: {host}")
blob = " ".join(flatten_strings(plan)).lower()
needles = allow.get("forbidden_keys", FALLBACK_NEEDLES)
for needle in needles:
if str(needle).lower() in blob:
failures.append(f"secret-adjacent key leaked into plan: {needle}")
required = ("job_class", "working_directory", "timezone", "retries")
for key in required:
if key not in plan:
failures.append(f"missing required key {key}; refuse inferred fill")
if failures:
print("assumption gate failed:")
for item in failures:
print(f"- {item}")
return 1
print("assumption gate passed")
return 0
if __name__ == "__main__":
if len(sys.argv) != 3:
print("usage: assumption_gate.py PLAN.json ALLOW.yaml", file=sys.stderr)
raise SystemExit(2)
raise SystemExit(main(sys.argv[1], sys.argv[2]))
CI step
# .github/workflows/agent-assumption-gate.yml
name: agent-assumption-gate
on:
pull_request:
paths:
- "agent/plans/**"
- "agent-runtime.allow.yaml"
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pyyaml
- run: python tools/assumption_gate.py agent/plans/current.json agent-runtime.allow.yaml
Run the same command on a laptop before review.
python tools/assumption_gate.py agent/plans/current.json agent-runtime.allow.yaml
echo $?
A non-zero exit is the entire control. The free host never receives the plan.
Better alternatives when the gate fails
Move the work. Do not loosen the allowlist in a hurry.
- Replay the plan on a laptop with recorded fixtures.
- Use an ephemeral CI runner that dies with the job.
- Pin timezone and paths in a checked-in manifest.
- Strip egress and rerun as a pure file function.
- Hand the incident box to a human operator.
Free model access can still draft the next plan. The execution host stays on a different side of the brake. That split is the actual control.
Exit criteria
Leave the free host, and stay off it, when any line holds.
- The plan needed a default the allowlist does not name.
- Prior logs showed inferred paths or inferred hosts.
- Retries grew without a budget change in git.
- The job class moved into incident or secret-adjacent work.
- A reviewer cannot point to the file behind each value.
Return only after every runtime field has a cited source. A citation is a repo path, a secret name, or a ticket. Model habit is not a source.
Who should not use this approach
Skip this gate in a few honest cases.
- Throwaway scripts with no network and no secrets.
- Workshops that run on paper plans, not hosts.
- Teams with no plan file and no will to add one.
- Workloads already locked in a single-tenant box.
The gate adds friction on purpose. Friction is wasted when the blast radius is a scratch folder.
Limitations
The script does not prove the plan is correct. It only proves the plan stayed inside a list. Homographs can still sneak through an allowlist. A host alias can match and still be wrong.
YAML comments are not policy. The CI example does not pin action SHAs. Teams should pin those SHAs before relying on the workflow.
This article does not claim quotas, model names, hardware, or uptime. Those figures go stale fast. Operators should read current product docs at publish time.
Closing
Uncited runtime is not a style issue. It is unreviewed process, disk, and network. A free host multiplies that guess.
The cheap path is the one that should fail first. Keep banned job classes on isolated compute after the gate fires.
Top comments (0)