AIアシスタントにおけるポーリングエージェント:11の実装パターン

AIエージェントのための信頼性の高いポーリングパターン

目次

ポーリングエージェントは、AIアシスタントのアーキテクチャの中で最も華やかではない部分の一つですが、同時に最も有用な部分の一つでもあります。

通常のチャットアシスタントは、ユーザーが何かを尋ねるのを待ちます。一方、ポーリングエージェントは絶えず監視し続けます。ソースをチェックし、変化を察知し、重要な変化があるかどうかを判断し、その後、アクションを実行します。そのアクションは、通知、要約、下書きの作成、ツール呼び出し、あるいは完全なワークフローの実行である可能性があります。

これにより、アシスタントは「質問に答える」ことから「これを監視しておく」ことへ進化します。リアクティブ(受動的)である代わりに、ユーザーの代わりに物事を察知し、条件が満たされたときにアクションを実行するバックグラウンドプロセスになります。

AIエージェントが未来的なコントロールコンソールでデータストリームを監視している

重要な設計上のポイントはシンプルです。言語モデル(LLM)に時間、状態、リトライ、ロックの責任を持たせないことです。そのような用途には通常のバックエンドインフラストラクチャを使用します。モデルはその価値が最大の分野で利用します。つまり、複雑な文脈の解釈、意味的な判断、有用な言語生成などです。

ポーリングエージェントとは何か

ポーリングエージェントは、ソースを繰り返しチェックし、特定の条件が満たされたときにアシスタントのアクションをトリガーするバックグラウンドプロセスです。より広範なAIシステムスタックにおいて、アシスタントはLLM、メモリ、ツール、ルーティング、可観測性を組み合わせていますが、ポーリングレイヤーはアシスタントを単なるリアクティブなものではなく、プロアクティブ(能動的)なものとします。5層構造の詳細については、AIアシスタントアーキテクチャ:LLM、メモリ、ツール、ルーティング、可観測性をご参照ください。

例:

  • 毎朝受信ボックスをチェックし、重要なメッセージを要約する。
  • Notionのタスクリストを監視し、次のTodoアイテムを実行する。
  • GitHubのIssueがステータス変更になるまで監視する。
  • 長時間実行されるAIジョブが完了するまでポーリングする。
  • 予約枠が空くまでチェックする。
  • 供給業者ポータルでドキュメントが表示されるまで監視する。
  • 週1回新しい研究論文をスキャンし、関連するものを要約する。

実用的なポーリングエージェントには、5つの役割があります。

  1. 適切なタイミングで起動する。
  2. ソースからデータを読み取る。
  3. すでに確認した内容を記憶する。
  4. 新しい状態が重要かどうかを判断する。
  5. 一度だけ安全にアクションを実行し、重複を防ぐ。

典型的な本番環境のフローは以下のようになります。

スケジューラ
  -> ポーリングワーカー
  -> ソースシステム
  -> 状態ストア
  -> 決定論的フィルタ
  -> オプションのLLM評価
  -> アシスタントアクション

この構造は、最も良い意味で「退屈」です。退屈なシステムは、深夜2時でもデバッグが容易です。

ポーリングエージェントに必要な状態

ポーリングエージェントには永続的な状態が必要です。会話履歴だけでは不十分です。アシスタントは会話を記憶できるかもしれませんが、システムには信頼性の高い運用記録が必要です。

優れたポーリング状態の記録には、通常、以下の要素が含まれます。

{
  "poll_id": "poll_123",
  "user_id": "user_456",
  "source_type": "notion",
  "source_ref": "database_tasks",
  "condition": "Todo状態のタスクを1つ取得して実行する",
  "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"
}

スキーマの詳細はソースに依存しますが、ほとんどのシステムではこれらの概念が必要です。

ポールの定義

エージェントが監視している対象とその理由を記述します。

poll_id
user_id
workspace_id
source_type
source_ref
condition_text
priority
status

例えば:

source_type: notion
source_ref: Tasks database
condition_text: 1つのTodoタスクを見つけ、取得し、実行し、Completeとしてマークする。

スケジュール

エージェントがいつ実行されるべきかを記述します。

interval_seconds
cron_expression
timezone
last_run_at
next_run_at
jitter

10分ごとにNotionをチェックするHermesエージェントの場合:

interval_seconds: 600
timezone: Australia/Melbourne

カーソルまたはスナップショット

エージェントが同じデータを再処理することを防ぎます。

ソースに応じて、以下のようなものを使用します。

last_seen_id
last_seen_timestamp
api_cursor
etag
version
content_hash

Notionタスクキューの場合、カーソルよりもタスクのステータスや取得フィールドの方が重要かもしれません。Gmail、GitHub、または同期APIの場合、カーソルは通常、非常に重要です。

