Ralph - Autonomní development loop pro Claude Code

Open-source autonomous development loop s inteligentní detekcí ukončení, rate limiting a session managementem.

BashNode.jsClaude Code

Open-source kontribuce

Brief

Claude Code je výborný v ad-hoc tasks: open file, fix bug, write component, close. Ale když potřebuješ, aby agent jel hodiny na jednom úkolu - refaktor 50 souborů, mass migration scraperu, generování 109 článků - narážíš na čtyři problémy: agent občas spadne na rate limit, agent občas tvrdí, že "skončil", i když není hotový, agent občas zacyklí, a já bych nad tím musel sedět a hlídat.

Ralph je smyčka, která ho hlídá místo mě. Open-source, repo na github.com/ondrejknedla/ralph-claude-code. Bash watchdog + Node session manager + on-disk manifest. Výsledek: spustím ralph start --task=docs.md před spaním a ráno najdu hotovou práci. Použil jsem ho v produkci na 8hodinovém DokladBot batchi (109 SEO článků), na refaktor Krteku scraperu, a na masovou migraci typů napříč monorepem.

Architektura

ralph/
├── bin/ralph                ← bash entrypoint, watchdog
├── src/
│   ├── manager.ts           ← Node session manager, spawns claude-code
│   ├── detector.ts          ← end-of-task heuristiky
│   ├── rate-limit.ts        ← exponenciální backoff, retry state
│   └── manifest.ts          ← on-disk state JSON
└── runs/
    └── <session-id>/
        ├── manifest.json    ← session state
        ├── transcript.log   ← live tail z claude-code
        └── checkpoints/     ← progresní snapshots

Bash watchdog je tenoučký - jen spawne Node manager, naslouchá SIGINT a logguje. Veškerá logika je v Node, protože tam má smysl práce s JSON state, AbortControllery, exponential backoff timery.

End-of-task detection

Tohle je nejtěžší část a kde Ralph vydělává oproti while true; do claude; sleep 60; done. Claude občas řekne "Done. Task completed." uprostřed multi-step tasku, protože jen dokončil subtask. Pokud watchdog tomu uvěří, předčasně ukončí session a zbytek práce nezůstane udělaný.

Ralph má 3-step heuristiku:

// src/detector.ts
const COMPLETION_PATTERNS = [
  /\bdone\.?\s*$/im,
  /\btask completed\.?\s*$/im,
  /\ball (?:done|finished)\.?\s*$/im,
  /\bnothing (?:more|left) to do\b/im,
];
 
export async function isReallyDone(state: SessionState): Promise<boolean> {
  // 1. textový pattern
  const lastChunk = state.transcript.slice(-2000);
  const matchesPattern = COMPLETION_PATTERNS.some((re) => re.test(lastChunk));
  if (!matchesPattern) return false;
 
  // 2. idle timeout - žádný nový output 90 sekund
  const idleMs = Date.now() - state.lastOutputAt;
  if (idleMs < 90_000) return false;
 
  // 3. recursive self-check - zeptáme agenta znovu
  const verification = await sendToClaude({
    sessionId: state.sessionId,
    prompt: `Are you actually finished with the original task? Re-read your initial brief and answer YES or NO with one sentence why.`,
  });
 
  return /^yes\b/i.test(verification.trim());
}

Krok 3 je deal-breaker. LLM vesele řekne "done" mid-task, ale když ho explicitně přinutíš vrátit se k původnímu briefu a ověřit, často odpoví "no, I still need to handle X". Ten extra round-trip stojí ~5 sekund a $0.01 - vs ztratit 6 hodin práce, protože smyčka spadla předčasně.

Rate limit handling

Anthropic API vrací standardizované rate limit headers:

anthropic-ratelimit-requests-remaining: 0
anthropic-ratelimit-tokens-reset: 2026-01-25T22:43:11Z
retry-after: 47

Ralph je čte a místo blind retry persistuje state a čeká přesně:

