Files
2nd/10_Wiki/Topic_Programming/AI_and_ML/Autonomous-Polling-Wait-Automation.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

8.1 KiB
Raw Blame History

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-autonomous-polling-wait Autonomous Polling & Wait Automation 10_Wiki/Topics verified self
폴링 자동화
async wait
agent loop
state polling
webhook fallback
hybrid wait
none B 0.85 applied
agent
polling
async
automation
notebooklm
research-loop
state-machine
retry
exponential-backoff
2026-05-10 pending
language framework
TypeScript / Python Async/Await / Promise

Autonomous Polling & Wait Automation

📌 한 줄 통찰

"매 sleeping researcher". 매 long-running task (3-10 분) 의 완료 의 agent 가 자동 감지 + 매 next step 으로 transition. 매 manual button click 의 X. 매 10초 polling + 매 webhook fallback + 매 timeout 의 hybrid.

📖 핵심

매 polling pattern

  • 매 short interval (1-30 sec) 의 state check.
  • 매 max attempts / timeout.
  • 매 simple, 매 stateless.

매 vs webhook

측면 Polling Webhook
Setup Simple Complex (public URL)
Latency Polling interval Near-zero
Server load High (N polls) Low (1 call)
Reliability Self-managed Webhook 의 lost OK
Use case Behind firewall Public service

→ 매 hybrid 의 best.

매 polling strategies

Fixed interval

  • 매 simple.
  • 매 short job 의 OK.

Exponential backoff

  • 매 wait = base × 2^n.
  • 매 server-friendly.

Adaptive

  • 매 ETA estimate.
  • 매 progress-based.

Long polling

  • 매 server 의 hold connection.
  • 매 latency ↓.

매 long-running 의 pattern

  1. Submit job → 매 job_id.
  2. Poll status until complete.
  3. Retrieve result when ready.
  4. Webhook as fallback (optional).
  5. Timeout + manual fallback.

매 NotebookLM Deep Research case

  • 매 average 3-10 min.
  • 매 10 sec polling × 60 = 매 max 10 min.
  • 매 status: "queued" → "running" → "completed" / "error".
  • 매 completed → 매 result fetch.

매 design 의 challenge

  1. Quota: 매 too frequent → 매 API rate limit.
  2. Stale state: 매 status 의 update 의 lag.
  3. Network failure: 매 retry 의 idempotent.
  4. Timeout: 매 server-side retry 의 inflight.
  5. Resource leak: 매 polling 의 stop 보장.

매 best practice

  • Initial delay: 매 즉시 poll X.
  • Exponential + cap: 매 max interval.
  • Jitter: 매 thundering herd 방지.
  • Cancellation: 매 abort signal.
  • Observability: 매 attempt count log.
  • Idempotency: 매 result fetch 의 retry-safe.

💻 패턴

Basic polling (TS)

async function pollUntilDone<T>(
  fetchStatus: () => Promise<{ done: boolean; result?: T }>,
  options: { intervalMs?: number; maxAttempts?: number; timeoutMs?: number } = {},
): Promise<T> {
  const { intervalMs = 10_000, maxAttempts = 60, timeoutMs = 600_000 } = options;
  const start = Date.now();
  
  for (let i = 0; i < maxAttempts; i++) {
    if (Date.now() - start > timeoutMs) throw new Error('Timeout');
    
    const status = await fetchStatus();
    if (status.done) return status.result!;
    
    await new Promise(r => setTimeout(r, intervalMs));
  }
  throw new Error('Max attempts exceeded');
}

Exponential backoff with jitter

async function pollWithBackoff<T>(
  fetchStatus: () => Promise<{ done: boolean; result?: T }>,
  options: { baseMs?: number; maxMs?: number; maxAttempts?: number } = {},
): Promise<T> {
  const { baseMs = 1000, maxMs = 30_000, maxAttempts = 30 } = options;
  
  for (let i = 0; i < maxAttempts; i++) {
    const status = await fetchStatus();
    if (status.done) return status.result!;
    
    const delay = Math.min(maxMs, baseMs * 2 ** i);
    const jittered = delay * (0.5 + Math.random() * 0.5);
    await new Promise(r => setTimeout(r, jittered));
  }
  throw new Error('Max attempts');
}

