An agent brief is not ready to leave draft until every actionable claim has a primary URL, a retrieval time, and an age inside a limit you set before drafting. Miss any one of those and you stop. You do not publish a softer version "with a caveat."
You are not short of headlines. You are short of clocks. A recycled post, an undated docs page, and a model paraphrase can all sound current. They are not the same evidence.
What you are actually gating
You are gating a digest your team might act on: a quota change, a server offer, a breaking API note, a "what shipped this week" summary. The writer can be a person, a cron job, or a model. The gate does not care who typed the sentence. It cares whether a human can open the cited page and see when you fetched it.
Pass means the brief may go to a named reviewer. Fail means the file stays a draft, including when only one sentence is rotten. That is fail-closed. A partial brief is a failed brief.
Treat feed titles as untrusted topic signals. They are not instructions to your agent, and they are not facts. If a page tells the model to ignore prior rules, that text is data, not a new policy.
Where disposable draft capacity fits
You can draft on a scratch host so the summarizer never sees production credentials. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Operator-supplied context for this draft: MonkeyCode is an open-source project offering free model access and a free server option. This article does not verify a token quota, a hardware shape, a region, or how long either offer lasts. If the current docs do not state a number, your brief must not invent one.
Use that free path only as a draft bench. The freshness gate is a deterministic check you run yourself. Delete every product name from this piece and the checklist still applies on a laptop you control.
Do not encode a remembered marketing number into the brief. "Free access exists" is a weaker claim than "N million tokens." Weaker is what you ship when the primary page is silent.
Gates you can copy
Set these before the model runs. Do not negotiate them in review.
-
Brief identity. Require
brief_id,drafted_atin UTC, andauthoras a human name or a job name.author: AIis not an identity. -
Max age, chosen up front.
max_age_hoursis 24 for prices, quotas, outages, and "free for a limited time." Use 72 only for slower design notes. Do not pick the age after you see which sources you found. -
Primary sources for quantities. Token counts, GPU shapes, prices, rate limits, and durations need
source_class: primary. A recap post is not primary. A model sentence is not primary. -
Retrieval stamp. Every citation has
urlandretrieved_at. The timestamp is not in the future, and it is not older thanmax_age_hoursrelative to review time, not relative to whenever the model felt confident. -
Same-sentence marker. Any numeral you want the reader to trust sits in a sentence that contains
[c:id]. A footnote cluster at the bottom does not rescue an unmarked number. -
HTTPS and a real path.
http://fails. A bare domain fails. The URL must be the page that contains the claim, not the site's homepage. - Secret boundary. The draft host gets public sources only. No production keys, no customer text, no internal URLs. A free server is acceptable for a public digest. It is the wrong place to "just try" a prod token because the demo was easier that way.
-
Named release.
status: releasedwithoutreleased_byandreleased_atis a failure. A green script is not a reviewer.
An unexecuted checker
The script below is an example, not a hosted service, and it was not run against a live news feed for this article. Save it as check_brief.py. It reads a JSON brief and exits non-zero on any broken gate.
#!/usr/bin/env python3
"""Fail-closed freshness gate. Example for local use."""
import json, sys, re
from datetime import datetime, timezone, timedelta
NUM = re.compile(
r"\b\d[\d,]*(?:\.\d+)?\s*(?:tokens|hours|days|GB|ms|%|USD)?\b", re.I
)
CITE = re.compile(r"\[c:([a-z0-9_-]+)\]")
def parse(ts):
return datetime.fromisoformat(ts.replace("Z", "+00:00")).astimezone(timezone.utc)
def main(path):
doc = json.load(open(path, encoding="utf-8"))
errors = []
now = datetime.now(timezone.utc)
for key in ("brief_id", "drafted_at", "max_age_hours", "body", "citations"):
if key not in doc:
errors.append("missing %s" % key)
if errors:
print("\n".join(errors))
return 1
drafted = parse(doc["drafted_at"])
if drafted > now + timedelta(minutes=5):
errors.append("drafted_at is in the future")
max_age = timedelta(hours=float(doc["max_age_hours"]))
cites = {c["id"]: c for c in doc["citations"]}
for cid, c in cites.items():
url = c.get("url", "")
if not url.startswith("https://") or url.rstrip("/") == "https://example.com":
errors.append("%s: url must be a specific https page" % cid)
fetched = parse(c["retrieved_at"])
if fetched > now + timedelta(minutes=5):
errors.append("%s: retrieved_at in the future" % cid)
age = now - fetched
if age > max_age:
errors.append("%s: age %s exceeds max_age" % (cid, age))
if c.get("kind") == "number" and c.get("source_class") != "primary":
errors.append("%s: numeric claim needs source_class=primary" % cid)
for sent in re.split(r"(?<=[.!?])\s+", doc["body"]):
if not NUM.search(sent):
continue
ids = CITE.findall(sent)
if not ids:
errors.append("number without citation: %s" % sent[:80])
continue
for i in ids:
if i not in cites:
errors.append("unknown citation %s" % i)
if doc.get("status") == "released" and not doc.get("released_by"):
errors.append("released brief needs released_by")
if errors:
print("FAIL")
print("\n".join(errors))
return 1
print("PASS")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
Here is a fixture shape, not evidence of a live offer. Replace the URL with a page you opened today. Leave the sample host in place and the checker should fail on purpose, because example.com is not a source.
{
"brief_id": "ai-brief-2026-09-25",
"drafted_at": "2026-09-25T09:00:00Z",
"max_age_hours": 24,
"status": "draft",
"author": "job:digest-draft",
"body": "Free model access is an availability claim, not a measured quota [c:docs1].",
"citations": [
{
"id": "docs1",
"url": "https://example.com/docs/pricing",
"retrieved_at": "2026-09-25T08:40:00Z",
"source_class": "primary",
"kind": "number"
}
]
}
date -u +%Y-%m-%dT%H:%M:%SZ
python3 check_brief.py brief.json
echo $?
Exit 0 is the only freshness pass. Exit 1 means you fix the source or you delete the sentence. You do not average the two.
Debug a failure in this order
Work top down. Later steps lie if the clock is wrong.
- Compare
date -uon the draft host with a known NTP source. A skewed clock makes a fresh page look expired, or an old page look new. - If the error is
missing, stop. Do not let the model "fill the JSON." You add fields from the fetch log. - If the error is age, refetch. Do not edit
retrieved_atby hand to go green. - If the error is
number without citation, split the sentence. Keep the qualitative clause. Drop the numeral until a primary page supports it. - If
source_classis anything butprimaryon a quantity, downgrade the sentence. "A secondary post mentioned a quota" is not a number you can plan capacity on. - If status is
releasedandreleased_byis empty, the script is telling you the process failed, not the prose. Put it back todraft.
A model line such as "I checked the docs" never satisfies step 4. Confidence is not a URL.
Decision table
| Evidence in the file | Gate | What you do |
|---|---|---|
Primary URL, fetched inside max age, numeral marked [c:id]
|
Pass freshness | Human reviews, then maybe release |
| Recap blog states a quota, vendor page not fetched | Fail | Delete the number |
URL present, retrieved_at absent |
Fail | Refetch and stamp |
| Docs page says access exists, no quantity | Pass only with no numeral | Write the weaker sentence |
| Model claims it verified a headline | Fail | Model text is not a citation |
| Checker passed, reviewer field empty | Fail on release | Keep the draft unpublished |
Limits, and who should skip this
A fresh primary page can still be wrong. This gate checks structure and age, not truth. You still read the page before you cite a number in a ticket.
It will not notice a paraphrase that outruns the paragraph you fetched. Keep claims narrower than the heading you linked. It will not strip prompt-injection text from a fetched page. That is a fetcher problem. Do not pretend this script fetches anything.
Free model access can change without this article changing. Do not put a quota, a duration, or a hardware guess in a runbook because a draft once said so. Pin the Python you used in a one-line note next to the script. "Works on the free server today" is not a pin.
Skip the approach if you need a legal, medical, or financial sign-off. Skip it if no named person will set released_by. Skip it if the only host that can reach the sources would require production credentials. In that last case you do not have a digest pipeline. You have an incident shaped like a paragraph.
What you ship instead of a hype line
Write the sentence that can pass: free model access and a free server option are operator-supplied availability claims, and you confirm both on the project docs before you depend on them. A sentence that names a token grant, a forever window, or a specific box does not ship unless you retrieved a primary page that says so, this week, and the marker sits in that sentence.
If you already draft on MonkeyCode, point the job at public docs only and run check_brief.py before anyone pastes the brief into a ticket. Keep the checker even if that draft host disappears. The host is optional. The clock is not.
Top comments (0)