クレーム(取得)またはリース

同じジョブを複数のワーカーが取得するのを防ぎます。

claimed_by
claimed_at
claim_expires_at
run_id

例えば、Notionタスクは以下のように変更できます:

Status: Todo

以下のように変更します:

Status: InProgress
ClaimedBy: hermes
ClaimedAt: 2026-06-19T01:00:00Z
ClaimExpiresAt: 2026-06-19T01:30:00Z
RunId: run_789

これは、「1つのワーカーだけが取得してくれることを願う」ことと、「システムが取得プロトコルを持っている」ことの違いです。

実行記録

実行中に何が起こったかを記録します。

run_id
poll_id
source_object_id
started_at
finished_at
status
items_checked
items_changed
decision_summary
error

実行記録はNotionや他の外部ツールだけでなく、アシスタントバックエンドに保存されるべきです。Notionは人間の可視性には優れていますが、唯一の実行ログとして理想的ではありません。

重複排除記録

重複する通知やアクションの繰り返しを防ぎます。

dedupe_key
poll_id
source_object_id
condition_version
action_type
delivered_at

例えば:

user_456:poll_123:notion_page_999:execute:v1

同じアクションが再度試行された場合、システムはそれを抑制できます。

方法1:スケジュールされたポーリングワーカー

これは最もシンプルで信頼性の高いパターンです。

スケジューラは一定間隔で起動し、ワーカーを呼び出します。ワーカーはソースを読み取り、状態を更新し、必要に応じてアシスタントアクションをトリガーします。

スケジューラ
  -> ワーカー
  -> ソースAPI
  -> データベース
  -> アシスタントアクション

実行方法

スケジューラは時間を担当します。これはcron、クラウドスケジューラ、Kubernetes CronJob、または小さな内部スケジューラである可能性があります。

各インターバルで、ジョブの実行を開始します。ワーカーは設定を読み込み、ターゲットソースをクエリし、結果を保存された状態と比較し、必要に応じてアクションを実行します。

シンプルなアシスタントの場合、これだけで十分です。単一のスケジューラと軽量なワーカープロセスがあれば、キュー、リース、分散協調を必要とせずに、1日数十回のチェックを処理できます。

状態モデル

スケジューラはほとんど何も保存しません。通常、ジョブをトリガーするタイミングのみを知っています。

アプリケーションデータベースは重要な状態を保存します。

ポールの定義
スケジュール
カーソルまたはスナップショット
最終実行時間
失敗カウント
ステータス

ワーカーはステートレスであるべきです。実行中に一時的なデータを持つことができますが、永続的な真実はデータベースに属します。

例のフロー

10分ごと:
  Hermesポーリングワーカーをトリガー

ワーカー:
  アクティブなポール設定を読み込む
  ソースをクエリ
  直前の状態と比較
  決定論的チェックを実行
  必要に応じてLLMを呼び出す
  状態を更新
  アシスタントイベントを発生させる

最も適した用途

スケジュールされたポーリングワーカーは以下に使用します。

  • 毎日の要約。
  • 毎時間のチェック。
  • 小規模な内部自動化。
  • シンプルな「これを監視する」タスク。
  • 低〜中規模のアシスタントジョブ。

弱点

スケジュールされたポーリングは理解しやすいですが、大規模化すると脆弱になる可能性があります。多くのポールが同時に実行されると、ワーカーが過負荷になったり、プロバイダのレート制限に達したりする可能性があります。スケジューラが直接作業を開始する場合、リトライも複雑になる可能性があります。

方法2:キューベースのポーリングワーカー

キューベースのポーリングは、本番環境のAIアシスタントにおいてデフォルトとして最も適しています。

スケジューラはポーリングを直接実行しません。キューにジョブを投入します。ワーカープロセスはキューからジョブを取得します。

スケジューラ
  -> キュー
  -> ワーカープール
  -> ソースAPI
  -> 状態ストア
  -> アシスタントアクション

実行方法

スケジューラは実行期限の来たポールをスキャンし、ジョブをキューに投入します。ワーカーはキャパシティがあるときにジョブを取得します。

これにより、バックプレッシャー(負荷制御)が実現します。システムが忙しい場合、ジョブはソースAPIやLLMプロバイダを圧倒することなくキューで待機します。

状態モデル

データベースはポールの状態を保存します。

poll_id
user_id
source_ref
condition_text
next_run_at
cursor
status
failure_count

キューメッセージは小さく保つべきです。

{
  "poll_id": "poll_123",
  "scheduled_for": "2026-06-19T01:10:00Z",
  "attempt": 1
}

ワーカーは開始時にデータベースから完全な状態を読み込みます。

例のフロー

1分ごと:
  スケジューラは next_run_at <= now のポールを見つけ
  スケジューラはジョブをキューに投入

