Ralph - Autonomní development loop pro Claude Code
Open-source autonomous development loop s inteligentní detekcí ukončení, rate limiting a session managementem.
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 case | Trvání | Steps | Result |
|---|---|---|---|
| DokladBot batch | 8h 12min | 109 articles | 107 published, 2 manual review |
| Krtek scraper migration | 3h 40min | 28 files refactored | 0 type errors |
| Monorepo type migration | 5h 20min | 51 files touched | 1 manual fix |
prace content generation | 6h 5min | 18 case studies + 12 blog posts | shipped |
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 / totalStepsv 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-afteraanthropic-ratelimit-tokens-resetsníž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).