The four agent patterns that ship
Module 03 · est. 35 min · You’ll walk away with: the ability to look at any task and instantly know which of the four agent shapes it should be — and why trying to be all four is the most common way people waste a month.
TL;DR: Every production agent is one of four shapes: polling (checks on a timer, acts if something changed), event-triggered (fires when a specific thing happens), scheduled (runs the same job at the same time, every time), or conversational (a human is in the loop, turn by turn). Pick the wrong shape and your agent is fragile, expensive, or annoying. The trap that kills beginners is building one agent that tries to be all four — it ends up bad at all of them.
[Operator’s read] I run 30+ agents and every single one is exactly one of these four shapes. Not because I’m disciplined — because the ones that tried to be two shapes at once all broke, and I rebuilt them as separate single-shape agents. This module is that lesson, pre-learned, so you skip the breakage.
Why shape matters more than smarts
A beginner thinks the hard question is “how smart is my agent.” The operator knows the hard question is “when and how does it run.” Shape determines your trigger, your cost, your failure modes, and your infrastructure. Get the shape right and a dumb agent works great. Get it wrong and a brilliant agent is a liability that fires at the wrong time, costs too much, or never fires at all.
Here are the four. I’ll give you each one’s mechanic, when to reach for it, its failure mode, and a real agent of mine that uses it.
Pattern 1 — Polling
The mechanic: the agent wakes up on a timer (every 5 min, every hour, whatever), checks a source, asks “did anything change that I care about?”, and acts only if yes. Most runs do nothing. That’s correct.
When to use it: when you need to react to changes in a system that won’t tell you when something changed — no webhook, no event stream, just a database or an API you have to ask. Polling is how you fake real-time when the source doesn’t offer it.
The shape in code:
// Runs on a 15-minute cron. The "did anything change?" guard is the whole game.
async function poll() {
const items = await fetchCurrentState(); // read the source
const lastSeen = await loadCheckpoint(); // what did we act on last time?
const newItems = items.filter((i) => i.id > lastSeen);
if (newItems.length === 0) return; // nothing changed → do nothing, cheaply
for (const item of newItems) {
await runAgentOn(item); // the expensive model call ONLY on new stuff
}
await saveCheckpoint(items.at(-1)!.id);
}The non-negotiable: the checkpoint. A polling agent must remember what it already acted on, or it’ll act twice — re-reply to the same comment, re-send the same alert. The checkpoint (a stored “last seen” marker) is the difference between a polling agent and a spam machine. Notice the model only runs on genuinely new items. Polling cheaply 96 times a day and thinking expensively twice is the whole efficiency play.
Failure mode: polling too often (cost + rate limits) or too rarely (you react late). And the silent killer: a broken checkpoint that makes it re-process everything. Test your checkpoint logic harder than your prompt.
My real one: social-reply via Metricool. It polls our inbox for new Google reviews and social comments across Pickleland’s channels. Most checks find nothing. When a new review lands, the model drafts a reply in our voice. No webhook from Metricool, so polling is the only option. Checkpoint = the last review ID I’ve handled.
Pattern 2 — Event-triggered
The mechanic: something happens, that something fires a webhook (or drops a message on a queue), and your agent runs in response to that specific event, with the event’s data in hand. No timer. It runs exactly when there’s something to do, and never otherwise.
When to use it: when the source can tell you something happened — a Stripe payment, a new form submission, a GitHub push, an inbound email, a calendar invite. If a webhook exists, prefer this over polling every time. It’s cheaper (you only run when there’s real work) and faster (zero polling lag).
The shape: a tiny web endpoint that receives the event and kicks off the loop.
// A Cloudflare Worker / Pages Function. The platform IS the trigger.
export async function onRequest(context: { request: Request }) {
const event = await context.request.json();
// 1. Verify it's really from who it claims (signature check). Skip this and
// you've built a public endpoint anyone can fire. We'll harden this in Module 06.
if (!verifySignature(context.request, event)) {
return new Response('bad signature', { status: 401 });
}
// 2. Run the agent ON THIS EVENT'S DATA.
await runAgentOn(event);
// 3. ALWAYS return 200 fast, or the sender retries and you double-process.
return new Response('ok');
}The non-negotiable: idempotency. Webhook senders retry. Stripe, GitHub, everyone — if you don’t 200 fast enough, they fire the same event again. So your agent must be safe to run twice on the same event (check an event ID, skip if already handled). Same lesson as the polling checkpoint, different costume: never act twice on one input.
Failure mode: unverified endpoints (security hole — anyone can trigger your agent with fake data) and slow handlers that trigger retries. Acknowledge fast, do heavy work async.
My real one: this is the shape I reach for when a system emits events — e.g. an inbound webhook kicking off a draft. The promote-upcoming-events flow leans event-ward when PlayByPoint surfaces a new bookable event. The principle: let the system tell you, don’t keep asking.
Pattern 3 — Scheduled
The mechanic: the same job, the same time, every time. Cron. 6am daily. Monday 7:30am. Last Saturday of the quarter. There’s no “did anything change” check — the schedule is the trigger, and the job runs regardless.
When to use it: for anything that should happen on a human cadence — reports, digests, recaps, recurring outreach, routine maintenance. If you’d put it on a recurring calendar reminder for yourself, it’s a scheduled agent. This is the most common shape and the easiest to ship, because cron is trivial and there’s no event plumbing.
The shape: a script and a cron entry. That’s the entire infrastructure.
# .github/workflows/morning-brief.yml — GitHub Actions as a free cron host
name: morning-brief
on:
schedule:
- cron: '0 11 * * *' # 11:00 UTC = 6:00am Central. (UTC always. Burn that in.)
workflow_dispatch: # ← also lets you trigger manually to test. Always add this.
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx tsx src/run.ts
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}The non-negotiable: timezones and the manual trigger. Cron runs in UTC; your brain runs in Central. Get this wrong and your “morning” brief arrives at 1am. And always add workflow_dispatch so you can fire it by hand to test — you don’t want to wait until 6am tomorrow to find out it’s broken.
Failure mode: the schedule fires but the world wasn’t ready — the data source hasn’t updated yet, the week isn’t over. Scheduled agents assume the world is in a known state at run time. When that assumption breaks, they produce confident garbage. Guard against it (“if last week’s data isn’t in yet, stop and tell me”).
My real ones — most of my fleet lives here:
- Daily morning brief — 6am, overnight summary + today’s calendar + top focus.
- Weekly marketing recap — Monday 7:30am, one page on last week’s Pickleland numbers.
- AI-trends digest — twice weekly, new AI workflow techniques worth stealing.
- Quarterly team bonding — last Saturday of the quarter, picks a date, books it, sends invites.
- LMNT reorder — periodic check, reorders before I run out.
Scheduled is the workhorse. If you’re not sure what shape something is, it’s probably this one.
Pattern 4 — Conversational
The mechanic: a human is in the loop, turn by turn. The agent responds, the human reacts, the agent responds again. State carries across turns. This is Claude Code. This is Cowork. This is the agent you talk to rather than the agent that runs for you.
When to use it: when the task needs human judgment in the moment — ambiguous goals, creative work, anything where you can’t fully specify “done” in advance. Conversational agents are copilots (remember Module 01): high-value, but they require you to be present. Don’t try to make a conversational agent run unattended; that’s a category error.
The shape: the Module 02 loop, but it doesn’t auto-start and it pauses for human input between turns instead of running to completion. The human is the trigger, every turn.
The non-negotiable: knowing when to hand off to a human gate vs. stay conversational. The art is hybrid: a scheduled agent that drafts and a human who approves in conversation. That’s most of my “talks to customers” agents — autonomous up to the action, then a human turn.
My real one: I do almost all my agent-building inside a conversational agent (Claude Code). And several production agents end in a conversational gate: promote-upcoming-events drafts everything autonomously, then hands me drafts to approve — that approval step is the conversational turn. The agent did the work; I do the judgment.
The trap: trying to be all four
Here’s the mistake that costs people a month. They build “an agent for X” and it slowly accretes shapes. It polls. Also it has a webhook. Also it runs on a schedule. Also you can chat with it. It becomes a tangle where you can’t reason about when it runs or why it did something, and every failure is a mystery because there are four possible triggers.
The fix is embarrassingly simple: one agent, one shape. If you need polling and a daily report, that’s two agents that share code, not one agent with two personalities. My social-reply agent polls. My weekly recap is scheduled. They’re separate, even though both touch our social data, because separate is debuggable and combined is not.
When you catch yourself saying “and it should also…”, stop. That “also” is a second agent. Spin it out. They can share a tools.ts. They should not share a trigger.
How to tell them apart in one question: “What makes this run?” If you have more than one honest answer, you have more than one agent.
A decision table you can tattoo on your arm
| If the work is… | Source can notify you? | Shape |
|---|---|---|
| React to a change | No (must ask) | Polling |
| React to a change | Yes (webhook) | Event-triggered |
| Happen on a human cadence | N/A — it’s a clock | Scheduled |
| Needs judgment in the moment | N/A — human present | Conversational |
Run any task through that table and you’ll land on a shape in ten seconds.
Hands-on lab
Step 1 — Classify your fleet-to-be. Take the 10+ tasks from your Module 01 audit. Put each in one of the four buckets using the decision table. Be strict. If something feels like two buckets, split it into two tasks right now.
Step 2 — Find your “all four” trap. Look for any task where you wrote “and also.” Rewrite it as two separate single-shape agents. Note which tools.ts they’d share.
Step 3 — Re-shape your Module 02 agent. You built something in Module 02 with you as the trigger. Decide its true shape. Most first agents are secretly scheduled — they want to run every morning. If so, you already saw the GitHub Actions YAML above; that’s your Module 07 homework previewed.
Step 4 — Pick your next two. Choose the two highest-leverage tasks from your classified list and write their shape + trigger on a sticky note. These are the agents you’ll build after the course. You now know their shape before writing a line of code — which means you’ll build them right the first time.
Deliverable: your task list, every item labeled with one of the four shapes, “and also” traps split out, and your top two next agents identified by shape. Next module: the three things that turn these shapes from clever scripts into agents you can actually trust — memory, tools, and evals.