ワーカー:
  キューからジョブを取得
  ポールにロックまたはリースを適用
  ソースをクエリ
  状態を更新
  必要に応じてアシスタントアクションを発生させる
  next_run_at を設定

最も適した用途

キューベースのポーリングは以下に使用します。

  • 複数ユーザーのAIアシスタント。
  • 多数の同時ポール。
  • レート制限を持つ統合。
  • リトライ可能なバックグラウンド作業。
  • 処理時間が異なるジョブ。
  • 信頼性が重要なSaaS製品。

弱点

キューはインフラストラクチャを追加します。デッドレター処理、冪等性、可視性タイムアウト、リトライポリシーが必要です。これは本番システムには価値がありますが、小さなプロトタイプには過剰かもしれません。

方法3:外部ツールをタスクキューとして利用

これはNotionとHermesの例におけるパターンです。

外部ツールは単なるデータソースではありません。人間の目に触れるタスクキューとなります。エージェントは定期的にツールをチェックし、1つのタスクを取得し、実行し、タスクのステータスを更新します。

スケジューラ
  -> Hermesワーカー
  -> Notionデータベース
  -> 1つのタスクを取得
  -> タスクを実行
  -> Notionステータスを更新

実行方法

10分ごとに、HermesはNotionデータベースをクエリし、Todo状態の1つのタスクを検索します。次のタスクを選択し、通常は優先度と作成時間に基づきます。その後、InProgressに設定することでタスクを取得します。

その後、Hermesはタスクを実行します。実行が成功した場合、タスクをCompleteとマークします。実行が失敗した場合、タスクをFailedとマークするか、リトライカウントとともにTodoに戻します。

状態モデル

Notionは人間の目に触れるタスク状態を保存します。

Title
Description
Status: Todo | InProgress | Complete | Failed
Priority
CreatedAt
ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId
RetryCount
LastError
CompletedAt

Hermesバックエンドは運用実行状態を保存します。

run_id
notion_page_id
started_at
finished_at
execution_status
tool_calls
LLM trace
error details
idempotency_key

この分割は重要です。Notionは可視性と手動編集に優れています。Hermesバックエンドはログ、リトライ、重複排除、監査履歴には適しています。

例のフロー

10分ごと:
  Hermesが起動

Hermes:
  Status = Todo のタスクをNotionからクエリ
  Priority, CreatedAt でソート
  選択したタスクを InProgress に更新
  ClaimedBy, ClaimedAt, ClaimExpiresAt, RunId を設定
  タスクを実行
  実行ログを記録
  タスクを Complete または Failed に設定

最も適した用途

このパターンは以下の場合に使用します。

  • 人間がすでにNotion、Jira、Linear、Trello、または他のツールで作業を管理している場合。
  • アシスタントが可視的なタスクを処理する場合。
  • タスクボードがユーザーインターフェースである場合。
  • シンプルなヒューマンインザループ(人間が関与する)自動化モデルが必要な場合。

弱点

外部ツールは完璧なキューではありません。アトミックな取得が制限される可能性があります。クエリの整合性に遅れが生じる可能性があります。レート制限が適用される可能性があります。エージェントが複数のインスタンスで実行できる場合、注意深い取得またはリース戦略が必要です。

実用的な推奨事項は、Notionを人間の目に触れるタスクインボックスとして使用し、すべての実行ログ、リトライ記録、トレース、冪等性キーをHermesに保持することです。Notionはユーザーに可視性を提供し、Hermesはシステムの信頼性を維持します。Hermesにおけるこのパターンの背後にあるディスパッチャーと並行性メカニクスについては、Hermesエージェントでのセルフホスト型LLMワークフローのためのKanbanをご参照ください。

方法4:長時間実行ワーカーループ

長時間実行ループは最もシンプルな実装です。

while True:
    due_polls = db.find_due_polls()
    for poll in due_polls:
        run_poll(poll)
    sleep(30)

このパターンはスケジューリングと実行を1つのサービスに組み合わせるため、バックグラウンドエージェント作業の最もシンプルな開始点となります。

実行方法

ワーカープロセスは継続的に実行されます。数秒または数分ごとに、データベースをチェックし、実行期限の来たポールを実行します。構築が容易で、論理的に理解しやすく、開発中の反復が高速です。

状態モデル

データベースはまだ永続的な状態を保存します。

ポール設定
next_run_at
カーソル
最終結果
失敗カウント
ステータス

プロセスメモリは一時的な状態のみを含むべきです。

現在のバッチ
短命キャッシュ
進行中の実行

重要な進捗をメモリにのみ保存しないでください。プロセスがクラッシュした場合、永続ストレージに書き込まれていない状態は消失し、次回の実行ではどこで中断したのかを知る術がなくなります。

