From d8fc473383fe1b52ce26f1a600acd61db2ced013 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 04:59:20 -0800 Subject: [PATCH 01/13] 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()) From 7e39d55028cc048a20281eeacf83e36bea47a223 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 05:48:44 -0800 Subject: [PATCH 02/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cookbook/copilot-sdk/README.md | 2 +- cookbook/copilot-sdk/dotnet/ralph-loop.md | 4 +- cookbook/copilot-sdk/go/ralph-loop.md | 12 +-- cookbook/copilot-sdk/nodejs/ralph-loop.md | 11 +-- .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 69 +++++++------- cookbook/copilot-sdk/python/ralph-loop.md | 12 +-- .../copilot-sdk/python/recipe/ralph_loop.py | 92 ++++++++++--------- 7 files changed, 105 insertions(+), 97 deletions(-) diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index c4892687..5a20ba0a 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -87,4 +87,4 @@ go run .go ## Status -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. +Cookbook structure is complete with 6 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 index 43d22e72..5580e0f4 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -5,8 +5,8 @@ Implement self-referential feedback loops where an AI agent iteratively improves > **Runnable example:** [recipe/ralph-loop.cs](recipe/ralph-loop.cs) > > ```bash -> cd dotnet/recipe -> dotnet run ralph-loop.cs +> cd dotnet +> dotnet run recipe/ralph-loop.cs > ``` ## What is RALPH-loop? diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index 37879c0b..2469c181 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -5,8 +5,8 @@ Implement self-referential feedback loops where an AI agent iteratively improves > **Runnable example:** [recipe/ralph-loop.go](recipe/ralph-loop.go) > > ```bash -> cd go/recipe -> go run ralph-loop.go +> cd go +> go run recipe/ralph-loop.go > ``` ## What is RALPH-loop? @@ -20,13 +20,13 @@ RALPH-loop is a development methodology for iterative AI-powered task completion ## 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: +You need to iteratively improve code until all tests pass. Instead of asking Copilot 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 +2. Copilot writes code and tests +3. Copilot runs tests and sees failures 4. Loop automatically re-sends the prompt -5. Claude reads test output and previous code, fixes issues +5. Copilot reads test output and previous code, fixes issues 6. Repeat until all tests pass and completion promise is output ## Basic Implementation diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index 1ad70708..bc4d1998 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -5,8 +5,7 @@ Implement self-referential feedback loops where an AI agent iteratively improves > **Runnable example:** [recipe/ralph-loop.ts](recipe/ralph-loop.ts) > > ```bash -> cd nodejs/recipe -> npm install +> cd recipe && npm install > npx tsx ralph-loop.ts > ``` @@ -21,13 +20,13 @@ RALPH-loop is a development methodology for iterative AI-powered task completion ## 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: +You need to iteratively improve code until all tests pass. Instead of asking Copilot 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 +2. Copilot writes code and tests +3. Copilot runs tests and sees failures 4. Loop automatically re-sends the prompt -5. Claude reads test output and previous code, fixes issues +5. Copilot reads test output and previous code, fixes issues 6. Repeat until all tests pass and completion promise is output ## Basic Implementation diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts index 19b16b06..93a7ebb2 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -22,47 +22,54 @@ class RalphLoop { * Run the RALPH-loop until completion promise is detected or max iterations reached. */ async run(initialPrompt: string): Promise { + let session: Awaited> | null = null; + 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} ===`); + session = await this.client.createSession({ + model: "gpt-5.1-codex-mini" + }); - // Build the prompt for this iteration - const currentPrompt = this.buildIterationPrompt(initialPrompt); - console.log(`Sending prompt (length: ${currentPrompt.length})...`); + try { + while (this.iteration < this.maxIterations) { + this.iteration++; + console.log(`\n=== Iteration ${this.iteration}/${this.maxIterations} ===`); - const response = await session.sendAndWait({ prompt: currentPrompt }, 300_000); - this.lastResponse = response?.data.content || ""; + // Build the prompt for this iteration + const currentPrompt = this.buildIterationPrompt(initialPrompt); + console.log(`Sending prompt (length: ${currentPrompt.length})...`); - // Display response summary - const summary = this.lastResponse.length > 200 - ? this.lastResponse.substring(0, 200) + "..." - : this.lastResponse; - console.log(`Response: ${summary}`); + const response = await session.sendAndWait({ prompt: currentPrompt }, 300_000); + this.lastResponse = response?.data.content || ""; - // Check for completion promise - if (this.lastResponse.includes(this.completionPromise)) { - console.log(`\n✓ Success! Completion promise detected: '${this.completionPromise}'`); - return this.lastResponse; + // 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...`); } - 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 { + if (session) { + await session.destroy(); + } } - - // 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(); } } diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index 5b208200..9a969ce6 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -5,11 +5,9 @@ Implement self-referential feedback loops where an AI agent iteratively improves > **Runnable example:** [recipe/ralph_loop.py](recipe/ralph_loop.py) > > ```bash -> cd python/recipe -> pip install -r requirements.txt +> cd 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: @@ -21,13 +19,13 @@ RALPH-loop is a development methodology for iterative AI-powered task completion ## 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: +You need to iteratively improve code until all tests pass. Instead of asking Copilot 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 +2. Copilot writes code and tests +3. Copilot runs tests and sees failures 4. Loop automatically re-sends the prompt -5. Claude reads test output and previous code, fixes issues +5. Copilot reads test output and previous code, fixes issues 6. Repeat until all tests pass and completion promise is output ## Basic Implementation diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index a8c228a2..00ecadcc 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -25,55 +25,59 @@ class RalphLoop: """ Run the RALPH-loop until completion promise is detected or max iterations reached. """ + session = None 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}'" + session = await self.client.create_session( + SessionConfig(model="gpt-5.1-codex-mini") ) - except Exception as e: - print(f"\nError during RALPH-loop: {e}") - raise + 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: + if session is not None: + await session.destroy() finally: - await session.destroy() await self.client.stop() def _build_iteration_prompt(self, initial_prompt): From bb9f63a89920f443481d287b1d1fb04b79a89883 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 05:49:46 -0800 Subject: [PATCH 03/13] Update README to remove RALPH-loop reference Removed mention of the RALPH-loop recipe from the README. --- cookbook/copilot-sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index 5a20ba0a..6e364457 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -87,4 +87,4 @@ go run .go ## Status -Cookbook structure is complete with 6 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. +Cookbook structure is complete with 6 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. From ab82accc085952e64d961b2b37b7936d62437a0a Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 06:35:14 -0800 Subject: [PATCH 04/13] Address review feedback: fix event handler leak, error handling, model alignment - Move session.On handler outside loop to prevent handler accumulation (C#) - Use TrySetResult instead of SetResult to avoid duplicate-set exceptions (C#) - Wrap CreateSessionAsync in broader try/finally so client always stops (C#) - Fix PersistentRalphLoop to use maxIterations parameter instead of hardcoded 10 - Align model name to gpt-5.1-codex-mini across all doc snippets - Fix completion promise DONE -> COMPLETE in usage snippet - Replace Claude references with generic model terminology --- cookbook/copilot-sdk/dotnet/ralph-loop.md | 138 ++++++++++-------- .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 65 +++++---- cookbook/copilot-sdk/nodejs/ralph-loop.md | 4 +- 3 files changed, 119 insertions(+), 88 deletions(-) diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index 5580e0f4..12aa0138 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -20,13 +20,13 @@ RALPH-loop is a development methodology for iterative AI-powered task completion ## 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: +You need to iteratively improve code until all tests pass. Instead of asking the model 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 +2. The model writes code and tests +3. The model runs tests and sees failures 4. Loop automatically re-sends the prompt -5. Claude reads test output and previous code, fixes issues +5. The model reads test output and previous code, fixes issues 6. Repeat until all tests pass and completion promise is output ## Basic Implementation @@ -52,56 +52,66 @@ public class RalphLoop 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 session = await _client.CreateSessionAsync( + new SessionConfig { Model = "gpt-5.1-codex-mini" }); + try + { var done = new TaskCompletionSource(); session.On(evt => { if (evt is AssistantMessageEvent msg) { _lastResponse = msg.Data.Content; - done.SetResult(msg.Data.Content); + done.TrySetResult(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)) + while (_iteration < _maxIterations) { - Console.WriteLine($"✓ Completion promise detected: {_completionPromise}"); - return response; + _iteration++; + Console.WriteLine($"\n--- Iteration {_iteration} ---"); + + done = new TaskCompletionSource(); + + // 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..."); } - Console.WriteLine($"Iteration {_iteration} complete. Continuing..."); + throw new InvalidOperationException( + $"Max iterations ({_maxIterations}) reached without completion promise"); + } + finally + { + await session.DisposeAsync(); } - - 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 loop = new RalphLoop(maxIterations: 5, completionPromise: "COMPLETE"); var result = await loop.RunAsync("Your task here"); Console.WriteLine(result); ``` @@ -115,11 +125,13 @@ public class PersistentRalphLoop { private readonly string _workDir; private readonly CopilotClient _client; + private readonly int _maxIterations; private int _iteration = 0; public PersistentRalphLoop(string workDir, int maxIterations = 10) { _workDir = workDir; + _maxIterations = maxIterations; Directory.CreateDirectory(_workDir); _client = new CopilotClient(); } @@ -127,26 +139,17 @@ public class PersistentRalphLoop 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); + var session = await _client.CreateSessionAsync( + new SessionConfig { Model = "gpt-5.1-codex-mini" }); - while (_iteration < 10) + try { - _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)}"); - } + // Store initial prompt + var promptFile = Path.Combine(_workDir, "prompt.md"); + await File.WriteAllTextAsync(promptFile, prompt); var done = new TaskCompletionSource(); string response = ""; @@ -155,29 +158,48 @@ public class PersistentRalphLoop if (evt is AssistantMessageEvent msg) { response = msg.Data.Content; - done.SetResult(msg.Data.Content); + done.TrySetResult(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")) + while (_iteration < _maxIterations) { - return response; - } - } + _iteration++; + Console.WriteLine($"\n--- Iteration {_iteration} ---"); - throw new InvalidOperationException("Max iterations reached"); + done = new TaskCompletionSource(); + + // 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)}"); + } + + 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(); + } } finally { - await session.DisposeAsync(); await _client.StopAsync(); } } diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index a1297656..0e81b5f8 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -62,54 +62,63 @@ public class RalphLoop 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 session = await _client.CreateSessionAsync(new SessionConfig + { + Model = "gpt-5.1-codex-mini" + }); + try + { var done = new TaskCompletionSource(); session.On(evt => { if (evt is AssistantMessageEvent msg) { _lastResponse = msg.Data.Content; - done.SetResult(msg.Data.Content); + done.TrySetResult(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)) + while (_iteration < _maxIterations) { - Console.WriteLine($"\n✓ Completion promise detected: '{_completionPromise}'"); - return response; + _iteration++; + Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ==="); + + done = new TaskCompletionSource(); + + 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..."); } - Console.WriteLine($"Iteration {_iteration} complete. Continuing..."); + throw new InvalidOperationException( + $"Max iterations ({_maxIterations}) reached without completion promise: '{_completionPromise}'"); + } + finally + { + await session.DisposeAsync(); } - - throw new InvalidOperationException( - $"Max iterations ({_maxIterations}) reached without completion promise: '{_completionPromise}'"); } finally { - await session.DisposeAsync(); await _client.StopAsync(); } } diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index bc4d1998..deafa628 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -49,7 +49,7 @@ class RalphLoop { async run(initialPrompt: string): Promise { await this.client.start(); - const session = await this.client.createSession({ model: "gpt-5" }); + const session = await this.client.createSession({ model: "gpt-5.1-codex-mini" }); try { while (this.iteration < this.maxIterations) { @@ -115,7 +115,7 @@ class PersistentRalphLoop { 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" }); + const session = await this.client.createSession({ model: "gpt-5.1-codex-mini" }); try { // Store initial prompt From 952372c1ec30194b1a365b9cc09346ff5837366f Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 11:28:41 -0800 Subject: [PATCH 05/13] Rewrite Ralph loop recipes: split into simple vs ideal versions Align all 4 language recipes (Node.js, Python, .NET, Go) with the Ralph Playbook architecture: - Simple version: minimal outer loop with fresh session per iteration - Ideal version: planning/building modes, backpressure, git integration - Fresh context isolation instead of in-session context accumulation - Disk-based shared state via IMPLEMENTATION_PLAN.md - Example prompt templates (PROMPT_plan.md, PROMPT_build.md, AGENTS.md) - Updated cookbook README descriptions --- cookbook/copilot-sdk/README.md | 8 +- cookbook/copilot-sdk/dotnet/ralph-loop.md | 396 +++++++++--------- .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 179 +++----- cookbook/copilot-sdk/go/ralph-loop.md | 347 ++++++++------- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 160 +++---- cookbook/copilot-sdk/nodejs/ralph-loop.md | 363 ++++++++-------- .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 169 +++----- cookbook/copilot-sdk/python/ralph-loop.md | 371 ++++++++-------- .../copilot-sdk/python/recipe/ralph_loop.py | 181 +++----- 9 files changed, 1052 insertions(+), 1122 deletions(-) diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index 6e364457..55981302 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -6,7 +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. +- [Ralph Loop](dotnet/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [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. @@ -15,7 +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. +- [Ralph Loop](nodejs/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [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. @@ -24,7 +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. +- [Ralph Loop](python/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [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. @@ -33,7 +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. +- [Ralph Loop](go/ralph-loop.md): Build autonomous AI coding loops with fresh context per iteration, planning/building modes, and backpressure. - [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. diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index 12aa0138..1cc98762 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -1,6 +1,6 @@ -# RALPH-loop: Iterative Self-Referential AI Loops +# Ralph Loop: Autonomous AI Task Loops -Implement self-referential feedback loops where an AI agent iteratively improves work by reading its own previous output. +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. > **Runnable example:** [recipe/ralph-loop.cs](recipe/ralph-loop.cs) > @@ -9,252 +9,250 @@ Implement self-referential feedback loops where an AI agent iteratively improves > dotnet run recipe/ralph-loop.cs > ``` -## What is RALPH-loop? +## What is a 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: +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. -- **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 +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` -## Example Scenario +**Core principles:** -You need to iteratively improve code until all tests pass. Instead of asking the model to "write perfect code," you use RALPH-loop to: +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) -1. Send the initial prompt with clear success criteria -2. The model writes code and tests -3. The model runs tests and sees failures -4. Loop automatically re-sends the prompt -5. The model reads test output and previous code, fixes issues -6. Repeat until all tests pass and completion promise is output +## Simple Version -## Basic Implementation +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: ```csharp using GitHub.Copilot.SDK; -public class RalphLoop +var client = new CopilotClient(); +await client.StartAsync(); + +try { - private readonly CopilotClient _client; - private int _iteration = 0; - private readonly int _maxIterations; - private readonly string _completionPromise; - private string? _lastResponse; + var prompt = await File.ReadAllTextAsync("PROMPT.md"); + var maxIterations = 50; - public RalphLoop(int maxIterations = 10, string completionPromise = "COMPLETE") + for (var i = 1; i <= maxIterations; i++) { - _client = new CopilotClient(); - _maxIterations = maxIterations; - _completionPromise = completionPromise; - } - - public async Task RunAsync(string prompt) - { - await _client.StartAsync(); + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); + // Fresh session each iteration — context isolation is the point + var session = await client.CreateSessionAsync( + new SessionConfig { Model = "claude-sonnet-4.5" }); try { - var session = await _client.CreateSessionAsync( - new SessionConfig { Model = "gpt-5.1-codex-mini" }); - - try + var done = new TaskCompletionSource(); + session.On(evt => { - var done = new TaskCompletionSource(); - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - _lastResponse = msg.Data.Content; - done.TrySetResult(msg.Data.Content); - } - }); + if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); + }); - while (_iteration < _maxIterations) - { - _iteration++; - Console.WriteLine($"\n--- Iteration {_iteration} ---"); - - done = new TaskCompletionSource(); - - // 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 session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; } finally { - await _client.StopAsync(); + await session.DisposeAsync(); } + + Console.WriteLine($"Iteration {i} complete."); } } - -// Usage -var loop = new RalphLoop(maxIterations: 5, completionPromise: "COMPLETE"); -var result = await loop.RunAsync("Your task here"); -Console.WriteLine(result); +finally +{ + await client.StopAsync(); +} ``` -## With File Persistence +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. -For tasks involving code generation, persist state to files so the AI can see changes: +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: ```csharp -public class PersistentRalphLoop +using System.Diagnostics; +using GitHub.Copilot.SDK; + +// Parse args: dotnet run [plan] [max_iterations] +var mode = args.Contains("plan") ? "plan" : "build"; +var maxArg = args.FirstOrDefault(a => int.TryParse(a, out _)); +var maxIterations = maxArg != null ? int.Parse(maxArg) : 50; +var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + +var client = new CopilotClient(); +await client.StartAsync(); + +var branchInfo = new ProcessStartInfo("git", "branch --show-current") + { RedirectStandardOutput = true }; +var branch = Process.Start(branchInfo)!; +var branchName = (await branch.StandardOutput.ReadToEndAsync()).Trim(); +await branch.WaitForExitAsync(); + +Console.WriteLine(new string('━', 40)); +Console.WriteLine($"Mode: {mode}"); +Console.WriteLine($"Prompt: {promptFile}"); +Console.WriteLine($"Branch: {branchName}"); +Console.WriteLine($"Max: {maxIterations} iterations"); +Console.WriteLine(new string('━', 40)); + +try { - private readonly string _workDir; - private readonly CopilotClient _client; - private readonly int _maxIterations; - private int _iteration = 0; + var prompt = await File.ReadAllTextAsync(promptFile); - public PersistentRalphLoop(string workDir, int maxIterations = 10) + for (var i = 1; i <= maxIterations; i++) { - _workDir = workDir; - _maxIterations = maxIterations; - Directory.CreateDirectory(_workDir); - _client = new CopilotClient(); - } - - public async Task RunAsync(string prompt) - { - await _client.StartAsync(); + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); + // Fresh session — each task gets full context budget + var session = await client.CreateSessionAsync( + new SessionConfig { Model = "claude-sonnet-4.5" }); try { - var session = await _client.CreateSessionAsync( - new SessionConfig { Model = "gpt-5.1-codex-mini" }); - - try + var done = new TaskCompletionSource(); + session.On(evt => { - // Store initial prompt - var promptFile = Path.Combine(_workDir, "prompt.md"); - await File.WriteAllTextAsync(promptFile, prompt); + if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); + }); - var done = new TaskCompletionSource(); - string response = ""; - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - response = msg.Data.Content; - done.TrySetResult(msg.Data.Content); - } - }); - - while (_iteration < _maxIterations) - { - _iteration++; - Console.WriteLine($"\n--- Iteration {_iteration} ---"); - - done = new TaskCompletionSource(); - - // 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)}"); - } - - 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 session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; } finally { - await _client.StopAsync(); + await session.DisposeAsync(); } + + // Push changes after each iteration + try + { + Process.Start("git", $"push origin {branchName}")!.WaitForExit(); + } + catch + { + Process.Start("git", $"push -u origin {branchName}")!.WaitForExit(); + } + + Console.WriteLine($"\nIteration {i} complete."); } + + Console.WriteLine($"\nReached max iterations: {maxIterations}"); } +finally +{ + await client.StopAsync(); +} +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +99999. When authoring documentation, capture the why. +999999. Implement completely. No placeholders or stubs. +9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +dotnet build + +## Validation + +- Tests: `dotnet test` +- Build: `dotnet build --no-restore` ``` ## 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 +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it -## 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 +## When to Use a 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 +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness **Not good for:** -- Tasks requiring human judgment or design input -- One-shot operations -- Tasks with vague success criteria -- Real-time interactive debugging +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index 0e81b5f8..c198c727 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -1,141 +1,90 @@ #:package GitHub.Copilot.SDK@* #:property PublishAot=false +using System.Diagnostics; 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. +// Ralph loop: autonomous AI task loop with fresh context per iteration. +// +// Two modes: +// - "plan": reads PROMPT_plan.md, generates/updates IMPLEMENTATION_PLAN.md +// - "build": reads PROMPT_build.md, implements tasks, runs tests, commits +// +// Each iteration creates a fresh session so the agent always operates in +// the "smart zone" of its context window. State is shared between +// iterations via files on disk (IMPLEMENTATION_PLAN.md, AGENTS.md, specs/*). +// +// Usage: +// dotnet run # build mode, 50 iterations +// dotnet run plan # planning mode +// dotnet run 20 # build mode, 20 iterations +// dotnet run plan 5 # planning mode, 5 iterations -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. +var mode = args.Contains("plan") ? "plan" : "build"; +var maxArg = args.FirstOrDefault(a => int.TryParse(a, out _)); +var maxIterations = maxArg != null ? int.Parse(maxArg) : 50; +var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; -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. +var client = new CopilotClient(); +await client.StartAsync(); -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. +var branchProc = Process.Start(new ProcessStartInfo("git", "branch --show-current") + { RedirectStandardOutput = true })!; +var branch = (await branchProc.StandardOutput.ReadToEndAsync()).Trim(); +await branchProc.WaitForExitAsync(); -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"); +Console.WriteLine(new string('━', 40)); +Console.WriteLine($"Mode: {mode}"); +Console.WriteLine($"Prompt: {promptFile}"); +Console.WriteLine($"Branch: {branch}"); +Console.WriteLine($"Max: {maxIterations} iterations"); +Console.WriteLine(new string('━', 40)); 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) + var prompt = await File.ReadAllTextAsync(promptFile); + + for (var i = 1; i <= maxIterations; i++) { - Console.WriteLine($"\nLast attempt:\n{loop.LastResponse}"); - } -} + Console.WriteLine($"\n=== Iteration {i}/{maxIterations} ==="); -// --- 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(); + // Fresh session — each task gets full context budget + var session = await client.CreateSessionAsync( + new SessionConfig { Model = "claude-sonnet-4.5" }); try { - var session = await _client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5.1-codex-mini" + var done = new TaskCompletionSource(); + session.On(evt => + { + if (evt is AssistantMessageEvent msg) + done.TrySetResult(msg.Data.Content); }); - try - { - var done = new TaskCompletionSource(); - session.On(evt => - { - if (evt is AssistantMessageEvent msg) - { - _lastResponse = msg.Data.Content; - done.TrySetResult(msg.Data.Content); - } - }); - - while (_iteration < _maxIterations) - { - _iteration++; - Console.WriteLine($"\n=== Iteration {_iteration}/{_maxIterations} ==="); - - done = new TaskCompletionSource(); - - 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 session.SendAsync(new MessageOptions { Prompt = prompt }); + await done.Task; } finally { - await _client.StopAsync(); + await session.DisposeAsync(); } + + // Push changes after each iteration + try + { + Process.Start("git", $"push origin {branch}")!.WaitForExit(); + } + catch + { + Process.Start("git", $"push -u origin {branch}")!.WaitForExit(); + } + + Console.WriteLine($"\nIteration {i} complete."); } - 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(); - } + Console.WriteLine($"\nReached max iterations: {maxIterations}"); +} +finally +{ + await client.StopAsync(); } diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index 2469c181..44d0d1ca 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -1,6 +1,6 @@ -# RALPH-loop: Iterative Self-Referential AI Loops +# Ralph Loop: Autonomous AI Task Loops -Implement self-referential feedback loops where an AI agent iteratively improves work by reading its own previous output. +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. > **Runnable example:** [recipe/ralph-loop.go](recipe/ralph-loop.go) > @@ -9,27 +9,37 @@ Implement self-referential feedback loops where an AI agent iteratively improves > go run recipe/ralph-loop.go > ``` -## What is RALPH-loop? +## What is a 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: +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. -- **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 +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ +``` -## Example Scenario +**Core principles:** -You need to iteratively improve code until all tests pass. Instead of asking Copilot to "write perfect code," you use RALPH-loop to: +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) -1. Send the initial prompt with clear success criteria -2. Copilot writes code and tests -3. Copilot runs tests and sees failures -4. Loop automatically re-sends the prompt -5. Copilot reads test output and previous code, fixes issues -6. Repeat until all tests pass and completion promise is output +## Simple Version -## Basic Implementation +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: ```go package main @@ -38,81 +48,59 @@ import ( "context" "fmt" "log" - "strings" + "os" 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 ralphLoop(ctx context.Context, promptFile string, maxIterations int) error { + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return err } -} + defer client.Stop() -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", - }) + prompt, err := os.ReadFile(promptFile) if err != nil { - return "", err + return err } - defer session.Destroy() - for r.iteration < r.maxIterations { - r.iteration++ - fmt.Printf("\n--- Iteration %d/%d ---\n", r.iteration, r.maxIterations) + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) - prompt := r.buildIterationPrompt(initialPrompt) - - result, err := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: prompt}) + // Fresh session each iteration — context isolation is the point + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "claude-sonnet-4.5", + }) if err != nil { - return "", err + return err } - if result != nil && result.Data.Content != nil { - r.LastResponse = *result.Data.Content + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + session.Destroy() + if err != nil { + return err } - if strings.Contains(r.LastResponse, r.completionPromise) { - fmt.Printf("✓ Completion promise detected: %s\n", r.completionPromise) - return r.LastResponse, nil - } + fmt.Printf("Iteration %d complete.\n", i) } - - return "", fmt.Errorf("max iterations (%d) reached without completion promise", - r.maxIterations) + return nil } -// Usage func main() { - ctx := context.Background() - loop := NewRalphLoop(5, "COMPLETE") - result, err := loop.Run(ctx, "Your task here") - if err != nil { + if err := ralphLoop(context.Background(), "PROMPT.md", 20); err != nil { log.Fatal(err) } - fmt.Println(result) } ``` -## With File Persistence +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. -For tasks involving code generation, persist state to files so the AI can see changes: +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: ```go package main @@ -120,121 +108,178 @@ package main import ( "context" "fmt" + "log" "os" - "path/filepath" + "os/exec" + "strconv" "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 ralphLoop(ctx context.Context, mode string, maxIterations int) error { + promptFile := "PROMPT_build.md" + if mode == "plan" { + promptFile = "PROMPT_plan.md" } -} -func (p *PersistentRalphLoop) Run(ctx context.Context, initialPrompt string) (string, error) { - if err := p.client.Start(ctx); err != nil { - return "", err + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return err } - defer p.client.Stop() + defer client.Stop() - os.WriteFile(filepath.Join(p.workDir, "prompt.md"), []byte(initialPrompt), 0644) + branchOut, _ := exec.Command("git", "branch", "--show-current").Output() + branch := strings.TrimSpace(string(branchOut)) - session, err := p.client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.1-codex-mini", - }) + fmt.Println(strings.Repeat("━", 40)) + fmt.Printf("Mode: %s\n", mode) + fmt.Printf("Prompt: %s\n", promptFile) + fmt.Printf("Branch: %s\n", branch) + fmt.Printf("Max: %d iterations\n", maxIterations) + fmt.Println(strings.Repeat("━", 40)) + + prompt, err := os.ReadFile(promptFile) if err != nil { - return "", err + return err } - defer session.Destroy() - for p.iteration < p.maxIterations { - p.iteration++ + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) - 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}) + // Fresh session — each task gets full context budget + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "claude-sonnet-4.5", + }) if err != nil { - return "", err + return err } - response := "" - if result != nil && result.Data.Content != nil { - response = *result.Data.Content + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + session.Destroy() + if err != nil { + return err } - os.WriteFile(filepath.Join(p.workDir, fmt.Sprintf("output-%d.txt", p.iteration)), - []byte(response), 0644) + // Push changes after each iteration + if err := exec.Command("git", "push", "origin", branch).Run(); err != nil { + exec.Command("git", "push", "-u", "origin", branch).Run() + } - if strings.Contains(response, "COMPLETE") { - return response, nil + fmt.Printf("\nIteration %d complete.\n", i) + } + + fmt.Printf("\nReached max iterations: %d\n", maxIterations) + return nil +} + +func main() { + mode := "build" + maxIterations := 50 + + for _, arg := range os.Args[1:] { + if arg == "plan" { + mode = "plan" + } else if n, err := strconv.Atoi(arg); err == nil { + maxIterations = n } } - return "", fmt.Errorf("max iterations reached") + if err := ralphLoop(context.Background(), mode, maxIterations); err != nil { + log.Fatal(err) + } } ``` +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +99999. When authoring documentation, capture the why. +999999. Implement completely. No placeholders or stubs. +9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +go build ./... + +## Validation + +- Tests: `go test ./...` +- Vet: `go vet ./...` +``` + ## 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 +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it -## 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 +## When to Use a 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 +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness **Not good for:** -- Tasks requiring human judgment or design input -- One-shot operations -- Tasks with vague success criteria -- Real-time interactive debugging +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index b99fe54d..1d317842 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -4,127 +4,101 @@ import ( "context" "fmt" "log" + "os" + "os/exec" + "strconv" "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 -} +// Ralph loop: autonomous AI task loop with fresh context per iteration. +// +// Two modes: +// - "plan": reads PROMPT_plan.md, generates/updates IMPLEMENTATION_PLAN.md +// - "build": reads PROMPT_build.md, implements tasks, runs tests, commits +// +// Each iteration creates a fresh session so the agent always operates in +// the "smart zone" of its context window. State is shared between +// iterations via files on disk (IMPLEMENTATION_PLAN.md, AGENTS.md, specs/*). +// +// Usage: +// go run ralph-loop.go # build mode, 50 iterations +// go run ralph-loop.go plan # planning mode +// go run ralph-loop.go 20 # build mode, 20 iterations +// go run ralph-loop.go plan 5 # planning mode, 5 iterations -// NewRalphLoop creates a new RALPH-loop instance. -func NewRalphLoop(maxIterations int, completionPromise string) *RalphLoop { - return &RalphLoop{ - client: copilot.NewClient(nil), - maxIterations: maxIterations, - completionPromise: completionPromise, +func ralphLoop(ctx context.Context, mode string, maxIterations int) error { + promptFile := "PROMPT_build.md" + if mode == "plan" { + promptFile = "PROMPT_plan.md" } -} -// 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) + client := copilot.NewClient(nil) + if err := client.Start(ctx); err != nil { + return fmt.Errorf("failed to start client: %w", err) } - defer r.client.Stop() + defer client.Stop() - session, err := r.client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.1-codex-mini", - }) + branchOut, _ := exec.Command("git", "branch", "--show-current").Output() + branch := strings.TrimSpace(string(branchOut)) + + fmt.Println(strings.Repeat("━", 40)) + fmt.Printf("Mode: %s\n", mode) + fmt.Printf("Prompt: %s\n", promptFile) + fmt.Printf("Branch: %s\n", branch) + fmt.Printf("Max: %d iterations\n", maxIterations) + fmt.Println(strings.Repeat("━", 40)) + + prompt, err := os.ReadFile(promptFile) if err != nil { - return "", fmt.Errorf("failed to create session: %w", err) + return fmt.Errorf("failed to read %s: %w", promptFile, err) } - defer session.Destroy() - for r.iteration < r.maxIterations { - r.iteration++ - fmt.Printf("\n=== Iteration %d/%d ===\n", r.iteration, r.maxIterations) + for i := 1; i <= maxIterations; i++ { + fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) - currentPrompt := r.buildIterationPrompt(initialPrompt) - fmt.Printf("Sending prompt (length: %d)...\n", len(currentPrompt)) - - result, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: currentPrompt, + // Fresh session — each task gets full context budget + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "claude-sonnet-4.5", }) if err != nil { - return "", fmt.Errorf("send failed on iteration %d: %w", r.iteration, err) + return fmt.Errorf("failed to create session: %w", err) } - if result != nil && result.Data.Content != nil { - r.LastResponse = *result.Data.Content - } else { - r.LastResponse = "" + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ + Prompt: string(prompt), + }) + session.Destroy() + if err != nil { + return fmt.Errorf("send failed on iteration %d: %w", i, err) } - // 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 + // Push changes after each iteration + if err := exec.Command("git", "push", "origin", branch).Run(); err != nil { + exec.Command("git", "push", "-u", "origin", branch).Run() } - fmt.Printf("Iteration %d complete. Continuing...\n", r.iteration) + fmt.Printf("\nIteration %d complete.\n", i) } - 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) + fmt.Printf("\nReached max iterations: %d\n", maxIterations) + return nil } 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. + mode := "build" + maxIterations := 50 -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 + for _, arg := range os.Args[1:] { + if arg == "plan" { + mode = "plan" + } else if n, err := strconv.Atoi(arg); err == nil { + maxIterations = n + } } - fmt.Println("\n=== FINAL RESULT ===") - fmt.Println(result) + if err := ralphLoop(context.Background(), mode, maxIterations); err != nil { + log.Fatal(err) + } } diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index deafa628..41ad3c71 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -1,6 +1,6 @@ -# RALPH-loop: Iterative Self-Referential AI Loops +# Ralph Loop: Autonomous AI Task Loops -Implement self-referential feedback loops where an AI agent iteratively improves work by reading its own previous output. +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. > **Runnable example:** [recipe/ralph-loop.ts](recipe/ralph-loop.ts) > @@ -9,200 +9,217 @@ Implement self-referential feedback loops where an AI agent iteratively improves > npx tsx ralph-loop.ts > ``` -## What is RALPH-loop? +## What is a 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: +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. -- **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 Copilot to "write perfect code," you use RALPH-loop to: - -1. Send the initial prompt with clear success criteria -2. Copilot writes code and tests -3. Copilot runs tests and sees failures -4. Loop automatically re-sends the prompt -5. Copilot 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.1-codex-mini" }); - - 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); +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ ``` -## With File Persistence +**Core principles:** -For tasks involving code generation, persist state to files so the AI can see changes: +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: ```typescript -import fs from "fs/promises"; -import path from "path"; +import { readFile } from "fs/promises"; import { CopilotClient } from "@github/copilot-sdk"; -class PersistentRalphLoop { - private client: CopilotClient; - private workDir: string; - private iteration: number = 0; - private maxIterations: number; +async function ralphLoop(promptFile: string, maxIterations: number = 50) { + const client = new CopilotClient(); + await client.start(); - constructor(workDir: string, maxIterations: number = 10) { - this.client = new CopilotClient(); - this.workDir = workDir; - this.maxIterations = maxIterations; - } + try { + const prompt = await readFile(promptFile, "utf-8"); - 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.1-codex-mini" }); + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); - 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; - } + // Fresh session each iteration — context isolation is the point + const session = await client.createSession({ model: "claude-sonnet-4.5" }); + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); } - throw new Error("Max iterations reached"); - } finally { - await session.destroy(); - await this.client.stop(); + console.log(`Iteration ${i} complete.`); } + } finally { + await client.stop(); } } + +// Usage: point at your PROMPT.md +ralphLoop("PROMPT.md", 20); +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```typescript +import { readFile } from "fs/promises"; +import { execSync } from "child_process"; +import { CopilotClient } from "@github/copilot-sdk"; + +type Mode = "plan" | "build"; + +async function ralphLoop(mode: Mode, maxIterations: number = 50) { + const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + const client = new CopilotClient(); + await client.start(); + + const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim(); + console.log(`Mode: ${mode} | Prompt: ${promptFile} | Branch: ${branch}`); + + try { + const prompt = await readFile(promptFile, "utf-8"); + + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + + // Fresh session — each task gets full context budget + const session = await client.createSession({ model: "claude-sonnet-4.5" }); + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } + + // Push changes after each iteration + try { + execSync(`git push origin ${branch}`, { stdio: "inherit" }); + } catch { + execSync(`git push -u origin ${branch}`, { stdio: "inherit" }); + } + + console.log(`Iteration ${i} complete.`); + } + } finally { + await client.stop(); + } +} + +// Parse CLI args: npx tsx ralph-loop.ts [plan] [max_iterations] +const args = process.argv.slice(2); +const mode: Mode = args.includes("plan") ? "plan" : "build"; +const maxArg = args.find(a => /^\d+$/.test(a)); +const maxIterations = maxArg ? parseInt(maxArg) : 50; + +ralphLoop(mode, maxIterations); +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +99999. When authoring documentation, capture the why. +999999. Implement completely. No placeholders or stubs. +9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +npm run build + +## Validation + +- Tests: `npm test` +- Typecheck: `npx tsc --noEmit` +- Lint: `npm run lint` ``` ## 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 +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it -## 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 +## When to Use a 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 +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness **Not good for:** -- Tasks requiring human judgment or design input -- One-shot operations -- Tasks with vague success criteria -- Real-time interactive debugging +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts index 93a7ebb2..018b8074 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -1,128 +1,79 @@ +import { readFile } from "fs/promises"; +import { execSync } from "child_process"; 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. + * Ralph loop: autonomous AI task loop with fresh context per iteration. + * + * Two modes: + * - "plan": reads PROMPT_plan.md, generates/updates IMPLEMENTATION_PLAN.md + * - "build": reads PROMPT_build.md, implements tasks, runs tests, commits + * + * Each iteration creates a fresh session so the agent always operates in + * the "smart zone" of its context window. State is shared between + * iterations via files on disk (IMPLEMENTATION_PLAN.md, AGENTS.md, specs/*). + * + * Usage: + * npx tsx ralph-loop.ts # build mode, 50 iterations + * npx tsx ralph-loop.ts plan # planning mode + * npx tsx ralph-loop.ts 20 # build mode, 20 iterations + * npx tsx ralph-loop.ts plan 5 # planning mode, 5 iterations */ -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; - } +type Mode = "plan" | "build"; - /** - * Run the RALPH-loop until completion promise is detected or max iterations reached. - */ - async run(initialPrompt: string): Promise { - let session: Awaited> | null = null; +async function ralphLoop(mode: Mode, maxIterations: number) { + const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; - await this.client.start(); - try { - session = await this.client.createSession({ - model: "gpt-5.1-codex-mini" + const client = new CopilotClient(); + await client.start(); + + const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim(); + + console.log("━".repeat(40)); + console.log(`Mode: ${mode}`); + console.log(`Prompt: ${promptFile}`); + console.log(`Branch: ${branch}`); + console.log(`Max: ${maxIterations} iterations`); + console.log("━".repeat(40)); + + try { + const prompt = await readFile(promptFile, "utf-8"); + + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + + // Fresh session — each task gets full context budget + const session = await client.createSession({ + model: "claude-sonnet-4.5", }); 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; + await session.sendAndWait({ prompt }, 600_000); } finally { - if (session) { - await session.destroy(); - } + await session.destroy(); } - } finally { - 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; + // Push changes after each iteration + try { + execSync(`git push origin ${branch}`, { stdio: "inherit" }); + } catch { + execSync(`git push -u origin ${branch}`, { stdio: "inherit" }); + } + + console.log(`\nIteration ${i} complete.`); } - // 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.`; + console.log(`\nReached max iterations: ${maxIterations}`); + } finally { + await client.stop(); } } -// 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. +// Parse CLI args +const args = process.argv.slice(2); +const mode: Mode = args.includes("plan") ? "plan" : "build"; +const maxArg = args.find((a) => /^\d+$/.test(a)); +const maxIterations = maxArg ? parseInt(maxArg) : 50; -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); +ralphLoop(mode, maxIterations).catch(console.error); diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index 9a969ce6..fb8e3ced 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -1,6 +1,6 @@ -# RALPH-loop: Iterative Self-Referential AI Loops +# Ralph Loop: Autonomous AI Task Loops -Implement self-referential feedback loops where an AI agent iteratively improves work by reading its own previous output. +Build autonomous coding loops where an AI agent picks tasks, implements them, validates against backpressure (tests, builds), commits, and repeats — each iteration in a fresh context window. > **Runnable example:** [recipe/ralph_loop.py](recipe/ralph_loop.py) > @@ -8,196 +8,235 @@ Implement self-referential feedback loops where an AI agent iteratively improves > cd 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: +## What is a Ralph Loop? -- **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 +A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflow where an AI agent iterates through tasks in isolated context windows. The key insight: **state lives on disk, not in the model's context**. Each iteration starts fresh, reads the current state from files, does one task, writes results back to disk, and exits. -## Example Scenario - -You need to iteratively improve code until all tests pass. Instead of asking Copilot to "write perfect code," you use RALPH-loop to: - -1. Send the initial prompt with clear success criteria -2. Copilot writes code and tests -3. Copilot runs tests and sees failures -4. Loop automatically re-sends the prompt -5. Copilot 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()) +``` +┌─────────────────────────────────────────────────┐ +│ loop.sh │ +│ while true: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Fresh session (isolated context) │ │ +│ │ │ │ +│ │ 1. Read PROMPT.md + AGENTS.md │ │ +│ │ 2. Study specs/* and code │ │ +│ │ 3. Pick next task from plan │ │ +│ │ 4. Implement + run tests │ │ +│ │ 5. Update plan, commit, exit │ │ +│ └─────────────────────────────────────────┘ │ +│ ↻ next iteration (fresh context) │ +└─────────────────────────────────────────────────┘ ``` -## With File Persistence +**Core principles:** -For tasks involving code generation, persist state to files so the AI can see changes: +- **Fresh context per iteration**: Each loop creates a new session — no context accumulation, always in the "smart zone" +- **Disk as shared state**: `IMPLEMENTATION_PLAN.md` persists between iterations and acts as the coordination mechanism +- **Backpressure steers quality**: Tests, builds, and lints reject bad work — the agent must fix issues before committing +- **Two modes**: PLANNING (gap analysis → generate plan) and BUILDING (implement from plan) + +## Simple Version + +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: ```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") - ) +async def ralph_loop(prompt_file: str, max_iterations: int = 50): + client = CopilotClient() + await client.start() - try: - # Store initial prompt - (self.work_dir / "prompt.md").write_text(initial_prompt) + try: + prompt = Path(prompt_file).read_text() - while self.iteration < self.max_iterations: - self.iteration += 1 - print(f"\n--- Iteration {self.iteration} ---") + for i in range(1, max_iterations + 1): + print(f"\n=== Iteration {i}/{max_iterations} ===") - # 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 + # Fresh session each iteration — context isolation is the point + session = await client.create_session( + SessionConfig(model="claude-sonnet-4.5") + ) + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 ) - response = result.data.content if result else "" + finally: + await session.destroy() - # Persist output - output_file = self.work_dir / f"output-{self.iteration}.txt" - output_file.write_text(response) + print(f"Iteration {i} complete.") + finally: + await client.stop() - if "COMPLETE" in response: - return response - raise RuntimeError("Max iterations reached") - finally: - await session.destroy() - await self.client.stop() +# Usage: point at your PROMPT.md +asyncio.run(ralph_loop("PROMPT.md", 20)) +``` + +This is all you need to get started. The prompt file tells the agent what to do; the agent reads project files, does work, commits, and exits. The loop restarts with a clean slate. + +## Ideal Version + +The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: + +```python +import asyncio +import subprocess +import sys +from pathlib import Path + +from copilot import CopilotClient, MessageOptions, SessionConfig + + +async def ralph_loop(mode: str = "build", max_iterations: int = 50): + prompt_file = "PROMPT_plan.md" if mode == "plan" else "PROMPT_build.md" + client = CopilotClient() + await client.start() + + branch = subprocess.check_output( + ["git", "branch", "--show-current"], text=True + ).strip() + + print("━" * 40) + print(f"Mode: {mode}") + print(f"Prompt: {prompt_file}") + print(f"Branch: {branch}") + print(f"Max: {max_iterations} iterations") + print("━" * 40) + + try: + prompt = Path(prompt_file).read_text() + + for i in range(1, max_iterations + 1): + print(f"\n=== Iteration {i}/{max_iterations} ===") + + # Fresh session — each task gets full context budget + session = await client.create_session( + SessionConfig(model="claude-sonnet-4.5") + ) + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 + ) + finally: + await session.destroy() + + # Push changes after each iteration + try: + subprocess.run( + ["git", "push", "origin", branch], check=True + ) + except subprocess.CalledProcessError: + subprocess.run( + ["git", "push", "-u", "origin", branch], check=True + ) + + print(f"\nIteration {i} complete.") + + print(f"\nReached max iterations: {max_iterations}") + finally: + await client.stop() + + +if __name__ == "__main__": + args = sys.argv[1:] + mode = "plan" if "plan" in args else "build" + max_iter = next((int(a) for a in args if a.isdigit()), 50) + asyncio.run(ralph_loop(mode, max_iter)) +``` + +### Required Project Files + +The ideal version expects this file structure in your project: + +``` +project-root/ +├── PROMPT_plan.md # Planning mode instructions +├── PROMPT_build.md # Building mode instructions +├── AGENTS.md # Operational guide (build/test commands) +├── IMPLEMENTATION_PLAN.md # Task list (generated by planning mode) +├── specs/ # Requirement specs (one per topic) +│ ├── auth.md +│ └── data-pipeline.md +└── src/ # Your source code +``` + +### Example `PROMPT_plan.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md (if present) to understand the plan so far. +0c. Study `src/` to understand existing code and shared utilities. + +1. Compare specs against code (gap analysis). Create or update + IMPLEMENTATION_PLAN.md as a prioritized bullet-point list of tasks + yet to be implemented. Do NOT implement anything. + +IMPORTANT: Do NOT assume functionality is missing — search the +codebase first to confirm. Prefer updating existing utilities over +creating ad-hoc copies. +``` + +### Example `PROMPT_build.md` + +```markdown +0a. Study `specs/*` to learn the application specifications. +0b. Study IMPLEMENTATION_PLAN.md. +0c. Study `src/` for reference. + +1. Choose the most important item from IMPLEMENTATION_PLAN.md. Before + making changes, search the codebase (don't assume not implemented). +2. After implementing, run the tests. If functionality is missing, add it. +3. When you discover issues, update IMPLEMENTATION_PLAN.md immediately. +4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` + then `git commit` with a descriptive message. + +99999. When authoring documentation, capture the why. +999999. Implement completely. No placeholders or stubs. +9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +``` + +### Example `AGENTS.md` + +Keep this brief (~60 lines). It's loaded every iteration, so bloat wastes context. + +```markdown +## Build & Run + +python -m pytest + +## Validation + +- Tests: `pytest` +- Typecheck: `mypy src/` +- Lint: `ruff check src/` ``` ## 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 +1. **Fresh context per iteration**: Never accumulate context across iterations — that's the whole point +2. **Disk is your database**: `IMPLEMENTATION_PLAN.md` is shared state between isolated sessions +3. **Backpressure is essential**: Tests, builds, lints in `AGENTS.md` — the agent must pass them before committing +4. **Start with PLANNING mode**: Generate the plan first, then switch to BUILDING +5. **Observe and tune**: Watch early iterations, add guardrails to prompts when the agent fails in specific ways +6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan +7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes +8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it -## 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 +## When to Use a 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 +- Implementing features from specs with test-driven validation +- Large refactors broken into many small tasks +- Unattended, long-running development with clear requirements +- Any work where backpressure (tests/builds) can verify correctness **Not good for:** -- Tasks requiring human judgment or design input -- One-shot operations -- Tasks with vague success criteria -- Real-time interactive debugging +- Tasks requiring human judgment mid-loop +- One-shot operations that don't benefit from iteration +- Vague requirements without testable acceptance criteria +- Exploratory prototyping where direction isn't clear diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index 00ecadcc..5e9d9422 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -1,127 +1,84 @@ #!/usr/bin/env python3 +""" +Ralph loop: autonomous AI task loop with fresh context per iteration. + +Two modes: + - "plan": reads PROMPT_plan.md, generates/updates IMPLEMENTATION_PLAN.md + - "build": reads PROMPT_build.md, implements tasks, runs tests, commits + +Each iteration creates a fresh session so the agent always operates in +the "smart zone" of its context window. State is shared between +iterations via files on disk (IMPLEMENTATION_PLAN.md, AGENTS.md, specs/*). + +Usage: + python ralph_loop.py # build mode, 50 iterations + python ralph_loop.py plan # planning mode + python ralph_loop.py 20 # build mode, 20 iterations + python ralph_loop.py plan 5 # planning mode, 5 iterations +""" + import asyncio +import subprocess +import sys +from pathlib import Path from copilot import CopilotClient, MessageOptions, SessionConfig -class RalphLoop: - """ - RALPH-loop implementation: Iterative self-referential AI loops. +async def ralph_loop(mode: str = "build", max_iterations: int = 50): + prompt_file = "PROMPT_plan.md" if mode == "plan" else "PROMPT_build.md" - The same prompt is sent repeatedly, with AI reading its own previous output. - Loop continues until completion promise is detected in the response. - """ + client = CopilotClient() + await client.start() - 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 + branch = subprocess.check_output( + ["git", "branch", "--show-current"], text=True + ).strip() - async def run(self, initial_prompt): - """ - Run the RALPH-loop until completion promise is detected or max iterations reached. - """ - session = None - await self.client.start() - try: - 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: - if session is not None: - await session.destroy() - finally: - 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") + print("━" * 40) + print(f"Mode: {mode}") + print(f"Prompt: {prompt_file}") + print(f"Branch: {branch}") + print(f"Max: {max_iterations} iterations") + print("━" * 40) 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}") + prompt = Path(prompt_file).read_text() + + for i in range(1, max_iterations + 1): + print(f"\n=== Iteration {i}/{max_iterations} ===") + + # Fresh session — each task gets full context budget + session = await client.create_session( + SessionConfig(model="claude-sonnet-4.5") + ) + try: + await session.send_and_wait( + MessageOptions(prompt=prompt), timeout=600 + ) + finally: + await session.destroy() + + # Push changes after each iteration + try: + subprocess.run( + ["git", "push", "origin", branch], check=True + ) + except subprocess.CalledProcessError: + subprocess.run( + ["git", "push", "-u", "origin", branch], check=True + ) + + print(f"\nIteration {i} complete.") + + print(f"\nReached max iterations: {max_iterations}") + finally: + await client.stop() if __name__ == "__main__": - asyncio.run(main()) + args = sys.argv[1:] + mode = "plan" if "plan" in args else "build" + max_iter = next((int(a) for a in args if a.isdigit()), 50) + asyncio.run(ralph_loop(mode, max_iter)) From 92df16da5af80cffab9a8a3240dd77ee941e5b5d Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 11:32:42 -0800 Subject: [PATCH 06/13] Remove git commands from Ralph loop recipes Git operations (commit, push) belong in the prompt instructions, not hardcoded in the loop orchestrator. The SDK recipes should focus purely on the SDK API: create client, create session, send prompt, destroy. --- cookbook/copilot-sdk/dotnet/ralph-loop.md | 18 ------------------ .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 17 ----------------- cookbook/copilot-sdk/go/ralph-loop.md | 11 ----------- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 10 ---------- cookbook/copilot-sdk/nodejs/ralph-loop.md | 11 +---------- .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 11 ----------- cookbook/copilot-sdk/python/ralph-loop.md | 16 ---------------- .../copilot-sdk/python/recipe/ralph_loop.py | 16 ---------------- 8 files changed, 1 insertion(+), 109 deletions(-) diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index 1cc98762..0b2bb570 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -92,7 +92,6 @@ This is all you need to get started. The prompt file tells the agent what to do; The full Ralph pattern with planning and building modes, matching the [Ralph Playbook](https://github.com/ClaytonFarr/ralph-playbook) architecture: ```csharp -using System.Diagnostics; using GitHub.Copilot.SDK; // Parse args: dotnet run [plan] [max_iterations] @@ -104,16 +103,9 @@ var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; var client = new CopilotClient(); await client.StartAsync(); -var branchInfo = new ProcessStartInfo("git", "branch --show-current") - { RedirectStandardOutput = true }; -var branch = Process.Start(branchInfo)!; -var branchName = (await branch.StandardOutput.ReadToEndAsync()).Trim(); -await branch.WaitForExitAsync(); - Console.WriteLine(new string('━', 40)); Console.WriteLine($"Mode: {mode}"); Console.WriteLine($"Prompt: {promptFile}"); -Console.WriteLine($"Branch: {branchName}"); Console.WriteLine($"Max: {maxIterations} iterations"); Console.WriteLine(new string('━', 40)); @@ -145,16 +137,6 @@ try await session.DisposeAsync(); } - // Push changes after each iteration - try - { - Process.Start("git", $"push origin {branchName}")!.WaitForExit(); - } - catch - { - Process.Start("git", $"push -u origin {branchName}")!.WaitForExit(); - } - Console.WriteLine($"\nIteration {i} complete."); } diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index c198c727..920a78f2 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -1,7 +1,6 @@ #:package GitHub.Copilot.SDK@* #:property PublishAot=false -using System.Diagnostics; using GitHub.Copilot.SDK; // Ralph loop: autonomous AI task loop with fresh context per iteration. @@ -28,15 +27,9 @@ var promptFile = mode == "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; var client = new CopilotClient(); await client.StartAsync(); -var branchProc = Process.Start(new ProcessStartInfo("git", "branch --show-current") - { RedirectStandardOutput = true })!; -var branch = (await branchProc.StandardOutput.ReadToEndAsync()).Trim(); -await branchProc.WaitForExitAsync(); - Console.WriteLine(new string('━', 40)); Console.WriteLine($"Mode: {mode}"); Console.WriteLine($"Prompt: {promptFile}"); -Console.WriteLine($"Branch: {branch}"); Console.WriteLine($"Max: {maxIterations} iterations"); Console.WriteLine(new string('━', 40)); @@ -69,16 +62,6 @@ try await session.DisposeAsync(); } - // Push changes after each iteration - try - { - Process.Start("git", $"push origin {branch}")!.WaitForExit(); - } - catch - { - Process.Start("git", $"push -u origin {branch}")!.WaitForExit(); - } - Console.WriteLine($"\nIteration {i} complete."); } diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index 44d0d1ca..cc7807c4 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -110,9 +110,7 @@ import ( "fmt" "log" "os" - "os/exec" "strconv" - "strings" copilot "github.com/github/copilot-sdk/go" ) @@ -129,13 +127,9 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { } defer client.Stop() - branchOut, _ := exec.Command("git", "branch", "--show-current").Output() - branch := strings.TrimSpace(string(branchOut)) - fmt.Println(strings.Repeat("━", 40)) fmt.Printf("Mode: %s\n", mode) fmt.Printf("Prompt: %s\n", promptFile) - fmt.Printf("Branch: %s\n", branch) fmt.Printf("Max: %d iterations\n", maxIterations) fmt.Println(strings.Repeat("━", 40)) @@ -163,11 +157,6 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { return err } - // Push changes after each iteration - if err := exec.Command("git", "push", "origin", branch).Run(); err != nil { - exec.Command("git", "push", "-u", "origin", branch).Run() - } - fmt.Printf("\nIteration %d complete.\n", i) } diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index 1d317842..18de5976 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "os" - "os/exec" "strconv" "strings" @@ -40,13 +39,9 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { } defer client.Stop() - branchOut, _ := exec.Command("git", "branch", "--show-current").Output() - branch := strings.TrimSpace(string(branchOut)) - fmt.Println(strings.Repeat("━", 40)) fmt.Printf("Mode: %s\n", mode) fmt.Printf("Prompt: %s\n", promptFile) - fmt.Printf("Branch: %s\n", branch) fmt.Printf("Max: %d iterations\n", maxIterations) fmt.Println(strings.Repeat("━", 40)) @@ -74,11 +69,6 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { return fmt.Errorf("send failed on iteration %d: %w", i, err) } - // Push changes after each iteration - if err := exec.Command("git", "push", "origin", branch).Run(); err != nil { - exec.Command("git", "push", "-u", "origin", branch).Run() - } - fmt.Printf("\nIteration %d complete.\n", i) } diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index 41ad3c71..13e74fc8 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -82,7 +82,6 @@ The full Ralph pattern with planning and building modes, matching the [Ralph Pla ```typescript import { readFile } from "fs/promises"; -import { execSync } from "child_process"; import { CopilotClient } from "@github/copilot-sdk"; type Mode = "plan" | "build"; @@ -92,8 +91,7 @@ async function ralphLoop(mode: Mode, maxIterations: number = 50) { const client = new CopilotClient(); await client.start(); - const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim(); - console.log(`Mode: ${mode} | Prompt: ${promptFile} | Branch: ${branch}`); + console.log(`Mode: ${mode} | Prompt: ${promptFile}`); try { const prompt = await readFile(promptFile, "utf-8"); @@ -109,13 +107,6 @@ async function ralphLoop(mode: Mode, maxIterations: number = 50) { await session.destroy(); } - // Push changes after each iteration - try { - execSync(`git push origin ${branch}`, { stdio: "inherit" }); - } catch { - execSync(`git push -u origin ${branch}`, { stdio: "inherit" }); - } - console.log(`Iteration ${i} complete.`); } } finally { diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts index 018b8074..3bd5e4cf 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -1,5 +1,4 @@ import { readFile } from "fs/promises"; -import { execSync } from "child_process"; import { CopilotClient } from "@github/copilot-sdk"; /** @@ -28,12 +27,9 @@ async function ralphLoop(mode: Mode, maxIterations: number) { const client = new CopilotClient(); await client.start(); - const branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim(); - console.log("━".repeat(40)); console.log(`Mode: ${mode}`); console.log(`Prompt: ${promptFile}`); - console.log(`Branch: ${branch}`); console.log(`Max: ${maxIterations} iterations`); console.log("━".repeat(40)); @@ -54,13 +50,6 @@ async function ralphLoop(mode: Mode, maxIterations: number) { await session.destroy(); } - // Push changes after each iteration - try { - execSync(`git push origin ${branch}`, { stdio: "inherit" }); - } catch { - execSync(`git push -u origin ${branch}`, { stdio: "inherit" }); - } - console.log(`\nIteration ${i} complete.`); } diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index fb8e3ced..ae666eb2 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -85,7 +85,6 @@ The full Ralph pattern with planning and building modes, matching the [Ralph Pla ```python import asyncio -import subprocess import sys from pathlib import Path @@ -97,14 +96,9 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): client = CopilotClient() await client.start() - branch = subprocess.check_output( - ["git", "branch", "--show-current"], text=True - ).strip() - print("━" * 40) print(f"Mode: {mode}") print(f"Prompt: {prompt_file}") - print(f"Branch: {branch}") print(f"Max: {max_iterations} iterations") print("━" * 40) @@ -125,16 +119,6 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): finally: await session.destroy() - # Push changes after each iteration - try: - subprocess.run( - ["git", "push", "origin", branch], check=True - ) - except subprocess.CalledProcessError: - subprocess.run( - ["git", "push", "-u", "origin", branch], check=True - ) - print(f"\nIteration {i} complete.") print(f"\nReached max iterations: {max_iterations}") diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index 5e9d9422..f6ef1f62 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -19,7 +19,6 @@ Usage: """ import asyncio -import subprocess import sys from pathlib import Path @@ -32,14 +31,9 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): client = CopilotClient() await client.start() - branch = subprocess.check_output( - ["git", "branch", "--show-current"], text=True - ).strip() - print("━" * 40) print(f"Mode: {mode}") print(f"Prompt: {prompt_file}") - print(f"Branch: {branch}") print(f"Max: {max_iterations} iterations") print("━" * 40) @@ -60,16 +54,6 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): finally: await session.destroy() - # Push changes after each iteration - try: - subprocess.run( - ["git", "push", "origin", branch], check=True - ) - except subprocess.CalledProcessError: - subprocess.run( - ["git", "push", "-u", "origin", branch], check=True - ) - print(f"\nIteration {i} complete.") print(f"\nReached max iterations: {max_iterations}") From 1074e34682942ed5e6f46304ee248c9af3e1dd33 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 11:56:11 -0800 Subject: [PATCH 07/13] Add SDK features to all Ralph loop recipes - Add WorkingDirectory/working_directory to pin sessions to project root - Add OnPermissionRequest/on_permission_request for unattended operation - Add tool execution event logging for visibility - Add See Also cross-links to error-handling and persisting-sessions - Add best practices for WorkingDirectory and permission auto-approval - Consistent across all 4 languages (Node.js, Python, .NET, Go) --- cookbook/copilot-sdk/dotnet/ralph-loop.md | 22 +++++++++++++++-- .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 15 ++++++++++-- cookbook/copilot-sdk/go/ralph-loop.md | 24 +++++++++++++++++-- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 16 +++++++++++-- cookbook/copilot-sdk/nodejs/ralph-loop.md | 24 +++++++++++++++++-- .../nodejs/recipe/package-lock.json | 2 +- .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 12 +++++++++- cookbook/copilot-sdk/python/ralph-loop.md | 23 +++++++++++++++--- .../copilot-sdk/python/recipe/ralph_loop.py | 19 ++++++++++++--- 9 files changed, 139 insertions(+), 18 deletions(-) diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index 0b2bb570..e8807c6d 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -119,13 +119,24 @@ try // Fresh session — each task gets full context budget var session = await client.CreateSessionAsync( - new SessionConfig { Model = "claude-sonnet-4.5" }); + new SessionConfig + { + Model = "claude-sonnet-4.5", + // Pin the agent to the project directory + WorkingDirectory = Environment.CurrentDirectory, + // Auto-approve tool calls for unattended operation + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionRequestResult { Kind = "approved" }), + }); try { var done = new TaskCompletionSource(); session.On(evt => { - if (evt is AssistantMessageEvent msg) + // Log tool usage for visibility + if (evt is ToolExecutionStartEvent toolStart) + Console.WriteLine($" ⚙ {toolStart.Data.ToolName}"); + else if (evt is AssistantMessageEvent msg) done.TrySetResult(msg.Data.Content); }); @@ -224,6 +235,8 @@ dotnet build 6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan 7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes 8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop ## When to Use a Ralph Loop @@ -238,3 +251,8 @@ dotnet build - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria - Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index 920a78f2..ba6b2d51 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -43,14 +43,25 @@ try // Fresh session — each task gets full context budget var session = await client.CreateSessionAsync( - new SessionConfig { Model = "claude-sonnet-4.5" }); + new SessionConfig + { + Model = "claude-sonnet-4.5", + // Pin the agent to the project directory + WorkingDirectory = Environment.CurrentDirectory, + // Auto-approve tool calls for unattended operation + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionRequestResult { Kind = "approved" }), + }); try { var done = new TaskCompletionSource(); session.On(evt => { - if (evt is AssistantMessageEvent msg) + // Log tool usage for visibility + if (evt is ToolExecutionStartEvent toolStart) + Console.WriteLine($" ⚙ {toolStart.Data.ToolName}"); + else if (evt is AssistantMessageEvent msg) done.TrySetResult(msg.Data.Content); }); diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index cc7807c4..f7a8c27c 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -111,6 +111,7 @@ import ( "log" "os" "strconv" + "strings" copilot "github.com/github/copilot-sdk/go" ) @@ -127,6 +128,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { } defer client.Stop() + cwd, _ := os.Getwd() + fmt.Println(strings.Repeat("━", 40)) fmt.Printf("Mode: %s\n", mode) fmt.Printf("Prompt: %s\n", promptFile) @@ -141,14 +144,24 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { for i := 1; i <= maxIterations; i++ { fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) - // Fresh session — each task gets full context budget session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-4.5", + WorkingDirectory: cwd, + OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { + return copilot.PermissionRequestResult{Kind: "approved"} + }, }) if err != nil { return err } + // Log tool usage for visibility + session.On(func(event copilot.Event) { + if te, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", te.Data.ToolName) + } + }) + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: string(prompt), }) @@ -258,6 +271,8 @@ go build ./... 6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan 7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes 8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `WorkingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `OnPermissionRequest` to allow tool calls without interrupting the loop ## When to Use a Ralph Loop @@ -272,3 +287,8 @@ go build ./... - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria - Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index 18de5976..30513927 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -39,6 +39,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { } defer client.Stop() + cwd, _ := os.Getwd() + fmt.Println(strings.Repeat("━", 40)) fmt.Printf("Mode: %s\n", mode) fmt.Printf("Prompt: %s\n", promptFile) @@ -53,14 +55,24 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { for i := 1; i <= maxIterations; i++ { fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) - // Fresh session — each task gets full context budget session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "claude-sonnet-4.5", + WorkingDirectory: cwd, + OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { + return copilot.PermissionRequestResult{Kind: "approved"} + }, }) if err != nil { return fmt.Errorf("failed to create session: %w", err) } + // Log tool usage for visibility + session.On(func(event copilot.Event) { + if te, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", te.Data.ToolName) + } + }) + _, err = session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: string(prompt), }) diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index 13e74fc8..5153790a 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -99,8 +99,21 @@ async function ralphLoop(mode: Mode, maxIterations: number = 50) { for (let i = 1; i <= maxIterations; i++) { console.log(`\n=== Iteration ${i}/${maxIterations} ===`); - // Fresh session — each task gets full context budget - const session = await client.createSession({ model: "claude-sonnet-4.5" }); + const session = await client.createSession({ + model: "claude-sonnet-4.5", + // Pin the agent to the project directory + workingDirectory: process.cwd(), + // Auto-approve tool calls for unattended operation + onPermissionRequest: async () => ({ allow: true }), + }); + + // Log tool usage for visibility + session.on((event) => { + if (event.type === "tool.execution_start") { + console.log(` ⚙ ${event.data.toolName}`); + } + }); + try { await session.sendAndWait({ prompt }, 600_000); } finally { @@ -200,6 +213,8 @@ npm run build 6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan 7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes 8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `workingDirectory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `onPermissionRequest` to allow tool calls without interrupting the loop ## When to Use a Ralph Loop @@ -214,3 +229,8 @@ npm run build - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria - Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json index a5a8fea5..47e85e5a 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json +++ b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json @@ -8,7 +8,7 @@ "name": "copilot-sdk-cookbook-recipes", "version": "1.0.0", "dependencies": { - "@github/copilot-sdk": "file:../../src" + "@github/copilot-sdk": "*" }, "devDependencies": { "@types/node": "^22.19.7", diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts index 3bd5e4cf..f22e2bee 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -39,9 +39,19 @@ async function ralphLoop(mode: Mode, maxIterations: number) { for (let i = 1; i <= maxIterations; i++) { console.log(`\n=== Iteration ${i}/${maxIterations} ===`); - // Fresh session — each task gets full context budget const session = await client.createSession({ model: "claude-sonnet-4.5", + // Pin the agent to the project directory + workingDirectory: process.cwd(), + // Auto-approve tool calls for unattended operation + onPermissionRequest: async () => ({ allow: true }), + }); + + // Log tool usage for visibility + session.on((event) => { + if (event.type === "tool.execution_start") { + console.log(` ⚙ ${event.data.toolName}`); + } }); try { diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index ae666eb2..55790c27 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -108,10 +108,20 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): for i in range(1, max_iterations + 1): print(f"\n=== Iteration {i}/{max_iterations} ===") - # Fresh session — each task gets full context budget - session = await client.create_session( - SessionConfig(model="claude-sonnet-4.5") + session = await client.create_session(SessionConfig( + model="claude-sonnet-4.5", + # Pin the agent to the project directory + working_directory=str(Path.cwd()), + # Auto-approve tool calls for unattended operation + on_permission_request=lambda _req, _ctx: {"kind": "approved", "rules": []}, + )) + + # Log tool usage for visibility + session.on(lambda event: + print(f" ⚙ {event.data.tool_name}") + if event.type.value == "tool.execution_start" else None ) + try: await session.send_and_wait( MessageOptions(prompt=prompt), timeout=600 @@ -210,6 +220,8 @@ python -m pytest 6. **The plan is disposable**: If the agent goes off track, delete `IMPLEMENTATION_PLAN.md` and re-plan 7. **Keep `AGENTS.md` brief**: It's loaded every iteration — operational info only, no progress notes 8. **Use a sandbox**: The agent runs autonomously with full tool access — isolate it +9. **Set `working_directory`**: Pin the session to your project root so tool operations resolve paths correctly +10. **Auto-approve permissions**: Use `on_permission_request` to allow tool calls without interrupting the loop ## When to Use a Ralph Loop @@ -224,3 +236,8 @@ python -m pytest - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria - Exploratory prototyping where direction isn't clear + +## See Also + +- [Error Handling](error-handling.md) — timeout patterns and graceful shutdown for long-running sessions +- [Persisting Sessions](persisting-sessions.md) — save and resume sessions across restarts diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index f6ef1f62..823981bf 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -43,10 +43,23 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): for i in range(1, max_iterations + 1): print(f"\n=== Iteration {i}/{max_iterations} ===") - # Fresh session — each task gets full context budget - session = await client.create_session( - SessionConfig(model="claude-sonnet-4.5") + session = await client.create_session(SessionConfig( + model="claude-sonnet-4.5", + # Pin the agent to the project directory + working_directory=str(Path.cwd()), + # Auto-approve tool calls for unattended operation + on_permission_request=lambda _req, _ctx: { + "kind": "approved", + "rules": [], + }, + )) + + # Log tool usage for visibility + session.on(lambda event: + print(f" ⚙ {event.data.tool_name}") + if event.type.value == "tool.execution_start" else None ) + try: await session.send_and_wait( MessageOptions(prompt=prompt), timeout=600 From 0e616701a5f8050c3ed9abca2aefaebfad84a998 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 12:50:30 -0800 Subject: [PATCH 08/13] Remove package-lock.json from tracking --- .../nodejs/recipe/package-lock.json | 629 ------------------ 1 file changed, 629 deletions(-) delete mode 100644 cookbook/copilot-sdk/nodejs/recipe/package-lock.json diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json deleted file mode 100644 index 47e85e5a..00000000 --- a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json +++ /dev/null @@ -1,629 +0,0 @@ -{ - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "dependencies": { - "@github/copilot-sdk": "*" - }, - "devDependencies": { - "@types/node": "^22.19.7", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } - }, - "../..": { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@github/copilot": "^0.0.388-1", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.5" - }, - "devDependencies": { - "@types/node": "^22.19.6", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "esbuild": "^0.27.0", - "eslint": "^9.0.0", - "glob": "^11.0.0", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.4.0", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.16" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "../../..": {}, - "../../src": {}, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@github/copilot-sdk": { - "resolved": "../../src", - "link": true - }, - "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - } - } -} From 3eb7efc9907a4a66b35536ed32c26d65bc8ffbd6 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 13:31:05 -0800 Subject: [PATCH 09/13] Use gpt-5.1-codex-mini as default model in Ralph loop recipes --- cookbook/copilot-sdk/dotnet/ralph-loop.md | 4 +- .../copilot-sdk/dotnet/recipe/ralph-loop.cs | 2 +- cookbook/copilot-sdk/go/ralph-loop.md | 4 +- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 2 +- cookbook/copilot-sdk/nodejs/ralph-loop.md | 4 +- .../nodejs/recipe/package-lock.json | 629 ++++++++++++++++++ .../copilot-sdk/nodejs/recipe/ralph-loop.ts | 2 +- cookbook/copilot-sdk/python/ralph-loop.md | 4 +- .../copilot-sdk/python/recipe/ralph_loop.py | 2 +- 9 files changed, 641 insertions(+), 12 deletions(-) create mode 100644 cookbook/copilot-sdk/nodejs/recipe/package-lock.json diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index e8807c6d..eccd2c29 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -58,7 +58,7 @@ try // Fresh session each iteration — context isolation is the point var session = await client.CreateSessionAsync( - new SessionConfig { Model = "claude-sonnet-4.5" }); + new SessionConfig { Model = "gpt-5.1-codex-mini" }); try { var done = new TaskCompletionSource(); @@ -121,7 +121,7 @@ try var session = await client.CreateSessionAsync( new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "gpt-5.1-codex-mini", // Pin the agent to the project directory WorkingDirectory = Environment.CurrentDirectory, // Auto-approve tool calls for unattended operation diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index ba6b2d51..d0baa2e7 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -45,7 +45,7 @@ try var session = await client.CreateSessionAsync( new SessionConfig { - Model = "claude-sonnet-4.5", + Model = "gpt-5.1-codex-mini", // Pin the agent to the project directory WorkingDirectory = Environment.CurrentDirectory, // Auto-approve tool calls for unattended operation diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index f7a8c27c..4d3bf373 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -70,7 +70,7 @@ func ralphLoop(ctx context.Context, promptFile string, maxIterations int) error // Fresh session each iteration — context isolation is the point session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "gpt-5.1-codex-mini", }) if err != nil { return err @@ -145,7 +145,7 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "gpt-5.1-codex-mini", WorkingDirectory: cwd, OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { return copilot.PermissionRequestResult{Kind: "approved"} diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index 30513927..72685927 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -56,7 +56,7 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { fmt.Printf("\n=== Iteration %d/%d ===\n", i, maxIterations) session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-sonnet-4.5", + Model: "gpt-5.1-codex-mini", WorkingDirectory: cwd, OnPermissionRequest: func(_ copilot.PermissionRequest, _ map[string]string) copilot.PermissionRequestResult { return copilot.PermissionRequestResult{Kind: "approved"} diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index 5153790a..c4074a1b 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -56,7 +56,7 @@ async function ralphLoop(promptFile: string, maxIterations: number = 50) { console.log(`\n=== Iteration ${i}/${maxIterations} ===`); // Fresh session each iteration — context isolation is the point - const session = await client.createSession({ model: "claude-sonnet-4.5" }); + const session = await client.createSession({ model: "gpt-5.1-codex-mini" }); try { await session.sendAndWait({ prompt }, 600_000); } finally { @@ -100,7 +100,7 @@ async function ralphLoop(mode: Mode, maxIterations: number = 50) { console.log(`\n=== Iteration ${i}/${maxIterations} ===`); const session = await client.createSession({ - model: "claude-sonnet-4.5", + model: "gpt-5.1-codex-mini", // Pin the agent to the project directory workingDirectory: process.cwd(), // Auto-approve tool calls for unattended operation diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json new file mode 100644 index 00000000..47e85e5a --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json @@ -0,0 +1,629 @@ +{ + "name": "copilot-sdk-cookbook-recipes", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "copilot-sdk-cookbook-recipes", + "version": "1.0.0", + "dependencies": { + "@github/copilot-sdk": "*" + }, + "devDependencies": { + "@types/node": "^22.19.7", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } + }, + "../..": { + "name": "@github/copilot-sdk", + "version": "0.1.8", + "license": "MIT", + "dependencies": { + "@github/copilot": "^0.0.388-1", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.5" + }, + "devDependencies": { + "@types/node": "^22.19.6", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "esbuild": "^0.27.0", + "eslint": "^9.0.0", + "glob": "^11.0.0", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.4.0", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.16" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../../..": {}, + "../../src": {}, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@github/copilot-sdk": { + "resolved": "../../src", + "link": true + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts index f22e2bee..fb0fbe45 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts +++ b/cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts @@ -40,7 +40,7 @@ async function ralphLoop(mode: Mode, maxIterations: number) { console.log(`\n=== Iteration ${i}/${maxIterations} ===`); const session = await client.createSession({ - model: "claude-sonnet-4.5", + model: "gpt-5.1-codex-mini", // Pin the agent to the project directory workingDirectory: process.cwd(), // Auto-approve tool calls for unattended operation diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index 55790c27..845354bd 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -59,7 +59,7 @@ async def ralph_loop(prompt_file: str, max_iterations: int = 50): # Fresh session each iteration — context isolation is the point session = await client.create_session( - SessionConfig(model="claude-sonnet-4.5") + SessionConfig(model="gpt-5.1-codex-mini") ) try: await session.send_and_wait( @@ -109,7 +109,7 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): print(f"\n=== Iteration {i}/{max_iterations} ===") session = await client.create_session(SessionConfig( - model="claude-sonnet-4.5", + model="gpt-5.1-codex-mini", # Pin the agent to the project directory working_directory=str(Path.cwd()), # Auto-approve tool calls for unattended operation diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index 823981bf..c182e3c7 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -44,7 +44,7 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): print(f"\n=== Iteration {i}/{max_iterations} ===") session = await client.create_session(SessionConfig( - model="claude-sonnet-4.5", + model="gpt-5.1-codex-mini", # Pin the agent to the project directory working_directory=str(Path.cwd()), # Auto-approve tool calls for unattended operation From 3b4d601ba7437eb1f5510fd2fc8556454c753e26 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 13:31:15 -0800 Subject: [PATCH 10/13] Remove package-lock.json from tracking --- .../nodejs/recipe/package-lock.json | 629 ------------------ 1 file changed, 629 deletions(-) delete mode 100644 cookbook/copilot-sdk/nodejs/recipe/package-lock.json diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json deleted file mode 100644 index 47e85e5a..00000000 --- a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json +++ /dev/null @@ -1,629 +0,0 @@ -{ - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "copilot-sdk-cookbook-recipes", - "version": "1.0.0", - "dependencies": { - "@github/copilot-sdk": "*" - }, - "devDependencies": { - "@types/node": "^22.19.7", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } - }, - "../..": { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@github/copilot": "^0.0.388-1", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.5" - }, - "devDependencies": { - "@types/node": "^22.19.6", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "esbuild": "^0.27.0", - "eslint": "^9.0.0", - "glob": "^11.0.0", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.4.0", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.16" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "../../..": {}, - "../../src": {}, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@github/copilot-sdk": { - "resolved": "../../src", - "link": true - }, - "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - } - } -} From 84486c2e46912b86c304b5c45d01df9d29120320 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 13:33:29 -0800 Subject: [PATCH 11/13] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 9 +++++++-- cookbook/copilot-sdk/nodejs/ralph-loop.md | 4 ++-- cookbook/copilot-sdk/python/ralph-loop.md | 8 ++++++-- cookbook/copilot-sdk/python/recipe/ralph_loop.py | 8 ++++---- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index 72685927..35d862b7 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -39,7 +39,10 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { } defer client.Stop() - cwd, _ := os.Getwd() + cwd, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get working directory: %w", err) + } fmt.Println(strings.Repeat("━", 40)) fmt.Printf("Mode: %s\n", mode) @@ -76,7 +79,9 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { _, err = session.SendAndWait(ctx, copilot.MessageOptions{ Prompt: string(prompt), }) - session.Destroy() + if destroyErr := session.Destroy(); destroyErr != nil { + log.Printf("failed to destroy session on iteration %d: %v", i, destroyErr) + } if err != nil { return fmt.Errorf("send failed on iteration %d: %w", i, err) } diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index c4074a1b..1abb692c 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -5,8 +5,8 @@ Build autonomous coding loops where an AI agent picks tasks, implements them, va > **Runnable example:** [recipe/ralph-loop.ts](recipe/ralph-loop.ts) > > ```bash -> cd recipe && npm install -> npx tsx ralph-loop.ts +> npm install +> npx tsx recipe/ralph-loop.ts > ``` ## What is a Ralph Loop? diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index 845354bd..82bfc7f5 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -4,10 +4,14 @@ Build autonomous coding loops where an AI agent picks tasks, implements them, va > **Runnable example:** [recipe/ralph_loop.py](recipe/ralph_loop.py) > +> From the repository root, install dependencies and run: +> > ```bash -> cd recipe && pip install -r requirements.txt -> python ralph_loop.py +> pip install -r cookbook/copilot-sdk/python/recipe/requirements.txt +> python cookbook/copilot-sdk/python/recipe/ralph_loop.py > ``` +> +> Make sure `PROMPT_build.md` and `PROMPT_plan.md` exist in your current working directory before running the loop. ## What is a Ralph Loop? diff --git a/cookbook/copilot-sdk/python/recipe/ralph_loop.py b/cookbook/copilot-sdk/python/recipe/ralph_loop.py index c182e3c7..918e8c66 100644 --- a/cookbook/copilot-sdk/python/recipe/ralph_loop.py +++ b/cookbook/copilot-sdk/python/recipe/ralph_loop.py @@ -55,11 +55,11 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): )) # Log tool usage for visibility - session.on(lambda event: - print(f" ⚙ {event.data.tool_name}") - if event.type.value == "tool.execution_start" else None - ) + def log_tool_event(event): + if event.type.value == "tool.execution_start": + print(f" ⚙ {event.data.tool_name}") + session.on(log_tool_event) try: await session.send_and_wait( MessageOptions(prompt=prompt), timeout=600 From ff69b804ac4d8a2ceb11b28eb28e82562a2d0e4d Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 14:23:25 -0800 Subject: [PATCH 12/13] renaming --- cookbook/copilot-sdk/dotnet/ralph-loop.md | 10 +- cookbook/copilot-sdk/go/ralph-loop.md | 10 +- cookbook/copilot-sdk/nodejs/ralph-loop.md | 108 +-- .../nodejs/recipe/package-lock.json | 629 ++++++++++++++++++ cookbook/copilot-sdk/python/ralph-loop.md | 23 +- 5 files changed, 710 insertions(+), 70 deletions(-) create mode 100644 cookbook/copilot-sdk/nodejs/recipe/package-lock.json diff --git a/cookbook/copilot-sdk/dotnet/ralph-loop.md b/cookbook/copilot-sdk/dotnet/ralph-loop.md index eccd2c29..8ff85246 100644 --- a/cookbook/copilot-sdk/dotnet/ralph-loop.md +++ b/cookbook/copilot-sdk/dotnet/ralph-loop.md @@ -39,7 +39,7 @@ A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflo ## Simple Version -The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: ```csharp using GitHub.Copilot.SDK; @@ -205,9 +205,9 @@ creating ad-hoc copies. 4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` then `git commit` with a descriptive message. -99999. When authoring documentation, capture the why. -999999. Implement completely. No placeholders or stubs. -9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. ``` ### Example `AGENTS.md` @@ -241,12 +241,14 @@ dotnet build ## When to Use a Ralph Loop **Good for:** + - Implementing features from specs with test-driven validation - Large refactors broken into many small tasks - Unattended, long-running development with clear requirements - Any work where backpressure (tests/builds) can verify correctness **Not good for:** + - Tasks requiring human judgment mid-loop - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index 4d3bf373..626ed5ea 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -39,7 +39,7 @@ A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflo ## Simple Version -The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: ```go package main @@ -241,9 +241,9 @@ creating ad-hoc copies. 4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` then `git commit` with a descriptive message. -99999. When authoring documentation, capture the why. -999999. Implement completely. No placeholders or stubs. -9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. ``` ### Example `AGENTS.md` @@ -277,12 +277,14 @@ go build ./... ## When to Use a Ralph Loop **Good for:** + - Implementing features from specs with test-driven validation - Large refactors broken into many small tasks - Unattended, long-running development with clear requirements - Any work where backpressure (tests/builds) can verify correctness **Not good for:** + - Tasks requiring human judgment mid-loop - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria diff --git a/cookbook/copilot-sdk/nodejs/ralph-loop.md b/cookbook/copilot-sdk/nodejs/ralph-loop.md index 1abb692c..87c5225f 100644 --- a/cookbook/copilot-sdk/nodejs/ralph-loop.md +++ b/cookbook/copilot-sdk/nodejs/ralph-loop.md @@ -39,35 +39,35 @@ A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflo ## Simple Version -The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: ```typescript import { readFile } from "fs/promises"; import { CopilotClient } from "@github/copilot-sdk"; async function ralphLoop(promptFile: string, maxIterations: number = 50) { - const client = new CopilotClient(); - await client.start(); + const client = new CopilotClient(); + await client.start(); - try { - const prompt = await readFile(promptFile, "utf-8"); + try { + const prompt = await readFile(promptFile, "utf-8"); - for (let i = 1; i <= maxIterations; i++) { - console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); - // Fresh session each iteration — context isolation is the point - const session = await client.createSession({ model: "gpt-5.1-codex-mini" }); - try { - await session.sendAndWait({ prompt }, 600_000); - } finally { - await session.destroy(); - } + // Fresh session each iteration — context isolation is the point + const session = await client.createSession({ model: "gpt-5.1-codex-mini" }); + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } - console.log(`Iteration ${i} complete.`); - } - } finally { - await client.stop(); + console.log(`Iteration ${i} complete.`); } + } finally { + await client.stop(); + } } // Usage: point at your PROMPT.md @@ -87,50 +87,50 @@ import { CopilotClient } from "@github/copilot-sdk"; type Mode = "plan" | "build"; async function ralphLoop(mode: Mode, maxIterations: number = 50) { - const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; - const client = new CopilotClient(); - await client.start(); + const promptFile = mode === "plan" ? "PROMPT_plan.md" : "PROMPT_build.md"; + const client = new CopilotClient(); + await client.start(); - console.log(`Mode: ${mode} | Prompt: ${promptFile}`); + console.log(`Mode: ${mode} | Prompt: ${promptFile}`); - try { - const prompt = await readFile(promptFile, "utf-8"); + try { + const prompt = await readFile(promptFile, "utf-8"); - for (let i = 1; i <= maxIterations; i++) { - console.log(`\n=== Iteration ${i}/${maxIterations} ===`); + for (let i = 1; i <= maxIterations; i++) { + console.log(`\n=== Iteration ${i}/${maxIterations} ===`); - const session = await client.createSession({ - model: "gpt-5.1-codex-mini", - // Pin the agent to the project directory - workingDirectory: process.cwd(), - // Auto-approve tool calls for unattended operation - onPermissionRequest: async () => ({ allow: true }), - }); + const session = await client.createSession({ + model: "gpt-5.1-codex-mini", + // Pin the agent to the project directory + workingDirectory: process.cwd(), + // Auto-approve tool calls for unattended operation + onPermissionRequest: async () => ({ allow: true }), + }); - // Log tool usage for visibility - session.on((event) => { - if (event.type === "tool.execution_start") { - console.log(` ⚙ ${event.data.toolName}`); - } - }); - - try { - await session.sendAndWait({ prompt }, 600_000); - } finally { - await session.destroy(); - } - - console.log(`Iteration ${i} complete.`); + // Log tool usage for visibility + session.on((event) => { + if (event.type === "tool.execution_start") { + console.log(` ⚙ ${event.data.toolName}`); } - } finally { - await client.stop(); + }); + + try { + await session.sendAndWait({ prompt }, 600_000); + } finally { + await session.destroy(); + } + + console.log(`Iteration ${i} complete.`); } + } finally { + await client.stop(); + } } // Parse CLI args: npx tsx ralph-loop.ts [plan] [max_iterations] const args = process.argv.slice(2); const mode: Mode = args.includes("plan") ? "plan" : "build"; -const maxArg = args.find(a => /^\d+$/.test(a)); +const maxArg = args.find((a) => /^\d+$/.test(a)); const maxIterations = maxArg ? parseInt(maxArg) : 50; ralphLoop(mode, maxIterations); @@ -182,9 +182,9 @@ creating ad-hoc copies. 4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` then `git commit` with a descriptive message. -99999. When authoring documentation, capture the why. -999999. Implement completely. No placeholders or stubs. -9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. ``` ### Example `AGENTS.md` @@ -219,12 +219,14 @@ npm run build ## When to Use a Ralph Loop **Good for:** + - Implementing features from specs with test-driven validation - Large refactors broken into many small tasks - Unattended, long-running development with clear requirements - Any work where backpressure (tests/builds) can verify correctness **Not good for:** + - Tasks requiring human judgment mid-loop - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria diff --git a/cookbook/copilot-sdk/nodejs/recipe/package-lock.json b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json new file mode 100644 index 00000000..47e85e5a --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/recipe/package-lock.json @@ -0,0 +1,629 @@ +{ + "name": "copilot-sdk-cookbook-recipes", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "copilot-sdk-cookbook-recipes", + "version": "1.0.0", + "dependencies": { + "@github/copilot-sdk": "*" + }, + "devDependencies": { + "@types/node": "^22.19.7", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } + }, + "../..": { + "name": "@github/copilot-sdk", + "version": "0.1.8", + "license": "MIT", + "dependencies": { + "@github/copilot": "^0.0.388-1", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.5" + }, + "devDependencies": { + "@types/node": "^22.19.6", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "esbuild": "^0.27.0", + "eslint": "^9.0.0", + "glob": "^11.0.0", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.4.0", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.16" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../../..": {}, + "../../src": {}, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@github/copilot-sdk": { + "resolved": "../../src", + "link": true + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/cookbook/copilot-sdk/python/ralph-loop.md b/cookbook/copilot-sdk/python/ralph-loop.md index 82bfc7f5..b0d1c4b6 100644 --- a/cookbook/copilot-sdk/python/ralph-loop.md +++ b/cookbook/copilot-sdk/python/ralph-loop.md @@ -43,7 +43,7 @@ A [Ralph loop](https://ghuntley.com/ralph/) is an autonomous development workflo ## Simple Version -The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | claude ; done`: +The minimal Ralph loop — the SDK equivalent of `while :; do cat PROMPT.md | copilot ; done`: ```python import asyncio @@ -117,14 +117,17 @@ async def ralph_loop(mode: str = "build", max_iterations: int = 50): # Pin the agent to the project directory working_directory=str(Path.cwd()), # Auto-approve tool calls for unattended operation - on_permission_request=lambda _req, _ctx: {"kind": "approved", "rules": []}, + on_permission_request=lambda _req, _ctx: { + "kind": "approved", "rules": [] + }, )) # Log tool usage for visibility - session.on(lambda event: - print(f" ⚙ {event.data.tool_name}") - if event.type.value == "tool.execution_start" else None - ) + def log_tool_event(event): + if event.type.value == "tool.execution_start": + print(f" ⚙ {event.data.tool_name}") + + session.on(log_tool_event) try: await session.send_and_wait( @@ -193,9 +196,9 @@ creating ad-hoc copies. 4. When tests pass, update IMPLEMENTATION_PLAN.md, then `git add -A` then `git commit` with a descriptive message. -99999. When authoring documentation, capture the why. -999999. Implement completely. No placeholders or stubs. -9999999. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. +5. When authoring documentation, capture the why. +6. Implement completely. No placeholders or stubs. +7. Keep IMPLEMENTATION_PLAN.md current — future iterations depend on it. ``` ### Example `AGENTS.md` @@ -230,12 +233,14 @@ python -m pytest ## When to Use a Ralph Loop **Good for:** + - Implementing features from specs with test-driven validation - Large refactors broken into many small tasks - Unattended, long-running development with clear requirements - Any work where backpressure (tests/builds) can verify correctness **Not good for:** + - Tasks requiring human judgment mid-loop - One-shot operations that don't benefit from iteration - Vague requirements without testable acceptance criteria From 717c0121bc0e6e9c8328338a41479d9125e20d85 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Wed, 11 Feb 2026 15:16:44 -0800 Subject: [PATCH 13/13] PR feedback --- cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs | 1 - cookbook/copilot-sdk/go/ralph-loop.md | 4 ++-- cookbook/copilot-sdk/go/recipe/ralph-loop.go | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs index d0baa2e7..9f153324 100644 --- a/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs +++ b/cookbook/copilot-sdk/dotnet/recipe/ralph-loop.cs @@ -1,5 +1,4 @@ #:package GitHub.Copilot.SDK@* -#:property PublishAot=false using GitHub.Copilot.SDK; diff --git a/cookbook/copilot-sdk/go/ralph-loop.md b/cookbook/copilot-sdk/go/ralph-loop.md index 626ed5ea..f8462c3d 100644 --- a/cookbook/copilot-sdk/go/ralph-loop.md +++ b/cookbook/copilot-sdk/go/ralph-loop.md @@ -157,8 +157,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { // Log tool usage for visibility session.On(func(event copilot.Event) { - if te, ok := event.(copilot.ToolExecutionStartEvent); ok { - fmt.Printf(" ⚙ %s\n", te.Data.ToolName) + if toolExecution, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", toolExecution.Data.ToolName) } }) diff --git a/cookbook/copilot-sdk/go/recipe/ralph-loop.go b/cookbook/copilot-sdk/go/recipe/ralph-loop.go index 35d862b7..03d99987 100644 --- a/cookbook/copilot-sdk/go/recipe/ralph-loop.go +++ b/cookbook/copilot-sdk/go/recipe/ralph-loop.go @@ -71,8 +71,8 @@ func ralphLoop(ctx context.Context, mode string, maxIterations int) error { // Log tool usage for visibility session.On(func(event copilot.Event) { - if te, ok := event.(copilot.ToolExecutionStartEvent); ok { - fmt.Printf(" ⚙ %s\n", te.Data.ToolName) + if toolExecution, ok := event.(copilot.ToolExecutionStartEvent); ok { + fmt.Printf(" ⚙ %s\n", toolExecution.Data.ToolName) } })