mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-20 02:15:12 +00:00
7.8 KiB
7.8 KiB
RALPH-loop: Iterative Self-Referential AI Loops
Implement self-referential feedback loops where an AI agent iteratively improves work by reading its own previous output.
Runnable example: recipe/ralph-loop.cs
cd dotnet dotnet run recipe/ralph-loop.cs
What is RALPH-loop?
RALPH-loop is a development methodology for iterative AI-powered task completion. Named after the Ralph Wiggum technique, it embodies the philosophy of persistent iteration:
- One prompt, multiple iterations: The same prompt is processed repeatedly
- Self-referential feedback: The AI reads its own previous work (file changes, git history)
- Completion detection: Loop exits when a completion promise is detected in output
- Safety limits: Always include a maximum iteration count to prevent infinite loops
Example Scenario
You need to iteratively improve code until all tests pass. Instead of asking Claude to "write perfect code," you use RALPH-loop to:
- Send the initial prompt with clear success criteria
- Claude writes code and tests
- Claude runs tests and sees failures
- Loop automatically re-sends the prompt
- Claude reads test output and previous code, fixes issues
- Repeat until all tests pass and completion promise is output
Basic Implementation
using GitHub.Copilot.SDK;
public class RalphLoop
{
private readonly CopilotClient _client;
private int _iteration = 0;
private readonly int _maxIterations;
private readonly string _completionPromise;
private string? _lastResponse;
public RalphLoop(int maxIterations = 10, string completionPromise = "COMPLETE")
{
_client = new CopilotClient();
_maxIterations = maxIterations;
_completionPromise = completionPromise;
}
public async Task<string> RunAsync(string prompt)
{
await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try
{
while (_iteration < _maxIterations)
{
_iteration++;
Console.WriteLine($"\n--- Iteration {_iteration} ---");
var done = new TaskCompletionSource<string>();
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
{
_lastResponse = msg.Data.Content;
done.SetResult(msg.Data.Content);
}
});
// Send prompt (on first iteration) or continuation
var messagePrompt = _iteration == 1
? prompt
: $"{prompt}\n\nPrevious attempt:\n{_lastResponse}\n\nContinue iterating...";
await session.SendAsync(new MessageOptions { Prompt = messagePrompt });
var response = await done.Task;
// Check for completion promise
if (response.Contains(_completionPromise))
{
Console.WriteLine($"✓ Completion promise detected: {_completionPromise}");
return response;
}
Console.WriteLine($"Iteration {_iteration} complete. Continuing...");
}
throw new InvalidOperationException(
$"Max iterations ({_maxIterations}) reached without completion promise");
}
finally
{
await session.DisposeAsync();
await _client.StopAsync();
}
}
}
// Usage
var loop = new RalphLoop(maxIterations: 5, completionPromise: "DONE");
var result = await loop.RunAsync("Your task here");
Console.WriteLine(result);
With File Persistence
For tasks involving code generation, persist state to files so the AI can see changes:
public class PersistentRalphLoop
{
private readonly string _workDir;
private readonly CopilotClient _client;
private int _iteration = 0;
public PersistentRalphLoop(string workDir, int maxIterations = 10)
{
_workDir = workDir;
Directory.CreateDirectory(_workDir);
_client = new CopilotClient();
}
public async Task<string> RunAsync(string prompt)
{
await _client.StartAsync();
var session = await _client.CreateSessionAsync(new SessionConfig { Model = "gpt-5" });
try
{
// Store initial prompt
var promptFile = Path.Combine(_workDir, "prompt.md");
await File.WriteAllTextAsync(promptFile, prompt);
while (_iteration < 10)
{
_iteration++;
Console.WriteLine($"\n--- Iteration {_iteration} ---");
// Build context including previous work
var contextBuilder = new StringBuilder(prompt);
var previousOutput = Path.Combine(_workDir, $"output-{_iteration - 1}.txt");
if (File.Exists(previousOutput))
{
contextBuilder.AppendLine($"\nPrevious iteration output:\n{await File.ReadAllTextAsync(previousOutput)}");
}
var done = new TaskCompletionSource<string>();
string response = "";
session.On(evt =>
{
if (evt is AssistantMessageEvent msg)
{
response = msg.Data.Content;
done.SetResult(msg.Data.Content);
}
});
await session.SendAsync(new MessageOptions { Prompt = contextBuilder.ToString() });
await done.Task;
// Persist output
await File.WriteAllTextAsync(
Path.Combine(_workDir, $"output-{_iteration}.txt"),
response);
if (response.Contains("COMPLETE"))
{
return response;
}
}
throw new InvalidOperationException("Max iterations reached");
}
finally
{
await session.DisposeAsync();
await _client.StopAsync();
}
}
}
Best Practices
- Write clear completion criteria: Include exactly what "done" looks like
- Use output markers: Include
<promise>COMPLETE</promise>or similar in completion condition - Always set max iterations: Prevents infinite loops on impossible tasks
- Persist state: Save files so AI can see what changed between iterations
- Include context: Feed previous iteration output back as context
- Monitor progress: Log each iteration to track what's happening
Example: Iterative Code Generation
var prompt = @"Write a function that:
1. Parses CSV data
2. Validates required fields
3. Returns parsed records or error
4. Has unit tests
5. Output <promise>COMPLETE</promise> when done";
var loop = new RalphLoop(maxIterations: 10, completionPromise: "COMPLETE");
var result = await loop.RunAsync(prompt);
Handling Failures
try
{
var result = await loop.RunAsync(prompt);
Console.WriteLine("Task completed successfully!");
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Max iterations"))
{
Console.WriteLine("Task did not complete within iteration limit.");
Console.WriteLine($"Last response: {loop.LastResponse}");
// Document what was attempted and suggest alternatives
}
When to Use RALPH-loop
Good for:
- Code generation with automatic verification (tests, linters)
- Tasks with clear success criteria
- Iterative refinement where each attempt learns from previous failures
- Unattended long-running improvements
Not good for:
- Tasks requiring human judgment or design input
- One-shot operations
- Tasks with vague success criteria
- Real-time interactive debugging