最も適した用途

長時間実行ループは以下に使用します。

  • プロトタイプ。
  • ローカル開発。
  • 内部ツール。
  • 単一テナントシステム。
  • 低容量エージェント。

弱点

このパターンは複数のレプリカで使用するとリスクが高くなります。リースなしでは、2つのワーカーが同じポールを実行する可能性があります。また、本物のキューまたはワークフローエンジンの運用機能も欠けています。

長時間実行ループは開始点として間違っているわけではありませんが、分散スケジューラではなく、そう扱うべきではありません。複数のレプリカやより強い信頼性の保証が必要になった瞬間、上記のより構造化されたパターンのいずれかに移行する必要があります。

方法5:Webhookファースト+ポーリングフォールバック

ソースがWebhookをサポートする場合、それを使用してください。ポーリングは主たるメカニズムではなく、バックアップであるべきです。この分割はエージェントプロトコル設計にも現れます。A2Aプッシュ通知がクライアントハンドラを起動し、その後GetTaskをポーリングして完全な状態を取得します。詳細はA2Aストリーミングと長期間実行エージェントワークフローのための非同期タスクをご参照ください。

外部システム
  -> Webhookエンドポイント
  -> イベントストア
  -> アシスタントアクション

調整ポーリング
  -> ソースAPI
  -> イベントストアと比較
  -> 逃れたイベントを修復

実行方法

外部システムは何か変化があったとき、Webhookエンドポイントにイベントを送信します。システムはイベントを保存し、非同期に処理します。

slowerな調整ポーリングは数時間ごとまたは1日1回実行されます。逃れたイベントがないかチェックします。

状態モデル

イベントストアは受信したWebhookを記録します。

event_id
source_type
source_object_id
event_type
received_at
payload_hash
processed_at
signature_valid

調整ポーリングは以下を保存します。

last_reconciliation_at
last_seen_cursor
last_seen_version

ソースオブジェクトテーブルは最新の状態を保存します。

external_id
current_status
external_updated_at
last_processed_event_id

最も適した用途

Webhookファーストアーキテクチャは以下に使用します。

  • GitHubイベント。
  • Stripeイベント。
  • Slackイベント。
  • CRM更新。
  • デプロイ通知。
  • チケットシステム。

弱点

Webhookはパブリックエンドポイント、署名検証、リプレイ保護、イベントの重複排除が必要です。一部のプロバイダは不完全なイベントも送信するため、完全なオブジェクトを取得する必要がある場合があります。

それでも、優れたWebhookが存在する場合、毎分ポーリングするのは通常無駄です。

方法6:プロバイダ側バックグラウンドジョブのポーリング

時には、ポーリング対象はAIジョブそのものです。

アプリケーションは長時間実行プロバイダジョブを開始し、ジョブIDを保存し、後で完了したかどうかをチェックします。

アプリ
  -> AIバックグラウンドジョブを開始
  -> プロバイダジョブIDを保存
  -> 状態をポーリング
  -> 結果を取得
  -> ユーザーに通知

実行方法

アシスタントはプロバイダでジョブを開始します。プロバイダはIDを返します。バックエンドはそのIDを保存し、ジョブが成功、失敗、期限切れ、またはタイムアウトするまでその状態をチェックします。

状態モデル

バックエンドは以下を保存します。

assistant_task_id
provider_job_id
user_id
status
created_at
last_checked_at
expires_at
result_ref

プロバイダは一時的なジョブ状態と出力を保存します。

出力が重要な場合は、ジョブが完了した直後に独自の永続ストレージにコピーしてください。プロバイダ側の結果ストレージは保持期間が短く、システム内の適切なアーカイブの代替にはなりません。

最も適した用途

プロバイダ側バックグラウンドジョブのポーリングは以下に使用します。

  • 長時間のAI研究タスク。
  • 大容量ドキュメント処理。
  • コードベース分析。
  • レポート生成。
  • データ抽出ジョブ。
  • 通常のHTTPリクエストタイムアウトを超えるタスク。

弱点

このパターンは1つの問題を解決します:長時間プロバイダジョブを待つこと。それはワークフローエンジン、スケジューラ、キュー、またはビジネス状態ストアの代替ではありません。

方法7:永続ワークフローエンジン

永続ワークフローエンジンは、長時間実行、タイマー、リトライ、および回復を管理します。TemporalはGoおよびPythonベースのアシスタントバックエンドで最も一般的な選択肢です。完全な実装ガイドについては、GoでのTemporalを使用したワークフローアプリケーションの実装をご参照ください。

すべての待機とリトライを手動で接続する代わりに、プロセスをワークフローとしてモデル化します。

