Polling Agents in AI Assistants: 11 Implementation Patterns
Reliable polling patterns for AI agents.
Polling agents are one of the least glamorous parts of AI assistant architecture, but they are also one of the most useful.
A normal chat assistant waits for the user to ask something. A polling agent keeps watching. It checks a source, notices changes, decides whether anything matters, and then acts. That action may be a notification, a summary, a draft, a tool call, or a full workflow.
This is how an assistant moves from “answer my question” to “keep an eye on this for me.” Instead of being reactive, it becomes a background process that notices things on the user’s behalf and acts when conditions are met.

The important design point is simple: do not make the language model responsible for time, state, retries, or locking. Use normal backend infrastructure for that. Use the model where it is valuable: interpreting messy context, making semantic judgments, and producing useful language.
What Is a Polling Agent?
A polling agent is a background process that repeatedly checks a source and triggers an assistant action when a condition is met. In the broader AI Systems stack — where the assistant combines an LLM, memory, tooling, routing, and observability — the polling layer is what makes the assistant proactive rather than purely reactive. For the full five-layer picture, see AI Assistant Architecture: LLM, Memory, Tools, Routing, Observability.
Examples:
- Check an inbox every morning and summarize important messages.
- Watch a Notion task list and execute the next todo item.
- Monitor a GitHub issue until it changes status.
- Poll a long-running AI job until the result is ready.
- Check a booking slot until one becomes available.
- Watch a supplier portal until a document appears.
- Scan new research papers once per week and summarize relevant ones.
A practical polling agent has five responsibilities:
- Wake up at the right time.
- Read from the source.
- Remember what it has already seen.
- Decide whether the new state matters.
- Act once, safely, without repeating itself.
A typical production flow looks like this:
scheduler
-> polling worker
-> source system
-> state store
-> deterministic filters
-> optional LLM evaluation
-> assistant action
This structure is boring in the best possible way. Boring systems are easier to debug at 2 AM.
The State Every Polling Agent Needs
Polling agents need durable state. Conversation history is not enough. The assistant may remember the conversation, but the system needs a reliable operational record.
A good polling state record usually contains:
{
"poll_id": "poll_123",
"user_id": "user_456",
"source_type": "notion",
"source_ref": "database_tasks",
"condition": "take one task in Todo state and execute it",
"interval_seconds": 600,
"last_run_at": "2026-06-19T01:00:00Z",
"next_run_at": "2026-06-19T01:10:00Z",
"last_seen_cursor": "cursor_or_timestamp",
"last_result_hash": "b64e8a...",
"failure_count": 0,
"status": "active"
}
The exact schema depends on the source, but most systems need these concepts.
Poll Definition
This describes what the agent is watching and why.
poll_id
user_id
workspace_id
source_type
source_ref
condition_text
priority
status
For example:
source_type: notion
source_ref: Tasks database
condition_text: Find one Todo task, claim it, execute it, mark it Complete.
Schedule
This describes when the agent should run.
interval_seconds
cron_expression
timezone
last_run_at
next_run_at
jitter
For a Hermes agent that checks Notion every 10 minutes:
interval_seconds: 600
timezone: Australia/Melbourne
Cursor or Snapshot
This helps the agent avoid reprocessing the same data.
Depending on the source, this may be:
last_seen_id
last_seen_timestamp
api_cursor
etag
version
content_hash
For a Notion task queue, the cursor may be less important than task status and claim fields. For Gmail, GitHub, or a sync API, the cursor is usually critical.
Claim or Lease
This prevents two workers from taking the same job.
claimed_by
claimed_at
claim_expires_at
run_id
For example, a Notion task can be changed from:
Status: Todo
to:
Status: InProgress
ClaimedBy: hermes
ClaimedAt: 2026-06-19T01:00:00Z
ClaimExpiresAt: 2026-06-19T01:30:00Z
RunId: run_789
This is the difference between “I hope only one worker picks it” and “the system has a claim protocol.”
Execution Record
This records what happened during a run.
run_id
poll_id
source_object_id
started_at
finished_at
status
items_checked
items_changed
decision_summary
error
The execution record should live in the assistant backend, not only in Notion or another external tool. Notion is good for human visibility. It is not ideal as your only execution log.
Dedupe Record
This prevents duplicate notifications or repeated actions.
dedupe_key
poll_id
source_object_id
condition_version
action_type
delivered_at
For example:
user_456:poll_123:notion_page_999:execute:v1
If the same action is attempted again, the system can suppress it.
Method 1: Scheduled Polling Worker
This is the simplest reliable pattern.
A scheduler wakes up every fixed interval and calls a worker. The worker reads the source, updates state, and triggers an assistant action if required.
scheduler
-> worker
-> source API
-> database
-> assistant action
How It Runs
The scheduler is responsible for time. It might be cron, a cloud scheduler, a Kubernetes CronJob, or a small internal scheduler.
Every interval, it starts a worker run. The worker loads its configuration, queries the target source, compares the result with stored state, and acts if needed.
For a simple assistant, this is often enough. A single scheduler and a lightweight worker process can handle dozens of daily checks without requiring queues, leases, or distributed coordination.
State Model
The scheduler stores very little. Usually it only knows when to trigger a job.
The application database stores the important state:
poll definition
schedule
cursor or snapshot
last run time
failure count
status
The worker should be stateless. It can hold temporary data while running, but the durable truth belongs in the database.
Example Flow
Every 10 minutes:
trigger Hermes polling worker
Worker:
load active poll configuration
query source
compare with previous state
run deterministic checks
call LLM only if needed
update state
emit assistant event
Best Fit
Use scheduled polling workers for:
- Daily summaries.
- Hourly checks.
- Small internal automations.
- Simple “watch this” tasks.
- Low to medium volume assistant jobs.
Weaknesses
Scheduled polling is easy to understand, but it can become fragile at scale. If many polls run at the same time, you may overload your workers or hit provider rate limits. Retries can also become messy if the scheduler directly starts the work.
Method 2: Queue-Based Polling Workers
Queue-based polling is usually the best default for production AI assistants.
The scheduler does not execute the poll directly. It puts a job on a queue. Worker processes consume jobs from the queue.
scheduler
-> queue
-> worker pool
-> source API
-> state store
-> assistant action
How It Runs
A scheduler scans for due polls and enqueues jobs. Workers pull jobs when they have capacity.
This gives you backpressure. If the system is busy, jobs wait in the queue instead of overwhelming the source API or the LLM provider.
State Model
The database stores the poll state:
poll_id
user_id
source_ref
condition_text
next_run_at
cursor
status
failure_count
The queue message should stay small:
{
"poll_id": "poll_123",
"scheduled_for": "2026-06-19T01:10:00Z",
"attempt": 1
}
The worker loads the full state from the database when it starts.
Example Flow
Every minute:
scheduler finds polls where next_run_at <= now
scheduler enqueues jobs
Workers:
pull jobs from queue
lock or lease the poll
query the source
update state
emit assistant action if needed
set next_run_at
Best Fit
Use queue-based polling for:
- Multi-user AI assistants.
- Many simultaneous polls.
- Integrations with rate limits.
- Retriable background work.
- Jobs that may take different amounts of time.
- SaaS products where reliability matters.
Weaknesses
Queues add infrastructure. You need dead letter handling, idempotency, visibility timeouts, and retry policies. This is worth it for production systems, but probably excessive for a small prototype.
Method 3: External Tool as a Task Queue
This is the pattern in the Notion plus Hermes example.
The external tool is not just a data source. It becomes the human-facing task queue. The agent periodically checks the tool, claims one task, executes it, and updates the task status.
scheduler
-> Hermes worker
-> Notion database
-> claim one task
-> execute task
-> update Notion status
How It Runs
Every 10 minutes, Hermes queries the Notion database for one task in Todo state. It chooses the next task, usually by priority and creation time. Then it claims the task by setting it to InProgress.
After that, Hermes executes the task. If execution succeeds, it marks the task as Complete. If execution fails, it marks the task as Failed or returns it to Todo with a retry count.
State Model
Notion stores the human-facing task state:
Title
Description
Status: Todo | InProgress | Complete | Failed
Priority
CreatedAt
ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId
RetryCount
LastError
CompletedAt
Hermes backend stores the operational execution state:
run_id
notion_page_id
started_at
finished_at
execution_status
tool_calls
LLM trace
error details
idempotency_key
This split matters. Notion is excellent for visibility and manual editing. Hermes backend is better for logs, retries, dedupe, and audit history.
Example Flow
Every 10 minutes:
Hermes wakes up
Hermes:
query Notion for one task where Status = Todo
sort by Priority, CreatedAt
update selected task to InProgress
set ClaimedBy, ClaimedAt, ClaimExpiresAt, RunId
execute the task
write execution log
set task to Complete or Failed
Best Fit
Use this pattern when:
- Humans already manage work in Notion, Jira, Linear, Trello, or another tool.
- You want the assistant to process visible tasks.
- The task board is the user interface.
- You need a simple human-in-the-loop automation model.
Weaknesses
External tools are rarely perfect queues. Atomic claims may be limited. Query consistency may lag. Rate limits may apply. If the agent can run in multiple instances, you need a careful claim or lease strategy.
The practical recommendation is to use Notion as the human-facing task inbox while keeping all execution logs, retry records, traces, and idempotency keys in Hermes. Notion gives users visibility; Hermes keeps the system reliable. For the dispatcher and concurrency mechanics that sit behind this pattern in Hermes, see Kanban in Hermes Agent for Self Hosted LLM Workflows.
Method 4: Long-Running Worker Loop
A long-running loop is the simplest implementation.
while True:
due_polls = db.find_due_polls()
for poll in due_polls:
run_poll(poll)
sleep(30)
This pattern combines scheduling and execution in one service, which makes it the simplest possible starting point for background agent work.
How It Runs
The worker process runs continuously. Every few seconds or minutes, it checks the database for due polls and executes them. It is easy to build, easy to reason about, and fast to iterate on during development.
State Model
The database still stores durable state:
poll configuration
next_run_at
cursor
last result
failure count
status
The process memory should only contain temporary state:
current batch
short-lived cache
in-flight run
Never store important progress only in memory. If the process crashes, any state that was not written to durable storage is gone, and the next run will have no way to know where things left off.
Best Fit
Use long-running loops for:
- Prototypes.
- Local development.
- Internal tools.
- Single-tenant systems.
- Low-volume agents.
Weaknesses
This pattern becomes risky with multiple replicas. Without leases, two workers may run the same poll. It also lacks the operational features of a real queue or workflow engine.
A long-running loop is not wrong as a starting point, but it is not a distributed scheduler and should not be treated as one. As soon as you need multiple replicas or stronger reliability guarantees, you will need to move to one of the more structured patterns above.
Method 5: Webhook-First With Polling Fallback
If the source supports webhooks, use them. Polling should often be the backup, not the primary mechanism.
external system
-> webhook endpoint
-> event store
-> assistant action
reconciliation poll
-> source API
-> compare with event store
-> repair missed events
How It Runs
The external system sends events to your webhook endpoint when something changes. Your system stores the event and processes it asynchronously.
A slower reconciliation poll runs every few hours or once per day. It checks whether any events were missed.
State Model
The event store records incoming webhooks:
event_id
source_type
source_object_id
event_type
received_at
payload_hash
processed_at
signature_valid
The reconciliation poll stores:
last_reconciliation_at
last_seen_cursor
last_seen_version
The source object table stores the latest known state:
external_id
current_status
external_updated_at
last_processed_event_id
Best Fit
Use webhook-first architecture for:
- GitHub events.
- Stripe events.
- Slack events.
- CRM updates.
- Deployment notifications.
- Ticketing systems.
Weaknesses
Webhooks require a public endpoint, signature validation, replay protection, and event dedupe. Some providers also send incomplete events, so you may still need to fetch the full object.
Even so, if good webhooks exist, polling every minute is usually wasteful.
Method 6: Provider-Side Background Job Polling
Sometimes the thing being polled is the AI job itself.
The application starts a long-running provider job, stores the job ID, and checks later whether it has completed.
app
-> start AI background job
-> store provider job id
-> poll status
-> fetch result
-> notify user
How It Runs
The assistant starts a job with the provider. The provider returns an ID. Your backend stores that ID and checks its status until the job succeeds, fails, expires, or times out.
State Model
Your backend stores:
assistant_task_id
provider_job_id
user_id
status
created_at
last_checked_at
expires_at
result_ref
The provider stores the temporary job state and output.
If the output matters, copy it into your own durable storage as soon as the job completes. Provider-side result storage has short retention windows and is not a substitute for a proper archive in your own system.
Best Fit
Use provider-side background job polling for:
- Long AI research tasks.
- Large document processing.
- Codebase analysis.
- Report generation.
- Data extraction jobs.
- Tasks that exceed normal HTTP request timeouts.
Weaknesses
This pattern solves one problem: waiting for a long provider job. It does not replace your workflow engine, scheduler, queue, or business state store.
Method 7: Durable Workflow Engine
A durable workflow engine manages long-running execution, timers, retries, and recovery. Temporal is the most common choice for Go and Python-based assistant backends; for a full implementation guide see Implementing Workflow Applications with Temporal in Go.
Instead of manually wiring every wait and retry, you model the process as a workflow.
workflow engine
-> activity: check source
-> timer: wait
-> activity: evaluate result
-> activity: notify user
How It Runs
The workflow starts once and then controls its own waiting. It can sleep for minutes, days, or weeks. If the worker process crashes, the workflow engine can resume from the recorded state.
State Model
The workflow engine stores:
workflow_id
execution history
timer state
activity attempts
retry policy
current workflow state
Your application database stores:
user-facing poll definition
authorization references
business records
notification records
The workflow engine owns process state — execution history, timers, retries, and activity attempts. Your database owns business state — user configurations, authorization records, notifications, and audit logs. Keeping these separate prevents each layer from becoming a confused hybrid of both.
Best Fit
Use durable workflows for:
- Multi-step business processes.
- Long-running automations.
- Human approval flows.
- Reliable retries.
- Auditable background work.
- Processes that must resume after failure.
Weaknesses
Workflow engines add concepts and infrastructure. They are excellent when the process is important, but heavy for simple hourly checks.
Method 8: Persistent Agent Runtime
Some agent frameworks can persist agent state, checkpoint execution, and resume later.
This is useful when the agent itself has a multi-step reasoning process.
scheduler or workflow
-> agent runtime
-> load checkpoint
-> call tools
-> save checkpoint
-> resume later
How It Runs
An external scheduler or workflow starts the agent. The agent runtime loads previous state, runs the next step, calls tools if needed, and writes a checkpoint.
The agent runtime should not be your only scheduler. It is better treated as the reasoning layer inside a larger backend architecture.
State Model
Agent checkpoint storage contains:
current node
messages
tool outputs
intermediate reasoning state
pending action
Long-term memory contains:
stable user preferences
facts
project context
source references
Operational state still belongs elsewhere:
poll schedule
cursor
status
retry count
dedupe records
A useful rule: memory is not a cursor, and a checkpoint is not a queue. Agent memory stores what the model knows; operational state tracks where the process is and what it has done. Conflating the two leads to subtle bugs that only appear under concurrency or after a restart. The full design space for working memory, durable state, and retrieval layers is covered in Memory Systems in AI Assistants.
Best Fit
Use persistent agent runtime for:
- Multi-step research.
- Agents that pause and resume.
- Human-in-the-loop work.
- Tool-heavy reasoning.
- Tasks where context accumulates over time.
Weaknesses
Agent persistence is not the same as operational reliability. You still need scheduling, locking, retries, rate limits, and audit logs.
Method 9: Database Sync Plus Change Evaluation
In this pattern, polling is used to sync external data into your own database. The assistant then reacts to local database changes rather than querying external APIs directly on every evaluation cycle.
sync poller
-> external API
-> local database
-> change evaluator
-> assistant action
This separates data synchronization from assistant intelligence. The sync worker is responsible for keeping local records current; the evaluator is responsible for deciding what to do about changes. Each layer can be tested, monitored, and scaled independently.
How It Runs
The sync worker periodically fetches external changes and writes normalized records into your database. A second worker or change stream detects updated rows and decides whether the assistant should act.
State Model
The sync table stores:
external_id
source_type
raw_payload
normalized_fields
external_updated_at
synced_at
version
content_hash
The sync state stores:
source_cursor
last_sync_at
rate_limit_status
failure_count
The assistant evaluation table stores:
object_id
evaluation_status
last_evaluated_hash
decision
notification_id
Best Fit
Use this pattern for:
- CRM sync.
- Ticketing systems.
- Accounting documents.
- Product inventory.
- Compliance review.
- Search indexing.
- Internal dashboards.
Weaknesses
Syncing everything can be expensive and unnecessary. It may also create privacy and retention obligations. Use this pattern when local data has value beyond a single assistant action.
Method 10: Adaptive Polling
Adaptive polling changes frequency based on state, urgency, or recent activity.
active object: poll every 1 minute
waiting object: poll every 1 hour
stale object: poll once per day
completed object: stop polling
How It Runs
After each run, the worker decides when the next run should happen.
If the object changed recently, poll sooner. If nothing has changed for a long time, slow down. If the task is complete, stop.
State Model
The poll state includes:
current_interval
minimum_interval
maximum_interval
backoff_policy
last_activity_at
priority
stop_condition
The source snapshot includes:
status
updated_at
activity_level
expected_next_change
Best Fit
Use adaptive polling for:
- Deployment status.
- Delivery tracking.
- Calendar slot availability.
- Price monitoring.
- Build jobs.
- Long-running provider tasks.
- Any source with bursty updates.
Weaknesses
Adaptive polling can be harder to reason about. If a task must run at a strict time, keep it strict. Do not make compliance jobs clever.
Method 11: Semantic Polling With an LLM Evaluator
Semantic polling is used when the condition is fuzzy.
Code can answer:
Is status equal to Complete?
Is price below 100?
Is there a new message?
An LLM can help answer:
Does this email sound urgent?
Is this customer likely unhappy?
Is this research paper relevant?
Does this change require my attention?
How It Runs
The worker first applies cheap deterministic filters. Only candidate items go to the LLM.
new item?
matches source filters?
not already processed?
not obviously irrelevant?
Then the LLM evaluates the smaller candidate set and returns structured output.
{
"should_notify": true,
"urgency": "high",
"reason": "The customer reports a production outage."
}
State Model
The poll definition stores:
semantic_condition
examples
negative_examples
user_preference_summary
model_config
The evaluation log stores:
input_reference
model
prompt_version
structured_output
confidence
cost
latency
The poll state stores:
last_seen_ids
last_evaluated_hashes
last_decision
last_decision_reason
Best Fit
Use semantic polling for:
- Important email detection.
- Customer sentiment monitoring.
- Research alerts.
- Sales opportunity detection.
- Security triage.
- Executive briefings.
Weaknesses
LLM calls cost money and add latency. They can also be inconsistent if prompts and schemas are loose. Use deterministic filters first. Ask the model only when judgment is actually needed.
Decision Table: Choosing a Polling Agent Method
| Method | Best Application | Pros | Cons |
|---|---|---|---|
| Scheduled polling worker | Simple recurring assistant tasks | Easy to build, easy to debug, minimal infrastructure | Limited scaling, basic retries, can overload workers if many polls fire together |
| Queue-based polling workers | Production SaaS assistants with many users | Scalable, resilient, supports retries and backpressure | Requires queue infrastructure, idempotency, dead letter handling |
| External tool as task queue | Notion, Jira, Linear, Trello based task execution | Human-friendly, easy to inspect, works with existing workflows | External tools are not perfect queues, atomic claim may be difficult |
| Long-running worker loop | Prototypes and internal tools | Very simple, fast to implement, few moving parts | Weak reliability, poor multi-replica behavior, limited operational control |
| Webhook-first with polling fallback | Event-driven integrations | Fast reaction, fewer API calls, reconciliation catches missed events | Needs public endpoint, event validation, dedupe, provider webhook support |
| Provider-side background job polling | Long-running AI provider jobs | Handles slow AI tasks, simple status model, good for async UX | Only manages provider job status, not full business workflow |
| Durable workflow engine | Long-running multi-step processes | Strong retries, timers, audit history, recovery after crashes | More infrastructure and concepts, heavy for simple polling |
| Persistent agent runtime | Multi-step reasoning agents | Preserves agent context, supports pause and resume, good for tool-heavy tasks | Not a scheduler or queue replacement, still needs operational backend |
| Database sync plus change evaluation | Systems where external data has local value | Clean separation, local reporting, fewer repeated external calls | More storage, more sync complexity, possible privacy and retention concerns |
| Adaptive polling | Bursty sources or variable urgency tasks | Reduces cost, respects rate limits, reacts faster when activity is high | Harder to reason about, not ideal for strict schedules |
| Semantic polling with LLM evaluator | Fuzzy conditions requiring judgment | Handles natural language intent, useful summaries, flexible decisions | Cost, latency, prompt quality risk, should not replace simple code checks |
Recommended Default Architecture
For most production AI assistants, start with this:
polls table
-> scheduler
-> queue
-> stateless workers
-> deterministic filters
-> optional LLM evaluator
-> notification or assistant action
A minimal schema:
CREATE TABLE polls (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
source_type TEXT NOT NULL,
source_ref TEXT NOT NULL,
condition_text TEXT NOT NULL,
schedule_type TEXT NOT NULL,
interval_seconds INTEGER,
timezone TEXT,
next_run_at TIMESTAMP NOT NULL,
last_run_at TIMESTAMP,
cursor_value TEXT,
last_hash TEXT,
status TEXT NOT NULL,
failure_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE TABLE poll_runs (
id TEXT PRIMARY KEY,
poll_id TEXT NOT NULL,
started_at TIMESTAMP NOT NULL,
finished_at TIMESTAMP,
status TEXT NOT NULL,
items_checked INTEGER,
items_matched INTEGER,
decision_summary TEXT,
error TEXT
);
CREATE TABLE notifications (
id TEXT PRIMARY KEY,
poll_id TEXT NOT NULL,
user_id TEXT NOT NULL,
dedupe_key TEXT NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL,
delivered_at TIMESTAMP,
UNIQUE (dedupe_key)
);
This gives you a clean separation:
scheduler owns time
queue owns buffering
worker owns execution
database owns state
LLM owns semantic judgment
assistant owns user interaction
That separation is the heart of a reliable polling agent.
Example: Hermes Agent Processing Notion Tasks
Now let us apply the architecture to a concrete case.
Assume a Notion database contains tasks. Hermes should run every 10 minutes, take one task in Todo state, set it to InProgress, execute it, and then mark it Complete.
This is best described as:
external tool as task queue
+
scheduled polling worker
+
claim or lease based execution
For a production version, it becomes:
queue-based polling with Notion as the human-facing task inbox
Notion Task Properties
The Notion database should contain fields like:
Name
Status: Todo | InProgress | Complete | Failed
Priority
CreatedAt
ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId
RetryCount
LastError
CompletedAt
The important fields are ClaimedAt, ClaimExpiresAt, and RunId. They make the task claim visible and recoverable.
Hermes Execution State
Hermes should also keep its own execution record:
run_id
notion_page_id
started_at
finished_at
status
input_snapshot
tool_calls
result_summary
error
idempotency_key
This protects you if Notion is edited manually, if an API call fails, or if you need to audit what Hermes actually did.
Execution Flow
Every 10 minutes:
Hermes scheduler creates a run
Hermes worker:
finds one Notion task where Status = Todo
sorts by Priority and CreatedAt
claims the task by setting Status = InProgress
writes ClaimedBy, ClaimedAt, ClaimExpiresAt, and RunId
executes the task
writes execution logs to Hermes backend
sets Notion Status = Complete on success
sets Notion Status = Failed on failure
If Hermes crashes after claiming a task, the lease can expire:
Status = InProgress
ClaimExpiresAt < now
A future run can then recover the task or mark it as failed.
Failure Handling
On success:
Status = Complete
CompletedAt = now
LastError = empty
On recoverable failure:
Status = Todo
RetryCount = RetryCount + 1
LastError = short error message
On non-recoverable failure:
Status = Failed
LastError = clear explanation
For safety, Hermes should also use an idempotency key:
notion_page_id + task_version + action_type
This prevents the same task from being executed twice if a retry happens at the wrong time.
Why This Is Not Just Polling
The polling part is only the wake-up mechanism. The real architecture is task claiming and reliable execution.
A naive implementation says:
Every 10 minutes, find a Todo task and do it.
A reliable implementation says:
Every 10 minutes, claim exactly one eligible task, record the run, execute idempotently, and move the task to a terminal state.
That is the difference between a demo and an agent you can trust.
Common Polling Agent Mistakes
Mistake 1: No Claim Protocol
If two workers can see the same task, they can both execute it.
Use:
ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId
Even if you currently run one worker, design as if a second worker might appear later.
Mistake 2: No Dedupe Key
Every external action should have a dedupe key.
user_id + poll_id + source_object_id + action_type + condition_version
This prevents repeated notifications, repeated emails, repeated task execution, and repeated tool calls. The broader principles behind scoping, storing, and testing these keys apply equally here — see Idempotency in Distributed Systems That Actually Works.
Mistake 3: Calling the LLM Too Early
Do not ask the model to do database filtering.
Bad:
Send all tasks to the LLM and ask which one is Todo.
Better:
Use the Notion API filter to fetch Todo tasks.
Then use the LLM only if task interpretation is needed.
Mistake 4: Treating Notion as the Only Backend
Notion is a good human interface. It is not a complete execution backend.
Keep execution logs, retries, traces, and idempotency records in Hermes.
Mistake 5: Infinite Polling
Every poll should have a stop condition.
Examples:
stop after success
stop after date
stop after max retries
stop when user disables it
stop after repeated authorization failure
A polling agent without a stop condition is a quiet cost leak.
Mistake 6: No Observability
You should be able to answer:
What did the agent run?
Why did it run?
What did it read?
What did it change?
Why did it fail?
Did it notify the user?
Did it run twice?
If you cannot answer those questions, the system is not ready for important work.
Observability Checklist
Track metrics such as:
polls_due
polls_started
polls_succeeded
polls_failed
tasks_claimed
tasks_completed
tasks_failed
claim_expired_count
duplicate_suppressed_count
llm_calls
llm_cost
rate_limit_count
average_run_duration
Log fields such as:
poll_id
run_id
source_type
source_object_id
claim_id
cursor_before
cursor_after
decision
dedupe_key
error
Build an admin view for:
active polls
stuck InProgress tasks
recent failures
high retry tasks
dead letter jobs
expensive LLM evaluations
disabled integrations
Polling agents run in the background, where failures are quiet and problems can compound before anyone notices. Background systems need visibility built in from the start, not added as an afterthought when something goes wrong. For the full observability stack for AI and LLM-backed systems — metrics, traces, structured logs, and SLOs — see Observability for LLM Systems: Metrics, Traces, Logs, and Testing in Production.
Final Recommendation
For a serious AI assistant, start with queue-based polling workers and a durable state store. Add webhooks where providers support them. Use adaptive polling when rate limits matter. Use a durable workflow engine when the process is long-running and multi-step. Use persistent agent runtime when the agent needs to reason over time.
For the Hermes and Notion example, the right architecture is:
Notion as the human-facing task inbox
Hermes scheduler every 10 minutes
Hermes worker with claim or lease logic
Hermes backend for execution logs and idempotency
Notion status updates for visibility
The polling interval is not the hard part. The hard part is making sure the agent claims one task, runs it once, records what happened, and leaves the system in a state humans can understand.
That is what turns a polling script into a reliable AI assistant — not the interval, not the model, but the discipline around claiming work, recording it, and leaving the system in a state that humans and future runs can both understand.