This is chapter 8 of my book Building Autonomous AI Agents with Claude Code — a field guide to turning Claude Code from a coding assistant into an agent that remembers, verifies its own work, and knows when to stop. Everything below is from a system I actually run every day on one Windows PC.
"Automatically collect contest and grant-program postings every day, filter out risky clauses, and produce
a one-page report to read in the morning."
The input (posting sites), processing (parsing and filtering), output (digest), and schedule (every morning) are all in one sentence.
When you hand work to an AI, this sentence becomes both the work order and the completion criterion. If it's fuzzy,
the AI will very quickly build "something plausible that isn't what I wanted."
The first prompt handed to the agent looks like this.
If you don't write the "completion criterion," the AI's definition of done is "I finished writing the code." The done we want is
"it runs." This two-line difference is also why the auditor from Chapter 6 is needed.
The AI will plausibly guess, "that site usually has this kind of structure." A parser built from that guess is always wrong. Enforce the order.
curl -sL "https://example.org/contest/list" -o /tmp/list.html
grep -o 'class="[a-z_-]*"' /tmp/list.html | sort | uniq -c | sort -rn | head -20
Measuring reveals things you could never know from guessing.
The third one is a failure we actually experienced. Without parsing the closed/open status, we assumed "it's on the list, so it's still open"
and reported a contest that had already ended as a participation candidate. We were verifying the hard things like eligibility and terms
while missing the most basic question: "is it accepting entries right now?" So this rule is now baked into the collector.
Checklist before recommending anything to a human: ① Is it accepting entries? ② Are we eligible? ③ Are the terms safe? Only something that passes all three is a candidate.
Freeze the HTML fragments confirmed during measurement directly into test fixtures. Even if the site changes later,
these tests guard against regressions in the parser logic itself.
SAMPLE = """
<div class="list-item">
</div>
<div class="list-item">
</div>
"""
def test_only_open_contests_are_collected():
items = parse_list(SAMPLE)
def test_deadline_is_extracted():
assert parse_list(SAMPLE)[0]["deadline"] == "2026-09-18"
Writing tests first narrows the AI's scope of work. Instead of "make something reasonably good," it becomes
"make these tests pass." This is the strongest form of control you have when delegating work to an AI.
Keep the collector as independent modules per source, so that if one dies the rest keep running.
In exchange, a dead source must always appear in the report.
def collect_all(sources):
results, failures = [], []
for name, fn in sources.items():
try:
items = fn()
results.extend(items)
failures.append(f"{name}: {type(e).__name__} {e}")
return results, failures
And the top of the report starts like this.
A system that only shows success counts cannot be trusted. Failures must appear on the same line for a human to believe it.
How a single except: pass line quietly kills a system is covered in detail in Chapter 9.
The real value of this project is not collection but selection. It finds risky clauses in posting terms
and classifies them as risky, caution, or safe.
RISK_PATTERNS = [
]
def judge(text):
hits = [(level, why) for pat, level, why in RISK_PATTERNS
if re.search(pat, text)]
Your domain surely has a "pre-participation checklist" like this too. Toxic contract clauses, a client's creditworthiness,
delivery terms — whatever it is, the moment you move the checklist from your head into code, mistakes are structurally blocked.
A tired human skips steps; a regex never does.
When putting it on the scheduler, follow the rules from Chapter 7 (English-only paths end to end + the wrapper chain) exactly.
Then hand verification to the independent auditor (Chapter 6) along with the goal sentence. In practice, the auditor
caught "it was only registered and never actually executed once," and the cross-AI review got the RSS parsing
changed from regex to a standard XML parser. Working alone, I would have missed both.
| Time | Who | What |
|---|---|---|
| 09:30 | System | Collect → filter → generate digest |
| 2 min in the morning | Human | Check new items (🆕) and the failures section |
| 5 min in the morning | Human | Run the terms filter only on postings of interest, then decide whether to participate |
The system does the collecting and selecting; the human only makes decisions. That division of labor is the target state of an autonomous agent.
| Field | Collection target | Risky clauses (filter) | Output |
|---|---|---|---|
| Freelancing | Outsourcing postings | Unlimited revisions, full IP transfer | 3 gigs to apply for |
| Job hunting | Job postings | Inclusive-wage clauses, probation pay cuts | Application candidates |
| Real estate | Listings | Existing mortgages, special conditions | Site-visit candidates |
| Development | Release notes | Breaking changes | Upgrade caution list |
The only things that change are the parser and the regexes; the skeleton (measure → fixtures → independent modules → visible failures → audit) stays the same. Build this skeleton once, and the next agent takes half a day.
Want the whole system? The book has 10 chapters plus 4 ready-to-use templates (CLAUDE.md starter, memory files, auditor checklist, measurement guide) and a hands-on section for every chapter. It's $19 as a PDF: https://dbsoul.gumroad.com/l/autonomous-ai-agents-claude-code
Not sure yet? The first three chapters are free, same PDF format: https://dbsoul.gumroad.com/l/autonomous-ai-agents-claude-code-free-sample
Questions about the setup are welcome in the comments — I'll answer with what actually happened, not theory.