ワークフローエンジン
  -> アクティビティ:ソースをチェック
  -> タイマー:待機
  -> アクティビティ:結果を評価
  -> アクティビティ:ユーザーに通知

実行方法

ワークフローは1回開始され、その後、独自の待機を制御します。数分、数日、または数週間のスリープが可能です。ワーカープロセスがクラッシュしても、ワークフローエンジンは記録された状態から再開できます。

状態モデル

ワークフローエンジンは以下を保存します。

workflow_id
execution history
timer state
activity attempts
retry policy
current workflow state

アプリケーションデータベースは以下を保存します。

ユーザー向けポール定義
認可参照
ビジネス記録
通知記録

ワークフローエンジンはプロセス状態(実行履歴、タイマー、リトライ、アクティビティ試行)を所有します。データベースはビジネス状態(ユーザー設定、認可記録、通知、監査ログ)を所有します。これらを分離することで、各レイヤーが両方の混在したハイブリッドにならないようにします。

最も適した用途

永続ワークフローは以下に使用します。

  • マルチステップのビジネスプロセス。
  • 長時間実行自動化。
  • 人間の承認フロー。
  • 信頼性の高いリトライ。
  • 監査可能なバックグラウンド作業。
  • 失敗後に再開する必要があるプロセス。

弱点

ワークフローエンジンは概念とインフラストラクチャを追加します。プロセスが重要な場合に優れていますが、単純な毎時間のチェックには重すぎます。

方法8:永続エージェントランタイム

一部のエージェントフレームワークは、エージェント状態を永続化し、実行をチェックポイントし、後で再開できます。

エージェント自体にマルチステップの推論プロセスがある場合に役立ちます。

スケジューラまたはワークフロー
  -> エージェントランタイム
  -> チェックポイントを読み込む
  -> ツールを呼び出す
  -> チェックポイントを保存
  -> 後で再開

実行方法

外部スケジューラまたはワークフローがエージェントを開始します。エージェントランタイムは以前の状態を読み込み、次のステップを実行し、必要に応じてツールを呼び出し、チェックポイントを記録します。

エージェントランタイムは唯一のスケジューラであるべきではありません。より大きなバックエンドアーキテクチャ内の推論レイヤーとして扱うのが適切です。

状態モデル

エージェントチェックポイントストレージには以下が含まれます。

current node
messages
tool outputs
intermediate reasoning state
pending action

長期メモリには以下が含まれます。

stable user preferences
facts
project context
source references

運用状態は依然として他にあります。

poll schedule
cursor
status
retry count
dedupe records

有用なルール:メモリはカーソルではなく、チェックポイントはキューではありません。エージェントメモリはモデルが知っているものを保存し、運用状態はプロセスがどこにいて、何をしたかを追跡します。これらを混同すると、並行性または再起動後にのみ現れる微妙なバグが引き起こされます。ワーキングメモリ、永続状態、取得レイヤーの完全な設計空間については、AIアシスタントにおけるメモリシステムをご参照ください。

最も適した用途

永続エージェントランタイムは以下に使用します。

  • マルチステップ研究。
  • 一時停止と再開を行うエージェント。
  • 人間が関与する作業。
  • ツール中心の推論。
  • 文脈が時間とともに蓄積されるタスク。

弱点

エージェントの永続化は運用信頼性と同じではありません。スケジューリング、ロック、リトライ、レート制限、監査ログが必要です。

方法9:データベース同期+変更評価

このパターンでは、ポーリングは外部データを独自のデータベースに同期するために使用されます。その後、アシスタントは外部APIを直接クエリするのではなく、ローカルデータベースの変更に対応します。

同期ポーラー
  -> 外部API
  -> ローカルデータベース
  -> 変更評価
  -> アシスタントアクション

これにより、データ同期とアシスタントの知性が分離されます。同期ワーカーはローカルレコードを最新に保つ責任を持ち、評価者は変更に対するアクションを決定する責任を持ちます。各レイヤーは独立してテスト、監視、スケーリングできます。

実行方法

同期ワーカーは定期的に外部変更を取得し、正規化されたレコードをデータベースに書き込みます。2番目のワーカーまたは変更ストリームは更新された行を検出し、アシスタントがアクションを実行する必要があるかどうかを決定します。

状態モデル

同期テーブルは以下を保存します。

external_id
source_type
raw_payload
normalized_fields
external_updated_at
synced_at
version
content_hash

同期状態は以下を保存します。

source_cursor
last_sync_at
rate_limit_status
failure_count

アシスタント評価テーブルは以下を保存します。

object_id
evaluation_status
last_evaluated_hash
decision
notification_id

最も適した用途

このパターンは以下に使用します。

  • CRM同期。
  • チケットシステム。
  • 会計ドキュメント。
  • 製品在庫。
  • 監査レビュー。
  • 検索インデックス。
  • 内部ダッシュボード。

