Shared agent batches fail during quiet hours, not compile time. Teams that skip a named window leak work into on-call. A one-page quiet-hours card stops that leak.
The failure looks ordinary from the outside at first. An agent starts a long refactor on Friday evening. Shared free compute keeps running without a human owner.
Monday morning holds a half-applied diff and a dead session. The on-call engineer inherits a job nobody can replay. Chat logs then become the only map of intent.
A kitchen closes with a last-ticket time each night. Burners stay hot, but cooks refuse a new roast. Agent compute needs the same last-ticket rule.
The card is that rule, written once for the wiki. Anyone can reject a batch that lacks a live card. The ritual is short, and it survives tool changes.
Ownership is a clock
Long prompts do not create ownership on a shared box. Ownership lives in a named person and a clock. When those two are missing, the batch becomes unattended work.
Unattended work collides with change freezes and half-reviewed diffs. It also collides with pages that fire after midnight. The missing artifact is operational, not statistical.
Recent talk about AI evals skips this floor entirely. Teams argue about scores while jobs run past freeze. A duty officer can reject a card in thirty seconds.
The duty officer owns the quiet window for one calendar day. That person may abort any shared agent job on sight. The batch owner files the card before the first request.
The reviewer reads the card before any agent diff lands. The on-call engineer does not hunt for a missing owner. Handoff stays boring on purpose, and that is the point.
The wiki holds the canonical card for the day. The job directory holds a copy beside the prompt pack. The ticket holds a receipt hash of the same text.
A missing receipt hash is a failed handoff, not a style issue. Reviewers should bounce the ticket until the digest appears. The bounce is cheaper than a Monday autopsy.
The wiki card
Paste the block below into the team wiki. Keep the pasted card to one wiki screen. Do not grow that card into a process novel.
# quiet-hours-card.yaml — sample only, adapt names
schema: quiet-hours-card/v1
date: "2026-09-20"
timezone: "America/Los_Angeles"
window_start: "09:00"
window_end: "17:00"
duty_officer: "alex"
batch_owner: "sam"
reviewer: "riley"
repo: "billing-api"
goal: "rename invoice status enum without behavior change"
freeze_overlap: false
data_class: "no-prod-secrets"
stop_condition: "first failing test or window_end"
abort_contact: "pagerduty://agent-batch"
Stamp a copy into the run directory before launch. Hash the file and paste the digest into the ticket. The commands below keep that stamp cheap.
mkdir -p .agent-run
cp "$WIKI_EXPORT/quiet-hours-card.yaml" .agent-run/quiet-hours-card.yaml
git hash-object .agent-run/quiet-hours-card.yaml | tee .agent-run/receipt.sha
gh issue comment "$TICKET" --body "quiet-hours $(cat .agent-run/receipt.sha)"
Those commands do not start the agent process yet. They only create an auditable trail for the ticket. The wrapper in the next section is the gate that reads the trail.
A wrapper that refuses closed windows
The sample checker below is a starting wrapper. Teams should adapt paths and timezones before use. It is not a security boundary, and it will not stop a raw curl.
#!/usr/bin/env python3
"""Sample quiet-hours wrapper. Adapt before use. Unexecuted until a team wires it."""
from __future__ import annotations
import datetime as dt
import sys
from pathlib import Path
from zoneinfo import ZoneInfo
try:
import yaml
except ImportError:
sys.exit("install pyyaml before running this sample wrapper")
CARD = Path(".agent-run/quiet-hours-card.yaml")
def fail(msg: str) -> None:
print(f"quiet-hours refuse: {msg}", file=sys.stderr)
raise SystemExit(2)
def load_card() -> dict:
if not CARD.exists():
fail("missing .agent-run/quiet-hours-card.yaml")
data = yaml.safe_load(CARD.read_text())
if not isinstance(data, dict):
fail("card is not a mapping")
return data
def parse_window(card: dict) -> tuple[dt.datetime, dt.datetime, dt.datetime]:
tz = ZoneInfo(str(card["timezone"]))
day = dt.date.fromisoformat(str(card["date"]))
start = dt.datetime.combine(
day, dt.time.fromisoformat(str(card["window_start"])), tz
)
end = dt.datetime.combine(
day, dt.time.fromisoformat(str(card["window_end"])), tz
)
now = dt.datetime.now(tz)
return now, start, end
def main() -> None:
card = load_card()
for key in (
"duty_officer",
"batch_owner",
"reviewer",
"repo",
"goal",
"abort_contact",
"data_class",
"stop_condition",
):
if not str(card.get(key, "")).strip():
fail(f"blank field {key}")
if card.get("freeze_overlap") is True:
fail("freeze_overlap is true")
if str(card.get("data_class")) != "no-prod-secrets":
fail("data_class is not no-prod-secrets")
now, start, end = parse_window(card)
if now.date() != start.date():
fail("card date is not today in the named timezone")
if not (start <= now <= end):
fail(f"now {now.isoformat()} outside {start.time()}–{end.time()}")
print("quiet-hours allow")
print(f"duty={card['duty_officer']} owner={card['batch_owner']}")
print(f"stop={card['stop_condition']}")
if __name__ == "__main__":
main()
Call the wrapper first, then start the agent process. A simple shell AND-chain keeps the order honest.
python3 sample_quiet_hours_check.py && ./run-agent.sh
If the window is closed, the agent never starts. If freeze_overlap is true, the agent never starts. If the data class drifts, the agent never starts.
Stop_condition belongs on the card for a reason. The duty officer needs a kill rule without rereading the prompt. First failing test is a clean default for refactors.
Some teams run these shared batches on MonkeyCode today. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The open-source project offers free model access and a free server option.
The quiet-hours card does not depend on that vendor. Remove the product name and the ritual still holds. Treat the server as one more shared kitchen tonight.
The same wrapper still applies on a laptop agent. Shared boxes just make the leak louder. Free compute without a last-ticket time recreates the Friday roast.
Limits of the ritual
The wrapper only reads a clock and a few strings. It cannot prove the duty officer was awake. It cannot prove the reviewer will read the later diff.
Timezone mistakes will pass a closed window as open. A copied yesterday card can look valid at a glance. The date check exists to catch that copy, not malice.
Anyone can invoke the agent binary without the wrapper. Teams that need a hard control must hide the raw entrypoint. This sample wrapper does not hide that entrypoint.
The card also does not measure model quality. It does not replace tests, code review, or a change freeze. It only blocks a start that nobody agreed to time.
Solo developers with no pager rotation should skip this card. Mature change boards that already block compute can ignore it. Work that touches production secrets should not use shared agent boxes.
Overnight unattended runs against customer data remain out of scope. A quiet window is not consent for dark data. Abort_contact must be a real human path, not a void channel.
Print the card, then name the officer, then clock the window. Start the batch only after the wrapper prints allow. Teams already using that free server can store the card beside the wrapper.
Top comments (0)