Files
Autonomous-Bug-Explorer/.ralph/specs/phase-08-job-queue.md

1.9 KiB

Phase 8: Job Queue System

Tabla jobs (SQLite)

CREATE TABLE IF NOT EXISTS jobs (
  id TEXT PRIMARY KEY,
  type TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'pending',
  payload TEXT NOT NULL,
  result TEXT,
  error TEXT,
  attempts INTEGER NOT NULL DEFAULT 0,
  max_attempts INTEGER NOT NULL DEFAULT 3,
  priority INTEGER NOT NULL DEFAULT 0,
  run_at TEXT NOT NULL,
  started_at TEXT,
  completed_at TEXT,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_jobs_poll ON jobs(status, run_at, priority DESC);

Interface

export interface IJobQueue {
  enqueue<T>(type: string, payload: T, opts?: { runAt?: Date; priority?: number; maxAttempts?: number }): Promise<string>;
  start(): void;
  pause(): void;
  waitForActive(timeoutMs: number): Promise<void>;
}

Polling logic

loop (cada pollIntervalMs):
  SELECT id, type, payload FROM jobs
  WHERE status = 'pending' AND run_at <= datetime('now')
  ORDER BY priority DESC, created_at ASC
  LIMIT 1

  if found:
    UPDATE jobs SET status = 'running', started_at = now, attempts = attempts + 1
    WHERE id = ? AND status = 'pending'  // optimistic lock
    
    if updated 0 rows → skip (otro worker lo tomó)
    
    try:
      result = await executeJob(type, payload)
      UPDATE jobs SET status = 'completed', result = ?, completed_at = now
    catch:
      if attempts >= max_attempts:
        UPDATE jobs SET status = 'failed', error = ?
      else:
        backoff = min(1000 * 2^attempts, 60000)
        UPDATE jobs SET status = 'pending', run_at = now + backoff, error = ?

Job types

  • exploration:run — payload: { sessionId, config }
  • report:generate — payload: { reportId, format, filters }
  • cleanup:old-data — payload: { retentionDays }

NO usar Redis

El job queue es SQLite-based para zero-dependency self-hosted. Es simple, funciona para el volumen esperado (decenas de jobs, no miles).