弱点

すべてを同期するのは高コストで不要な場合があります。プライバシーと保持の義務が生じる可能性もあります。ローカルデータに単一のアシスタントアクションを超えた価値がある場合にこのパターンを使用します。

方法10:アダプティブポーリング

アダプティブポーリングは、状態、緊急度、または最近のアクティビティに基づいて頻度を変更します。

アクティブなオブジェクト:1分ごとポーリング
待機中のオブジェクト:1時間ごとポーリング
古いオブジェクト:1日1回ポーリング
完了したオブジェクト:ポーリング停止

実行方法

各実行後、ワーカーは次回の実行タイミングを決定します。

オブジェクトが最近変更された場合、早くポーリングします。長い間変更がない場合、速度を落とします。タスクが完了した場合、停止します。

状態モデル

ポール状態には以下が含まれます。

current_interval
minimum_interval
maximum_interval
backoff_policy
last_activity_at
priority
stop_condition

ソーススナップショットには以下が含まれます。

status
updated_at
activity_level
expected_next_change

最も適した用途

アダプティブポーリングは以下に使用します。

  • デプロイメントステータス。
  • 配送追跡。
  • カレンダー枠の可用性。
  • 価格監視。
  • ビルドジョブ。
  • 長時間実行プロバイダタスク。
  • バースト的な更新がある任意のソース。

弱点

アダプティブポーリングは論理的に理解しにくい場合があります。タスクが厳密な時間に実行される必要がある場合、厳密に保ってください。コンプライアンスジョブを賢くしすぎないでください。

方法11:LLM評価者によるセマンティックポーリング

セマンティックポーリングは、条件が曖昧な場合に使用されます。

コードは以下に回答できます。

ステータスはCompleteですか?
価格は100未満ですか?
新しいメッセージがありますか?

LLMは以下に回答を支援できます。

このメールは緊急に聞こえますか?
この顧客は不満を抱えている可能性が高いですか?
この研究論文は関連していますか?
この変更は私の注意が必要ですか?

実行方法

ワーカーはまず安価な決定論的フィルタを適用します。候補アイテムのみがLLMに送られます。

新しいアイテム?
ソースフィルタに一致?
すでに処理されていない?
明らかに無関係ではない?

その後、LLMはより小さな候補セットを評価し、構造化された出力を返します。

{
  "should_notify": true,
  "urgency": "high",
  "reason": "顧客は本番障害を報告しています。"
}

状態モデル

ポール定義は以下を保存します。

semantic_condition
examples
negative_examples
user_preference_summary
model_config

評価ログは以下を保存します。

input_reference
model
prompt_version
structured_output
confidence
cost
latency

ポール状態は以下を保存します。

last_seen_ids
last_evaluated_hashes
last_decision
last_decision_reason

最も適した用途

セマンティックポーリングは以下に使用します。

  • 重要なメールの検出。
  • 顧客感情の監視。
  • 研究アラート。
  • セールス機会の検出。
  • セキュリティ分類。
  • 経営陣向け要約。

弱点

LLM呼び出しはコストがかかり、レイテンシーを追加します。プロンプトとスキーマが緩いと一貫性がない場合もあります。まず決定論的フィルタを使用してください。判断が必要な場合にのみモデルに問いかけます。

決定表:ポーリングエージェント方法の選択

方法 最も適したアプリケーション 利点 欠点
スケジュールされたポーリングワーカー シンプルな再帰的アシスタントタスク 構築容易、デバッグ容易、最小限のインフラストラクチャ スケーリング制限、基本的なリトライ、多数のポールが同時に実行されるとワーカーが過負荷になる可能性
キューベースのポーリングワーカー 多数のユーザーを持つ本番SaaSアシスタント スケーラブル、堅牢、リトライとバックプレッシャーをサポート キューインフラストラクチャ、冪等性、デッドレター処理が必要
外部ツールをタスクキューとして利用 Notion、Jira、Linear、Trelloベースのタスク実行 人間フレンドリー、検査容易、既存のワークフローと連携 外部ツールは完璧なキューではない、アトミックな取得が困難な可能性
長時間実行ワーカーループ プロトタイプと内部ツール とてもシンプル、実装迅速、移動部品が少ない 信頼性が弱い、マルチレプリカでの動作が悪い、運用制御が限定的
Webhookファースト+ポーリングフォールバック イベント駆動型統合 高速反応、API呼び出しが少なく、調整が逃れたイベントを捕捉 パブリックエンドポイント、イベント検証、重複排除、プロバイダWebhookサポートが必要
プロバイダ側バックグラウンドジョブのポーリング 長時間実行AIプロバイダジョブ 遅いAIタスクを処理、シンプルなステータスモデル、非同期UXに良い プロバイダジョブ状態のみを管理、完全なビジネスワークフローではない
永続ワークフローエンジン 長時間実行マルチステッププロセス 強力なリトライ、タイマー、監査履歴、クラッシュ後の回復 インフラストラクチャと概念が増加、単純なポーリングには重い
永続エージェントランタイム マルチステップ推論エージェント エージェントコンテキストを保持、一時停止と再開をサポート、ツール中心のタスクに適している スケジューラやキューの代替ではない、依然として運用バックエンドが必要
データベース同期+変更評価 外部データにローカル価値があるシステム 明確な分離、ローカルレポート、反復的な外部呼び出しが少なく ストレージ増加、同期の複雑さ、プライバシーと保持に関する懸念の可能性
アダプティブポーリング バースト的なソースまたは可変緊急度のタスク コスト削減、レート制限を尊重、アクティビティが高いときにより高速に反応 論理的に理解しにくい、厳密なスケジュールには適さない
LLM評価者によるセマンティックポーリング 判断が必要な曖昧な条件 自然言語の意図を処理、有用な要約、柔軟な決定 コスト、レイテンシー、プロンプト品質リスク、単純なコードチェックの代替ではない

