Polling Agents in AI Assistants: 11 Implementation Patterns

Reliable polling patterns for AI agents.

Page content

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.

AI agent monitoring data streams at a futuristic control console

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:

  1. Wake up at the right time.
  2. Read from the source.
  3. Remember what it has already seen.
  4. Decide whether the new state matters.
  5. 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

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.

Subscribe

Get new posts on AI systems, Infrastructure, and AI engineering.