// src/rate-limit.ts
export async function handleRateLimit(
  err: AnthropicAPIError,
  manifest: Manifest,
): Promise<void> {
  const retryAfter = Number(err.headers['retry-after'] ?? 60);
  const resetAt = new Date(err.headers['anthropic-ratelimit-tokens-reset']);
  const waitMs = Math.max(retryAfter * 1000, resetAt.getTime() - Date.now());
 
  // pulpé exponenciální backoff jen když retry-after chybí
  const backoffMs = retryAfter ? waitMs : Math.min(2 ** manifest.retryCount * 1000, 5 * 60_000);
 
  manifest.retryCount += 1;
  manifest.nextRetryAt = Date.now() + backoffMs;
  manifest.lastError = err.message;
  await writeManifest(manifest);
 
  log.info(`rate-limited, sleeping ${(backoffMs / 1000).toFixed(0)}s (retry ${manifest.retryCount})`);
  await sleep(backoffMs);
}

Klíč: po 5 retries Ralph nezdvojnásobuje, stopne a alertuje. Lepší probudit mě v 03:00, než vyzkoušet další 4× za sebou.

Session continuity

Manifest je single source of truth o stavu session. Když Ralph spadne (host reboot, OOM kill, manuál Ctrl+C), ralph resume <session-id> načte manifest a pokračuje:

{
  "sessionId": "ralph-2026-01-25-dokladbot-batch",
  "task": "Generate 109 SEO articles from articles-batch.csv",
  "createdAt": "2026-01-25T22:00:00Z",
  "status": "in-progress",
  "currentStep": 47,
  "totalSteps": 109,
  "lastCheckpointAt": "2026-01-26T03:18:42Z",
  "retryCount": 2,
  "claudeSessionId": "abc123-resume-token",
  "outputDir": "/home/george/dokladbot/articles/batch-2026-01-25"
}

claudeSessionId je critical - Claude Code má --resume <token> flag, takže Ralph navazuje na konkrétní session, ne začíná novou s prázdným kontextem.

Checkpointing běží po každém dokončeném sub-tasku (article, file, migration step). On-disk JSON sync s O_DSYNC, takže reboot v půlce zápisu nezničí state.

Konkrétní use cases

Use caseTrváníStepsResult
DokladBot batch8h 12min109 articles107 published, 2 manual review
Krtek scraper migration3h 40min28 files refactored0 type errors
Monorepo type migration5h 20min51 files touched1 manual fix
prace content generation6h 5min18 case studies + 12 blog postsshipped

DokladBot batch je nejlepší příklad. Spustil jsem 22:00, šel spát. Ráno 6:30 byl hotový. Přes noc Ralph zvládl 1 rate-limit pause (~12 min) a 2 self-check verifications, kdy Claude tvrdil "done" a Ralph mu řekl "no, you have 62 articles left."

Open-source

Repo: github.com/ondrejknedla/ralph-claude-code. MIT licence, ~2 000 LoC, závislosti: node:^20, claude-code CLI. README má quickstart pro prvních 5 minut, plus konkrétní recepty (batch generation, refaktor across monorepo, scraper migration).

Pull requesty vítány. Top wishlist: Slack/Discord notifikace při end-of-task, multi-agent fan-out (Ralph spawn 5 paralelních claude sessions), web dashboard místo bash logů.

Lessons

  • End-of-task detection je hardest problem. Naivní version (pattern matching) má 35 % false positive rate na real-world úkolech. Self-check round-trip stáhl FP rate pod 3 %.
  • Deterministické checkpointy beat LLM-judged completion. Ralph nikdy "neví", jestli je task celý hotový - má jen heuristiky. Ale currentStep / totalSteps v manifest je deterministický a jediné, co skutečně řídí, jestli loop pokračuje nebo se ukončí.
  • Rate limit headers musíš poslouchat. První verze používala flat 60s sleep po 429. Ten někdy nestačil (token reset až za 4 minuty), jindy zbytečně čekal. Číst retry-after a anthropic-ratelimit-tokens-reset snížilo wasted time na 0.
  • Bash + Node combo. Bash je jen 30 řádků (process spawn, signal handling, log file). Vše s reálnou logikou v TypeScript. Mít dva jazyky v projektu se zdá weird, ale každý dělá co umí - bash pro shell-level lifecycle, TS pro state a heuristiky.
  • Open-source from day 1. Mám Ralph public, protože issues od ostatních jsou nejlepší tester. Někdo mi reportoval bug v rate-limit handleru, který jsem na vlastních batches nechytil (Anthropic zaktualizoval header format a já tu změnu prošvihl).