推奨デフォルトアーキテクチャ

大多数の本番AIアシスタントでは、以下から始めてください。

ポールステーブル
  -> スケジューラ
  -> キュー
  -> ステートレスワーカー
  -> 決定論的フィルタ
  -> オプションのLLM評価者
  -> 通知またはアシスタントアクション

最小限のスキーマ:

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)
);

これにより、明確な分離が得られます。

スケジューラは時間を所有
キューはバッファリングを所有
ワーカーは実行を所有
データベースは状態を所有
LLMはセマンティック判断を所有
アシスタントはユーザー相互作用を所有

この分離は、信頼性の高いポーリングエージェントの核心です。

例:HermesエージェントによるNotionタスクの処理

ここで、このアーキテクチャを具体的なケースに適用しましょう。

Notionデータベースにタスクが含まれていると仮定します。Hermesは10分ごとに実行され、Todo状態の1つのタスクを取得し、InProgressに設定し、実行し、その後Completeとマークします。

これは以下として最もよく記述されます。

外部ツールをタスクキューとして利用
+
スケジュールされたポーリングワーカー
+
クレームまたはリースベースの実行

本番版では、以下になります。

Notionを人間の目に触れるタスクインボックスとするキューベースのポーリング

Notionタスクプロパティ

Notionデータベースには以下のようなフィールドを含めるべきです。

Name
Status: Todo | InProgress | Complete | Failed
Priority
CreatedAt
ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId
RetryCount
LastError
CompletedAt

重要なフィールドはClaimedAtClaimExpiresAtRunIdです。これらはタスクの取得を可視化し、回復可能にします。

Hermes実行状態

Hermesは独自の実行記録も保持すべきです。

run_id
notion_page_id
started_at
finished_at
status
input_snapshot
tool_calls
result_summary
error
idempotency_key

これにより、Notionが手動で編集された場合、API呼び出しが失敗した場合、またはHermesが実際に何をしたかを監査する必要がある場合に保護されます。

実行フロー

10分ごと:
  Hermesスケジューラが実行を作成

Hermesワーカー:
  Status = Todo のNotionタスクを1つ見つける
  Priority と CreatedAt でソート
  Status = InProgress に設定してタスクを取得
  ClaimedBy, ClaimedAt, ClaimExpiresAt, RunId を記録
  タスクを実行
  実行ログをHermesバックエンドに記録
  成功時にNotion Status = Complete に設定
  失敗時にNotion Status = Failed に設定

Hermesがタスクを取得した後にクラッシュした場合、リースは期限切れになります。

Status = InProgress
ClaimExpiresAt < now

将来の実行は、タスクを回復するか、失敗としてマークできます。

障害処理

成功時:

Status = Complete
CompletedAt = now
LastError = empty

回復可能な失敗時:

Status = Todo
RetryCount = RetryCount + 1
LastError = 短いエラーメッセージ

回復不可能な失敗時:

Status = Failed
LastError = 明確な説明

安全性のために、Hermesは冪等性キーも使用するべきです。

notion_page_id + task_version + action_type

これにより、リトライが不適切なタイミングで発生した場合でも、同じタスクが2回実行されるのを防ぎます。

なぜこれが単なるポーリングではないのか

ポーリング部分は起動メカニズムにすぎません。真のアーキテクチャはタスクの取得と信頼性の高い実行です。

ナイーブな実装は以下と言います。

10分ごと、Todoタスクを見つけて実行する。

信頼性の高い実装は以下と言います。

10分ごと、正確に1つの適格タスクを取得し、実行を記録し、冪等的に実行し、タスクを最終状態に移動する。