Hybrid (poll + webhook)

async function awaitJobHybrid(jobId: string, webhookUrl?: string): Promise<Result> {
  // 매 webhook 의 우선 setup
  const webhookPromise = webhookUrl 
    ? listenForWebhook(jobId, webhookUrl, { timeoutMs: 600_000 })
    : null;
  
  // 매 polling 의 fallback
  const pollingPromise = pollUntilDone(
    () => api.getJobStatus(jobId),
    { intervalMs: 10_000, timeoutMs: 600_000 },
  );
  
  // 매 둘 다 race
  return Promise.race([webhookPromise, pollingPromise].filter(Boolean));
}

Cancellation (AbortController)

async function pollCancellable<T>(
  fetchStatus: (signal: AbortSignal) => Promise<{ done: boolean; result?: T }>,
  signal: AbortSignal,
): Promise<T> {
  while (!signal.aborted) {
    const status = await fetchStatus(signal);
    if (status.done) return status.result!;
    await sleep(10_000, signal);
  }
  throw new DOMException('Cancelled', 'AbortError');
}

function sleep(ms: number, signal: AbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    const t = setTimeout(resolve, ms);
    signal.addEventListener('abort', () => {
      clearTimeout(t);
      reject(new DOMException('Cancelled', 'AbortError'));
    });
  });
}

Webhook handler (FastAPI)

from fastapi import FastAPI, BackgroundTasks
import asyncio

pending: dict[str, asyncio.Future] = {}

@app.post('/webhooks/job-done')
async def job_done(payload: dict):
    job_id = payload['id']
    if job_id in pending:
        pending[job_id].set_result(payload)
    return {'ok': True}

async def wait_for_webhook(job_id: str, timeout: float = 600):
    future = asyncio.Future()
    pending[job_id] = future
    try:
        return await asyncio.wait_for(future, timeout=timeout)
    finally:
        pending.pop(job_id, None)

Idempotent result fetch

def fetch_result_idempotent(job_id, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = api.get_result(job_id)
            return response.data
        except TransientError as e:
            if attempt == max_retries - 1: raise
            sleep(2 ** attempt)
        except PermanentError:
            raise

Progress-aware polling

def poll_progress(job_id):
    last_progress = 0
    while True:
        status = api.get_status(job_id)
        if status.done: return status.result
        
        if status.progress > last_progress:
            log(f'Job {job_id}: {status.progress*100:.1f}%')
            last_progress = status.progress
        
        # 매 ETA 기반 의 dynamic
        remaining_eta = (1 - status.progress) * status.elapsed / max(status.progress, 0.01)
        next_poll = min(30, max(2, remaining_eta / 5))
        sleep(next_poll)

🤔 결정 기준

상황 Pattern
Fast (1-30 sec) Fixed 1-2 sec polling
Medium (1-10 min) 5-10 sec polling
Long (10 min-hour) Hybrid (webhook + polling)
Variable Exponential backoff
Cancellable AbortController
Resource-constrained Webhook only
Behind firewall Polling only

기본값: Hybrid (webhook + 10 sec polling) + jitter + cancellation.

🔗 Graph

🤖 LLM 활용

언제: 매 long-running job. 매 agent automation. 매 third-party API integration. 매 batch inference orchestration. 언제 X: 매 streaming (SSE 가 better). 매 sub-second job.

안티패턴

  • No timeout: 매 무한 hang.
  • No jitter: 매 thundering herd.
  • Too short interval: 매 quota burn.
  • No cancel: 매 resource leak.
  • No idempotent fetch: 매 retry 의 corruption.
  • Webhook only (firewall): 매 silent loss.
  • Tight retry on permanent error: 매 useless burn.

🧪 검증 / 중복

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — polling pattern + webhook + 매 TS / Python code (basic, backoff, hybrid, cancellation)