Files
awesome-copilot/cookbook/copilot-sdk/dotnet/ralph-loop.md
Anthony Shaw d8fc473383 Add RALPH-loop recipe to Copilot SDK cookbook
Add iterative RALPH-loop (Read, Act, Log, Persist, Halt) pattern
implementations for all four supported languages:

- C#/.NET: ralph-loop.cs with documentation
- Node.js/TypeScript: ralph-loop.ts with documentation
- Python: ralph_loop.py with documentation (async API)
- Go: ralph-loop.go with documentation

Each recipe demonstrates:
- Self-referential iteration where AI reviews its own output
- Completion promise detection to halt the loop
- Max iteration safety limits
- File persistence between iterations

Verified against real Copilot SDK APIs:
- Python: fully verified end-to-end with github-copilot-sdk
- Node.js: fully verified end-to-end with @github/copilot-sdk
- C#: compiles and runs successfully with GitHub.Copilot.SDK
- Go: compiles against github.com/github/copilot-sdk/go v0.1.23
2026-02-11 04:59:20 -08:00

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/recipe
dotnet run 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:

  1. Send the initial prompt with clear success criteria
  2. Claude writes code and tests
  3. Claude runs tests and sees failures
  4. Loop automatically re-sends the prompt
  5. Claude reads test output and previous code, fixes issues
  6. 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

  1. Write clear completion criteria: Include exactly what "done" looks like
  2. Use output markers: Include <promise>COMPLETE</promise> or similar in completion condition
  3. Always set max iterations: Prevents infinite loops on impossible tasks
  4. Persist state: Save files so AI can see what changed between iterations
  5. Include context: Feed previous iteration output back as context
  6. 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