これは、デモと信頼できるエージェントとの違いです。

一般的なポーリングエージェントの間違い

間違い1:取得プロトコルの欠如

2つのワーカーが同じタスクを見える場合、両方が実行する可能性があります。

以下を使用してください。

ClaimedBy
ClaimedAt
ClaimExpiresAt
RunId

現在1つのワーカーを実行している場合でも、後で2番目のワーカーが現れるかもしれないという前提で設計してください。

間違い2:重複排除キーの欠如

すべての外部アクションには重複排除キーが必要です。

user_id + poll_id + source_object_id + action_type + condition_version

これにより、重複する通知、重複するメール、重複するタスク実行、および重複するツール呼び出しを防ぎます。これらのキーのスコーピング、保存、テストに関する広範な原則はここでも同等に適用されます — 実際に機能する分散システムにおける冪等性をご参照ください。

間違い3:LLMの早期呼び出し

モデルにデータベースフィルタリングをさせないでください。

悪い例:

すべてのタスクをLLMに送信し、どれがTodoかを尋ねる。

良い例:

Notion APIフィルタを使用してTodoタスクを取得する。
その後、タスクの解釈が必要な場合にのみLLMを使用する。

間違い4:Notionを唯一のバックエンドとして扱う

Notionは優れた人間インターフェースです。完全な実行バックエンドではありません。

実行ログ、リトライ、トレース、冪等性記録をHermesに保持してください。

間違い5:無限ポーリング

すべてのポールには停止条件が必要です。

例:

成功後に停止
日付後に停止
最大リトライ後に停止
ユーザーが無効にした後に停止
繰り返し認証失敗後に停止

停止条件のないポーリングエージェントは、静かなコストの漏れです。

間違い6:可観測性の欠如

以下に答えるべきです。

エージェントは何を実行した?
なぜ実行した?
何を读了?
何を変更した?
なぜ失敗した?
ユーザーに通知した?
2回実行した?

これらの質問に答えられない場合、システムは重要な作業の準備が整っていません。

可観測性チェックリスト

以下のメトリクスを追跡してください。

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

以下のログフィールドを記録してください。

poll_id
run_id
source_type
source_object_id
claim_id
cursor_before
cursor_after
decision
dedupe_key
error

以下のための管理者ビューを構築してください。

アクティブなポール
進捗が止まったInProgressタスク
最近の失敗
高リトライタスク
デッドレタージョブ
高コストLLM評価
無効化された統合

ポーリングエージェントはバックグラウンドで実行され、そこで障害は静かで、問題が誰かに気づかれる前に積み重なる可能性があります。バックグラウンドシステムには、何か問題が発生した後に追従して追加するのではなく、初期から組み込まれた可視性が必要です。AIおよびLLM搭載システムのための完全な可観測性スタック(メトリクス、トレース、構造化ログ、SLO)については、LLMシステムのための可観測性:メトリクス、トレース、ログ、および本番環境でのテストをご参照ください。

最終推奨事項

ポーリングパターンは、マルチエージェントシステムの下のプロアクティブなスケジューリングレイヤーを処理します。独立してポーリングするだけでなく、相互に調整する必要のある複数のエージェントを所有するようになったら、次の設計決定はそれらがどのように調整するかです:ハブアンドスポーク、パイプライン、ファンアウト、またはスワーム。マルチエージェントオーケストレーションパターンは、障害モードと決定フレームワークとともに、それらの調整トポロジをカバーしています。

本格的なAIアシスタントの場合、キューベースのポーリングワーカーと永続状態ストアから始めてください。プロバイダがサポートする場所にWebhookを追加してください。レート制限が重要な場合にアダプティブポーリングを使用してください。プロセスが長時間実行でマルチステップの場合に永続ワークフローエンジンを使用してください。エージェントが時間とともに推論を必要とする場合に永続エージェントランタイムを使用してください。

HermesとNotionの例の場合、正しいアーキテクチャは以下です。

Notionを人間の目に触れるタスクインボックスとして
Hermesスケジューラは10分ごと
Hermesワーカーは取得またはリースロジックを備える
Hermesバックエンドは実行ログと冪等性のために
Notionステータス更新は可視性のために

ポーリングインターバルは難しい部分ではありません。難しい部分は、エージェントが1つのタスクを取得し、1回実行し、何が起こったかを記録し、人間が理解できる状態にシステムを残すことを確実にすることです。

ポーリングスクリプトを信頼性の高いAIアシスタントに変えるのは、インターバルでもモデルでもなく、作業の取得、記録、そして人間と将来の実行の両方が理解できる状態にシステムを残すという規律です。

購読する

システム、インフラ、AIエンジニアリングの新記事をお届けします。