From d8fc473383fe1b52ce26f1a600acd61db2ced013 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 04:59:20 -0800 Subject: [PATCH] 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 --- cookbook/copilot-sdk/README.md | 6 +- cookbook/copilot-sdk/dotnet/ralph-loop.md | 238 +++++++++++++++++ .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 132 ++++++++++ cookbook/copilot-sdk/go/ralph-loop.md | 240 ++++++++++++++++++ cookbook/copilot-sdk/go/recipe/ralph-loop.go | 130 ++++++++++ cookbook/copilot-sdk/nodejs/ralph-loop.md | 209 +++++++++++++++ .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 121 +++++++++ cookbook/copilot-sdk/python/ralph-loop.md | 205 +++++++++++++++ .../copilot-sdk/python/recipe/ralph_loop.py | 123 +++++++++ 9 files changed, 1403 insertions(+), 1 deletion(-) create mode 100644 cookbook/copilot-sdk/dotnet/ralph-loop.md create mode 100644 cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs create mode 100644 cookbook/copilot-sdk/go/ralph-loop.md create mode 100644 cookbook/copilot-sdk/go/recipe/ralph-loop.go create mode 100644 cookbook/copilot-sdk/nodejs/ralph-loop.md create mode 100644 cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts create mode 100644 cookbook/copilot-sdk/python/ralph-loop.md create mode 100644 cookbook/copilot-sdk/python/recipe/ralph_loop.py diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index 53c91a70..c4892687 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -6,6 +6,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### .NET (C#) +- [RALPH-loop](dotnet/ralph-loop.md): Implement iterative self-referential AI loops for task completion with automatic retries. - [Error Handling](dotnet/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](dotnet/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](dotnet/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -14,6 +15,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Node.js / TypeScript +- [RALPH-loop](nodejs/ralph-loop.md): Implement iterative self-referential AI loops for task completion with automatic retries. - [Error Handling](nodejs/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](nodejs/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](nodejs/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -22,6 +24,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Python +- [RALPH-loop](python/ralph-loop.md): Implement iterative self-referential AI loops for task completion with automatic retries. - [Error Handling](python/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](python/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](python/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -30,6 +33,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t ### Go +- [RALPH-loop](go/ralph-loop.md): Implement iterative self-referential AI loops for task completion with automatic retries. - [Error Handling](go/error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. - [Multiple Sessions](go/multiple-sessions.md): Manage multiple independent conversations simultaneously. - [Managing Local Files](go/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. @@ -83,4 +87,4 @@ go run .go ## Status -Cookbook structure is complete with 4 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. +Cookbook structure is complete with 5 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. The RALPH-loop recipe demonstrates iterative self-referential AI loops for autonomous task completion. diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md new file mode 100644 index 00000000..43d22e72 --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -0,0 +1,238 @@ +# 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](recipe/ralph-loop.cs) +> +> ```bash +> 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 + +```csharp +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 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(); + 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: + +```csharp +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 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 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 `COMPLETE` 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 + +```csharp +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 COMPLETE when done"; + +var loop = new RalphLoop(maxIterations: 10, completionPromise: "COMPLETE"); +var result = await loop.RunAsync(prompt); +``` + +## Handling Failures + +```csharp +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 diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs new file mode 100644 index 00000000..a1297656 --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -0,0 +1,132 @@ +#:package GitHub.Copilot.SDK@* +#:property PublishAot=false + +using GitHub.Copilot.SDK; +using System.Text; + +// RALPH-loop: Iterative self-referential AI loops. +// The same prompt is sent repeatedly, with AI reading its own previous output. +// Loop continues until completion promise is detected in the response. + +var prompt = @"You are iteratively building a small library. Follow these phases IN ORDER. +Do NOT skip ahead — only do the current phase, then stop and wait for the next iteration. + +Phase 1: Design a DataValidator class that validates records against a schema. + - Schema defines field names, types (string, int, float, bool), and whether required. + - Return a list of validation errors per record. + - Show the class code only. Do NOT output COMPLETE. + +Phase 2: Write at least 4 unit tests covering: missing required field, wrong type, + valid record, and empty input. Show test code only. Do NOT output COMPLETE. + +Phase 3: Review the code from phases 1 and 2. Fix any bugs, add docstrings, and add + an extra edge-case test. Show the final consolidated code with all fixes. + When this phase is fully done, output the exact text: COMPLETE"; + +var loop = new RalphLoop(maxIterations: 5, completionPromise: "COMPLETE"); + +try +{ + var result = await loop.RunAsync(prompt); + Console.WriteLine("\n=== FINAL RESULT ==="); + Console.WriteLine(result); +} +catch (InvalidOperationException ex) +{ + Console.WriteLine($"\nTask did not complete: {ex.Message}"); + if (loop.LastResponse != null) + { + Console.WriteLine($"\nLast attempt:\n{loop.LastResponse}"); + } +} + +// --- RalphLoop class definition --- + +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 string? LastResponse => _lastResponse; + + public async Task RunAsync(string initialPrompt) + { + await _client.StartAsync(); + var session = await _client.CreateSessionAsync(new SessionConfig + { + Model = "gpt-5.1-codex-mini" + }); + + try + { + while (_iteration < _maxIterations) + { + _iteration++; + Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ==="); + + var done = new TaskCompletionSource(); + session.On(evt => + { + if (evt is AssistantMessageEvent msg) + { + _lastResponse = msg.Data.Content; + done.SetResult(msg.Data.Content); + } + }); + + var currentPrompt = BuildIterationPrompt(initialPrompt); + Console.WriteLine($"Sending prompt (length: {currentPrompt.Length})..."); + + await session.SendAsync(new MessageOptions { Prompt = currentPrompt }); + var response = await done.Task; + + var summary = response.Length > 200 + ? response.Substring(0, 200) + "..." + : response; + Console.WriteLine($"Response: {summary}"); + + if (response.Contains(_completionPromise)) + { + Console.WriteLine($"\n✓ Completion promise detected: '{_completionPromise}'"); + return response; + } + + Console.WriteLine($"Iteration {_iteration} complete. Continuing..."); + } + + throw new InvalidOperationException( + $"Max iterations ({_maxIterations}) reached without completion promise: '{_completionPromise}'"); + } + finally + { + await session.DisposeAsync(); + await _client.StopAsync(); + } + } + + private string BuildIterationPrompt(string initialPrompt) + { + if (_iteration == 1) + return initialPrompt; + + var sb = new StringBuilder(); + sb.AppendLine(initialPrompt); + sb.AppendLine(); + sb.AppendLine("=== CONTEXT FROM PREVIOUS ITERATION ==="); + sb.AppendLine(_lastResponse); + sb.AppendLine("=== END CONTEXT ==="); + sb.AppendLine(); + sb.AppendLine("Continue working on this task. Review the previous attempt and improve upon it."); + return sb.ToString(); + } +} diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md new file mode 100644 index 00000000..37879c0b --- /dev/null +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -0,0 +1,240 @@ +# 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.go](recipe/ralph-loop.go) +> +> ```bash +> cd go/recipe +> go run ralph-loop.go +> ``` + +## 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 + +```go +package main + +import ( + "context" + "fmt" + "log" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +type RalphLoop struct { + client *copilot.Client + iteration int + maxIterations int + completionPromise string + LastResponse string +} + +func NewRalphLoop(maxIterations int, completionPromise string) *RalphLoop { + return &RalphLoop{ + client: copilot.NewClient(nil), + maxIterations: maxIterations, + completionPromise: completionPromise, + } +} + +func (r *RalphLoop) Run(ctx context.Context, initialPrompt string) (string, error) { + if err := r.client.Start(ctx); err != nil { + return "", err + } + defer r.client.Stop() + + session, err := r.client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + }) + if err != nil { + return "", err + } + defer session.Destroy() + + for r.iteration < r.maxIterations { + r.iteration++ + fmt.Printf("\n--- Iteration %d/%d ---\n", r.iteration, r.maxIterations) + + prompt := r.buildIterationPrompt(initialPrompt) + + result, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt}) + if err != nil { + return "", err + } + + if result != nil && result.Data.Content != nil { + r.LastResponse = *result.Data.Content + } + + if strings.Contains(r.LastResponse, r.completionPromise) { + fmt.Printf("✓ Completion promise detected: %s\n", r.completionPromise) + return r.LastResponse, nil + } + } + + return "", fmt.Errorf("max iterations (%d) reached without completion promise", + r.maxIterations) +} + +// Usage +func main() { + ctx := context.Background() + loop := NewRalphLoop(5, "COMPLETE") + result, err := loop.Run(ctx, "Your task here") + if err != nil { + log.Fatal(err) + } + fmt.Println(result) +} +``` + +## With File Persistence + +For tasks involving code generation, persist state to files so the AI can see changes: + +```go +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +type PersistentRalphLoop struct { + client *copilot.Client + workDir string + iteration int + maxIterations int +} + +func NewPersistentRalphLoop(workDir string, maxIterations int) *PersistentRalphLoop { + os.MkdirAll(workDir, 0755) + return &PersistentRalphLoop{ + client: copilot.NewClient(nil), + workDir: workDir, + maxIterations: maxIterations, + } +} + +func (p *PersistentRalphLoop) Run(ctx context.Context, initialPrompt string) (string, error) { + if err := p.client.Start(ctx); err != nil { + return "", err + } + defer p.client.Stop() + + os.WriteFile(filepath.Join(p.workDir, "prompt.md"), []byte(initialPrompt), 0644) + + session, err := p.client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + }) + if err != nil { + return "", err + } + defer session.Destroy() + + for p.iteration < p.maxIterations { + p.iteration++ + + prompt := initialPrompt + prevFile := filepath.Join(p.workDir, fmt.Sprintf("output-%d.txt", p.iteration-1)) + if data, err := os.ReadFile(prevFile); err == nil { + prompt = fmt.Sprintf("%s\n\nPrevious iteration:\n%s", initialPrompt, string(data)) + } + + result, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt}) + if err != nil { + return "", err + } + + response := "" + if result != nil && result.Data.Content != nil { + response = *result.Data.Content + } + + os.WriteFile(filepath.Join(p.workDir, fmt.Sprintf("output-%d.txt", p.iteration)), + []byte(response), 0644) + + if strings.Contains(response, "COMPLETE") { + return response, nil + } + } + + return "", fmt.Errorf("max iterations reached") +} +``` + +## Best Practices + +1. **Write clear completion criteria**: Include exactly what "done" looks like +2. **Use output markers**: Include `COMPLETE` 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 + +```go +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 COMPLETE when done` + +loop := NewRalphLoop(10, "COMPLETE") +result, err := loop.Run(context.Background(), prompt) +``` + +## Handling Failures + +```go +ctx := context.Background() +loop := NewRalphLoop(5, "COMPLETE") +result, err := loop.Run(ctx, prompt) +if err != nil { + log.Printf("Task failed: %v", err) + log.Printf("Last attempt: %s", loop.LastResponse) +} +``` + +## 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 diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go new file mode 100644 index 00000000..b99fe54d --- /dev/null +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +// RalphLoop implements iterative self-referential feedback loops. +// The same prompt is sent repeatedly, with AI reading its own previous output. +// Loop continues until completion promise is detected in the response. +type RalphLoop struct { + client *copilot.Client + iteration int + maxIterations int + completionPromise string + LastResponse string +} + +// NewRalphLoop creates a new RALPH-loop instance. +func NewRalphLoop(maxIterations int, completionPromise string) *RalphLoop { + return &RalphLoop{ + client: copilot.NewClient(nil), + maxIterations: maxIterations, + completionPromise: completionPromise, + } +} + +// Run executes the RALPH-loop until completion promise is detected or max iterations reached. +func (r *RalphLoop) Run(ctx context.Context, initialPrompt string) (string, error) { + if err := r.client.Start(ctx); err != nil { + return "", fmt.Errorf("failed to start client: %w", err) + } + defer r.client.Stop() + + session, err := r.client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "gpt-5.1-codex-mini", + }) + if err != nil { + return "", fmt.Errorf("failed to create session: %w", err) + } + defer session.Destroy() + + for r.iteration < r.maxIterations { + r.iteration++ + fmt.Printf("\n=== Iteration %d/%d ===\n", r.iteration, r.maxIterations) + + currentPrompt := r.buildIterationPrompt(initialPrompt) + fmt.Printf("Sending prompt (length: %d)...\n", len(currentPrompt)) + + result, err := session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: currentPrompt, + }) + if err != nil { + return "", fmt.Errorf("send failed on iteration %d: %w", r.iteration, err) + } + + if result != nil && result.Data.Content != nil { + r.LastResponse = *result.Data.Content + } else { + r.LastResponse = "" + } + + // Display response summary + summary := r.LastResponse + if len(summary) > 200 { + summary = summary[:200] + "..." + } + fmt.Printf("Response: %s\n", summary) + + // Check for completion promise + if strings.Contains(r.LastResponse, r.completionPromise) { + fmt.Printf("\n✓ Success! Completion promise detected: '%s'\n", r.completionPromise) + return r.LastResponse, nil + } + + fmt.Printf("Iteration %d complete. Continuing...\n", r.iteration) + } + + return "", fmt.Errorf("maximum iterations (%d) reached without detecting completion promise: '%s'", + r.maxIterations, r.completionPromise) +} + +func (r *RalphLoop) buildIterationPrompt(initialPrompt string) string { + if r.iteration == 1 { + return initialPrompt + } + + return fmt.Sprintf(`%s + +=== CONTEXT FROM PREVIOUS ITERATION === +%s +=== END CONTEXT === + +Continue working on this task. Review the previous attempt and improve upon it.`, + initialPrompt, r.LastResponse) +} + +func main() { + prompt := `You are iteratively building a small library. Follow these phases IN ORDER. +Do NOT skip ahead — only do the current phase, then stop and wait for the next iteration. + +Phase 1: Design a DataValidator struct that validates records against a schema. + - Schema defines field names, types (string, int, float, bool), and whether required. + - Return a slice of validation errors per record. + - Show the struct and method code only. Do NOT output COMPLETE. + +Phase 2: Write at least 4 unit tests covering: missing required field, wrong type, + valid record, and empty input. Show test code only. Do NOT output COMPLETE. + +Phase 3: Review the code from phases 1 and 2. Fix any bugs, add doc comments, and add + an extra edge-case test. Show the final consolidated code with all fixes. + When this phase is fully done, output the exact text: COMPLETE` + + ctx := context.Background() + loop := NewRalphLoop(5, "COMPLETE") + + result, err := loop.Run(ctx, prompt) + if err != nil { + log.Printf("Task did not complete: %v", err) + log.Printf("Last attempt: %s", loop.LastResponse) + return + } + + fmt.Println("\n=== FINAL RESULT ===") + fmt.Println(result) +} diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md new file mode 100644 index 00000000..1ad70708 --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -0,0 +1,209 @@ +# 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.ts](recipe/ralph-loop.ts) +> +> ```bash +> cd nodejs/recipe +> npm install +> npx tsx ralph-loop.ts +> ``` + +## 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 + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +class RalphLoop { + private client: CopilotClient; + private iteration: number = 0; + private maxIterations: number; + private completionPromise: string; + private lastResponse: string | null = null; + + constructor(maxIterations: number = 10, completionPromise: string = "COMPLETE") { + this.client = new CopilotClient(); + this.maxIterations = maxIterations; + this.completionPromise = completionPromise; + } + + async run(initialPrompt: string): Promise { + await this.client.start(); + const session = await this.client.createSession({ model: "gpt-5" }); + + try { + while (this.iteration < this.maxIterations) { + this.iteration++; + console.log(`\n--- Iteration ${this.iteration}/${this.maxIterations} ---`); + + // Build prompt including previous response as context + const prompt = this.iteration === 1 + ? initialPrompt + : `${initialPrompt}\n\nPrevious attempt:\n${this.lastResponse}\n\nContinue improving...`; + + const response = await session.sendAndWait({ prompt }); + this.lastResponse = response?.data.content || ""; + + console.log(`Response (${this.lastResponse.length} chars)`); + + // Check for completion promise + if (this.lastResponse.includes(this.completionPromise)) { + console.log(`✓ Completion promise detected: ${this.completionPromise}`); + return this.lastResponse; + } + + console.log(`Continuing to iteration ${this.iteration + 1}...`); + } + + throw new Error( + `Max iterations (${this.maxIterations}) reached without completion promise` + ); + } finally { + await session.destroy(); + await this.client.stop(); + } + } +} + +// Usage +const loop = new RalphLoop(5, "COMPLETE"); +const result = await loop.run("Your task here"); +console.log(result); +``` + +## With File Persistence + +For tasks involving code generation, persist state to files so the AI can see changes: + +```typescript +import fs from "fs/promises"; +import path from "path"; +import { CopilotClient } from "@github/copilot-sdk"; + +class PersistentRalphLoop { + private client: CopilotClient; + private workDir: string; + private iteration: number = 0; + private maxIterations: number; + + constructor(workDir: string, maxIterations: number = 10) { + this.client = new CopilotClient(); + this.workDir = workDir; + this.maxIterations = maxIterations; + } + + async run(initialPrompt: string): Promise { + await fs.mkdir(this.workDir, { recursive: true }); + await this.client.start(); + const session = await this.client.createSession({ model: "gpt-5" }); + + try { + // Store initial prompt + await fs.writeFile(path.join(this.workDir, "prompt.md"), initialPrompt); + + while (this.iteration < this.maxIterations) { + this.iteration++; + console.log(`\n--- Iteration ${this.iteration} ---`); + + // Build context from previous outputs + let context = initialPrompt; + const prevOutputFile = path.join(this.workDir, `output-${this.iteration - 1}.txt`); + try { + const prevOutput = await fs.readFile(prevOutputFile, "utf-8"); + context += `\n\nPrevious iteration:\n${prevOutput}`; + } catch { + // No previous output yet + } + + const response = await session.sendAndWait({ prompt: context }); + const output = response?.data.content || ""; + + // Persist output + await fs.writeFile( + path.join(this.workDir, `output-${this.iteration}.txt`), + output + ); + + if (output.includes("COMPLETE")) { + return output; + } + } + + throw new Error("Max iterations reached"); + } finally { + await session.destroy(); + await this.client.stop(); + } + } +} +``` + +## Best Practices + +1. **Write clear completion criteria**: Include exactly what "done" looks like +2. **Use output markers**: Include `COMPLETE` 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 + +```typescript +const 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 COMPLETE when done`; + +const loop = new RalphLoop(10, "COMPLETE"); +const result = await loop.run(prompt); +``` + +## Handling Failures + +```typescript +try { + const result = await loop.run(prompt); + console.log("Task completed successfully!"); +} catch (error) { + console.error("Task failed:", error.message); + // Analyze 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 diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts new file mode 100644 index 00000000..19b16b06 --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -0,0 +1,121 @@ +import { CopilotClient } from "@github/copilot-sdk"; + +/** + * RALPH-loop implementation: Iterative self-referential AI loops. + * The same prompt is sent repeatedly, with AI reading its own previous output. + * Loop continues until completion promise is detected in the response. + */ +class RalphLoop { + private client: CopilotClient; + private iteration: number = 0; + private readonly maxIterations: number; + private readonly completionPromise: string; + public lastResponse: string | null = null; + + constructor(maxIterations: number = 10, completionPromise: string = "COMPLETE") { + this.client = new CopilotClient(); + this.maxIterations = maxIterations; + this.completionPromise = completionPromise; + } + + /** + * Run the RALPH-loop until completion promise is detected or max iterations reached. + */ + async run(initialPrompt: string): Promise { + await this.client.start(); + const session = await this.client.createSession({ + model: "gpt-5.1-codex-mini" + }); + + try { + while (this.iteration < this.maxIterations) { + this.iteration++; + console.log(`\n=== Iteration ${this.iteration}/${this.maxIterations} ===`); + + // Build the prompt for this iteration + const currentPrompt = this.buildIterationPrompt(initialPrompt); + console.log(`Sending prompt (length: ${currentPrompt.length})...`); + + const response = await session.sendAndWait({ prompt: currentPrompt }, 300_000); + this.lastResponse = response?.data.content || ""; + + // Display response summary + const summary = this.lastResponse.length > 200 + ? this.lastResponse.substring(0, 200) + "..." + : this.lastResponse; + console.log(`Response: ${summary}`); + + // Check for completion promise + if (this.lastResponse.includes(this.completionPromise)) { + console.log(`\n✓ Success! Completion promise detected: '${this.completionPromise}'`); + return this.lastResponse; + } + + console.log(`Iteration ${this.iteration} complete. Checking for next iteration...`); + } + + // Max iterations reached without completion + throw new Error( + `Maximum iterations (${this.maxIterations}) reached without detecting completion promise: '${this.completionPromise}'` + ); + } catch (error) { + console.error(`\nError during RALPH-loop: ${error instanceof Error ? error.message : String(error)}`); + throw error; + } finally { + await session.destroy(); + await this.client.stop(); + } + } + + /** + * Build the prompt for the current iteration, including previous output as context. + */ + private buildIterationPrompt(initialPrompt: string): string { + if (this.iteration === 1) { + // First iteration: just the initial prompt + return initialPrompt; + } + + // Subsequent iterations: include previous output as context + return `${initialPrompt} + +=== CONTEXT FROM PREVIOUS ITERATION === +${this.lastResponse} +=== END CONTEXT === + +Continue working on this task. Review the previous attempt and improve upon it.`; + } +} + +// Example usage demonstrating RALPH-loop +async function main() { + const prompt = `You are iteratively building a small library. Follow these phases IN ORDER. +Do NOT skip ahead — only do the current phase, then stop and wait for the next iteration. + +Phase 1: Design a DataValidator class that validates records against a schema. + - Schema defines field names, types (str, int, float, bool), and whether required. + - Return a list of validation errors per record. + - Show the class code only. Do NOT output COMPLETE. + +Phase 2: Write at least 4 unit tests covering: missing required field, wrong type, + valid record, and empty input. Show test code only. Do NOT output COMPLETE. + +Phase 3: Review the code from phases 1 and 2. Fix any bugs, add docstrings, and add + an extra edge-case test. Show the final consolidated code with all fixes. + When this phase is fully done, output the exact text: COMPLETE`; + + const loop = new RalphLoop(5, "COMPLETE"); + + try { + const result = await loop.run(prompt); + console.log("\n=== FINAL RESULT ==="); + console.log(result); + } catch (error) { + console.error(`\nTask did not complete: ${error instanceof Error ? error.message : String(error)}`); + if (loop.lastResponse) { + console.log(`\nLast attempt:\n${loop.lastResponse}`); + } + } +} + +main().catch(console.error); diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md new file mode 100644 index 00000000..5b208200 --- /dev/null +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -0,0 +1,205 @@ +# 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.py](recipe/ralph_loop.py) +> +> ```bash +> cd python/recipe +> pip install -r requirements.txt +> python ralph_loop.py +> ``` + +## 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 + +```python +import asyncio +from copilot import CopilotClient, MessageOptions, SessionConfig + +class RalphLoop: + """Iterative self-referential feedback loop using Copilot.""" + + def __init__(self, max_iterations=10, completion_promise="COMPLETE"): + self.client = CopilotClient() + self.iteration = 0 + self.max_iterations = max_iterations + self.completion_promise = completion_promise + self.last_response = None + + async def run(self, initial_prompt): + """Run the RALPH-loop until completion promise detected or max iterations reached.""" + await self.client.start() + session = await self.client.create_session( + SessionConfig(model="gpt-5.1-codex-mini") + ) + + try: + while self.iteration < self.max_iterations: + self.iteration += 1 + print(f"\n--- Iteration {self.iteration}/{self.max_iterations} ---") + + # Build prompt including previous response as context + if self.iteration == 1: + prompt = initial_prompt + else: + prompt = f"{initial_prompt}\n\nPrevious attempt:\n{self.last_response}\n\nContinue improving..." + + result = await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=300 + ) + + self.last_response = result.data.content if result else "" + print(f"Response ({len(self.last_response)} chars)") + + # Check for completion promise + if self.completion_promise in self.last_response: + print(f"✓ Completion promise detected: {self.completion_promise}") + return self.last_response + + print(f"Continuing to iteration {self.iteration + 1}...") + + raise RuntimeError( + f"Max iterations ({self.max_iterations}) reached without completion promise" + ) + finally: + await session.destroy() + await self.client.stop() + +# Usage +async def main(): + loop = RalphLoop(5, "COMPLETE") + result = await loop.run("Your task here") + print(result) + +asyncio.run(main()) +``` + +## With File Persistence + +For tasks involving code generation, persist state to files so the AI can see changes: + +```python +import asyncio +from pathlib import Path +from copilot import CopilotClient, MessageOptions, SessionConfig + +class PersistentRalphLoop: + """RALPH-loop with file-based state persistence.""" + + def __init__(self, work_dir, max_iterations=10): + self.client = CopilotClient() + self.work_dir = Path(work_dir) + self.work_dir.mkdir(parents=True, exist_ok=True) + self.iteration = 0 + self.max_iterations = max_iterations + + async def run(self, initial_prompt): + """Run the loop with persistent state.""" + await self.client.start() + session = await self.client.create_session( + SessionConfig(model="gpt-5.1-codex-mini") + ) + + try: + # Store initial prompt + (self.work_dir / "prompt.md").write_text(initial_prompt) + + while self.iteration < self.max_iterations: + self.iteration += 1 + print(f"\n--- Iteration {self.iteration} ---") + + # Build context from previous outputs + context = initial_prompt + prev_output = self.work_dir / f"output-{self.iteration - 1}.txt" + if prev_output.exists(): + context += f"\n\nPrevious iteration:\n{prev_output.read_text()}" + + result = await session.send_and_wait( + MessageOptions(prompt=context), timeout=300 + ) + response = result.data.content if result else "" + + # Persist output + output_file = self.work_dir / f"output-{self.iteration}.txt" + output_file.write_text(response) + + if "COMPLETE" in response: + return response + + raise RuntimeError("Max iterations reached") + finally: + await session.destroy() + await self.client.stop() +``` + +## Best Practices + +1. **Write clear completion criteria**: Include exactly what "done" looks like +2. **Use output markers**: Include `COMPLETE` 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 + +```python +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 COMPLETE when done""" + +async def main(): + loop = RalphLoop(10, "COMPLETE") + result = await loop.run(prompt) + +asyncio.run(main()) +``` + +## Handling Failures + +```python +try: + result = await loop.run(prompt) + print("Task completed successfully!") +except RuntimeError as e: + print(f"Task failed: {e}") + if loop.last_response: + print(f"\nLast attempt:\n{loop.last_response}") +``` + +## 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 diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py new file mode 100644 index 00000000..a8c228a2 --- /dev/null +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +import asyncio + +from copilot import CopilotClient, MessageOptions, SessionConfig + + +class RalphLoop: + """ + RALPH-loop implementation: Iterative self-referential AI loops. + + The same prompt is sent repeatedly, with AI reading its own previous output. + Loop continues until completion promise is detected in the response. + """ + + def __init__(self, max_iterations=10, completion_promise="COMPLETE"): + """Initialize RALPH-loop with iteration limits and completion detection.""" + self.client = CopilotClient() + self.iteration = 0 + self.max_iterations = max_iterations + self.completion_promise = completion_promise + self.last_response = None + + async def run(self, initial_prompt): + """ + Run the RALPH-loop until completion promise is detected or max iterations reached. + """ + await self.client.start() + session = await self.client.create_session( + SessionConfig(model="gpt-5.1-codex-mini") + ) + + try: + while self.iteration < self.max_iterations: + self.iteration += 1 + print(f"\n=== Iteration {self.iteration}/{self.max_iterations} ===") + + current_prompt = self._build_iteration_prompt(initial_prompt) + print(f"Sending prompt (length: {len(current_prompt)})...") + + result = await session.send_and_wait( + MessageOptions(prompt=current_prompt), + timeout=300, + ) + + self.last_response = result.data.content if result else "" + + # Display response summary + summary = ( + self.last_response[:200] + "..." + if len(self.last_response) > 200 + else self.last_response + ) + print(f"Response: {summary}") + + # Check for completion promise + if self.completion_promise in self.last_response: + print( + f"\n✓ Success! Completion promise detected: '{self.completion_promise}'" + ) + return self.last_response + + print( + f"Iteration {self.iteration} complete. Checking for next iteration..." + ) + + raise RuntimeError( + f"Maximum iterations ({self.max_iterations}) reached without " + f"detecting completion promise: '{self.completion_promise}'" + ) + + except Exception as e: + print(f"\nError during RALPH-loop: {e}") + raise + finally: + await session.destroy() + await self.client.stop() + + def _build_iteration_prompt(self, initial_prompt): + """Build the prompt for the current iteration, including previous output as context.""" + if self.iteration == 1: + return initial_prompt + + return f"""{initial_prompt} + +=== CONTEXT FROM PREVIOUS ITERATION === +{self.last_response} +=== END CONTEXT === + +Continue working on this task. Review the previous attempt and improve upon it.""" + + +async def main(): + """Example usage demonstrating RALPH-loop.""" + prompt = """You are iteratively building a small library. Follow these phases IN ORDER. +Do NOT skip ahead — only do the current phase, then stop and wait for the next iteration. + +Phase 1: Design a DataValidator class that validates records against a schema. + - Schema defines field names, types (str, int, float, bool), and whether required. + - Return a list of validation errors per record. + - Show the class code only. Do NOT output COMPLETE. + +Phase 2: Write at least 4 unit tests covering: missing required field, wrong type, + valid record, and empty input. Show test code only. Do NOT output COMPLETE. + +Phase 3: Review the code from phases 1 and 2. Fix any bugs, add docstrings, and add + an extra edge-case test. Show the final consolidated code with all fixes. + When this phase is fully done, output the exact text: COMPLETE""" + + loop = RalphLoop(max_iterations=5, completion_promise="COMPLETE") + + try: + result = await loop.run(prompt) + print("\n=== FINAL RESULT ===") + print(result) + except RuntimeError as e: + print(f"\nTask did not complete: {e}") + if loop.last_response: + print(f"\nLast attempt:\n{loop.last_response}") + + +if __name__ == "__main__": + asyncio.run(main())